멀티플레이어 게임에서는 특정 목표를 달성하기 위해 플레이어들이 팀 단위로 경쟁하거나 협동합니다. 각 팀의 플레이어 수가 게임플레이에 큰 영향을 미치므로 개발자들은 즐거운 경험을 제공할 수 있도록 플레이어의 비율을 일정하게 조절합니다.
팀 밸런싱을 통해 설계한 비율대로 팀에 플레이어를 배분할 수 있습니다. 대부분의 멀티플레이어 게임에서는 특정 팀이 유리하지 않도록 팀을 균등하게 배분합니다. 하지만 4명의 플레이어가 1명의 강력한 플레이어와 경쟁하도록 만드는 등 의도적으로 균등하지 않은 상황을 만드는 게임도 있습니다. 어떻게 설정하건 플레이어들을 팀으로 나눠 흥미로운 경험을 제공하려면 팀 밸런스가 아주 중요합니다.
이 가이드를 마치면 새 플레이어가 게임에 참가할 때마다 런타임에 다이내믹하게 팀 플레이어 밸런스를 맞추는 방법을 익히게 됩니다. 완성된 스크립트가 이 가이드 끝에 참고용으로 포함되어 있습니다.
사용된 Verse 언어 기능
option: 이 장치는 옵션을 사용해 특정 플레이어가 현재 속한 팀보다 플레이어 수가 적은 다른 팀이 있는지 확인합니다.
사용된 Verse API
Subscribable:
PlayerAddedEvent()에 등록해 진행 중인 게임에 새 플레이어가 참가했을 때 팀의 밸런스를 동적으로 재조정합니다.Teams: 팀 클래스는 플레이어를 팀에 추가하고, 팀에서 플레이어를 얻거나 제거합니다. 이 튜토리얼에서는 팀 클래스를 사용해 직접 플레이어를 조작하고 팀 배정을 변경할 것입니다.
Playspace: 플레이 스페이스는 플레이어의 게임 참가 및 퇴장과 관련된 등록 가능한 이벤트를 추적합니다. 또한 플레이어 및 팀 목록을 얻어 특정 플레이어의 팀을 찾는 작업도 처리합니다. 이 튜토리얼에서는 다양한 플레이스페이스 이벤트를 등록하고, 직접 조작할 수 있도록 플레이스페이스 메서드를 사용해 플레이어와 팀을 얻을 것입니다.
레벨 구성하기
이 예시에서는 다음 장치를 사용합니다.
플레이어 생성 패드 장치(4): 이 장치는 플레이어가 게임 시작 시에 생성될 위치를 정의합니다.
레벨을 구성하려면 다음 단계를 따릅니다.
레벨에 플레이어 생성 패드(Player Spawn Pad)를 하나 추가합니다.
아웃라이너(Outliner)에서 생성 패드를 선택하고 디테일(Details) 패널을 엽니다.
디테일 패널의 사용자 옵션(User Options)을 다음과 같이 설정합니다.
플레이어 팀(Player Team)을 팀 인덱스(Team Index)로 설정하고 값을 1로 설정
게임 내 표시(Visible in Game) 활성화
이미지를 클릭하면 확대됩니다.
생성 패드를 복제해 첫 번째 생성 패드 옆에 배치합니다.
두 개의 생성 패드를 모두 복제해 기존의 생성 패드들로부터 멀리 떨어진 곳에 배치합니다. 이곳이 팀 2의 플레이어들이 생성될 장소입니다.
복제한 생성 패드를 모두 선택하고 디테일 패널 아래의 사용자 옵션에서 팀 인덱스 값을 모두 2로 변경합니다.
아웃라이너에서 섬 설정(IslandSettings) 장치를 선택하고 디테일 패널을 엽니다. 사용자 옵션 - 게임 규칙(User Options - Game Rules)을 다음과 같이 설정합니다.
팀(Teams)을 팀 인덱스로 설정하고 값을 2로 설정합니다. 이 예시에서는 2개의 팀을 사용하지만, 원하는 만큼 팀을 생성할 수도 있습니다.
팀 크기(Team Size)를 동적(Dynamic)으로 설정합니다. Verse 코드가 팀 밸런싱을 처리할 수 있도록 만드는 설정입니다.
게임 중 합류(Join in Progress)를 생성(Spawn)으로 설정해 게임 실행 중에 새 플레이어가 참가할 수 있도록 합니다.
이미지를 클릭하면 확대됩니다.
Verse 익스플로러를 사용해 새 Verse 장치 team_multiplayer_balancing을 만들어 레벨로 드래그합니다. (Verse에서 새 장치를 만드는 방법을 알아보려면 Verse를 사용하여 나만의 장치 만들기를 참고하세요.)
레벨은 다음과 같은 모습이어야 합니다.
팀을 균등하게 나누기
게임 시작 시 팀 밸런스 맞추기
이 단계에서는 게임을 시작했을 때와 새 플레이어가 게임에 참가했을 때 팀에 플레이어를 균등하게 배분하는 방법을 학습합니다.
Verse 익스플로러를 열고 team_multiplayer_balancing.verse를 더블클릭하여 Visual Studio Code에서 스크립트를 엽니다.
team_multiplayer_balancing클래스 정의에 플레이어들이 있는 각 팀의 레퍼런스를 저장할team배열 변수Teams를 추가합니다.Verseteam_multiplayer_balance := class(creative_device): # Holds the teams found with GetTeams() var Teams : []team = array{}섬 설정에서 앞서 구성한 팀에 맞게
OnBegin()함수에서Teams를 업데이트합니다.fort_team_collectionAPI의GetTeams()함수를 호출해 플레이스페이스 내의 모든 팀을 얻습니다.VerseOnBegin<override>()<suspends>:void= Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams()GetPlayers()함수를 호출해 게임 내 모든 플레이어를 얻어서 플레이어 배열AllPlayers에 저장합니다.VerseOnBegin<override>()<suspends>:void= Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams() AllPlayers := GetPlayspace().GetPlayers()모든 플레이어의 목록을 순회하며 반복작업으로 플레이어 수가 동일하게 배분된 팀들을 생성합니다. 특정 플레이어가 현재 속한 팀을 다른 팀들과 비교해 해당 플레이어를 이동해야 할지 결정합니다. 여러 팀이 있는 게임 모드에서 모든 플레이어는 팀에 속해 있어야 하므로, 이 경우에는 게임이 시작했을 때
GetTeam[]을 사용해 플레이어를 팀에 자동으로 할당할 수 있습니다.GetTeam[]은 에이전트 타입의 파라미터가 필요하지만, 플레이어가 에이전트의 서브클래스이므로 타입 형변환 없이 플레이어를 전달할 수 있습니다.VerseAllPlayers := GetPlayspace().GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): # Assign Players to a new team if teams are unbalanced팀은 내부 클래스이므로 인스턴스화할 수 없으며 기존 팀 오브젝트의 레퍼런스로만 사용될 수 있습니다.
모든 팀의 밸런스가 맞을 때까지 플레이어 수가 가장 적은 팀에 플레이어를 배정해야 합니다. 이를 위해
for루프를 사용해 각 플레이어를 확인하고 각 팀을 확인합니다. for 루프를 두 개 사용해 하나로 플레이어를 반복작업하고 다른 하나로 팀을 반복작업할 수도 있지만, 이 예시에서는 팀을 반복작업하는 for 루프를 별도의 메서드로 분리할 것입니다. 각 플레이어에 대한 레퍼런스를 얻은 다음,CurrentTeam으로 명명된 상수에서 플레이어가 속한 각 팀에 대한 레퍼런스를 얻어 플레이어 for 루프를 구성합니다.새 integer 변수
TeamSize를 생성하고0으로 초기화한 후, 이 플레이어가 현재 속한 팀의 크기를 대입합니다.GetAgents[]는 실패 가능 표현식이므로 if 명령문으로 감싸야 합니다.Versefor (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): # Assign Players to a new team if teams are unbalanced var TeamSize : int = 0 if(set TeamSize = GetPlayspace().GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}")FindSmallestTeam()으로 명명된 메서드를 생성합니다. 이 메서드는TeamSize를 실행인자로 전달하면 팀 옵션(?team)을 반환하고, 가장 플레이어 수가 적은 팀을 찾아서 반환합니다.FindSmallestTeam()내의 새 팀 옵션SmallestTeam을 초기화합니다. 옵션을 사용하는 이유는FindSmallestTeam()을 호출했을 때 플레이어가 이미 가장 작은 팀에 속해 있을 수 있기 때문입니다.SmallestTeam옵션의 기본값을 false로 설정했으므로, 더 작은 팀을 찾지 못하면 값이false로 유지됩니다.FindSmallestTeam()이false를 반환하면 정의에 따라 해당 플레이어가 이미 가장 작은 팀에 속해 있다는 것을 알 수 있습니다. 또한 int 변수CurrentTeamSize를TeamSize의 값으로 초기화해야 합니다. 플레이어 수가 더 적은 팀을 찾으면 값을 업데이트해야 하므로CurrentTeamSize는 변수로 선언해야 합니다.VerseFindSmallestTeam(CurrentTeamSize : int) : ?team= var SmallestTeam : ?team = false var TeamSize : int = CurrentTeamSizeTeamSize가SmallestTeam의 크기를 추적하므로, 각 팀의 크기와 비교해야 합니다. 각 팀을 반복작업하여 로컬 intCandidateTeamSize에서 팀 크기를 구합니다.CandidateTeamSize가TeamSize보다 작다면SmallestTeam을 이 팀으로 설정하고TeamSize를 해당 팀의 크기로 설정합니다.TeamSize > CandidateTeamSize조건은 for 루프의 괄호 안에서 확인되는 필터링 조건입니다. 필터링 조건을 사용하면 조건이 성공할 때만 루프 내의 코드가 실행됩니다. 따라서 플레이어 수가 가장 적은 팀이 발견된다면SmallestTeam이 해당 팀으로 설정됩니다. 플레이어 수가 더 적은 팀이 없다면SmallestTeam값은 false로 유지됩니다.마지막으로, 모든 팀을 확인했다면
SmallestTeam을 반환합니다.Versefor(Team : Teams, CandidateTeamSize := GetPlayspace().GetTeamCollection().GetAgents[Team].Length, TeamSize > CandidateTeamSize): set SmallestTeam = option{Team} set TeamSize = CandidateTeamSize Print("Found a team with less players: {CandidateTeamSize}") return SmallestTeamOnBegin()에서for루프 안에 새 팀 옵션SmallestTeam을 생성하고FindSmallestTeam()에TeamSize를 실행인자로 전달한 값으로 초기화합니다.VerseSmallestTeam : ?team = FindSmallestTeam(TeamSize)다음으로, 옵션 변수
SmallestTeam의 값에 액세스를 시도합니다. 값이 false라면 해당 플레이어는 이미 플레이어 수가 가장 적은 팀에 속해 있으므로 새 팀에 배정할 필요가 없습니다. 그렇지 않다면 플레이어를 새 팀에 배정해야 합니다. 옵션을 실행인자로 직접 전달하는 것을 허용하지 않는 메서드가 많으므로, 로컬 변수TeamToAssign으로 값을 추출해야 합니다.AddToTeam[player, team]을 사용해 플레이어를 이 팀에 할당하려고 시도할 수 있습니다. 플레이어가 이미 속해 있는 팀에 할당하려고 시도하는 경우 할당에 실패합니다. 하지만for루프는 해당 플레이어를 기존 팀에 유지하고 다음 플레이어의 반복작업으로 넘어갈 뿐이므로 부정적인 영향을 미치지는 않습니다.Verseif (TeamToAssign := SmallestTeam?, GetPlayspace().GetTeamCollection().AddToTeam[TeamPlayer, TeamToAssign]): Print("Attempting to assign player to a new team")
OnBegin()코드 블록은 이제 아래와 같습니다.VerseOnBegin<override>()<suspends> : void = Print("Verse Device Started!") set Teams = Self.GetPlayspace().GetTeamCollection().GetTeams() Print("Beginning to Assign Players") Playspace := GetPlayspace() AllPlayers := Playspace.GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := Playspace.GetTeamCollection().GetTeam[TeamPlayer]): var TeamSize : int = 0 if(set TeamSize = Playspace.GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}")
진행 중인 게임에 참가하는 플레이어 처리하기
진행 중인 게임에서도 자동으로 팀의 밸런스가 맞춰지는 것이 좋으므로, 새 플레이어가 참가할 때 발생하는 이벤트에 등록해야 합니다. 방금 작성한 모든 코드를 반복할 필요 없이 공통 메서드로 리팩터링하면 됩니다.
BalanceTeams()라는 메서드를 생성하고GetTeams()를 사용해Teams변수를 설정한 이후의 코드를 모두 옮깁니다. 이 메서드는 게임 시작 시 팀 밸런스를 맞추기 위해OnBegin()메서드에서 호출됩니다.BalanceTeams()코드는 다음과 같아야 합니다.VerseBalanceTeams() : void = AllPlayers := GetPlayspace().GetPlayers() for (TeamPlayer : AllPlayers, CurrentTeam := GetPlayspace().GetTeamCollection().GetTeam[TeamPlayer]): var TeamSize : int = 0 if(set TeamSize = GetPlayspace().GetTeamCollection().GetAgents[CurrentTeam].Length): Print("Size of this player's starting team is {TeamSize}") SmallestTeam : ?team = FindSmallestTeam(TeamSize) if (TeamToAssign := SmallestTeam?, GetPlayspace().GetTeamCollection().AddToTeam[TeamPlayer, TeamToAssign]): Print("Attempting to assign player to a new team")BalanceTeams()를 호출하는OnPlayerAdded()메서드를 추가로 생성합니다.player변수를 사용하지는 않지만, 이 메서드를 사용하여PlayerAddedEvent()에 등록하므로 메서드 정의에 필요합니다. 등록 가능 이벤트에 대한 자세한 내용은 장치 상호작용 코딩하기를 참고하세요.VerseOnPlayerAdded(InPlayer : player) : void = Print("A new Player joined, assigning them to a team!") BalanceTeams()OnBegin()에서OnPlayerAdded를 사용해PlayerAddedEvent()에 등록합니다. 이제 플레이어가 게임에 참가하면OnPlayerAdded가BalanceTeams()를 호출해 자동으로 팀 밸런스를 맞춥니다.VerseOnBegin<override>()<suspends> : void = GetPlayspace().PlayerAddedEvent().Subscribe(OnPlayerAdded) Print("Beginning to balance teams") BalanceTeams()Visual Studio Code로 스크립트를 저장하고 Verse 코드 빌드(Build Verse Code)를 클릭해 컴파일합니다.
UEFN 툴바에서 세션 시작(Launch Session)을 클릭하여 레벨을 플레이테스트합니다.
레벨을 플레이테스트하면 출력 로그에 각 팀의 플레이어 수가 표시되고, 만약 플레이어 수가 더 적은 팀이 발견되면 역시 표시되어야 합니다. 플레이어가 균등하게 팀들에 배분되어야 하며, 새 플레이어가 게임에 참가해도 밸런스가 유지되어야 합니다.
전체 스크립트
다음 코드는 자동으로 플레이어들이 속한 팀의 밸런스를 맞추는 장치의 완성된 스크립트입니다.
using { /UnrealEngine.com/Temporary/Diagnostics }
using { /Fortnite.com/Devices }
using { /Verse.org/Simulation }
team_multiplayer_balance := class(creative_device):
# Holds the teams found with GetTeams()
var Teams : []team = array{}
OnBegin<override>()<suspends> : void =
Print("Verse Device Started!")
직접 해보기
지금까지 이 가이드를 통해 Verse를 사용해 자동으로 플레이어들이 속한 팀의 밸런스를 맞추는 장치의 생성 방법을 배웠습니다.
학습한 내용을 바탕으로, 플레이어 한 명이 플레이어 네 명과 경쟁하는 것처럼 의도적으로 팀 밸런스가 맞지 않는 불균형적인 게임 모드를 제작해 보세요.