ProudNet UE5 Example

A comprehensive guide for implementing multiplayer games using ProudNet networking library and Unreal Engine 5. Covers chat systems, character networking, and advanced multiplayer features.

📋 Table of Contents

1. Project Setup

This section covers the initial setup and configuration needed to create an Unreal Engine 5 project with ProudNet enabled.

1.1. ProudNet Installation

  1. Install the latest release from the https://github.com/Nettention/ProudNet repository.
    1. The default installation path is "C:\Program Files (x86)\Nettention\ProudNet". This guide and the UE5 plugin are configured for this path, so if you change the installation path, you'll need to modify the plugin source code and carefully review the guide.

1.2. Visual Studio Project Configuration

  1. Create a blank project in Visual Studio
    • This guide uses the following path/name.
    Visual Studio Project Creation

    Visual Studio Project Creation

  2. Add 3 new items to the chat_server project: main.cpp, setting.cpp, setting.h. Adding Project Files

    Adding Project Files

    Project Structure

    Project Structure

  3. Modify the properties of the chat_server project.
    1. "C/C++ - Language - C++ Language Standard"
      1. Select "ISO C++20 Standard (/std:C++20)" from the dropdown
    2. "C/C++ - General - Additional Include Directories"
      1. Select <Edit…> from the dropdown
      2. Add the following path in the popup window
      C:\Program Files (x86)\Nettention\ProudNet\include
    3. "Linker - General - Additional Library Directories"
      1. Select <Edit…> from the dropdown
      2. Add the following path in the popup window
      C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)
    4. "Linker - Input - Additional Dependencies"
      1. Select <Edit…> from the dropdown
      2. Add the following filenames in the popup window
      ProudNetServer.lib
      ProudNetClient.lib
    5. "Build Events - Post-Build Event - Command Line"
      1. Select <Edit…> from the dropdown
      2. Add the following commands in the popup window
      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)"
  4. Enter code into each source file.
    • setting.h (diff)
      + #pragma once
      + 
      + namespace ProudSetting
      + {
      +     namespace CHAT
      +     {
      +         extern const ::Proud::Guid version;
      +         extern const int server_port;
      +     }
      + }
    • setting.cpp (diff)
      + #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;
      +     }
      + }
    • main.cpp (diff)
      + #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;
      + }

    Complete Code

    • setting.h
      #pragma once
      
      namespace ProudSetting
      {
          namespace CHAT
          {
              extern const ::Proud::Guid version;
              extern const int server_port;
          }
      }
    • setting.cpp
      #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;
          }
      }
    • main.cpp
      #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;
      }
  5. Build and run the project (F5) to see the following screen. Server Running Screen

    Server Running Screen

