Replicated

[Drag Down] 자체 매치메이킹 연동 성공 (Unreal <-> Spring Boot) 본문

언리얼 엔진/Drag Down

[Drag Down] 자체 매치메이킹 연동 성공 (Unreal <-> Spring Boot)

라구넹 2025. 5. 11. 15:49
USTRUCT(BlueprintType)
struct FRoomSummary
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString RoomId;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString RoomName;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString HostUserName;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	int32 CurrentPlayerCount;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	int32 MaxPlayers;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	bool bIsGameStarted;
};

USTRUCT(BlueprintType)
struct FRoomDetails
{
	GENERATED_BODY()

	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString RoomId;

	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString RoomName;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString HostUserName;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	FString HostIPAddress;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	int32 HostPort;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	int32 MaxPlayers;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	TArray<FString> Players;
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	TMap<FString, FString> PlayerIPs;	// all users' username -> ipAddress mapped
	UPROPERTY(EditAnywhere, BlueprintReadOnly)	bool bIsGameStarted;
};

일단 DTO

룸 생성 -> FRoomSummary가 리스폰스로 옴

룸 리스트업 -> FRoomDetails가 리스폰스로 옴

 

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnListRoomsCompleted, const TArray<FRoomSummary>&, Rooms);
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnJoinRoomCompleted, const TArray<FRoomDetails>&, RoomDetails);

리스트업, 조인 델리게이트

 

void UDDHttpApiSubsystem::SendCreateRoomRequest(const FString& RoomName)
{
	FString Url = TEXT("http://localhost:8080/api/MatchRooms");

	TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject());
	JsonObject->SetStringField(TEXT("roomName"), RoomName);
	JsonObject->SetStringField(TEXT("ipAddress"), IP);
	JsonObject->SetStringField(TEXT("port"), Port);

	FString OutputString;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&OutputString);
	FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);

	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(Url);
	Request->SetVerb(TEXT("POST"));
	Request->SetHeader("Content-Type", "application/json");

	UE_LOG(LogDD, Log, TEXT("Token: %s"), *GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>()->GetToken());
	FString RawToken = GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>()->GetToken();
	FString Bearer = FString::Printf(TEXT("Bearer %s"), *RawToken);
	Request->SetHeader("Authorization", Bearer);
	Request->SetContentAsString(OutputString); 
	Request->OnProcessRequestComplete().BindUObject(this, &UDDHttpApiSubsystem::OnCreateRoomResponseReceived);
	Request->ProcessRequest(); 
}

void UDDHttpApiSubsystem::OnCreateRoomResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	FResponseStruct ResponseStruct;
	ResponseStruct.bWasSuccessful = bWasSuccessful;

	if (bWasSuccessful && Response.IsValid())
	{
		int32 ResponseCode = Response->GetResponseCode();
		FString ResponseContent = Response->GetContentAsString();
		ResponseStruct.ResponseCode = ResponseCode;
		ResponseStruct.ResponseContent = ResponseContent;

		UE_LOG(LogDD, Log, TEXT("ResponseCode: %d"), ResponseCode);
		UE_LOG(LogDD, Log, TEXT("ResponseContent: %s"), *ResponseContent);

		GetWorld()->ServerTravel(TEXT("/Game/02_Level/L_Map_Game?game=/Game/01_Blueprint/Game/BP_DDGameMode.BP_DDGameMode_C?listen"));
	}
	else
	{
		FString Error = Response.IsValid() ? Response->GetContentAsString() : TEXT("Invalid Response");
		ResponseStruct.ErrorContent = Error;

		UE_LOG(LogDD, Error, TEXT("Request Failed: %s"), *Error);
	}

	// UnBind는 바인딩하는 쪽에서 알아서
	OnRequestCompleted.Broadcast(ResponseStruct);
}

룸 생성

일단 테스트용으로 룸 생성 성공하면 바로 게임룸으로 이동하는데, 추후 대기방으로 변경예정

 

룸 이름 입력하면 그에 맞는 입력을 서버로 보냄

 

void UDDHttpApiSubsystem::SendListRoomsRequest()
{
	FString Url = TEXT("http://localhost:8080/api/MatchRooms");
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(Url);
	Request->SetVerb(TEXT("GET"));
	Request->SetHeader("Content-Type", "application/json");

	FString RawToken = GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>()->GetToken();
	FString Bearer = FString::Printf(TEXT("Bearer %s"), *RawToken);
	Request->SetHeader("Authorization", Bearer);

	Request->OnProcessRequestComplete().BindUObject(this, &UDDHttpApiSubsystem::OnListRoomsResponseReceived);
	Request->ProcessRequest();
}

