Маркеры целей используются во многих играх, чтобы направлять игрока к следующей цели или точке интереса. В этом уроке вы узнаете, как создать повторно используемый маркер цели с помощью устройства «Указатель на карте» и Verse.
Используемые функции языка Verse
-
struct
: группирует переменные разных типов в структуру. -
Метод расширения: специальный тип функции, который действует как член существующего класса или типа, но не требует создания нового типа или подкласса. В этом уроке вы создадите метод расширения для структуры.
-
Именованный аргумент: аргумент, который передаётся при вызове функции с указанным именем параметра.
Используемые API Verse
-
Prop API: предоставляет методы для перемещения объектов окружения.
-
Editable Properties: редактируемые свойства создают ссылки на устройства и обновляют значения переменных, ускоряя тестирование.
Инструкции
Выполните следующие шаги, чтобы узнать, как настроить одно устройство маркера цели, способное перемещаться между несколькими целями или точками интереса. Для справки в конце данного руководства приведены полные сценарии.
Подготовка уровня
В этом примере используются следующие объекты окружения и устройства.
-
1 объект-здание: объект окружения для перемещения устройства «Указатель на карте».
-
1 устройство «Указатель на карте»: устройство, которое отображает пользовательские маркеры на мини-карте и обзорной карте.
-
1 устройство «Точка появления игрока»: разместите его около объекта окружения, чтобы игрок появился рядом с ним.
Использование Prop API
Первый шаг в перемещении устройства с помощью Verse — это перемещение объекта окружения посредством Prop API. Выполните следующие шаги, чтобы перемещать объект окружения по уровню.
-
Создайте новое устройство Verse с именем objective_coordinator_device.
-
После стандартных выражений
using
в начале файла Verse добавьте выражениеusing
для модуляSpatialMath
. Этот модуль содержит код, на который вы будете ссылаться для перемещения объектов окружения.using { /UnrealEngine.com/Temporary/SpatialMath }
-
Добавьте два редактируемых свойства:
-
Константу
creative_prop
с именемRootProp
для хранения ссылки на движущийся объект окружения. -
Константу
transform
с именемDestination
для хранения местоположения, куда будет перемещаться объект окружения.
objective_coordinator_device<public> := class<concrete>(creative_device): @editable RootProp<public> : creative_prop = creative_prop{} @editable Destination<public> : transform = transform{}
-
-
Если вы запустите этот код и добавите устройство objective_coordinator_device на уровень, оба свойства будут отображаться на панели «Сведения».
-
В сущности, объект окружения перемещается методом
TeleportTo[]
. Вызовите его внутри выраженияif
и используйте квадратные скобки вместо круглых, потому что выражениеTeleportTo[]
имеет неоднозначный результат. Блокif
создаёт контекст, допускающий неоднозначность.if(RootProp.TeleportTo[Destination.Translation, Destination.Rotation]): Print("Объект окружения был перемещён") else: Print("Объект окружения не был перемещён")
-
Аргументами метода
TeleportTo[]
являются Translation (перенос) и Rotation (вращение). Оба они связаны со свойством Destination. -
Вернитесь в редактор и в Каталоге ресурсов добавьте на уровень объект окружения из Fortnite > Galleries > Объекты. В данном руководстве мы используем Coastal Buoy 02B, но отлично подойдёт и любой другой объект из папки «Объекты».
-
Выберите objective coordinator device в окне «Структура». На панели «Сведения» задайте для объекта окружения параметр RootProp. В данном примере для параметра RootProp задано значение Coastal Buoy 02B.
-
На панели «Сведения» разверните параметр Destination. Поскольку константа Destination имеет тип
transform
, она включает в себя параметры Scale (масштаб), Rotation (вращение) и Translation (перенос). Чтобы переместить объект окружения, достаточно изменить параметр Translation, поэтому разверните его. Установите в поле, название которого заканчивается на X, значение 5000,0.При тестировании кода рекомендуется изменять значения существенно, чтобы эффект был заметен. При небольших изменениях вы не узнаете, выполняется ли ваш код должным образом.
using { /Verse.org/Simulation } using { /Fortnite.com/Devices } using { /UnrealEngine.com/Temporary/SpatialMath } objective_coordinator_device<public> := class<concrete>(creative_device): @editable RootProp<public> : creative_prop = creative_prop{} # Место, куда будет перемещён маркер @editable Destination<public> : transform = transform{} OnBegin<override>()<suspends> : void = if(RootProp.TeleportTo[Destination.Translation, Destination.Rotation]): Print("Объект окружения был перемещён") else: Print("Объект окружения не был перемещён")
-
Нажмите Verse > Собрать код Verse > Запуск сеанса. Наконец, нажмите «Запустить игру». Вы увидите, как объект окружения перемещается.
Родительские элементы и struct
Теперь у вас есть объект окружения, который перемещается по уровню. Однако наша конечная цель — это перемещение устройства «Указатель на карте», чтобы игроки могли использовать его в качестве ориентира. Выполните следующие шаги, чтобы добавить на уровень объект-здание и устройство «Указатель на карте», присоединив его к объекту-зданию.
-
Нажмите правой кнопкой мыши в Каталоге ресурсов, чтобы открыть контекстное меню.
-
Выберите Класс Blueprint в контекстном меню.
-
В окне «Выбрать родительский класс» нажмите «Объект-здание».
-
В каталоге ресурсов появится новый класс Blueprint. Переименуйте его в BuildingProp.
-
Добавьте объект-здание на уровень. Поскольку этот объект окружения не имеет сетки, вы увидите лишь его графический ориентир преобразования.
-
В окне «Структура» добавьте устройство «Указатель на карте» на объект-здание. В результате чего объект-здание станет родительским элементом для устройства «Указатель на карте». Теперь при перемещении объекта-здания устройство «Указатель на карте» будет перемещаться вместе с ним.
Вы уже знаете, как создать устройство с помощью Verse, при этом можно создавать файлы Verse, которые не имеют собственных устройств.
-
Создайте новый файл Verse и назовите его objective_marker. Этот файл не создаёт устройство. Вместо этого он будет содержать определение
struct
для созданного ранее устройства Verse. -
Сначала объявите структуру
struct
с именем objective_marker. Она будет содержать два члена:RootProp
иMapIndicator
. Оба должны иметь спецификатор@editable
.objective_marker<public> := struct<concrete>: @editable RootProp<public> : creative_prop = creative_prop{} @editable MapIndicator<public> : map_indicator_device = map_indicator_device{}
Методы расширения и именованные аргументы
Объявите отдельный метод MoveMarker
, который будет перемещать член RootProp
и присоединённое к нему устройство «Указатель на карте». Этот метод отражает две особенности языка: методы расширения и именованные аргументы.
(Marker : objective_marker).MoveMarker<public>(Transform : transform, ?OverTime : float)<suspends> : void =
-
Методы расширения: добавим метод
MoveMarker()
в структуруobjective_marker
. При объявлении метода расширения необходимо указать в круглых скобках идентификатор и тип через двоеточие. В данном случае:(Marker : objective_marker)
. -
Именованные аргументы: во втором аргументе
?OverTime
используется символ?
. Он указывает на то, что аргумент должен быть именован при вызове функцииMoveMarker
. Это позволяет любому разработчику, читающему или пишущему вызовMoveMarker
, понять назначение аргумента типаfloat
.
MoveMarker()
вызывает один из двух методов с помощью Prop API: использованный ранее TeleportTo[]
или MoveTo()
. Создайте блок if..else
для проверки того, что параметр OverTime
больше 0.0
. Если это так, вызовите MoveTo()
. В результате ваша цель переместится в следующее место за указанное вами время, вместо того чтобы телепортироваться мгновенно.
(Marker : objective_marker).MoveMarker<public>(Transform : transform, ?OverTime : float)<suspends> : void =
if (OverTime > 0.0):
Marker.RootProp.MoveTo(Transform.Translation, Transform.Rotation, OverTime)
else:
if:
Marker.RootProp.TeleportTo[Transform.Translation, Transform.Rotation]
Если теперь скомпилировать код, всё должно получиться, но в Каталоге ресурсов в папке CreativeDevices вы не увидите новое устройство. Причина в том, что objective_marker является структурой (struct
), а не классом, который наследует от creative_device
.
Обновление устройства objective coordinator device
Теперь, когда у вас есть новый тип, на который можно ссылаться, необходимо создать ссылку на устройство objective_coordinator_device.
-
Удалите свойство
RootProp
и замените его свойством с именемPickupMarker
типаobjective_marker
. Это тип, который вы создали. -
Для
MoveMarker()
требуется аргумент типаfloat
. Создайте такой аргумент в виде редактируемого свойства с именемMoveTime
. -
Удалите вызов
TeleportTo[]
. Вместо этого вызовите методMoveMarker()
, который вы создали дляobjective_marker
. Ему требуется именованный аргумент?OverTime
.
objective_coordinator_device<public> := class<concrete>(creative_device):
@editable
PickupMarker<public> : objective_marker = objective_marker{}
# Место, куда будет перемещён маркер
@editable
Destination<public> : transform = transform{}
# За какое время маркер должен достичь новое местоположение
@editable
MoveTime<public> : float = 0.0
OnBegin<override>()<suspends> : void =
PickupMarker.MoveMarker(Destination, ?OverTime := MoveTime)
Скомпилируйте этот код и проверьте «Сведения» устройства objective coordinator device. Вы должны увидеть свойства PickupMarker и MoveTime, а свойство PickupMarker должно содержать RootProp и MapIndicator.
-
Выберите в поле RootProp значение BuildingProp, а в поле MapIndicator — значение Устройство «Указатель на карте».
-
Скомпилируйте свой код и нажмите на кнопку «Запуск сеанса». Вы должны увидеть маркер на мини-карте, который начнёт перемещаться вскоре после начала игры. Попробуйте разные значения
MoveTime
, в том числе0.0
. Подумайте, какое перемещение лучше всего подходит для разных сценариев.
GetPlayers() и ActivateObjectivePulse()
Существует способ немного помочь игрокам в достижении следующей цели. Это так называемая пульсация цели. Когда она активна, отображается пунктирная линия от игрока в направлении устройства «Указатель на карте». Выполните следующие шаги, чтобы добавить пульсацию цели к устройству objective coordinator device.