1.3. Unreal Engine Project Configuration

  1. Add Infima Games' Free FPS Template to your library.
  2. Create a template project following the procedure below.
    1. Launch Epic Games Launcher
    2. Navigate to the Unreal Engine tab
    3. Navigate to the Library tab
    4. Click "Create Project" for "Free FPS Template & Tutorial" in the Fab library list
    5. Change the name and folder, then click Create
      • This guide uses the following name and path.
      Unreal Project Creation

      Unreal Project Creation

  3. Create a C++ class following the procedure below.
    1. Top Toolbar - Tools - New C++ Class C++ Class Creation Menu

      C++ Class Creation Menu

    2. Show All Classes, then select GameInstanceSubsystem GameInstanceSubsystem Selection

      GameInstanceSubsystem Selection

    3. Class Type - Select Public and enter class name, then create class
      1. This guide uses GissChatNet. Enter Class Name

        Enter Class Name

  4. Generate the VS solution for the Unreal project following the procedure below.
    1. Close the Unreal Engine editor.
    2. Navigate to the Unreal project folder.
    3. Right-click PdnUE5ExampleClient.uproject and select Generate Visual Studio project files
  5. Change the startup project (recommended)
    • Guide image
    Change Startup Project

    Change Startup Project

  6. Apply the UE5 ProudNet plugin following the procedure below.
    1. Open the Unreal project folder.

      Shortcut: Epic Games Launcher → My Projects → Right-click project → Show in Folder

    2. Extract the following file and place it in the Plugins folder.

      Create a new Plugins folder if it doesn't exist.

      ProudNet UE5 Lib Linker Plugin.zip

      Plugin Folder Structure

      Plugin Folder Structure

    3. Regenerate the Visual Studio project solution.
      1. Close the Visual Studio IDE.
      2. Right-click PdnUE5ExampleClient.uproject and select Generate Visual Studio project files
    4. Modify the PdnUE5ExampleClient.Build.cs code.
      • Guide image Build.cs File Location

        Build.cs File Location

      • Modifications
        // 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
        	}
        }
      • Complete Code
        // 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
        	}
        }
  7. Modify the GissChatNet.h and .cpp code.
    • Guide image GissChatNet Files

      GissChatNet Files

    • GissChatNet.h (diff)
      // 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;
      };
    • GissChatNet.cpp (diff)
      // 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;
      + }
    • Complete Code
      • GissChatNet.h
        // 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;
        };
      • GissChatNet.cpp
        // 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;
        }
  8. Testing
    1. Build and run chat_server from PdnUE5ExampleServer. chat_server Execution

      chat_server Execution

    2. Build PdnUE5ExampleClient and run the level in the Unreal Engine editor. Unreal Editor Execution

      Unreal Editor Execution

    3. Success if client and server connect as shown below. Server Connection Success

      Server Connection Success

      Client Connection Success

      Client Connection Success

2. Creating Local Chat

This section covers creating a local chat window and implementing basic chat functionality in Unreal Engine 5.

2.1. Creating Chat Widget

  1. Create a BP_ChatWidget widget blueprint following the procedure below
    1. Right-click on an empty area in the Content Browser to open the context menu.
    2. Select "Create Advanced Asset - User Interface - Widget Blueprint". Widget Blueprint Creation

      Widget Blueprint Creation

    3. Select "User Widget"
    4. Change the name to BP_ChatWidget Widget Name Change

      Widget Name Change

  2. Configure the appearance of the BP_ChatWidget widget following the procedure below.
    1. Double-click BP_ChatWidget to open the Blueprint editor.
    2. Configure the widget according to the structure below.
      • Widget Element Structure
        >> Canvas Panel
        	>> Canvas Panel
        		- Anchor: Bottom Left
        		- Position: [x: 100, y: -200]
        		- Size: [x: 640, y: 360]
        		- Alignment: [x: 0, y: 1]
        		>> Scroll Box
        			- Anchor: Top
        			- Position: [x: 0, y: 0]
        			- Size: [x: 640, y: 300]
        			- Alignment: [x: 0.5, y: 0]
        		>> Canvas Panel
        			- Anchor: Bottom
        			- Position: [x: 0, y: 0]
        			- Size: [x: 640, y: 48]
        			- Alignment: [x: 0.5, y: 1]
        			>> Text Box
        				- Anchor: Left
        				- Position: [x: 0, y: 0]
        				- Size: [x: 520, y: 48]
        				- Alignment: [x: 0, y: 0.5]
        			>> Button
        				- Anchor: Right
        				- Position: [x: 0, y: 0]
        				- Size: [x: 100, y: 48]
        				- Alignment: [x: 1, y: 0.5]
        				>> Text
        					- Text: "Send"

      The appearance upon completion of configuration is as follows.

      Chat Widget Configuration

      Chat Widget Configuration

  3. Add BP_ChatWidget to viewport following the procedure below.
    1. Open the Level Blueprint editor. Opening Level Blueprint Editor

      Opening Level Blueprint Editor

    2. Add Create Widget node and change the class to BP_ChatWidget. Adding Create Widget Node

      Adding Create Widget Node

      Class Setting

      Class Setting

    3. Add Add to Viewport node and connect them. Adding Add to Viewport Node

      Adding Add to Viewport Node

      Node Connection

      Node Connection

  4. Run the level and confirm that the chat window has been added. Screen with Chat Window Added

    Screen with Chat Window Added

