Files
BusyRabbit/Source/BusyRabbit/Private/Level/Map/TerrainLayerComponent.cpp
2025-09-23 02:52:33 +08:00

265 lines
7.3 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Level/Map/TerrainLayerComponent.h"
#include "Level/Generator/VoronoiTerrainGenerator.h"
#include "Paper2D/Classes/PaperTileLayer.h"
#include "Paper2D/Classes/PaperTileMapComponent.h"
#include "Paper2D/Classes/PaperTileSet.h"
#include "PaperTileMap.h"
/* 相邻四格地形信息映射到TileSet的索引
* 以左上、右上、左下、右下地形块存在为1不存在为0, 从左到右构建一个4位二进制整数
* 假设原始TileSet中0号索引左上、右上、右下为空, 对应二进制整数为0x0010
* 那么TileSet的这个索引与相邻数据对应关系就是[0] -> 2
* 也就是DefaultNeighborDataToIdxMappings[2] = 0
*/
const static int DefaultNeighborDataToIdxMappings[16] = {
12, 13, 0, 3, 8, 1, 14, 5, 15, 4, 11, 2, 9, 10, 7, 6
};
const static FGameplayTag SortedTerrainsWithPriority [] = {
FGameplayTag::RequestGameplayTag("Terrain.Desert"),
FGameplayTag::RequestGameplayTag("Terrain.Forest"),
FGameplayTag::RequestGameplayTag("Terrain.Grassland"),
FGameplayTag::RequestGameplayTag("Terrain.Swamp.Land"),
FGameplayTag::RequestGameplayTag("Terrain.Swamp.Water"),
FGameplayTag::RequestGameplayTag("Terrain.Land"),
FGameplayTag::RequestGameplayTag("Terrain.Water.Deep"),
FGameplayTag::RequestGameplayTag("Terrain.Water.Shallow"),
};
static bool GenerateTerrain(const TArray<FGameplayTag>& InTerrains, const TArray<int32> InPriority, int32 InMapSize, TArray<FGameplayTag>& OutTerrains)
{
// 验证输入参数
if (InTerrains.Num() == 0 || InTerrains.Num() != InPriority.Num() || InMapSize <= 0)
{
return false;
}
// 计算总权重
int32 TotalPriority = 0;
for (int32 Priority : InPriority)
{
if (Priority < 0) return false; // 权重不能为负数
TotalPriority += Priority;
}
if (TotalPriority == 0) return false; // 总权重不能为零
// 准备输出数组
OutTerrains.Empty();
OutTerrains.SetNum(InMapSize * InMapSize);
// 计算每种地形的阈值范围
TArray<float> Thresholds;
Thresholds.SetNum(InTerrains.Num());
float Accumulated = 0.0f;
for (int32 i = 0; i < InPriority.Num(); i++)
{
Accumulated += static_cast<float>(InPriority[i]) / TotalPriority;
Thresholds[i] = Accumulated;
}
// 设置柏林噪声参数
float Frequency = 0.05f; // 可以调整这个值来改变地形的细节程度
float OffsetX = FMath::RandRange(0.0f, 1000.0f); // 随机偏移以确保每次生成不同的地形
float OffsetY = FMath::RandRange(0.0f, 1000.0f);
// 生成地形
for (int32 Y = 0; Y < InMapSize; Y++)
{
for (int32 X = 0; X < InMapSize; X++)
{
// 计算柏林噪声值 (范围在-1到1之间)
float NoiseValue = FMath::PerlinNoise2D(FVector2D((X + OffsetX) * Frequency, (Y + OffsetY) * Frequency));
// 将噪声值映射到0到1范围
float NormalizedNoise = (NoiseValue + 1.0f) / 2.0f;
// 根据噪声值选择地形
for (int32 i = 0; i < Thresholds.Num(); i++)
{
if (NormalizedNoise <= Thresholds[i] || i == Thresholds.Num() - 1)
{
OutTerrains[Y * InMapSize + X] = InTerrains[i];
break;
}
}
}
}
return true;
}
static UPaperTileLayer* GetTileMapLayer(
const FGameplayTag& InTerrainType,
const TMap<FGameplayTag, TObjectPtr<UPaperTileMapComponent>>& TileMapMeshes
){
// 将给定的数据绘制到TileMapLayer上
const TObjectPtr<UPaperTileMapComponent> *TileMapMesh = TileMapMeshes.Find(InTerrainType);
if (!TileMapMesh)
{
return nullptr;
}
const TObjectPtr<UPaperTileMap> TileMap = TileMapMesh->Get()->TileMap;
if (!TileMap)
{
return nullptr;
}
if (TileMap->TileLayers.Num() == 0)
{
return nullptr;
}
return TileMap->TileLayers[0];
}
static UPaperTileSet* GetMapTileSet(
const FGameplayTag& InTerrainType,
TMap<FGameplayTag, TObjectPtr<UPaperTileSet>>& TileSetConfigs
){
if (const TObjectPtr<UPaperTileSet> *TileSet = TileSetConfigs.Find(InTerrainType))
{
return TileSet->Get();
}
return nullptr;
}
static inline int32 GetTileSetIndex(const bool LeftUp, const bool RightUp, const bool LeftDown, const bool RightDown)
{
int32 GetTileSetIndex = 0;
GetTileSetIndex += (LeftUp ? 1 : 0) << 3;
GetTileSetIndex += (RightUp ? 1 : 0) << 2;
GetTileSetIndex += (LeftDown ? 1 : 0) << 1;
GetTileSetIndex += (RightDown ? 1 : 0);
return GetTileSetIndex;
}
UTerrainLayerComponent::UTerrainLayerComponent()
{
}
void UTerrainLayerComponent::BeginPlay()
{
Super::BeginPlay();
SetupTerrainMeshes();
TArray<int32> Priority;
TArray<FGameplayTag> TerrainData;
TArray<FGameplayTag> TerrainTypes;
TArray<bool> FilteredTerrainData;
for (auto TileSetConfig : TileSetConfigs)
{
TerrainTypes.Add(TileSetConfig.Key);
Priority.Add(1);
}
GenerateTerrain(TerrainTypes, Priority, MapWidth, TerrainData);
for (auto TerrainType : TerrainTypes)
{
FilteredTerrainData.Init(false, TerrainData.Num());
for (int32 i = 0; i < TerrainData.Num(); i++)
{
FilteredTerrainData[i] = (TerrainData[i] == TerrainType);
}
SetTerrainData(TerrainType, FilteredTerrainData);
}
}
void UTerrainLayerComponent::SetTerrainData(const FGameplayTag& InTerrainType, const TArray<bool>& TerrainData)
{
// 将给定的数据绘制到TileMapLayer上
UPaperTileSet* TileSet = GetMapTileSet(InTerrainType, TileSetConfigs);
UPaperTileLayer *TileMapLayer = GetTileMapLayer(InTerrainType, TerrainMeshes);
if (!TileSet || !TileMapLayer)
{
UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData"));
return;
}
if (TerrainData.Num() != MapWidth * MapHeight)
{
UE_LOG(LogTemp, Warning, TEXT("UTerrainLayerComponent::SetTerrainData"));
return;
}
for (int32 i = 0; i < MapHeight - 1; i++)
{
for (int32 j = 0; j < MapWidth - 1; j++)
{
FPaperTileInfo TileInfo;
const int32 CurRow = i * MapWidth;
const int32 NextRow = (i + 1) * MapWidth;
const int32 NeighborIndex = GetTileSetIndex(
TerrainData[CurRow + j],
TerrainData[CurRow + j + 1],
TerrainData[NextRow + j],
TerrainData[NextRow + j + 1]
);
TileInfo.TileSet = TileSet;
TileInfo.PackedTileIndex = DefaultNeighborDataToIdxMappings[NeighborIndex];
TileMapLayer->SetCell(j, i, TileInfo);
}
}
}
void UTerrainLayerComponent::SetupTerrainMeshes()
{
if (TileSetConfigs.Num() == 0)
{
return;
}
TerrainMeshes.Empty();
USceneComponent *RootScene = GetOwner()->GetRootComponent();
int32 Z = 0;
for (const FGameplayTag &TerrainType : SortedTerrainsWithPriority)
{
if (TileSetConfigs.Find(TerrainType) == nullptr)
{
continue;
}
// 创建一个新的TileMap组件新的TileMap新的TileLayer
auto* NewTileMapMesh = NewObject<UPaperTileMapComponent>(this);
NewTileMapMesh->RegisterComponent();
UPaperTileMap* NewTileMap = NewObject<UPaperTileMap>(NewTileMapMesh);
NewTileMap->MapWidth = MapWidth;
NewTileMap->MapHeight = MapHeight;
NewTileMap->TileWidth = 128;
NewTileMap->TileHeight = 128;
UPaperTileLayer* NewLayer = NewObject<UPaperTileLayer>(NewTileMap);
NewLayer->LayerName = FText::FromString("TerrainLayer");
NewLayer->ResizeMap(NewTileMap->MapWidth, NewTileMap->MapHeight);
NewTileMap->TileLayers.Add(NewLayer);
NewTileMapMesh->SetTileMap(NewTileMap);
NewTileMapMesh->SetRelativeLocation(FVector(-(NewTileMap->TileWidth / 2), -(NewTileMap->TileHeight / 2), Z++));
NewTileMapMesh->SetRelativeRotation(FRotator(0, 0, -90));
NewTileMapMesh->AttachToComponent(RootScene, FAttachmentTransformRules::KeepWorldTransform);
TerrainMeshes.Add(TerrainType, NewTileMapMesh);
}
}