Закончить код
Ниже представлен полный код игры «Меж двух огней» с тремя командами, который позволяет распределять игроков по командам ассиметрично, чтобы создать динамичный игровой процесс.
triad_infiltration_game.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/FortPlayerUtilities }
using { /Verse.org/Simulation }
using { /Verse.org/Random }
using { /UnrealEngine.com/Temporary/Diagnostics }
triad_infiltration_log_channel := class(log_channel){}
triad_infiltration := class(creative_device):
Logger:log = log{Channel := triad_infiltration_log_channel}
# Во избежание невозможности присоединиться к команде нужно задать максимальное количество
# игроков в настройках острова, равное сумме всех переменных Maximum(Team).
# Максимальное количество игроков в команде захватчиков.
@editable
MaximumInfiltrators:int = 2
# Максимальное количество игроков в команде атакующих.
@editable
MaximumAttackers:int = 4
# Максимальное количество игроков в команде защитников.
@editable
MaximumDefenders:int = 4
# Массив телепортов, телепортирующих игроков в точку появления их команды после начала игры.
@editable
Teleporters:[]teleporter_device = array{}
# Ссылка на сценарий invisibility_manager, управляющий невидимостью захватчиков.
@editable
InvisibilityManager:invisibility_manager = invisibility_manager{}
# Массив устройств выдачи оружия для каждой команды.
@editable
var WeaponGranters:[]item_granter_device = array{}
# Массив точек появления игроков для каждой команды.
@editable
PlayersSpawners:[]player_spawner_device = array{}
# Ссылка на команду захватчиков.
var MaybeInfiltrators:?team = false
# Ссылка на команду атакующих.
var MaybeAttackers:?team = false
# Ссылка на команду защитников.
var MaybeDefenders:?team = false
# Массив всех команд в игре.
var AllTeams:[]team = array{}
# Ассоциативный массив, содержащий максимальное количество игроков в командах.
var TeamsAndTotals:[team]int = map{}
OnBegin<override>()<suspends>:void =
# Получить все команды.
set AllTeams = GetPlayspace().GetTeamCollection().GetTeams()
var AllPlayers:[]player = GetPlayspace().GetPlayers()
# Сохранить команды для последующего обращения.
set MaybeInfiltrators = option{AllTeams[0]}
set MaybeAttackers = option{AllTeams[1]}
set MaybeDefenders = option{AllTeams[2]}
if:
Infiltrators := MaybeInfiltrators?
Attackers := MaybeAttackers?
Defenders := MaybeDefenders?
Logger.Print("Найдены все три команды")
set TeamsAndTotals[Infiltrators] = MaximumInfiltrators
set TeamsAndTotals[Attackers] = MaximumAttackers
set TeamsAndTotals[Defenders] = MaximumDefenders
Logger.Print("Все три команды заданы в TeamsAndTotals")
then:
#Подписаться на PlayerAddedEvent для возможного перераспределения игроков по командам при присоединении нового игрока.
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
BalanceTeams()
Logger.Print("Игроки распределены по командам, вызываем сценарий установки невидимости")
InvisibilityManager.StartInvisibilityManager(AllTeams, AllPlayers, Infiltrators)
Sleep(0.25)
TeleportPlayersToStartLocations()
else:
Logger.Print("Не удаётся найти все команды. Убедитесь, что вы задали правильные команды в настройках острова.")
# Выдать игрокам оружие исходя из индекса их команды в массиве команд,
# выбрав элемент массива WeaponGranters по индексу.
GrantTeamWeapon(InPlayer:player):void=
if(CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[InPlayer]):
for(TeamIndex -> PlayerTeam:AllTeams, PlayerTeam = CurrentTeam):
if(WeaponGranter := WeaponGranters[TeamIndex]):
WeaponGranter.GrantItem(InPlayer)
Logger.Print("Выдано оружие игроку команды {TeamIndex + 1}")
# Выполняется, когда в точке появления появляется любой игрок.
# Вызывает GrantTeamWeapon с аргументом SpawnedAgent.
OnPlayerSpawn(SpawnedAgent:agent):void=
if(SpawnedPlayer := player[SpawnedAgent]):
Logger.Print("Пытаемся выдать оружие появившемуся игроку")
GrantTeamWeapon(SpawnedPlayer)
# Обрабатывает запрос на присоединение нового игрока.
OnPlayerAdded(InPlayer:player):void=
Logger.Print("Присоединился новый игрок. Распределяем его в команду")
FortTeamCollection := GetPlayspace().GetTeamCollection()
# Ассиметрично распределить нового игрока в команду с наименьшим количеством игроков.
BalancePlayer(InPlayer)
for:
TeamIndex -> PlayerTeam:AllTeams
PlayerTeam = FortTeamCollection.GetTeam[InPlayer]
TeamTeleporter := Teleporters[TeamIndex]
Transform := TeamTeleporter.GetTransform()
do:
InPlayer.Respawn(Transform.Translation, Transform.Rotation)
Logger.Print("Появившийся игрок телепортирован в начальную точку")
# Если игрок был захватчиком, вызвать OnInfiltratorJoined в
# InvisibilityManager.
if(PlayerTeam = MaybeInfiltrators?):
InvisibilityManager.OnInfiltratorJoined(InPlayer)
# Распределить всех игроков по всем командам в игре
BalanceTeams():void=
Logger.Print("Начинаем выравнивать состав команд")
var AllPlayers:[]player := GetPlayspace().GetPlayers()
set AllPlayers = Shuffle(AllPlayers)
Logger.Print("Длина AllPlayers составляет {AllPlayers.Length}")
for (TeamPlayer:AllPlayers):
BalancePlayer(TeamPlayer)
# Для каждого игрока пройтись по списку команд и назначить игроков в
# самую маленькую команду или оставить их в первоначальных командах, если у команд равный состав.
BalancePlayer(InPlayer:player):void=
Logger.Print("Начинаем распределять игроков")
var TeamToAssign:?team = false
set TeamToAssign = FindTeamWithLargestDifference()
if (AssignedTeam := TeamToAssign?, GetPlayspace().GetTeamCollection().AddToTeam[InPlayer, AssignedTeam]):
Logger.Print("Игрок назначен в новую команду")
else:
Logger.Print("Этот игрок уже был в самой маленькой команде")
# Найти команду с наибольшей разницей в количестве игроков относительно
# максимального количества игроков.
FindTeamWithLargestDifference():?team =
Logger.Print("Пытаемся найти самую маленькую команду")
var TeamToAssign:?team = false
var LargestDifference:int = 0
for:
CandidateTeamIndex -> CandidateTeam:AllTeams
CurrentTeamSize := GetPlayspace().GetTeamCollection().GetAgents[CandidateTeam].Length
MaximumTeamSize := TeamsAndTotals[CandidateTeam]
do:
Logger.Print("Проверяем команду…")
Logger.Print("Максимальное количество игроков в команде {CandidateTeamIndex + 1} составляет {MaximumTeamSize}")
DifferenceFromMaximum := MaximumTeamSize - CurrentTeamSize
Logger.Print("Разница относительно максимального количества составляет {DifferenceFromMaximum}")
if(LargestDifference < DifferenceFromMaximum):
set LargestDifference = DifferenceFromMaximum
set TeamToAssign = option{CandidateTeam}
Logger.Print("Найдена команда {CandidateTeamIndex + 1} с разницей {DifferenceFromMaximum}")
return TeamToAssign
# Телепортирует игроков в зону появления их команды после завершения выравнивания состава команд.
TeleportPlayersToStartLocations():void=
Logger.Print("Телепортируем игроков в начальные точки")
for:
TeamIndex -> PlayerTeam:AllTeams
TeamPlayers := GetPlayspace().GetTeamCollection().GetAgents[PlayerTeam]
TeamTeleporter := Teleporters[TeamIndex]
do:
for(TeamPlayer:TeamPlayers):
TeamTeleporter.Teleport(TeamPlayer)
Logger.Print("Этот игрок телепортирован в начальную точку")
invisibility.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/Characters }
using { /Verse.org/Simulation }
using { /UnrealEngine.com/Temporary/Diagnostics }
triad_invisibility_log_channel := class(log_channel){}
invisibility_manager := class(creative_device):
Logger:log = log{Channel := triad_invisibility_log_channel}
# Массив точек появления игроков для команды захватчиков
@editable
PlayersSpawners:[]player_spawner_device = array{}
# Распространяется ли видимость захватчика на других участников команды.
@editable
IsVisibilityShared:logic = true
# Как долго захватчики остаются видимыми после получения урона.
@editable
VulnerableSeconds:float = 3.0
# Как часто захватчики мерцают при получении урона.
@editable
FlickerRateSeconds:float = 0.4
# Массив всех команд в игре.
var AllTeams:[]team = array{}
# Ассоциативный массив, содержащий оставшееся время мерцания в секундах для каждого игрока.
var PlayerVisibilitySeconds:[agent]float = map{}
OnBegin<override>()<suspends>:void=
# Ждать, пока состав команд выровняется, чтобы подписаться на события, которые делают игроков невидимыми.
Logger.Print("Ожидаем завершения выравнивания состава команд…")
# Запускает логику диспетчера невидимости. Вызывается из класса triad_infiltration после завершения выравнивания состава команд
StartInvisibilityManager<public>(GameTeams:[]team, AllPlayers:[]player, Infiltrators:team):void=
Logger.Print("Сценарий установки невидимости запущен!")
set AllTeams = GameTeams
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
# Для каждого игрока, появившегося в команде захватчиков, создать отдельный экземпляр функции
# OnInfiltratorDamaged. Затем сделать его персонаж невидимым.
for(TeamPlayer:AllPlayers):
if:
FortCharacter:fort_character = TeamPlayer.GetFortCharacter[]
CurrentTeam:team := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]
Logger.Print("Текущая команда этого игрока определена")
Infiltrators = CurrentTeam
set PlayerVisibilitySeconds[TeamPlayer] = 0.0
Logger.Print("Игрок добавлен в PlayerVisibilitySeconds")
then:
spawn{OnInfiltratorDamaged(TeamPlayer)}
Logger.Print("Игрок появился в качестве захватчика, делаем его невидимым")
FortCharacter.Hide()
else:
Logger.Print("Этот игрок не захватчик")
# Обеспечивает мерцание агента, периодически скрывая и отображая его fort_character
FlickerCharacter(InCharacter:fort_character)<suspends>:void=
Logger.Print("Вызвана функция FlickerCharacter()")
# Попеременное скрытие и отображение персонажа для создания эффекта мерцания.
loop:
InCharacter.Hide()
Sleep(FlickerRateSeconds)
InCharacter.Show()
Sleep(FlickerRateSeconds)
# В каждом цикле уменьшать время мерцания персонажа на FlickerRateSeconds.
# Если оставшееся время достигло 0, выйти из цикла.
if:
TimeRemaining := set PlayerVisibilitySeconds[InCharacter.GetAgent[]] -= FlickerRateSeconds * 2
TimeRemaining <= 0.0
then:
InCharacter.Hide()
break
# Обеспечивает мерцание агента всякий раз, когда он получает урон
OnInfiltratorDamaged(InAgent:agent)<suspends>:void=
Logger.Print("Пытаемся инициировать мерцание этого персонажа")
TeamCollection := GetPlayspace().GetTeamCollection()
if (FortCharacter := InAgent.GetFortCharacter[]):
loop:
if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]):
# Для каждого участника команды задать PlayerVisibilitySeconds и создать FlickerEvent.
for(Teammate:TeamAgents):
Logger.Print("Вызываем StartOrResetFlickering для Teammate")
StartOrResetFlickering(Teammate)
else:
# Сделать мерцающим только персонаж, получивший урон.
Logger.Print("Вызываем StartOrResetFlickering для InAgent")
StartOrResetFlickering(InAgent)
FortCharacter.DamagedEvent().Await()
# Создаёт новое событие мерцания, если агент был невидим, в противном случае
# сбрасывает текущее мерцание агента.
StartOrResetFlickering(InAgent:agent):void=
if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]):
Logger.Print("Пытаемся инициировать НОВОЕ событие FlickerEvent для этого персонажа")
# Запущено новое мерцание.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
spawn{FlickerCharacter(FortCharacter)}
Logger.Print("Сгенирировано событие FlickerEvent для этого персонажа")
else:
# Сбросить текущее мерцание.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
Logger.Print("Сбросим FlickerTimer персонажа на VulnerableSeconds")
# Возвращает информацию о том, осталось ли у игрока время мерцания
IsFlickering(InAgent:agent)<decides><transacts>:void=
PlayerVisibilitySeconds[InAgent] > 0.0
# Создаёт экземпляр функции OnInfiltratorDamaged, когда к игре присоединяется новый захватчик
OnInfiltratorJoined<public>(InAgent:agent):void=
spawn{OnInfiltratorDamaged(InAgent)}
# Обрабатывает появление игрока в точке появления захватчиков
OnPlayerSpawn(SpawnedAgent:agent):void=
Logger.Print("Игрок только что появился в точке появления захватчиков!")
if:
FortCharacter:fort_character = SpawnedAgent.GetFortCharacter[]
CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[SpawnedAgent]
AllTeams[0] = CurrentTeam
Logger.Print("Игрок появился в качестве захватчика, делаем его невидимым")
then:
FortCharacter.Hide()
item_capture_manager.verse
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
using { /Fortnite.com/Characters }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
triad_item_capture_log_channel := class(log_channel){}
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{}
# Объект окружения, который парит над головой игрока, удерживающего предмет из
# CaptureItemSpawner.
@editable
CaptureItemIndicator:creative_prop = creative_prop{}
# Указатель, показывающий на карте, где находятся цели каждой команды.
@editable
MapIndicator:map_indicator_device = map_indicator_device{}
# Как часто обновляется положение CaptureItemIndicator.
@editable
UpdateRateSeconds:float = 0.15
# Как высоко над головой игрока парит CaptureItemIndicator.
@editable
VerticalOffset:float = 180.0
# Отображает сообщение, когда игрок хватает предмет для захвата.
@editable
ItemGrabbedMessageDevice:hud_message_device = hud_message_device{}
# Время ожидания перед возвратом указателя CaptureItem и указателя на карте.
# Отрицательное время означает, что указатели никогда не вернутся, если только цель
# не будет схвачена снова.
@editable
ReturnTime:float = 10.0
# Начислить очки игроку, захватившему предмет для захвата.
@editable
ScoreManagerDevice:score_manager_device = score_manager_device{}
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)
Logger.Print("Маячок возвращён в генератор предметов для захвата")
# Сообщить каждому игроку, когда игрок схватит цель.
OnItemPickedUp(InAgent:agent):void=
Logger.Print("Цель схвачена")
if(FortCharacter := InAgent.GetFortCharacter[]):
ItemGrabbedMessageDevice.Show()
spawn{FollowCharacter(FortCharacter)}
# Когда игрок бросит предмет, создать экземпляр функции WaitForReturn(),
# если ReturnTime больше 0.
OnItemDropped(InAgent:agent):void=
Logger.Print("Цель брошена")
if(ReturnTime >= 0.0):
spawn{WaitForReturn()}
else:
Logger.Print("Брошенная цель не возвращается")
# При захвате предмета начислить очки захватившей команде и вернуть указатели.
OnItemCaptured(CapturingAgent:agent):void=
Logger.Print("Цель захвачена")
ScoreManagerDevice.Activate()
ReturnIndicators()
# Подождать в течение ReturnTime, после чего вернуть указатели.
WaitForReturn()<suspends>:void=
Logger.Print("Ожидание возврата указателей…")
# Вернуть указатель CaptureItem и указатель на карте, если предмет для захвата
# не был подобран до истечения времени.
ShouldReturn:logic := race:
block:
Sleep(ReturnTime)
истина
block:
CaptureItemSpawner.ItemPickedUpEvent.Await()
ложь
if(ShouldReturn?):
ReturnIndicators()
# Заставляет 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("Цель сброшена или захвачена")
# Возвращает указатель на карте и указатель предмета для захвата в начальные местоположения над генераторами.
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("Указатели возвращены в генератор предметов для захвата")
Самостоятельная работа
В этом уроке вы узнали, как использовать Verse для создания игры, в которой игроки распределяются по командам ассиметрично.
Используя полученные знания, попробуйте сделать следующее:
- Поэкспериментируйте с различными параметрами захватчиков, нападающих и защитников, чтобы создать идеальный игровой процесс. Что будет, если захватчикам выдавать оружие ближнего боя? Что будет, если защитники также будут невидимыми?
- Могут ли захватчики и атакующие сражаться за одну и ту же цель? Можете ли вы изменить условие победы?