2.2. Implementing Chat Functionality

  1. Create a new widget blueprint BP_ChatBlock and configure its appearance.
    1. Configure the widget according to the structure below.
      • Widget Element Structure
        >> Canvas Panel
        	>> Border
        		- Anchor: Top Left
        		- Position: [x: 0, y: 0]
        		- Size: [x: 640, y: 40]
        		- Alignment: [x: 0, y: 0]
        		- Brush Color: [R: 1, G: 1, B: 1, A: 0.5]
        		>> Text
        			> Name: ChatText
        			> Check Variable
        			- Horizontal Alignment: Left
        			- Vertical Alignment: Center
        			- Font-Size: 20

      The appearance upon completion of configuration is as follows.

      BP_ChatBlock Configuration

      BP_ChatBlock Configuration

  2. Configure the Blueprint for BP_ChatBlock widget following the procedure below.
    1. Switch to Blueprint Graph mode. Blueprint Graph Mode

      Blueprint Graph Mode

    2. Configure the SetChatText function.
      1. Create a function in the Blueprint window. Function Creation

        Function Creation

      2. Set function parameters in the Details window. Function Parameter Setting

        Function Parameter Setting

      3. Drag ChatText from the variable list in the Blueprint window and select Get ChatText.
      4. Create and connect SetText (Text) node. Be careful not to accidentally select other items. Adding SetText Node

        Adding SetText Node

        Node Connection Complete

        Node Connection Complete

  3. Configure the BP_ChatWidget widget following the procedure below.
    1. Open the BP_ChatWidget Blueprint editor and switch to Designer mode.
    2. Modify the widget referring to the following.
      • Widget Element Structure
        -> Canvas Panel
        	-> Canvas Panel
        		-> Scroll Box
        			> Name: ChatBlockArea
        			> Check Variable
        		-> Canvas Panel
        			-> Button
        				> Name: SendButton
        				> Check Variable
        			-> Text Box
        				> Name: ChatTextBox
        				> Check Variable

      The appearance after modification is as follows.

      BP_ChatWidget Modification Complete

      BP_ChatWidget Modification Complete

  4. Switch the Blueprint editor to Graph mode.
  5. Configure the PrintChat function.
    1. Add a function. Adding PrintChat Function

      Adding PrintChat Function

    2. Add Create Widget node and specify BP_ChatBlock for the class.
    3. Add and connect BP_ChatBlock's SetChatText node.
    4. Drag and add the ChatBlockArea variable.
    5. Add Add Child node and connect. PrintChat Function Configuration

      PrintChat Function Configuration

  6. Implement the chat message sending event.
    1. Select SendButton from variables and add a click event. Adding Button Click Event

      Adding Button Click Event

    2. Drag and add the ChatTextBox variable and PrintChat function.
    3. Add Get Text (Text Box) node and connect all. Chat Send Event Complete

      Chat Send Event Complete

  7. Additional features can be implemented by modifying the Blueprint graph as follows (optional). Additional Feature Implementation 1

    Additional Feature Implementation 1

    Additional Feature Implementation 2

    Additional Feature Implementation 2

    1. Ignore sending if no message is entered
    2. Automatically delete the input message after sending
    3. Auto-scroll chat window to bottom when new chat arrives
  8. Run the level and confirm that chat functionality has been added. Chat Function Verification

    Chat Function Verification

3. Creating Online Chat

This section covers implementing network chat functionality using ProudNet.

