레이싱 게임에서는 플레이어가 이전 라운드를 얼마나 잘 수행했는지에 따라 시작 위치가 달라지는 것이 일반적입니다. 이는 플레이어가 1위를 하지 못하더라도 다른 플레이어보다 앞서 시작하기 위해 경주를 더 빨리 마치도록 장려합니다.
이를 위해 게임에서 플레이어가 현재 어느 라운드에 있는지 알아야 하며, 레이서 완주 순서는 모든 게임 세션이 아닌, 모든 라운드에 걸쳐 유지되어야 합니다. Verse의 세션 Weak 맵 변수는 라운드마다 데이터를 리셋하므로, 이 라운드 정보는 플레이어 Weak 맵 변수를 사용하여 각 플레이어와 함께 저장되어야 하며 게임 종료 후 리셋됩니다.
현재 하나의 프로젝트는 최대 2개의 플레이어 Weak 맵 변수만 보유할 수 있습니다. 프로젝트에 이미 플레이어 Weak 맵 변수가 하나 있는 경우, 항상 유지되어야 하는 데이터를 게임이 종료되거나 플레이어가 세션에서 나간 후 리셋할 데이터와 구분하기 위해 두 번째 변수가 라운드 정보를 기록하도록 하는 것이 좋습니다.
또한 라운드별 로직을 적용하고 마지막 라운드에서 라운드 정보를 리셋하기 위해 플레이어가 현재 있는 라운드를 아는 것도 중요합니다. 지금으로서는 현재 라운드를 가져올 수 있는 API가 없기 때문에, 이 정보는 각 플레이어의 퍼시스턴스 데이터에도 기록되어야 합니다.
요약하자면, 최소한 다음 정보가 있는 플레이어 Weak 맵 변수가 필요합니다.
완주 순서
마지막 완주 라운드
다음 섹션에는 이 데이터와 라운드 로직을 구성하는 방법이 나와 있습니다. 페이지 마지막에 완성된 코드를 확인할 수 있습니다.
마지막 완주 라운드 기록하기
각 플레이어에 대한 퍼시스턴스 데이터를 구성하고 마지막 완주 라운드를 기록하려면 다음 단계를 따릅니다.
여러 라운드의 플레이어 정보를 저장하기 위해
player_circuit_info로 명명된 퍼시스턴스 클래스를 생성합니다. 이 클래스에는 플레이어의 마지막 완주 순서인LastRoundFinishOrder및 마지막 완주 라운드인LastCompletedRound를 나타내는 필드가 있어야 합니다. 이러한 필드는 유효하지 않은 값을 나타내기 위해-1로 초기화되므로, 필드에 유용한 정보가 실제로 포함되는 시점을 알 수 있습니다.Verse# Tracks the number of and in what order a player finished the previous round. player_circuit_info<public> := class<final><persistable>: Version:int = 0 LastRoundFinishOrder:int = -1 LastCompletedRound<public>:int = -1라운드 정보를 플레이어와 함께 유지하기 위해
player_circuit_info클래스를 사용하여 플레이어 Weak 맵 변수를 생성합니다.Verse# A persistable map that maps each player to # what order they finished the previous round. var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}퍼시스턴스 데이터 작업의 모범 사례로서, 퍼시스턴스 클래스의 생성자를 생성하여 각 플레이어의 정보를 손쉽게 업데이트할 수 있도록 합니다. 자세한 내용은 부분 업데이트에 생성자 사용하기를 참고하세요.
Verse# Creates a new player_circuit_info from the given older player_circuit_info. MakePlayerCircuitInfo<constructor>(OldPlayerCircuitInfo:player_circuit_info)<transacts> := player_circuit_info: Version := OldPlayerCircuitInfo.Version LastRoundFinishOrder := OldPlayerCircuitInfo.LastRoundFinishOrder LastCompletedRound := OldPlayerCircuitInfo.LastCompletedRound이제 이 데이터의 구조를 정의했으니, 플레이어의 완주 순서를 기록하고 퍼시스턴스 데이터를 업데이트하는 함수를 추가합니다. 이 함수는 이전 단계에서 만든 생성자를 사용하여 유일한 관련 정보인 완주 순서 데이터를 부분적으로 업데이트합니다.
Verse# Creates a new player_circuit_info for the given player with the order they finished the round in. RecordPlayerFinishOrder<public>(Agent:agent, FinishOrder:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo:player_circuit_info = if: Info := CircuitInfo[Player] then: Info else: player_circuit_info{}플레이어의 마지막 완주 라운드만 업데이트하기 위한 또 다른 함수를 생성합니다.
Verse# Updates a player's player_circuit_info with their last completed round. UpdateRound<public>(Agent:agent, CompletedRound:int)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastCompletedRound := CompletedRound이제 플레이어의 마지막 완주 라운드를 기록할 수 있으므로, 기록된 최신 라운드가 있는 플레이어를 확인하여 게임의 마지막 완주 라운드를 계산하는 함수를 생성합니다. 진행 중인 세션에 참가했을 수도 있는 플레이어까지 고려하려면 모든 플레이어를 확인해야 합니다. 마지막 완주 라운드 변수는 유효하지 않은 데이터를 나타내기 위해
-1로 초기화됩니다.-1보다 큰 값을 가진 플레이어가 있다면 라운드가 이미 종료된 것입니다.Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # Update LastCompletedRound if this player has the highest last completed round. else if:라운드와 플레이어 완주 순서가 예상대로 작동하는지 테스트하기 위한 Verse 장치를 생성합니다. 섬 설정에서 총 라운드 프로퍼티를 설정하여 프로젝트가 여러 라운드로 구성되도록 합니다.
Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1 Print("Current round is {CurrentRound}") for:
플레이어가 떠날 때 라운드 정보 리셋하기
라운드 정보에 대한 플레이어의 퍼시스턴스 데이터는 플레이어가 게임 도중 떠날 때 리셋되어야 합니다. 플레이스페이스의 PlayerRemovedEvent에 등록하여 플레이어가 떠나는 시점을 알 수 있습니다.
플레이어가 떠날 때 라운드 정보를 리셋하려면 다음 단계를 따릅니다.
라운드 정보에 대한 플레이어의 퍼시스턴스 데이터를 리셋하는 함수를 생성합니다. 즉, 이러한 필드의 유효하지 않은 데이터를 나타내기 위해 값을
-1또는 결정한 임의의 값으로 설정해야 합니다. 이러한 구현은 여기에서 리셋해서는 안 되는 퍼시스턴스 클래스에 추후 다른 정보를 추가할 경우에 대비하여 어떤 데이터를 리셋해야 할지 선택합니다.Verse# Resets a player's player_circuit_info. ResetCircuitInfo<public>(Agent:agent)<decides><transacts>:void= Player := player[Agent] Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] set CircuitInfo[Player] = player_circuit_info: MakePlayerCircuitInfo<constructor>(PlayerCircuitInfo) LastRoundFinishOrder := -1 LastCompletedRound := -1플레이어가 떠날 때 라운드 정보를 리셋하기 위해
OnPlayerRemoved로 명명된 함수를 생성합니다.Verse# When a player is removed from the race, reset their circuit to prevent their # stats from showing up on billboards and player references. OnPlayerRemoved(Player:player):void= # Reset circuit info when player leaves the game. if: ResetCircuitInfo[Player] else: Print("Unable to reset circuit info for player")GetPlayspace().PlayerRemovedEvent()이벤트에 등록하여 플레이어가 게임을 떠날 때 호출할OnPlayerRemoved함수를 구성합니다.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved) Players := GetPlayspace().GetPlayers() CurrentRound := GetLastCompletedRound(Players) + 1플레이어가 게임을 떠나면 플레이어의 정보가 리셋되는지 확인하기 위한 테스트를 진행합니다.
게임 종료 시 라운드 정보 리셋하기
Verse 장치의 OnBegin 함수는 라운드 시작 시마다 실행됩니다. 이때 마지막 완주 라운드가 총 라운드 수와 동일한 경우와 같이 플레이어에게 예상치 못한 퍼시스턴스 데이터가 있는지 확인하는 것이 좋습니다. 있는 경우, 플레이어의 해당 데이터를 리셋해야 합니다. 현재 게임 내 총 라운드 수를 파악하는 용도의 API는 없습니다. 대신 Verse 코드의 총 라운드에 대한 편집 가능 프로퍼티를 Verse 장치에 추가하고 섬 설정의 총 라운드 프로퍼티와 일치하도록 해야 합니다.
게임 종료 시 라운드 정보를 리셋하려면 다음 단계를 따릅니다.
Verse 장치에 게임 내 총 라운드 수에 대한 편집 가능 프로퍼티를 추가합니다. TotalRounds가
0이하가 되지 않도록 프로퍼티 값을1이상으로 제한합니다.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)GetLastCompletedRound의 함수를 업데이트하여 마지막 완주 라운드에 대한 플레이어의 퍼시스턴스 데이터가 게임 내 예상 라운드 수보다 큰 경우 리셋합니다.Verse# Returns the highest last completed round among all players. GetLastCompletedRound<public>(Players:[]player, TotalRounds:int)<transacts>:int= var LastCompletedRound:int = -1 for: Player : Players Player.IsActive[] PlayerCircuitInfo := CircuitInfo[Player] do: # If player's recorded info is greater than the total rounds for whatever reason, # then need to reset the player's circuit info because they shouldn't have more than what's allowed.플레이어 라운드 정보가 예상치 못한 상태인 경우 이를 리셋하기 위해
GetLastCompletedRound에 대한 호출을 업데이트하여 총 라운드를 실행인자로 전달합니다.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)모든 라운드를 플레이 및 완료한 후에도 플레이어 라운드 정보가 리셋되는지 확인하기 위한 테스트를 진행합니다.
현재 라운드 기반 로직 추가하기
이제 이 정보를 사용하여 플레이어의 현재 라운드에 따라 커스텀 로직을 보유할 수 있습니다. 예를 들어 게임의 첫 번째 라운드에서 로컬 순위표를 보여주고자 할 수 있습니다.
어떤 라운드를 플레이하고 있는지 알아야 할 때마다 GetLastCompletedRound()를 호출하는 것은 바람직하지 않습니다. 대신 라운드당 한 번 이 작업을 수행하고 라운드 정보를 세션 Weak 맵 변수에 기록하면 됩니다. 그러면 프로젝트의 모든 Verse 코드가 매번 다시 계산하지 않아도 언제든지 이 값에 액세스할 수 있습니다.
이는 코드에서 세션 Weak 맵 변수와 플레이어 Weak 맵 변수를 사용하는 경우의 차이점과 그 이유를 보여주는 좋은 예입니다.
세션 Weak 맵 변수는 싱글톤과 매번 다시 계산하길 원치 않는 현재 라운드의 데이터를 저장하는 데 있어 유용합니다.
플레이어 Weak 맵 변수는 여러 라운드와 게임 세션 간에 유지되어야 하지만 개별 플레이어와 연결되어 있어야 하는 정보를 위해 마련되었습니다.
현재 라운드를 저장하기 위한 세션 Weak 맵 변수를 구성하려면 다음 단계를 따릅니다.
라운드 정보를 저장할 클래스를 생성합니다. 적어도
CurrentRound에 대한 필드가 하나 필요하며, 플레이어의 시작 위치 및 탈것 등 모든 Verse 코드에 걸쳐 저장할 다른 라운드 정보도 포함할 수 있습니다. 유효하지 않은 데이터를 나타내도록CurrentRound를-1로 초기화합니다.Verseround_info := class: CurrentRound:int = -1라운드 정보를 세션과 함께 저장하기 위해
round_info클래스를 사용하여 세션 Weak 맵 변수를 생성합니다.Verse# Maps the current session to its associated round info. var RoundInfo:weak_map(session, round_info) = map{}세션 Weak 맵 변수에서 현재 라운드를 얻는 게터 함수를 추가합니다.
VerseGetRound<public>()<decides><transacts>:int= RoundInfo[GetSession()].CurrentRound현재 라운드를 얻어 세션 Weak 맵 변수에 저장하는 함수를 추가합니다.
VerseRecordCurrentRound<public>(Players:[]player, TotalRounds:int):void= var CurrentRoundInfo:round_info = if: Info := RoundInfo[GetSession()] then: Info else: LastCompletedRound := GetLastCompletedRound(Players, TotalRounds) round_info: CurrentRound := LastCompletedRound + 1Verse 장치가 새
RecordCurrentRound함수를 사용하고, 어느 라운드인지 알아야 할 때GetRound를 호출하도록 업데이트합니다.Verse# A Verse-authored creative device that can be placed in a level test_round_info_device := class(creative_device): # The total number of rounds in the race. @editable TotalRounds:type {Rounds:int where 1 <= Rounds} = 3 # Runs when the device is started in a running game OnBegin<override>()<suspends>:void= GetPlayspace().PlayerRemovedEvent().Subscribe(OnPlayerRemoved)
이제 이 정보가 세션 Weak 맵 변수에 저장되었으니, 라운드를 위한 커스텀 로직을 손쉽게 추가할 수 있습니다. 예를 들면, 첫 번째 라운드인지 확인하고 플레이어를 위한 대기실 및 순위표 보기 영역을 구성할 수 있습니다.
# Returns true if this is the first round of the game.
IsFirstRound<public>(RoundToCheck:int)<decides><transacts>:void=
RoundToCheck <= 0직접 해보기
플레이어의 시작 순서를 결정하기 위해 레이싱 게임에서 이 코드를 사용하는 방법은 Verse 퍼시스턴스가 적용된 스피드웨이 경주를 확인하세요.
템플릿을 확인한 후 다음과 같은 작업을 해 보세요.
어떤 탈것이 플레이어에게 할당되었는지 등 부가적인 라운드 정보를 추가합니다.
각 라운드 시작 시 맵의 다양한 영역으로 플레이어를 순간이동시킵니다.
라운드별 로직을 사용하는 다른 게임에는 어떤 것이 있을지 생각해 보세요.
완성된 코드
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
# A persistable map that maps each player to
# what order they finished the previous round.
var CircuitInfo<public>:weak_map(player, player_circuit_info) = map{}
# Maps the current session to its associated round info.
var RoundInfo:weak_map(session, round_info) = map{}