This commit is contained in:
2025-07-14 22:24:27 +08:00
parent daacc18ecf
commit 4af19ef574
7722 changed files with 72086 additions and 0 deletions

View File

@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class TerracottaWarriorsTarget : TargetRules
{
public TerracottaWarriorsTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Game;
DefaultBuildSettings = BuildSettingsVersion.V4;
ExtraModuleNames.AddRange( new string[] { "TerracottaWarriors" } );
}
}

View File

@ -0,0 +1,28 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "BPTools.h"
FString UBPTools::ReadConfigString(const FString& FilePath)
{
if (!FPaths::FileExists(FilePath))
{
UE_LOG(LogTemp, Error, TEXT("file no exist: %s"), *FilePath);
return "";
}
FString FileContent;
bool bSuccess = FFileHelper::LoadFileToString(FileContent, *FilePath);
if (bSuccess)
{
UE_LOG(LogTemp, Log, TEXT("file content : %s"), *FileContent);
return FileContent;
}
else
{
UE_LOG(LogTemp, Error, TEXT("file read failed: %s"), *FilePath);
return "";
}
return FileContent;
}

View File

@ -0,0 +1,12 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "Tools.h"
Tools::Tools()
{
}
Tools::~Tools()
{
}

View File

@ -0,0 +1,270 @@
#include "UDPInstanceSubsystem.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonWriter.h"
#include "Sockets.h"
#include "Camera/CameraComponent.h"
#include "Common/UdpSocketBuilder.h"
#include "Common/UdpSocketReceiver.h"
#include "GameFramework/Pawn.h"
#include "Kismet/KismetMathLibrary.h"
TSharedPtr<FJsonObject> FDeviceData::ToJsonObject() const
{
TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
JsonObject->SetStringField("id", ID);
JsonObject->SetNumberField("x", X);
JsonObject->SetNumberField("y", Y);
JsonObject->SetNumberField("z", Z);
JsonObject->SetNumberField("yaw", Yaw);
JsonObject->SetNumberField("roll", Roll);
JsonObject->SetNumberField("pitch", Pitch);
return JsonObject;
}
bool FDeviceData::FromJsonObject(const TSharedPtr<FJsonObject>& JsonObject, FDeviceData& OutDeviceData)
{
if (!JsonObject.IsValid())
return false;
if (!JsonObject->TryGetStringField("id", OutDeviceData.ID))
return false;
if (!JsonObject->TryGetNumberField("x", OutDeviceData.X))
return false;
if (!JsonObject->TryGetNumberField("y", OutDeviceData.Y))
return false;
if (!JsonObject->TryGetNumberField("z", OutDeviceData.Z))
return false;
if (!JsonObject->TryGetNumberField("yaw", OutDeviceData.Yaw))
return false;
if (!JsonObject->TryGetNumberField("roll", OutDeviceData.Roll))
return false;
if (!JsonObject->TryGetNumberField("pitch", OutDeviceData.Pitch))
return false;
return true;
}
void UUDPInstanceSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
Super::Initialize(Collection);
InitNetwork();
UUID = FGuid::NewGuid().ToString();
}
void UUDPInstanceSubsystem::Deinitialize()
{
CleanupNetwork();
Super::Deinitialize();
}
void UUDPInstanceSubsystem::InitNetwork()
{
// 初始化发送socket
SenderSocket = FUdpSocketBuilder(TEXT("UDPSenderSocket"))
.AsReusable()
.WithBroadcast();
// 设置远程端点 (192.168.1.28:1234)
FIPv4Address::Parse(TEXT("192.168.30.254"), RemoteEndpoint.Address);
RemoteEndpoint.Port = 1234;
// 初始化接收socket
ReceiverSocket = FUdpSocketBuilder(TEXT("UDPReceiverSocket"))
.AsReusable()
.WithBroadcast()
.BoundToEndpoint(FIPv4Endpoint(FIPv4Address::Any, 9999)) // 监听相同端口
.WithReceiveBufferSize(65535);
// 启动接收线程
SocketReceiver = new FUdpSocketReceiver(ReceiverSocket, FTimespan::FromMilliseconds(100), TEXT("UDPReceiver"));
SocketReceiver->OnDataReceived().BindUObject(this, &UUDPInstanceSubsystem::OnDataReceived);
SocketReceiver->Start();
}
void UUDPInstanceSubsystem::CleanupNetwork()
{
// 停止接收线程
if (SocketReceiver)
{
SocketReceiver->Stop();
delete SocketReceiver;
SocketReceiver = nullptr;
}
// 关闭接收socket
if (ReceiverSocket)
{
ReceiverSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(ReceiverSocket);
ReceiverSocket = nullptr;
}
// 关闭发送socket
if (SenderSocket)
{
SenderSocket->Close();
ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SenderSocket);
SenderSocket = nullptr;
}
}
void UUDPInstanceSubsystem::SetTrackedPawn(APawn* NewPawn)
{
TrackedPawn = NewPawn;
}
void UUDPInstanceSubsystem::SendDeviceData(const FDeviceData& DeviceData)
{
if (!SenderSocket)
return;
// 将结构体转换为JSON
TSharedPtr<FJsonObject> JsonObject = DeviceData.ToJsonObject();
FString JsonString;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&JsonString);
FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);
// 发送数据
TArray<uint8> Data;
FTCHARToUTF8 Converter(*JsonString);
Data.Append((uint8*)Converter.Get(), Converter.Length());
int32 BytesSent = 0;
SenderSocket->SendTo(Data.GetData(), Data.Num(), BytesSent, RemoteEndpoint.ToInternetAddr().Get());
}
void UUDPInstanceSubsystem::SendCurrentPawnData()
{
if (!TrackedPawn)
return;
FDeviceData DeviceData;
/*DeviceData.ID = "PlayerPawn";*/
DeviceData.ID = UUID;
UCameraComponent* TargetCamera = TrackedPawn->FindComponentByClass<UCameraComponent>();
FVector Location = TargetCamera->GetComponentLocation();
DeviceData.X = Location.X;
DeviceData.Y = Location.Y;
DeviceData.Z = 0;
FRotator Rotation = TargetCamera->GetComponentRotation();
DeviceData.Yaw = Rotation.Yaw;
DeviceData.Roll = Rotation.Roll;
DeviceData.Pitch = Rotation.Pitch;
/*FVector Location = TrackedPawn->GetActorLocation();
DeviceData.X = Location.X;
DeviceData.Y = Location.Y;
DeviceData.Z = Location.Z;
FRotator Rotation = TrackedPawn->GetActorRotation();
DeviceData.Yaw = Rotation.Yaw;
DeviceData.Roll = Rotation.Roll;
DeviceData.Pitch = Rotation.Pitch;*/
SendDeviceData(DeviceData);
}
void UUDPInstanceSubsystem::SpawnActorInGameThread(TSubclassOf<AActor> Actor, const FVector& Location, const FRotator& Rotation, const FString& ID, FOnActorSpawned OnActorSpawned)
{
// 强制要求在 GameThread 调用
if (IsInGameThread())
{
UE_LOG(LogTemp, Error, TEXT("In main thread"));
return;
}
// 记录开始创建
UE_LOG(LogTemp, Log, TEXT("Actor1111 "));
// 使用智能指针或委托机制才能安全地处理异步结果
AsyncTask(ENamedThreads::GameThread, [this, Location, Rotation, ID, Actor, OnActorSpawned]()
{
if (!GetWorld())
{
UE_LOG(LogTemp, Error, TEXT("Cannot spawn actor: invalid world"));
OnActorSpawned.ExecuteIfBound(nullptr, ID);
return;
}
if (!Actor)
{
UE_LOG(LogTemp, Error, TEXT("Cannot spawn actor: invalid actor class"));
OnActorSpawned.ExecuteIfBound(nullptr, ID);
return;
}
AActor* NewActor = GetWorld()->SpawnActor<AActor>(Actor, Location, Rotation);
if (NewActor)
{
UE_LOG(LogTemp, Log, TEXT("Actor %s spawned at %s"), ID.Len() ? *ID : TEXT("Unknown"), *Location.ToString());
}
else
{
UE_LOG(LogTemp, Warning, TEXT("Failed to spawn actor %s"), *ID);
}
OnActorSpawned.ExecuteIfBound(NewActor, ID);
});
}
void UUDPInstanceSubsystem::OnDataReceived(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt)
{
// 将接收到的二进制数据转换为字符串
uint8* Data = (uint8*)ArrayReaderPtr->GetData();
int32 DataSize = ArrayReaderPtr->Num();
// 添加字符串结束符
TArray<uint8> DataWithNull;
DataWithNull.Append(Data, DataSize);
DataWithNull.Add(0); // 添加null终止符
// 转换为FString
FString DataString = UTF8_TO_TCHAR(DataWithNull.GetData());
ParseReceivedData(DataString);
// 在游戏线程中处理数据
// AsyncTask(ENamedThreads::GameThread, [this, DataString]() {
//
// });
}
void UUDPInstanceSubsystem::ParseReceivedData(const FString& DataString)
{
//UE_LOG(LogTemp, Warning, TEXT("Received Data: %s"), *DataString);
TMap<FString, FDeviceData> DeviceDataMap;
TSharedPtr<FJsonObject> JsonObject;
TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(DataString);
if (FJsonSerializer::Deserialize(Reader, JsonObject) && JsonObject.IsValid())
{
for (const auto& Pair : JsonObject->Values)
{
if (Pair.Value.IsValid() && Pair.Value->Type == EJson::Object)
{
FDeviceData DeviceData;
if (FDeviceData::FromJsonObject(Pair.Value->AsObject(), DeviceData))
{
DeviceData.ID = Pair.Key; // Set ID from map key
DeviceDataMap.Add(Pair.Key, DeviceData);
}
}
}
}
FDeviceDataMap DeviceDataMapStruct;
DeviceDataMapStruct.ReceivedDataMap = DeviceDataMap;
AsyncTask(ENamedThreads::GameThread, [&,this, DeviceDataMapStruct]()
{
OnDeviceDataReceived.Broadcast(DeviceDataMapStruct);
});
}

