레플리케이션
숫자 야구 게임
서버에서 액터를 스폰하고 클라로 복제할 때, 현재 서버에서의 값들을 클라에 보내주어 클라에서 이 값들을 초기값으로 인스턴스를 다시 만드는 것이었음
그러므로 이 상태까지는 서버와 클라에서의 액터의 메모리 구조랑 값은 완전히 동일
하지만, 멤버변수같이 값이 이후에 바뀌게 되면 서로 달라짐
이걸 동기화 시켜주는 방법이 Replication
동기화는 무조건 서버에서 클라로. 클라가 값 바꾸고 서버를 동기화시키는건 절대 안 됨
액터 레플리케이션 활성화
생성자에서 bReplicates를 true로 설정하여 활성화
동기화할 멤버변수에 키워드 넣기
UPROPERTY(Replicated) 사용하여 동기화 설정
동기화 함수 구현
GetLifetimeReplicatedProps() 함수에 동기화할 멤버변수를 명시
// MyActor.h
UCLASS()
class MYGAME_API AMyActor : public AActor
{
GENERATED_BODY()
public:
AMyActor();
// 레플리케이션 대상 변수 선언
UPROPERTY(Replicated)
int32 ReplicatedValue;
// 동기화 함수
virtual void GetLifetimeReplicatedProps(TArray<FLifetimeProperty>&
OutLifetimeProps) const override;
};
// MyActor.cpp
#include "MyActor.h"
#include "Net/UnrealNetwork.h"
AMyActor::AMyActor()
{
bReplicates = true; // 액터의 동기화 설정
}
void AMyActor::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>&
OutLifetimeProps) const {
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// ReplicatedValue 변수를 클라이언트로 동기화 하도록 지정
DOREPLIFETIME(AMyActor, ReplicatedValue);
}
ReplicatedUsing=함수이름 키워드 사용하기// MyActor.h
UPROPERTY(Replicated, ReplicatedUsing=OnRep_ReplicatedValue)
int32 ReplicatedValue;
UFUNCTION()
void OnRep_ReplicatedValue() { /* 추가 동작 */ }
지금까지 멀티플레이어(데디케이티드 서버)를 공부한 것을 기반으로, 여러 명이서 동시에 플레이 할 수 있는 숫자야구게임을 만들어보려고 함
실시간 채팅과 동기화에 신경써보며 만들어볼 예정
void ACXGameModeBase::BeginPlay()
{
Super::BeginPlay();
FMath::RandInit(FDateTime::Now().GetTicks());
SecretNumberString = GenerateSecreteNumber();
}
FString ACXGameModeBase::GenerateSecreteNumber()
{
// TArray<int> Numbers 초기화 {1, 2, ... 9} //
FString Result;
for (int32 i = 0; i < 3; i++)
{
int32 Index = FMath::RandRange(0, Numbers.Num() - 1);
Result.Append(FString::FromInt(Numbers[Index]));
Numbers.RemoveAt(Index);
}
return FString();
}
FMath::RandInit(FDateTime::Now().GetTicks())로 시드값 초기화
시드값을 초기화 안 해주면 매번 같은 시퀀스로 난수가 나옴
한번만 초기화해주고, RandRange통해 난수 받으면 된다
// CXGameModeBase.h
class ACXPlayerController;
UCLASS()
class CHATX_API ACXGameModeBase : public AGameModeBase
{
// ... //
protected:
TArray<TObjectPtr<ACXPlayerController>> AllPlayerControllers;
};
// CXGameModeBase.cpp
void ACXGameModeBase::OnPostLogin(AController* NewPlayer)
{
Super::OnPostLogin(NewPlayer);
ACXPlayerController* NewCXPlayerController =Cast<ACXPlayerController>(NewPlayer);
if (IsValid(NewCXPlayerController))
AllPlayerControllers.Add(NewCXPlayerController);
}
OnPostLogin은 새 클라이언트가 접속하여 로그인 된 후에 호출되는 함수// CXPlayerController.h
UFUNCTION(Server, Reliable)
void ServerRPCPrintChatMessageString(const FString& InChatMessageString);
// CXPlayerController.cpp
void ACXPlayerController::ServerRPCPrintChatMessageString_Implementation(
const FString& InChatMessageString)
{
ACXGameModeBase* CXGameModeBase = GetWorld()->GetAuthGameMode<ACXGameModeBase>();
CXGameModeBase->PrintChatMessageString(this, InChatMessageString);
}
ServerRPCPrintChatMessageString() 이 함수는 Server RPC함수로, 클라에서 채팅쳐서 이 함수 호출하면, 서버에 있는 해당 컨트롤러의 이 함수가 실행됨
서버에는 게임모드 있으니 GetWorld()->GetAuthGameMode<ACXGameModeBase>(); 이 함수로 게임모드 가져와 각 클라이언트 화면에 채팅의 정답을 보여주는 함수 호출
헷갈렸는데, 서버에서 실행하는 프로그램과 클라에서 실행하는 프로그램은 동일함. 그래서 클라에도 게임모드 파일은 있는거지 World상에서 생성만 안 되는 것
정답 판정 로직은 서버에서 동작해야하므로, 게임모드에 구현
숫자입력하면 정답을, 그냥 채팅이면 채팅을 화면에 띄움
// CXGameModeBase.cpp
void ACXGameModeBase::PrintChatMessageString(
ACXPlayerController* InChattingPlayerController,
const FString& InChatMessageString)
{
FString Message = InChatMessageString;
if (IsGuessNumberString(InChatMessageString)) // 숫자입력한거면
{
FString Result = JudgeResult(SecretNumberString, InChatMessageString);
Message.Append(" -> ").Append(Result);
}
// 모든 클라이언트에게 Clinet RPC 보냄
for (const TObjectPtr<ACXPlayerController> CXPlayerController :
AllPlayerControllers)
{
CXPlayerController->ClientRPCPrintChatMessageString(Message);
}
}
ClientRPCPrintChatMessageString를 통해 각 클라에게 전달플레이어별 이름은 플레이어 스테이트에 저장
값 설정은 서버에서 해줘야함. 그리고 클라로 동기화 작업 필요(레플리케이션)
// CXPlayerState.cpp
#include "Player/CXPlayerState.h"
#include "Net/UnrealNetwork.h"
void ACXPlayerState::BeginPlay()
{
Super::BeginPlay();
bReplicates = true; // 레플리케이션 활성화
}
void ACXPlayerState::GetLifetimeReplicatedProps(
TArray<FLifetimeProperty>& OutLifetimeProps) const
{
Super::GetLifetimeReplicatedProps(OutLifetimeProps);
// PlayerNameString 변수를 등록
DOREPLIFETIME(ACXPlayerState, PlayerNameString);
}
GameModeBase::PostLogin()에서 해줌// CXGameModeBase.cpp
void ACXGameModeBase::OnPostLogin(AController* NewPlayer)
{
Super::OnPostLogin(NewPlayer);
ACXPlayerController* NewCXPlayerController =Cast<ACXPlayerController>(NewPlayer);
if (IsValid(NewCXPlayerController))
{
// 모든 클라의 플레이어컨트롤러 배열
AllPlayerControllers.Add(NewCXPlayerController);
ACXPlayerState* CXPlayerState = NewCXPlayerController->
GetPlayerState<ACXPlayerState>();
if (IsValid(CXPlayerState))
{
CXPlayerState->PlayerNameString = TEXT("Player") +
FString::FromInt(AllPlayerControllers.Num());
}
}
이렇게 하면, 서버에 접속한 클라이언트에게 "Player숫자"형식으로 서버와 클라이언트의 PlayerState에 이름이 설정되게 된다
지금 만든 PlayerState는 꼭 GameMode에 등록해주기