Невидимость захватчиков создаёт интересную проблему, когда игрок хватает цель, принадлежащую защитникам. Как защитники могут обнаружить невидимого игрока, который, возможно, бежит назад на базу с захваченной целью? Чтобы решить эту проблему, можно добавить визуальные подсказки — в нашем случае этот будет объект окружения — чтобы показать защитникам, где находится захватчик.
Следуйте инструкциям ниже, чтобы узнать, как создать объект, который будет парить над головой игрока, удерживающего цель.
Создание диспетчера захвата предметов
- Создайте в Проводнике Verse новое устройство Verse с именем item_capture_manager и перетащите его на уровень.
- В начале файла
item_capture_manager
:- Добавьте
using { /UnrealEngine.com/Temporary/SpatialMath }
, чтобы обращаться к структуреvector3
. Это позволит определять, куда телепортировать указатель, парящий над головой игрока. Также добавьте строкуusing { /Fortnite.com/Characters }
, чтобы можно было обращаться кfort_character
игрока.using { /Fortnite.com/Devices } using { /Verse.org/Simulation } using { /Fortnite.com/Characters } using { /UnrealEngine.com/Temporary/Diagnostics } using { /UnrealEngine.com/Temporary/SpatialMath }
- Добавьте
- В определении класса
item_capture_manager
добавьте следующие поля:- Редактируемый массив устройств «Генератор предметов для захвата» с именем
CaptureItemSpawners
. Этот массив будет содержать устройство «Генератор предметов для захвата» для захватчиков.item_capture_manager := class(creative_device): Logger:log = log{Channel := triad_item_capture_log_channel} # Генератор предмета для захвата, создающий предмет для захвата. @editable CaptureItemSpawner:capture_item_spawner_device = capture_item_spawner_device{}
- Редактируемый объект окружения творческого режима
CaptureItemIndicator
. Это объект окружения, который будет парить над головой захватчика, когда он схватит цель.# Генератор предмета для захвата, создающий предмет для захвата. @editable CaptureItemSpawner:capture_item_spawner_device = capture_item_spawner_device{} # Объект окружения, который парит над головой игрока, удерживающего предмет из # CaptureItemSpawner. @editable CaptureItemIndicator:creative_prop = creative_prop{}
- Редактируемое устройство «Указатель на карте»
MapIndicator
. Оно будет находиться под CaptureItemSpawner на уровне и показывать на карте, где находятся цели каждой команды.# Объект окружения, который парит над головой игрока, удерживающего предмет из # CaptureItemSpawner. @editable CaptureItemIndicator:creative_prop = creative_prop{} # Указатель, показывающий на карте, где находятся цели каждой команды @editable MapIndicator:map_indicator_device = map_indicator_device{}
- Два редактируемых переменных типа
float
:UpdateRateSeconds
иVerticalOffset
. Первая будет определять период обновления положенияCaptureItemIndicator
, а вторая — расстояние до объектаCaptureItemIndicator
над головой игрока.# Указатель, показывающий на карте, где находятся цели каждой команды @editable MapIndicator:map_indicator_device = map_indicator_device{} # Как часто обновляется положение CaptureItemIndicator. @editable UpdateRateSeconds:float = 0.15 # Как высоко над головой игрока парит CaptureItemIndicator. @editable VerticalOffset:float = 180.0
- Редактируемое устройство для вывода сообщений в интерфейсе
ItemGrabbedMessageDevice
. Передаёт каждому игроку сообщение при подборе цели.# Как высоко над головой игрока парит CaptureItemIndicator. @editable VerticalOffset:float = 180.0 # Отображает сообщение, когда игрок хватает предмет для захвата. @editable ItemGrabbedMessageDevice:hud_message_device = hud_message_device{}
- Редактируемое устройство «Панель управления счётом»
ScoreManagerDevice
. Начисляет команде очки, когда игрок захватывает цель# Отображает сообщение, когда игрок хватает предмет для захвата. @editable ItemGrabbedMessageDevice:hud_message_device = hud_message_device{} # Начислить очки игроку, захватившему предмет для захвата. @editable ScoreManagerDevice:score_manager_device = score_manager_device{}
- Редактируемая переменная с плавающей запятой
ReturnTime
. Если у предмета для захвата есть время возврата, по истечении которого он должен вернуться в CaptureItemSpawner, нужно отслеживать это время, чтобы знать, когда возвращать указатели в CaptureItemSpawner.
- Редактируемый массив устройств «Генератор предметов для захвата» с именем
- В определении класса
item_capture_manager
добавьте новый методFollowCharacter()
. Этот метод содержитfort_character
и отслеживает игрока с помощью указателей над его головой. Добавьте к этой функции спецификатор<suspends>
, потому что каждый раз, когда игрок берёт в руки цель, нужно создавать для него отдельный экземпляр функции.# Заставляет CaptureItemIndicator постоянно следовать за игроком над его головой. # Конкурентное выполнение цикла обновления CaptureItemIndictator и события # захвата предмета игроком, сброса предмета или устранения игрока. FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Порождён экземпляр функции FollowCharacter")
Организация кода с использованием выражения race, когда игрок удерживает цель
Важно решить, что будет происходить, когда игрок хватает цель. Игрок может:
- Перемещаться. Здесь пригодятся указатель CaptureItem и указатель на карте, с помощью которых можно отслеживать местоположение игрока. Такое поведение можно реализовать с помощью цикла.
- Захватить цель. В этом случае нужно вернуть указатели в CaptureItemSpawner так, чтобы они находились вне поля зрения, поскольку они не должны быть видны, если только игрок не удерживает предмет для захвата.
- Бросить цель или быть устранённым. В этом случае указатели должны оставаться там, где был брошен предмет, и вернуться в CaptureItemSpawner, когда вернётся предмет для захвата.
Для реализации требуемого поведения будем использовать выражение race. Поместив выражение race
между тремя вышеприведёнными условиями, вы сможете обновлять местоположение указателей, пока будете ожидать сброс или захват цели игроком.
- Добавьте выражение
race
вFollowCharacter()
. Это выражение должно запускаться между выражениемloop
иAwait()
для событияItemCapturedEvent
устройстваCaptureItemSpawner
,Await()
для событияItemCapturedDroppedEvent
устройстваCaptureItemSpawner
иAwait()
для событияEliminatedEvent()
персонажаFortCharacter
.FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Порождён экземпляр функции FollowCharacter") race: loop: CaptureItemSpawner.ItemCapturedEvent.Await() CaptureItemSpawner.ItemDroppedEvent.Await() FortCharacter.EliminatedEvent().Await()
- Получите местоположение
FortCharacter
вloop
и сохраните его в переменнойTransform
.loop: Transform := FortCharacter.GetTransform()
- Теперь создайте экземпляр
MoveTo()
, чтобы переместитьCaptureItemIndicator
иMapIndicator
в соответствии с переносом и поворотом, заданными вTransform
, прибавив установленное ранее смещение по вертикалиVerticalOffset
, в течение времениUpdateRateSeconds
. Необходимо применитьSpawn{}
к обеим функциямMoveTo()
, потому чтоCaptureItemIndicator
иMapIndicator
должны перемещаться одновременно, а не дожидаться завершения выполнения выражения для каждого другого указателя. Поскольку перенос — этоvector3
, состоящий из координатX
,Y
иZ
, нужно записатьVerticalOffset
в новую переменнуюvector3
. Так какVerticalOffset
— это расстояние по вертикали над головой игрока, задайте для него значениеZ
изvector3
.loop: Transform := FortCharacter.GetTransform() spawn{CaptureItemIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)}
- Наконец, запрограммируйте ожидание в течение
0.0
секунд. Это гарантирует, что цикл будет выполнен только раз за одно обновление симуляции и не выйдет из-под контроля, создавая новые функцииMoveTo()
. Теперь ваш кодFollowCharacter()
должен выглядеть так:# Заставляет CaptureItemIndicator постоянно следовать за игроком над его головой. # Конкурентное выполнение цикла обновления CaptureItemIndictator и события # захвата предмета игроком, сброса предмета или устранения игрока. FollowCharacter(FortCharacter:fort_character)<suspends>:void= Logger.Print("Порождён экземпляр функции FollowCharacter") race: loop: Transform := FortCharacter.GetTransform() spawn{CaptureItemIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(Transform.Translation + vector3{Z := VerticalOffset}, Transform.Rotation, UpdateRateSeconds)} # Этот цикл должен выполняться только раз за одно обновление симуляции, поэтому ожидаем в течение одного игрового такта. Sleep(0.0) CaptureItemSpawner.ItemCapturedEvent.Await() CaptureItemSpawner.ItemDroppedEvent.Await() FortCharacter.EliminatedEvent().Await() Logger.Print("Цель сброшена или захвачена")
Сброс указателей
- Когда предмет для захвата захватывается или возвращается, нужно вернуть указатели в
CaptureItemSpawner
где-нибудь вне поля зрения. Поэтому телепортируйте их в место высоко надCaptureItemSpawner
. Для этого добавьте функциюReturnIndicators()
в определение классаitem_capture_manager
.# Возвращает указатель на карте и указатель предмета для захвата в начальные местоположения над генераторами. ReturnIndicators(InAgent:agent):void=
- Получите преобразование
CaptureItemSpawner
и сохраните его в переменнойSpawnerTransform
. Затем создайте экземплярыMoveTo()
дляCaptureItemIndicator
иMapIndicator
, чтобы переместить их в соответствии с преобразованием и поворотомCaptureItemSpawner
, добавив смещениеVerticalOffset
таким же образом, как в циклеloop
, чтобы поместить их надCaptuerItemSpawnwer
. Если вы хотите, чтобы объект окружения оставался вне зоны видимости, можно умножитьVerticalOffset
на большое число, в данном случае 10. Полный методReturnIndicators()
должен выглядеть следующим образом:# Возвращает указатель на карте и указатель предмета для захвата в начальные местоположения над генераторами. ReturnIndicators():void= SpawnerTransform := CaptureItemSpawner.GetTransform() # Телепортировать игрока обратно к точке появления, скрыв CaptureItemIndicator и MapIndicator над картой вне поля зрения. spawn{CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)} spawn{MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)} Logger.Print("Указатели возвращены в генератор предметов для захвата")
Обработка поведения игроков, хватающих, бросающих и захватывающих цель.
- В определении класса
item_capture_manager
добавьте новый методOnItemPickedUp()
. Этот метод принимает аргументagent
и порождает экземплярFollowCharacter()
для этого персонажа.# Сообщить каждому игроку, когда игрок схватит цель OnItemPickedUp(InAgent:agent):void= Logger.Print("Цель схвачена")
- Задайте получение
FortCharacter
дляInAgent
и создание экземпляра функцииFollowCharacter()
с помощью аргументаFortCharacter
. Полностью методOnItemPickedUp()
должен выглядеть следующим образом:# Сообщить каждому игроку, когда игрок схватит цель OnItemPickedUp(InAgent:agent):void= Logger.Print("Цель схвачена") if(FortCharacter := InAgent.GetFortCharacter[]): ItemGrabbedMessageDevice.Show() spawn{FollowCharacter(FortCharacter)}
- Добавьте новый метод
OnItemCaptured()
в определение классаitem_capture_manager
. Этот метод принимает в качестве аргумента агента (agent
), захватившего цель.# При захвате предмета начислить очки захватившей команде и вернуть указатели. OnItemCaptured(CapturingAgent:agent):void= Logger.Print("Цель захвачена")
- В
OnItemCaptured()
активируйтеScoreManagerDevice
, чтобы начислить очки команде захватившего игрока, и вызовитеReturnIndicators()
, чтобы вернуть указатели.# При захвате предмета начислить очки захватившей команде и вернуть указатели. OnItemCaptured(CapturingAgent:agent):void= Logger.Print("Цель захвачена") ScoreManagerDevice.Activate() ReturnIndicators()
- Добавьте новый метод
OnItemDropped()
в определение классаitem_capture_manager
. Этот метод принимает в качестве аргумента агента (agent
), бросившего предмет.# Когда игрок бросит предмет, создать экземпляр функции WaitForReturn(), # если ReturnTime больше 0. OnItemDropped(InAgent:agent):void= Logger.Print("Цель брошена")
- Когда цель брошена, указатели должны оставаться рядом с ней до тех пор, пока она не будет подобрана или не вернётся в
CaptureItemSpawner
. Чтобы знать, когда возвращать указатели, вы будете использовать переменнуюReturnTime
, определённую ранее. Если значениеReturnTime
больше или равно0.0
, нужно подождать это время, а затем вернуть указатели. ЕслиReturnTime
имеет отрицательное значение, у цели нет времени возврата, поэтому нет необходимости возвращать указатели. Чтобы вернуть указатели, создайте новый экземпляр вспомогательной функцииWaitForReturn()
, которую вы определите на следующем шаге.# Когда игрок бросит предмет, создать экземпляр функции WaitForReturn(), # если ReturnTime больше 0. OnItemDropped(InAgent:agent):void= Logger.Print("Цель брошена") if(ReturnTime >= 0.0): spawn{WaitForReturn()} else: Logger.Print("Брошенная цель не возвращается")
- Добавьте новый метод
WaitForReturn()
в определение классаitem_capture_manager
. Эта функция ждёт в течение времениReturnTime
, а затем возвращает управление, если цель не была подобрана до истечения времени ожидания. Добавьте к этому методу модификатор<suspends>
, чтобы он мог выполнятьSleep()
.# Подождать в течение ReturnTime, после чего вернуть указатели. WaitForReturn()<suspends>:void= Logger.Print("Ожидание возврата указателей…")
- Возвращать указатели или нет, зависит от того, была ли цель подобрана до истечения времени
ReturnTime
. Если цель подобрали, то возвращать указатели не нужно, поскольку они немедленно переходят к игроку, что может создать странные визуальные эффекты. Для решения этой задачи вы будете использовать логическую переменную, значение которой равно результату конкурентного выполнения (race).# Подождать в течение ReturnTime, после чего вернуть указатели. WaitForReturn()<suspends>:void= Logger.Print("Ожидание возврата указателей…") # Вернуть указатель CaptureItem и указатель на карте, если предмет для захвата # не был подобран до истечения времени. ShouldReturn:logic := race:
- Ваша функция
WaitForReturn()
будет ожидать, какое из двух следующих условий выполнится первым. ВремяReturnTime
истекает, и цель возвращается вCaptureItemSpawner
; в этом случае необходимо вернуть указатели, а переменнаяShouldReturn
должна иметь значениеtrue
. Или же цель подбирается до истеченияReturnTime
; в этом случае переменнаяShouldReturn
должна иметь значениеfalse
. Поскольку каждое из этих условий возвращает значение, два отдельных выраженияblock
выполняются конкурентно.ShouldReturn:logic := race: block: block:
- В первом блоке вызовите
Sleep()
на времяReturnTime
, а затем вернитеtrue
. Во втором блоке ожидайте событияCaptureItemSpawner.ItemPickedUpEvent
с помощью функцииAwait()
и верните false. Теперь переменнаяShouldReturn
будет инициализирована по-разному в зависимости от того, какой из этих блоков завершится первым.ShouldReturn:logic := race: block: Sleep(ReturnTime) истина block: CaptureItemSpawner.ItemPickedUpEvent.Await() ложь
- Если
ShouldReturn
имеет значение true, необходимо вернуть указатели. ВызовитеReturnIndicators()
, еслиShouldReturn
оценивается какtrue
. Ваш полный кодWaitForReturn()
должен выглядеть так:# Подождать в течение ReturnTime, после чего вернуть указатели. WaitForReturn()<suspends>:void= Logger.Print("Ожидание возврата указателей…") # Вернуть указатель CaptureItem и указатель на карте, если предмет для захвата # не был подобран до истечения времени. ShouldReturn:logic := race: block: Sleep(ReturnTime) истина block: CaptureItemSpawner.ItemPickedUpEvent.Await() ложь if(ShouldReturn?): ReturnIndicators()
- Теперь в функции
OnBegin()
подпишитеItemPickedUpEvent
устройстваCaptureItemSpawner
наOnItemPickedUp()
,ItemCapturedEvent
этого устройства — наOnItemCaptured()
, аItemDroppedEvent
— наOnItemDropped()
.OnBegin<override>()<suspends>:void= CaptureItemSpawner.ItemPickedUpEvent.Subscribe(OnItemPickedUp) CaptureItemSpawner.ItemCapturedEvent.Subscribe(OnItemCaptured) CaptureItemSpawner.ItemDroppedEvent.Subscribe(OnItemDropped) SpawnerTransform := CaptureItemSpawner.GetTransform()
- Наконец, в
OnBegin()
установите указатели в их начальные местоположения при запуске сценария, вызвавMoveTo()
дляCaptureItemIndicator
иMapIndicator
. Теперь блок кодаOnBegin()
должен выглядеть следующим образом:OnBegin<override>()<suspends>:void= CaptureItemSpawner.ItemPickedUpEvent.Subscribe(OnItemPickedUp) CaptureItemSpawner.ItemCapturedEvent.Subscribe(OnItemCaptured) CaptureItemSpawner.ItemDroppedEvent.Subscribe(OnItemDropped) SpawnerTransform := CaptureItemSpawner.GetTransform() # Телепортировать игрока обратно к точке появления, скрыв CaptureItemIndicator под картой, чтобы его не было видно. CaptureItemIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds) MapIndicator.MoveTo(SpawnerTransform.Translation + vector3{Z := VerticalOffset * 10.0}, SpawnerTransform.Rotation, UpdateRateSeconds)
-
Вернитесь в редактор, сохраните сценарий, скомпилируйте его и перетащите устройство на уровень. Выберите объект окружения, который будет выступать в качестве
CaptureItemIndicator
на уровне. Это может быть что угодно, лишь бы объект был достаточно заметным. В данном примере мы используем бриллиант. На панели Сведения выберите в качестве CaptureItemSpawner устройство InfiltratorCaptureSpawner, а в качестве CaptureItemIndicator — выбранный вами объект окружения. Также выберите в качестве MapIndicator указатель на карте захватчика, в качестве ItemGrabbedMessageDevice — устройство для вывода сообщений в интерфейсе захватчика, а в качестве ScoreManagerDevice — панель управления счётом захватчика. Запишите в ReturnTime отрицательное число, так как удерживаемый предмет команды захватчиков не возвращается.Также нужно создать экземпляр
item_capture_manager
для атакующих. Не забудьте заменить CaptureItemIndicator на объект окружения, отличающийся от объектов захватчиков, чтобы не путать команды, и не забудьте назначить все остальные устройства. Запишите в ReturnTime положительное число, так как предмет для захвата атакующих возвращается через установленное время. - Нажмите Запустить сеанс на панели инструментов UEFN, чтобы выполнить игровой тест уровня. Во время игрового теста уровня у игрока, схватившего цель, над головой должен отображаться объект окружения. Этот объект должен перемещаться вместе с игроком. Если игрок бросит или захватит цель, объект должен телепортироваться назад к генератору предметов для захвата.

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