이 섹션에는 생성한 Verse 파일에 추가할 완성된 코드가 나와 있습니다.
완성된 코드
이 프로젝트에는 여러 개의 Verse 파일이 있습니다.
-
heartbeat.verse: 파일의 완성된 코드는 다음을 참조하세요.
-
base_team.verse: 파일의 완성된 코드는 다음을 참조하세요.
-
hunter_team.verse: 파일의 완성된 코드는 다음을 참조하세요.
-
prop_team.verse: 파일의 완성된 코드는 다음을 참조하세요.
-
round_timer.verse: 파일의 완성된 코드는 다음을 참조하세요.
-
waiting_for_more_players.verse: 파일의 완성된 코드는 다음을 참조하세요.
heartbeat.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_heart_beat := class(log_channel){}
# 이러한 메시지는 심장 박동이 표시되지 않도록 이동해야 하는 사물 에이전트에게 메시지로 알리거나 사물 에이전트를 숨기는 데 사용됩니다.
HeartBeatWarningMessage<localizes>(Time:int):message = "Heart Beat in {Time} Seconds. Move!"
HeartBeatWarningClear<localizes>:message = ""
# 이 클래스는 prop_hunt 장치에 심장 박동의 편집 가능 프로퍼티를 노출했습니다.
heart_beat := class<concrete>():
Logger:log = log{Channel:=log_heart_beat}
@editable # 심장 박동으로 인해 위치가 발각되기 전에 사물 에이전트가 반드시 이동해야 하기까지 남은 시간(초)입니다.
MoveTime:float = 15.0
@editable # 심장 박동 경고가 표시되기 전 남은 시간(초)입니다. HeartBeatTimer보다 클 수 없습니다.
WarningTime:float = 5.0
@editable # 심장 박동 VFX 장치의 배열입니다. 플레이어당 하나씩 있습니다.
AgentVFX:[]heartbeat_vfx = array{}
@editable # 심장 박동 사운드 이펙트(SFX) 재생에 사용되는 오디오 플레이어 장치입니다.
SFXPlayer:radio_device = radio_device{}
# 이 맵은 각 사물 에이전트에게 심장 박동 경고를 표시하는 UI와 연관되어 있습니다.
var WarningUI:[agent]heartbeat_warning_ui = map{}
# SFX 장치를 관리할 수 있도록 심장 박동이 활성화된 플레이어 수를 추적합니다.
var NumberOfHeartBeats:int = 0
# 에이전트의 심장 박동 UI를 구성합니다.
SetUpUI(PropAgent:agent):void =
if:
not WarningUI[PropAgent]
AsPlayer := player[PropAgent]
PlayerUI := GetPlayerUI[AsPlayer]
then:
UIData:heartbeat_warning_ui = heartbeat_warning_ui{}
UIData.CreateCanvas()
PlayerUI.AddWidget(UIData.Canvas, player_ui_slot{ZOrder := 1})
if (set WarningUI[PropAgent] = UIData) {}
# 지정된 플레이어의 심장 박동 VFX 및 SFX를 활성화합니다.
Enable(PropAgent:agent, HeartBeatVFXData:heartbeat_vfx):void =
if:
# 씬에서 사물 에이전트의 위치를 찾는 데 사용되는 캐릭터를 얻습니다.
Character := PropAgent.GetFortCharacter[]
then:
# 심장 박동 VFX의 위치를 사물 에이전트의 위치로 설정합니다.
HeartBeatVFXData.Activate(Character.GetTransform())
# 심장 박동 수를 증가시키며, 처음 재생되는 심장 박동인 경우 오디오가 시작되도록 재생해야 합니다.
set NumberOfHeartBeats += 1
if (NumberOfHeartBeats = 1) then SFXPlayer.Play()
# 오디오 플레이어 장치에 사물 에이전트를 등록하여 심장 박동 오디오가 해당 위치에서 재생되도록 합니다.
SFXPlayer.Register(PropAgent)
else:
Logger.Print("Character, Index, or HeartBeatVFXData not found. Cannot start the heartbeat")
# 지정된 사물 에이전트의 심장 박동 VFX 및 SFX를 지웁니다.
Disable(PropAgent:agent, HeartBeatVFXData:heartbeat_vfx):void =
Logger.Print("Disabling heart beat.")
# VFX 비주얼을 비활성화합니다.
HeartBeatVFXData.Deactivate()
# 오디오 플레이어 장치에서 사물 에이전트를 등록 취소하여 심장 박동 오디오가 중지되도록 합니다.
SFXPlayer.Unregister(PropAgent)
# 심장 박동 카운터를 감소시킵니다. 이 카운터는 0 미만으로 내려갈 수 없습니다.
set NumberOfHeartBeats -= 1
if (NumberOfHeartBeats < 0) then set NumberOfHeartBeats = 0
# 모든 사물 에이전트의 모든 심장 박동 VFX 및 SFX를 지웁니다.
DisableAll():void =
Logger.Print("Disabling all heart beats.")
# 모든 VFX에서 반복작업하여 0,0,0으로 이동합니다.
for (HeartBeatVFXDevice : AgentVFX):
HeartBeatVFXDevice.Deactivate()
# 심장 박동 오디오에서 모든 플레이어를 등록 취소합니다.
SFXPlayer.UnregisterAll()
# 심장 박동 카운터를 0으로 재초기화합니다.
set NumberOfHeartBeats = 0
# heartbeat_warning_ui 클래스에는 플레이어별 UI 캔버스 및 text_block을 추적하는 데이터 구조체와 새 심장 박동 경고 UI 캔버스를 생성하는 함수가 포함되어 있습니다.
heartbeat_warning_ui := class:
var Canvas:canvas = canvas{}
var Text:text_block = text_block{}
# 경고 메시지를 위한 UI 캔버스를 생성합니다.
CreateCanvas():void =
set Text = text_block{DefaultTextColor := NamedColors.White, DefaultShadowColor := NamedColors.Black}
set Canvas = canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := vector2{X := 0.5, Y := 0.75}, Maximum := vector2{X := 0.5, Y := 0.75}}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := vector2{X := 0.5, Y := 1.0}
SizeToContent := true
Widget := Text
# heartbeat_vfx 클래스에는 플레이어별 VFX의 루트 및 vfx_spawner_device 오브젝트를 추적하는 데이터 구조체와 VFX를 어떤 위치로 설정하거나 리셋하는 함수가 포함되어 있습니다.
heartbeat_vfx := class<concrete>:
@editable # 각 심장 박동의 VFX 장치입니다.
VFXDevice:vfx_spawner_device = vfx_spawner_device{}
# 이 오프셋은 사물 에이전트의 머리 위에 심장 박동을 배치하는 데 사용됩니다.
HeartBeatVFXOffset:vector3 = vector3{X := 0.0, Y := 0.0, Z := 110.0}
# 심장 박동 VFX의 위치를 설정한 다음 VFX를 활성화합니다.
Activate(Transform:transform):void =
VFXPosition := Transform.Translation + HeartBeatVFXOffset
if (VFXDevice.TeleportTo[VFXPosition, Transform.Rotation]):
VFXDevice.Enable()
# VFX를 비활성화하여 심장 박동 비주얼을 숨깁니다.
Deactivate():void =
VFXDevice.Disable()
base_team.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_team := class(log_channel){}
# 이 클래스는 경험 내 여러 팀에 필요한 장치를 정의합니다.
# 이 클래스는 추상이므로 자체적으로는 사용할 수 없으며, 다른 클래스에서 상속받아야 합니다.
base_team := class<abstract>:
Logger:log = log{Channel:=log_team}
@editable # 플레이어를 팀에 설정하는 데 사용됩니다.
ClassSelector:class_and_team_selector_device = class_and_team_selector_device{}
@editable # 팀의 에이전트에게 점수를 부여하는 데 사용됩니다.
ScoreManager:score_manager_device = score_manager_device{}
@editable # 팀 할당 제목을 표시하는 데 사용됩니다.
TeamTitle:hud_message_device = hud_message_device{}
@editable # 팀 할당 설명을 표시하는 데 사용됩니다.
TeamDescription:hud_message_device = hud_message_device{}
@editable # 팀원(사물 팀) 또는 적(헌터 팀) 처치됨 이벤트를 구독하는 데 사용됩니다.
TeamManager:team_settings_and_inventory_device = team_settings_and_inventory_device{}
# 팀의 에이전트 배열입니다.
var TeamAgents<private>:[]agent = array{}
# 이 이벤트는 TeamAgents 배열이 빈 상태가 될 때 라운드 종료 신호를 전송받습니다.
TeamEmptyEvent:event() = event(){}
# 현재 TeamAgents 배열을 반환합니다.
# TeamAgents 배열이 프라이빗이므로 다른 클래스에서 직접 액세스할 수 없기 때문에 필요합니다.
GetAgents()<decides><transacts>:[]agent =
TeamAgents
# TeamAgents 배열의 크기를 반환합니다.
# TeamAgents 배열이 프라이빗이므로 다른 클래스에서 직접 액세스할 수 없기 때문에 함수가 필요합니다.
Count()<transacts>:int =
TeamAgents.Length
# 에이전트의 TeamAgents 배열에 있는 인덱스를 반환하고, 그렇지 않으면 실패합니다.
FindOnTeam(Agent:agent)<decides><transacts>: int =
Index := TeamAgents.Find[Agent]
# 에이전트를 팀에 설정하고 플레이어에게 알립니다.
InitializeAgent(Agent:agent):void =
AddAgentToTeam(Agent)
ClassSelector.ChangeTeamAndClass(Agent)
DisplayTeamInformation(Agent)
# TeamAgents에 에이전트를 추가합니다.
AddAgentToTeam(AgentToAdd:agent):void =
if (not FindOnTeam[AgentToAdd]):
Logger.Print("Adding agent to team.")
set TeamAgents += array{AgentToAdd}
# HUD 메시지 장치를 활성화하여 플레이어에게 소속 팀을 표시합니다.
DisplayTeamInformation(Agent:agent):void =
TeamTitle.Show(Agent)
TeamDescription.Show(Agent)
# 에이전트가 매치에서 나갈 때 TeamAgents 배열에서 에이전트를 제거하고 라운드 종료를 확인합니다.
EliminateAgent(Agent:agent)<suspends>:void =
Sleep(0.0) # 게임 틱 1틱을 딜레이하여 진행 전에 플레이어가 부활되도록 합니다.
RemoveAgentFromTeam(Agent)
# TeamAgents에서 에이전트를 제거합니다.
# 제거된 에이전트가 마지막 남은 에이전트였다면 TeamEmptyEvent에 신호를 전송합니다.
RemoveAgentFromTeam(AgentToRemove:agent):void =
set TeamAgents = TeamAgents.RemoveAllElements(AgentToRemove)
Logger.Print("{Count()} agent(s) on team remaining.")
if (Count() < 1):
Logger.Print("No agents on team remaining. Ending the round.")
TeamEmptyEvent.Signal()
hunter_team.verse
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Verse.org/Simulation }
# base_team 클래스에서 상속하는 hunter_team 클래스에는 헌터 팀과 그 에이전트에 관련된 장치 정의 및 함수가 포함되어 있습니다.
hunter_team := class<concrete>(base_team):
@editable # 라운드마다 플레이어 n명당 하나의 헌터 에이전트가 생성됩니다. 예시: HunterTeamPerNumberOfPlayers = 5.0인 경우 플레이어 5명당 헌터 에이전트 1개를 뜻합니다. 플레이어가 6명인 경우 헌터 에이전트 2개가 생성됩니다.
HunterAgentPerNumberOfPlayers:float = 5.0 # 최소 1개의 사물 에이전트가 생성되기 위해 최소 1.1의 값이 강제됩니다.
@editable # 헌터 에이전트가 생성되기 전에 남은 시간(초)으로, 사물 에이전트가 숨어서 유리하게 시작하도록 합니다.
SpawnDelay:float = 15.0
@editable # 헌터 에이전트가 사물 에이전트 처치를 위해 획득하는 최대 베이스 포인트입니다. 이 포인트는 남은 사물 에이전트 수로 나뉩니다.
MaxEliminationScore:int = 5000
@editable # 사물에게 숨을 유예 시간을 부여하는 데 사용하는 타이머 장치입니다.
WaitTimer:timer_device = timer_device{}
# 에이전트를 헌터 에이전트로 설정합니다.
InitializeAgent<override>(NewHunterAgent:agent):void =
Logger.Print("Setting a new hunter agent.")
(super:)InitializeAgent(NewHunterAgent)
# 헌터 에이전트가 매치에서 나갈 때 HunterAgents 배열에서 헌터 에이전트를 제거하고 라운드 종료를 확인합니다.
# 사물 팀의 경우처럼 추가 데이터를 전달할 필요가 없으므로 이 함수를 오버라이드하는 것입니다.
EliminateAgent<override>(HunterAgent:agent)<suspends>:void =
Logger.Print("Hunter agent eliminated.")
(super:)EliminateAgent(HunterAgent)
prop_team.verse
using { /Fortnite.com/Characters }
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
# 이 메시지는 라운드 중에 모든 플레이어에게 남은 사물 수를 출력하는 데 사용됩니다.
PropAgentsRemainingMessage<localizes>(Count:int):message = "{Count} Prop(s) Remaining"
# base_team 클래스에서 상속하는 prop_team 클래스에는 사물 팀과 그 에이전트에 관련된 장치 정의 및 함수가 포함되어 있습니다.
# 특히, 사물 에이전트의 심장 박동 비헤이비어는 이 클래스에서 찾을 수 있습니다.
prop_team := class<concrete>(base_team):
@editable # 사물 에이전트가 초당 받는 점수입니다.
ScorePerSecond:int = 10
@editable # 사물 에이전트가 심장 박동 타이머를 리셋하기 위해 이동해야 하는 최소 거리입니다.
MinimumMoveDistance:float = 100.0
@editable # 사물에 점수를 부여하는 데 사용되는 타이머 장치입니다.
ScoreTimer:timer_device = timer_device{}
@editable # 이 추적기 장치는 화면에 남은 사물을 표시하는 데 사용됩니다.
PropsRemainingTracker:tracker_device = tracker_device{}
@editable # heart_beat 클래스에서 프로퍼티를 얻습니다.
HeartBeat:heart_beat = heart_beat{}
# 에이전트를 사물 에이전트로 설정하고 심장 박동 경고 UI를 할당합니다.
InitializeAgent<override>(NewPropAgent:agent):void =
Logger.Print("Setting a new prop agent.")
(super:)InitializeAgent(NewPropAgent)
# PropScoreTimer가 완료될 때 모든 사물 에이전트에게 점수를 부여합니다. 이벤트 구독에 PropInstigator가 필요하지만, 사용되지는 않습니다.
OnPropsScore(PropInstigator:?agent):void =
if (PropAgents := GetAgents[]):
for (PropAgent : PropAgents):
ScoreManager.Activate(PropAgent)
# 사물 에이전트가 처치되거나 매치에서 나갈 때 PropAgents 배열에서 사물 에이전트를 제거하고 라운드 종료를 확인합니다.
# 남은 사물 메시지를 업데이트하기 위해 모든 플레이어를 함수에 전달하므로 이 함수는 오버라이드하지 않습니다.
EliminateAgent<override>(PropAgent:agent)<suspends>:void =
Logger.Print("Prop agent eliminated.")
(super:)EliminateAgent(PropAgent)
# 남은 사물 수를 업데이트합니다.
UpdatePropsRemainingTracker()
# 남은 사물 수를 표시하는 추적기 장치의 값을 업데이트합니다.
UpdatePropsRemainingTracker():void =
PropsRemainingTracker.SetValue(Count())
# 사물 에이전트가 이동을 멈추면, 사물 에이전트가 MinimumMoveDistance를 초과하여 이동하는지, 심장 박동 타이머가 완료되는지 또는 사물 에이전트가 처치되는지 확인하기 위해 경합합니다.
RunPropGameLoop(PropAgent:agent)<suspends>:void =
Logger.Print("Starting prop agent game loop.")
# 사물 에이전트가 처치되거나 플레이어가 세션에서 나갈 때까지 사물 비헤이비어를 무한 루프합니다.
race:
PropAgent.AwaitNoLongerAProp()
loop:
# 사물 에이전트가 최소 거리 미만으로 이동할 때까지 대기한 다음 진행합니다.
PropAgent.AwaitStopMoving(MinimumMoveDistance)
# 사물 에이전트가 최소 거리를 초과하여 이동하기까지 심장 박동 카운트다운을 한 다음 심장 박동을 무한 재생합니다.
race:
PropAgent.AwaitStartMoving(MinimumMoveDistance)
block:
CountdownTimer(PropAgent)
PropAgent.StartHeartbeat()
Sleep(0.0) # race가 완료되면(사물 에이전트가 이동하면) 루프가 다시 시작됩니다.
# 사물 에이전트가 더 이상 PropAgents 배열의 일부가 아닐 때까지 루프합니다. 사물 에이전트가 처치되어 헌터로 바뀌거나 플레이어가 세션에서 나가면 제거가 일어납니다.
(PropAgent:agent).AwaitNoLongerAProp()<suspends>:void =
loop:
if (not FindOnTeam[PropAgent]):
Logger.Print("Cancelling prop agent behavior.")
break
Sleep(0.0) # 다음 게임 틱으로 진행합니다.
# 에이전트가 MinimumDistance보다 적게 이동할 때까지 루프합니다.
(PropAgent:agent).AwaitStopMoving(MinimumDistance:float)<suspends>:void =
Logger.Print("Checking if the agent has moved less than the minimum distance.")
# 씬 내 에이전트의 캐릭터로부터 에이전트의 초기 위치를 얻습니다.
if (Tracked := PropAgent.GetFortCharacter[]):
var StartPosition:vector3 = Tracked.GetTransform().Translation
loop:
Sleep(0.0) # 다음 게임 틱에서 에이전트의 위치를 얻습니다.
NewPosition := Tracked.GetTransform().Translation
# 시작 위치에서 새 위치까지의 거리가 MinimumDistance보다 짧은 경우, 에이전트는 이동하지 않은 것이며 루프가 중단됩니다.
if (Distance(StartPosition, NewPosition) < MinimumDistance):
Logger.Print("Agent has moved less than the minimum distance.")
break
# 그렇지 않으면 플레이어가 새 위치에서 이동하도록 하기 위해 StartPosition을 리셋합니다.
else:
set StartPosition = NewPosition
# 에이전트가 MinimumDistance보다 많이 이동할 때까지 루프합니다.
(PropAgent:agent).AwaitStartMoving(MinimumDistance:float)<suspends>:void =
Logger.Print("Checking if the agent moves further than the minimum distance.")
# 씬 내 에이전트의 캐릭터로부터 에이전트의 초기 위치를 얻습니다.
if (Tracked := PropAgent.GetFortCharacter[]):
StartPosition:vector3 = Tracked.GetTransform().Translation
loop:
Sleep(0.0) # 다음 게임 틱에서 에이전트의 위치를 얻습니다.
NewPosition := Tracked.GetTransform().Translation
# 시작 위치에서 새 위치까지의 거리가 MinimumDistance보다 길거나 같은 경우, 에이전트는 이동한 것이며 루프가 중단됩니다.
if (Distance(StartPosition, NewPosition) >= MinimumDistance):
Logger.Print("Agent has moved more than or equal to the minimum distance.")
break
# HeartBeatWarningTime이 시작될 때까지 딜레이합니다. 그런 다음 HeartBeatWarningTime으로 카운트다운하고 카운트다운 텍스트를 설정합니다. 지연되면 텍스트를 지웁니다.
CountdownTimer(PropAgent:agent)<suspends>:void =
Logger.Print("Starting heart beat countdown.")
if (UIData := HeartBeat.WarningUI[PropAgent]):
Sleep(HeartBeat.MoveTime - HeartBeat.WarningTime) # 경고가 표시되기 전 해당 시간 동안 슬립합니다.
Logger.Print("Starting heart beat warning.")
var WarningTimeRemaining:int = 0
if (set WarningTimeRemaining = Ceil[HeartBeat.WarningTime]) {}
# defer는 함수가 완료될 때나, 경합 패배와 같이 취소될 때 일어납니다.
# 따라서 이 경우에는 카운트다운이 완료되거나 사물 에이전트가 카운트다운 완료 전에 이동하면 경고 텍스트가 지워집니다.
defer:
UIData.Text.SetText(HeartBeatWarningClear)
# 경고 텍스트를 남은 시간으로 설정하고, 1초 대기한 다음, 남은 시간을 감소시킵니다. 카운트다운이 완료되면 루프가 중단됩니다.
loop:
Logger.Print("Heart beat in {WarningTimeRemaining} seconds.")
UIData.Text.SetText(HeartBeatWarningMessage(WarningTimeRemaining))
Sleep(1.0)
set WarningTimeRemaining -= 1
if (WarningTimeRemaining <= 0):
break
else:
Logger.Print("UIData not found.")
# 심장 박동 VFX 및 SFX를 켭니다. 지연되기까지 무한 대기한 다음 심장 박동 VFX 및 SFX를 비활성화합니다.
(PropAgent:agent).StartHeartbeat()<suspends>:void =
Logger.Print("Spawning heart beat.")
# PropAgent가 파괴되거나 게임에서 나간 후 나중에 defer에 전달할 수 있도록 심장 박동 데이터를 저장합니다.
var HeartBeatVFXData:heartbeat_vfx = heartbeat_vfx{}
if:
# PropAgents 배열에서 사물 에이전트의 인덱스를 얻은 다음, 해당하는 심장 박동 VFX 액터에 액세스합니다.
Index := FindOnTeam[PropAgent]
set HeartBeatVFXData = HeartBeat.AgentVFX[Index]
then:
HeartBeat.Enable(PropAgent, HeartBeatVFXData)
# 이 함수가 사물 에이전트의 이동, 처치됨 또는 플레이어의 세션 퇴장으로 인해 취소되면 심장 박동을 비활성화합니다.
defer:
HeartBeat.Disable(PropAgent, HeartBeatVFXData)
Sleep(Inf) # 경합이 완료될 때까지 슬립을 중단하지 않습니다.
round_timer.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_round_timer_device := class(log_channel){}
# 지정된 범위 사이에 값을 허용하는 인티저입니다. 이 타입은 player_ui_slot.ZOrder에 필요합니다.
round_int_clamped := type{_X:int where 0 <= _X, _X <= 2147483647}
# 이 메시지는 라운드 종료 전 남은 시간을 출력하는 데 사용됩니다.
TimeRemainingMessage<localizes>(Minutes:string, Seconds:string):message = "{Minutes}:{Seconds}"
<#
이 클래스에는 라운드 시간을 관리하고 화면에 시간을 표시하기 위한 모든 로직이 포함되어 있습니다.
이 장치를 round_settings_device와 함께 사용하여 실제로 라운드를 종료할 수 있습니다.
이 장치는 타이머를 사용하지 않고 시간을 관리합니다.
이 클래스를 사용하려면 다음 단계를 따릅니다.
1) 프로젝트에 파일을 추가합니다.
2) 툴바의 Verse 메뉴에서 Verse 코드를 컴파일합니다.
3) 콘텐츠 브라우저에 있는 섬의 콘텐츠(Content)/Creative Devices 폴더에서 장치를 섬으로 드래그합니다.
4) 다음을 통해 또 다른 Verse 스크립트에 waiting_for_more_players 클래스를 추가합니다.
@editable
RoundTimer:round_timer = round_timer{}
5) 툴바의 Verse 메뉴에서 Verse 코드를 컴파일합니다.
6) 3단계에서 만든 장치를 Verse 장치에 연결합니다.
7) 다음 Verse로 라운드 타이머를 시작합니다.
RoundTimer.Start()
8) 해당 함수로 타이머를 재시작하거나 중지합니다.
9) 다음을 통해 타이머 시작을 대기합니다.
RoundTimer.AwaitStart()
10) 다음을 통해 타이머 완료를 대기합니다.
RoundTimer.AwaitEnd()
round_settings_device에서 EndRound 함수를 호출하여 실제로 게임 라운드를 종료합니다.
#>
round_timer := class(creative_device):
Logger:log = log{Channel:=log_prop_hunt_device}
@editable # 라운드 지속 시간(분)입니다.
RoundTimeInMinutes:float = 5.0
@editable # 화면상 타이머 UI의 수평 및 수직 위치입니다. X 0~1은 왼쪽~오른쪽, Y 0~1은 상단~하단입니다.
UIPosition:vector2 = vector2{X:= 0.98, Y:=0.13}
@editable # 화면상 타이머 UI의 수평 및 수직 위치입니다. X 0~1은 왼쪽~오른쪽, Y 0~1은 상단~하단입니다.
UIAlignment:vector2 = vector2{X := 1.0, Y := 0.0}
@editable # 다른 UI 엘리먼트와 비교한 UI의 Z 순서입니다.
UIZOrder:round_int_clamped = 4
# 라운드가 시작되었을 때 신호를 전송합니다.
RoundStarted:event() = event(){}
# 라운드가 종료되려고 할 때 신호를 전송합니다.
RoundEndedEvent:event() = event(){}
# 이 맵은 각 플레이어에게 시간을 표시하는 텍스트 박스와 연관되어 있습니다.
var TimeRemainingTextBlocks:[player]text_block = map{}
# 라운드가 완료되기 전 남은 시간으로, 인티저로 표현됩니다.
var TimeRemainingInSeconds:int = 0
# 라운드 타이머가 시작될 때까지 대기합니다.
AwaitStart()<suspends>:void =
RoundStarted.Await()
Logger.Print("Round timer started.")
# 라운드 타이머를 시작하는 데 사용됩니다.
Start():void =
Logger.Print("Starting the round timer.")
RoundStarted.Signal()
set TimeRemainingInSeconds = GetRoundTimeInSeconds()
spawn{ Running() }
# 타이머를 RoundTime으로 재시작합니다.
Restart():void =
Logger.Print("Restarting the round timer.")
set TimeRemainingInSeconds = GetRoundTimeInSeconds()
# 타이머 로직을 실행합니다.
Running()<suspends>:void =
Logger.Print("Round timer running.")
loop:
UpdateTimeUI()
Sleep(1.0)
# TimeRemaining을 1초 줄인 다음, 시간이 다 되었는지 확인합니다. 다 되었다면 라운드를 종료합니다.
set TimeRemainingInSeconds -= 1
if (TimeRemainingInSeconds < 0):
Stop()
break
# 타이머를 중지하고 라운드를 종료합니다.
Stop():void =
Logger.Print("Ending the round timer.")
# 씬에 남은 플레이어로부터 플레이어를 얻어 라운드를 종료합니다.
Players:[]player = GetPlayspace().GetPlayers()
if (Instigator := Players[0]):
RoundEndedEvent.Signal()
# 라운드 타이머가 종료되기 직전까지 대기합니다.
AwaitEnd()<suspends>:void =
RoundEndedEvent.Await()
Logger.Print("Round timer ended.")
# 시간 값을 분 단위로 수락하여 초 단위로 반환합니다.
GetRoundTimeInSeconds():int =
var InSeconds:int = 0
if (set InSeconds = Round[RoundTimeInMinutes * 60.0]) {}
InSeconds
# 타이머가 완료되면 남은 시간을 업데이트하고 시간이 만료되었는지 확인합니다.
UpdateTimeUI():void =
# Minutes를 나머지 없이 TimeRemainingInSeconds/60으로 설정합니다.
var Minutes:int = 0
if (set Minutes = Floor(TimeRemainingInSeconds / 60)) {}
# Seconds를 TimeRemainingInSeconds/60의 나머지로 설정합니다.
var Seconds:int = 0
if (set Seconds = Mod[TimeRemainingInSeconds, 60]) {}
# Minutes 및 Seconds를 스트링으로 변환합니다.
MinutesAsString := string("{Minutes}")
# Seconds가 10 미만인 경우 :#가 아닌 :0#로 표시되도록 값 앞부분에 0을 추가해야 합니다.
SecondsAsString := if (Seconds < 10) then Join(array{string("{0}"),string("{Seconds}")},string()) else string("{Seconds}")
# 모든 플레이어를 대상으로 반복작업하고, TimeRemainingTextBlock이 있는지 확인합니다. 없는 경우 하나를 부여합니다. 그런 다음 텍스트를 업데이트합니다.
Players:[]player = GetPlayspace().GetPlayers()
for (Player : Players):
var TextBlock:text_block = text_block{}
if (set TextBlock = TimeRemainingTextBlocks[Player]) {}
else:
set TextBlock = SetUpTimeRemainingUI(Player)
TextBlock.SetText(TimeRemainingMessage(MinutesAsString, SecondsAsString))
# 플레이어를 수락한 다음, 화면에 라운드 시간 UI 캔버스를 추가하고 향후 업데이트를 위해 TimeRemainingTextBlock을 저장합니다.
SetUpTimeRemainingUI(Player:player):text_block =
Logger.Print("Adding round timer UI to a player.")
# 화면에 남은 시간 텍스트를 출력하는 text_block입니다.
TextBlock:text_block = text_block:
DefaultTextColor := NamedColors.White
DefaultShadowColor := NamedColors.Black
if (PlayerUI := GetPlayerUI[Player]):
if (set TimeRemainingTextBlocks[Player] = TextBlock) {}
# 화면에 text_block을 고정하고 위치를 지정하는 캔버스입니다.
Canvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := UIPosition, Maximum := UIPosition}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := UIAlignment
SizeToContent := true
Widget := TextBlock
# 캔버스를 플레이어에게 할당합니다.
PlayerUI.AddWidget(Canvas, player_ui_slot{ZOrder := UIZOrder})
# text_block을 맵에 저장하고 시간이 줄어듦에 따라 향후 업데이트할 수 있도록 text_block이 반환됩니다.
return TextBlock
waiting_for_more_players.verse
using { /Fortnite.com/Devices }
using { /Fortnite.com/UI }
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /UnrealEngine.com/Temporary/UI }
using { /Verse.org/Colors }
using { /Verse.org/Simulation }
log_waiting_for_more_players_device := class(log_channel){}
# 지정된 범위 사이에 값을 허용하는 인티저입니다. 이 타입은 player_ui_slot.ZOrder에 필요합니다.
waiting_int_clamped := type{_X:int where 0 <= _X, _X <= 2147483647}
# 이 메시지는 라운드를 시작할 수 있기까지 필요한 플레이어 수를 출력하는 데 사용됩니다.
WaitingForMorePlayersMessage<localizes>(Count:int):message = "Waiting for {Count} more Player(s)"
# 이 클래스는 라운드를 시작하는 데 필요한 플레이어 수를 표시하는 용도입니다.
waiting_for_more_players_ui := class:
var Canvas:canvas
var TextBlock:text_block
<#
이 클래스에는 최소 플레이어 수를 설정하고 라운드를 시작하기에 충분한지 확인하기 위한 모든 로직이 포함되어 있습니다.
이 클래스를 사용하려면 다음 단계를 따릅니다.
1) 프로젝트에 파일을 추가합니다.
2) 툴바의 Verse 메뉴에서 Verse 코드를 컴파일합니다.
3) 콘텐츠 브라우저에 있는 섬의 콘텐츠(Content)/Creative Devices 폴더에서 장치를 섬으로 드래그합니다.
4) 타이머 장치를 이 장치의 'WaitingForMorePlayersTimer' 프로퍼티에 연결합니다.
5) 다음을 통해 또 다른 Verse 스크립트에 waiting_for_more_players 클래스를 추가합니다.
@editable
WaitingForMorePlayers:waiting_for_more_players = waiting_for_more_players{}
6) 툴바의 Verse 메뉴에서 Verse 코드를 컴파일합니다.
7) 3단계에서 만든 장치를 6단계에서 노출한 Verse 장치 및 프로퍼티에 연결합니다.
8) 플레이어에 전달하여 CheckForMinimumNumberOfPlayers 함수를 대기합니다. 예시:
Players = GetPlayspace().GetPlayers()
CheckForMinimumNumberOfPlayers(Players)
9) IslandSettings에서 게임 시작 카운트다운을 0.0으로 설정합니다.
#>
waiting_for_more_players := class(creative_device):
Logger:log = log{Channel:=log_waiting_for_more_players_device}
@editable # 라운드를 시작하기 위해 매치에 필요한 최소 플레이어 수입니다.
MinimumNumberOfPlayers:int = 2
@editable # 화면상 타이머 UI의 수평 및 수직 위치입니다. X 0~1은 왼쪽~오른쪽, Y 0~1은 상단~하단입니다.
UIPosition:vector2 = vector2{X:= 0.5, Y:=0.4}
@editable # 화면상 타이머 UI의 수평 및 수직 위치입니다. X 0~1은 왼쪽~오른쪽, Y 0~1은 상단~하단입니다.
UIAlignment:vector2 = vector2{X := 0.5, Y := 0.5}
@editable # 다른 UI 엘리먼트와 비교한 UI의 Z 순서입니다.
UIZOrder:waiting_int_clamped = 3
@editable # 이 타이머는 플레이어가 매치에 참가하기를 기다린 후 라운드 시작까지 카운트다운하는 데 사용됩니다.
WaitingForMorePlayersTimer:timer_device = timer_device{}
# 이 맵은 라운드를 시작하는 데 필요한 플레이어 수를 표시하기 위한 UI 캔버스를 각 플레이어에게 연결합니다.
var WaitingForMorePlayersUI:[player]?waiting_for_more_players_ui = map{}
# 라운드를 시작하기에 충분한 수의 플레이어가 있는지 확인합니다. 없는 경우 플레이어 수가 MinimumNumberOfPlayers보다 크거나 같아질 때까지 대기합니다.
WaitForMinimumNumberOfPlayers(Players:[]player)<suspends>:[]player =
Logger.Print("Waiting if there are enough players for the round to start.")
# 더 많은 플레이어가 참가하면 수정할 수 있도록 새 변수를 생성합니다. 함수에 전달된 플레이어 배열로 초기화합니다.
var PlayersWaiting:[]player = Players
# 플레이어 수가 라운드를 시작하는 데 필요한 최솟값 미만인 경우...
if (PlayersWaiting.Length < MinimumNumberOfPlayers):
loop: # 플레이어 수가 필요한 최솟값보다 크거나 같아질 때까지 루프합니다.
Logger.Print("{PlayersWaiting.Length}/{MinimumNumberOfPlayers} players needed for the round to start.")
# '더 많은 플레이어 기다리는 중(Waiting for more players)' UI를 업데이트합니다.
DisplayWaitingForMorePlayers(PlayersWaiting)
Sleep(2.0) # 더 많은 플레이어가 매치에 참가하는지 확인하기 위해 대기한 다음, 라운드를 시작하기에 충분한 수의 플레이어가 있는지 확인합니다.
set PlayersWaiting = GetPlayspace().GetPlayers()
if (PlayersWaiting.Length >= MinimumNumberOfPlayers):
# 이제 플레이어가 충분하다면 '더 많은 플레이어 기다리는 중' UI를 지웁니다.
Logger.Print("{PlayersWaiting.Length}/{MinimumNumberOfPlayers} players in round, preparing for round start.")
ClearWaitingForMorePlayers()
# 그런 다음 루프를 중단합니다.
break
# 라운드 시작 카운트다운을 시작하고, 카운트다운이 완료될 때까지 대기합니다.
WaitingForMorePlayersTimer.Start()
WaitingForMorePlayersTimer.SuccessEvent.Await()
Logger.Print("Starting round.")
# 세션 내 플레이어 목록을 반환합니다.
return PlayersWaiting
# 각 플레이어에게 '더 많은 플레이어 기다리는 중' UI 메시지가 없는 경우 해당 메시지를 표시합니다. 모든 플레이어의 플레이어 카운터를 업데이트합니다.
DisplayWaitingForMorePlayers(Players:[]player):void =
PlayersNeededCount := MinimumNumberOfPlayers - Players.Length
Logger.Print("{Players.Length} players in round, waiting for {PlayersNeededCount} more player(s) to join.")
for (Player: Players):
# 플레이어에게 WaitingForMorePlayersUI가 있는 경우 라운드를 시작하는 데 필요한 올바른 플레이어 수를 표시하도록 TextBlock을 얻고 텍스트를 새로고침합니다.
if (UIData := WaitingForMorePlayersUI[Player]?):
UIData.TextBlock.SetText(WaitingForMorePlayersMessage(PlayersNeededCount))
# 그렇지 않으면 플레이어에게 WaitingForMorePlayersUI를 생성합니다.
else:
SetUpWaitingForMorePlayersUI(Player, PlayersNeededCount)
# 플레이어 및 player_ui를 수락하여 화면에 '더 많은 플레이어 기다리는 중' UI 캔버스를 추가합니다.
SetUpWaitingForMorePlayersUI(Player:player, PlayersNeededCount:int):void =
Logger.Print("Creating 'waiting for more players' UI.")
if (PlayerUI := GetPlayerUI[Player]):
# 화면에 '더 많은 플레이어 기다리는 중' 텍스트를 출력하는 text_block입니다.
TextBlock:text_block = text_block:
DefaultText := WaitingForMorePlayersMessage(PlayersNeededCount)
DefaultTextColor := NamedColors.White
DefaultShadowColor := NamedColors.Black
# 화면에 text_block을 고정하고 위치를 지정하는 캔버스입니다.
Canvas := canvas:
Slots := array:
canvas_slot:
Anchors := anchors{Minimum := UIPosition, Maximum := UIPosition}
Offsets := margin{Top := 0.0, Left := 0.0, Right := 0.0, Bottom := 0.0}
Alignment := UIAlignment
SizeToContent := true
Widget := TextBlock
# 캔버스를 플레이어에게 할당합니다.
PlayerUI.AddWidget(Canvas, player_ui_slot{ZOrder := UIZOrder})
# 더 많은 플레이어가 게임에 참가하면 나중에 텍스트를 업데이트할 수 있도록 text_block을 맵에 저장합니다.
if (set WaitingForMorePlayersUI[Player] = option{ waiting_for_more_players_ui{Canvas := Canvas, TextBlock := TextBlock} }) {}
# '더 많은 플레이어 기다리는 중' UI 메시지가 있는 각 플레이어로부터 해당 메시지를 제거합니다.
ClearWaitingForMorePlayers():void =
Logger.Print("Clearing 'waiting for more players' UI.")
for (Player -> UIData : WaitingForMorePlayersUI):
if:
PlayerUI := GetPlayerUI[Player]
Canvas := UIData?.Canvas
set WaitingForMorePlayersUI[Player] = false
then:
PlayerUI.RemoveWidget(Canvas)