ProudNet 네트워킹 라이브러리와 Unreal Engine 5를 사용하여 멀티플레이어 게임을 구현하는 종합 가이드입니다. 채팅 시스템, 캐릭터 네트워킹, 고급 멀티플레이어 기능을 다룹니다.
ProudNet 통합을 위한 Unreal Engine 5 프로젝트 준비 및 구성
테스트 및 기반을 위한 기본 로컬 채팅 시스템 생성
ProudNet을 사용한 네트워크 채팅 기능 구현
향상된 게임플레이 기능을 가진 미라지 캐릭터 구축
멀티플레이어 게임플레이를 위한 미라지 캐릭터와 ProudNet 네트워킹 통합
프로젝트 마무리, 빌드 및 종합 테스트 절차
이 섹션에서는 ProudNet이 활성화된 Unreal Engine 5 프로젝트를 생성하는 데 필요한 초기 설정 및 구성을 다룹니다.
Visual Studio 프로젝트 생성
프로젝트 파일 추가
프로젝트 구조
C:\Program Files (x86)\Nettention\ProudNet\include
C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)
ProudNetServer.lib ProudNetClient.lib
xcopy /Y "C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)\libcrypto-3-x64.dll" "$(OutDir)" xcopy /Y "C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)\libssl-3-x64.dll" "$(OutDir)"
+ #pragma once + + namespace ProudSetting + { + namespace CHAT + { + extern const ::Proud::Guid version; + extern const int server_port; + } + }
+ #include <ProudNetClient.h> + #include "setting.h" + + namespace ProudSetting + { + namespace CHAT + { + const ::Proud::PNGUID guid = { 0x3ae33249, 0xecc6, 0x4980, { 0xbc, 0x5d, 0x7b, 0xa, 0x99, 0x9c, 0x7, 0x39 } }; + const ::Proud::Guid version = ::Proud::Guid(guid); + const int server_port = 33337; + } + }
+ #include <iostream> + #include <format> + #include <memory> + + #include <ProudNetServer.h> + #include "setting.h" + + std::shared_ptr<Proud::CNetServer> net_server; + + int main() + { + net_server = std::shared_ptr<Proud::CNetServer>(Proud::CNetServer::Create()); + + net_server->OnClientJoin = [](Proud::CNetClientInfo* clientInfo) + { + std::cout << "Client[" << (int)clientInfo->m_HostID << "] connected.\n"; + }; + net_server->OnClientLeave = [](Proud::CNetClientInfo* clientInfo, Proud::ErrorInfo* error, const Proud::ByteArray byte_arr) + { + std::cout << "Client[" << (int)clientInfo->m_HostID << "] disconnected.\n"; + }; + + Proud::CStartServerParameter start_param; + start_param.m_protocolVersion = ProudSetting::CHAT::version; + start_param.m_tcpPorts.Add(ProudSetting::CHAT::server_port); + + try + { + net_server->Start(start_param); + } + catch (Proud::Exception& error) + { + std::cout << "Server start failed: " << error.what() << endl; + return 0; + } + + std::cout << ("Server started. Enterable commands:\n"); + std::cout << ("-q : Quit.\n"); + std::string input; + while (true) + { + std::cin >> input; + if (input[0] == '-') + { + if (input == "-q") + break; + } + } + + std::cout << "Stopping server...\n"; + net_server->Stop(); + net_server = nullptr; + std::cout << "Server stopped.\n"; + return 0; + }
전체 코드
#pragma once namespace ProudSetting { namespace CHAT { extern const ::Proud::Guid version; extern const int server_port; } }
#include <ProudNetClient.h> #include "setting.h" namespace ProudSetting { namespace CHAT { const ::Proud::PNGUID guid = { 0x3ae33249, 0xecc6, 0x4980, { 0xbc, 0x5d, 0x7b, 0xa, 0x99, 0x9c, 0x7, 0x39 } }; const ::Proud::Guid version = ::Proud::Guid(guid); const int server_port = 33337; } }
#include <iostream> #include <format> #include <memory> #include <ProudNetServer.h> #include "setting.h" std::shared_ptr<Proud::CNetServer> net_server; int main() { net_server = std::shared_ptr<Proud::CNetServer>(Proud::CNetServer::Create()); net_server->OnClientJoin = [](Proud::CNetClientInfo* clientInfo) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] connected.\n"; }; net_server->OnClientLeave = [](Proud::CNetClientInfo* clientInfo, Proud::ErrorInfo* error, const Proud::ByteArray byte_arr) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] disconnected.\n"; }; Proud::CStartServerParameter start_param; start_param.m_protocolVersion = ProudSetting::CHAT::version; start_param.m_tcpPorts.Add(ProudSetting::CHAT::server_port); try { net_server->Start(start_param); } catch (Proud::Exception& error) { std::cout << "Server start failed: " << error.what() << endl; return 0; } std::cout << ("Server started. Enterable commands:\n"); std::cout << ("-q : Quit.\n"); std::string input; while (true) { std::cin >> input; if (input[0] == '-') { if (input == "-q") break; } } std::cout << "Stopping server...\n"; net_server->Stop(); net_server = nullptr; std::cout << "Server stopped.\n"; return 0; }
서버 실행 화면
언리얼 프로젝트 생성
C++ 클래스 생성 메뉴
GameInstanceSubsystem 선택
클래스 이름 입력
시작 프로젝트 변경
숏컷 : 에픽게임즈 런처 → 내 프로젝트 목록 → 프로젝트 우클릭 → 폴더 보기
Plugins 폴더가 없다면 새로 만듭니다.
ProudNet UE5 Lib Linker Plugin.zip
플러그인 폴더 구조
Build.cs 파일 위치
// Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class PdnUE5ExampleClient : ModuleRules { public PdnUE5ExampleClient(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ProudNet" }); PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true } }
// Fill out your copyright notice in the Description page of Project Settings. using UnrealBuildTool; public class PdnUE5ExampleClient : ModuleRules { public PdnUE5ExampleClient(ReadOnlyTargetRules Target) : base(Target) { PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ProudNet" }); PrivateDependencyModuleNames.AddRange(new string[] { }); // Uncomment if you are using Slate UI // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); // Uncomment if you are using online features // PrivateDependencyModuleNames.Add("OnlineSubsystem"); // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true } }
GissChatNet 파일들
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Subsystems/GameInstanceSubsystem.h" #include "GissChatNet.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UGissChatNet : public UGameInstanceSubsystem { GENERATED_BODY() + private: + virtual void Initialize(FSubsystemCollectionBase& Collection) override; + virtual void Deinitialize() override; };
// Fill out your copyright notice in the Description page of Project Settings. #include "GissChatNet.h" + #include <format> + #include <functional> + + #include <ProudNetClient.h> + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_server/setting.h" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_server/setting.cpp" + + static void LogPrint(const std::string& str) + { + UE_LOG(LogTemp, Log, TEXT("%s"), UTF8_TO_TCHAR(str.c_str())); + } + + static Proud::CriticalSection global_critical_section; + static std::shared_ptr<Proud::CNetClient> net_client; + static FDelegateHandle update_handle; + + void UGissChatNet::Initialize(FSubsystemCollectionBase& Collection) + { + net_client = std::shared_ptr<Proud::CNetClient>(Proud::CNetClient::Create()); + + bool connected = false; + + net_client->OnJoinServerComplete = [&](Proud::ErrorInfo* info, const Proud::ByteArray& replyFromServer) + { + Proud::CriticalSectionLock lock(global_critical_section, true); + + if (info->m_errorType == Proud::ErrorType::Ok) + { + auto log = std::format("Succeed to connect server. Allocated hostID={}\n", (int)net_client->GetLocalHostID()); + LogPrint(log); + + connected = true; + } + else + { + auto log = "Failed to connect to server.\n"; + LogPrint(log); + } + }; + + net_client->OnLeaveServer = [&](Proud::ErrorInfo* errorInfo) + { + Proud::CriticalSectionLock lock(global_critical_section, true); + + auto log = std::format("OnLeaveServer. {} \n", StringT2A(errorInfo->m_comment).GetString()); + LogPrint(log); + + connected = false; + if (update_handle.IsValid()) + { + FCoreDelegates::OnEndFrame.Remove(update_handle); + update_handle.Reset(); + } + }; + + Proud::CNetConnectionParam connection_param; + connection_param.m_protocolVersion = ProudSetting::CHAT::version; + connection_param.m_serverIP = _PNT("localhost"); + connection_param.m_serverPort = ProudSetting::CHAT::server_port; + connection_param.m_closeNoPingPongTcpConnections = false; + + net_client->Connect(connection_param); + + update_handle = FCoreDelegates::OnEndFrame.AddStatic([]() + { + if (net_client) + net_client->FrameMove(); + } + ); + } + + void UGissChatNet::Deinitialize() + { + net_client->Disconnect(); + net_client = nullptr; + }
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Subsystems/GameInstanceSubsystem.h" #include "GissChatNet.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UGissChatNet : public UGameInstanceSubsystem { GENERATED_BODY() private: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; };
// Fill out your copyright notice in the Description page of Project Settings. #include "GissChatNet.h" #include <format> #include <functional> #include <ProudNetClient.h> #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_server/setting.h" #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_server/setting.cpp" static void LogPrint(const std::string& str) { UE_LOG(LogTemp, Log, TEXT("%s"), UTF8_TO_TCHAR(str.c_str())); } static Proud::CriticalSection global_critical_section; static std::shared_ptr<Proud::CNetClient> net_client; static FDelegateHandle update_handle; void UGissChatNet::Initialize(FSubsystemCollectionBase& Collection) { net_client = std::shared_ptr<Proud::CNetClient>(Proud::CNetClient::Create()); bool connected = false; net_client->OnJoinServerComplete = [&](Proud::ErrorInfo* info, const Proud::ByteArray& replyFromServer) { Proud::CriticalSectionLock lock(global_critical_section, true); if (info->m_errorType == Proud::ErrorType::Ok) { auto log = std::format("Succeed to connect server. Allocated hostID={}\n", (int)net_client->GetLocalHostID()); LogPrint(log); connected = true; } else { auto log = "Failed to connect to server.\n"; LogPrint(log); } }; net_client->OnLeaveServer = [&](Proud::ErrorInfo* errorInfo) { Proud::CriticalSectionLock lock(global_critical_section, true); auto log = std::format("OnLeaveServer. {} \n", StringT2A(errorInfo->m_comment).GetString()); LogPrint(log); connected = false; if (update_handle.IsValid()) { FCoreDelegates::OnEndFrame.Remove(update_handle); update_handle.Reset(); } }; Proud::CNetConnectionParam connection_param; connection_param.m_protocolVersion = ProudSetting::CHAT::version; connection_param.m_serverIP = _PNT("localhost"); connection_param.m_serverPort = ProudSetting::CHAT::server_port; connection_param.m_closeNoPingPongTcpConnections = false; net_client->Connect(connection_param); update_handle = FCoreDelegates::OnEndFrame.AddStatic([]() { if (net_client) net_client->FrameMove(); } ); } void UGissChatNet::Deinitialize() { net_client->Disconnect(); net_client = nullptr; }
chat_server 실행
언리얼 에디터 실행
서버 연결 성공
클라이언트 연결 성공
이 섹션에서는 Unreal Engine 5에서 로컬 채팅창을 만들고 기본적인 채팅 기능을 구현합니다.
위젯 블루프린트 생성
위젯 이름 변경
>> 캔버스 패널 >> 캔버스 패널 - 앵커: 좌하단 - 위치: [x: 100, y: -200] - 크기: [x: 640, y: 360] - 정렬: [x: 0, y: 1] >> 스크롤 박스 - 앵커: 상단 - 위치: [x: 0, y: 0] - 크기: [x: 640, y: 300] - 정렬: [x: 0.5, y: 0] >> 캔버스 패널 - 앵커: 하단 - 위치: [x: 0, y: 0] - 크기: [x: 640, y: 48] - 정렬: [x: 0.5, y: 1] >> 텍스트 박스 - 앵커: 왼쪽 - 위치: [x: 0, y: 0] - 크기: [x: 520, y: 48] - 정렬: [x: 0, y: 0.5] >> 버튼 - 앵커: 오른쪽 - 위치: [x: 0, y: 0] - 크기: [x: 100, y: 48] - 정렬: [x: 1, y: 0.5] >> 텍스트 - 텍스트: "전송"
구성 완료 시 모습은 아래와 같습니다.
채팅창 위젯 구성
레벨 블루프린트 에디터 열기
Create Widget 노드 추가
클래스 설정
Add to Viewport 노드 추가
노드 연결
채팅창이 추가된 화면
>> 캔버스 패널 >> 보더 - 앵커: 좌상단 - 위치: [x: 0, y: 0] - 크기: [x: 640, y: 40] - 정렬: [x: 0, y: 0] - 브러시 컬러: [R: 1, G: 1, B: 1, A: 0.5] >> 텍스트 > 이름: ChatText > 변수 여부 체크 - 가로정렬: 왼쪽 - 세로정렬: 중앙 - 폰트-크기: 20
구성 완료 시 모습은 아래와 같습니다.
BP_ChatBlock 구성
블루프린트 그래프 모드
함수 생성
함수 인자 설정
SetText 노드 추가
노드 연결 완료
-> 캔버스 패널 -> 캔버스 패널 -> 스크롤 박스 > 이름: ChatBlockArea > 변수 여부 체크 -> 캔버스 패널 -> 버튼 > 이름: SendButton > 변수 여부 체크 -> 텍스트 박스 > 이름: ChatTextBox > 변수 여부 체크
수정 후 모습은 아래와 같습니다.
BP_ChatWidget 수정 완료
PrintChat 함수 추가
PrintChat 함수 구성
버튼 클릭 이벤트 추가
채팅 전송 이벤트 완성
추가 기능 구현 1
추가 기능 구현 2
채팅 기능 확인
이 섹션에서는 ProudNet을 사용하여 네트워크 채팅 기능을 구현합니다.
C:\"Program Files (x86)"\Nettention\ProudNet\util\PIDL.exe "%(FullPath)" -cpp
%(Filename).PIDL Compiling...
%(RootDir)%(Directory)\%(Filename)_common.cpp %(RootDir)%(Directory)\%(Filename)_common.h %(RootDir)%(Directory)\%(Filename)_proxy.cpp %(RootDir)%(Directory)\%(Filename)_proxy.h %(RootDir)%(Directory)\%(Filename)_stub.cpp %(RootDir)%(Directory)\%(Filename)_stub.h
[access=public] global CHAT_C2S 3000 { Chat([in] Proud::String message); }
[access=public] global CHAT_S2C 4000 { SystemChat([in] Proud::String message); BroadcastChat([in] int sender_id, [in] Proud::String message); }
PIDL 빌드 결과
#include <iostream> #include <format> #include <memory> #include <ProudNetServer.h> #include "setting.h" + #include "../chat_pidl/S2C_common.h" + #include "../chat_pidl/S2C_common.cpp" + #include "../chat_pidl/S2C_proxy.h" + #include "../chat_pidl/S2C_proxy.cpp" + + #include "../chat_pidl/C2S_common.h" + #include "../chat_pidl/C2S_common.cpp" + #include "../chat_pidl/C2S_stub.h" + #include "../chat_pidl/C2S_stub.cpp" + + + Proud::HostID group_host_id = Proud::HostID_None; + CHAT_S2C::Proxy s2c_proxy; + + struct CHAT_C2S_Stub : public CHAT_C2S::Stub + { + public: + DECRMI_CHAT_C2S_Chat; + }; + + DEFRMI_CHAT_C2S_Chat(CHAT_C2S_Stub) + { + Proud::RmiContext rmi_context; + rmi_context.m_enableLoopback = false; + s2c_proxy.BroadcastChat(group_host_id, rmi_context, (int)remote, message); + std::cout << std::format("Player[{}] say {}\n", (int)remote, StringT2A(message).GetString()); + return true; + } + + CHAT_C2S_Stub c2s_stub; std::shared_ptr<Proud::CNetServer> net_server; int main() { net_server = std::shared_ptr<Proud::CNetServer>(Proud::CNetServer::Create()); net_server->OnClientJoin = [](Proud::CNetClientInfo* clientInfo) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] connected.\n"; + + Proud::HostID list[100]; + int listCount = net_server->GetClientHostIDs(list, 100); + group_host_id = net_server->CreateP2PGroup(list, listCount, Proud::ByteArray()); + + auto message = std::format("Player[{}] has joined.", (int)clientInfo->m_HostID); + Proud::RmiContext rmi_context; + s2c_proxy.SystemChat(group_host_id, rmi_context, message); }; net_server->OnClientLeave = [](Proud::CNetClientInfo* clientInfo, Proud::ErrorInfo* error, const Proud::ByteArray byte_arr) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] disconnected.\n"; + + auto message = std::format("Player[{}] has left the game.", (int)clientInfo->m_HostID); + Proud::RmiContext rmi_context; + s2c_proxy.SystemChat(group_host_id, rmi_context, message); }; + net_server->AttachProxy(&s2c_proxy); + net_server->AttachStub(&c2s_stub); + Proud::CStartServerParameter start_param; start_param.m_protocolVersion = ProudSetting::CHAT::version; start_param.m_tcpPorts.Add(ProudSetting::CHAT::server_port); try { net_server->Start(start_param); } catch (Proud::Exception& error) { std::cout << "Server start failed: " << error.what() << endl; return 0; } std::cout << ("Server started. Enterable commands:\n"); std::cout << ("-q : Quit.\n"); std::string input; while (true) { std::cin >> input; if (input[0] == '-') { if (input == "-q") break; } + else + { + Proud::RmiContext rmi_context; + s2c_proxy.SystemChat(group_host_id, rmi_context, input); + } } std::cout << "Stopping server...\n"; net_server->Stop(); net_server = nullptr; std::cout << "Server stopped.\n"; return 0; }
#include <iostream> #include <format> #include <memory> #include <ProudNetServer.h> #include "setting.h" #include "../chat_pidl/S2C_common.h" #include "../chat_pidl/S2C_common.cpp" #include "../chat_pidl/S2C_proxy.h" #include "../chat_pidl/S2C_proxy.cpp" #include "../chat_pidl/C2S_common.h" #include "../chat_pidl/C2S_common.cpp" #include "../chat_pidl/C2S_stub.h" #include "../chat_pidl/C2S_stub.cpp" Proud::HostID group_host_id = Proud::HostID_None; CHAT_S2C::Proxy s2c_proxy; struct CHAT_C2S_Stub : public CHAT_C2S::Stub { public: DECRMI_CHAT_C2S_Chat; }; DEFRMI_CHAT_C2S_Chat(CHAT_C2S_Stub) { Proud::RmiContext rmi_context; rmi_context.m_enableLoopback = false; s2c_proxy.BroadcastChat(group_host_id, rmi_context, (int)remote, message); std::cout << std::format("Player[{}] say {}\n", (int)remote, StringT2A(message).GetString()); return true; } CHAT_C2S_Stub c2s_stub; std::shared_ptr<Proud::CNetServer> net_server; int main() { net_server = std::shared_ptr<Proud::CNetServer>(Proud::CNetServer::Create()); net_server->OnClientJoin = [](Proud::CNetClientInfo* clientInfo) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] connected.\n"; Proud::HostID list[100]; int listCount = net_server->GetClientHostIDs(list, 100); group_host_id = net_server->CreateP2PGroup(list, listCount, Proud::ByteArray()); auto message = std::format("Player[{}] has joined.", (int)clientInfo->m_HostID); Proud::RmiContext rmi_context; s2c_proxy.SystemChat(group_host_id, rmi_context, message); }; net_server->OnClientLeave = [](Proud::CNetClientInfo* clientInfo, Proud::ErrorInfo* error, const Proud::ByteArray byte_arr) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] disconnected.\n"; auto message = std::format("Player[{}] has left the game.", (int)clientInfo->m_HostID); Proud::RmiContext rmi_context; s2c_proxy.SystemChat(group_host_id, rmi_context, message); }; net_server->AttachProxy(&s2c_proxy); net_server->AttachStub(&c2s_stub); Proud::CStartServerParameter start_param; start_param.m_protocolVersion = ProudSetting::CHAT::version; start_param.m_tcpPorts.Add(ProudSetting::CHAT::server_port); try { net_server->Start(start_param); } catch (Proud::Exception& error) { std::cout << "Server start failed: " << error.what() << endl; return 0; } std::cout << ("Server started. Enterable commands:\n"); std::cout << ("-q : Quit.\n"); std::string input; while (true) { std::cin >> input; if (input[0] == '-') { if (input == "-q") break; } else { Proud::RmiContext rmi_context; s2c_proxy.SystemChat(group_host_id, rmi_context, input); } } std::cout << "Stopping server...\n"; net_server->Stop(); net_server = nullptr; std::cout << "Server stopped.\n"; return 0; }
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "BaseChatWidget.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UBaseChatWidget : public UUserWidget { GENERATED_BODY() + public: + UFUNCTION(BlueprintImplementableEvent) + void PrintChat(const FText& message); };
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "BaseChatWidget.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UBaseChatWidget : public UUserWidget { GENERATED_BODY() public: UFUNCTION(BlueprintImplementableEvent) void PrintChat(const FText& message); };
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Subsystems/GameInstanceSubsystem.h" #include "GissChatNet.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UGissChatNet : public UGameInstanceSubsystem { GENERATED_BODY() + public: + UFUNCTION(BlueprintCallable) + void SendChat(const FText& message); private: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; };
+ #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/C2S_common.h" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/C2S_common.cpp" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/C2S_proxy.h" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/C2S_proxy.cpp" + + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/S2C_common.h" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/S2C_common.cpp" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/S2C_stub.h" + #include "C:/proudnet_ue5_example/PdnUE5ExampleServer/chat_pidl/S2C_stub.cpp" + + static Proud::HostID pop_group_host_id = Proud::HostID_None; + static CHAT_C2S::Proxy c2s_proxy; + + void UGissChatNet::SendChat(const FText& message) + { + Proud::String message_pstr(*message.ToString()); + Proud::RmiContext context; + context.m_enableLoopback = true; + c2s_proxy.Chat(Proud::HostID_Server, context, message_pstr); + }
PrintChat 이벤트 변환
부모 클래스 변경
GissChatNet 노드 추가
BP_ChatWidget 최종 모습
채팅 기능 테스트 1
채팅 기능 테스트 2
이 섹션에서는 향상된 게임플레이 기능을 가진 미라지 캐릭터를 구축합니다.
BP_MirageCharacter 설정
아래와 같은 모습으로 배치되면 현 시점 기준 정상입니다.
미라지 캐릭터 배치 확인
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BaseMirageCharacter.generated.h" UCLASS() class PDNUE5EXAMPLECLIENT_API ABaseMirageCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABaseMirageCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) + void UpdateAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); };
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BaseMirageCharacter.generated.h" UCLASS() class PDNUE5EXAMPLECLIENT_API ABaseMirageCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABaseMirageCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void UpdateAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); };
BlueprintUpdateAnimation 이벤트
Update Animation Parameter 노드
변수 연결 완료
변수 추가
파라미터 저장
Event Tick 연결 해제
애니메이션 변수 연결
달리기, 점프, 조준 등의 동작 시 모션을 따라하는 미라지 캐릭터의 모습을 관찰 가능합니다.
애니메이션 복제 확인
스켈레톤 지정
애니메이션 삭제
애디티브 세팅
Apply Additive 노드 연결
모든 애니메이션 노드 수정
팔 골격의 위치가 정상 범위 내로 보정 된 것을 확인할 수 있습니다.
애니메이션 보정 결과
Layered blend per bone 설정
애니메이션 루프 활성화
A_FP_AssaultRifle_Run_Loop 최종 모습
전체 그래프 1
전체 그래프 2
걷기와 달리기 애니메이션이 재생됨을 확인할 수 있습니다.
다리 애니메이션 확인
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BaseMirageCharacter.generated.h" UCLASS() class PDNUE5EXAMPLECLIENT_API ABaseMirageCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABaseMirageCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void UpdateAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) + void OnFired(); + + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) + void OnReloaded(); };
Reload 관련 구역
On Reloaded 노드 추가
Reload 이벤트 연결
Shoot 관련 구역
Shoot 이벤트 연결
On Reloaded 이벤트 연결
On Fired 이벤트 연결
액션 애니메이션 테스트
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BaseMirageCharacter.generated.h" UCLASS() class PDNUE5EXAMPLECLIENT_API ABaseMirageCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABaseMirageCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) + void UpdateTransform(FVector3f position, FQuat4f orientation, FVector3f linearVelocity, FVector3f angularVelocity, float aimPitch); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void UpdateAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void OnFired(); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void OnReloaded(); };
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "GameFramework/Character.h" #include "BaseMirageCharacter.generated.h" UCLASS() class PDNUE5EXAMPLECLIENT_API ABaseMirageCharacter : public ACharacter { GENERATED_BODY() public: // Sets default values for this character's properties ABaseMirageCharacter(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; // Called to bind functionality to input virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void UpdateTransform(FVector3f position, FQuat4f orientation, FVector3f linearVelocity, FVector3f angularVelocity, float aimPitch); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void UpdateAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void OnFired(); UFUNCTION(BlueprintCallable, BlueprintImplementableEvent) void OnReloaded(); };
AimPitch 변수 추가
라우트 노드 1
라우트 노드 2
라우트 노드 이동
Update Transform 그래프 완성
BP_MirageCharacter UpdateTransform 구현
Position Make Vector 연결
Shoot 관련 그래프 구성
ABP_MirageCharacter AimPitch 변수
Mouse Sway 관련 부분
Transform Bone 노드 적용
위치 회전 복제 테스트
이 섹션에서는 멀티플레이어 게임플레이를 위한 미라지 캐릭터와 ProudNet 네트워킹 통합을 다룹니다.
C:\Program Files (x86)\Nettention\ProudNet\include
C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)
ProudNetServer.lib ProudNetClient.lib
xcopy /Y "C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)\libcrypto-3-x64.dll" "$(OutDir)" xcopy /Y "C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)\libssl-3-x64.dll" "$(OutDir)"
#pragma once namespace ProudSetting { namespace GAME { extern const ::Proud::Guid version; extern const int server_port; } }
#include <ProudNetClient.h> #include "setting.h" namespace ProudSetting { namespace GAME { const ::Proud::PNGUID guid = { 0x3ae33249, 0xecc6, 0x4980, { 0xbc, 0x5d, 0x7b, 0xa, 0x99, 0x9c, 0x7, 0x39 } }; const ::Proud::Guid version = ::Proud::Guid(guid); const int server_port = 33338; } }
#include <iostream> #include <format> #include <memory> #include <ProudNetServer.h> #include "setting.h" std::shared_ptr<Proud::CNetServer> net_server; int main() { net_server = std::shared_ptr<Proud::CNetServer>(Proud::CNetServer::Create()); net_server->OnClientJoin = [](Proud::CNetClientInfo* clientInfo) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] connected.\n"; }; net_server->OnClientLeave = [](Proud::CNetClientInfo* clientInfo, Proud::ErrorInfo* error, const Proud::ByteArray byte_arr) { std::cout << "Client[" << (int)clientInfo->m_HostID << "] disconnected.\n"; }; Proud::CStartServerParameter start_param; start_param.m_protocolVersion = ProudSetting::GAME::version; start_param.m_tcpPorts.Add(ProudSetting::GAME::server_port); try { net_server->Start(start_param); } catch (Proud::Exception& error) { std::cout << "Server start failed: " << error.what() << endl; return 0; } std::cout << ("Server started. Enterable commands:\n"); std::cout << ("-q : Quit.\n"); std::string input; while (true) { std::cin >> input; if (input[0] == '-') { if (input == "-q") break; } } std::cout << "Stopping server...\n"; net_server->Stop(); net_server = nullptr; std::cout << "Server stopped.\n"; return 0; }
C:\"Program Files (x86)"\Nettention\ProudNet\util\PIDL.exe "%(FullPath)" -cpp
%(Filename).PIDL Compiling...
%(RootDir)%(Directory)\%(Filename)_common.cpp %(RootDir)%(Directory)\%(Filename)_common.h %(RootDir)%(Directory)\%(Filename)_proxy.cpp %(RootDir)%(Directory)\%(Filename)_proxy.h %(RootDir)%(Directory)\%(Filename)_stub.cpp %(RootDir)%(Directory)\%(Filename)_stub.h
[access=public] global GAME_P2P 3000 { Transform([in] Proud::CharacterTransformData transformData); AnimationParams([in] Proud::CharacterAnimationParams animationParams); Action([in] Proud::CharacterAction actionId); }
// Fill out your copyright notice in the Description page of Project Settings. #pragma once #include "CoreMinimal.h" #include "Subsystems/GameInstanceSubsystem.h" #include "GissGameNet.generated.h" /** * */ UCLASS() class PDNUE5EXAMPLECLIENT_API UGissGameNet : public UGameInstanceSubsystem { GENERATED_BODY() public: UFUNCTION(BlueprintCallable) void UpdateCharacterTransform(FVector3f position, FQuat4f rotation, FVector3f linearVelocity, FVector3f angularVelocity, float aimPitch); UFUNCTION(BlueprintCallable) void UpdateCharacterAnimationParameter(bool aiming, bool closeToWall, bool moving, bool running, float jumpVelocity, FVector3f mouseSwayLocation, FVector3f moveSwayLocation); UFUNCTION(BlueprintCallable) void SendCharacterFired(); UFUNCTION(BlueprintCallable) void SendCharacterReloaded(); private: virtual void Initialize(FSubsystemCollectionBase& Collection) override; virtual void Deinitialize() override; };
Update Character Transform 변경
Send Character Fired 변경
Send Character Reloaded 변경
Update Character Animation Parameter 변경
이 섹션에서는 프로젝트 마무리, 빌드 및 종합 테스트 절차를 다룹니다.
Position 직접 연결
MirageCharacter 인스턴스 제거
빌드 구성 변경
빌드된 실행파일
언리얼 프로젝트 빌드
빌드된 클라이언트 파일
최종 테스트 결과