UE5.3 C++ 房屋管理系统(一)

一.框架思路

1.如何加载。房屋管理,既然管理。就存在动态加载,和静态加载的考虑。如果是静态加载,就是在编辑器情况下放置,但这样方便了摆放,但管理就需要在开始是将所有的房屋找到加到管理者里。你无法决定拖入场景的类符不符合房屋管理者的要求限制,如果属性不对,可能会有Bug。动态加载,在运行时,才可以生成设置。这个生成就是一定满足管理者,不然不生成,相反无论怎么写都不太可能,写的运行时和编辑器一样方便摆放设置。各有优缺点,所以这里我使用的动态加载,原因只是因为动态加载我写过。

2.采用哪个容器存储,使用TMap。因为频繁的访问,用键值队可以快速找到值也就是房屋类。并且删除,和修改。不会是大量的数据同时删除和增加。TMap的建,就使用房屋的ID属性。保证它的唯一性,和不重复性。

	UPROPERTY(EditAnywhere)TMap<int, ABuildBase*>	m_TargetMap;

3.设计模型,采用单例模式。这里用房屋管理者是单例 UBuildManagerInstance,全局访问,负责增删改查。房屋工厂类是单例 UBuildFactory,动态组装房屋,并设置ID等属性。

4. 多态,管理不同类型的房屋,这里主要是提高可扩展性,被没有一定要分很多类型。每个类型,都继承自房屋基本类ABuildBase。管理TMap里也是存储 BuildBase。如果有拓展的功能,就用Cast,将对于指向基类指针转换为,多态的子类。扩展的属性和功能就能访问到。

二.框架搭建

1.先把基类和管理者写好

基类本质继承自Actor。里面有通用属性注释里都有。动态加载 模型后,也是存储在它自己的,m_childComponentMap组件字典里,这个用在自己读取JSON文件动态加载,组装后加入到这个Map。相当于每个房屋,管理自己的身上的模型等部件。

UCLASS()
class LZJCORE_API ABuildBase : public AActor
{GENERATED_BODY()public:	// Sets default values for this actor's propertiesABuildBase();protected:// Called when the game starts or when spawnedvirtual void BeginPlay() override;
protected:FConfigData ConfigData;  //配置信息//Load model assets in form TEXT("xxxx")class UStaticMesh* GetStaticMeshAssets(const FString MeshName);
public:	UFUNCTION(BlueprintCallable)int GetID();// Called every framevirtual void Tick(float DeltaTime) override;UFUNCTION(BlueprintCallable)virtual void Constitution(const FString& strFile);UFUNCTION(BlueprintCallable)virtual void LoadStaticMesh();UPROPERTY(VisibleAnywhere)TMap<FString, UActorComponent*>	m_childComponentMap;	//组件字典TMap<FString, EComType>	m_childComponentTypeMap;//组件类型字典UPROPERTY(VisibleAnywhere)FString		m_ResourcesPath; //加载模型的路径UPROPERTY(VisibleAnywhere)FString		m_BuildName;  //房屋对于的名字,加载文件夹UPROPERTY(VisibleAnywhere, BlueprintReadWrite)int m_targetID = 0;//唯一标识//通用//面积//蓝图可访问通信UPROPERTY(VisibleAnywhere,BlueprintReadWrite)int m_Area = 100;//通用//面积//蓝图可访问通信UPROPERTY(VisibleAnywhere, BlueprintReadWrite)FString m_Type = "";//居住人口//蓝图可访问通信UPROPERTY(VisibleAnywhere,BlueprintReadWrite)int m_ResidentPopulation = 100;//价值//蓝图可访问通信UPROPERTY(VisibleAnywhere, BlueprintReadWrite)int m_Price = 100;//位置//蓝图可访问通信UPROPERTY(VisibleAnywhere,BlueprintReadWrite)FVector m_Position = FVector::Zero();//方位//蓝图可访问通信UPROPERTY(VisibleAnywhere, BlueprintReadWrite)FRotator m_Rotation = FRotator::ZeroRotator;// 大小//蓝图可访问通信UPROPERTY(VisibleAnywhere, BlueprintReadWrite)FVector m_Scale = FVector(1, 1, 1);
};

