언리얼엔진 학습 및 과제

온라인 채팅 구현하기 (4)

park94128 2025. 11. 25. 23:42

4. 원격으로 호출하기

언리얼 엔진의 공식 문서에 따르면, RPC(Remote Procedure Call)는 호출하는 곳과 다른 곳에서 원격 실행되는 함수를 말한다. 온라인 채팅은 이 RPC를 이용하여 구현한다.

 

RPC 함수를 만드는 것은 간단하다. 원격으로 실행될 함수에 UFUNCTION을 붙이고 RPC관련 키워드를 삽입하면 된다.

 

//BaseballPlayerController.h

private:
	//클라이언트에서 실행하는 채팅 출력 함수
	UFUNCTION(Client, Reliable)
	void ClientRPCPrintChatMessageString(const FString& ChatMessage);

	//서버에서 실행하는 채팅 출력 함수
	UFUNCTION(Server, Reliable)
	void ServerRPCPrintChatMessageString(const FString& ChatMessage);
//BaseballPlayerController.cpp

void ABaseballPlayerController::ClientRPCPrintChatMessageString_Implementation(const FString& ChatMessage)
{
	//클라이언트는 그저 자기 자신의 채팅 출력을 실행할 뿐
	PrintChatMessageString(ChatMessage);
}

void ABaseballPlayerController::ServerRPCPrintChatMessageString_Implementation(const FString& ChatMessage)
{
	//서버는 접속한 모든 플레이어 컨트롤러의 채팅 출력 함수를 실행한다.
	for (TActorIterator<ABaseballPlayerController> Iter(GetWorld()); Iter; ++Iter)
	{
		if (ABaseballPlayerController* PlayerController = *Iter; IsValid(PlayerController))
		{
			PlayerController->ClientRPCPrintChatMessageString(ChatMessage);
		}
	}
}

플레이어 컨트롤러에 2개의 함수를 만들었는데 이 두 함수는 UFUNCTION이며 각각 실행 위치와 실행 보장에 대한 키워드가 있다.

 

실행 위치에 대한 키워드는 3개가 있다. 이 키워드가 있으면 RPC함수가 되며 구현되는 함수의 이름은 선언시의 이름 뒤에 _Implementation라는 것이 붙는다.

- Client : 클라이언트에서 호출하면 그 클라이언트에서 실행하며 서버에서 실행하면 주인인 클라이언트에서 실행한다.

- Server : 서버 전용. 서버나 주인인 클라이언트에서 호출시 서버에서 실행하며 주인이 아닌 클라이언트의 호출은 무시된다.

- NetMulticast : 클라이언트에서 호출하면 그 클라이언트에서 실행하며 서버에서 실행하면 서버 포함 모든 곳에서 실행한다.

 

실행 보장에 대한 키워드는 2개가 있다. 반드시 위의 실행 위치 관련 키워드가 있어야 한다.

- Reliable : 원격 호출시 반드시 실행함을 보장한다. 이 키워드가 없으면 Unreliable이 있는 것으로 간주한다.

- Unreliable : 원격 호출이 있어도 실행하지 않을 수도 있다.

 

즉, 위에 적은 코드 중 위쪽에 있는 함수는 클라이언트용 RPC함수이며 자신의 화면에 채팅을 출력하는 함수를 실행한다. 아래쪽에 있는 함수는 서버용 RPC함수이며 서버에 있는 모든 플레이어 컨트롤러가 가진 클라이언트용 RPC함수를 호출한다.

 

여기서 문뜩 떠올릴 수 있는게, 그냥 NetMulticast로 선언하면 굳이 서버용, 클라이언트용 함수로 나누고 서버용 함수에서 for문을 써가면서 각 플레이어 컨트롤러마다 일일이 호출할 필요가 없을 것이라고 생각할 수도 있다. 그러나 어느 한 플레이어의 컨트롤러는 주인인 클라이언트와 서버에만 존재하며 주인이 아닌 클라이언트에는 존재할 수가 없다. 위 코드는 플레이어 컨트롤러에 있는 함수라서 NetMulticast 키워드를 사용하면 안된다.

 

