mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 16:47:12 +08:00
592 lines
25 KiB
C#
592 lines
25 KiB
C#
|
|
using UnityEngine;
|
|||
|
|
using Newtonsoft.Json;
|
|||
|
|
using System;
|
|||
|
|
using System.Collections.Generic;
|
|||
|
|
using System.Linq;
|
|||
|
|
using System.Threading.Tasks; // 引入 System.Threading.Tasks
|
|||
|
|
using Managers;
|
|||
|
|
|
|||
|
|
namespace Map
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定义图案中单个瓦片的数据。
|
|||
|
|
/// </summary>
|
|||
|
|
public class PatternTileData
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的X坐标偏移。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("relativeX", Required = Required.Always)]
|
|||
|
|
public int RelativeX { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的Y坐标偏移。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("relativeY", Required = Required.Always)]
|
|||
|
|
public int RelativeY { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 该位置放置的瓦片定义名称。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("tileDefName", Required = Required.Always)]
|
|||
|
|
public string TileDefName { get; set; } = "DefaultBuildingTile";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 定义一个可重复使用的瓦片图案。
|
|||
|
|
/// </summary>
|
|||
|
|
public class PatternDefinition
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 图案的唯一ID。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("patternId", Required = Required.Always)]
|
|||
|
|
public string PatternId { get; set; } = "DefaultPattern";
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构成图案的瓦片列表。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("tiles", Required = Required.Always)]
|
|||
|
|
public List<PatternTileData> Tiles { get; set; } = new List<PatternTileData>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 图案的宽度(从最小到最大X相对坐标的范围)。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int Width { get; private set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 图案的高度(从最小到最大Y相对坐标的范围)。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int Height { get; private set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的最小X坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int MinRelativeX { get; private set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的最大X坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int MaxRelativeX { get; private set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的最小Y坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int MinRelativeY { get; private set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 相对于图案锚点的最大Y坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] public int MaxRelativeY { get; private set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 计算图案的边界和尺寸。应在Init解析后调用一次。
|
|||
|
|
/// </summary>
|
|||
|
|
public void CalculateBounds()
|
|||
|
|
{
|
|||
|
|
if (Tiles == null || Tiles.Count == 0)
|
|||
|
|
{
|
|||
|
|
MinRelativeX = 0;
|
|||
|
|
MaxRelativeX = 0;
|
|||
|
|
Width = 1;
|
|||
|
|
MinRelativeY = 0;
|
|||
|
|
MaxRelativeY = 0;
|
|||
|
|
Height = 1;
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
MinRelativeX = int.MaxValue;
|
|||
|
|
MaxRelativeX = int.MinValue;
|
|||
|
|
MinRelativeY = int.MaxValue;
|
|||
|
|
MaxRelativeY = int.MinValue;
|
|||
|
|
foreach (var tileData in Tiles)
|
|||
|
|
{
|
|||
|
|
MinRelativeX = Mathf.Min(MinRelativeX, tileData.RelativeX);
|
|||
|
|
MaxRelativeX = Mathf.Max(MaxRelativeX, tileData.RelativeX);
|
|||
|
|
MinRelativeY = Mathf.Min(MinRelativeY, tileData.RelativeY);
|
|||
|
|
MaxRelativeY = Mathf.Max(MaxRelativeY, tileData.RelativeY);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Width = MaxRelativeX - MinRelativeX + 1;
|
|||
|
|
Height = MaxRelativeY - MinRelativeY + 1;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 缓存已加载的TileBase对象,避免重复从TileManager获取。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonIgnore] private Dictionary<string, UnityEngine.Tilemaps.TileBase> _tileCache;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 获取或缓存指定定义名称的TileBase。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="defName">瓦片定义名称。</param>
|
|||
|
|
/// <returns>对应的TileBase对象,如果未找到则为null。</returns>
|
|||
|
|
public UnityEngine.Tilemaps.TileBase GetCachedTile(string defName)
|
|||
|
|
{
|
|||
|
|
_tileCache ??= new Dictionary<string, UnityEngine.Tilemaps.TileBase>();
|
|||
|
|
if (!_tileCache.TryGetValue(defName, out var tile))
|
|||
|
|
{
|
|||
|
|
tile = TileManager.Instance.GetTile(defName);
|
|||
|
|
if (tile != null)
|
|||
|
|
{
|
|||
|
|
_tileCache[defName] = tile;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return tile;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 抽象基类,用于定义图案的放置指令。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonConverter(typeof(PlacementInstructionConverter))]
|
|||
|
|
public abstract class PlacementInstruction
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 要放置的图案ID。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("patternId", Required = Required.Always)]
|
|||
|
|
public string PatternId { get; set; } = string.Empty;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// (可选) 必须放置在其上的基础瓦片名称。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("requiredBaseTileDefName")]
|
|||
|
|
public string RequiredBaseTileDefName { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 放置指令的类型字段,用于JsonConverter识别。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("type", Required = Required.Always)]
|
|||
|
|
public string Type { get; protected set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构造函数。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="type">指令类型字符串。</param>
|
|||
|
|
protected PlacementInstruction(string type)
|
|||
|
|
{
|
|||
|
|
Type = type;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 用于反序列化PlacementInstruction及其派生类的JSON转换器。
|
|||
|
|
/// </summary>
|
|||
|
|
public class PlacementInstructionConverter : JsonConverter
|
|||
|
|
{
|
|||
|
|
public override bool CanConvert(Type objectType)
|
|||
|
|
{
|
|||
|
|
return objectType == typeof(PlacementInstruction);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
|
|||
|
|
JsonSerializer serializer)
|
|||
|
|
{
|
|||
|
|
var jsonObject = Newtonsoft.Json.Linq.JObject.Load(reader);
|
|||
|
|
var type = jsonObject["type"]?.ToString();
|
|||
|
|
if (type == null)
|
|||
|
|
{
|
|||
|
|
throw new JsonSerializationException("放置指令JSON中必须包含'type'字段。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
PlacementInstruction instruction;
|
|||
|
|
switch (type.ToLowerInvariant())
|
|||
|
|
{
|
|||
|
|
case "fixed":
|
|||
|
|
instruction = new FixedPlacementInstruction();
|
|||
|
|
break;
|
|||
|
|
case "random":
|
|||
|
|
instruction = new RandomPlacementInstruction();
|
|||
|
|
break;
|
|||
|
|
default:
|
|||
|
|
throw new JsonSerializationException($"未知的放置指令类型: {type}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
serializer.Populate(jsonObject.CreateReader(), instruction);
|
|||
|
|
return instruction;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
|||
|
|
{
|
|||
|
|
serializer.Serialize(writer, value, value?.GetType() ?? typeof(object));
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指定固定位置放置图案的指令。
|
|||
|
|
/// </summary>
|
|||
|
|
public class FixedPlacementInstruction : PlacementInstruction
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 放置的绝对X坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("x", Required = Required.Always)]
|
|||
|
|
public int X { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 放置的绝对Y坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("y", Required = Required.Always)]
|
|||
|
|
public int Y { get; set; }
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构造函数。
|
|||
|
|
/// </summary>
|
|||
|
|
public FixedPlacementInstruction() : base("fixed")
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 指定随机位置放置图案的指令。
|
|||
|
|
/// </summary>
|
|||
|
|
public class RandomPlacementInstruction : PlacementInstruction
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 尝试放置的次数。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("count", Required = Required.Always)]
|
|||
|
|
public int Count { get; set; } = 1;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 随机放置区域的最小X坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("minX")] public int MinX { get; set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 随机放置区域的最大X坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("maxX")] public int MaxX { get; set; } = 99;
|
|||
|
|
/// <summary>
|
|||
|
|
/// 随机放置区域的最小Y坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("minY")] public int MinY { get; set; }
|
|||
|
|
/// <summary>
|
|||
|
|
/// 随机放置区域的最大Y坐标。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("maxY")] public int MaxY { get; set; } = 99;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 为每个图案尝试放置的最大次数,防止无限循环。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("maxAttemptsPerPattern")]
|
|||
|
|
public int MaxAttemptsPerPattern { get; set; } = 20;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 构造函数。
|
|||
|
|
/// </summary>
|
|||
|
|
public RandomPlacementInstruction() : base("random")
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 建筑/图案生成器的配置类。
|
|||
|
|
/// </summary>
|
|||
|
|
public class BuildingPatternGeneratorConfig
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 所有预定义的图案列表。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("patterns", Required = Required.Always)]
|
|||
|
|
public List<PatternDefinition> Patterns { get; set; } = new List<PatternDefinition>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 放置图案的指令列表。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("placementInstructions", Required = Required.Always)]
|
|||
|
|
public List<PlacementInstruction> PlacementInstructions { get; set; } =
|
|||
|
|
new List<PlacementInstruction>();
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 是否防止图案互相重叠。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("preventOverlap")] public bool PreventOverlap { get; set; } = true;
|
|||
|
|
/// <summary>
|
|||
|
|
/// 地图生成区域的宽度(单元格数量)。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("mapCellSizeX")] public int MapCellSizeX { get; set; } = 100;
|
|||
|
|
/// <summary>
|
|||
|
|
/// 地图生成区域的高度(单元格数量)。
|
|||
|
|
/// </summary>
|
|||
|
|
[JsonProperty("mapCellSizeY")] public int MapCellSizeY { get; set; } = 100;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 负责根据JSON配置生成建筑和复杂图案的地图生成器。
|
|||
|
|
/// </summary>
|
|||
|
|
public class BuildingPatternMapGenerator : MapGeneratorWorkClassBase
|
|||
|
|
{
|
|||
|
|
/// <summary>
|
|||
|
|
/// 建筑/图案生成器的配置。
|
|||
|
|
/// </summary>
|
|||
|
|
private BuildingPatternGeneratorConfig _config;
|
|||
|
|
/// <summary>
|
|||
|
|
/// 存储图案ID到PatternDefinition的查找表,用于快速访问。
|
|||
|
|
/// </summary>
|
|||
|
|
private Dictionary<string, PatternDefinition> _patternLookup;
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 初始化建筑/图案生成器,解析JSON配置字符串。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="value">包含生成参数的JSON字符串。</param>
|
|||
|
|
public override void Init(string value)
|
|||
|
|
{
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
_config = JsonConvert.DeserializeObject<BuildingPatternGeneratorConfig>(value);
|
|||
|
|
|
|||
|
|
if (_config == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
"建筑图案地图生成器:配置反序列化为空。请检查JSON格式。中止初始化。");
|
|||
|
|
_config = new BuildingPatternGeneratorConfig();
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_patternLookup = new Dictionary<string, PatternDefinition>();
|
|||
|
|
foreach (var pattern in _config.Patterns)
|
|||
|
|
{
|
|||
|
|
if (string.IsNullOrWhiteSpace(pattern.PatternId))
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
"建筑图案地图生成器:图案定义中存在空的图案ID。已跳过。");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_patternLookup.ContainsKey(pattern.PatternId))
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
$"建筑图案地图生成器:发现重复的图案ID '{pattern.PatternId}'。将覆盖现有定义。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pattern.CalculateBounds();
|
|||
|
|
_patternLookup[pattern.PatternId] = pattern;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (_config.MapCellSizeX <= 0) _config.MapCellSizeX = 100;
|
|||
|
|
if (_config.MapCellSizeY <= 0) _config.MapCellSizeY = 100;
|
|||
|
|
}
|
|||
|
|
catch (JsonSerializationException ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
$"建筑图案地图生成器:JSON反序列化错误:{ex.Message}。输入JSON: '{value}'");
|
|||
|
|
_config = null;
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
$"建筑图案地图生成器:初始化过程中发生未预期错误:{ex.Message}。输入JSON: '{value}'");
|
|||
|
|
_config = null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 根据配置参数在 `MapGenerator` 的 `buildingTilemap` 上生成建筑和图案。
|
|||
|
|
/// 此方法现在是异步的,以避免阻塞主线程。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="map">MapGenerator实例,包含要操作的Tilemap。</param>
|
|||
|
|
public override async Task Process(MapGenerator map) // 标记为 async Task
|
|||
|
|
{
|
|||
|
|
if (_config == null || _patternLookup == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
"建筑图案地图生成器:在初始化之前或初始化失败后调用Process。中止生成。");
|
|||
|
|
return; // 返回一个已完成的Task
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (map == null || map.buildingTilemap == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
"建筑图案地图生成器:MapGenerator或建筑瓦片地图为空。无法生成建筑。中止。");
|
|||
|
|
return; // 返回一个已完成的Task
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (map.baseTilemap == null &&
|
|||
|
|
_config.PlacementInstructions.Any(p => !string.IsNullOrEmpty(p.RequiredBaseTileDefName)))
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
"建筑图案地图生成器:某些放置指令需要基础瓦片,但基础瓦片地图为空。基础瓦片要求将被忽略或导致错误。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 迭代每个放置指令
|
|||
|
|
foreach (var instruction in _config.PlacementInstructions)
|
|||
|
|
{
|
|||
|
|
if (!_patternLookup.TryGetValue(instruction.PatternId, out var patternDef))
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
$"建筑图案地图生成器:在已定义的图案中找不到图案ID '{instruction.PatternId}'。跳过该指令。");
|
|||
|
|
continue;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (instruction is FixedPlacementInstruction fixedInstruction)
|
|||
|
|
{
|
|||
|
|
// 固定放置通常很快,不需要 await Task.Yield()
|
|||
|
|
PlacePattern(map, patternDef, fixedInstruction.X, fixedInstruction.Y,
|
|||
|
|
instruction.RequiredBaseTileDefName, _config.PreventOverlap);
|
|||
|
|
}
|
|||
|
|
else if (instruction is RandomPlacementInstruction randomInstruction)
|
|||
|
|
{
|
|||
|
|
// 随机放置可能会尝试多次,因此内部添加 await Task.Yield()
|
|||
|
|
// 标记了 private async Task,并await它。
|
|||
|
|
await AttemptRandomPlacement(map, patternDef, randomInstruction, instruction.RequiredBaseTileDefName,
|
|||
|
|
_config.PreventOverlap);
|
|||
|
|
}
|
|||
|
|
// 每处理一个指令后,暂停一下,以防有大量指令
|
|||
|
|
await Task.Yield();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 尝试在地图上随机位置放置一个图案。此方法现在是异步的,以避免阻塞主线程。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="map">MapGenerator实例。</param>
|
|||
|
|
/// <param name="patternDef">要放置的图案定义。</param>
|
|||
|
|
/// <param name="instruction">随机放置指令。</param>
|
|||
|
|
/// <param name="requiredBaseTileDefName">所需的基础瓦片定义名称。</param>
|
|||
|
|
/// <param name="preventOverlap">是否防止重叠。</param>
|
|||
|
|
private async Task AttemptRandomPlacement(MapGenerator map, PatternDefinition patternDef, // 标记为 async Task
|
|||
|
|
RandomPlacementInstruction instruction, string requiredBaseTileDefName, bool preventOverlap)
|
|||
|
|
{
|
|||
|
|
for (var attempt = 0; attempt < instruction.MaxAttemptsPerPattern; attempt++)
|
|||
|
|
{
|
|||
|
|
// 确保随机放置的范围在地图尺寸内且能容纳图案
|
|||
|
|
// 注意:`randomX`, `randomY`在此逻辑中被计算为图案的“左上角”世界坐标,而不是锚点(0,0)的世界坐标。
|
|||
|
|
var rangeMinX = Mathf.Max(instruction.MinX, 0);
|
|||
|
|
var rangeMaxX =
|
|||
|
|
Mathf.Min(instruction.MaxX,
|
|||
|
|
_config!.MapCellSizeX - patternDef.Width); // 确保图案水平方向能放下
|
|||
|
|
var rangeMinY = Mathf.Max(instruction.MinY, 0);
|
|||
|
|
var rangeMaxY =
|
|||
|
|
Mathf.Min(instruction.MaxY,
|
|||
|
|
_config!.MapCellSizeY - patternDef.Height); // 确保图案垂直方向能放下
|
|||
|
|
|
|||
|
|
if (rangeMinX > rangeMaxX || rangeMinY > rangeMaxY)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
$"建筑图案地图生成器:图案 '{patternDef.PatternId}' 的随机放置范围 ({instruction.MinX}-{instruction.MaxX}, {instruction.MinY}-{instruction.MaxY}) 无效或小于图案尺寸 ({patternDef.Width}x{patternDef.Height})。跳过随机放置尝试。");
|
|||
|
|
return; // 返回一个已完成的Task
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var randomX =
|
|||
|
|
UnityEngine.Random.Range(rangeMinX,
|
|||
|
|
rangeMaxX + 1); // +1 因为Range(int, int)对于max是排他性的
|
|||
|
|
var randomY = UnityEngine.Random.Range(rangeMinY, rangeMaxY + 1);
|
|||
|
|
|
|||
|
|
// 根据现有逻辑,`CanPlacePatternAt`和`PlacePattern`的`worldAnchorX, worldAnchorY`参数需要是图案(0,0)相对点的世界坐标。
|
|||
|
|
// 而这里计算的`randomX, randomY`是图案边界的左上角。
|
|||
|
|
// 故将`randomX + patternDef.MinRelativeX`和`randomY + patternDef.MinRelativeY`作为传递给方法的锚点。
|
|||
|
|
if (CanPlacePatternAt(map, patternDef, randomX + patternDef.MinRelativeX,
|
|||
|
|
randomY + patternDef.MinRelativeY, requiredBaseTileDefName, preventOverlap))
|
|||
|
|
{
|
|||
|
|
PlacePattern(map, patternDef, randomX + patternDef.MinRelativeX, randomY + patternDef.MinRelativeY, requiredBaseTileDefName, preventOverlap);
|
|||
|
|
return; // 放置成功,退出尝试循环,返回一个已完成的Task
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 在每次尝试后,暂停一下,避免一个随机放置尝试次数过多导致阻塞
|
|||
|
|
await Task.Yield();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
$"建筑图案地图生成器:在尝试 {instruction.MaxAttemptsPerPattern} 次后,未能随机放置图案 '{patternDef.PatternId}'。");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 检查一个图案是否可以在给定位置放置,同时满足基础瓦片和重叠要求。
|
|||
|
|
/// 这个方法执行的是快速检查,不需要异步。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="map">MapGenerator实例。</param>
|
|||
|
|
/// <param name="patternDef">要放置的图案定义。</param>
|
|||
|
|
/// <param name="worldAnchorX">图案锚点(相对坐标0,0)的世界X坐标。</param>
|
|||
|
|
/// <param name="worldAnchorY">图案锚点(相对坐标0,0)的世界Y坐标。</param>
|
|||
|
|
/// <param name="requiredBaseTileDefName">所需的基础瓦片定义名称。</param>
|
|||
|
|
/// <param name="preventOverlap">是否防止重叠。</param>
|
|||
|
|
/// <returns>如果可以放置则为true,否则为false。</returns>
|
|||
|
|
private bool CanPlacePatternAt(MapGenerator map, PatternDefinition patternDef, int worldAnchorX,
|
|||
|
|
int worldAnchorY, string requiredBaseTileDefName, bool preventOverlap)
|
|||
|
|
{
|
|||
|
|
foreach (var tileData in patternDef.Tiles)
|
|||
|
|
{
|
|||
|
|
var currentWorldX = worldAnchorX + tileData.RelativeX;
|
|||
|
|
var currentWorldY = worldAnchorY + tileData.RelativeY;
|
|||
|
|
var tilePosition = new Vector3Int(currentWorldX, currentWorldY, 0);
|
|||
|
|
|
|||
|
|
if (currentWorldX < 0 || currentWorldX >= _config!.MapCellSizeX ||
|
|||
|
|
currentWorldY < 0 || currentWorldY >= _config.MapCellSizeY)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (preventOverlap)
|
|||
|
|
{
|
|||
|
|
var existingBuildingTile = map.buildingTilemap.GetTile(tilePosition);
|
|||
|
|
if (existingBuildingTile != null)
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!string.IsNullOrEmpty(requiredBaseTileDefName))
|
|||
|
|
{
|
|||
|
|
if (map.baseTilemap == null)
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
$"建筑图案地图生成器:已配置基础瓦片名称 ('{requiredBaseTileDefName}') 但基础瓦片地图为空。将忽略基础瓦片要求。");
|
|||
|
|
continue; // 忽略此瓦片的基础检查,继续其他检查
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var baseTile = map.baseTilemap.GetTile(tilePosition);
|
|||
|
|
// 注意:GetTile().name 可能不是最优方式,推荐使用 ScriptableObject 的引用比较或唯一ID
|
|||
|
|
// 但如果 requiredBaseTileDefName 确实是 GetTile().name,则保持此逻辑。
|
|||
|
|
if (baseTile == null || baseTile.name != TileManager.Instance.GetTile(requiredBaseTileDefName)?.name) // 比较名称
|
|||
|
|
{
|
|||
|
|
// 考虑缓存 requiredBaseTile 的引用以防止每次循环都查找
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// <summary>
|
|||
|
|
/// 实际在地图上放置一个图案。该方法假设CanPlacePatternAt已确认可以放置。
|
|||
|
|
/// 这个方法执行的是快速设置瓦片的操作,不需要异步。
|
|||
|
|
/// </summary>
|
|||
|
|
/// <param name="map">MapGenerator实例。</param>
|
|||
|
|
/// <param name="patternDef">要放置的图案定义。</param>
|
|||
|
|
/// <param name="worldAnchorX">图案锚点(相对坐标0,0)的世界X坐标。</param>
|
|||
|
|
/// <param name="worldAnchorY">图案锚点(相对坐标0,0)的世界Y坐标。</param>
|
|||
|
|
/// <param name="requiredBaseTileDefName">所需的基础瓦片定义名称。</param>
|
|||
|
|
/// <param name="preventOverlap">是否防止重叠。</param>
|
|||
|
|
private void PlacePattern(MapGenerator map, PatternDefinition patternDef, int worldAnchorX, int worldAnchorY,
|
|||
|
|
string requiredBaseTileDefName, bool preventOverlap)
|
|||
|
|
{
|
|||
|
|
// 再次检查放置可行性,确保在多线程或异步上下文中没有竞态条件导致的状态变更
|
|||
|
|
// 但如果 PlacePattern 立即在 CanPlacePatternAt 之后调用,且都在主线程,这个检查主要用于防御性编程。
|
|||
|
|
if (!CanPlacePatternAt(map, patternDef, worldAnchorX, worldAnchorY, requiredBaseTileDefName,
|
|||
|
|
preventOverlap))
|
|||
|
|
{
|
|||
|
|
Debug.LogWarning(
|
|||
|
|
$"建筑图案地图生成器:尝试在 ({worldAnchorX},{worldAnchorY}) 放置图案 '{patternDef.PatternId}' 失败,原因是非法放置。此情况本不应发生,请检查放置逻辑或异步执行中的竞态条件。");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
foreach (var tileData in patternDef.Tiles)
|
|||
|
|
{
|
|||
|
|
var currentWorldX = worldAnchorX + tileData.RelativeX;
|
|||
|
|
var currentWorldY = worldAnchorY + tileData.RelativeY;
|
|||
|
|
var tilePosition = new Vector3Int(currentWorldX, currentWorldY, 0);
|
|||
|
|
|
|||
|
|
var tileToPlace = patternDef.GetCachedTile(tileData.TileDefName);
|
|||
|
|
if (tileToPlace != null)
|
|||
|
|
{
|
|||
|
|
map.buildingTilemap.SetTile(tilePosition, tileToPlace);
|
|||
|
|
}
|
|||
|
|
else
|
|||
|
|
{
|
|||
|
|
Debug.LogError(
|
|||
|
|
$"建筑图案地图生成器:未能获取定义名称为 '{tileData.TileDefName}' 的瓦片,用于图案 '{patternDef.PatternId}' 在 ({currentWorldX},{currentWorldY}) 处的放置。");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|