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