В последнем шаге обучения на примере игры Головоломка из лампочек с тегами вы найдёте полный сценарий головоломки и идеи для самостоятельной работы над примером.
Полный сценарий
Ниже представлен полный сценарий для головоломки, которую можно использовать повторно и которая требует от игрока найти правильную комбинацию горящих лампочек за счёт переключения их состояния с помощью кнопок.
using { /Fortnite.com/Devices }
using { /Verse.org/Native }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation/Tags }
using { /Verse.org/Simulation }
# Наследуется от класса `tag` из модуля Verse.org/Simulation/Tags для создания нового тега игрового процесса.
puzzle_light := class(tag){}
log_tagged_lights_puzzle := class(log_channel){}
<#
Вы можете использовать экземпляр этого класса для подписки на
событие InteractedWithEvent устройства button_device, чтобы получить доступ к свойствам каждого события (в данном случае Indices и PuzzleDevice) в обработчике событий.
Представьте, что у каждой кнопки в данном случае будет своё состояние и свой обработчик событий.
Поскольку класс не имеет спецификатора <concrete>, указывать значения по умолчанию для полей не требуется.
#>
button_event_handler := class():
# Позиции, используемые для обращения к лампочкам, переключаемым по нажатию этой кнопки.
Indices : []int
# Объект типа tagged_lights_puzzle, создавший обработчик button_event_handler для возможности вызова функций.
PuzzleDevice : tagged_lights_puzzle
OnButtonPressed(InPlayer : agent) : void =
# Даём устройству PuzzleDevice команду переключить лампочки в позициях, к которым привязана эта кнопка.
PuzzleDevice.ToggleLights(Indices)
tagged_lights_puzzle := class<concrete>(creative_device):
Logger : log = log{Channel := log_tagged_lights_puzzle}
# Кнопки, которые игрок использует для взаимодействия с головоломкой.
@editable
Buttons : []button_device = array{}
<#
Количество элементов в ButtonsToLights должно соответствовать количеству элементов в Buttons.
Каждый массив индексов с нулевым индексом описывает, какие лампочки будет переключать кнопка из массива Buttons[x] (где x — индекс кнопки).
Например, Buttons[2] переключает первую и вторую лампочку, согласно array{0,1}.
#>
ButtonsToLights : [][]int = array{array{0, 3}, array{0, 1, 2}, array{0, 1}, array{1}}
<#
Массив LightsState с логическими значениями отслеживает включённое-выключенное состояние всех лампочек.
Он также задаёт начальное состояние лампочек, поэтому количество элементов в нём
должно соответствовать количеству лампочек с тегом puzzle_light.
#>
@editable
var LightsState : []logic = array{false, false, false, false}
<#
Головоломка будет решена, когда LightsState совпадёт с SolvedLightsState.
Это означает, что в SolvedLightsState должно быть столько же элементов, сколько и в LightsState.
#>
@editable
SolvedLightsState : []logic = array{true, true, true, true}
@editable
# Генератор предметов (ItemSpawner) активируется при решении головоломки.
ItemSpawner : item_spawner_device = item_spawner_device{}
# Хранит лампочки, найденные по тегам игрового процесса.
var Lights : []customizable_light_device = array{}
# Хранит обработчики событий InteractedWithEvent каждой кнопки, чтобы отписаться от события после успешного решения головоломки.
var ButtonSubscriptions : []cancelable = array{}
OnBegin<override>()<suspends> : void =
SetupPuzzleLights()
<#
Для каждой кнопки и её индекса при наличии допустимых значений LightsIndices
создаём новый обработчик button_event_handler с индексами Indices, которые он использует, и ссылкой на tagged_light_puzzle (Self).
Используем метод OnButtonPressed обработчика для подписки на событие InteractedWithEvent кнопки.
Сохраняем эти подписки в массиве ButtonSubscriptions для последующей отписки, чтобы отключить взаимодействие с головоломкой после её решения.
#>
set ButtonSubscriptions = for:
ButtonIndex -> Button : Buttons
LightIndices := ButtonsToLights[ButtonIndex]
do:
Button.InteractedWithEvent.Subscribe(button_event_handler{Indices := LightIndices, PuzzleDevice := Self}.OnButtonPressed)
SetupPuzzleLights() : void =
# Имейте в виду, что при получении через теги игрового процесса устройства возвращаются в случайном порядке.
TaggedActors := GetCreativeObjectsWithTag(puzzle_light{})
<#
Проверяем, относится ли каждое устройство с тегом puzzle_light к типу customizable_light_device путём приведения типов.
Если это так, получаем его начальное состояние LightState, чтобы включить (TurnOn()) или выключить (TurnOff()) устройство LightDevice.
Сохраняем все объекты customizable_light_device с тегом в массив Lights.
#>
set Lights = for:
ActorIndex -> TaggedActor : TaggedActors
LightDevice := customizable_light_device[TaggedActor]
ShouldLightBeOn := LightsState[ActorIndex]
do:
Logger.Print("Добавляю лампочку с индексом {ActorIndex} и состоянием:{if (ShouldLightBeOn?) then "Вкл." else "Выкл."}")
if (ShouldLightBeOn?) then LightDevice.TurnOn() else LightDevice.TurnOff()
LightDevice # Последнее выражение в блоке — это его результат. Выражения с `for` возвращают все значения в массиве.
ToggleLights(LightIndices : []int) : void =
<#
Для каждого Index получаем соответствующее состояние LightState и ссылку на устройство Light.
Если они оба действительны, определяем, включена ли лампочка (IsLightOn), по текущему состоянию LightState (если включена->выключаем и наоборот), после чего
для соответствующего LighsState[Index] выставляем IsLightOn.
#>
for:
LightIndex : LightIndices
IsLightOn := LightsState[LightIndex]
Light := Lights[LightIndex]
do:
<#
Чтобы инвертировать булево значение в Verse: if (MyLogic?) {false} else {true}
Мы также включаем/выключаем лампочку Light (при помощи TurnOff()/TurnOn()) перед возвратом инвертированного значения.
#>
Logger.Print("Перевожу лампочку с индексом {LightIndex} в положение {if (IsLightOn?) then "Выкл." else "Вкл."}")
NewLightState :=
if (IsLightOn?):
Light.TurnOff()
ложь
else:
Light.TurnOn()
истина
if (set LightsState[Index] = NewLightState): # Индексация массива может не вернуть результат, поэтому она должна выполняться внутри контекста, допускающего неоднозначность.
Logger.Print("Обновлено состояние лампочки с индексом {LightIndex}")
# Значение LightsState могло измениться, поэтому проверяем, решена ли головоломка.
if (IsPuzzleSolved[]):
Logger.Print("Головоломка решена!")
ItemSpawner.Enable()
# Отменяем подписки на все обработчики событий, чтобы игрок больше не мог переключать лампочки.
for (ButtonSubscription : ButtonSubscriptions):
ButtonSubscription.Cancel()
<#
Функция со спецификатором <decides> может использоваться только в контексте, допускающем неоднозначность, к примеру в конструкции с if (IsPuzzleSolved[]).
Обратите внимание, что функция с неоднозначным результатом вызывается с помощью [], а не ().
#>
IsPuzzleSolved()<decides><transacts> : void =
<#
Перебираем массив LightsState и проверяем на соответствие с SolvedLightsState.
При первом же несоответствии внутри блока с for функция останавливается, и
происходит возврат к вызывающему коду.
В противном случае функция выполняется до конца.
#>
for:
LightIndex -> IsLightOn : LightsState
IsLightOnInSolution := SolvedLightsState[LightIndex]
do:
IsLightOn = IsLightOnInSolution
Самостоятельная работа
В этом уроке вы узнали, как создать на Verse головоломку с возможностью повторного использования, в которой требуется подобрать правильную комбинацию включённых лампочек, переключая их с помощью кнопок.
Используя полученные знания, попробуйте сделать следующее:
- Создайте больше тегов и используйте их, чтобы управлять переключением лампочек в заданном порядке.
- Создайте похожие головоломки, изменив начальные условия и решения, а также добавив больше кнопок и лампочек.
- Используйте другие типы управляющих и управляемых устройств.