AI 实现初版随机地图
This commit is contained in:
		
							
								
								
									
										288
									
								
								Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										288
									
								
								Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,288 @@ | ||||
| #include "Level/PaperTerrainMapActor.h" | ||||
| #include "Level/TerrainGenerator.h" | ||||
| #include "Level/TerrainGeneratorBlueprintLibrary.h" | ||||
| #include "Paper2D/Classes/PaperTileMapComponent.h" | ||||
| #include "Paper2D/Classes/PaperTileSet.h" | ||||
| #include "Paper2D/Classes/PaperTileMap.h" | ||||
| #include "Paper2D/Classes/PaperTileLayer.h" | ||||
| #include "Engine/Engine.h" | ||||
| #include "UObject/ConstructorHelpers.h" | ||||
|  | ||||
| APaperTerrainMapActor::APaperTerrainMapActor() | ||||
| { | ||||
| 	PrimaryActorTick.bCanEverTick = false; | ||||
|  | ||||
| 	// 创建根组?? | ||||
| 	RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent")); | ||||
|  | ||||
| 	// 创建TileMap组件 | ||||
| 	TileMapComponent = CreateDefaultSubobject<UPaperTileMapComponent>(TEXT("TileMapComponent")); | ||||
| 	TileMapComponent->SetupAttachment(RootComponent); | ||||
|  | ||||
| 	// 创建地形生成?? | ||||
| 	TerrainGenerator = CreateDefaultSubobject<UTerrainGenerator>(TEXT("TerrainGenerator")); | ||||
|  | ||||
| 	// 默认参数设置 | ||||
| 	bAutoGenerateOnConstruction = true; | ||||
| 	bAutoGenerateOnBeginPlay = false; | ||||
| 	DefaultTileIndex = 0; | ||||
|  | ||||
| 	// 设置默认地形映射 | ||||
| 	SetupDefaultTerrainConfig(); | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::BeginPlay() | ||||
| { | ||||
| 	Super::BeginPlay(); | ||||
|  | ||||
| 	if (bAutoGenerateOnBeginPlay) | ||||
| 	{ | ||||
| 		GenerateTerrainMap(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::OnConstruction(const FTransform& Transform) | ||||
| { | ||||
| 	Super::OnConstruction(Transform); | ||||
|  | ||||
| 	// 初始化TileMap | ||||
| 	InitializeTileMap(); | ||||
|  | ||||
| 	if (bAutoGenerateOnConstruction) | ||||
| 	{ | ||||
| 		GenerateTerrainMap(); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::GenerateTerrainMap() | ||||
| { | ||||
| 	if (!TerrainGenerator) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Error, TEXT("TerrainGenerator is null!")); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// 生成地形数据 | ||||
| 	GeneratedTerrainData = TerrainGenerator->GenerateMap(MapWidth, MapHeight); | ||||
|  | ||||
| 	// 应用到TileMap | ||||
| 	ApplyTerrainToTileMap(GeneratedTerrainData); | ||||
|  | ||||
| 	UE_LOG(LogTemp, Log, TEXT("Terrain map generated successfully! Size: %dx%d"), MapWidth, MapHeight); | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::ClearMap() | ||||
| { | ||||
| 	if (!TileMapComponent || !TileMapComponent->TileMap) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// 清除所有Tile - 通过重新创建空的TileMap来实?? | ||||
| 	if (TileMapComponent->TileMap) | ||||
| 	{ | ||||
| 		UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(this); | ||||
| 		NewTileMap->MapWidth = MapWidth; | ||||
| 		NewTileMap->MapHeight = MapHeight; | ||||
| 		NewTileMap->TileWidth = TileSize; | ||||
| 		NewTileMap->TileHeight = TileSize; | ||||
| 		 | ||||
| 		// 添加一个空图层 | ||||
| 		UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(NewTileMap); | ||||
| 		NewLayer->LayerName = FText::FromString("TerrainLayer"); | ||||
| 		NewTileMap->TileLayers.Add(NewLayer); | ||||
| 		 | ||||
| 		TileMapComponent->SetTileMap(NewTileMap); | ||||
| 	} | ||||
|  | ||||
| 	GeneratedTerrainData.Empty(); | ||||
| 	UE_LOG(LogTemp, Log, TEXT("Map cleared!")); | ||||
| } | ||||
|  | ||||
| UTerrainGenerator* APaperTerrainMapActor::GetTerrainGenerator() const | ||||
| { | ||||
| 	return TerrainGenerator; | ||||
| } | ||||
|  | ||||
| TArray<FGameplayTag> APaperTerrainMapActor::GetGeneratedTerrainData() const | ||||
| { | ||||
| 	return GeneratedTerrainData; | ||||
| } | ||||
|  | ||||
| #if WITH_EDITOR | ||||
| void APaperTerrainMapActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) | ||||
| { | ||||
| 	Super::PostEditChangeProperty(PropertyChangedEvent); | ||||
|  | ||||
| 	FName PropertyName = (PropertyChangedEvent.Property != nullptr)  | ||||
| 		? PropertyChangedEvent.Property->GetFName()  | ||||
| 		: NAME_None; | ||||
|  | ||||
| 	// 当地图尺寸或Tile大小改变时,重新初始化TileMap | ||||
| 	if (PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapWidth) || | ||||
| 		PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, MapHeight) || | ||||
| 		PropertyName == GET_MEMBER_NAME_CHECKED(APaperTerrainMapActor, TileSize)) | ||||
| 	{ | ||||
| 		InitializeTileMap(); | ||||
| 		 | ||||
| 		// 如果已经有生成的数据,重新应?? | ||||
| 		if (GeneratedTerrainData.Num() > 0) | ||||
| 		{ | ||||
| 			ApplyTerrainToTileMap(GeneratedTerrainData); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| #endif | ||||
|  | ||||
| void APaperTerrainMapActor::InitializeTileMap() | ||||
| { | ||||
| 	if (!TileMapComponent) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// 创建或获取TileMap资产 | ||||
| 	UPaperTileMap* TileMap = TileMapComponent->TileMap; | ||||
| 	if (!TileMap) | ||||
| 	{ | ||||
| 		TileMap = NewObject<UPaperTileMap>(this); | ||||
| 		TileMapComponent->SetTileMap(TileMap); | ||||
| 	} | ||||
|  | ||||
| 	// 设置TileMap参数 | ||||
| 	TileMap->MapWidth = MapWidth; | ||||
| 	TileMap->MapHeight = MapHeight; | ||||
| 	TileMap->TileWidth = TileSize; | ||||
| 	TileMap->TileHeight = TileSize; | ||||
|  | ||||
| 	// 确保有足够的图层 | ||||
| 	if (TileMap->TileLayers.Num() == 0) | ||||
| 	{ | ||||
| 		UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(TileMap); | ||||
| 		NewLayer->LayerName = FText::FromString("TerrainLayer"); | ||||
| 		TileMap->TileLayers.Add(NewLayer); | ||||
| 	} | ||||
|  | ||||
| 	// 设置组件大小 | ||||
| 	FVector NewScale(TileSize * MapWidth / 100.0f, 1.0f, TileSize * MapHeight / 100.0f); | ||||
| 	TileMapComponent->SetRelativeScale3D(NewScale); | ||||
|  | ||||
| 	UE_LOG(LogTemp, Log, TEXT("TileMap initialized: %dx%d, TileSize: %d"), MapWidth, MapHeight, TileSize); | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData) | ||||
| { | ||||
| 	if (!TileMapComponent || !TileMapComponent->TileMap || TerrainData.Num() != MapWidth * MapHeight) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Error, TEXT("Cannot apply terrain data to TileMap!")); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if (TileMapComponent->TileMap->TileLayers.Num() == 0) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Error, TEXT("No tile layers in TileMap!")); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// 应用地形数据到TileMap - 创建新的TileMap并设置所有单元格 | ||||
| 	UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(this); | ||||
| 	NewTileMap->MapWidth = MapWidth; | ||||
| 	NewTileMap->MapHeight = MapHeight; | ||||
| 	NewTileMap->TileWidth = TileSize; | ||||
| 	NewTileMap->TileHeight = TileSize; | ||||
|  | ||||
| 	// 创建地形图层 | ||||
| 	UPaperTileLayer* TerrainLayer = NewObject<UPaperTileLayer>(NewTileMap); | ||||
| 	TerrainLayer->LayerName = FText::FromString("TerrainLayer"); | ||||
| 	NewTileMap->TileLayers.Add(TerrainLayer); | ||||
|  | ||||
| 	// 设置图层单元??- 使用SetCell方法 | ||||
| 	// 首先调整图层尺寸 | ||||
| 	TerrainLayer->ResizeMap(MapWidth, MapHeight); | ||||
| 	 | ||||
| 	for (int32 Y = 0; Y < MapHeight; Y++) | ||||
| 	{ | ||||
| 		for (int32 X = 0; X < MapWidth; X++) | ||||
| 		{ | ||||
| 			int32 Index = Y * MapWidth + X; | ||||
| 			if (TerrainData.IsValidIndex(Index)) | ||||
| 			{ | ||||
| 				FGameplayTag TerrainTag = TerrainData[Index]; | ||||
| 				int32 TileIndex = GetTileIndexForTerrain(TerrainTag); | ||||
| 				 | ||||
| 				// 创建Tile信息并使用SetCell方法 | ||||
| 				FPaperTileInfo TileInfo; | ||||
| 				TileInfo.TileSet = TileSet; | ||||
| 				TileInfo.PackedTileIndex = TileIndex; | ||||
| 				TerrainLayer->SetCell(X, Y, TileInfo); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 设置新的TileMap | ||||
| 	TileMapComponent->SetTileMap(NewTileMap); | ||||
|  | ||||
| 	UE_LOG(LogTemp, Log, TEXT("Terrain data applied to TileMap!")); | ||||
| } | ||||
|  | ||||
| int32 APaperTerrainMapActor::GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const | ||||
| { | ||||
| 	if (!TerrainTag.IsValid()) | ||||
| 	{ | ||||
| 		return DefaultTileIndex; | ||||
| 	} | ||||
|  | ||||
| 	// 查找地形映射 | ||||
| 	for (const FTerrainTileMapping& Mapping : TerrainTileMappings) | ||||
| 	{ | ||||
| 		if (Mapping.TerrainTag == TerrainTag) | ||||
| 		{ | ||||
| 			return Mapping.TileIndex; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// 如果没有找到映射,返回默认Tile | ||||
| 	return DefaultTileIndex; | ||||
| } | ||||
|  | ||||
| void APaperTerrainMapActor::SetupDefaultTerrainConfig() | ||||
| { | ||||
| 	// 设置默认地形映射 | ||||
| 	TerrainTileMappings.Empty(); | ||||
|  | ||||
| 	// 森林 -> Tile 1 | ||||
| 	FTerrainTileMapping ForestMapping; | ||||
| 	ForestMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest")); | ||||
| 	ForestMapping.TileIndex = 1; | ||||
| 	TerrainTileMappings.Add(ForestMapping); | ||||
|  | ||||
| 	// 草地 -> Tile 2 | ||||
| 	FTerrainTileMapping GrasslandMapping; | ||||
| 	GrasslandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland")); | ||||
| 	GrasslandMapping.TileIndex = 2; | ||||
| 	TerrainTileMappings.Add(GrasslandMapping); | ||||
|  | ||||
| 	// 水体 -> Tile 3 | ||||
| 	FTerrainTileMapping WaterMapping; | ||||
| 	WaterMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water")); | ||||
| 	WaterMapping.TileIndex = 3; | ||||
| 	TerrainTileMappings.Add(WaterMapping); | ||||
|  | ||||
| 	// 沼泽 -> Tile 4 | ||||
| 	FTerrainTileMapping SwampMapping; | ||||
| 	SwampMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp")); | ||||
| 	SwampMapping.TileIndex = 4; | ||||
| 	TerrainTileMappings.Add(SwampMapping); | ||||
|  | ||||
| 	// 土地 -> Tile 5 | ||||
| 	FTerrainTileMapping LandMapping; | ||||
| 	LandMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Land")); | ||||
| 	LandMapping.TileIndex = 5; | ||||
| 	TerrainTileMappings.Add(LandMapping); | ||||
|  | ||||
| 	// 设置地形生成器的默认配置 | ||||
| 	if (TerrainGenerator) | ||||
| 	{ | ||||
| 		UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(TerrainGenerator); | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										402
									
								
								Source/BusyRabbit/Private/Level/TerrainGenerator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										402
									
								
								Source/BusyRabbit/Private/Level/TerrainGenerator.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,402 @@ | ||||
| #include "Level/TerrainGenerator.h" | ||||
| #include "Math/UnrealMathUtility.h" | ||||
| #include "Math/RandomStream.h" | ||||
|  | ||||
| UTerrainGenerator::UTerrainGenerator() | ||||
| { | ||||
| 	NextHandle = 0; | ||||
| } | ||||
|  | ||||
| int32 UTerrainGenerator::AddTerrain(const FGameplayTag& TerrainTag) | ||||
| { | ||||
| 	FTerrainNodeInfo NewNode; | ||||
| 	NewNode.TerrainTag = TerrainTag; | ||||
| 	NewNode.Probability = 1.0f; | ||||
| 	NewNode.Weight = 1.0f; | ||||
| 	NewNode.ParentHandle = -1; | ||||
| 	NewNode.bIsLeafNode = true; | ||||
|  | ||||
| 	int32 Handle = NextHandle++; | ||||
| 	TerrainNodes.Add(Handle, NewNode); | ||||
| 	 | ||||
| 	return Handle; | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::SetWeight(int32 Handle, float Weight) | ||||
| { | ||||
| 	if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) | ||||
| 	{ | ||||
| 		Node->Weight = FMath::Max(0.0f, Weight); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::SetProbability(int32 Handle, float Probability) | ||||
| { | ||||
| 	if (FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) | ||||
| 	{ | ||||
| 		Node->Probability = FMath::Clamp(Probability, 0.0f, 1.0f); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::SetExclusive(const TArray<int32>& Handles){ | ||||
| 	if (Handles.Num() <= 1) return; | ||||
| 	ExclusiveGroups.Add(TSet<int32>(Handles)); | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles) | ||||
| { | ||||
| 	if (FTerrainNodeInfo* ParentNode = TerrainNodes.Find(ParentHandle)) | ||||
| 	{ | ||||
| 		// 设置父节点为非叶子节点 | ||||
| 		ParentNode->bIsLeafNode = false; | ||||
| 		 | ||||
| 		// 添加子节点 | ||||
| 		ParentNode->ChildHandles = ChildHandles; | ||||
| 		 | ||||
| 		// 设置子节点的父节点 | ||||
| 		for (int32 ChildHandle : ChildHandles) | ||||
| 		{ | ||||
| 			if (FTerrainNodeInfo* ChildNode = TerrainNodes.Find(ChildHandle)) | ||||
| 			{ | ||||
| 				ChildNode->ParentHandle = ParentHandle; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| TArray<FGameplayTag> UTerrainGenerator::GenerateMap(int32 Width, int32 Height) | ||||
| { | ||||
| 	TArray<FGameplayTag> Map; | ||||
| 	Map.SetNum(Width * Height); | ||||
| 	// 获取有效的叶子节点 | ||||
| 	TArray<int32> ValidLeafNodes = CalcValidLeafNodes(); | ||||
| 	 | ||||
| 	if (ValidLeafNodes.Num() == 0) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Error, TEXT("No valid terrain nodes to generate map!")); | ||||
| 		return Map; | ||||
| 	} | ||||
| 	 | ||||
| 	// 使用区域生长算法生成基础地图 | ||||
| 	RegionGrowing(Map, Width, Height, ValidLeafNodes); | ||||
| 	 | ||||
| 	// 应用柏林噪声进行平滑处理 | ||||
| 	ApplyPerlinNoiseSmoothing(Map, Width, Height); | ||||
| 	 | ||||
| 	return Map; | ||||
| } | ||||
|  | ||||
| TMap<int32, FTerrainNodeInfo> UTerrainGenerator::GetAllTerrainNodes() const | ||||
| { | ||||
| 	return TerrainNodes; | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const{ | ||||
| 	bool bShouldAppear; | ||||
| 	const FTerrainNodeInfo* Node; | ||||
| 	int32 Handle, TerrainNodeCount; | ||||
| 	TArray<bool> AppearanceCheckResult; | ||||
|  | ||||
| 	TerrainNodeCount = TerrainNodes.Num(); | ||||
| 	AppearanceCheckResult.Init(true, TerrainNodeCount); | ||||
|  | ||||
| 	for (const auto& Pair : TerrainNodes) { | ||||
| 		Handle = Pair.Key; | ||||
| 		Node = &Pair.Value; | ||||
|  | ||||
| 		if (AppearanceCheckResult[Handle]) { | ||||
| 			bShouldAppear = FMath::FRand() < Node->Probability; | ||||
| 			AppearanceCheckResult[Handle] = bShouldAppear; | ||||
| 		} | ||||
| 		else { | ||||
| 			bShouldAppear = false; | ||||
| 		} | ||||
|  | ||||
| 		if (!bShouldAppear) { | ||||
| 			for (auto ChildHandle : Pair.Value.ChildHandles) { | ||||
| 				AppearanceCheckResult[ChildHandle] = false; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for (int i = 0; i < TerrainNodeCount; ++i) { | ||||
| 		if (AppearanceCheckResult[i]) { | ||||
| 			VisibleHandles.Add(i); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| bool UTerrainGenerator::ShouldNodeAppear(int32 Handle) const | ||||
| { | ||||
| 	const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); | ||||
| 	if (!Node) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// 检查概率 | ||||
| 	if (FMath::FRand() > Node->Probability) | ||||
| 	{ | ||||
| 		return false; | ||||
| 	} | ||||
| 	 | ||||
| 	// 递归检查父节点 | ||||
| 	if (Node->ParentHandle != -1) | ||||
| 	{ | ||||
| 		return ShouldNodeAppear(Node->ParentHandle); | ||||
| 	} | ||||
| 	 | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool UTerrainGenerator::CheckExclusive(int32 Handle, const TSet<int32>& AppearingNodes) const { | ||||
| 	//for (const TArray<int32>& Group : ExclusiveGroups) | ||||
| 	//{ | ||||
| 	//	if (Group.Contains(Handle)) | ||||
| 	//	{ | ||||
| 	//		// 检查互斥组中是否有其他节点已经出现 | ||||
| 	//		for (int32 OtherHandle : Group) | ||||
| 	//		{ | ||||
| 	//			if (OtherHandle != Handle && AppearingNodes.Contains(OtherHandle)) | ||||
| 	//			{ | ||||
| 	//				return false; | ||||
| 	//			} | ||||
| 	//		} | ||||
| 	//	} | ||||
| 	//} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| TArray<int32> UTerrainGenerator::CalcValidLeafNodes()const{ | ||||
| 	TSet<int32> ValidNodes; | ||||
| 	GetPreCheckVisibleHandles(ValidNodes); | ||||
| 	 | ||||
| 	// 检查互斥关系 | ||||
| 	TSet<int32> FinalNodes; | ||||
| 	for (int32 Handle : ValidNodes){ | ||||
| 		if (FinalNodes.Find(Handle)) continue; | ||||
| 		if (CheckExclusive(Handle, ValidNodes)) | ||||
| 		{ | ||||
| 			FinalNodes.Add(Handle); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// 只保留叶子节点 | ||||
| 	TArray<int32> LeafNodes; | ||||
| 	for (int32 Handle : FinalNodes) | ||||
| 	{ | ||||
| 		const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); | ||||
| 		if (Node && Node->bIsLeafNode) | ||||
| 		{ | ||||
| 			LeafNodes.Add(Handle); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	return LeafNodes; | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::RegionGrowing(TArray<FGameplayTag>& Map, int32 Width, int32 Height,  | ||||
| 									 const TArray<int32>& ValidLeafNodes) const | ||||
| { | ||||
| 	if (ValidLeafNodes.Num() == 0) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	FRandomStream RandomStream(FMath::Rand()); | ||||
| 	 | ||||
| 	// 计算总权重 | ||||
| 	float TotalWeight = 0.0f; | ||||
| 	for (int32 Handle : ValidLeafNodes) | ||||
| 	{ | ||||
| 		if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) | ||||
| 		{ | ||||
| 			TotalWeight += Node->Weight; | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	if (TotalWeight <= 0.0f) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	 | ||||
| 	// 创建权重分布 | ||||
| 	TArray<float> WeightDistribution; | ||||
| 	TArray<int32> HandleDistribution; | ||||
| 	float CurrentWeight = 0.0f; | ||||
| 	 | ||||
| 	for (int32 Handle : ValidLeafNodes) | ||||
| 	{ | ||||
| 		if (const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle)) | ||||
| 		{ | ||||
| 			CurrentWeight += Node->Weight / TotalWeight; | ||||
| 			WeightDistribution.Add(CurrentWeight); | ||||
| 			HandleDistribution.Add(Handle); | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// 区域生长算法 | ||||
| 	TArray<int32> RegionSizes; | ||||
| 	RegionSizes.SetNum(ValidLeafNodes.Num()); | ||||
| 	 | ||||
| 	int32 TotalCells = Width * Height; | ||||
| 	for (int32 i = 0; i < ValidLeafNodes.Num(); i++) | ||||
| 	{ | ||||
| 		float Ratio = 0.0f; | ||||
| 		if (const FTerrainNodeInfo* Node = TerrainNodes.Find(ValidLeafNodes[i])) | ||||
| 		{ | ||||
| 			Ratio = Node->Weight / TotalWeight; | ||||
| 		} | ||||
| 		RegionSizes[i] = FMath::CeilToInt(TotalCells * Ratio); | ||||
| 	} | ||||
| 	 | ||||
| 	// 初始化地图为无效标签 | ||||
| 	FGameplayTag InvalidTag; | ||||
| 	for (int32 i = 0; i < Map.Num(); i++) | ||||
| 	{ | ||||
| 		Map[i] = InvalidTag; | ||||
| 	} | ||||
| 	 | ||||
| 	// 从随机点开始生长 | ||||
| 	for (int32 RegionIndex = 0; RegionIndex < ValidLeafNodes.Num(); RegionIndex++) | ||||
| 	{ | ||||
| 		int32 Handle = ValidLeafNodes[RegionIndex]; | ||||
| 		FGameplayTag TerrainTag = GetFinalTerrainTag(Handle); | ||||
| 		int32 TargetSize = RegionSizes[RegionIndex]; | ||||
| 		int32 CurrentSize = 0; | ||||
| 		 | ||||
| 		// 寻找起始点 | ||||
| 		int32 StartX, StartY; | ||||
| 		do | ||||
| 		{ | ||||
| 			StartX = RandomStream.RandRange(0, Width - 1); | ||||
| 			StartY = RandomStream.RandRange(0, Height - 1); | ||||
| 		} while (Map[StartY * Width + StartX].IsValid()); | ||||
| 		 | ||||
| 		// 使用队列进行区域生长 | ||||
| 		TQueue<FIntPoint> Queue; | ||||
| 		Queue.Enqueue(FIntPoint(StartX, StartY)); | ||||
| 		Map[StartY * Width + StartX] = TerrainTag; | ||||
| 		CurrentSize++; | ||||
| 		 | ||||
| 		while (!Queue.IsEmpty() && CurrentSize < TargetSize) | ||||
| 		{ | ||||
| 			FIntPoint CurrentPoint; | ||||
| 			Queue.Dequeue(CurrentPoint); | ||||
| 			 | ||||
| 			// 检查四个方向 | ||||
| 			TArray<FIntPoint> Directions = { | ||||
| 				FIntPoint(1, 0), FIntPoint(-1, 0), | ||||
| 				FIntPoint(0, 1), FIntPoint(0, -1) | ||||
| 			}; | ||||
| 			 | ||||
| 			for (const FIntPoint& Dir : Directions) | ||||
| 			{ | ||||
| 				FIntPoint Neighbor = CurrentPoint + Dir; | ||||
| 				 | ||||
| 				if (Neighbor.X >= 0 && Neighbor.X < Width &&  | ||||
| 					Neighbor.Y >= 0 && Neighbor.Y < Height) | ||||
| 				{ | ||||
| 					int32 Index = Neighbor.Y * Width + Neighbor.X; | ||||
| 					 | ||||
| 					if (!Map[Index].IsValid()) | ||||
| 					{ | ||||
| 						Map[Index] = TerrainTag; | ||||
| 						Queue.Enqueue(Neighbor); | ||||
| 						CurrentSize++; | ||||
| 						 | ||||
| 						if (CurrentSize >= TargetSize) | ||||
| 						{ | ||||
| 							break; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	 | ||||
| 	// 填充剩余的空格 | ||||
| 	for (int32 i = 0; i < Map.Num(); i++) | ||||
| 	{ | ||||
| 		if (!Map[i].IsValid()) | ||||
| 		{ | ||||
| 			float RandValue = RandomStream.FRand(); | ||||
| 			for (int32 j = 0; j < WeightDistribution.Num(); j++) | ||||
| 			{ | ||||
| 				if (RandValue <= WeightDistribution[j]) | ||||
| 				{ | ||||
| 					Map[i] = GetFinalTerrainTag(HandleDistribution[j]); | ||||
| 					break; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void UTerrainGenerator::ApplyPerlinNoiseSmoothing(TArray<FGameplayTag>& Map, int32 Width, int32 Height) const | ||||
| { | ||||
| 	FRandomStream RandomStream(FMath::Rand()); | ||||
| 	float NoiseScale = 0.1f; | ||||
| 	 | ||||
| 	for (int32 Y = 0; Y < Height; Y++) | ||||
| 	{ | ||||
| 		for (int32 X = 0; X < Width; X++) | ||||
| 		{ | ||||
| 			int32 Index = Y * Width + X; | ||||
| 			 | ||||
| 			// 计算柏林噪声值 | ||||
| 			float NoiseValue = FMath::PerlinNoise2D(FVector2D(X * NoiseScale, Y * NoiseScale)); | ||||
| 			NoiseValue = (NoiseValue + 1.0f) / 2.0f; // 映射到 0-1 | ||||
| 			 | ||||
| 			// 50%概率根据噪声调整地形 | ||||
| 			if (RandomStream.FRand() < 0.5f && NoiseValue > 0.7f) | ||||
| 			{ | ||||
| 				// 查找周围最多的地形 | ||||
| 				TMap<FGameplayTag, int32> NeighborCount; | ||||
| 				 | ||||
| 				for (int32 dy = -1; dy <= 1; dy++) | ||||
| 				{ | ||||
| 					for (int32 dx = -1; dx <= 1; dx++) | ||||
| 					{ | ||||
| 						if (dx == 0 && dy == 0) continue; | ||||
| 						 | ||||
| 						int32 NX = X + dx; | ||||
| 						int32 NY = Y + dy; | ||||
| 						 | ||||
| 						if (NX >= 0 && NX < Width && NY >= 0 && NY < Height) | ||||
| 						{ | ||||
| 							FGameplayTag NeighborTag = Map[NY * Width + NX]; | ||||
| 							NeighborCount.FindOrAdd(NeighborTag)++; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 				 | ||||
| 				// 找到最多的邻居地形 | ||||
| 			FGameplayTag MostCommonTag; | ||||
| 				int32 MaxCount = 0; | ||||
| 				 | ||||
| 				for (const auto& Pair : NeighborCount) | ||||
| 				{ | ||||
| 					if (Pair.Value > MaxCount) | ||||
| 					{ | ||||
| 						MaxCount = Pair.Value; | ||||
| 						MostCommonTag = Pair.Key; | ||||
| 					} | ||||
| 				} | ||||
| 				 | ||||
| 				if (MaxCount > 0) | ||||
| 				{ | ||||
| 					Map[Index] = MostCommonTag; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| FGameplayTag UTerrainGenerator::GetFinalTerrainTag(int32 Handle) const{ | ||||
| 	const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); | ||||
| 	if (Node){ | ||||
| 		return Node->TerrainTag; | ||||
| 	} | ||||
| 	return FGameplayTag(); | ||||
| } | ||||
| @ -0,0 +1,131 @@ | ||||
| #include "Level/TerrainGeneratorBlueprintLibrary.h" | ||||
| #include "Level/TerrainGenerator.h" | ||||
| #include "GameplayTagsManager.h" | ||||
| #include "Engine/Engine.h" | ||||
|  | ||||
| UTerrainGenerator* UTerrainGeneratorBlueprintLibrary::CreateTerrainGenerator() | ||||
| { | ||||
| 	return NewObject<UTerrainGenerator>(); | ||||
| } | ||||
|  | ||||
| void UTerrainGeneratorBlueprintLibrary::SetupExampleTerrainConfig(UTerrainGenerator* Generator) | ||||
| { | ||||
| 	if (!Generator) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	// 定义地形标签 | ||||
| 	FGameplayTag ForestTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest")); | ||||
| 	FGameplayTag GrasslandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland")); | ||||
| 	FGameplayTag WaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water")); | ||||
| 	FGameplayTag SwampTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp")); | ||||
| 	FGameplayTag LandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Land")); | ||||
| 	 | ||||
| 	FGameplayTag SwampLandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp.Land")); | ||||
| 	FGameplayTag SwampWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp.Water")); | ||||
| 	FGameplayTag ShallowWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water.Shallow")); | ||||
| 	FGameplayTag DeepWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water.Deep")); | ||||
|  | ||||
| 	// 添加父地形 | ||||
| 	int32 ForestHandle = Generator->AddTerrain(ForestTag); | ||||
| 	int32 GrasslandHandle = Generator->AddTerrain(GrasslandTag); | ||||
| 	int32 WaterHandle = Generator->AddTerrain(WaterTag); | ||||
| 	int32 SwampHandle = Generator->AddTerrain(SwampTag); | ||||
| 	int32 LandHandle = Generator->AddTerrain(LandTag); | ||||
| 	 | ||||
| 	// 添加子地形 | ||||
| 	int32 SwampLandHandle = Generator->AddTerrain(SwampLandTag); | ||||
| 	int32 SwampWaterHandle = Generator->AddTerrain(SwampWaterTag); | ||||
| 	int32 ShallowWaterHandle = Generator->AddTerrain(ShallowWaterTag); | ||||
| 	int32 DeepWaterHandle = Generator->AddTerrain(DeepWaterTag); | ||||
| 	 | ||||
| 	// 绑定父子关系 | ||||
| 	Generator->BindChildTerrain(SwampHandle, {SwampLandHandle, SwampWaterHandle}); | ||||
| 	Generator->BindChildTerrain(WaterHandle, {ShallowWaterHandle, DeepWaterHandle}); | ||||
| 	 | ||||
| 	// 设置概率 | ||||
| 	Generator->SetProbability(ForestHandle, 0.8f);	  // 森林出现概率80% | ||||
| 	Generator->SetProbability(GrasslandHandle, 0.9f);   // 草地出现概率90% | ||||
| 	Generator->SetProbability(WaterHandle, 0.7f);	   // 水体出现概率70% | ||||
| 	Generator->SetProbability(SwampHandle, 0.6f);	   // 沼泽出现概率60% | ||||
| 	Generator->SetProbability(LandHandle, 1.0f);		// 土地总是出现 | ||||
| 	 | ||||
| 	// 设置权重 | ||||
| 	Generator->SetWeight(ForestHandle, 9.0f);		   // 森林权重9 | ||||
| 	Generator->SetWeight(GrasslandHandle, 5.0f);		// 草地权重5 | ||||
| 	Generator->SetWeight(LandHandle, 3.0f);			 // 土地权重3 | ||||
| 	 | ||||
| 	Generator->SetWeight(SwampLandHandle, 1.0f);		// 沼泽陆地权重1 | ||||
| 	Generator->SetWeight(SwampWaterHandle, 2.0f);	   // 沼泽水域权重2 | ||||
| 	 | ||||
| 	Generator->SetWeight(ShallowWaterHandle, 3.0f);	 // 浅水权重3 | ||||
| 	Generator->SetWeight(DeepWaterHandle, 1.0f);		// 深水权重1 | ||||
| 	 | ||||
| 	// 设置互斥关系 | ||||
| 	Generator->SetExclusive({ForestHandle, SwampWaterHandle}); // 森林和沼泽水域互斥 | ||||
| } | ||||
|  | ||||
| TArray<FGameplayTag> UTerrainGeneratorBlueprintLibrary::GenerateMap( | ||||
| 	UTerrainGenerator* Generator,  | ||||
| 	int32 Width,  | ||||
| 	int32 Height) | ||||
| { | ||||
| 	TArray<FGameplayTag> Result; | ||||
| 	 | ||||
| 	if (!Generator) | ||||
| 	{ | ||||
| 		return Result; | ||||
| 	} | ||||
|  | ||||
| 	// 直接返回一维地图数据 | ||||
| 	return Generator->GenerateMap(Width, Height); | ||||
| } | ||||
|  | ||||
| void UTerrainGeneratorBlueprintLibrary::GetMapDimensions( | ||||
| 	UTerrainGenerator* Generator, | ||||
| 	int32& Width, | ||||
| 	int32& Height) | ||||
| { | ||||
| 	Width = 256; | ||||
| 	Height = 256; | ||||
| 	 | ||||
| 	if (Generator) | ||||
| 	{ | ||||
| 		// 这里可以根据需要从生成器获取实际的尺寸信息 | ||||
| 		// 目前返回默认值 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| FString UTerrainGeneratorBlueprintLibrary::GetTerrainDisplayName(const FGameplayTag& TerrainTag) | ||||
| { | ||||
| 	if (!TerrainTag.IsValid()) | ||||
| 	{ | ||||
| 		return TEXT("无效地形"); | ||||
| 	} | ||||
|  | ||||
| 	FString TagName = TerrainTag.ToString(); | ||||
| 	 | ||||
| 	// 从标签中提取显示名称 | ||||
| 	if (TagName.StartsWith(TEXT("Terrain."))) | ||||
| 	{ | ||||
| 		TagName.RemoveFromStart(TEXT("Terrain.")); | ||||
| 	} | ||||
| 	 | ||||
| 	// 替换下划线为空格 | ||||
| 	TagName.ReplaceCharInline(TEXT('.'), TEXT(' ')); | ||||
| 	TagName.ReplaceCharInline(TEXT('_'), TEXT(' ')); | ||||
| 	 | ||||
| 	// 首字母大写 | ||||
| 	if (TagName.Len() > 0) | ||||
| 	{ | ||||
| 		TagName[0] = FChar::ToUpper(TagName[0]); | ||||
| 	} | ||||
| 	 | ||||
| 	return TagName; | ||||
| } | ||||
|  | ||||
| bool UTerrainGeneratorBlueprintLibrary::IsValidTerrainTag(const FGameplayTag& TerrainTag) | ||||
| { | ||||
| 	return TerrainTag.IsValid(); | ||||
| } | ||||
							
								
								
									
										122
									
								
								Source/BusyRabbit/Private/Level/TerrainGeneratorTest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								Source/BusyRabbit/Private/Level/TerrainGeneratorTest.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | ||||
| #include "Level/TerrainGenerator.h" | ||||
| #include "GameplayTagsManager.h" | ||||
| #include "Engine/Engine.h" | ||||
|  | ||||
| // 测试地形生成器功能 | ||||
| void TestTerrainGenerator() | ||||
| { | ||||
| 	// 创建地形生成器实例 | ||||
| 	UTerrainGenerator* Generator = NewObject<UTerrainGenerator>(); | ||||
| 	 | ||||
| 	// 定义地形标签 | ||||
| 	FGameplayTag ForestTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest")); | ||||
| 	FGameplayTag GrasslandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland")); | ||||
| 	FGameplayTag WaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water")); | ||||
| 	FGameplayTag SwampTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp")); | ||||
| 	FGameplayTag LandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Land")); | ||||
| 	 | ||||
| 	FGameplayTag SwampLandTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp.Land")); | ||||
| 	FGameplayTag SwampWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Swamp.Water")); | ||||
| 	FGameplayTag ShallowWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water.Shallow")); | ||||
| 	FGameplayTag DeepWaterTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water.Deep")); | ||||
| 	 | ||||
| 	// 添加父地形 | ||||
| 	int32 ForestHandle = Generator->AddTerrain(ForestTag); | ||||
| 	int32 GrasslandHandle = Generator->AddTerrain(GrasslandTag); | ||||
| 	int32 WaterHandle = Generator->AddTerrain(WaterTag); | ||||
| 	int32 SwampHandle = Generator->AddTerrain(SwampTag); | ||||
| 	int32 LandHandle = Generator->AddTerrain(LandTag); | ||||
| 	 | ||||
| 	// 添加子地形 | ||||
| 	int32 SwampLandHandle = Generator->AddTerrain(SwampLandTag); | ||||
| 	int32 SwampWaterHandle = Generator->AddTerrain(SwampWaterTag); | ||||
| 	int32 ShallowWaterHandle = Generator->AddTerrain(ShallowWaterTag); | ||||
| 	int32 DeepWaterHandle = Generator->AddTerrain(DeepWaterTag); | ||||
| 	 | ||||
| 	// 绑定父子关系 | ||||
| 	Generator->BindChildTerrain(SwampHandle, {SwampLandHandle, SwampWaterHandle}); | ||||
| 	Generator->BindChildTerrain(WaterHandle, {ShallowWaterHandle, DeepWaterHandle}); | ||||
| 	 | ||||
| 	// 设置概率 | ||||
| 	Generator->SetProbability(ForestHandle, 1.0f); | ||||
| 	Generator->SetProbability(GrasslandHandle, 0.9f); | ||||
| 	Generator->SetProbability(WaterHandle, 0.7f); | ||||
| 	Generator->SetProbability(SwampHandle, 1.0f); // 设置为1.0确保沼泽总是出现 | ||||
| 	Generator->SetProbability(LandHandle, 1.0f); | ||||
| 	 | ||||
| 	// 设置权重 | ||||
| 	Generator->SetWeight(ForestHandle, 9.0f); | ||||
| 	Generator->SetWeight(GrasslandHandle, 5.0f); | ||||
| 	Generator->SetWeight(LandHandle, 3.0f); | ||||
| 	Generator->SetWeight(SwampHandle, 2.0f); // 为沼泽父节点设置权重 | ||||
| 	 | ||||
| 	Generator->SetWeight(SwampLandHandle, 1.0f); | ||||
| 	Generator->SetWeight(SwampWaterHandle, 2.0f); | ||||
| 	 | ||||
| 	Generator->SetWeight(ShallowWaterHandle, 3.0f); | ||||
| 	Generator->SetWeight(DeepWaterHandle, 1.0f); | ||||
| 	 | ||||
| 	// 设置互斥关系 | ||||
| 	Generator->SetExclusive({ForestHandle, SwampWaterHandle}); | ||||
| 	 | ||||
| 	// 生成地图 | ||||
| 	TArray<FGameplayTag> Map = Generator->GenerateMap(64, 64); | ||||
| 	 | ||||
| 	// 统计地形分布 | ||||
| 	TMap<FGameplayTag, int32> TerrainCount; | ||||
| 	for (const FGameplayTag& Tag : Map) | ||||
| 	{ | ||||
| 		TerrainCount.FindOrAdd(Tag)++; | ||||
| 	} | ||||
| 	 | ||||
| 	// 输出统计结果 | ||||
| 	UE_LOG(LogTemp, Log, TEXT("=== 地形生成统计 ===")); | ||||
| 	UE_LOG(LogTemp, Log, TEXT("地图大小: %dx%d"), 64, 64); | ||||
| 	UE_LOG(LogTemp, Log, TEXT("总格子数: %d"), Map.Num()); | ||||
| 	 | ||||
| 	for (const auto& Pair : TerrainCount) | ||||
| 	{ | ||||
| 		float Percentage = (float)Pair.Value / Map.Num() * 100.0f; | ||||
| 		UE_LOG(LogTemp, Log, TEXT("%s: %d (%.1f%%)"),  | ||||
| 			   *Pair.Key.ToString(), Pair.Value, Percentage); | ||||
| 	} | ||||
| 	 | ||||
| 	// 验证互斥关系 | ||||
| 	bool HasForest = TerrainCount.Contains(ForestTag); | ||||
| 	bool HasSwampWater = TerrainCount.Contains(SwampWaterTag); | ||||
| 	 | ||||
| 	if (HasForest && HasSwampWater) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Warning, TEXT("警告: 森林和沼泽水域同时出现,互斥关系可能未正确工作")); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Log, TEXT("互斥关系验证: 通过")); | ||||
| 	} | ||||
| 	 | ||||
| 	// 验证父子关系 | ||||
| 	bool HasSwamp = TerrainCount.Contains(SwampTag); | ||||
| 	bool HasSwampChildren = TerrainCount.Contains(SwampLandTag) || TerrainCount.Contains(SwampWaterTag); | ||||
| 	 | ||||
| 	if (HasSwamp && !HasSwampChildren) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Warning, TEXT("警告: 沼泽出现但没有子地形")); | ||||
| 	} | ||||
| 	else if (!HasSwamp && HasSwampChildren) | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Warning, TEXT("警告: 沼泽子地形出现但沼泽未出现")); | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		UE_LOG(LogTemp, Log, TEXT("父子关系验证: 通过")); | ||||
| 	} | ||||
| 	 | ||||
| 	UE_LOG(LogTemp, Log, TEXT("=== 测试完成 ===")); | ||||
| } | ||||
|  | ||||
| // 控制台命令用于测试 | ||||
| static FAutoConsoleCommand TestTerrainGeneratorCommand( | ||||
| 	TEXT("TestTerrainGenerator"), | ||||
| 	TEXT("测试地形生成器功能"), | ||||
| 	FConsoleCommandDelegate::CreateStatic(TestTerrainGenerator) | ||||
| ); | ||||
							
								
								
									
										117
									
								
								Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "GameFramework/Actor.h" | ||||
| #include "GameplayTagContainer.h" | ||||
| #include "PaperTerrainMapActor.generated.h" | ||||
|  | ||||
| // 地形到Tile的映射结<E5B084><E7BB93>? | ||||
| USTRUCT(BlueprintType) | ||||
| struct FTerrainTileMapping | ||||
| { | ||||
| 	GENERATED_BODY() | ||||
|  | ||||
| 	// 地形标签 | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	FGameplayTag TerrainTag; | ||||
|  | ||||
| 	// 对应的TileSet中的Tile索引 | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	int32 TileIndex; | ||||
|  | ||||
| 	FTerrainTileMapping() | ||||
| 		: TileIndex(0) | ||||
| 	{ | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| UCLASS(Blueprintable, BlueprintType) | ||||
| class BUSYRABBIT_API APaperTerrainMapActor : public AActor | ||||
| { | ||||
| 	GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
| 	APaperTerrainMapActor(); | ||||
|  | ||||
| protected: | ||||
| 	virtual void BeginPlay() override; | ||||
| 	virtual void OnConstruction(const FTransform& Transform) override; | ||||
|  | ||||
| public: | ||||
| 	// 生成地图(可在蓝图中调用<E8B083><E794A8>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Map") | ||||
| 	void GenerateTerrainMap(); | ||||
|  | ||||
| 	// 清除当前地图 | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Map") | ||||
| 	void ClearMap(); | ||||
|  | ||||
| 	// 获取地形生成器实<E599A8><E5AE9E>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Map") | ||||
| 	class UTerrainGenerator* GetTerrainGenerator() const; | ||||
|  | ||||
| 	// 获取生成的地形数<E5BDA2><E695B0>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Map") | ||||
| 	TArray<FGameplayTag> GetGeneratedTerrainData() const; | ||||
|  | ||||
| #if WITH_EDITOR | ||||
| 	virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; | ||||
| #endif | ||||
|  | ||||
| protected: | ||||
| 	// Paper2D瓦片地图组件 | ||||
| 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") | ||||
| 	class UPaperTileMapComponent* TileMapComponent; | ||||
|  | ||||
| 	// 地形生成器实<E599A8><E5AE9E>? | ||||
| 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") | ||||
| 	class UTerrainGenerator* TerrainGenerator; | ||||
|  | ||||
| 	// 生成的地形数<E5BDA2><E695B0>? | ||||
| 	UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") | ||||
| 	TArray<FGameplayTag> GeneratedTerrainData; | ||||
|  | ||||
| 	// 地图宽度(格子数<E5AD90><E695B0>? | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024")) | ||||
| 	int32 MapWidth = 16; | ||||
|  | ||||
| 	// 地图高度(格子数<E5AD90><E695B0>? | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024")) | ||||
| 	int32 MapHeight = 16; | ||||
|  | ||||
| 	// 瓦片大小(像素) | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1")) | ||||
| 	int32 TileSize = 16; | ||||
|  | ||||
| 	// 是否在构造时自动生成地图 | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings") | ||||
| 	bool bAutoGenerateOnConstruction; | ||||
|  | ||||
| 	// 是否在开始时自动生成地图 | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings") | ||||
| 	bool bAutoGenerateOnBeginPlay; | ||||
|  | ||||
| 	// 地形到Tile的映射表 | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping") | ||||
| 	TArray<FTerrainTileMapping> TerrainTileMappings; | ||||
|  | ||||
| 	// 默认Tile索引(用于未映射的地形) | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping", meta = (ClampMin = "0")) | ||||
| 	int32 DefaultTileIndex; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, Category = Sprite) | ||||
| 	TObjectPtr<class UPaperTileSet> TileSet; | ||||
|  | ||||
| private: | ||||
| 	// 初始化TileMap组件 | ||||
| 	void InitializeTileMap(); | ||||
|  | ||||
| 	// 应用地形数据到TileMap | ||||
| 	void ApplyTerrainToTileMap(const TArray<FGameplayTag>& TerrainData); | ||||
|  | ||||
| 	// 根据地形标签获取Tile索引 | ||||
| 	int32 GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const; | ||||
|  | ||||
| 	// 创建默认地形配置 | ||||
| 	void SetupDefaultTerrainConfig(); | ||||
| }; | ||||
							
								
								
									
										91
									
								
								Source/BusyRabbit/Public/Level/TerrainGenerator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								Source/BusyRabbit/Public/Level/TerrainGenerator.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,91 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "UObject/NoExportTypes.h" | ||||
| #include "GameplayTagContainer.h" | ||||
| #include "TerrainGenerator.generated.h" | ||||
|  | ||||
| USTRUCT(BlueprintType) | ||||
| struct FTerrainNodeInfo | ||||
| { | ||||
| 	GENERATED_BODY() | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	FGameplayTag TerrainTag; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	float Probability; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	float Weight; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	int32 ParentHandle; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	TArray<int32> ChildHandles; | ||||
|  | ||||
| 	UPROPERTY(EditAnywhere, BlueprintReadWrite) | ||||
| 	bool bIsLeafNode; | ||||
|  | ||||
| 	FTerrainNodeInfo() | ||||
| 		: Probability(1.0f) | ||||
| 		, Weight(1.0f) | ||||
| 		, ParentHandle(-1) | ||||
| 		, bIsLeafNode(false) | ||||
| 	{ | ||||
| 	} | ||||
| }; | ||||
|  | ||||
|  | ||||
| UCLASS(BlueprintType, Blueprintable) | ||||
| class BUSYRABBIT_API UTerrainGenerator : public UObject | ||||
| { | ||||
| 	GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
| 	UTerrainGenerator(); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	int32 AddTerrain(const FGameplayTag& TerrainTag); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	void SetWeight(int32 Handle, float Weight); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	void SetProbability(int32 Handle, float Probability); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	void SetExclusive(const TArray<int32>& Handles); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	void BindChildTerrain(int32 ParentHandle, const TArray<int32>& ChildHandles); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	TArray<FGameplayTag> GenerateMap(int32 Width = 256, int32 Height = 256); | ||||
|  | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	TMap<int32, FTerrainNodeInfo> GetAllTerrainNodes() const; | ||||
|  | ||||
| private: | ||||
| 	TMap<int32, FTerrainNodeInfo> TerrainNodes; | ||||
|  | ||||
| 	TArray<TSet<int32>> ExclusiveGroups; | ||||
|  | ||||
| 	int32 NextHandle; | ||||
|  | ||||
| 	void GetPreCheckVisibleHandles(TSet<int32>& VisibleHandles)const; | ||||
|  | ||||
| 	bool ShouldNodeAppear(int32 Handle) const; | ||||
|  | ||||
| 	bool CheckExclusive(int32 Handle, const TSet<int32>& AppearingNodes) const; | ||||
|  | ||||
| 	TArray<int32> CalcValidLeafNodes()const; | ||||
|  | ||||
| 	void RegionGrowing(TArray<FGameplayTag>& Map, int32 Width, int32 Height,  | ||||
| 					  const TArray<int32>& ValidLeafNodes) const; | ||||
|  | ||||
| 	void ApplyPerlinNoiseSmoothing(TArray<FGameplayTag>& Map, int32 Width, int32 Height) const; | ||||
|  | ||||
| 	FGameplayTag GetFinalTerrainTag(int32 Handle) const; | ||||
| }; | ||||
| @ -0,0 +1,46 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "CoreMinimal.h" | ||||
| #include "Kismet/BlueprintFunctionLibrary.h" | ||||
| #include "GameplayTagContainer.h" | ||||
| #include "TerrainGeneratorBlueprintLibrary.generated.h" | ||||
|  | ||||
| // 蓝图函数库,方便在蓝图中使用地形生成<E7949F><E68890>? | ||||
| UCLASS() | ||||
| class BUSYRABBIT_API UTerrainGeneratorBlueprintLibrary : public UBlueprintFunctionLibrary | ||||
| { | ||||
| 	GENERATED_BODY() | ||||
|  | ||||
| public: | ||||
| 	// 创建地形生成器实<E599A8><E5AE9E>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static class UTerrainGenerator* CreateTerrainGenerator(); | ||||
|  | ||||
| 	// 快速设置示例地形配<E5BDA2><E9858D>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static void SetupExampleTerrainConfig(class UTerrainGenerator* Generator); | ||||
|  | ||||
| 	// 生成地图并返回一维数组(蓝图不支持嵌套TArray<61><79>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static TArray<FGameplayTag> GenerateMap( | ||||
| 		class UTerrainGenerator* Generator,  | ||||
| 		int32 Width = 256,  | ||||
| 		int32 Height = 256 | ||||
| 	); | ||||
|  | ||||
| 	// 获取地图尺寸信息(用于在蓝图中解析一维数组) | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static void GetMapDimensions( | ||||
| 		class UTerrainGenerator* Generator, | ||||
| 		int32& Width, | ||||
| 		int32& Height | ||||
| 	); | ||||
|  | ||||
| 	// 获取地形标签的显示名<E7A4BA><E5908D>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static FString GetTerrainDisplayName(const FGameplayTag& TerrainTag); | ||||
|  | ||||
| 	// 检查地形标签是否有<E590A6><E69C89>? | ||||
| 	UFUNCTION(BlueprintCallable, Category = "Terrain Generator") | ||||
| 	static bool IsValidTerrainTag(const FGameplayTag& TerrainTag); | ||||
| }; | ||||
		Reference in New Issue
	
	Block a user