● 플레이어 스테이트에 각 플레이어의 정보를 저장하기
플레이어 스테이트(Player State)에는 플레이어별로 다른 정보를 저장한다. 예를 들어 이름, 무기, 상태이상 같은 것이다. 여기서는 이름과 시도 횟수를 저장하는 데에 쓰일 것이다.
UCLASS()
class BASEBALLGAME_API ABaseballPlayerState : public APlayerState
{
GENERATED_BODY()
public:
ABaseballPlayerState();
virtual void GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const override;
//시도 횟수 현황
FString GuessCountInfo();
//플레이어 이름
UPROPERTY(Replicated)
FString PlayerNameString;
//현재 시도 횟수
UPROPERTY(Replicated)
int32 CurrentGuessCount;
//최대 시도 횟수
static constexpr int32 MAX_GUESS_COUNT = 3;
};
플레이어 스테이트를 만들고 여기에 플레이어 이름과 시도 횟수를 저장하는 변수를 만든다. 참고로 플레이어 스테이트는 서버에서 관리하지만 각 클라이언트에게 복사본이 있다. 따라서 이 둘은 Replicated 키워드가 있는 UPROPERTY로 선언한다.
최대 시도 횟수는 3회로 고정이다. 이는 불변이며 이 클래스와 관련된 내용이니 static constexpr로 선언한 변수로 만들었다.
레플리케이션을 할테니 GetLifttimeReplicatedProps 함수는 필수다. 이와 함께 시도 횟수를 FString으로 반환하는 함수도 선언한다.
ABaseballPlayerState::ABaseballPlayerState()
{
bReplicates = true;
PlayerNameString = TEXT("None");
CurrentGuessCount = 0;
}
void ABaseballPlayerState::GetLifetimeReplicatedProps(TArray<class FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
DOREPLIFETIME(ThisClass, PlayerNameString);
DOREPLIFETIME(ThisClass, CurrentGuessCount);
}
FString ABaseballPlayerState::GuessCountInfo()
{
//형식 : "플레이어 이름 (현재 시도 횟수 / 최대 시도 횟수)";
FString ReturnValue = PlayerNameString + TEXT(" (") + FString::FromInt(CurrentGuessCount) + TEXT(" / ") + FString::FromInt(MAX_GUESS_COUNT) + TEXT(")");
return ReturnValue;
}
생성자에서 bReplicates를 true로 만드는건 필수다. 그외 나머지 변수도 초기화한다.
GetLifttimeReplicatedProps에서 레플리케이션할 모든 변수에 대하여 DOREPLIFETIME 매크로를 사용한다. 변수 하나당 매크로 하나씩 사용한다. 이때 같이 적는 클래스 이름은 그 대신 ThisClass를 써도 무방하다.
시도 횟수를 반환하는 함수의 경우, "이름 (N / 3)"형식으로 반환한다.

