Персонаж в этом примере использует определение персонажа пользовательского типа, так как ему нужно просто передвигаться, без доступа к API охранника или диких животных. Поведение персонажа реализуется с помощью пользовательского поведения в Verse с названием verse_commander_character.
Охранники — это неигровые персонажи, которые могут двигаться по указанному пути и стать враждебными, чтобы атаковать вражеских игроков. Дикие животные — это, например, курица или кабан, которые также могут передвигаться по указанному пути и атаковать вражеских игроков.
Чтобы начать создавать пользовательского NPC, создайте новое поведение NPC, назвав его verse_commander_character с помощью проводника Verse. О том, как создать пользовательское поведение NPC, рассказано в разделе «Создание пользовательского поведения неигрового персонажа».
Персонаж должен обладать следующими свойствами и возможностями:
CommandWaitTime: время ожидания между командами.
FocusTime: продолжительность удержания цели в фокусе. Повороты персонажа влево или вправо обеспечиваются с помощью
focus_interfaceперсонажа. При этом он удерживает взгляд на конкретной точке слева или справа. Так как фокусировка на цели не прекращается до тех пор, пока её не прервать, здесь задаётся очень низкое значение — время, которого должно быть достаточно для поворота персонажа в сторону наблюдения.ReachRadius: насколько близко персонаж должен подойти к цели навигации, чтобы считалось, что она достигнута.
VerseCommanderMinigame: это ссылка на VerseCommanderMinigame на уровне, которая позволяет персонажу прослушивать поступающие оттуда команды.
Ссылки на визуальные эффекты и стрелки: это ссылки на различные визуальные эффекты при телепортации в точку и из неё, а также на объект «Стрелка вперёд», помогающий определить ориентацию персонажа в пространстве.
Verse# A Verse-authored NPC Behavior that can be used within an NPC Definition or a Character Spawner device's Behavior Script Override. verse_commander_character<public> := class(npc_behavior): # The VFX that play when the NPC teleports out. @editable CharacterTeleportOutVFX:vfx_spawner_device = vfx_spawner_device{} # The VFX that play when the NPC teleports in. @editable CharacterTeleportInVFX:vfx_spawner_device = vfx_spawner_device{}Теперь, когда определены свойства персонажа, давайте определим варианты поведения и управляющие ими функции.
Движение персонажа
В этой игре у персонажа предусмотрены следующие варианты поведения:
Вперёд: команда Вперёд заставляет персонажа переместиться вперёд на 1 плитку на игровой доске.
Повернуть направо или Повернуть налево: команды Повернуть направо и Повернуть налево заставляют персонажа повернуться на 90 градусов, чтобы он смотрел в правую или левую сторону соответственно. При этом персонаж не сходит с плитки, на которой стоит.
Сброс: при активации команды Сброс персонаж телепортируется обратно в начальное положение на игровой доске.
Ожидание команд: поскольку движением персонажа невозможно управлять напрямую, он должен ожидать и выполнять команды, поступающие от устройства VerseCommanderMinigame на уровне. После выполнения всех команд он остановится и будет ожидать новых команд.
У NPC в этом шаблоне есть лишь несколько настроек: он может двигаться вперёд по направлению взгляда по одной плитке, а также поворачивать направо или налево. Каждая из этих настроек выполняется с помощью функции GetNavTarget(), создающей новую цель навигации для персонажа, удалённую на одну плитку TileDistance. Эта цель располагается перед самим персонажем, справа или слева от него, в зависимости от конкретной команды: «вперёд», «направо» или «налево».
# Gets a new navigation target for the NPC based on the current transform and the given command.
GetNavTarget(CurrentTransform:transform, Command:command, TileDistance:vector3):transform=
# Based on the command, get the character's local forward, right, or left (negative right).
Direction :=
if (Command = Commands.Forward):
CurrentTransform.Rotation.GetLocalForward()
else if (Command = Commands.TurnRight):
CurrentTransform.Rotation.GetLocalRight()
else if (Command = Commands.TurnLeft):
-CurrentTransform.Rotation.GetLocalRight()
Когда NPC получает сигнал Execute, то он перебирает список полученных команд и передаёт каждую из них в функцию ExecuteCommand(). Сначала он получает интерфейс персонажа focus_interface и navigatable, затем выполняет различные действия с учётом команды. Для каждой из команд «вперёд», «направо» и «налево» вызывается GetNavTarget() для поиска нового преобразования, которое будет использовать NPC. Затем он либо переходит вперёд к новому преобразованию, используя NavigateTo() из интерфейса navigatable, либо использует focus_interface для фокусировки на цели справа или слева.
# Executes the given command, either moving the NPC forward one tile or turning them left
# or right.
ExecuteCommand(Command:command, TileSize:vector3)<suspends>:void=
if:
# Get the Agent (the NPC).
Agent := GetAgent[]
# Gets the Fortnite Character interface, which gets you access to its gameplay data
# including its AI module for navigation and focus.
Character := Agent.GetFortCharacter[]
Визуальные эффекты персонажа
Когда персонаж двигается по игровой доске, объект «Стрелка» показывает его положение и ориентацию, чтобы легче было за ним следить, наблюдая через камеру с видом сверху. Эта стрелка должна следовать за персонажем и обновлять своё положение при его поворотах и перемещении. Функция MoveArrow() обновляет положение стрелки, чтобы она соответствовала положению персонажа, путём копирования его положения и ориентации. Функция CreateArrow() создаёт объект «Стрелка» и сначала вызывает MoveArrow(), чтобы сразу можно было увидеть местоположение персонажа.
# Creates an arrow prop at the NPC's position that visually shows the orientation of the NPC.
CreateArrow(Agent:agent):void=
if :
Character := Agent.GetFortCharacter[]
then:
var Transform:transform = Character.GetTransform()
# Spawn the arrow prop, then set the mesh and material for the prop.
SpawnPropResult := SpawnProp(ForwardArrowAsset, Transform)
if:
SpawnedProp := SpawnPropResult(0)?
Когда персонаж появляется на доске, перемещается на новую доску или сбрасывается до начальной позиции на доске при помощи команды сброса, анимация телепортации воспроизводится и для появления при телепортации, и при исчезновении при телепортации. Для создания эффекта телепортации сначала вызываем Hide() на персонаже и стрелке, а затем воспроизводим TeleportOutVFX, перемещая генератор визуальных эффектов в место нахождения персонажа и активируя его там. При завершении визуального эффекта исчезновения при телепортации нужно телепортировать персонажа в новое положение и воспроизвести там эффект TeleportInVFX. Когда всё будет выполнено, можно будет вызвать Show() на персонаже и объекте «Стрелка», чтобы персонаж появился в новом положении.
# Hides the NPC and the arrow prop, then teleports both to a new position,
# playing VFX for teleporting in and teleporting out.
PlayVFXAndMoveCharacter(StartPosition:transform)<suspends>:void=
if:
Agent := GetAgent[]
FortCharacter := Agent.GetFortCharacter[]
then:
# Hide the NPC and the arrow.
FortCharacter.Hide()
ForwardArrow.Hide()
Телепортация персонажа выполняется вспомогательной функцией MoveToTile(), которая выбирает преобразование, в которое переместится персонаж, и телепортирует его туда. К значению Z в преобразовании добавляется небольшое смещение, чтобы персонаж не проваливался сквозь пол.
# Teleports the NPC to the given transform.
MoveToTile(Transform:transform)<transacts><decides>:void=
# Get the Agent (the NPC).
Agent := GetAgent[]
# Gets the Fortnite Character interface, which gets you access to its gameplay data
# including its AI module for navigation and focus.
Character := Agent.GetFortCharacter[]
var NewTransform:transform = Transform
Обработка команд
Если персонаж на доске бездействует, ему нужно сесть и прослушивать сигнал Execute, чтобы знать, что делать дальше. Это происходит в функции AwaitCommands(). Эта функция имеет спецификатор suspends, поэтому она может выполняться асинхронно, так как персонаж должен ожидать (Await()) событие ExecuteCommandsEvent. Так как команды поступают в виде кортежа, содержащего массив команд и TileSize, используемый для таких команд, каждая из них должна быть обработана в цикле for путём вызова ExecuteCommand(). При выполнении каждой команды мы скрываем стрелку «вперёд» и показываем её повторно только по окончании выполнения команды. Когда выполнение всех команд завершится, уведомляем Verse Commander Minigame о том, что выполнение команд закончилось и можно подавать новые.
# Waits for commands to be sent from the verse_commander_minigame, then
# executes each command.
AwaitCommands()<suspends>:void=
if:
Agent := GetAgent[]
then:
# Wait for commands to be sent from the verse commander minigame.
ExecuteResult := VerseCommanderMinigame.ExecuteCommandsEvent.Await()
# For each execute result tuple, execute the command and pass the tile size from the tuple.
Вместо обработки новых команд персонажа также можно вернуть на начало текущей игровой доски, используя кнопку сброса. Поскольку сброс выполняется сразу и не требует использования очереди команд, персонажу нужно прослушивать её отдельно от сигнала Execute. Это происходит в функцииAwaitReset(), которая ожидает, когда BoardResetEvent подаст сигнал из Verse Commander Minigame. Когда это произойдёт, она вызывает PlayVFXAndMoveCharacter(), чтобы переместить персонажа в начальное положение на доске.
# Waits for the current board to be reset, then moves the
# NPC back to the starting position of the board along with VFX.
AwaitReset()<suspends>:void=
# Wait for the current board to be reset.
# The event payload is the starting position for the board.
StartPosition := VerseCommanderMinigame.BoardResetEvent.Await()
spawn{PlayVFXAndMoveCharacter(StartPosition)}Запуск игрового цикла персонажа
Итак, различные функции, обрабатывающие команды, настроены и пора создать основной игровой цикл персонажа. Персонаж должен постоянно прослушивать сигнал Execute для обработки списка команд либо сигнал Reset для сброса на начало доски. Поскольку ожидание сигналов Execute и Reset должно происходить асинхронно и может возникать по несколько раз на каждой доске, понадобится отдельная вспомогательная функция, обрабатывающая циклы для каждого из них. Это обеспечивает функция CharacterCommandLoop(), которая запускает основной игровой цикл для персонажа. В выражении race она обрабатывает функцию AwaitReset() и цикл, который непрерывно вызывает AwaitCommands(), чтобы персонаж постоянно прослушивал команды.
# Race between resetting the character to start of the board and awaiting commands for that character.
CharacterCommandLoop()<suspends>:void=
race:
AwaitReset()
loop:
AwaitCommands()Когда начнётся игра, персонаж на уровне не появится до тех пор, пока он не будет создан генератором NPC. То есть, когда он появится, ему нужно будет найти Verse Commander Minigame на уровне, поскольку у него нет ссылки на неё. Для этого он использует GetCreativeObjectsWithTag() для поиска объекта с тегом игрового процесса verse_commander_minigame_tag и устанавливает его в качестве VerseCommanderMinigame. При создании своего режима мини-игры не забудьте установить теги, чтобы персонажи на уровне смогли найти объекты, с которыми они должны связываться.
После обнаружения Verse Commander Minigame персонажу нужно создать стрелку «вперёд», которая будет следовать за ним с помощью CreateArrow(). Для запуска игрового цикла необходимо в цикле выполнять функцию CharacterCommandLoop(), чтобы перезапустить её при появлении сигнала Reset. Это также должно происходить в выражении race для GameEndedEvent из Verse Commander Minigame, так как если игра завершится, персонажу нужно будет сразу прекратить свои действия.
# This function runs when the NPC is spawned in the world and ready to follow a behavior.
OnBegin<override>()<suspends>:void=
# Get the Verse Commander Minigame Device.
# Assumption is that there is only one device in the level.
CreativeObjects := GetCreativeObjectsWithTag(verse_commander_minigame_tag{})
if:
CreativeObject := CreativeObjects[0]
MinigameManager := verse_commander_minigame[CreativeObject]
then:
Следующий шаг
Мы определили пользовательского NPC, который принимает данные команд от устройства Verse и использует их для перемещения по игровой доске. Полный код для создания пользовательского персонажа приведён в последнем шаге 7. Окончательный результат.
В следующем шаге вы узнаете, как создавать доску для персонажа, чтобы он мог перемещаться по ней и решать головоломки.