I’ve had this solved for a while, hopefully it helps someone else out as well.
Console commands were not the answer, sockets were. Basically, I just need to send a string to a certain port, and read it from Unreal Engine. Can’t say the same would work if I needed to access a variable that’s not one of the basic types, such as Anim Sequences, but I assume it should work if you carefully send the correct file path to the variable you’d want.
The code is simple (and probably bad), but here it is. It is just a basic client/server code, where Unreal Engine acts as a listener, and you send commands from python.
#include "Python.h"
#include "Engine/World.h"
#include "Networking.h"
#include "Sockets.h"
// Constructor initializes the actor. No specific initialization is needed.
APython::APython()
{
PrimaryActorTick.bCanEverTick = false;
}
// Called when the actor is spawned (which is right when the game is started). Initiates the server setup.
void APython::BeginPlay()
{
Super::BeginPlay();
StartServer();
}
// Called when the actor is destroyed. Shuts down the server and cleans up resources.
void APython::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
EndServer();
Super::EndPlay(EndPlayReason);
}
// Empty Tick function that is not needed for the code to work.
void APython::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
}
// Both functions have to exist so they can be called from Unreal Engine
// Called when Avatar starts signing. Pauses the processing of incoming data.
void APython::PauseHandlingData()
{
bShouldHandleData = false;
}
// Called when Avatar ends signing. Resumes the processing of incoming data.
void APython::ResumeHandlingData()
{
bShouldHandleData = true;
}
// Start the server to listen for incoming data, and define a socket for sending a response
void APython::StartServer()
{
// Sets up a TCP server on localhost:PORT.
FIPv4Endpoint ListenerEndpoint(FIPv4Address(127, 0, 0, 1), ListenerPort);
// Listening for incoming connections
ListenerSocket = FTcpSocketBuilder(TEXT("Python Listener"))
.AsReusable() // Can be used after being deleted (after the server is closed), without having to wait some time
.BoundToEndpoint(ListenerEndpoint) // Binds it to the Endpoint
.Listening(8); // Max number of pending connections
// Define a socket that will send a response back to Python when necessary
ResponseSocket = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateSocket(NAME_Stream, TEXT("Send response to Python"), false);
if (ListenerSocket) // If ListenerSocket was created successfully
{
// Initializes a timer to periodically handle incoming data from connected clients (Python script).
FTimerDelegate TimerDelegate;
TimerDelegate.BindLambda([this]()
{
HandleData();
});
GetWorldTimerManager().SetTimer(TickTimerHandle, TimerDelegate, 1.0f, true);
UE_LOG(LogTemp, Log, TEXT("Listener server started."));
}
// Note: The server listens for incoming connections and uses a separate socket for sending responses to Python.
}
// Send a response back to Python on a specified port.
void APython::SendResponse(const FString& Response)
{
if (ResponseSocket) // If ResponseSocket was created successfully
{
// Connects to the Python server using the ResponseSocket.
FIPv4Endpoint SendEndpoint(FIPv4Address(127, 0, 0, 1), SendPort);
ResponseSocket->Connect(*SendEndpoint.ToInternetAddr());
// Sending the response data.
const char* ResponseBuffer = TCHAR_TO_UTF8(*Response);
int32 BytesSent = 0;
ResponseSocket->Send((uint8*)ResponseBuffer, strlen(ResponseBuffer), BytesSent);
if (BytesSent == strlen(ResponseBuffer))
{
UE_LOG(LogTemp, Log, TEXT("Data sent: %s"), *Response);
}
}
}
// Handles the incoming data from connected clients (Python script)
void APython::HandleData()
{
if (!bShouldHandleData) // Data handling possibly paused by Unreal Engine
{
UE_LOG(LogExit, Log, TEXT("Currently signing."));
return;
}
if (!ListenerSocket) // If socket is not valid for any reason stop
{
UE_LOG(LogExit, Warning, TEXT("Listener server stopped!"));
return;
}
// Accepts an incoming connection.
FSocket* ActiveListenerSocket = ListenerSocket->Accept(TEXT("Listener Connection"));
if (ActiveListenerSocket && ActiveListenerSocket->GetConnectionState() == SCS_Connected)
{
// Receives data from the active socket.
char Buffer[1024] = { 0 };
int32 BytesRead = 0;
ActiveListenerSocket->Recv((uint8*)Buffer, sizeof(Buffer), BytesRead);
// Converts the data to FString and triggers an event (inside of Unreal Engine)
ReceivedData = FString(FUTF8ToTCHAR(Buffer));
OnReceivedDataChanged();
UE_LOG(LogTemp, Log, TEXT("Data received: %s"), *FString(ReceivedData));
}
// Note: Data handling may be paused by Unreal Engine using bShouldHandleData flag.
}
// Closes the server socket and cleans up associated resources.
// Also closes the socket used for sending responses to Python.
void APython::EndServer()
{
if (ListenerSocket)
{
ListenerSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ListenerSocket);
ListenerSocket = nullptr;
UE_LOG(LogTemp, Log, TEXT("Listener server ended."));
}
if (ResponseSocket)
{
ResponseSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ResponseSocket);
ResponseSocket = nullptr;
UE_LOG(LogTemp, Log, TEXT("Send server ended"));
}
}
Of course, this is just for my use-case, but it should be easily modifiable for others. I take this actor, place it inside of the level where I would like to use it (or the character), and then use the blueprint functions. It also has the functionality of sending a response back, if necessary, as a blueprint callable function. It should be fairly easy to delete that functionality if it isn’t necessary. Here is the header file:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "Python.generated.h"
UCLASS()
class PROJECT_API APython : public AActor
{
GENERATED_BODY()
private:
void StartServer();
void HandleData();
void EndServer();
FSocket* ListenerSocket;
FSocket* ResponseSocket;
FTimerHandle TickTimerHandle;
UPROPERTY(EditAnywhere)
int32 ListenerPort = 7777;
UPROPERTY(EditAnywhere)
int32 SendPort = 7776;
bool bShouldHandleData = true;
bool bShouldSendData = false;
public:
UPROPERTY(BlueprintReadWrite)
FString ReceivedData;
UFUNCTION(BlueprintImplementableEvent)
void OnReceivedDataChanged();
UFUNCTION(BlueprintCallable)
void PauseHandlingData();
UFUNCTION(BlueprintCallable)
void ResumeHandlingData();
UFUNCTION(BlueprintCallable)
void SendResponse(const FString& Response);
public:
APython();
protected:
virtual void BeginPlay() override;
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override;
public:
virtual void Tick(float DeltaTime) override;
};
On the python side, it’s a lot simpler:
import socket as st
# Starts the server
def start_server(host = "127.0.0.1", port = 7777):
server_address = host, port
socket = st.socket(st.AF_INET, st.SOCK_STREAM)
socket.connect(server_address)
return(socket)
# Close the server
def end_server(socket):
socket.close()
# Sends to the socket and then closes the server
def send_data(socket, data):
client_socket = start_server()
data_bytes = data.encode('utf-8')
socket.sendall(data_bytes)
end_server(socket)
Also, don’t forget to add the following to your PROJECT.Build.cs file, because it’s necessary for the socket stuff to work in Unreal Engine.
PrivateDependencyModuleNames.AddRange(new string[] { "Sockets", "Networking" });
And that should be it. If you carefully do the blueprint side of things as well, it should work. Hopefully this was understandable