-
[UE5] GameState의 PlayerArray가 동기화되는 방법언리얼엔진/그 외 2024. 9. 9. 14:59
게임 스테이트는 클라이언트에 레플리케이트 되어서 서버와 클라이언트에 모두 존재하며, 게임의 상태를 동기화 해서 클라이언트에서도 알수있게 된다.
예를 들면
AGameState
에서 다루는 변수인MatchState
와ElapsedTime
은 서버와 동기화 되므로, 이 값이 변함에 따라서 클라이언트에서도 상황에 맞는 작업을 할 수 있다.이렇게 동기화 된다고 알려진 정보중에
AGameStateBase
의PlayerArray
도 있다.PlayerArray
는 현재 존재하는 모든 플레이어들의 플레이어 스테이트를 담아둔 배열이다.플레이어 스테이트는 서버와 클라이언트에 모두 존재하고, 존재하는 모든 플레이어 스테이트가 각 클라이언트에 레플리케이트 되므로 게임 스테이트의
PlayerArray
를 통해 클라이언트에서도 원할때마다 접속된 모든 플레이어들을 확인할 수 있다.이를 통해 새로운 플레이어가 접속하거나 기존의 플레이어가 접속을 끊은 상황을 이
PlayerArray
배열을 이용해서 클라이언트 입장에서도 RPC 없이 알아낼 수 있다.보통 레플리케이트 되는 변수는 RepNotify를 통해 변화가 생겼음을 알려주는 함수가 호출되도록 설정된 경우가 많으므로, 이 함수를 활용하면 실시간으로 플레이어의 접속 상황을 확인할 수 있을 것이다.
(참고로 비슷한 작업을 게임 모드의
Login
과PostLogin
함수를 통해 확인할 수 있지만, 게임 모드는 서버에만 존재하고 레플리케이트 되지 않으니 클라이언트에서는 사용할 수 없다.)마침 이런 이벤트를 받아야하는 상황이 생겨서 엔진 코드를 확인해봤지만, 결과는 예상과 달랐다.
GameStateBase.h
를 찾아보면PlayerArray
는 아래와 같이 선언되어있다.// GameStateBase.h /** Array of all PlayerStates, maintained on both server and clients (PlayerStates are always relevant) */ UPROPERTY(Transient, BlueprintReadOnly, Category=GameState) TArray<TObjectPtr<APlayerState>> PlayerArray;
Replicate 변수로 지정되어있지 않으며, 따라서 RepNotify 함수도 없다.
그리고 달려있는 주석을 보면 서버와 클라이언트 모두 maintain 된다고 적혀있다.
레플리케이트 되지는 않지만, 어떠한 방법을 통해 관리되어 상태를 유지하고 있다는걸 확인할 수 있다.
PlayerArray
가 어떻게 관리되고있는지 확인해보니 아래와 같은 두 함수를 찾을 수 있었다.// GameStateBase.h /** Add PlayerState to the PlayerArray */ ENGINE_API virtual void AddPlayerState(APlayerState* PlayerState); /** Remove PlayerState from the PlayerArray. */ ENGINE_API virtual void RemovePlayerState(APlayerState* PlayerState); // GameStateBase.cpp void AGameStateBase::AddPlayerState(APlayerState* PlayerState) { // Determine whether it should go in the active or inactive list if (!PlayerState->IsInactive()) { // make sure no duplicates PlayerArray.AddUnique(PlayerState); } } void AGameStateBase::RemovePlayerState(APlayerState* PlayerState) { for (int32 i=0; i<PlayerArray.Num(); i++) { if (PlayerArray[i] == PlayerState) { PlayerArray.RemoveAt(i,1); return; } } }
만약 클라이언트에서 다른 플레이어의 접속과 접속 해제 이벤트를 알고싶다면, 위의 두 함수를 활용하면 된다.
추가로 이 두 함수가 어디에서 호출되는지도 확인해보면,
APlayerState
의PostInitializeComponents()
와Destroyed()
함수에서 월드의 게임 스테이트를 찾아서 직접 호출해주고 있는걸 알 수 있다.void APlayerState::PostInitializeComponents() { // ... UWorld* World = GetWorld(); AGameStateBase* GameStateBase = World->GetGameState(); // register this PlayerState with the game state if (GameStateBase != nullptr ) { GameStateBase->AddPlayerState(this); } // ... } void APlayerState::Destroyed() { UWorld* World = GetWorld(); if (World->GetGameState() != nullptr) { World->GetGameState()->RemovePlayerState(this); } // ... }
즉 플레이어 스테이트가 레플리케이트 되어서 생성될때와 제거될때에
PlayerArray
에 스스로 추가되거나, 제거되는 방식으로 관리되고 있다.
생각해보면 접속중인 플레이어 배열은 크기가 커질수도 있으니 배열 자체를 레플리케이트 하는건 좋지 않은 선택이다.
그 뿐만 아니라
PlayerArray
가 레플리케이트 된다고 가정해보면, 클라이언트 입장에서 새로운 플레이어가 접속했을때를 알고자 할 때에는PlayerArray
의 RepNotify를 활용할 것이다.하지만 플레이어 스테이트 자체도 레플리케이트 되고,
PlayerArray
도 별도로 레플리케이트 될텐데 이런 경우에는 어떤것이 먼저 레플리케이트 될지는 알 수 없다.만약
PlayerArray
가 먼저 레플리케이트 되어서 플레이어 스테이트가 생성되기 전에 OnRep 함수가 호출된다면,PlayerArray
에는 존재하지 않는 플레이어 스테이트를 가리키는 원소가 포함될테니 원하는 정보를 얻을 수 없게된다.PlayerArray
가 유지되는 방식을 응용해서 멀티플레이 게임에서 여러 팀들의 팀원 목록을 관리할 수도 있다.이 팀원 목록도 레플리케이트 될 필요는 없으며, 대신 플레이어 스테이트에서 자신의 팀 정보를 레플리케이트 받아서, 업데이트가 된 경우 게임 스테이트에게 알리면 된다.
'언리얼엔진 > 그 외' 카테고리의 다른 글
[UE5] C++로 애니메이션 시퀀스 변경, 커브 추가 (0) 2024.07.03 [UE5] 캐릭터 무브먼트 컴포넌트 작동 순서 정리 (0) 2024.06.13 [UE5] USTRUCT의 NetSerialize (1) 2024.03.12 [UE5] 블렌더로 루트 모션 애니메이션 만드는 방법 (4) 2024.01.25 [UE5] Enhanced Input Binding with Gameplay Tags C++ (0) 2024.01.21