mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 06:47:14 +08:00
Co-authored-by: m0_75251201 <m0_75251201@noreply.gitcode.com> Reviewed-on: http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite/pulls/60
303 lines
14 KiB
C#
303 lines
14 KiB
C#
using System.Collections.Generic;
|
||
using System.Threading.Tasks;
|
||
using Data;
|
||
using Managers;
|
||
using Newtonsoft.Json;
|
||
using UnityEngine;
|
||
using UnityEngine.Tilemaps;
|
||
|
||
namespace Map
|
||
{
|
||
public class BuildingGeneratorConfig
|
||
{
|
||
[JsonProperty("mapping")] public Dictionary<int, string> Mapping; // 图案值到定义名称的映射
|
||
[JsonProperty("defName")] public string DefName { get; set; } // 图案定义名称
|
||
[JsonProperty("pattern")] public int[,] Pattern { get; set; } // 建筑图案
|
||
[JsonProperty("baseTileDefName")] public string BaseTileDefName { get; set; } // 基底所需瓦片的定义名称
|
||
[JsonProperty("autoExpand")] public bool AutoExpand { get; set; } // 是否自动扩展搜索区域
|
||
|
||
[JsonProperty("positionX", Required = Required.Always)]
|
||
public int PositionX { get; set; } // 起始位置X坐标
|
||
|
||
[JsonProperty("positionY", Required = Required.Always)]
|
||
public int PositionY { get; set; } // 起始位置Y坐标
|
||
}
|
||
|
||
public class BuildingMapGenerator : MapGeneratorWorkClassBase
|
||
{
|
||
private BuildingGeneratorConfig _config;
|
||
private string _requiredBaseTileName; // 缓存基底瓦片TileBase的运行时名称
|
||
|
||
/// <summary>
|
||
/// 初始化建筑生成器,解析JSON配置。
|
||
/// </summary>
|
||
/// <param name="value">包含BuildingGeneratorConfig的JSON字符串。</param>
|
||
public override void Init(string value)
|
||
{
|
||
if (string.IsNullOrEmpty(value))
|
||
{
|
||
Debug.LogError("建筑生成器: Init接收到空或null的JSON值。");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
_config = JsonConvert.DeserializeObject<BuildingGeneratorConfig>(value);
|
||
if (_config == null)
|
||
{
|
||
Debug.LogError("建筑生成器: 无法从JSON反序列化BuildingGeneratorConfig。");
|
||
return;
|
||
}
|
||
|
||
// 预加载基底瓦片定义
|
||
if (string.IsNullOrEmpty(_config.BaseTileDefName)) return;
|
||
var baseAsset = TileManager.Instance.GetTile(_config.BaseTileDefName);
|
||
if (baseAsset != null)
|
||
_requiredBaseTileName = baseAsset.name; // 缓存TileBase的运行时名称
|
||
else
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 通过TileManager未找到定义'{_config.BaseTileDefName}'的基底瓦片资产。");
|
||
}
|
||
catch (JsonException ex)
|
||
{
|
||
Debug.LogError($"建筑生成器: JSON反序列化错误: {ex.Message}\nJSON: {value}");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取建筑生成器将影响的区域大小。
|
||
/// </summary>
|
||
/// <returns>表示区域宽和高的Vector2Int。</returns>
|
||
public override Vector2Int GetSize()
|
||
{
|
||
if (_config == null) return Vector2Int.zero;
|
||
|
||
if (_config.Pattern != null)
|
||
return new Vector2Int(_config.Pattern.GetLength(1),
|
||
_config.Pattern.GetLength(0)); // GetLength(1)是宽度,GetLength(0)是高度
|
||
|
||
// 如果没有图案,默认为单格建筑
|
||
return new Vector2Int(1, 1);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行建筑生成逻辑。
|
||
/// </summary>
|
||
/// <param name="landform">MapGenerator实例,提供Tilemap和坐标转换功能。</param>
|
||
public override async Task Process(Landform landform)
|
||
{
|
||
if (_config == null)
|
||
// 错误已在Init中记录,或者Init未被调用。
|
||
return;
|
||
if (landform == null || landform.buildingTilemap == null || landform.baseTilemap == null)
|
||
{
|
||
Debug.LogError("建筑生成器: MapGenerator或其Tilemap未赋值。");
|
||
return;
|
||
}
|
||
|
||
var patternSize = GetSize();
|
||
if (patternSize == Vector2Int.zero)
|
||
{
|
||
Debug.LogWarning("建筑生成器: 图案大小为零,未生成任何内容。");
|
||
return;
|
||
}
|
||
|
||
var actualStartPosition = new Vector2Int(_config.PositionX, _config.PositionY);
|
||
// 检查是否有要求基底瓦片,并且已经成功获取到基底瓦片的运行时名称
|
||
if (!string.IsNullOrEmpty(_config.BaseTileDefName) && !string.IsNullOrEmpty(_requiredBaseTileName))
|
||
{
|
||
// 检查当前指定位置是否满足基底条件
|
||
var baseSatisfied = CheckBaseTileCondition(landform.baseTilemap, actualStartPosition, patternSize,
|
||
_requiredBaseTileName);
|
||
if (!baseSatisfied && _config.AutoExpand)
|
||
{
|
||
// 如果不满足且允许自动扩展,则搜索新的有效起始位置
|
||
var foundPos = FindSuitableStartPosition(landform.baseTilemap, actualStartPosition, patternSize,
|
||
_requiredBaseTileName, 5); // 搜索半径可配置
|
||
if (foundPos.HasValue)
|
||
{
|
||
actualStartPosition = foundPos.Value;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 无法为'{_config.DefName ?? "图案"}'在基底'{_requiredBaseTileName}'上找到合适的基底瓦片位置,已进行自动扩展。");
|
||
return; // 无法找到合适位置,跳过生成
|
||
}
|
||
}
|
||
else if (!baseSatisfied && !_config.AutoExpand)
|
||
{
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 基底瓦片条件'{_requiredBaseTileName}'在({actualStartPosition.x}, {actualStartPosition.y})处不满足,且autoExpand为false。跳过生成。");
|
||
return; // 不满足且不允许扩展,跳过生成
|
||
}
|
||
}
|
||
else if (!string.IsNullOrEmpty(_config.BaseTileDefName) && string.IsNullOrEmpty(_requiredBaseTileName))
|
||
{
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 已指定基底瓦片定义('{_config.BaseTileDefName}'),但通过TileManager未找到其资产。跳过基底条件检查。");
|
||
}
|
||
|
||
// 遍历图案并生成建筑
|
||
if (_config.Pattern != null)
|
||
{
|
||
for (var y = 0; y < patternSize.y; y++) // 行 (height)
|
||
for (var x = 0; x < patternSize.x; x++) // 列 (width)
|
||
{
|
||
var patternValue = _config.Pattern[y, x];
|
||
if (patternValue == 0) // 0通常表示空,不生成
|
||
continue;
|
||
|
||
if (_config.Mapping == null || !_config.Mapping.TryGetValue(patternValue, out var defName))
|
||
{
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 图案值{patternValue}在相对位置({x},{y})没有映射。跳过。");
|
||
continue;
|
||
}
|
||
|
||
// 计算当前建筑单元的世界/网格坐标
|
||
var currentGridPos =
|
||
new Vector3Int(actualStartPosition.x + x, actualStartPosition.y + y,
|
||
0); // 网格坐标与 position(X, Y) 对应
|
||
var currentWorldPos = landform.GetWorldCoordinates(currentGridPos);
|
||
await GenerateIndividualBuilding(landform, defName, currentGridPos, currentWorldPos);
|
||
}
|
||
}
|
||
else if (!string.IsNullOrEmpty(_config.DefName))
|
||
{
|
||
// 如果没有图案,生成单个建筑
|
||
var currentGridPos = new Vector3Int(actualStartPosition.x, actualStartPosition.y, 0);
|
||
var currentWorldPos = landform.GetWorldCoordinates(currentGridPos);
|
||
await GenerateIndividualBuilding(landform, _config.DefName, currentGridPos, currentWorldPos);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning("建筑生成器: 配置中未指定图案或DefName。未生成任何内容。");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成单个建筑(瓦片或实体)。
|
||
/// </summary>
|
||
/// <param name="landform">MapGenerator实例。</param>
|
||
/// <param name="defName">建筑定义名称。</param>
|
||
/// <param name="gridPos">建筑的网格位置。</param>
|
||
/// <param name="worldPos">建筑的世界位置。</param>
|
||
private async Task GenerateIndividualBuilding(Landform landform, string defName, Vector3Int gridPos,
|
||
Vector3 worldPos)
|
||
{
|
||
var buildingDef = DefineManager.Instance.FindDefine<BuildingDef>(defName);
|
||
if (buildingDef == null)
|
||
{
|
||
Debug.LogWarning($"建筑生成器: 未找到建筑定义'{defName}'。");
|
||
return;
|
||
}
|
||
|
||
if (buildingDef.buildingType == BuildingType.Static)
|
||
{
|
||
if (buildingDef.tile != null && !string.IsNullOrEmpty(buildingDef.tile.defName))
|
||
{
|
||
var tileToPlace = TileManager.Instance.GetTile(buildingDef.tile.defName);
|
||
if (tileToPlace != null)
|
||
landform.buildingTilemap.SetTile(gridPos, tileToPlace);
|
||
// Debug.Log($"建筑生成器: 在网格位置{gridPos}放置了静态建筑'{defName}'('{buildingDef.tile.defName}')。");
|
||
else
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 未找到静态建筑'{defName}'的瓦片资产('{buildingDef.tile.defName}')。跳过。");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning(
|
||
$"建筑生成器: 静态建筑'{defName}'没有有效的TileDef或其defName为空。跳过。");
|
||
}
|
||
}
|
||
else if (buildingDef.buildingType == BuildingType.Dynamic)
|
||
{
|
||
EntityManager.Instance.GenerateBuildingEntity(landform.GetDimensionID(), buildingDef, worldPos);
|
||
// Debug.Log($"建筑生成器: 在世界位置{worldPos}生成了动态建筑实体'{defName}'。");
|
||
}
|
||
|
||
await Task.Yield(); // 异步操作,避免阻塞
|
||
}
|
||
|
||
/// <summary>
|
||
/// 检查给定区域的基底瓦片条件是否满足。
|
||
/// </summary>
|
||
/// <param name="baseTilemap">基底瓦片地图。</param>
|
||
/// <param name="startPos">区域的起始网格位置 (X, Y)。</param>
|
||
/// <param name="size">区域的尺寸 (宽, 高)。</param>
|
||
/// <param name="requiredTileBaseName">要求的基底瓦片运行时名称。</param>
|
||
/// <returns>如果整个区域都满足基底瓦片条件,则返回true。</returns>
|
||
private bool CheckBaseTileCondition(Tilemap baseTilemap, Vector2Int startPos, Vector2Int size,
|
||
string requiredTileBaseName)
|
||
{
|
||
if (string.IsNullOrEmpty(requiredTileBaseName))
|
||
{
|
||
Debug.LogWarning(
|
||
"建筑生成器: 所需基底瓦片资产名称为空。基底条件检查已跳过或失败。");
|
||
return false;
|
||
}
|
||
|
||
for (var y = 0; y < size.y; y++)
|
||
for (var x = 0; x < size.x; x++)
|
||
{
|
||
var currentCheckPos = new Vector3Int(startPos.x + x, startPos.y + y, 0);
|
||
var tileBase = baseTilemap.GetTile(currentCheckPos);
|
||
if (tileBase == null || !tileBase.name.Equals(requiredTileBaseName)) return false; // 任何一个位置不满足则整个区域不满足
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 在给定范围内搜索适合放置建筑的起始位置。
|
||
/// 使用广度优先搜索 (BFS) 算法。
|
||
/// </summary>
|
||
/// <param name="baseTilemap">基底瓦片地图。</param>
|
||
/// <param name="originalStartPos">最初指定的起始位置。</param>
|
||
/// <param name="patternSize">建筑图案的尺寸。</param>
|
||
/// <param name="requiredTileBaseName">要求的基底瓦片运行时名称。</param>
|
||
/// <param name="searchRadius">搜索的最大半径(从原始起始位置的曼哈顿距离)。</param>
|
||
/// <returns>找到的第一个适合的起始位置,如果没有找到则为 null。</returns>
|
||
private Vector2Int? FindSuitableStartPosition(Tilemap baseTilemap, Vector2Int originalStartPos,
|
||
Vector2Int patternSize, string requiredTileBaseName, int searchRadius)
|
||
{
|
||
var queue = new Queue<Vector2Int>();
|
||
var visited = new HashSet<Vector2Int>();
|
||
queue.Enqueue(originalStartPos);
|
||
visited.Add(originalStartPos);
|
||
while (queue.Count > 0)
|
||
{
|
||
var currentPos = queue.Dequeue();
|
||
// 检查当前位置是否满足条件
|
||
if (CheckBaseTileCondition(baseTilemap, currentPos, patternSize, requiredTileBaseName))
|
||
return currentPos;
|
||
|
||
// 探索邻近位置
|
||
Vector2Int[] directions =
|
||
{
|
||
new(0, 1), // 上
|
||
new(0, -1), // 下
|
||
new(1, 0), // 右
|
||
new(-1, 0) // 左
|
||
};
|
||
foreach (var dir in directions)
|
||
{
|
||
var nextPos = new Vector2Int(currentPos.x + dir.x, currentPos.y + dir.y);
|
||
// 检查是否在搜索半径内 (曼哈顿距离)
|
||
if (Mathf.Abs(nextPos.x - originalStartPos.x) + Mathf.Abs(nextPos.y - originalStartPos.y) >
|
||
searchRadius)
|
||
continue; // 超出搜索范围
|
||
|
||
if (!visited.Contains(nextPos))
|
||
{
|
||
visited.Add(nextPos);
|
||
queue.Enqueue(nextPos);
|
||
}
|
||
}
|
||
}
|
||
|
||
return null; // 未找到合适位置
|
||
}
|
||
}
|
||
} |