3.1. Visual Studio Project Work

  1. Add a new empty project chat_pidl in PdnUE5ExampleServer.
  2. Add two files C2S.PIDL, S2C.PIDL and modify their properties.
    1. Select "Custom Build Tool" from "General - Item Type" dropdown
    2. Enter text in "Custom Build Tool - General - Command Line"
      C:\"Program Files (x86)"\Nettention\ProudNet\util\PIDL.exe "%(FullPath)" -cpp
    3. Enter text in "Custom Build Tool - General - Description"
      %(Filename).PIDL Compiling...
    4. Select <Edit…> from "Custom Build Tool - General - Outputs" dropdown and add the following paths in the popup window
      %(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
  3. Enter code in each file.
    • Complete Code
      • C2S.PIDL
        [access=public]
        global CHAT_C2S 3000
        {
            Chat([in] Proud::String message);
        }
      • S2C.PIDL
        [access=public]
        global CHAT_S2C 4000
        {
            SystemChat([in] Proud::String message);
            BroadcastChat([in] int sender_id, [in] Proud::String message);
        }
  4. Build the chat_pidl project. You can check the built source code files in the chat_pidl project folder. PIDL Build Results

    PIDL Build Results

  5. Modify the main.cpp source code of the chat_server project.
    • Modifications
      #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;
      }
    • Complete Code
      • main.cpp
        #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;
        }

3.2. Unreal Project Work

  1. Create a C++ class BaseChatWidget that inherits from UserWidget in the Unreal Engine editor.
  2. Modify the source code of the created BaseChatWidget class.
    • Modifications
      • BaseChatWidget.h
        // 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);
        };
    • Complete Code
      • BaseChatWidget.h
        // 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);
        };
  3. Modify the source code of the GissChatNet class.
    • Modifications (showing main parts only)
      • GissChatNet.h
        // 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;
        };
      • GissChatNet.cpp (main addition parts)
        + #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);
        + }
  4. Modify the Blueprint graph of BP_ChatWidget.
    1. Convert the PrintChat function to an event. PrintChat Event Conversion

      PrintChat Event Conversion

    2. Change the parent class of BP_ChatWidget to BaseChatWidget. Parent Class Change

      Parent Class Change

    3. Modify the event graph when SendButton is clicked.
      1. Add Get GissChatNet node and Send Chat node Adding GissChatNet Node

        Adding GissChatNet Node

      2. Replace the existing Print Chat node with Send Chat node.
    4. The final appearance of the BP_ChatWidget Blueprint graph is as follows. BP_ChatWidget Final Appearance

      BP_ChatWidget Final Appearance

3.3. Chat Testing

  1. Build and run the chat_server project.
  2. Run the Unreal Engine project level and verify the chat functionality. Chat Function Test 1

    Chat Function Test 1

    Chat Function Test 2

    Chat Function Test 2

4. Implementing Mirage Character

This section covers building a Mirage character with enhanced gameplay features.

4.1. Adding Mirage Blueprint Character

  1. Duplicate two Blueprint classes.
    1. Navigate to "All - Content - InfimaGames - FreeFPSTemplate - Core" in the Content Browser.
    2. Duplicate the two Blueprint classes BP_Character and ABP_Character.
    3. Rename them to BP_MirageCharacter and ABP_MirageCharacter respectively.
  2. Open the BP_MirageCharacter Blueprint editor and proceed with the following steps.
    1. Select CharacterArms in the Components window.
    2. Change the Anim Class of Animation to ABP_MirageCharacter.
    3. Set the Skeletal Mesh Asset of the mesh to SKM_Manny_Simple. BP_MirageCharacter Configuration

      BP_MirageCharacter Configuration

  3. Place BP_MirageCharacter in the level and run to verify.

    If placed as shown below, it is normal based on the current point in time.

    Mirage Character Placement Verification

    Mirage Character Placement Verification

4.2. Movement Animation Mirroring

