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

发布于:2025-05-09 ⋅ 阅读:(8) ⋅ 点赞:(0)

一.框架思路

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 properties
	ABuildBase();

protected:
	// Called when the game starts or when spawned
	virtual 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 frame
	virtual 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"));
			}
		}
		else
			tmpComp->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"));
			}
		}
		else
			tmpComp->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;
}


网站公告

今日签到

点亮在社区的每一天
去签到