AI 实现初版随机地图

This commit is contained in:
2025-09-07 03:34:14 +08:00
parent 4e7f89bd38
commit 89d751ac34
22 changed files with 1555 additions and 1 deletions

View 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);
}
}

View 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();
}

View File

@ -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();
}

View 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)
);

View 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();
};

View 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;
};

View File

@ -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);
};