Прежде чем приступить к написанию кода Verse для игры Головоломка из лампочек с тегами, стоит подумать о том, как лучше всего осуществить задуманное. В этом разделе мы покажем, какой подход использовать для создания механики головоломки. По завершении этого шага у вас будет псевдокод, который представляет собой алгоритм для создания этой головоломки. В следующем шаге будет показано, как реализовать алгоритм на Verse в UEFN.
Определение целей, требований и ограничений
Первый шаг заключается в том, чтобы определить цели, требования и ограничения. Требования часто основываются на результатах разбивки более масштабных целей на более мелкие.
| Цели |
|
| Требования |
|
| Ограничения |
|
Разбиваем задачу
После того, как вы разобрались с тем, чего вы хотите и с чем работаете, необходимо разбить общую задачу на более мелкие, которые легче будет сформулировать. Для этого следует задаться вопросами:
- Как игрок может взаимодействовать с головоломкой?
- Как использовать теги игрового процесса для поиска лампочек?
- Как определить начальные условия и решения, которые можно будет изменить через редактор?
- Как соотносить состояние игры, хранящееся в структуре Verse, с визуальным отображением в игре?
- Как взаимодействие игрока может менять конкретные комбинации лампочек?
- Как отключить головоломку для игрока после её решения?
Теперь необходимо определить, как могут пересекаться между собой эти мелкие задачи. В данном случае может показаться, что задачи не пересекаются, однако стоит обдумать это ещё раз:
- Вопросы 1, 5 и 6 слабо связаны между собой.
- В вопросах 1 и 6 способ взаимодействия игрока с головоломкой не определяет возможность отключения головоломки после её решения.
- В вопросах 1 и 5 одно взаимодействие включает сразу несколько лампочек. Это позволит определить структуру данных, которую нужно будет использовать для сопоставления взаимодействия с лампочками.
- Вопрос 2 является важным аспектом геймдизайна. То, как работает API тегов игрового процесса, может повлиять на то, как контроль состояния лампочек будет выполняться в самом коде. Это будет влиять на вопросы 4 и 5, так как вам нужно будет изменять состояние лампочек в игре. По этой причине нужно будет найти общий способ реализации данной механики.
- Вопросы 3 и 4, вероятно, должны сходиться к созданию единой базовой структуры данных для начального, текущего и конечного состояний лампочек.
Поиск возможных решений
Мы успешно разбили одну большую задачу на несколько более мелких. Самое время заняться поиском ответов на вопросы, связанные с решением этих мелких задач:
1. Как игрок может взаимодействовать с головоломкой?
Существует несколько решений этой задачи. В целом, вы можете использовать любое устройство, с которым игрок может взаимодействовать и которое Verse может использовать для обнаружения событий взаимодействия. В инструментах творческого режима есть много устройств, отвечающих этим требованиям, например устройства «Триггер», «Кнопки», а также «Меняющие цвет клетки» и «Триггеры восприятия».
В этом примере будет использоваться устройство «Кнопка», в частности, метод InteractedWithEvent, который срабатывает каждый раз, когда игрок взаимодействует с кнопкой, пока кнопка активирована. Более подробно о событиях рассказано в разделе Реализация взаимодействия с устройствами.
2. Как использовать теги игрового процесса для поиска лампочек?
При помощи тегов игрового процесса вы можете получить группы акторов, которым присвоен пользовательский тег, прописанный в коде Verse.
Вы можете использовать функцию GetCreativeObjectsWithTag(), чтобы получить массив всех акторов, которым был присвоен соответствующий пользовательский тег. Функция будет возвращать массив всех объектов, в которых реализован creative_object_interface. Объект customizable_light_device — это написанное на Verse представление устройства «Настраиваемое освещение», которое представляет собой класс, реализующий интерфейс creative_object_interface.
Список устройств, возвращаемый при помощи GetCreativeObjectsWithTag(), не имеет гарантированного порядка своих элементов, а вызов функции с возвратом всех устройств может занять некоторое время (особенно если устройств на уровне много), поэтому хорошей идеей будет сохранить лампочки для быстрого доступа к ним позже. Это называется кэшированием и зачастую помогает повысить производительность. Поскольку коллекция лампочек будет представлять собой коллекцию элементов одного типа, для их хранения можно использовать [массив] (array-in-verse).
Это значит, что вы можете:
- Создать новый тег
puzzle_light. - Назначить всем лампочкам в головоломке тег
puzzle_light. - Задать вызов
GetCreativeObjectsWithTag(puzzle_light), чтобы получить все акторы с тегомpuzzle_light. - Определить, какие результаты вызова функции являются объектом
customizable_light_device. - Сохранить список объектов
customizable_light_deviceв массиве, чтобы иметь к ним доступ в дальнейшем.
3. Как определить начальные условия и решения, которые можно будет изменить через редактор?
Лампочка имеет только два состояния: вкл. и выкл.. В Verse для представления состояния включения/выключения лампочек вы можете использовать тип logic: всё потому, что переменная данного типа может содержать только значение true или false. Поскольку лампочек у нас несколько, для хранения всех значений типа logic также можно использовать массив. При этом позиция в массиве (индекс) для состояния лампочки будет совпадать с индексом самой лампочки, к которой оно относится.
Такой массив значений типа logic может быть использован для определения начального состояния лампочек в головоломке. Он также будет содержать текущее состояние каждой из лампочек во время игры. Вы можете предоставить доступ к массиву для редактора при помощи атрибута @editable. В начале игры лампочки могут быть включены или выключены, чтобы визуально соответствовать состояниям, хранящимся в массиве.
Решение головоломки должно совпадать с типом, используемым для хранения текущего состояния лампочек: это позволит успешно выполнить проверку решения головоломки путём сравнения двух одинаковых типов. Это значит, что у вас будет два редактируемых массива значений типа logic, один из которых представляет текущее состояние лампочек, а другой — решение головоломки. Это значит, что вы можете изменить начальное состояние лампочек головоломки и решение головоломки в редакторе и таким образом повторно использовать головоломку с различными комбинациями начальных состояний и решений.
4. Как соотносить состояние игры, хранящееся в структуре Verse, с визуальным отображением в игре?
Вы можете включать и выключать customizable_light_device в игре при помощи методов TurnOn() и TurnOff(). Поэтому всякий раз, когда вы обновляете текущее состояние лампочек (как представлено массивом логических значений), вам также необходимо вызывать метод TurnOn() или TurnOff(), чтобы привести визуальное отображение внутри игры в соответствие с фактическим состоянием лампочек.
5. Как взаимодействие игрока может менять определённые комбинации лампочек?
По первому вопросу вы уже определили, что игрок будет взаимодействовать с головоломкой посредством устройства «Кнопка». Вы можете подписаться на обработчик события для метода InteractedWithEvent устройства «Кнопка», который будет менять состояние лампочек при взаимодействии игрока с этим устройством. Поскольку игрок может использовать несколько кнопок, здесь снова можно использовать массив, где все кнопки будут храниться вместе.
Теперь следует определить, как сопоставить событие каждой из кнопок с набором переключаемых лампочек.
Поскольку порядок лампочек в массиве customizable_light_device будет таким же, как и в логическом массиве для представления состояния лампочек, вы можете создать определённую связь между кнопкой и индексами лампочек, на которые она будет влиять. Эту связь можно представить в виде массива, где порядок элементов будет соответствовать порядку кнопок, а элементы будут представлять собой массивы индексов.
Вы можете сделать массив редактируемым, чтобы связь кнопок и лампочек можно было изменять непосредственно в редакторе и иметь возможность повторно использовать головоломку без необходимости вносить изменения непосредственно в код.
6. Как отключить возможность взаимодействия для игрока после решения головоломки?
Вы уже знаете, что игрок взаимодействует с головоломкой при помощи устройства «Кнопка», где само взаимодействие обрабатывается при помощи InteractedWithEvent.
Как же сделать так, чтобы после решения головоломки устройство прекращало обрабатывать ввод со стороны игрока и игрок больше не мог изменять головоломку?
Это можно сделать как минимум тремя способами:
- Отключить кнопки внутри игры после решения головоломки.
- Добавить поле
logicкtagged_lights_puzzle, которое изменяется после решения головоломки. Перед любым изменением состояния игры это поле типаlogicпроходит проверку на решение головоломки. - Отписатся от события
InteractedWithEventкнопки после решения головоломки, чтобы обработчики событий больше не вызывались.
Третий вариант является наилучшим, поскольку это простое и эффективное решение. В этом случае вам не нужно создавать новые поля для проверки соблюдения условий в коде. Концепция удаления привязки к событию устройства может быть использована и в других ситуациях. В целом, хорошей практикой является создание подписки на событие в тех ситуациях, когда необходимо получать уведомления о нём, а также отмена этой подписки, когда это более не требуется. Более подробно реализация такого подхода будет разъяснена в одном из последующих разделов обучения.
Комбинирование решений и планирование при помощи псевдокода
Теперь, когда у вас есть решения для более мелких задач, самое время объединить их воедино, чтобы решить исходную задачу. Оформите алгоритм для того, чтобы создать решение при помощи псевдокода.
Что происходит, когда начинается игра? Создаются лампочки. Вы подписываетесь на метод InteractedWithEvent устройства «Кнопка», находите все устройства с тегом puzzle_light tag и кэшируете их. Вы также включаете/выключаете лампочки внутри игры на основе начального состояния LightState.
OnBegin:
Результат GetCreativeObjectsWithTag(puzzle_light) сохраняется в переменной FoundDevices (найденные устройства)
for (для) каждого Устройства в FoundDevices:
if (если) Устройство является Устройством «Настраиваемое освещение»:
Сохранить Лампочку
if (если) ShouldLightBeOn? (Лампочка должна быть включена?):
Включить Лампочку
else:
Отключить Лампочку
for (для) каждой Кнопки:
Подписаться на событие InteractedWithEvent кнопки используя обработчик OnButtonInteractedWith
Псевдокод функции OnButtonInteractedWith может выглядеть следующим образом: InteractedButtonIndex будет относиться к индексу для массива объектов button_device, который будет соответствовать устройству «Кнопка», с которым взаимодействует игрок. Чуть позже вы узнаете, как получить эту информацию внутри обработчика события.
OnButtonInteractedWith:
Get (получить) лампочки связанные с кнопкой взаимодействие с которой производилось через массив ButtonsToLights и сохранить их в переменной Lights (лампочки)
# Переключаем лампочки
for (для) каждой Лампочки в Lights:
if (если) IsLightOn? (Лампочка включена):
Set (установить) Лампочке состояние выкл в игре
Отключить Лампочку
else:
Set (установить) лампочке состояние вкл в игре
Включить Лампочку
if (если) IsPuzzleSolved():
Включить Генератор предметов
for (для) каждой Кнопки:
Отписаться от события кнопки InteractedWithEvent
Псевдокод для функции IsPuzzleSolved будет проверять, соответствует ли текущее состояние лампочек решению. Если текущее состояние не соответствует решению, проверка не проходит, а блок if IsPuzzleSolved из приведённого псевдокода не выполняется. Если текущее состояние совпадает с решением, то проверка проходит успешно и выполняется блок if IsPuzzleSolved.
IsPuzzleSolved:
for (для) каждой Лампочки:
if (если) IsLightOn (Лампочка включена) не равно IsLightOnInSolution (Лампочка включена как в решении)
fail (не выдавать результат) и return (выполнить возврат)
succeed (успешно)
Вы успешно создали свой собственный алгоритм!
Что дальше
Дальше мы реализуем построенный алгоритм на языке Verse и протестируем проект, чтобы проверить его реализацию.