4.2.1. Animation Variable Duplication

  1. Implement the C++ class BaseMirageCharacter.
    1. Create a BaseMirageCharacter class that inherits from Character
    2. Modify the source code of the created class.
      • Modifications
        • BaseMirageCharacter.h
          // 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);
          
          };
      • Complete Code
        • BaseMirageCharacter.h
          // 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);
          
          };
  2. Set BaseMirageCharacter as the parent class of BP_MirageCharacter
  3. Implement animation variable duplication using event functions.
    1. Modify the ABP_Character Blueprint.
      1. Open the ABP_Character Blueprint editor and navigate to the Event Graph tab.
      2. Navigate to the end of the BlueprintUpdateAnimation event execution flow. BlueprintUpdateAnimation Event

        BlueprintUpdateAnimation Event

      3. Create and connect a Get Actor Of Class node and specify BP_MirageCharacter for the Class.
      4. Create and connect an Update Animation Parameter node and connect all variables. All variables can be obtained from the Blueprint's variable list. Update Animation Parameter Node

        Update Animation Parameter Node

        Variable Connection Complete

        Variable Connection Complete

    2. Modify the BP_MirageCharacter Blueprint.
      1. Open the BP_MirageCharacter Blueprint editor and navigate to the Event Graph tab.
      2. Add 4 variables. Add Variables

        Add Variables

      3. Add an Event UpdateAnimationParameter node.
      4. Store the event parameters in Blueprint variables. Parameter Storage

        Parameter Storage

      5. Disconnect the Event Tick node connection. Event Tick Disconnection

        Event Tick Disconnection

    3. Modify the ABP_MirageCharacter Blueprint.
      1. Open the ABP_MirageCharacter Blueprint editor and navigate to the Event Graph tab.
      2. Add a Try Get Pawn Owner node.
      3. Add a Cast To BP_MirageCharacter node and connect it.
      4. Get variables from the cast character and store them in the animation Blueprint variables. Animation Variable Connection

        Animation Variable Connection

  4. Run level and verify animation duplication functionality

    You can observe the mirage character mimicking motions during actions like running, jumping, and aiming.

    Animation Duplication Verification

    Animation Duplication Verification

4.2.2. Corrective Animation Application

  1. Prepare corrective animation
    1. Download the animation file.

      Invert.fbx (File download required)

    2. Drag the downloaded file into the Content Browser of the Unreal Editor.
    3. Specify SKEL_UE5_Mannequin in the Skeleton field of the Animations tab. Skeleton Assignment

      Skeleton Assignment

    4. Click the Import button.
    5. Delete one of the two generated animations. (Optional) Animation Deletion

      Animation Deletion

  2. Modify the corrective animation.
    1. Open the Blueprint editor for the Inver_Anim_Scene animation sequence.
    2. Modify the Additive Settings in the Asset Details window.
      1. Additive Anim Type - Select Mesh Space
      2. Base Pose Type - Select Selected animation frame
      3. Base Pose Animation - Select A_FP_AssaultRifle_Idle_Loop Additive Settings

        Additive Settings

  3. Modify the ABP_MirageCharacter Blueprint graph.
    1. Add Apply Additive node and Invert_Anim_Scene animation node.
    2. Connect with the A_FP_AssaultRifle_Idle_Loop animation node. Apply Additive Node Connection

      Apply Additive Node Connection

    3. Perform the same operation for the remaining 4 animation nodes. Use Inver_Anim_Scene and Apply Additive identically for all. All Animation Node Modifications

      All Animation Node Modifications

  4. Run level and verify animation correction results

    You can confirm that the arm skeleton positions have been corrected to normal ranges.

    Animation Correction Results

    Animation Correction Results

