265 lines
7.3 KiB
C++
265 lines
7.3 KiB
C++
#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);
|
||
}
|
||
}
|