View File

@ -0,0 +1,27 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BPTools.generated.h"
/**
*
*/
UCLASS()
class TERRACOTTAWARRIORS_API UBPTools : public UBlueprintFunctionLibrary
{
GENERATED_BODY()
public:
/**
* Reads the content of a file and returns it as a string.
* @param FilePath The path to the file to read.
* @return The content of the file as a string, or an empty string if the file does not exist or cannot be read.
*/
UFUNCTION(BlueprintCallable)
static FString ReadConfigString(const FString& FilePath);
};

View File

@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
/**
*
*/
class TERRACOTTAWARRIORS_API Tools
{
public:
Tools();
~Tools();
};

View File

@ -0,0 +1,116 @@
#pragma once
#include "CoreMinimal.h"
#include "Subsystems/GameInstanceSubsystem.h"
#include "Networking.h"
#include "Serialization/JsonSerializer.h"
#include "UDPInstanceSubsystem.generated.h"
USTRUCT(BlueprintType)
struct FDeviceData
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString ID;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float X;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Y;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Z;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Yaw;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Roll;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
float Pitch;
// JSON 序列化
TSharedPtr<FJsonObject> ToJsonObject() const;
static bool FromJsonObject(const TSharedPtr<FJsonObject>& JsonObject, FDeviceData& OutDeviceData);
};
USTRUCT(BlueprintType)
struct FDeviceDataMap
{
GENERATED_BODY()
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Device Data")
TMap<FString, FDeviceData> ReceivedDataMap;
};
// 声明代理类型
// C++
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnDeviceDataReceived, const FDeviceDataMap&, ReceivedDataMap);
UCLASS()
class TERRACOTTAWARRIORS_API UUDPInstanceSubsystem : public UGameInstanceSubsystem
{
GENERATED_BODY()
public:
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override;
// 设置要跟踪的Pawn
UFUNCTION(BlueprintCallable, Category = "UDP")
void SetTrackedPawn(APawn* NewPawn);
// 发送设备数据
UFUNCTION(BlueprintCallable, Category = "UDP")
void SendDeviceData(const FDeviceData& DeviceData);
// 发送当前Pawn的位置数据
UFUNCTION(BlueprintCallable, Category = "UDP")
void SendCurrentPawnData();
// 设备数据接收代理
UPROPERTY(BlueprintAssignable, Category = "UDP")
FOnDeviceDataReceived OnDeviceDataReceived;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
FString UUID;
// UDPInstanceSubsystem.h 中合适的位置如类前向声明或public部分
DECLARE_DYNAMIC_DELEGATE_TwoParams(FOnActorSpawned, AActor*, SpawnedActor, const FString&, ID);
UFUNCTION(BlueprintCallable)
void SpawnActorInGameThread(TSubclassOf<AActor> Actor, const FVector& Location, const FRotator& Rotation, const FString& ID, FOnActorSpawned OnActorSpawned);
private:
// UDP 发送相关
FSocket* SenderSocket;
FIPv4Endpoint RemoteEndpoint;
// UDP 接收相关
FSocket* ReceiverSocket;
FUdpSocketReceiver* SocketReceiver;
TArray<FDeviceData> ReceivedDeviceData;
FCriticalSection ReceivedDataCriticalSection;
// 要跟踪的Pawn
UPROPERTY(Transient)
APawn* TrackedPawn;
// 初始化网络
void InitNetwork();
// 清理网络
void CleanupNetwork();
// UDP 接收回调
void OnDataReceived(const FArrayReaderPtr& ArrayReaderPtr, const FIPv4Endpoint& EndPt);
// 解析接收到的数据
void ParseReceivedData(const FString& DataString);
};