4.2.3. Applying Leg Animation During Movement

  1. Modify the part connected to A_FP_AssaultRifle_Run_Loop in the ABP_MirageCharacter - AnimGraph Blueprint graph following the procedure below.
    1. Add a Layered blend per bone node.
    2. Modify the details of the added node.
      1. Settings - Layer Setup - Add two branch filters to Index[0].
      2. Specify the bone names as thigh_l and thigh_r respectively. Layered Blend Per Bone Settings

        Layered Blend Per Bone Settings

    3. Connect the existing Apply Additive node to the Base Pose of the above node.
    4. Add an MM_Run_Fwd animation node and connect it to Blend Poses 0 of the above node.
    5. Enable animation loop in the details window of the MM_Run_Fwd node. Animation Loop Activation

      Animation Loop Activation

    6. The final appearance is as follows. Adjusting the size of comment nodes is optional. A_FP_AssaultRifle_Run_Loop Final Appearance

      A_FP_AssaultRifle_Run_Loop Final Appearance

  2. Apply the above process similarly to the remaining 4 animation nodes.
    1. The Layered blend per bone node settings are identical, so you can copy and use them.
    2. Instead of the MM_Run_Fwd node, you should use MM_Walk_Fwd or MM_Idle as appropriate.
    3. After completing all processes, the graph appears as follows. Complete Graph 1

      Complete Graph 1

      Complete Graph 2

      Complete Graph 2

  3. Run level and verify leg animation application

    You can confirm that walking and running animations are being played.

    Leg Animation Verification

    Leg Animation Verification

4.3. Action Animation and Motion Duplication

  1. Modify the source code of the BaseMirageCharacter class.
    • Modifications
      • BaseMirageCharacter.h
        // 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();
        
        };
  2. Modify the BP_Character Blueprint graph.
    1. Create a graph that calls the mirage character's reload event when reloading.
      1. Navigate to the Reload-related area at the top of the graph. Reload Related Area

        Reload Related Area

      2. Create a Get Actor Of Class node and specify BP_MirageCharacter for the class.
      3. Connect to the On Reloaded function node. On Reloaded Node Addition

        On Reloaded Node Addition

      4. Connect between the Reload and Delay functions. Reload Event Connection

        Reload Event Connection

    2. Create a graph that calls the mirage character's fire event when shooting.
      1. Navigate to the end of the Shoot-related area on the right side of the graph. Shoot Related Area

        Shoot Related Area

      2. Create a Get Actor Of Class node and specify BP_MirageCharacter for the class.
      3. Connect to the On Fired function node.
      4. Connect between the (Current Ammunition) Set and Delay functions. Shoot Event Connection

        Shoot Event Connection

  3. Modify the BP_MirageCharacter Blueprint graph.
    1. Navigate to the Reload-related area at the top of the graph.
    2. Connect the On Reloaded event node in front of the Play Montage node On Reloaded Event Connection

      On Reloaded Event Connection

    3. Navigate to the Shoot-related area on the right side of the graph.
    4. Connect the On Fired event node in front of the Shoot node On Fired Event Connection

      On Fired Event Connection

  4. Run the level and you can see the mirage character mimicking reload and fire actions. Action Animation Test

    Action Animation Test