5. 플레이 도중 새로운 플레이어의 접속

A. 에디터 설정

에디터 개인설정 화면에서 실험단계 기능을 눌러서 PIE - 늦은 조인 허용(Allow Late Joining)을 체크한다.

 

그 다음에 실행해보면 버튼이 1개 더 생긴다. 이 버튼은 실행한 후에 다른 클라이언트를 추가하는 버튼이 생긴다.

 

B. 다른 플레이어의 접속 알림

//BaseballGameStateBase.h

UCLASS()
class BASEBALLGAME_API ABaseballGameStateBase : public AGameStateBase
{
	GENERATED_BODY()
	
public:
	UFUNCTION(NetMulticast, Reliable)
	void MulticastRPCBroadcastLoginMessage(const FString& NameString);
};
//BaseballGameStateBase.cpp

void ABaseballGameStateBase::MulticastRPCBroadcastLoginMessage_Implementation(const FString& NameString)
{
	if (!HasAuthority())//서버가 아닐때만 동작
	{
		//새 플레이어 접속 알림
		if (APlayerController* PlayerController = UGameplayStatics::GetPlayerController(GetWorld(), 0); IsValid(PlayerController))
		{
			if (ABaseballPlayerController* BaseballPlayerController = Cast<ABaseballPlayerController>(PlayerController); IsValid(BaseballPlayerController))
			{
				const FString NotificationString = NameString + TEXT("님이 입장하셨습니다.");
				BaseballPlayerController->PrintChatMessageString(NotificationString);
			}
		}
	}
}

게임 스테이트를 만들고 RPC함수를 만든다. 이 함수는 누군가가 접속하면 모두에게 알리는 기능을 수행한다.

이 함수에서 사용한 HasAuthority함수는 서버에서 호출시 true를, 아니면 false를 반환하는 함수라서 이를 이용하여 서버를 제외한 모든 클라이언트에서의 실행을 구현할 수 있다.

 

//BaseballGameMode.h

UCLASS()
class BASEBALLGAME_API ABaseballGameMode : public AGameModeBase
{
	GENERATED_BODY()

public:
	virtual void BeginPlay() override;
	virtual void OnPostLogin(AController* NewPlayer) override;

private:
	int32 PlayerCount = 1;
};
//BaseballGameMode.h

void ABaseballGameMode::BeginPlay()
{
	Super::BeginPlay();

	PlayerCount = 1;
}

void ABaseballGameMode::OnPostLogin(AController* NewPlayer)
{
	Super::OnPostLogin(NewPlayer);

	if (ABaseballGameStateBase* BaseballGameState = GetGameState<ABaseballGameStateBase>(); IsValid(BaseballGameState))
	{
		//접속한 플레이이어의 이름을 "PlayerN"형식으로 정한다.
		const FString NewName = TEXT("Player") + FString::FromInt(PlayerCount++);
		BaseballGameState->MulticastRPCBroadcastLoginMessage(NewName);
	}
}

계속해서, 지금까지 빈 껍데기였던 게임 모드에 함수 2개를 만든다. 여기서 중요한 것은 OnPostLogin함수다. 네트워크 환경에서는 게임모드가 클리이언트에 존재하지 않고 서버에 존재한다. 그래서 존재하는 함수인데 클라이언트가 접속하면 그 클라이언트의 컨트롤러를 매개변수로 하여 서버에서 실행하는 함수다.

 

여기서 이 함수는 접속한 순서대로 클라이언트에 "PlayerN"형식의 이름을 부여하며 플레이어의 접속을 알리는 함수를 실행한다. (지금은 접속 외에 이름을 쓰는 일이 없어서 이름 부여 기능은 없다.)

 

여기까지 만든 것을 실행해보니 잘 작동한다.