숫자 야구게임은 "362"<< 이런 숫자를 컴퓨터가 랜덤하게 만들어주면
그걸 2명이서 3번의 기회를 가지고 맞추는 식으로 만들었다.
숫자야구 게임을 만들다가..
각 플레이어별로 남은 기회가 다르게 나오도록 해야하는데 계속 같은 숫자의 기회가 나오는 문제가 있었다.
예를 들어, Host의 남은 기회가 2면, Guest의 남은 기회도 2로 같이 내려가는 것이었다.
그럼 내 차례가 아닌데 상대가 나의 기회를 차감시키는 문제가 생겨버려서 정말 큰일이다.
문제를 해결하고 있는 와중에 블로그에다 정리를 한 것이기 때문에 정리가 말끔히 되지 않은 부분이 있다.
나중에 차차 정리해보자.
왜 그럴까 곰곰히 생각해보았는데 PlayerController가 문제였다.
FString AYMGameMode::JudgeGuessNum(const FString& Guess)
{
// 모든 플레이어 컨트롤러 가져오기
for (FConstPlayerControllerIterator It = GetWorld()->GetPlayerControllerIterator(); It; ++It)
{
APlayerController* PC = It->Get();
if (!PC)
{
UE_LOG(LogTemp, Error, TEXT("PlayerController is nullptr!"));
continue;
}
AYMPlayerController* MyPC = Cast<AYMPlayerController>(PC);
if (!MyPC)
{
UE_LOG(LogTemp, Error, TEXT("Cast to failed! PC: %s"), *PC->GetClass()->GetName());
continue;
}
UE_LOG(LogTemp, Log, TEXT("Found PlayerController: %s"), *MyPC->GetName());
}
}
위 코드는 플레이어가 숫자를 맞추려고 "123" << 이런식으로 입력을 하면
그 값을 받아서 만약 컴퓨터가 생성한 숫자가 "321"이라면
"1S 2B 0OUT"이라는 메시지를 반환하게 하는 함수다.
AYMPlayerState* PlayerState = MyPC->GetPlayerState<AYMPlayerState>();
PlayerState->DecreaseAttempts(); // 정상 입력이면 기회가 1 감소
그런 판단을 해주는 함수에서 판단이 끝나면 오직 입력을 한 플레이어만 정답을 맞출 기회가 감소해야한다.
만약 위 코드가 저기 MyPC의 if문에 들어간다면..
두 명의 컨트롤러를 가지고 와서 2개의 플레이어 스테이트에 있는 정보(정답을 맞출 기회)가 업데이트 되기 때문에
FString AYMGameMode::JudgeGuessNum(APlayerController* PC, const FString& Guess)
{
// 여기도 GuessNum을 못맞출시 기회가 그 컨트롤러에만 있는 기회가 날라가야 해서 매개변수로 추가..
if (!PC) return "";
AYMPlayerState* PlayerState = PC->GetPlayerState<AYMPlayerState>();
이런식으로 플레이어 컨트롤러를 매개변수로 가지고와서
JudgeGuessNum 함수가 호출될 때 입력을 한 쪽의 플레이어 컨트롤러에서만
정답을 맞출 기회를 감소하는 로직이 발동되도록 하였다.
그럼 JudgeGuessNum의 PC는 어디서 받아야할까?
void AYMGameMode::Server_GotMessageFromClient_Implementation(APlayerController* PC, const FString& GuessMsg, const FString& UserID)
{
UserId = UserID;
JudgeResult = JudgeGuessNum(PC, GuessMsg);
바로 같은 게임모드에 있는 함수인 Server_GotMessageFromClient_Implementation이란 함수에서 JudgeGuessNum으로 인자를 넘겨주게 된다.
그렇다면 Server_GotMessageFromClient_Implementation의 플레이어 컨트롤러는 어디서 인자를 받아오는 것일까?
바로 플레이어 컨트롤러에 있는 아래 Server RPC에서 값을 넘겨주게 된다.
UFUNCTION(Server, Reliable, BlueprintCallable)
void Server_OnSendPlayerControllerToServer(const FString& Msg, const FString& UserID);
어떤 느낌이냐면 구현부를 보면 된다.
void AYMPlayerController::Server_OnSendPlayerControllerToServer_Implementation(const FString& Msg, const FString& UserID)
{
AYMGameMode* GM = Cast<AYMGameMode>(GetWorld()->GetAuthGameMode());
if (GM)
{
// 현재 입력한 플레이어의 ID, 메시지, 컨트롤러를 서버에 전달
GM->Server_GotMessageFromClient(this, Msg, UserID);
}
}
이런식으로 Server RPC를 이용해서 서버에 있는 컨트롤러 중 입력을 받은 this(자기 자신)만
전달하도록 하였다. 그럼 Server_OnSendPlayerControllerToServer_Implementation 함수는 누가 호출할까? 원래는 C++로 다 옮기려고 했지만 간단한 부분은 블루프린트를 써도 괜찮을 거 같아 아래처럼 컨트롤러에서 서버에서만 실행되는 이벤트랑 연결해서 저 빨간 이벤트가 호출되면 위 함수가 호출되도록 하였다.
그럼 OnSendMsgToServer는 언제 호출될까?
플레이어 컨트롤러 블루프린트에서는 BeginPlay이벤트 노드에서 Set Message to User Controller라는 이벤트가 호출되면 OnSendMsgToServer도 호출되도록 Bind(연결)해주었다.
이런 채팅 UI에서 플레이어가 엔터를 눌렀을때 그 메시지가 담겨서 위 파란 노드에 Msg로 값이 담기는걸 볼 수 있다. 그 값이 방금 전 Create Event노드에 설정한 이벤트에 인자로 가게 되는 것이다.
아직 네트워크에 대해 공부 중이고 실험 도중에 글을 정리한 것이라
멀티캐스트, Run on Server, Run on owning Client 등 뭘 써야할지 몰랐는데
틀린 부분이 있을 수 있다.
_Implementation
을 붙여야 한다.UFUNCTION(Server, Reliable)
void Server_GotMessageFromClient(const FString& Msg, const FString& UserID);
만약 헤더에 이렇게 RPC 함수를 쓴다면, 소스파일에는 반드시 저 함수랑 똑같은 이름이 아닌
Server_GotMessageFromClient_Implementation
의 형태로 _Implementation
을 붙여야 동작한다.
RPC함수라면 언리얼이 자동으로 _Implementation
을 찾는다고 한다.
여기서 왜 그런지 더 파고들어 공부할 것이다.
일단 되는대로 노션에 정리하고 그 중에서 많은 시간과 공을 들여 얻게 된 지식을 올리는 것이라 TIL을 매일 작성하지 못하였다. 좀 더 꾸준히 하려면 어떻게 해야 될까?
TIL(하루 동안 배운 것을 정리한 것)의 양을 줄이고 대신에 그만큼 하루동안 시간을 많이 쓰고 공을 들여 성공 OR 실패한 것 중에 핵심을 깨달은 것을 정리해야겠다.
요약 : 양보다 질(그래도 매일하는건 중요하다.) 최소 주제 1개 이상(##
< 이거 하나 정도만)