4.4. Position, Rotation and Other Duplications

  1. Modify the source code of the BaseMirageCharacter class.
    • Modifications
      • BaseMirageCharacter.h
        // 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();
        
        };
    • Complete Code
      • BaseMirageCharacter.h
        // 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();
        
        };
  2. Modify the BP_Character Blueprint graph.
    1. Add a float type AimPitch variable. AimPitch Variable Addition

      AimPitch Variable Addition

    2. Navigate to the end of the Tick event code.
    3. Create two route nodes and connect the Branch node's False and Stop Running nodes. Route Node 1

      Route Node 1

      Route Node 2

      Route Node 2

    4. Move the route nodes. Route Node Movement

      Route Node Movement

    5. Create a graph that calls BP_MirageCharacter's Update Transform The completed graph appearance is as follows, and you can check the process below. Update Transform Graph Complete

      Update Transform Graph Complete

      View Process
      1. Create a Get Actor Of Class node and specify BP_MirageCharacter for the class.
      2. Connect to the Update Transform function node. Update Transform Node Addition

        Update Transform Node Addition

      3. Create nodes that get position/rotation values using Get Actor Location and Get Actor Rotation. Position/Rotation Node Addition

        Position/Rotation Node Addition

      4. Connect Get Actor Rotation to the To Quaternion node. To Quaternion Connection

        To Quaternion Connection

      5. Create a Get Character Arms node and connect to nodes that get velocity and angular velocity. Character Arms Node

        Character Arms Node

        Velocity/Angular Velocity Nodes

        Velocity/Angular Velocity Nodes

      6. Create a Get Control Rotation node and calculate the Pitch rotation value from the return value. All nodes can be connected through node names. The multiplication node in the middle can be created with Multiply. Control Rotation and Pitch Calculation

        Control Rotation and Pitch Calculation

      7. Connect all added nodes to the appropriate parameters of Update Transform. The Get Physics Linear Velocity node must have its execution pin connected.
  3. Modify the BP_MirageCharacter Blueprint graph.
    1. Add a float type AimPitch variable.
    2. Add an UpdateTransform event node.
    3. Add and connect each node appropriate for the event node. BP_MirageCharacter UpdateTransform Implementation

      BP_MirageCharacter UpdateTransform Implementation

    4. Disconnect the Position node connection and add a Make Vector node to connect. This is a measure to prevent bug behavior in single test environments. Position Make Vector Connection

      Position Make Vector Connection

    5. Navigate to the Shoot-related section.
    6. Configure and connect the graph as follows. Shoot Related Graph Configuration

      Shoot Related Graph Configuration

  4. Modify the event graph of the ABP_MirageCharacter Blueprint.
    1. Add a float type AimPitch variable.
    2. Retrieve and store values from the cast BP_MirageCharacter. ABP_MirageCharacter AimPitch Variable

      ABP_MirageCharacter AimPitch Variable

  5. Modify the AnimGraph of the ABP_MirageCharacter Blueprint.
    1. Navigate to the Mouse Sway related section. Mouse Sway Related Section

      Mouse Sway Related Section

    2. Add a Transform (Modify) Bone node and connect it in front of the Mouse Sway comment node.
    3. Create a Rotator with the AimPitch variable value and apply it to the node's Rotation. Transform Bone Node Application

      Transform Bone Node Application

  6. Run the level and test the functionality. The mirage character now aims and fires in the same direction as the player character. Position Rotation Duplication Test

    Position Rotation Duplication Test

5. Mirage Character Online Integration

This section covers integrating mirage character with ProudNet networking for multiplayer gameplay.

5.1. Visual Studio Project Work

5.1.1. game_server Project Creation

  1. Create an empty project game_server in Visual Studio
  2. Add 3 new items to the game_server project: main.cpp, setting.cpp, setting.h.
  3. Modify the properties of the game_server project.
    1. "C/C++ - Language - C++ Language Standard" Select "ISO C++20 Standard (/std:C++20)" from the dropdown
    2. "C/C++ - General - Additional Include Directories" Select <Edit…> from the dropdown Add the following path in the popup window
      C:\Program Files (x86)\Nettention\ProudNet\include
    3. "Linker - General - Additional Library Directories" Select <Edit…> from the dropdown Add the following path in the popup window
      C:\Program Files (x86)\Nettention\ProudNet\lib\$(Platform)\v140\$(Configuration)
    4. "Linker - Input - Additional Dependencies" Select <Edit…> from the dropdown Add the following filenames in the popup window
      ProudNetServer.lib
      ProudNetClient.lib
    5. "Build Events - Post-Build Event - Command Line" Select <Edit…> from the dropdown Add the following commands in the popup window
      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)"
  4. Enter code into each source file.
    • Complete Code
      • setting.h
        #pragma once
        
        namespace ProudSetting
        {
            namespace GAME
            {
                extern const ::Proud::Guid version;
                extern const int server_port;
            }
        }
      • setting.cpp
        #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;
            }
        }
      • main.cpp
        #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;
        }

