완성된 코드
다음은 플레이어들을 비대칭 방식으로 구성하여 박진감 넘치는 플레이 경험을 제공하는 삼중 침투 게임의 전체 코드입니다.
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}
# 플레이어가 팀에 참여할 수 없는 상황을 방지하려면 섬 설정에서 플레이어의 최대 수를
# 최대(팀) 변수의 총합으로 설정해야 합니다.
# 침투 팀 플레이어의 최대 수입니다.
@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("Found all three teams")
set TeamsAndTotals[Infiltrators] = MaximumInfiltrators
set TeamsAndTotals[Attackers] = MaximumAttackers
set TeamsAndTotals[Defenders] = MaximumDefenders
Logger.Print("Set all three teams in TeamsAndTotals")
then:
#새 플레이어가 게임 합류 시 팀 밸런싱을 재조정할 수 있도록 PlayerAddedEvent에 등록합니다.
GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded)
for(PlayerSpawner:PlayersSpawners):
PlayerSpawner.SpawnedEvent.Subscribe(OnPlayerSpawn)
BalanceTeams()
Logger.Print("Teams balanced, calling invisibility script")
InvisibilityManager.StartInvisibilityManager(AllTeams, AllPlayers, Infiltrators)
Sleep(0.25)
TeleportPlayersToStartLocations()
else:
Logger.Print("Couldn't find all teams, make sure to assign the correct teams in your island settings.")
# 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("Granted the a Player on team {TeamIndex + 1} a weapon")
# 어떤 플레이어든 생성 패드에서 생성될 때 실행합니다.
# 제공된 SpawnedAgent를 사용하여 GrantTeamWeapon을 호출합니다.
OnPlayerSpawn(SpawnedAgent:agent):void=
if(SpawnedPlayer := player[SpawnedAgent]):
Logger.Print("Attempting to grant spawned player a weapon")
GrantTeamWeapon(SpawnedPlayer)
# 게임에 합류한 새 플레이어를 처리합니다.
OnPlayerAdded(InPlayer:player):void=
Logger.Print("A new Player joined, assigning them to a team")
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("Teleported the spawned player to their start location")
# 플레이어가 침투자였다면, InvisibilityManager에서
# OnInfiltratorJoined를 호출합니다.
if(PlayerTeam = MaybeInfiltrators?):
InvisibilityManager.OnInfiltratorJoined(InPlayer)
# 게임에 속한 모든 팀의 모든 플레이어를 밸런싱합니다.
BalanceTeams():void=
Logger.Print("Beginning to balance teams")
var AllPlayers:[]player := GetPlayspace().GetPlayers()
set AllPlayers = Shuffle(AllPlayers)
Logger.Print("AllPlayers Length is {AllPlayers.Length}")
for (TeamPlayer:AllPlayers):
BalancePlayer(TeamPlayer)
# 각 플레이어에 대해 팀 목록 전체를 반복작업하여
# 플레이어 수가 가장 적은 팀, 동률이 발생할 경우 시작 팀에 할당합니다.
BalancePlayer(InPlayer:player):void=
Logger.Print("Beginning to balance player")
var TeamToAssign:?team = false
set TeamToAssign = FindTeamWithLargestDifference()
if (AssignedTeam := TeamToAssign?, GetPlayspace().GetTeamCollection().AddToTeam[InPlayer, AssignedTeam]):
Logger.Print("Assigned player to a new team")
else:
Logger.Print("This player was already on the smallest team")
# 최대 플레이어 수와 현재 플레이어 수가 가장 큰 차이를 보이는
# 팀을 찾습니다.
FindTeamWithLargestDifference():?team =
Logger.Print("Attempting to find smallest team")
var TeamToAssign:?team = false
var LargestDifference:int = 0
for:
CandidateTeamIndex -> CandidateTeam:AllTeams
CurrentTeamSize := GetPlayspace().GetTeamCollection().GetAgents[CandidateTeam].Length
MaximumTeamSize := TeamsAndTotals[CandidateTeam]
do:
Logger.Print("Checking a team...")
Logger.Print("Maximum size of team {CandidateTeamIndex + 1} is {MaximumTeamSize}")
DifferenceFromMaximum := MaximumTeamSize - CurrentTeamSize
Logger.Print("Difference from maximum is {DifferenceFromMaximum}")
if(LargestDifference < DifferenceFromMaximum):
set LargestDifference = DifferenceFromMaximum
set TeamToAssign = option{CandidateTeam}
Logger.Print("Found team {CandidateTeamIndex + 1} with difference {DifferenceFromMaximum}")
return TeamToAssign
# 팀 밸런싱 후 해당 팀의 생성 구역으로 플레이어를 순간이동시킵니다.
TeleportPlayersToStartLocations():void=
Logger.Print("Teleporting players to start locations")
for:
TeamIndex -> PlayerTeam:AllTeams
TeamPlayers := GetPlayspace().GetTeamCollection().GetAgents[PlayerTeam]
TeamTeleporter := Teleporters[TeamIndex]
do:
for(TeamPlayer:TeamPlayers):
TeamTeleporter.Teleport(TeamPlayer)
Logger.Print("Teleported this player to their start location")
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("Waiting for teams to be balanced...")
# 투명 관리 장치 로직을 시작합니다. 팀 밸런싱이 끝난 뒤에 triad_infiltration으로부터 호출됩니다.
StartInvisibilityManager<public>(GameTeams:[]team, AllPlayers:[]player, Infiltrators:team):void=
Logger.Print("Invisibility script started!")
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("Got this player's current team")
Infiltrators = CurrentTeam
set PlayerVisibilitySeconds[TeamPlayer] = 0.0
Logger.Print("Added player to PlayerVisibilitySeconds")
then:
spawn{OnInfiltratorDamaged(TeamPlayer)}
Logger.Print("Player spawned as an infiltrator, making them invisible")
FortCharacter.Hide()
else:
Logger.Print("This player isn't an infiltrator)")
# 에이전트의 fort_character를 반복적으로 숨기고 표시함으로써 에이전트의 비저빌리티를 깜빡입니다.
FlickerCharacter(InCharacter:fort_character)<suspends>:void=
Logger.Print("FlickerCharacter() invoked")
# 캐릭터의 숨김과 표시를 루프하여 깜빡임 효과를 만듭니다.
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("Attempting to start flickering this character")
TeamCollection := GetPlayspace().GetTeamCollection()
if (FortCharacter := InAgent.GetFortCharacter[]):
loop:
if(IsVisibilityShared?, CurrentTeam := TeamCollection.GetTeam[InAgent], TeamAgents := TeamCollection.GetAgents[CurrentTeam]):
# 각 팀 멤버에 대해 PlayerVisibility 시간(초)을 설정하고 FlickerEvent를 스폰합니다.
for(Teammate:TeamAgents):
Logger.Print("Calling StartOrResetFlickering on a Teammate")
StartOrResetFlickering(Teammate)
else:
# 피해를 입은 캐릭터를 깜빡입니다.
Logger.Print("Calling StartOrResetFlickering on InAgent")
StartOrResetFlickering(InAgent)
FortCharacter.DamagedEvent().Await()
# 에이전트가 투명했다면 새 깜빡임 이벤트를 시작합니다.
# 아니면 에이전트의 현재 깜빡임을 리셋합니다.
StartOrResetFlickering(InAgent:agent):void=
if (not IsFlickering[InAgent], FortCharacter := InAgent.GetFortCharacter[]):
Logger.Print("Attempting to start NEW FlickerEvent for this character")
# 새 깜빡임이 시작됩니다.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
spawn{FlickerCharacter(FortCharacter)}
Logger.Print("Spawned a FlickerEvent for this character")
else:
# 진행 중인 깜빡임이 리셋됩니다.
if (set PlayerVisibilitySeconds[InAgent] = VulnerableSeconds):
Logger.Print("Reset character's FlickerTimer to 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("A player just spawned from an infiltrator spawn pad!")
if:
FortCharacter:fort_character = SpawnedAgent.GetFortCharacter[]
CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[SpawnedAgent]
AllTeams[0] = CurrentTeam
Logger.Print("Player spawned as an infiltrator, making them invisible")
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("Returned Beacon to capture spawner")
# 플레이어가 목표물을 획득하면 각 플레이어에게 신호합니다.
OnItemPickedUp(InAgent:agent):void=
Logger.Print("Objective Grabbed")
if(FortCharacter := InAgent.GetFortCharacter[]):
ItemGrabbedMessageDevice.Show()
spawn{FollowCharacter(FortCharacter)}
# 플레이어가 아이템을 드롭했을 때 ReturnTime이 0보다 클 경우
# WaitForReturn() 함수를 스폰합니다.
OnItemDropped(InAgent:agent):void=
Logger.Print("Objective Dropped")
if(ReturnTime >= 0.0):
spawn{WaitForReturn()}
else:
Logger.Print("The dropped objective does not return")
# 아이템이 회수되었을 때, 회수 팀이 점수를 획득하고 표시 장치를 반환합니다.
OnItemCaptured(CapturingAgent:agent):void=
Logger.Print("Objective Captured")
ScoreManagerDevice.Activate()
ReturnIndicators()
# ReturnTime 동안 기다린 후 표시 장치를 반환합니다.
WaitForReturn()<suspends>:void=
Logger.Print("Waiting t return the indicators...")
# 시간이 만료되기 전에 회수 아이템이 픽업되지 않으면
# CaptureItem과 지도 표시 장치를 반환합니다.
ShouldReturn:logic := race:
block:
Sleep(ReturnTime)
True
block:
CaptureItemSpawner.ItemPickedUpEvent.Await()
False
if(ShouldReturn?):
ReturnIndicators()
# CaptureItemIndicator가 플레이어 머리 위에서 계속 따라다니게 합니다.
# CaptureItemIndicator에 대한 업데이트 루프, 플레이어가 아이템을 회수했는지, 아이템을 드롭했는지, 아이템이 제거되었는지 여부
# 사이의 레이스입니다.
FollowCharacter(FortCharacter:fort_character)<suspends>:void=
Logger.Print("Spawned FollowCharacter function")
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("Objective dropped or captured")
# 지도와 회수 아이템 표시 장치를 처음에 있던 생성 장치 위로 되돌립니다.
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("Returned Indicators to capture spawner")
직접 해보기
이 가이드를 완료했으므로 이제 여러분은 Verse를 사용하여 플레이어 팀을 비대칭으로 밸런싱하는 게임을 제작하는 방법에 익숙해지셨을 것입니다.
여기에서 배운 것을 활용하여 다음과 같은 작업을 해보세요.
- 침투자, 공격자 및 방어자에 다양한 파라미터를 적용하여 이상적인 플레이 경험을 만들어 보세요. 침투자에게 근접 무기를 지급하거나, 방어자도 투명하게 만들어 볼 수 있습니다.
- 침투자와 공격자가 동일한 목표물을 두고 경쟁하게 하거나, 승리 조건을 바꿔볼 수 있습니다.