Метод, активирующий пульсацию цели, называется ActivateObjectivePulse()
. Он принимает единственный аргумент типа agent
. Сначала создайте метод, чтобы получить экземпляр типа agent
, представляющий вашего игрового персонажа.
-
Объявите функцию
FindPlayer()
со спецификатором<private>
и возвращаемым значениемvoid
. -
Получите массив всех игроков на уровне с помощью
Self.GetPlayspace().GetPlayers()
. Сохраните массив в переменнойAllPlayers
.FindPlayer<private>() : void = AllPlayers := Self.GetPlayspace().GetPlayers()
-
Чтобы получить ссылку на единственного игрока на уровне, присвойте первый элемент массива его собственной переменной. Обращение к массиву — это выражение с неоднозначным результатом, поэтому поместите его в выражение
if
.if (FirstPlayer := AllPlayers[0]):
-
Поскольку присвоение значения
player
переменной может не дать результат, при ссылке на игрока в коде следует использовать переменную типаoption
. Объявите переменную игрока типа?player
без гарантированного значения. Она должна использоваться совместно с переменными других участников.objective_coordinator_device<public> := class<concrete>(creative_device): var PlayerOpt<private> : ?player = false @editable PickupMarker<public> : objective_marker = objective_marker{} # Место, куда будет перемещён маркер @editable Destination<public> : transform = transform{} # За какое время маркер должен достичь новое местоположение @editable MoveTime<public> : float = 0.0
-
Задайте новую переменную и создайте блок
else
с выражениемPrint()
, которое сообщит вам, если игрок не будет найден. Ваша функцияFindPlayer()
готова.FindPlayer<private>() : void = # Так как игра одиночная, первый игрок имеет индекс [0] и # будет единственным доступным. AllPlayers := Self.GetPlayspace().GetPlayers() if (FirstPlayer := AllPlayers[0]): set PlayerOpt = option{FirstPlayer} Print("Игрок найден") else: # Если не можем найти игрока, регистрируем ошибку. Print("Подходящий игрок не найден")
В функции OnBegin()
нужно внести ещё два изменения:
-
Вызовите функцию
FindPlayer()
.OnBegin<override>()<suspends> : void = FindPlayer()
-
После вызова
MoveMarker()
используйте ещё одно выражениеif
, чтобы задать переменную игрока без гарантированного значения новой переменной. Передайте её в качестве аргумента вPickupMarker.MapIndicator.ActivateObjectivePulse()
if (FoundPlayer := PlayerOpt?): PickupMarker.MapIndicator.ActivateObjectivePulse(FoundPlayer)
Если вы запустите код сейчас, то увидите линию пульсации цели, направленную от персонажа к месту расположения маркера цели на уровне!
Полные сценарии
Objective_marker.verse
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Fortnite.com/Devices/CreativeAnimation }
objective_marker<public> := struct<concrete>:
# Перемещаемый объект окружения
@editable
RootProp<public> : creative_prop = creative_prop{}
# Дочерний объект окружения, который будет перемещаться вместе с ним
@editable
MapIndicator<public> : map_indicator_device = map_indicator_device{}
# Метод расширения для objective_marker
# Символ ? перед OverTime указывает, что это именованный аргумент
(Marker : objective_marker).MoveMarker<public>(Transform : transform, ?OverTime : float)<suspends> : void =
if (OverTime > 0.0):
Marker.RootProp.MoveTo(Transform.Translation, Transform.Rotation, OverTime)
else:
if:
Marker.RootProp.TeleportTo[Transform.Translation, Transform.Rotation]
Objective_coordinator_device.verse
using { /Verse.org/Simulation }
using { /Fortnite.com/Devices }
using { /Fortnite.com/Playspaces }
using { /UnrealEngine.com/Temporary/SpatialMath }
objective_coordinator_device<public> := class<concrete>(creative_device):
var PlayerOpt<private> : ?player = false
@editable
PickupMarker<public> : objective_marker = objective_marker{}
# Место, куда будет перемещён маркер
@editable
Destination<public> : transform = transform{}
# За какое время маркер должен достичь новое местоположение
@editable
MoveTime<public> : float = 0.0
OnBegin<override>()<suspends> : void =
FindPlayer()
PickupMarker.MoveMarker(Destination, ?OverTime := MoveTime)
# Если в Player не передано false, для найденного игрока активируется пульсация цели
if (FoundPlayer := PlayerOpt?):
PickupMarker.MapIndicator.ActivateObjectivePulse(FoundPlayer)
FindPlayer<private>() : void =
# Так как игра одиночная, первый игрок имеет индекс [0] и
# будет единственным доступным.
AllPlayers := Self.GetPlayspace().GetPlayers()
if (FirstPlayer := AllPlayers[0]):
set PlayerOpt = option{FirstPlayer}
Print("Игрок найден")
else:
# Если не можем найти игрока, регистрируем ошибку.
Print("Подходящий игрок не найден")
Самостоятельная работа
Имейте в виду, что написанный вами код перемещения работает для любого объекта окружения. Если сделать перемещаемый объект окружения родительским элементом устройства, то устройство будет перемещаться вместе с ним. Попробуйте перемещать другие объекты окружения и устройства. Подумайте, в каких ещё играх они будут уместны.
Что дальше
Если вы используете это руководство для создания игры с механикой «подбери и доставь», далее вам нужно научиться создавать таймер обратного отсчёта.