5.1.2. game_pidl Project Creation

  1. Add a new empty project game_pidl in PdnUE5ExampleServer.
  2. Add file P2P.PIDL and modify its properties.
    1. "General - Item Type" Select "Custom Build Tool" from the dropdown
    2. "Custom Build Tool - General - Command Line" Enter text
      C:\"Program Files (x86)"\Nettention\ProudNet\util\PIDL.exe "%(FullPath)" -cpp
    3. "Custom Build Tool - General - Description" Enter text
      %(Filename).PIDL Compiling...
    4. "Custom Build Tool - General - Outputs" Select <Edit…> from the dropdown Add the following paths in the popup window
      %(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
  3. Enter code into the file.
    • Complete Code
      • P2P.PIDL
        [access=public]
        global GAME_P2P 3000
        {
            Transform([in] Proud::CharacterTransformData transformData);
            AnimationParams([in] Proud::CharacterAnimationParams animationParams);
            Action([in] Proud::CharacterAction actionId);
        }
  4. Build the game_pidl project.

5.1.3. game_server Project Source Code Modification

  1. Modify the source code of the game_server project.
    • Apply modifications to add P2P group management functionality.
    • Add data structures and serialization functions to setting.h and setting.cpp.

5.2. Unreal Project Work

  1. GissGameNet C++ Class Creation
    1. Create a C++ class GissGameNet that inherits from GameInstanceSubsystem.
    2. Modify the source code of the GissGameNet class.
      • GissGameNet.h
        // 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;
        };
  2. Modify the BP_Character Blueprint graph.
    1. Replace the graph that calls BP_MirageCharacter's Update Transform with a graph that calls GissGameNet's Update Character Transform. Update Character Transform Change

      Update Character Transform Change

    2. Replace the graph that calls BP_MirageCharacter's On Fired with a graph that calls GissGameNet's Send Character Fired. Send Character Fired Change

      Send Character Fired Change

    3. Replace the graph that calls BP_MirageCharacter's On Reloaded with a graph that calls GissGameNet's Send Character Reloaded. Send Character Reloaded Change

      Send Character Reloaded Change

  3. Modify the event graph of the ABP_Character Blueprint.
    1. Replace the graph that calls BP_MirageCharacter's Update Animation Parameter with a graph that calls GissGameNet's Update Character Animation Parameter. Update Character Animation Parameter Change

      Update Character Animation Parameter Change

  4. After building and running the server, when running the level, you can confirm the same results as before the integration work.

6. Final Project Build and Testing

This section covers project finalization, build, and comprehensive testing procedures.

6.1. Test Code Modification

  1. Modify the BaseMirageCharacter class code.
    • Add SetId and GetId functions for character ID management.
  2. Modify the GissGameNet class code.
    • Implement an ID-based management system for character identification in multiplayer environments.
    • Add functionality to automatically create mirage characters when P2P members join.
    • Modify to disable loopback so that one's own actions are not sent back to oneself.
  3. Modify the BP_MirageCharacter Blueprint class.
    1. Reconnect the Position of the Update Transform event directly to Set Actor Location. Position Direct Connection

      Position Direct Connection

  4. Remove the BP_MirageCharacter instance from the Outliner window in the Level Editor. MirageCharacter Instance Removal

    MirageCharacter Instance Removal

6.2. Project Build

  1. Server Executable Build
    1. Change the build configuration through the dropdown at the top of Visual Studio. Build Configuration Change

      Build Configuration Change

    2. Build the chat_server and game_server projects respectively.
    3. You can check the built executable files in the solution's subfolders. Built Executable Files

      Built Executable Files

  2. Client Executable Build
    1. You can build the Unreal project through the button next to level execution in the Unreal Editor. Unreal Project Build

      Unreal Project Build

    2. You can check the built files through the specified path. Built Client Files

      Built Client Files

6.3. Testing

  1. Run the built chat_server.exe and game_server.exe from the server project.
  2. Run PdnUE5ExampleClient.exe from the Windows folder built in the Unreal project. Be careful as this may put a heavy load on the computer depending on your PC specifications.
  3. You can see two clients using the chat server and game server. Final Test Results

    Final Test Results