void UDDHttpApiSubsystem::OnListRoomsResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	TArray<FRoomSummary> RoomSummaries;

	if (bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200)
	{
		UE_LOG(LogDD, Log, TEXT("%s"), *Response->GetContentAsString());

		FString JsonString = Response->GetContentAsString();
		TSharedPtr<FJsonValue> JsonValue;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);

		if ( FJsonSerializer::Deserialize(Reader, JsonValue) && JsonValue.IsValid() && JsonValue->AsArray().Num() > 0)
		{
			const TArray<TSharedPtr<FJsonValue>>& Arr = JsonValue->AsArray();

			for (auto& Entry : Arr)
			{
				const TSharedPtr<FJsonObject>& Obj = Entry->AsObject();
				if (!Obj.IsValid()) return;

				FRoomSummary Info;
				Info.RoomId = Obj->GetStringField(TEXT("roomId"));
				Info.RoomName = Obj->GetStringField(TEXT("roomName"));
				Info.HostUserName = Obj->GetStringField(TEXT("hostUsername"));
				Info.CurrentPlayerCount = Obj->GetIntegerField(TEXT("currentPlayerCount"));
				Info.MaxPlayers = Obj->GetIntegerField(TEXT("maxPlayers"));
				Info.bIsGameStarted = Obj->GetBoolField(TEXT("gameStarted"));

				RoomSummaries.Emplace(Info);
			}
		}
	}
	else
	{
		UE_LOG(LogDD, Log, TEXT("List Rooms Failed: %s"), *Response->GetContentAsString());
	}

	OnListRoomsCompleted.Broadcast(RoomSummaries);
}

룸 리스트업

룸 리스트를 얻어낸다

OnListRoomsCompleted에 FRoomSummary 배열을 인자로 브로드캐스트

 

void UDDHttpApiSubsystem::SendJoinRoomRequest(const FString& RoomID, const FString& InIPAddress, int32 InPort)
{
	FString Url = FString::Printf(TEXT("http://localhost:8080/api/MatchRooms/%s/join"), *RoomID);
	TSharedRef<IHttpRequest, ESPMode::ThreadSafe> Request = FHttpModule::Get().CreateRequest();
	Request->SetURL(Url);
	Request->SetVerb(TEXT("POST"));
	Request->SetHeader("Content-Type", "application/json");

	FString RawToken = GetGameInstance()->GetSubsystem<UDDUserAuthSubsystem>()->GetToken();
	FString Bearer = FString::Printf(TEXT("Bearer %s"), *RawToken);
	Request->SetHeader("Authorization", Bearer);

	TSharedPtr<FJsonObject> Json = MakeShareable(new FJsonObject());
	Json->SetStringField(TEXT("ipAddress"), InIPAddress);
	Json->SetNumberField(TEXT("port"), InPort);

	FString Body;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Body);
	FJsonSerializer::Serialize(Json.ToSharedRef(), Writer);
	Request->SetContentAsString(Body);

	Request->OnProcessRequestComplete().BindUObject(this, &UDDHttpApiSubsystem::OnJoinRoomResponseReceived);
	Request->ProcessRequest();
}

void UDDHttpApiSubsystem::OnJoinRoomResponseReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful)
{
	FRoomDetails RoomDetails;

	if ( bWasSuccessful && Response.IsValid() && Response->GetResponseCode() == 200 )
	{
		FString JsonString = Response->GetContentAsString();
		TSharedPtr<FJsonObject> RoomJson;
		TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(JsonString);

		if ( FJsonSerializer::Deserialize(Reader, RoomJson) && RoomJson.IsValid() )
		{
			RoomDetails.RoomId = RoomJson->GetStringField(TEXT("roomId"));
			RoomDetails.RoomName = RoomJson->GetStringField(TEXT("roomName"));
			RoomDetails.HostUserName = RoomJson->GetStringField(TEXT("hostUsername"));
			RoomDetails.HostIPAddress = RoomJson->GetStringField(TEXT("hostIpAddress"));
			RoomDetails.HostPort = RoomJson->GetIntegerField(TEXT("hostPort"));
			RoomDetails.MaxPlayers = RoomJson->GetIntegerField(TEXT("maxPlayers"));
			RoomDetails.bIsGameStarted = RoomJson->GetBoolField(TEXT("gameStarted"));

			const TArray<TSharedPtr<FJsonValue>>& Arr = RoomJson->GetArrayField(TEXT("players"));
			for ( auto& Player : Arr )
			{
				RoomDetails.Players.Emplace(Player->AsString());
			}

			TSharedPtr<FJsonObject> IPObjs = RoomJson->GetObjectField(TEXT("playerIps"));
			for (auto& IPPair : IPObjs->Values)
			{
				RoomDetails.PlayerIPs.Emplace(IPPair.Key, IPPair.Value->AsString());
			}

			FString Path = FString::Printf(TEXT("%s:%d"), *RoomDetails.HostIPAddress, RoomDetails.HostPort);
			UE_LOG(LogDD, Log, TEXT("Path: %s"), *Path);

			GetWorld()->GetFirstPlayerController()->ClientTravel(Path, TRAVEL_Absolute);
		}
	}
	else
	{
		UE_LOG(LogDD, Log, TEXT("Join Room Failed"));
	}
}

룸 이름과 플레이어 ID, Port를 보내면 성공 시 ClientTravel로 리슨 서버에 참여

 

 

아직 UI는 제대로 안만들고 Find하면 바로 첫번째 방에 Join되게 만들어놔서 이제 그거 수정 필요