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