diff --git a/.gitignore b/.gitignore index 7b2784a..9905b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # ---> UnrealEngine # Visual Studio 2015 user specific files .vs/ +.idea/ # Compiled Object files *.slo diff --git a/Config/DefaultGameplayTags.ini b/Config/DefaultGameplayTags.ini index da7183b..a886ef0 100644 --- a/Config/DefaultGameplayTags.ini +++ b/Config/DefaultGameplayTags.ini @@ -35,4 +35,12 @@ NetIndexFirstBitSegment=16 +GameplayTagList=(Tag="Recover.Role.Health",DevComment="回复生命值") +GameplayTagList=(Tag="Recover.Role.Hunger",DevComment="恢复饥饿值") +GameplayTagList=(Tag="Status.Role.Invincible",DevComment="不掉血标签") - ++GameplayTagList=(Tag="Terrain.Forest",DevComment="森林") ++GameplayTagList=(Tag="Terrain.Grassland",DevComment="草地") ++GameplayTagList=(Tag="Terrain.Water",DevComment="水体") ++GameplayTagList=(Tag="Terrain.Water.Shallow", DevComment="浅水区") ++GameplayTagList=(Tag="Terrain.Water.Deep", DevComment="深水区") ++GameplayTagList=(Tag="Terrain.Land",DevComment="土地") ++GameplayTagList=(Tag="Terrain.Swamp", DevComment="沼泽") ++GameplayTagList=(Tag="Terrain.Swamp.Land", DevComment="沼泽陆地") ++GameplayTagList=(Tag="Terrain.Swamp.Water", DevComment="沼泽水域") diff --git a/Content/Level/BP_Test.uasset b/Content/Level/BP_Test.uasset new file mode 100644 index 0000000..c737e3d Binary files /dev/null and b/Content/Level/BP_Test.uasset differ diff --git a/Content/Level/NewWorld.umap b/Content/Level/NewWorld.umap new file mode 100644 index 0000000..bf5375d Binary files /dev/null and b/Content/Level/NewWorld.umap differ diff --git a/Content/Level/tileset.uasset b/Content/Level/tileset.uasset new file mode 100644 index 0000000..1b5cb55 Binary files /dev/null and b/Content/Level/tileset.uasset differ diff --git a/Content/Level/tileset_TileSet.uasset b/Content/Level/tileset_TileSet.uasset new file mode 100644 index 0000000..b282d02 Binary files /dev/null and b/Content/Level/tileset_TileSet.uasset differ diff --git a/Content/Lua/UI/ModelView/Hearth/PreCookModelView.lua b/Content/Lua/UI/ModelView/Hearth/PreCookModelView.lua new file mode 100644 index 0000000..81e73dd --- /dev/null +++ b/Content/Lua/UI/ModelView/Hearth/PreCookModelView.lua @@ -0,0 +1,11 @@ +local function InitPreCookViewModel(vm, wco) + vm.selected_precook_tools = {} + vm.selected_precook_containers = {} +end + + +return function(wco) + local vm = {} + InitPreCookViewModel(vm, wco) + return vm +end \ No newline at end of file diff --git a/Content/Lua/UI/ViewModel/Hearth/PreCookViewModel.lua b/Content/Lua/UI/ViewModel/Hearth/PreCookViewModel.lua new file mode 100644 index 0000000..c1d0729 --- /dev/null +++ b/Content/Lua/UI/ViewModel/Hearth/PreCookViewModel.lua @@ -0,0 +1,7 @@ +local PreCookModelView = {} + + + +return function(wco) + +end \ No newline at end of file diff --git a/Content/Resource/Map/FalconPlain/Test.uasset b/Content/Resource/Map/FalconPlain/Test.uasset index 4fb0822..6b24590 100644 Binary files a/Content/Resource/Map/FalconPlain/Test.uasset and b/Content/Resource/Map/FalconPlain/Test.uasset differ diff --git a/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/cutting_board.uasset b/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/cutting_board.uasset index 33c1312..d5c5500 100644 Binary files a/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/cutting_board.uasset and b/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/cutting_board.uasset differ diff --git a/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/knife.uasset b/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/knife.uasset index 5e3b37c..e2b8108 100644 Binary files a/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/knife.uasset and b/Content/Resource/Texture/UI/Homeland/Hearth/PreCook/knife.uasset differ diff --git a/Content/UI/HomeLand/Hearth/WBP_PreCookStationPanel.uasset b/Content/UI/HomeLand/Hearth/WBP_PreCookStationPanel.uasset index 2829fdb..f6d1226 100644 Binary files a/Content/UI/HomeLand/Hearth/WBP_PreCookStationPanel.uasset and b/Content/UI/HomeLand/Hearth/WBP_PreCookStationPanel.uasset differ diff --git a/Content/UI/HomeLand/Hearth/Widgets/PrepCookStation/WBP_PreCookCenterWidget.uasset b/Content/UI/HomeLand/Hearth/Widgets/PrepCookStation/WBP_PreCookCenterWidget.uasset index ee720ba..5f534ac 100644 Binary files a/Content/UI/HomeLand/Hearth/Widgets/PrepCookStation/WBP_PreCookCenterWidget.uasset and b/Content/UI/HomeLand/Hearth/Widgets/PrepCookStation/WBP_PreCookCenterWidget.uasset differ diff --git a/Docs/PaperTerrainMapActor_Usage.md b/Docs/PaperTerrainMapActor_Usage.md new file mode 100644 index 0000000..b39594e --- /dev/null +++ b/Docs/PaperTerrainMapActor_Usage.md @@ -0,0 +1,181 @@ +# PaperTerrainMapActor 使用说明 + +## 概述 + +`APaperTerrainMapActor` 是一个封装了Paper2D TileMap组件的地形地图生成器Actor,集成了之前实现的地形生成器功能,可以直接在场景中生成和显示随机地图。 + +## 核心功能 + +### 1. 集成地形生成器 +- 内置 `UTerrainGenerator` 实例 +- 支持所有地形父子关系、概率权重和互斥关系 +- 自动应用默认地形配置 + +### 2. Paper2D TileMap 支持 +- 自动创建和管理PaperTileMap组件 +- 支持自定义地图尺寸和Tile大小 +- 自动调整组件缩放以适应地图尺寸 + +### 3. 地形到Tile映射 +- 可配置的地形标签到Tile索引映射 +- 支持自定义Tile映射关系 +- 默认Tile索引用于未映射的地形 + +### 4. 自动生成选项 +- 在构造时自动生成地图 +- 在游戏开始时自动生成地图 +- 支持手动触发生成 + +## 基本用法 + +### 在场景中使用 + +1. 在内容浏览器中右键 -> 创建高级资源 -> Blueprint类 +2. 选择 `APaperTerrainMapActor` 作为父类 +3. 将创建的Blueprint拖放到场景中 + +### 配置参数 + +在Details面板中可以配置以下参数: + +**Map Settings:** +- `MapWidth`: 地图宽度(格子数) +- `MapHeight`: 地图高度(格子数) +- `TileSize`: 每个Tile的大小(像素) +- `bAutoGenerateOnConstruction`: 是否在构造时自动生成 +- `bAutoGenerateOnBeginPlay`: 是否在游戏开始时自动生成 + +**Terrain Mapping:** +- `TerrainTileMappings`: 地形到Tile的映射表 +- `DefaultTileIndex`: 默认Tile索引 + +### 蓝图调用 + +在蓝图中可以调用以下函数: + +```blueprint +- GenerateTerrainMap():生成地图 +- ClearMap():清除当前地图 +- GetTerrainGenerator():获取地形生成器实例 +- GetGeneratedTerrainData():获取生成的地形数据 +``` + +## 高级配置 + +### 自定义地形映射 + +可以在Details面板中编辑 `TerrainTileMappings` 数组,为每个地形标签指定对应的Tile索引: + +```cpp +// 示例:自定义地形映射 +FTerrainTileMapping CustomMapping; +CustomMapping.TerrainTag = FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest")); +CustomMapping.TileIndex = 10; // 使用TileSet中的第10个Tile +``` + +### 修改地形生成配置 + +通过获取地形生成器实例,可以动态修改地形配置: + +```cpp +// 在蓝图中或代码中修改配置 +UTerrainGenerator* Generator = PaperTerrainMapActor->GetTerrainGenerator(); +Generator->SetProbability(ForestHandle, 0.9f); +Generator->SetWeight(ForestHandle, 8.0f); +``` + +### 动态生成地图 + +可以在运行时动态生成不同尺寸的地图: + +```cpp +// 设置新尺寸 +PaperTerrainMapActor->MapWidth = 128; +PaperTerrainMapActor->MapHeight = 128; + +// 重新生成地图 +PaperTerrainMapActor->GenerateTerrainMap(); +``` + +## 性能优化 + +### 地图尺寸建议 +- 小地图:64x64 - 128x128(适合手机设备) +- 中地图:256x256(默认,平衡性能和细节) +- 大地图:512x512+(需要性能优化) + +### 生成时机 +- 在加载界面预生成地图 +- 使用后台线程生成(需要自行实现线程逻辑) +- 支持渐进式生成和流式加载 + +## 调试功能 + +### 日志输出 +Actor会输出详细的生成日志: +- 地图初始化信息 +- 地形生成统计 +- TileMap应用结果 + +### 可视化调试 +- 在编辑器中实时查看生成结果 +- 支持运行时重新生成 +- 可以随时清除和重新生成地图 + +## 集成示例 + +### 与游戏逻辑集成 + +```cpp +// 在GameMode中生成初始地图 +void ABusyGameMode::BeginPlay() +{ + Super::BeginPlay(); + + // 生成地图 + APaperTerrainMapActor* MapActor = GetWorld()->SpawnActor(); + MapActor->GenerateTerrainMap(); + + // 保存地图引用 + CurrentMap = MapActor; +} +``` + +### 与导航系统集成 + +可以根据生成的地形数据设置导航网格: + +```cpp +// 根据地形类型设置导航成本 +for (const FGameplayTag& TerrainTag : MapActor->GetGeneratedTerrainData()) +{ + if (TerrainTag.MatchesTag(FGameplayTag::RequestGameplayTag(TEXT("Terrain.Water")))) + { + // 水域导航成本高 + SetNavigationCost(TerrainTag, 10.0f); + } + else + { + // 普通地形导航成本低 + SetNavigationCost(TerrainTag, 1.0f); + } +} +``` + +## 注意事项 + +1. **TileSet配置**:需要提前配置好Paper2D TileSet资源 +2. **地形标签**:确保GameplayTags设置中包含所需的地形标签 +3. **性能考虑**:大地图生成可能耗时,建议在后台线程进行 +4. **内存使用**:大地图会占用较多内存,注意资源管理 + +## 扩展建议 + +### 添加多层地形 +可以扩展支持多层TileMap,用于表现地形高度、装饰物等。 + +### 动态地形修改 +支持运行时修改特定位置的地形,用于表现建筑、道路等。 + +### 序列化支持 +添加地图数据的序列化功能,支持保存和加载生成的地图。 diff --git a/Docs/TerrainGenerator_Usage.md b/Docs/TerrainGenerator_Usage.md new file mode 100644 index 0000000..bf3dd2d --- /dev/null +++ b/Docs/TerrainGenerator_Usage.md @@ -0,0 +1,149 @@ +# 地形生成器使用说明 + +## 概述 + +这个地形生成器实现了基于区域生长算法和柏林噪声的随机地图生成系统,支持复杂的地形父子关系、概率权重配置和互斥关系。 + +## 核心功能 + +### 1. 地形父子关系 +- 支持多层级父子关系(类似B+树结构) +- 父节点不出现时,所有子节点都不出现 +- 支持祖孙关系处理 + +### 2. 概率和权重配置 +- 概率 (0-1):控制地形是否出现 +- 权重 (≥0):控制地形在出现时的面积占比 +- 权重为0表示不生成该地形 + +### 3. 互斥关系 +- 支持多个地形之间的互斥关系 +- 互斥的地形不会同时出现 +- 支持复杂的互斥组配置 + +### 4. 生成算法 +- **区域生长算法**:生成连续的地形区域 +- **柏林噪声**:用于地形边界平滑和自然过渡 +- 支持自定义地图大小(默认256x256) + +## 基本用法 + +### C++ 使用示例 + +```cpp +// 创建生成器实例 +UTerrainGenerator* Generator = NewObject(); + +// 添加地形 +int32 ForestHandle = Generator->AddTerrain(FGameplayTag::RequestGameplayTag(TEXT("Terrain.Forest"))); +int32 GrasslandHandle = Generator->AddTerrain(FGameplayTag::RequestGameplayTag(TEXT("Terrain.Grassland"))); + +// 设置概率和权重 +Generator->SetProbability(ForestHandle, 0.8f); +Generator->SetWeight(ForestHandle, 9.0f); + +// 生成地图 +TArray Map = Generator->GenerateMap(256, 256); +``` + +### 蓝图使用示例 + +1. 在蓝图中调用 `CreateTerrainGenerator` 创建实例 +2. 调用 `SetupExampleTerrainConfig` 快速设置示例配置 +3. 调用 `Generate2DMap` 生成二维地图数组 +4. 使用 `GetTerrainDisplayName` 获取地形显示名称 + +## 地形标签配置 + +需要在项目的GameplayTags设置中配置以下地形标签: + +```ini +[GameplayTags] ++GameplayTagList=(Tag="Terrain.Forest") ++GameplayTagList=(Tag="Terrain.Grassland") ++GameplayTagList=(Tag="Terrain.Water") ++GameplayTagList=(Tag="Terrain.Swamp") ++GameplayTagList=(Tag="Terrain.Land") ++GameplayTagList=(Tag="Terrain.Swamp.Land") ++GameplayTagList=(Tag="Terrain.Swamp.Water") ++GameplayTagList=(Tag="Terrain.Water.Shallow") ++GameplayTagList=(Tag="Terrain.Water.Deep") +``` + +## 高级配置 + +### 父子关系绑定 + +```cpp +// 添加父地形和子地形 +int32 SwampHandle = Generator->AddTerrain(SwampTag); +int32 SwampLandHandle = Generator->AddTerrain(SwampLandTag); +int32 SwampWaterHandle = Generator->AddTerrain(SwampWaterTag); + +// 绑定父子关系 +Generator->BindChildTerrain(SwampHandle, {SwampLandHandle, SwampWaterHandle}); +``` + +### 互斥关系设置 + +```cpp +// 设置森林和沼泽水域互斥 +Generator->SetExclusive({ForestHandle, SwampWaterHandle}); +``` + +### 复杂权重配置 + +```cpp +// 设置沼泽子地形的权重比例 +Generator->SetWeight(SwampLandHandle, 1.0f); // 沼泽陆地占沼泽区域的1/3 +Generator->SetWeight(SwampWaterHandle, 2.0f); // 沼泽水域占沼泽区域的2/3 +``` + +## 性能考虑 + +- 地图生成可以在后台线程中进行 +- 256x256地图生成时间约100-500ms(取决于硬件) +- 建议在游戏加载时预生成地图 +- 支持渐进式生成和流式加载 + +## 调试和测试 + +### 控制台命令 + +```bash +# 在控制台中输入以下命令测试生成器 +TestTerrainGenerator +``` + +### 日志输出 + +生成器会输出详细的统计信息: +- 各地形类型的数量和百分比 +- 互斥关系验证结果 +- 父子关系验证结果 + +## 自定义扩展 + +### 添加新的地形类型 + +1. 在GameplayTags配置中添加新标签 +2. 使用 `AddTerrain` 添加新地形 +3. 配置相应的概率和权重 + +### 修改生成算法 + +可以重写以下方法来自定义生成行为: +- `RegionGrowing`: 区域生长算法实现 +- `ApplyPerlinNoiseSmoothing`: 噪声平滑处理 +- `GetValidLeafNodes`: 有效节点筛选逻辑 + +## 注意事项 + +1. **概率为0**:地形绝对不会出现,所有子节点也不会出现 +2. **权重为0**:地形不会生成,但可能影响互斥关系 +3. **互斥组**:多次调用SetExclusive不会传导互斥关系 +4. **生成失败**:如果没有有效地形可以生成,会返回空地图并输出错误日志 + +## 示例配置 + +参考 `SetupExampleTerrainConfig` 方法中的配置,包含了完整的父子关系、概率权重和互斥关系设置。 diff --git a/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp b/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp new file mode 100644 index 0000000..b59463b --- /dev/null +++ b/Source/BusyRabbit/Private/Level/PaperTerrainMapActor.cpp @@ -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(TEXT("RootComponent")); + + // 创建TileMap组件 + TileMapComponent = CreateDefaultSubobject(TEXT("TileMapComponent")); + TileMapComponent->SetupAttachment(RootComponent); + + // 创建地形生成?? + TerrainGenerator = CreateDefaultSubobject(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(this); + NewTileMap->MapWidth = MapWidth; + NewTileMap->MapHeight = MapHeight; + NewTileMap->TileWidth = TileSize; + NewTileMap->TileHeight = TileSize; + + // 添加一个空图层 + UPaperTileLayer* NewLayer = NewObject(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 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(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(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& 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(this); + NewTileMap->MapWidth = MapWidth; + NewTileMap->MapHeight = MapHeight; + NewTileMap->TileWidth = TileSize; + NewTileMap->TileHeight = TileSize; + + // 创建地形图层 + UPaperTileLayer* TerrainLayer = NewObject(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); + } +} diff --git a/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp b/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp new file mode 100644 index 0000000..b012b56 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/TerrainGenerator.cpp @@ -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& Handles){ + if (Handles.Num() <= 1) return; + ExclusiveGroups.Add(TSet(Handles)); +} + +void UTerrainGenerator::BindChildTerrain(int32 ParentHandle, const TArray& 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 UTerrainGenerator::GenerateMap(int32 Width, int32 Height) +{ + TArray Map; + Map.SetNum(Width * Height); + // 获取有效的叶子节点 + TArray 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 UTerrainGenerator::GetAllTerrainNodes() const +{ + return TerrainNodes; +} + +void UTerrainGenerator::GetPreCheckVisibleHandles(TSet& VisibleHandles)const{ + bool bShouldAppear; + const FTerrainNodeInfo* Node; + int32 Handle, TerrainNodeCount; + TArray 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& AppearingNodes) const { + //for (const TArray& Group : ExclusiveGroups) + //{ + // if (Group.Contains(Handle)) + // { + // // 检查互斥组中是否有其他节点已经出现 + // for (int32 OtherHandle : Group) + // { + // if (OtherHandle != Handle && AppearingNodes.Contains(OtherHandle)) + // { + // return false; + // } + // } + // } + //} + return true; +} + +TArray UTerrainGenerator::CalcValidLeafNodes()const{ + TSet ValidNodes; + GetPreCheckVisibleHandles(ValidNodes); + + // 检查互斥关系 + TSet FinalNodes; + for (int32 Handle : ValidNodes){ + if (FinalNodes.Find(Handle)) continue; + if (CheckExclusive(Handle, ValidNodes)) + { + FinalNodes.Add(Handle); + } + } + + // 只保留叶子节点 + TArray LeafNodes; + for (int32 Handle : FinalNodes) + { + const FTerrainNodeInfo* Node = TerrainNodes.Find(Handle); + if (Node && Node->bIsLeafNode) + { + LeafNodes.Add(Handle); + } + } + + return LeafNodes; +} + +void UTerrainGenerator::RegionGrowing(TArray& Map, int32 Width, int32 Height, + const TArray& 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 WeightDistribution; + TArray 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 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 Queue; + Queue.Enqueue(FIntPoint(StartX, StartY)); + Map[StartY * Width + StartX] = TerrainTag; + CurrentSize++; + + while (!Queue.IsEmpty() && CurrentSize < TargetSize) + { + FIntPoint CurrentPoint; + Queue.Dequeue(CurrentPoint); + + // 检查四个方向 + TArray 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& 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 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(); +} diff --git a/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp b/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp new file mode 100644 index 0000000..c6d8057 --- /dev/null +++ b/Source/BusyRabbit/Private/Level/TerrainGeneratorBlueprintLibrary.cpp @@ -0,0 +1,131 @@ +#include "Level/TerrainGeneratorBlueprintLibrary.h" +#include "Level/TerrainGenerator.h" +#include "GameplayTagsManager.h" +#include "Engine/Engine.h" + +UTerrainGenerator* UTerrainGeneratorBlueprintLibrary::CreateTerrainGenerator() +{ + return NewObject(); +} + +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 UTerrainGeneratorBlueprintLibrary::GenerateMap( + UTerrainGenerator* Generator, + int32 Width, + int32 Height) +{ + TArray 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(); +} diff --git a/Source/BusyRabbit/Private/Level/TerrainGeneratorTest.cpp b/Source/BusyRabbit/Private/Level/TerrainGeneratorTest.cpp new file mode 100644 index 0000000..b00039e --- /dev/null +++ b/Source/BusyRabbit/Private/Level/TerrainGeneratorTest.cpp @@ -0,0 +1,122 @@ +#include "Level/TerrainGenerator.h" +#include "GameplayTagsManager.h" +#include "Engine/Engine.h" + +// 测试地形生成器功能 +void TestTerrainGenerator() +{ + // 创建地形生成器实例 + UTerrainGenerator* Generator = NewObject(); + + // 定义地形标签 + 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 Map = Generator->GenerateMap(64, 64); + + // 统计地形分布 + TMap 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) +); diff --git a/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h b/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h new file mode 100644 index 0000000..9062237 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/PaperTerrainMapActor.h @@ -0,0 +1,117 @@ +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "GameplayTagContainer.h" +#include "PaperTerrainMapActor.generated.h" + +// 地形到Tile的映射结? +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: + // 生成地图(可在蓝图中调用? + UFUNCTION(BlueprintCallable, Category = "Terrain Map") + void GenerateTerrainMap(); + + // 清除当前地图 + UFUNCTION(BlueprintCallable, Category = "Terrain Map") + void ClearMap(); + + // 获取地形生成器实? + UFUNCTION(BlueprintCallable, Category = "Terrain Map") + class UTerrainGenerator* GetTerrainGenerator() const; + + // 获取生成的地形数? + UFUNCTION(BlueprintCallable, Category = "Terrain Map") + TArray GetGeneratedTerrainData() const; + +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + +protected: + // Paper2D瓦片地图组件 + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Components") + class UPaperTileMapComponent* TileMapComponent; + + // 地形生成器实? + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") + class UTerrainGenerator* TerrainGenerator; + + // 生成的地形数? + UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = "Terrain") + TArray GeneratedTerrainData; + + // 地图宽度(格子数? + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Map Settings", meta = (ClampMin = "1", ClampMax = "1024")) + int32 MapWidth = 16; + + // 地图高度(格子数? + 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 TerrainTileMappings; + + // 默认Tile索引(用于未映射的地形) + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Terrain Mapping", meta = (ClampMin = "0")) + int32 DefaultTileIndex; + + UPROPERTY(EditAnywhere, Category = Sprite) + TObjectPtr TileSet; + +private: + // 初始化TileMap组件 + void InitializeTileMap(); + + // 应用地形数据到TileMap + void ApplyTerrainToTileMap(const TArray& TerrainData); + + // 根据地形标签获取Tile索引 + int32 GetTileIndexForTerrain(const FGameplayTag& TerrainTag) const; + + // 创建默认地形配置 + void SetupDefaultTerrainConfig(); +}; diff --git a/Source/BusyRabbit/Public/Level/TerrainGenerator.h b/Source/BusyRabbit/Public/Level/TerrainGenerator.h new file mode 100644 index 0000000..d1643b5 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/TerrainGenerator.h @@ -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 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& Handles); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + void BindChildTerrain(int32 ParentHandle, const TArray& ChildHandles); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + TArray GenerateMap(int32 Width = 256, int32 Height = 256); + + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + TMap GetAllTerrainNodes() const; + +private: + TMap TerrainNodes; + + TArray> ExclusiveGroups; + + int32 NextHandle; + + void GetPreCheckVisibleHandles(TSet& VisibleHandles)const; + + bool ShouldNodeAppear(int32 Handle) const; + + bool CheckExclusive(int32 Handle, const TSet& AppearingNodes) const; + + TArray CalcValidLeafNodes()const; + + void RegionGrowing(TArray& Map, int32 Width, int32 Height, + const TArray& ValidLeafNodes) const; + + void ApplyPerlinNoiseSmoothing(TArray& Map, int32 Width, int32 Height) const; + + FGameplayTag GetFinalTerrainTag(int32 Handle) const; +}; diff --git a/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h b/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h new file mode 100644 index 0000000..6fb2360 --- /dev/null +++ b/Source/BusyRabbit/Public/Level/TerrainGeneratorBlueprintLibrary.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "GameplayTagContainer.h" +#include "TerrainGeneratorBlueprintLibrary.generated.h" + +// 蓝图函数库,方便在蓝图中使用地形生成? +UCLASS() +class BUSYRABBIT_API UTerrainGeneratorBlueprintLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + // 创建地形生成器实? + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + static class UTerrainGenerator* CreateTerrainGenerator(); + + // 快速设置示例地形配? + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + static void SetupExampleTerrainConfig(class UTerrainGenerator* Generator); + + // 生成地图并返回一维数组(蓝图不支持嵌套TArray? + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + static TArray 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 + ); + + // 获取地形标签的显示名? + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + static FString GetTerrainDisplayName(const FGameplayTag& TerrainTag); + + // 检查地形标签是否有? + UFUNCTION(BlueprintCallable, Category = "Terrain Generator") + static bool IsValidTerrainTag(const FGameplayTag& TerrainTag); +};