实现如下,一开始把文件共有路径设置,Constitution读取自己对于类型的JSON文件,并进行解析。解析JSON后,获取配置文件,存到ConfigData结构体里。LoadStaticMesh,加载模型。就是从ConfigData里,读取数据动态加载,并拼接位置信息,组装成一个完整房屋。

#include "BuildBase.h"
#include "Components/ChildActorComponent.h"
#include "JsonReadHelper.h"// Sets default values
ABuildBase::ABuildBase()
{// Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.PrimaryActorTick.bCanEverTick = true;SetRootComponent(CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")));
}// Called when the game starts or when spawned
void ABuildBase::BeginPlay()
{Super::BeginPlay();m_ResourcesPath = TEXT("/Script/Engine.StaticMesh'/Game/Models/");
}UStaticMesh* ABuildBase::GetStaticMeshAssets(const FString MeshName)
{///Script/Engine.StaticMesh'/Game/Models/Pipe1/Big.Big'FString tmpPath = m_ResourcesPath + m_BuildName + TEXT("/") + MeshName + TEXT(".") + MeshName + TEXT("'");return LoadObject<UStaticMesh>(nullptr, *tmpPath);
}int ABuildBase::GetID()
{return m_targetID;
}// Called every frame
void ABuildBase::Tick(float DeltaTime)
{Super::Tick(DeltaTime);}void ABuildBase::Constitution(const FString& strFile)
{if (!FPlatformFileManager::Get().GetPlatformFile().FileExists(*strFile)){UE_LOG(LogTemp, Error, TEXT("%s not exist"), *strFile);return;}//获取配置数据m_BuildName = UJsonReadHelper::JsonReader_Line(strFile, TEXT("Name"));ConfigData.StaticMeshInfo = UJsonReadHelper::JsonReader_StaticMesh(strFile);//造房子了LoadStaticMesh();
}void ABuildBase::LoadStaticMesh()
{if (ConfigData.StaticMeshInfo.Num() <= 0)	return;//加载各个组件。。MeshName绝不能重名,否则NewObject和TMap结构都会出问题for (auto& it : ConfigData.StaticMeshInfo){//UClass* baseClass = FindObject<UClass>(ANY_PACKAGE, TEXT("StaticMeshComponent"));UStaticMeshComponent* tmpComp = NewObject<UStaticMeshComponent>(Cast<UObject>(this), *it.NodeID);if (m_childComponentMap.Contains(it.ParentName)){UStaticMeshComponent* tmpParent = Cast<UStaticMeshComponent>(m_childComponentMap[it.ParentName]);if (tmpComp->AttachToComponent(tmpParent, FAttachmentTransformRules::KeepRelativeTransform)){UE_LOG(LogTemp, Warning, TEXT("AttachToComponent"));}}elsetmpComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);tmpComp->RegisterComponent();AddInstanceComponent(tmpComp);tmpComp->SetRelativeLocation(it.MeshPos);tmpComp->SetRelativeRotation(it.MeshRot);UStaticMesh* tmpMesh = nullptr;tmpMesh = GetStaticMeshAssets(it.MeshName);tmpComp->SetStaticMesh(tmpMesh);//FVector tmpscale = tmpComp->GetRelativeScale3D();tmpComp->SetRelativeScale3D(it.MeshScale/*FVector(tmpscale.X * (it.ScaleX), tmpscale.Y * (it.ScaleY), tmpscale.Z * (it.ScaleZ))*/);m_childComponentMap.Add(it.NodeID, tmpComp);m_childComponentTypeMap.Add(it.NodeID, EComType::MESH_TYPE);}}

2.其中里面的Constitution函数,是读取JSON配置文件,利用JSONReaderHelp类。来解析,里面JSONObject,和Reader,FJsonSerializer都是UE自带的常用的反序列化解析的指针和函数。将JOSN文件里,StaticMesh域下的 各个属性,解析出来存到结构体数组ConfigData.StaticMeshInfo里。

TArray<FStaticMeshInfo> UJsonReadHelper::JsonReader_StaticMesh(const FString& Path)
{TArray<FStaticMeshInfo> tmpArray;TSharedRef< TJsonReader<> > Reader = TJsonReaderFactory<>::Create(GetFileContentString(Path));TSharedPtr<FJsonObject> Root;if (FJsonSerializer::Deserialize(Reader, Root)){if (Root->HasField(TEXT("StaticMesh"))){const TArray<TSharedPtr<FJsonValue>> Arr = Root->GetArrayField(TEXT("StaticMesh"));for (const auto JsonPtr : Arr) {auto Obj = JsonPtr->AsObject();FStaticMeshInfo tmpStruct;if (Obj->HasField(TEXT("NodeID")))tmpStruct.NodeID = Obj->GetStringField(TEXT("NodeID"));if (Obj->HasField(TEXT("MeshName")))tmpStruct.MeshName = Obj->GetStringField(TEXT("MeshName"));if (Obj->HasField(TEXT("MeshType")))tmpStruct.MeshType = Obj->GetStringField(TEXT("MeshType"));if (Obj->HasField(TEXT("ParentName")))tmpStruct.ParentName = Obj->GetStringField(TEXT("ParentName"));auto smPos = Obj->GetArrayField(TEXT("Position"));tmpStruct.MeshPos.X = smPos[0]->AsNumber();tmpStruct.MeshPos.Y = smPos[1]->AsNumber();tmpStruct.MeshPos.Z = smPos[2]->AsNumber();auto smAtitude = Obj->GetArrayField(TEXT("Rotation"));tmpStruct.MeshRot.Pitch = smAtitude[0]->AsNumber();tmpStruct.MeshRot.Yaw = smAtitude[1]->AsNumber();tmpStruct.MeshRot.Roll = smAtitude[2]->AsNumber();if (Obj->HasField(TEXT("Scale"))){auto scaleVal = Obj->GetArrayField(TEXT("Scale"));tmpStruct.MeshScale.X = scaleVal[0]->AsNumber();tmpStruct.MeshScale.Y = scaleVal[1]->AsNumber();tmpStruct.MeshScale.Z = scaleVal[2]->AsNumber();}tmpArray.Add(tmpStruct);}}}return tmpArray;
}

再从数组里,动态加载网格体模型。相当于加载上面三个模型,拼接他们的相对位置,大小,名字组件,到一个房屋类上。为什么暴露出来再JSON文件里,因为打包后的模型资源你是改不了的。但你可以通过改这个配置文件,对房屋里的组成部分进行微调。大小,位置,旋转。

void ABuildBase::LoadStaticMesh()
{if (ConfigData.StaticMeshInfo.Num() <= 0)	return;//加载各个组件。。MeshName绝不能重名,否则NewObject和TMap结构都会出问题for (auto& it : ConfigData.StaticMeshInfo){//UClass* baseClass = FindObject<UClass>(ANY_PACKAGE, TEXT("StaticMeshComponent"));UStaticMeshComponent* tmpComp = NewObject<UStaticMeshComponent>(Cast<UObject>(this), *it.NodeID);if (m_childComponentMap.Contains(it.ParentName)){UStaticMeshComponent* tmpParent = Cast<UStaticMeshComponent>(m_childComponentMap[it.ParentName]);if (tmpComp->AttachToComponent(tmpParent, FAttachmentTransformRules::KeepRelativeTransform)){UE_LOG(LogTemp, Warning, TEXT("AttachToComponent"));}}elsetmpComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);tmpComp->RegisterComponent();AddInstanceComponent(tmpComp);tmpComp->SetRelativeLocation(it.MeshPos);tmpComp->SetRelativeRotation(it.MeshRot);UStaticMesh* tmpMesh = nullptr;tmpMesh = GetStaticMeshAssets(it.MeshName);tmpComp->SetStaticMesh(tmpMesh);//FVector tmpscale = tmpComp->GetRelativeScale3D();tmpComp->SetRelativeScale3D(it.MeshScale/*FVector(tmpscale.X * (it.ScaleX), tmpscale.Y * (it.ScaleY), tmpscale.Z * (it.ScaleZ))*/);m_childComponentMap.Add(it.NodeID, tmpComp);m_childComponentTypeMap.Add(it.NodeID, EComType::MESH_TYPE);}}

3.房屋管理者,使用UE的UGameInstanceSubsystem的单例方式。这样自己管理生命周期。

这里就有增删查,和管理的容器   TMap<int, ABuildBase*>    m_TargetMap。本质管理者是对容器里的指针指向的在场景里的对象增删查。改留在UI里写。

UCLASS()
class LZJCORE_API UBuildManagerInstance : public UGameInstanceSubsystem
{GENERATED_BODY()
public://最早的初始化virtual void Initialize(FSubsystemCollectionBase& Collection);//初始化加载 表里的建筑类void InitialBuilds(TMap<int, FBuildTableStruct> tmp);void setWorld(UWorld* world);void ClearCtrlTarget();public:UFUNCTION()void	AddTarget(int targetID,ABuildBase* target);UFUNCTION()void	RemoveTarget(ABuildBase* target);void	RemoveTarget(int targetID);UFUNCTION()void	RemoveAllTarget();UFUNCTION()bool  IsHasTarget(int targetID);ABuildBase* GetTargetByID(int targetID);UPROPERTY(EditAnywhere)TMap<int, ABuildBase*>	m_TargetMap;UPROPERTY(EditAnywhere)ULZJUserWidget* LZJUserWidget;
protected:UPROPERTY()ABuildBase* m_CtrlTarget;UPROPERTY()UWorld* m_World;
};

实现如下,正常的TMap操作,只是要判断空指针。这里Initialize初始化,将后续管理的UI界面初始化在主页界面上。

void UBuildManagerInstance::Initialize(FSubsystemCollectionBase& Collection)
{Super::Initialize(Collection);//!可能加载不了if (UClass* MyWidgetClass = LoadClass<ULZJUserWidget>(NULL, TEXT("/Script/UMGEditor.WidgetBlueprint'/LZJCore/BP_LZJBuildManager.BP_LZJBuildManager_C'"))){if (APlayerController* PC = GetWorld()->GetFirstPlayerController()){LZJUserWidget = CreateWidget<ULZJUserWidget>(PC, MyWidgetClass);if (LZJUserWidget){LZJUserWidget->AddToViewport();}}}
}void UBuildManagerInstance::InitialBuilds(TMap<int, FBuildTableStruct> tmp)
{//for (auto It = tmp.CreateIterator(); It; ++It) {//	int Key = It.Key();   // 获取ID 键//	FBuildTableStruct Value = It.Value(); // 获取值//	UE_LOG(LogTemp, Warning, TEXT("Key: %d"), Key);//}
}void UBuildManagerInstance::setWorld(UWorld* world)
{m_World = world;}void UBuildManagerInstance::ClearCtrlTarget()
{
}void UBuildManagerInstance::AddTarget(int targetID, ABuildBase* target)  //增
{if (target->IsValidLowLevel()){if(!m_TargetMap.Contains(target->m_targetID)){m_TargetMap.Add(targetID, target);}}
}void UBuildManagerInstance::RemoveTarget(ABuildBase* target)  //删
{m_TargetMap.Remove(target->GetID());if (target != nullptr){target->Destroy();//m_World->DestroyActor(target);}
}void UBuildManagerInstance::RemoveTarget(int targetID) //删
{if (m_TargetMap.Contains(targetID)){ABuildBase* ab = *m_TargetMap.Find(targetID);if (ab != nullptr && ab->IsValidLowLevel() && !ab->IsPendingKill()){ab->Destroy();}m_TargetMap.Remove(targetID);}
}void UBuildManagerInstance::RemoveAllTarget()  //全删
{//m_CtrlTarget = nullptr;for (auto& it : m_TargetMap){ABuildBase* ab = it.Value;if (ab != nullptr){ab->Destroy();}}m_TargetMap.Reset();
}bool UBuildManagerInstance::IsHasTarget(int targetID)
{if (m_TargetMap.Contains(targetID)){return true;}return false;
}ABuildBase* UBuildManagerInstance::GetTargetByID(int targetID) //查 ,改
{if (m_TargetMap.Contains(targetID)){return	*m_TargetMap.Find(targetID);}return nullptr;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/79624.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/79624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

4.1【LLaMA-Factory 实战】医疗领域大模型:从数据到部署的全流程实践

【LLaMA-Factory实战】医疗领域大模型&#xff1a;从数据到部署的全流程实践 一、引言 在医疗AI领域&#xff0c;构建专业的疾病诊断助手需要解决数据稀缺、知识专业性强、安全合规等多重挑战。本文基于LLaMA-Factory框架&#xff0c;详细介绍如何从0到1打造一个垂直领域的医…

解决LangChain4j报错HTTP/1.1 header parser received no bytes

问题描述 当使用langchain4j-open-ai调用自己部署的大模型服务时报错&#xff1a; public static void main(String[] args) {OpenAiChatModel model OpenAiChatModel.builder().apiKey("none").modelName("qwen2.5-instruct").baseUrl("http://19…

阿里云codeup以及本地gitclone+http

cmd命令行乱码问题、解决 chcp 65001 git代码提交 git add . git commit -m init git push origin master

2025.05.07-淘天算法岗-第二题

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 02. 完美拼图挑战 问题描述 A先生是一位拼图爱好者,他有两种形状的拼图块: a a a

Spring Boot中Redis序列化配置详解

精心整理了最新的面试资料和简历模板&#xff0c;有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在使用Spring Boot集成Redis时&#xff0c;序列化方式的选择直接影响数据存储的效率和系统兼容性。默认的JDK序列化存在可读性差、存储空间大等问题&am…

紫禁城多语言海外投资理财返利源码带前端uniapp纯工程文件

测试环境&#xff1a;Linux系统CentOS7.6、宝塔、PHP7.2、MySQL5.6&#xff0c;根目录public&#xff0c;伪静态thinkphp&#xff0c;开启ssl证书 语言&#xff1a;中文简体、英文、越南语、马来语、日语、巴西语、印尼语、泰语 前端是uniapp的源码&#xff0c;我已经把nmp给你…

搭建大数据学习的平台

一、基础环境准备 1. 硬件配置 物理机&#xff1a;建议 16GB 内存以上&#xff0c;500GB 硬盘&#xff0c;多核 CPU虚拟机&#xff1a;至少 3 台&#xff08;1 主 2 从&#xff09;&#xff0c;每台 4GB 内存&#xff0c;50GB 硬盘 2. 操作系统 Ubuntu 20.04 LTS 或 CentOS…

Linux 软硬连接详解

目录 一、软链接&#xff08;Symbolic Link&#xff09; ‌定义与特性 ‌实现方法‌使用 ln -s 命令&#xff1a; 二、硬链接&#xff08;Hard Link&#xff09; 1、是什么 2、工作机制 3、实现方式 一、软链接&#xff08;Symbolic Link&#xff09; ‌定义与特性 定义…

每日c/c++题 备战蓝桥杯(洛谷P1115 最大子段和)

洛谷P1115 最大子段和 题解 题目描述 最大子段和是一道经典的动态规划问题。题目要求&#xff1a;给定一个包含n个整数的序列&#xff0c;找出其中和最大的连续子序列&#xff0c;并输出该最大和。若所有数均为负数&#xff0c;则取最大的那个数。 输入格式&#xff1a; 第…

前端取经路——框架修行:React与Vue的双修之路

大家好,我是老十三,一名前端开发工程师。在前端的江湖中,React与Vue如同两大武林门派,各有千秋。今天,我将带你进入这两大框架的奥秘世界,共同探索组件生命周期、状态管理、性能优化等核心难题的解决之道。无论你是哪派弟子,掌握双修之术,才能在前端之路上游刃有余。准…

PyTorch API 1 - 概述、数学运算、nn、实用工具、函数、张量

文章目录 torch张量创建操作索引、切片、连接与变异操作 加速器生成器随机采样原地随机采样准随机采样 序列化并行计算局部禁用梯度计算数学运算常量逐点运算归约操作比较运算频谱操作其他操作BLAS 和 LAPACK 运算遍历操作遍历操作遍历操作遍历操作遍历操作遍历操作遍历操作遍历…

java命令行打包class为jar并运行

1.创建无包名类: 2.添加依赖jackson 3.引用依赖包 4.命令编译class文件 生成命令: javac -d out -classpath lib/jackson-core-2.13.3.jar:lib/jackson-annotations-2.13.3.jar:lib/jackson-databind-2.13.3.jar src/UdpServer.java 编译生成class文件如下 <

ABC 转 STL 全攻略:格式解析、方法实操与问题解决

在 3D 建模与设计领域&#xff0c;不同格式文件间的转换是一项基础且重要的操作。ABC&#xff08;Alembic&#xff09;和 STL&#xff08;Standard Triangle Language&#xff09;是其中常见的两种格式。ABC 格式因其高效存储和传输 3D 数据的特性&#xff0c;常被用于影视特效…

编写一个处理txt的loader插件,适用于wbepack

处理txt的webpack的loader插件 编写一个处理txt的loader插件&#xff0c;适用于wbepack 编写一个处理txt的loader插件&#xff0c;适用于wbepack 实现一个处理txt的插件&#xff0c;给文本每行前后添加**** module.exports function txtLoader(content) {// 确保 Loader 是异…

DeepSeek的100个应用场景

在春节前夕&#xff0c;浙江杭州的AI企业DeepSeek推出了其开源模型DeepSeek-R1&#xff0c;以仅相当于Open AI最新模型1/30的训练成本&#xff0c;在数学、编程等关键领域展现出媲美GPT-o1的出色性能。发布仅数日&#xff0c;DeepSeek-R1便迅速攀升至中美两国苹果应用商店免费榜…

ev_loop_fork函数

libev监视器介绍&#xff1a;libev监视器用法-CSDN博客 libev loop对象介绍&#xff1a;loop对象-CSDN博客 libev ev_loop_fork函数介绍:ev_loop_fork函数-CSDN博客 libev API吐血整理&#xff1a;https://download.csdn.net/download/qq_39466755/90794251?spm1001.2014.3…

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】金融风控分析案例-10.1 风险数据清洗与特征工程

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL金融风控分析案例&#xff1a;风险数据清洗与特征工程实战一、案例背景&#xff1a;金融风控数据处理需求二、风险数据清洗实战&#xff08;一&#xff09;缺失值…

OpenCV 的 CUDA 模块中用于将一个多通道 GpuMat 图像拆分成多个单通道图像的函数split()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::cuda::split 是 OpenCV CUDA 模块中的一个函数&#xff0c;用于将一个多通道的 GpuMat 图像拆分成多个单通道的 GpuMat 图像。这个函数是 CP…

【WebRTC-13】是在哪,什么时候,创建编解码器?

Android-RTC系列软重启&#xff0c;改变以往细读源代码的方式 改为 带上实际问题分析代码。增加实用性&#xff0c;方便形成肌肉记忆。同时不分种类、不分难易程度&#xff0c;在线征集问题切入点。 问题&#xff1a;编解码器的关键实体类是什么&#xff1f;在哪里&什么时候…

c语言第一个小游戏:贪吃蛇小游戏03

我们为贪吃蛇的节点设置为一个结构体&#xff0c;构成贪吃蛇的身子的话我们使用链表&#xff0c;链表的每一个节点是一个结构体 显示贪吃蛇身子的一个节点 我们这边node就表示一个蛇的身体 就是一小节 输出结果如下 显示贪吃蛇完整身子 效果如下 代码实现 这个hasSnakeNode(…