После завершения этого раздела обучения на примере игры «Головоломка из лампочек с тегами» вы узнаете, как переключать группу лампочек в зависимости от кнопки, с которой взаимодействует игрок.
Переключение лампочек
Вам будет необходимо создать связь между кнопкой и группой лампочек, которые она должна переключать, когда игрок с ней взаимодействует. Для этого можно сопоставить каждую кнопку с индексами лампочек в массиве Lights.
В данном примере используется следующая связь между кнопками и лампочками:
- кнопка 1 привязана к лампочкам с индексом 0 и 3;
- кнопка 2 привязана к лампочкам с индексами 0, 1 и 2;
- кнопка 3 привязана к лампочкам с индексами 0 и 1;
- кнопка 4 привязана к лампочке с индексом 1.
Данную связь можно представить в виде массива ButtonsToLights, где каждый элемент ButtonsToLights будет представлять собой другой массив, в котором будут содержаться индексы лампочек. В этом случае массив ButtonsToLights будет состоять из элементов типа [][]int, что указывает на то, что ButtonsToLights — это массив из массивов целочисленных значений.
| Индекс | 0 | 1 | 2 | 3 |
| Элемент | array{0, 3} | array{0, 1, 2} | array{0, 1} | array{1} |
Для переключения лампочек выполните следующие действия:
- Создайте массив, состоящий из массивов целочисленных значений, с названием
ButtonsToLightsи инициализируйте его, создав привязку индексов кнопок и лампочек, как описано в таблице выше.ButtonsToLights : [][]int = array{array{0, 3}, array{0, 1, 2}, array{0, 1}, array{1}}Нумерация индексов в массиве начинается с 0, соответственно индекс последнего элемента будет равняться количеству элементов в массиве минус 1. Таким образом, первый элемент массива
ButtonsToLightsбудет иметь индекс 0, а последний — индекс 3. - Добавьте новый метод под названием
ToggleLights()к классуtagged_lights_puzzle. Этот метод будет включать/выключать лампочки в массивеLightsна основе индексов массивов целочисленных значений, передаваемых функции (чтобы соответствовать элементам массиваButtonsToLights), и обновлять элементы с теми же индексами вLightsState.- Добавьте параметр
LightIndices : []intк методуToggleLights()и выведите каждый индекс в журнал выходных данных при помощи выражения сfor.ToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices do: Logger.Print("Переключаю лампочку по индексу: {LightIndex}") - Вызовите метод
ToggleLights()через методOnBegin(), чтобы сразу протестировать его. Для проверки используйте первый элемент массиваButtonsToLights. Поскольку индексация в массиве является выражением с возможной ошибкой, вам будет необходимо обращаться к массиву из контекста, допускающего ошибку. В этом примере для такого контекста используется выражениеif.
OnBegin<override>()<suspends> : void = SetupPuzzleLights() # Используйте первый элемент из ButtonsToLights, чтобы протестировать метод ToggleLights if (LightIndices : []int = ButtonsToLights[0]): ToggleLights(LightIndices) - Добавьте параметр
- Теперь, когда у вас есть индекс
LightIndex, обратитесь по нему к массивамLightsиLightsState, чтобы получить ссылку на устройство «Настраиваемое освещение» и его текущее состояние. Переключение лампочки будет заключаться в следующем: если она включена, то выключаем её, и наоборот. Измените выражение print так, чтобы выводилось обновлённое состояние лампочки.ToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices Light := Lights[LightIndex] IsLightOn := LightsState[LightIndex] do: Logger.Print("Перевожу лампочку с индексом {LightIndex} в состояние {if (IsLightOn?) then "Выкл." else "Вкл."}") - Теперь обновите состояние устройства «Настраиваемое освещение» как в игре, так и в массиве
LightsState.- Вызываем метод
TurnOn()для лампочки, еслиIsLightOnравноfalse, иTurnOff(), еслиIsLightOnравноtrue. Последнее выражение в блоке кода будет представлять собой результат, поэтому использование значенияfalseилиtrueв качестве последнего выражения означает, что это значение будет сохранено в переменнойNewLightState.ToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices Light := Lights[LightIndex] IsLightOn := LightsState[LightIndex] do: Logger.Print("Перевожу лампочку с индексом {LightIndex} в состояние {if (IsLightOn?) then "Выкл." else "Вкл."}") NewLightState := if (IsLightOn?): Light.TurnOff() ложь else: Light.TurnOn() истина - Обновите
LighsStateсоответствующего элемента вLightIndexпри помощи значенияNewLightState. Поскольку обращение к элементам массива по индексу является выражением с неоднозначным результатом, то присвоение значения дляLightsStateдолжно выполняться в контексте, допускающем неоднозначность. В этом примере таким контекстом будет служить выражение сif. В журнале выходных данных отобразите сообщение о том, что состояние было обновлено.ToggleLights(LightIndices : []int) : void = for: LightIndex : LightIndices IsLightOn := LightsState[LightIndex] Light := Lights[LightIndex] do: Logger.Print("Перевожу лампочку с индексом {LightIndex} в состояние {if (IsLightOn?) then "Выкл." else "Вкл."}") NewLightState := if (IsLightOn?): Light.TurnOff() ложь else: Light.TurnOn() истина if (set LightsState[LightIndex] = NewLightState): Logger.Print("Обновлено состояние лампочки с индексом {LightIndex}")
Хорошей практикой также будет вывод соответствующего сообщения в журнале выходных данных при использовании контекста, допускающего ошибку. Если при выполнении кода в таком контексте будет получена ошибка, все изменения, внесённые в этом контексте, будут отменены, как будто их и не было. С помощью сообщения в журнале выходных данных можно перепроверить, изменится ли что-то в случае появления этого сообщения в указанном журнале. Не обязательно полагаться только на внутриигровое состояние при проверке успешности внесённого изменения.
- Вызываем метод
Привязка нажатий кнопок к переключению лампочек
Мы разобрались, как включать и выключать лампочки, поэтому следующий шаг — реализовать это через нажатие кнопок.
Это можно сделать при помощи метода InteractedWithEvent. Более подробно о привязке к событиям рассказывается в разделе Реализация взаимодействия с устройствами.
Метод InteractedWithEvent ожидает обработчика событий с одним параметром InPlayer : agent и типом возвращаемого значения void. Обработчику также необходимо знать, к каким лампочкам подключена кнопка, отправившая сигнал о событии, а также иметь ссылку на устройство Verse с именем tagged_lights_puzzle, чтобы иметь возможность вызвать его метод ToggleLights().
Вы можете собрать всю эту информацию в пользовательский объект, создав новый класс, содержащий индексы и функцию для подписки. Благодаря этому у каждой кнопки будет своё собственное состояние и обработчик событий, представленный созданным классом.
Ниже пошагово описан процесс создания пользовательского объекта для обработки событий:
- Создайте новый класс с названием
button_event_handler. Определение класса должно содержать следующее:- Массив целочисленных значений
Indices. - Поле
tagged_lights_puzzleс именемPuzzleDevice, которое будет служить ссылкой на соответствующее устройство Verse, что позволит вызвать методToggleLights(). - Метод
OnButtonPressed()с параметромInPlayer : agentи типом возвращаемого значенияvoid, который будет вызывать методToggleLights()дляPuzzleDevice.button_event_handler := class(): # Позиции, используемые для обращения к лампочкам, переключаемым по нажатию этой кнопки. Indices : []int # Объект типа tagged_lights_puzzle, создавший обработчик button_event_handler для возможности вызова функций. PuzzleDevice : tagged_lights_puzzle OnButtonPressed(InPlayer : agent) : void = # Даём устройству PuzzleDevice команду переключить лампочки в позициях, к которым привязана эта кнопка. PuzzleDevice.ToggleLights(Indices)
- Массив целочисленных значений
- Создайте редактируемое поле с массивом
button_deviceв классеtagged_lights_puzzleдля хранения ссылок на кнопки, с которыми может взаимодействовать игрок:@editable Buttons : []button_device = array{} - Теперь обновите метод
OnBegin()классаtagged_lights_puzzleтак, чтобы создать экземпляр обработчикаbutton_event_handlerдля каждой кнопки. Обработчику button_event_handler потребуется следующая информация:- Индексы лампочек, связанных с кнопкой, которые можно получить из массива
ButtonsToLightsпо индексуButtonIndex, к которому мы обращаемся в циклеfor. Эта информация может быть представлена как условие фильтрования в выраженииfor, используемом для итерации по всем кнопкамButtons. Она также позволяет обезопасить код от индексирования недействительных данных. Если индексирование завершиться с ошибкой, программа всё равно будет работать, поскольку неудачная итерация будет пропущена (это может произойти, если вы забыли сопоставить количество элементов вButtonsToLightsс количеством кнопокButtons). - Ссылка на ваше устройство Verse, экземпляр
tagged_lights_puzzle. Чтобы получить ссылку на текущий объект внутри определения класса, вы можете использоватьSelf. - Созданный объект
button_event_handlerс этими данными будет выглядеть следующим образом:OnBegin<override>()<suspends> : void = SetupPuzzleLights() for: ButtonIndex -> Button : Buttons LightIndices := ButtonsToLights[ButtonIndex] do: button_event_handler{Indices := LightIndices, PuzzleDevice := Self}
- Индексы лампочек, связанных с кнопкой, которые можно получить из массива
- Теперь вы можете использовать метод
OnButtonPressed()нового обработчика, чтобы подписаться на методInteractedWithEventустройства Button. Когда вызывается методOnButtonPressed(), вы получаете доступ к индексам лампочек и ссылке на устройствоtagged_lights_puzzle, связанное с кнопкой, с которой взаимодействует игрок.OnBegin<override>()<suspends> : void = SetupPuzzleLights() for: ButtonIndex -> Button : Buttons LightIndices := ButtonsToLights[ButtonIndex] do: Button.InteractedWithEvent.Subscribe(button_event_handler{Indices := LightIndices, PuzzleDevice := Self}.OnButtonPressed) - Сохраните сценарий в Visual Studio Code.
- Чтобы обновить устройство Verse на уровне с помощью написанного кода, нажмите Создать сценарии Verse на панели инструментов UEFN.
- Чтобы открыть панель Сведения устройства, выберите устройство tagged_lights_puzzle в разделе Окно сборки.
- На панели «Сведения» добавьте четыре элемента в массив
Buttonsи назначьте для каждого из них свою кнопку. Вам не нужно изменять другие свойства, так как сценарий сам всё заполнит. - На панели инструментов UEFN нажмите Играть, чтобы протестировать уровень.
Теперь у вас есть возможность взаимодействовать с кнопками, при этом каждая кнопка должна переключать различные комбинации лампочек в соответствии с тем, что содержится в ButtonsLightsIndices. Обратите внимание: GetCreativeObjectsWithTag() не возвращает элементы лишь в каком-то определённом порядке, поэтому лампочки, которые переключаются с учётом порядка в сценарии, могут не соответствовать порядку, который вы увидите на уровне.

Что дальше
Дальше вы узнаете, как отследить решение головоломки игроком, чтобы можно было создать предмет и остановить взаимодействие с этой головоломкой.