게임 모드에서 플레이어 스테이트 지정을 깜빡했다. 생각난 김에 지정하고 계속 진행한다.
● 플레이어 스테이트 활용하기
if (ABaseballGameStateBase* BaseballGameState = GetGameState<ABaseballGameStateBase>(); IsValid(BaseballGameState))
{
//접속한 플레이이어의 이름을 "PlayerN"형식으로 정한다.
FString NewName = TEXT("Player") + FString::FromInt(PlayerCount++);
if (ABaseballPlayerState* BaseballPlayerState = BaseballPlayerController->GetPlayerState<ABaseballPlayerState>(); IsValid(BaseballPlayerState))
{
BaseballPlayerState->PlayerNameString = NewName;
}
//접속한 플레이어 알림
BaseballGameState->MulticastRPCBroadcastLoginOrLogoutMessage(NewName, true);
}
이름을 저장할 곳이 생겼다. 그래서 게임 모드의 OnPostLogin에서 접속한 클라이언트의 플레이어 스테이트를 찾아 그 이름을 저장하도록 한다.
//지정한 플레이어가 시도 가능한지 확인
bool CanGuess(const ABaseballPlayerController* BaseballPlayerController);
//지정한 플레이어의 시도 횟수 증가
void IncreaseGuessCount(const ABaseballPlayerController* BaseballPlayerController);
//지정한 플레이어의 시도 횟수 정보
FString GuessCountInfo(const ABaseballPlayerController* BaseballPlayerController);
게임 모드에 함수를 더 선언한다. 각 함수의 용도는 주석에 적었다. 우선 시도 횟수와 관련된 함수를 먼저 정의한다.
bool ABaseballGameMode::CanGuess(const ABaseballPlayerController* BaseballPlayerController)
{
const ABaseballPlayerState* BaseballPlayerState = BaseballPlayerController->GetPlayerState<ABaseballPlayerState>();
if (!IsValid(BaseballPlayerState))
return false;
return BaseballPlayerState->CurrentGuessCount < ABaseballPlayerState::MAX_GUESS_COUNT;
}
각 플레이어는 기회가 3번 주어진다. 따라서 답을 3번 미만 제출했다면 아직 기회가 남은 상태로 본다.
void ABaseballGameMode::IncreaseGuessCount(const ABaseballPlayerController* BaseballPlayerController)
{
//시도 횟수 증가
if (ABaseballPlayerState* BaseballPlayerState = BaseballPlayerController->GetPlayerState<ABaseballPlayerState>(); IsValid(BaseballPlayerState))
{
BaseballPlayerState->CurrentGuessCount += 1;
}
}
답을 제출하면 이 함수를 실행하여 시도 횟수를 1 증가한다.
FString ABaseballGameMode::GuessCountInfo(const ABaseballPlayerController* BaseballPlayerController)
{
ABaseballPlayerState* BaseballPlayerState = BaseballPlayerController->GetPlayerState<ABaseballPlayerState>();
if (!IsValid(BaseballPlayerState))
return TEXT("");
return BaseballPlayerState->GuessCountInfo();
}
시도 횟수에 대한 정보는 플레이어 스테이트의 함수의 반환값을 그대로 반환한다.
void ABaseballGameMode::PrintChatMessageString(ABaseballPlayerController* BaseballPlayerController, const FString& ChatMessage)
{
int Index = ChatMessage.Len() - 3;
//채팅 내용 중 마지막 3개의 문자가 0이 아닌 숫자 3개라면 답 입력으로 취급하고 정답 체크
//단, 이미 최대 시도 횟수를 채웠다면 더이상 답을 맞울 수 없다.
const FString SubmittedString = ChatMessage.RightChop(Index);
if (CanGuess(BaseballPlayerController) && IsGuessNumberString(SubmittedString))
{
//이 플레이어의 시도 횟수 증가
IncreaseGuessCount(BaseballPlayerController);
//정답 체크
FJudgeInfo JudgeInfo = JudgeResult(SubmittedString);
const FString JudgeResultString = JudgeInfo.ToString();
//채팅과 결과를 각 플레이어에게 전달
for (TActorIterator<ABaseballPlayerController> Iter(GetWorld()); Iter; ++Iter)
{
if (ABaseballPlayerController* PlayerController = *Iter; IsValid(PlayerController))
{
FString CombinedMessage = GuessCountInfo(BaseballPlayerController) + TEXT(" : ") + ChatMessage + TEXT(" -> ") + JudgeResultString;
PlayerController->ClientRPCPrintChatMessageString(CombinedMessage);
}
}
}
else//아니면 그냥 채팅으로 인식하고 전달
{
for (TActorIterator<ABaseballPlayerController> Iter(GetWorld()); Iter; ++Iter)
{
if (ABaseballPlayerController* PlayerController = *Iter; IsValid(PlayerController))
{
FString CombinedMessage = GuessCountInfo(BaseballPlayerController) + TEXT(" : ") + ChatMessage;
PlayerController->ClientRPCPrintChatMessageString(CombinedMessage);
}
}
}
}
채팅 전달 및 정답 체크 함수에 이 3개의 함수를 활용한다. 이제 단순히 채팅 형식만 따지는 것이 아니라 기회가 남은 플레이어에 대해서만 정답 확인을 할 것이다. 정답 확인마다 해당 플레이어의 시도 횟수가 증가한다.
각 플레이어의 채팅시 채팅 내용 앞에 플레이어 이름과 시도 횟수가 출력되도록 변경했다.
'언리얼엔진 학습 및 과제' 카테고리의 다른 글
| 액터 리플리케이션을 이용한 게임 제작 (1) (0) | 2025.12.12 |
|---|---|
| 네트워크 채팅을 이용한 숫자야구 게임 (3) (0) | 2025.12.02 |
| 네트워크 채팅을 이용한 숫자야구 게임 (1) (0) | 2025.11.27 |
| 언리얼 네트워크 관련 사전 정리 (0) | 2025.11.26 |
| 온라인 채팅 구현하기 (4) (0) | 2025.11.25 |