
언리얼 서버는 데디케이트와 리슨서버로 구분 되는데
데디케이트는 전용서버를 말하고 리슨서버는 게스트가 호스트가 될 수 있는 서버를 이야기 한다.

대충만든 스크룰 박스와
대충만든 입력창을 넣어둔다.
해당 입력창은 OnTexCommitted()를 통해서 입력에 대한 이벤트를 여러 함수를 제공한다.
게임모드의 기능은 다음과같다.
// 헤더
UCLASS()
class CHATSERVERPRJ_API AChatGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AChatGameMode();
public:
// Client to Server
UFUNCTION(Server, Reliable, WithValidation)
void ReceiveClientMessage(APlayerController* Sender, const FString& Message);
// NetMultiCast는 서버에서 클라이언트에게 브로드캐스트를 하기 위한 지정자
UFUNCTION(NetMulticast, Reliable)
void BroadcastMessage(const FString& SenderName, const FString& Message);
protected:
virtual void BeginPlay() override;
};
UFUNCTION의 지정자
Server는 클라이언트에서 서버로 보내는 데이터일 경우 사용하는 것이다.
Multicast는 서버에서 모든 클라이언트에게 적용될 경우 사용된다.
특이사항으로는 해당 함수를 구현할 별도의 함수를 통해서
서버와 클라이언트의 게이트웨이는 컨트롤러가 하므로 컨트롤러의 역할은 다음과같다.
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "ChatController.generated.h"
UCLASS()
class CHATSERVERPRJ_API AChatController : public APlayerController
{
GENERATED_BODY()
protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSubclassOf<class UChatWindow> ChatWindowClass;
//TSoftClassPtr<class UChatWindow> ChatWindowClass;
UPROPERTY(EditAnywhere, BlueprintReadWrite)
TSoftObjectPtr<class UChatWindow> ChatWindowInstance;
UPROPERTY(VisibleAnywhere,BlueprintReadOnly)
FString ControllerID;
public:
UFUNCTION(Client, Reliable)
void ReceiveMessage(const FString& SenderName,const FString& Message);
UFUNCTION(Server, Reliable)
void SendServerMessage(APlayerController* Sender, const FString& Message);
UFUNCTION(BlueprintCallable,BlueprintPure)
FString GetUserID() const;
protected:
virtual void BeginPlay() override;
bool IsServer();
};
#include "ChatController.h"
#include "Kismet/GameplayStatics.h"
#include "ChatServerPrj/GameUI/ChatWindow.h"
#include "ChatGameMode.h"
#include "ChatGameState.h"
void AChatController::ReceiveMessage_Implementation(const FString& SenderName,const FString& Message)
{
if (ChatWindowInstance)
{
ChatWindowInstance->AddMessage(SenderName, Message);
}
}
void AChatController::SendServerMessage_Implementation(APlayerController* Sender,const FString& Message)
{
if (AChatGameState* ChatGameState = GetWorld()->GetGameState<AChatGameState>())
{
if (AChatController* Cont = Cast<AChatController>(Sender))
{
ChatGameState->BroadcastMessage(Cont->GetUserID(), Message);
}
}
}
FString AChatController::GetUserID() const
{
return ControllerID;
}
void AChatController::BeginPlay()
{
Super::BeginPlay();
if (IsServer())
{
ControllerID = FString::Printf(TEXT("[Host]"));
}
else
{
ControllerID = FString::Printf(TEXT("[Guest%d]"),GetUniqueID());
}
if (!GetRemoteRole() == ROLE_None)
{
// T딜레이 줘야함
if (ChatWindowClass)
{
ChatWindowInstance = CreateWidget<UChatWindow>(this, ChatWindowClass.Get());
if (ChatWindowInstance.IsValid())
{
ChatWindowInstance->AddToViewport();
bShowMouseCursor = true;
SetInputMode(FInputModeGameAndUI());
}
}
}
}
bool AChatController::IsServer()
{
if (UWorld* World = GetWorld())
{
ENetMode NetMode = World->GetNetMode();
switch (NetMode)
{
case NM_Standalone:
break;
case NM_DedicatedServer:
return true;
break;
case NM_ListenServer:
if (IsLocalController())
{
return true;
}
else
{
return false;
}
break;
case NM_Client:
break;
case NM_MAX:
break;
default:
break;
}
}
return false;
}
GameMode는 서버에만 존재하기 때문에 Multicast를 GameMode에 두게 되면 Server에서만 실행되고 클라이언트에서는 실행되지 않을 가능성이 높다.
따라서 Controller나 State에 Multicast를 두는것이 바람직하다.
NetMulticast는 복제된 엑터에만 적용이 가능하다. 그러나 Controller는 클라이언트마다 고유한 객체 하나만 가져야 하기 때문에 복제가 되지 않고 Multicast는 작동하지 않을 수 있다.
Gamestate는 클라이언트에게 복제되므로 동작하는데 문제가 없으므로, GameState에 두는것이 바람직하다.
| 클래스 | 복제여부 | multicast적합여부 | 적용 사례 |
|---|---|---|---|
| Controller(Host) | X | 클라이언트별 로직, UI | - |
| AGameState | O | O | 게임 전체 브로드캐스트 |
| AGameMode | X(서버만) | X | 게임 규칙, 서버로직 |