はくとうのゲーム開発ライフ

UE4周りの技術メモ

PythonからUE5にテキストを送信してみた

はじめに

Python→UE5への通信をしてRPC的なことをやってみたかったので、前座として単純にテキストを送信するプログラムを作ってみました。 今回は、クライアントサーバー型を採用しています。

サーバー  : UE5
クライアント: Pythonスクリプト

上記のような分担になります。



サーバー実装

まずはソケットの作成を行う必要があります。 以下C++関数例です。

bool AMyRemoteControlServer::CreateSocket()
{
    if (IpAddress.IsMulticastAddress())
    {
        Socket = FUdpSocketBuilder(TEXT("Multicast"))
            .WithMulticastLoopback()
            .WithMulticastTtl(1)
            .JoinedToGroup(IpAddress)
            .BoundToPort(Port)
            .Build();
    }
    else
    {
         Socket = FUdpSocketBuilder(TEXT("Unicast"))
            .BoundToAddress(IpAddress)
            .BoundToPort(Port)
            .Build();
    }
    return true;
}

IpAddressとPortについては以下のような形になっています。

 UPROPERTY(BlueprintReadWrite, EditAnywhere)
    int32 Port {7000};

    FIPv4Address IpAddress;
    

※IpAddressの初期値は、127.0.0.1(ローカルホスト)に設定

次はレシーバーの実装です。 これを実装することで、クライアントからメッセージを受け取ることができます。

bool AMyRemoteControlServer::CreateReceiver()
{
    //レシーバー作成
    Receiver = new FUdpSocketReceiver(Socket, FTimespan::FromMilliseconds(1), TEXT("Receiver"));

    //レシーバーチェック
    if (!Receiver)
    {
        return false;
    }

    //受け取った時のイベントバインド
    Receiver->OnDataReceived().BindUObject(this, &AMyRemoteControlServer::OnDataReceived);

    //受付開始
    Receiver->Start();


    return true;
}



レシーバーに登録するイベントは以下のように実装しています。

void AMyRemoteControlServer::OnDataReceived(const FArrayReaderPtr& Reader, const FIPv4Endpoint& Sender)
{
    //メッセージデータの作成
    FMessageData Data;
    for (int i = 0; i < Reader->Num(); i++)
    {
        Data.RecvName.AppendChar(Reader->GetData()[i]);
    }

    //BPイベント呼び出し
    BP_OnDataReceivedEvent(Data);
    
}

※FMessageDataはFString変数(RecvName)のみが実装されています。


ゲーム終了時に作成したソケット情報やレシーバーの削除を行います。クラッシュの原因になったりするので必要です。

void AMyRemoteControlServer::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    Super::EndPlay(EndPlayReason);


    //レシーバー削除
    delete Receiver;
    Receiver = nullptr;

    //ソケット削除
    if (Socket)
    {
        Socket->Close();
        ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket);
    }
}

最後に注意点として後でレベルに配置するので、 Actorを継承したクラスで実装しておいた方がいいです。



クライアントの実装

前述のとおり、Pythonスクリプトで実装します。

import socket
import time

M_SIZE = 1024

# Serverのアドレスを用意。Serverのアドレスは確認しておく必要がある。
serv_address = ('127.0.0.1', 7000)

# ①ソケットを作成する
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

message = 'test'
i=0

#メッセージ送信
while True:
    
    send_len = sock.sendto(message.encode('utf-8'), serv_address)
    print(message)
    i=i+1
    
    if  i==10:
        break
    
    time.sleep(1)


#終了処理
print('closing socket')
sock.close()
print('done')

IPアドレスとポート番号はサーバーに合わせています。



実行前準備

まずは、サーバー実装を行ったクラスを継承したBPを作成し、 上記のように、作成したソケット作成関数やレシーバー作成関数呼び出しをBeginPlayイベントで行います。

次にレシーバーに登録したイベントで呼ばれているBPイベント「BP_OnDataReceivedEvent」の実装を行います。

最後に上記準備を整えたサーバーアクターをレベルに配置します。



実装結果

UE5でPIEを実行して、Pythonスクリプトを実行した結果です。 うまくいけば下記のように、表示されるはずです。



まとめ

通信処理に慣れないこともあり、結構実装に時間かかりました。 受信できないときは、ReceiverのStart関数が呼ばれているかの確認をしてください。 その他だと、ソケットの設定に問題があることが多かったです。 次は、この機能を使って実際にUE5でキャラクターコントロールしてみたいと思います。