Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Map/BuildingMapGenerator.cs

329 lines
15 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.

using UnityEngine;
using UnityEngine.Tilemaps;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Threading.Tasks;
using Data;
using Managers;
namespace Map
{
public class BuildingGeneratorConfig
{
[JsonProperty("defName")] public string DefName { get; set; } // 图案定义名称
[JsonProperty("mapping")] public Dictionary<int, string> Mapping; // 图案值到定义名称的映射
[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)是高度
}
else
{
// 如果没有图案,默认为单格建筑
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, searchRadius: 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 Vector2Int(0, 1), // 上
new Vector2Int(0, -1), // 下
new Vector2Int(1, 0), // 右
new Vector2Int(-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; // 未找到合适位置
}
}
}