View File

@ -0,0 +1,33 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
public class TerracottaWarriors : ModuleRules
{
public TerracottaWarriors(ReadOnlyTargetRules Target) : base(Target)
{
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
PublicDependencyModuleNames.AddRange(new string[]
{
"Core", "CoreUObject", "Engine", "InputCore",
"Networking",
"Sockets",
"Json",
"JsonUtilities"
});
PrivateDependencyModuleNames.AddRange(new string[] { "Networking",
"Sockets",
"Json",
"JsonUtilities" });
// 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
}
}

View File

@ -0,0 +1,6 @@
// Fill out your copyright notice in the Description page of Project Settings.
#include "TerracottaWarriors.h"
#include "Modules/ModuleManager.h"
IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, TerracottaWarriors, "TerracottaWarriors" );

View File

@ -0,0 +1,6 @@
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"

View File

@ -0,0 +1,15 @@
// Fill out your copyright notice in the Description page of Project Settings.
using UnrealBuildTool;
using System.Collections.Generic;
public class TerracottaWarriorsEditorTarget : TargetRules
{
public TerracottaWarriorsEditorTarget(TargetInfo Target) : base(Target)
{
Type = TargetType.Editor;
DefaultBuildSettings = BuildSettingsVersion.V4;
ExtraModuleNames.AddRange( new string[] { "TerracottaWarriors" } );
}
}