この記事では、初心者でも理解できるように、Unreal Engine 5.4.2 で C++ と Python を WebSocket で通信させる方法を丁寧に解説します。サンプルコードをたくさん使って、わかりやすく説明していきますので、ぜひ最後までお付き合いください。
動作動画
UE5のC++でWebSocket通信してみた⑤
やっとキャラクターがEキー押すとそれがWebSocketでサーバーに飛ぶ超シンプルなやつできた!!
---
UE4からUE5.4.2への脳内変換時に苦労したこと(参考にした動画はたぶんUE4。。。)
✅マッピングコンテキストを使う必要がある… https://t.co/4iSxIaqFBi pic.twitter.com/PHMY29P4yx— Maki@Sunwood AI Labs. (@hAru_mAki_ch) June 9, 2024
WebSocket について
WebSocket は、クライアントとサーバー間で双方向のリアルタイム通信を可能にするプロトコルです。従来の HTTP 通信とは異なり、一度接続を確立すれば、クライアントとサーバーは自由にデータをやり取りできます。ゲーム開発においても、WebSocket を使うことで、リアルタイムのマルチプレイヤー機能などを実現できます。
プロジェクトの準備
まずは、Unreal Engine 5.4.2 で新しいプロジェクトを作成しましょう。このチュートリアルでは、Third Person テンプレートを使用します。プロジェクト名は WebSocketTestV2
とします。
プロジェクトを作成したら、以下のようにプロジェクトの Build.cs
ファイルを編集して、WebSocket モジュールを追加します。
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
public class WebSocketTestV2 : ModuleRules
{
public WebSocketTestV2(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "EnhancedInput", "WebSockets", });
}
}
コードの説明:
PublicDependencyModuleNames.AddRange
は、プロジェクトで使用するモジュールを指定する部分です。WebSockets
を追加することで、WebSocket 機能が使えるようになります。
C++ 側の実装
GameInstance の設定
WebSocket の接続管理は、GameInstance
クラスで行います。以下のように、WebSocketTestGameInstance.h
と WebSocketTestGameInstance.cpp
を作成します。
WebSocketTestGameInstance.h
:
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "IWebSocket.h"
#include "WebSocketTestGameInstance.generated.h"
/**
*
*/
UCLASS()
class WEBSOCKETTESTV2_API UWebSocketTestGameInstance : public UGameInstance
{
GENERATED_BODY()
public:
virtual void Init() override;
virtual void Shutdown() override;
TSharedPtr<IWebSocket> WebSocket;
private:
};
コードの説明:
TSharedPtr<IWebSocket> WebSocket;
は、WebSocket オブジェクトを格納する変数です。
WebSocketTestGameInstance.cpp
:
// Fill out your copyright notice in the Description page of Project Settings.
#include "WebSocketTestGameInstance.h"
#include "WebSocketsModule.h"
void UWebSocketTestGameInstance::Init()
{
Super::Init();
if (!FModuleManager::Get().IsModuleLoaded("WebSockets"))
{
FModuleManager::Get().LoadModule("WebSockets");
}
WebSocket = FWebSocketsModule::Get().CreateWebSocket("ws://localhost:8080");
WebSocket->OnConnected().AddLambda([]()
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Green, "Successfully connected");
});
WebSocket->OnConnectionError().AddLambda([](const FString& Error)
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Red, Error);
});
WebSocket->OnClosed().AddLambda([](int32 StatusCode, const FString& Reason, bool bWasClean)
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, bWasClean ? FColor::Green : FColor::Red, "Connection closed " + Reason);
});
WebSocket->OnMessage().AddLambda([](const FString& MessageString)
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Cyan, "Received message: " + MessageString);
});
WebSocket->OnMessageSent().AddLambda([](const FString& MessageString)
{
GEngine->AddOnScreenDebugMessage(-1, 15.0f, FColor::Yellow, "Sent message: " + MessageString);
});
WebSocket->Connect();
}
void UWebSocketTestGameInstance::Shutdown()
{
if (WebSocket->IsConnected())
{
WebSocket->Close();
}
Super::Shutdown();
}
コードの説明:
FModuleManager::Get().LoadModule("WebSockets");
は、WebSocket モジュールをロードします。WebSocket = FWebSocketsModule::Get().CreateWebSocket("ws://localhost:8080");
は、WebSocket オブジェクトを作成し、localhost
の8080
ポートに接続します。WebSocket->OnConnected()
などのラムダ関数は、WebSocket の各イベントに対応する処理を記述します。WebSocket->Connect();
で、WebSocket サーバーに接続します。Shutdown
関数で、WebSocket の接続を切断します。
Character の設定
次に、WebSocketTestV2Character.h
と WebSocketTestV2Character.cpp
を編集して、キー入力に応じて WebSocket でメッセージを送信できるようにします。
WebSocketTestV2Character.h
:
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "Logging/LogMacros.h"
#include "WebSocketTestV2Character.generated.h"
class USpringArmComponent;
class UCameraComponent;
class UInputMappingContext;
class UInputAction;
struct FInputActionValue;
DECLARE_LOG_CATEGORY_EXTERN(LogTemplateCharacter, Log, All);
UCLASS(config=Game)
class AWebSocketTestV2Character : public ACharacter
{
GENERATED_BODY()
/** Camera boom positioning the camera behind the character */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
USpringArmComponent* CameraBoom;
/** Follow camera */
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
UCameraComponent* FollowCamera;
/** MappingContext */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputMappingContext* DefaultMappingContext;
/** Jump Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* JumpAction;
/** Move Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* MoveAction;
/** Look Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* LookAction;
/** Notify Input Action */
UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input, meta = (AllowPrivateAccess = "true"))
UInputAction* NotifyAction;
public:
AWebSocketTestV2Character();
protected:
/** Called for movement input */
void Move(const FInputActionValue& Value);
/** Called for looking input */
void Look(const FInputActionValue& Value);
void NotifyServer();
void StartNotifyServer();
void EndNotifyServer();
private:
bool bIsNotifying = false;
protected:
// APawn interface
virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
// To add mapping context
virtual void BeginPlay();
public:
/** Returns CameraBoom subobject **/
FORCEINLINE class USpringArmComponent* GetCameraBoom() const { return CameraBoom; }
/** Returns FollowCamera subobject **/
FORCEINLINE class UCameraComponent* GetFollowCamera() const { return FollowCamera; }
};
コードの説明:
NotifyAction
は、WebSocket でメッセージを送信するためのアクションです。
WebSocketTestV2Character.cpp
の一部:
void AWebSocketTestV2Character::NotifyServer()
{
UWebSocketTestGameInstance* GameInstance = Cast<UWebSocketTestGameInstance>(GetGameInstance());
if (GameInstance)
{
if (GameInstance->WebSocket->IsConnected())
{
GameInstance->WebSocket->Send("pressed E key");
}
}
}
void AWebSocketTestV2Character::StartNotifyServer()
{
if (!bIsNotifying)
{
bIsNotifying = true;
NotifyServer();
}
}
void AWebSocketTestV2Character::EndNotifyServer()
{
bIsNotifying = false;
}
コードの説明:
NotifyServer
関数で、GameInstance
から WebSocket オブジェクトを取得し、接続中であればメッセージを送信します。StartNotifyServer
とEndNotifyServer
は、キーの押下状態を管理するための関数です。
入力設定
最後に、プロジェクトの入力設定を行います。Project Settings
の Input
セクションで、NotifyAction
を E
キーにバインドします。
以上で、C++ 側の実装は完了です。
Python 側の実装
次に、Python 側で WebSocket サーバーを実装します。以下のようなコードを websocket_server.py
として保存します。
Python 側で WebSocket サーバーを実装する方法を解説します。以下のコードは、websockets
ライブラリを使用して、localhost
の 8080
ポートで WebSocket サーバーを起動し、クライアントからの接続を処理します。
import asyncio
import websockets
import uuid
from loguru import logger
import time
from tqdm import tqdm
connected_clients = set()
async def handle_client(websocket, path):
# このWebSocket接続用のユニークなIDを生成
connection_id = uuid.uuid4()
client_address = websocket.remote_address
client_info = f"接続元 IP: {client_address[0]}, ポート: {client_address[1]}"
if connection_id not in connected_clients:
# 初回接続時のみ、WebSocket IDを含むクライアント情報をログに記録し、クライアントに送信
logger.info(f"クライアント接続: {client_info} (WebSocket ID: {connection_id})")
connection_info_message = f"サーバーに接続しました。接続情報: {client_info}, WebSocket ID: {connection_id}"
await websocket.send(connection_info_message)
logger.info(f"{client_address}に接続情報を送信: {connection_info_message}")
connected_clients.add(connection_id)
try:
async for message in websocket:
logger.info(f"{client_address}からメッセージを受信 (WebSocket ID: {connection_id}): {message}")
response = f"サーバーが受信: {message}"
for i in tqdm(range(10)):
time.sleep(1)
await websocket.send(response)
logger.info(f"{client_address}に応答を送信 (WebSocket ID: {connection_id}): {response}")
except websockets.ConnectionClosed as e:
logger.info(f"クライアントによる接続切断: {client_info} (WebSocket ID: {connection_id}), コード: {e.code}, 理由: {e.reason}")
connected_clients.remove(connection_id)
logger.info(f"クライアント切断: {client_info} (WebSocket ID: {connection_id})")
async def start_server():
server = await websockets.serve(handle_client, "localhost", 8080)
logger.info("WebSocketサーバーを ws://localhost:8080 で起動")
await server.wait_closed()
if __name__ == "__main__":
logger.add("websocket_server.log", rotation="1 MB", retention="7 days")
asyncio.run(start_server())
コードの説明
-
必要なライブラリをインポートします。
asyncio
: 非同期プログラミングのためのライブラリwebsockets
: WebSocket サーバーを実装するためのライブラリuuid
: 一意の識別子を生成するためのライブラリloguru
: ログ出力を行うためのライブラリtime
,tqdm
: 処理の遅延とプログレスバーを表示するためのライブラリ
-
connected_clients
変数を定義します。この変数は、接続中のクライアントの WebSocket ID を格納するための集合です。 -
handle_client
関数を定義します。この関数は、クライアントからの接続を処理します。- 接続したクライアントごとにユニークな WebSocket ID を生成します。
- クライアントの接続情報(IP アドレスとポート)をログに記録します。
- 初回接続時のみ、WebSocket ID を含むクライアント情報をログに記録し、クライアントに送信します。
- クライアントからのメッセージを受信し、ログに記録します。
- 受信したメッセージに対する応答を生成し、10秒間のプログレスバーを表示した後、クライアントに送信します。
- クライアントが接続を切断した場合、切断情報をログに記録し、
connected_clients
から WebSocket ID を削除します。
-
start_server
関数を定義します。この関数は、WebSocket サーバーを起動します。websockets.serve
関数を使用して、localhost
の8080
ポートで WebSocket サーバーを起動します。- サーバーが起動したことをログに記録します。
-
if __name__ == "__main__":
ブロックで、ログファイルの設定を行い、start_server
関数を非同期で実行します。
動作確認
ここまでで、C++ と Python 両方の実装が完了しました。以下の手順で動作確認を行います。
-
websocket_server.py
を実行して、WebSocket サーバーを起動します。 -
Unreal Editor でプロジェクトを開き、プレイボタンを押してゲームを開始します。
-
ゲーム画面で
E
キーを押すと、WebSocket でメッセージが送信され、Python 側で受信したメッセージと現在の接続数が表示されます。また、Unreal 側では、送信したメッセージと受信したメッセージがデバッグメッセージとして表示されます。
以上で、Unreal Engine 5.4.2 で C++ と Python を WebSocket で通信させる方法の解説は終了です。
まとめ
この記事では、Unreal Engine 5.4.2 で C++ と Python を WebSocket で通信させる方法を、初心者にもわかりやすく解説しました。サンプルコードを多用し、コメントアウトで丁寧に説明することで、理解しやすくなっていると思います。
WebSocket は、リアルタイムの双方向通信を実現するための強力なツールです。ゲーム開発だけでなく、様々な分野で活用されています。この記事を通じて、WebSocket の基本的な使い方を理解し、自分の開発に役立てていただければ幸いです。
コメント