mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 03:17:12 +08:00
(client) feat:支持定义实体的碰撞体大小和偏移;建筑支持定义实体建筑和瓦片建筑,建筑支持指定按钮回调;添加存档管理器;Dev支持设置是否暂停;实体允许定义事件组;添加基地界面 (#57)
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/57
This commit is contained in:
@@ -2,12 +2,19 @@ using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Threading.Tasks; // 引入 System.Threading.Tasks
|
||||
using System.Threading.Tasks;
|
||||
using Managers;
|
||||
using Utils;
|
||||
|
||||
namespace Map
|
||||
{
|
||||
public enum MapDimension
|
||||
{
|
||||
Base = 0, // 对应 Landform.baseTilemap
|
||||
Building = 1, // 对应 Landform.buildingTilemap
|
||||
Plant = 2 // 对应 Landform.plantTilemap
|
||||
}
|
||||
|
||||
public class BasicTerrainGeneratorConfig
|
||||
{
|
||||
[JsonProperty("tileDefName", Required = Required.Always)]
|
||||
@@ -15,25 +22,30 @@ namespace Map
|
||||
|
||||
// 将 Threshold 改为 MinThreshold,并保持旧的JsonProperty名称以兼容旧JSON
|
||||
[JsonProperty("threshold")]
|
||||
public double MinThreshold { get; set; } = 0.0; // 柏林噪声的最小阈值。0到1之间。将其默认值改为0.0以适应-1到1的噪声范围,并提供更灵活的区间开始。
|
||||
public double MinThreshold { get; set; } // 柏林噪声的最小阈值。-1到1之间。将其默认值改为0.0以适应-1到1的噪声范围,并提供更灵活的区间开始。
|
||||
|
||||
[JsonProperty("maxThreshold")]
|
||||
public double MaxThreshold { get; set; } = 1.0; // 柏林噪声的最大阈值。0到1之间。默认为1.0以兼容旧配置。
|
||||
[JsonProperty("maxThreshold")] public double MaxThreshold { get; set; } = 1.0; // 柏林噪声的最大阈值。-1到1之间。默认为1.0以兼容旧配置。
|
||||
|
||||
[JsonProperty("elseTileDefName")]
|
||||
public string ElseTileDefName { get; set; } = null; // 不满足Min/MaxThreshold范围时放置的瓦片定义名称,默认为null表示不放置。
|
||||
public string ElseTileDefName { get; set; } // 不满足Min/MaxThreshold范围时放置的瓦片定义名称,默认为null表示不放置。
|
||||
|
||||
[JsonProperty("scale")] public double Scale { get; set; } = 0.1; // 柏林噪声的缩放因子,控制地形特征的大小
|
||||
[JsonProperty("offsetX")] public double OffsetX { get; set; } = 0.0; // 柏林噪声的X轴偏移
|
||||
[JsonProperty("offsetY")] public double OffsetY { get; set; } = 0.0; // 柏林噪声的Y轴偏移
|
||||
[JsonProperty("offsetX")] public double OffsetX { get; set; } // 柏林噪声的X轴偏移
|
||||
[JsonProperty("offsetY")] public double OffsetY { get; set; } // 柏林噪声的Y轴偏移
|
||||
[JsonProperty("mapCellSizeX")] public int MapCellSizeX { get; set; } = 100; // 地图生成区域的宽度(单元格数量)
|
||||
[JsonProperty("mapCellSizeY")] public int MapCellSizeY { get; set; } = 100; // 地图生成区域的高度(单元格数量)
|
||||
|
||||
[JsonProperty("targetDimension")] public MapDimension TargetDimension { get; set; } = MapDimension.Base;
|
||||
|
||||
// 新增:地图生成区域的起始X坐标(单元格数量)
|
||||
[JsonProperty("originX")] public int OriginX { get; set; }
|
||||
|
||||
// 新增:地图生成区域的起始Y坐标(单元格数量)
|
||||
[JsonProperty("originY")] public int OriginY { get; set; }
|
||||
}
|
||||
|
||||
|
||||
// BasicTerrainGeneratorConfig 应该在同一个文件或者它自己的文件中定义,
|
||||
// 这里为了清晰展示修改,假定它在 Map 命名空间下可用。
|
||||
|
||||
// MapDimension 枚举在此文件中不重复定义,因为它通常位于自己的文件中或 Map 命名空间的其他通用文件中。
|
||||
// 如果它与 BasicTerrainGeneratorConfig 在同一个文件,则在 BasicTerrainGeneratorConfig 定义后。
|
||||
public class BasicTerrainMapGenerator : MapGeneratorWorkClassBase
|
||||
{
|
||||
private BasicTerrainGeneratorConfig _config;
|
||||
@@ -47,57 +59,52 @@ namespace Map
|
||||
try
|
||||
{
|
||||
_config = JsonConvert.DeserializeObject<BasicTerrainGeneratorConfig>(value);
|
||||
|
||||
// 参数有效性检查
|
||||
// _config 不是单例,所以需要进行空检查。
|
||||
if (_config == null)
|
||||
{
|
||||
Debug.LogError("BasicTerrainMapGenerator: Configuration deserialized to null. Check JSON format.");
|
||||
_config = new BasicTerrainGeneratorConfig(); // 提供默认配置以防崩溃
|
||||
Debug.LogError("基本地形生成器: 配置反序列化为空。请检查JSON格式。");
|
||||
_config = new BasicTerrainGeneratorConfig();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_config.TileDefName))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"BasicTerrainMapGenerator: TileDefName is empty. Using default 'DefaultTerrainTile'.");
|
||||
Debug.LogWarning("基本地形生成器: 瓦片定义名称为空。使用默认值 'DefaultTerrainTile'。");
|
||||
_config.TileDefName = "DefaultTerrainTile";
|
||||
}
|
||||
|
||||
if (_config.Scale <= 0)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"BasicTerrainMapGenerator: Scale must be positive. Setting to default 0.1. Current: {_config.Scale}");
|
||||
Debug.LogWarning($"基本地形生成器: 缩放值必须为正。设置为默认值 0.1。当前值: {_config.Scale}");
|
||||
_config.Scale = 0.1;
|
||||
}
|
||||
|
||||
if (_config.MapCellSizeX <= 0 || _config.MapCellSizeY <= 0)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"BasicTerrainMapGenerator: MapCellSizeX or MapCellSizeY is zero or negative. Setting to default (100, 100). Current: ({_config.MapCellSizeX}, {_config.MapCellSizeY})");
|
||||
$"基本地形生成器: 地图单元格宽度或高度为零或负数。设置为默认值 (100, 100)。当前值: ({_config.MapCellSizeX}, {_config.MapCellSizeY})");
|
||||
_config.MapCellSizeX = 100;
|
||||
_config.MapCellSizeY = 100;
|
||||
}
|
||||
|
||||
// 新增:MinThreshold 和 MaxThreshold 的校验
|
||||
// 确保阈值在柏林噪声的合理范围内 (-1.0 到 1.0),假定 PerlinNoise.Instance.Noise 返回此范围
|
||||
// 将阈值限定在 -1.0 到 1.0 的有效范围内
|
||||
_config.MinThreshold = Math.Max(-1.0, Math.Min(1.0, _config.MinThreshold));
|
||||
_config.MaxThreshold = Math.Max(-1.0, Math.Min(1.0, _config.MaxThreshold));
|
||||
|
||||
// 确保 MinThreshold 不大于 MaxThreshold
|
||||
if (_config.MinThreshold > _config.MaxThreshold)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"BasicTerrainMapGenerator: MinThreshold ({_config.MinThreshold}) cannot be greater than MaxThreshold ({_config.MaxThreshold}). Swapping values.");
|
||||
$"基本地形生成器: 最小阈值 ({_config.MinThreshold}) 不能大于最大阈值 ({_config.MaxThreshold})。交换值。");
|
||||
(_config.MinThreshold, _config.MaxThreshold) = (_config.MaxThreshold, _config.MinThreshold); // 交换值
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
Debug.LogError($"BasicTerrainMapGenerator: JSON deserialization error: {ex.Message}. Input: {value}");
|
||||
Debug.LogError($"基本地形生成器: JSON反序列化错误: {ex.Message}。输入: {value}");
|
||||
_config = new BasicTerrainGeneratorConfig(); // 失败时使用默认配置
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"BasicTerrainMapGenerator: An unexpected error occurred during Init: {ex.Message}. Input: {value}");
|
||||
Debug.LogError($"基本地形生成器: Init 期间发生意外错误: {ex.Message}。输入: {value}");
|
||||
_config = new BasicTerrainGeneratorConfig(); // 失败时使用默认配置
|
||||
}
|
||||
}
|
||||
@@ -105,70 +112,101 @@ namespace Map
|
||||
/// <summary>
|
||||
/// 根据柏林噪声生成地形。此方法现在是异步的,以避免阻塞主线程。
|
||||
/// </summary>
|
||||
/// <param name="map">MapGenerator实例,包含要操作的Tilemap。</param>
|
||||
public override async Task Process(MapGenerator map) // 标记为 async Task
|
||||
/// <param name="landform">地图生成器实例,包含要操作的瓦片地图。</param>
|
||||
public override async Task Process(Landform landform)
|
||||
{
|
||||
// _config 不是单例,所以需要进行空检查。
|
||||
if (_config == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"BasicTerrainMapGenerator: Process called before Init or Init failed. Using default configuration.");
|
||||
_config = new BasicTerrainGeneratorConfig(); // 确保有默认配置
|
||||
Debug.LogError("基本地形生成器: Process 在 Init 之前调用或 Init 失败。使用默认配置。");
|
||||
_config = new BasicTerrainGeneratorConfig();
|
||||
}
|
||||
|
||||
var terrainTile = TileManager.Instance.GetTile(_config.TileDefName);
|
||||
if (terrainTile == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"BasicTerrainMapGenerator: Failed to get tile for defName: '{_config.TileDefName}'. Aborting generation.");
|
||||
Debug.LogError($"基本地形生成器: 无法获取瓦片定义名称: '{_config.TileDefName}'。中止生成。");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取不满足条件时放置的瓦片
|
||||
TileBase elseTile = null;
|
||||
if (!string.IsNullOrWhiteSpace(_config.ElseTileDefName))
|
||||
{
|
||||
elseTile = TileManager.Instance.GetTile(_config.ElseTileDefName);
|
||||
if (elseTile == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"BasicTerrainMapGenerator: Failed to get else-tile for defName: '{_config.ElseTileDefName}'. Will not place else-tiles.");
|
||||
Debug.LogWarning($"基本地形生成器: 无法获取 else-瓦片定义名称: '{_config.ElseTileDefName}'。将不会放置 else-瓦片。");
|
||||
}
|
||||
}
|
||||
|
||||
Tilemap targetTilemap;
|
||||
switch (_config.TargetDimension) // 直接对枚举值进行 switch
|
||||
{
|
||||
case MapDimension.Base:
|
||||
targetTilemap = landform.baseTilemap;
|
||||
break;
|
||||
case MapDimension.Building:
|
||||
targetTilemap = landform.buildingTilemap;
|
||||
break;
|
||||
case MapDimension.Plant:
|
||||
targetTilemap = landform.plantTilemap;
|
||||
break;
|
||||
default: // 理论上不会走到这里,因为 TargetDimension 是枚举类型,但以防万一
|
||||
Debug.LogWarning(
|
||||
$"基本地形生成器: 收到未知的目标维度枚举值 '{_config.TargetDimension}'。将使用默认的 'Base' 瓦片地图。");
|
||||
targetTilemap = landform.baseTilemap;
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetTilemap == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"基本地形生成器: 选定的 Tilemap ({_config.TargetDimension}) 为 null。请确保 Landform 对象上已正确分配 Tilemap。中止生成。");
|
||||
return;
|
||||
}
|
||||
|
||||
for (var x = 0; x < _config.MapCellSizeX; x++)
|
||||
{
|
||||
for (var y = 0; y < _config.MapCellSizeY; y++)
|
||||
{
|
||||
// 计算柏林噪声的输入坐标,结合缩放和偏移
|
||||
var sampleX = x / (double)_config.MapCellSizeX * _config.Scale + _config.OffsetX;
|
||||
var sampleY = y / (double)_config.MapCellSizeY * _config.Scale + _config.OffsetY;
|
||||
|
||||
// 获取柏林噪声值 (假定 PerlinNoise.Instance.Noise 返回 -1 到 1)
|
||||
var noiseValue = PerlinNoise.Instance.Noise(sampleX, sampleY);
|
||||
|
||||
// 根据噪声值和阈值范围设置瓦片
|
||||
// 核心修改部分:将 x, y 与 OriginX, OriginY 相加,用于指定瓦片的实际放置位置。
|
||||
Vector3Int tilePosition = new Vector3Int(x + _config.OriginX, y + _config.OriginY, 0);
|
||||
if (noiseValue >= _config.MinThreshold && noiseValue <= _config.MaxThreshold)
|
||||
{
|
||||
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), terrainTile);
|
||||
targetTilemap.SetTile(tilePosition, terrainTile);
|
||||
}
|
||||
else if (elseTile != null) // 如果定义了elseTile且成功获取
|
||||
else if (elseTile != null)
|
||||
{
|
||||
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), elseTile);
|
||||
targetTilemap.SetTile(tilePosition, elseTile);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果不满足条件,且没有elseTile定义,则不放置任何瓦片 (兼容旧逻辑,即留空)
|
||||
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), null);
|
||||
targetTilemap.SetTile(tilePosition, null);
|
||||
}
|
||||
}
|
||||
|
||||
// 在处理完每一列(或每行)后,暂停,允许Unity渲染帧并处理其他事件。
|
||||
// 这可以防止长时间的计算阻塞主线程,提高应用程序的响应性。
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前配置的地图尺寸。
|
||||
/// </summary>
|
||||
/// <returns>表示地图宽度和高度的 Vector2Int。</returns>
|
||||
public override Vector2Int GetSize()
|
||||
{
|
||||
// _config 不是单例,所以需要进行空检查。
|
||||
if (_config == null)
|
||||
{
|
||||
Debug.LogError("BasicTerrainMapGenerator: GetSize 在 Init 之前调用或 Init 失败。返回默认尺寸 (0,0)。");
|
||||
return Vector2Int.zero;
|
||||
}
|
||||
|
||||
// 返回的是生成区域的尺寸,而不是它的起始位置。
|
||||
return new Vector2Int(_config.MapCellSizeX, _config.MapCellSizeY);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
328
Client/Assets/Scripts/Map/BuildingMapGenerator.cs
Normal file
328
Client/Assets/Scripts/Map/BuildingMapGenerator.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
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; // 未找到合适位置
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Client/Assets/Scripts/Map/BuildingMapGenerator.cs.meta
Normal file
3
Client/Assets/Scripts/Map/BuildingMapGenerator.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1141bb705ec84ef4b5719a666f311a5d
|
||||
timeCreated: 1758337362
|
||||
@@ -1,591 +0,0 @@
|
||||
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}) 处的放置。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a30dc148916d40fe828d028c131f7ee2
|
||||
timeCreated: 1758021375
|
||||
250
Client/Assets/Scripts/Map/ConditionalTileGenerator.cs
Normal file
250
Client/Assets/Scripts/Map/ConditionalTileGenerator.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Managers;
|
||||
using Newtonsoft.Json;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
|
||||
namespace Map
|
||||
{
|
||||
public class ConditionalTileGeneratorConfig
|
||||
{
|
||||
// ====== 生成瓦片配置 ======
|
||||
[JsonProperty("outputTileDefName", Required = Required.Always)]
|
||||
public string OutputTileDefName { get; set; }
|
||||
[JsonProperty("outputLayer", Required = Required.Always)]
|
||||
public string OutputLayer { get; set; } // "base", "building", "plant"
|
||||
// ====== 条件检查配置 ======
|
||||
[JsonProperty("conditionTileDefNames")] // 属性名改为复数形式
|
||||
public List<string> ConditionTileDefNames { get; set; } = null; // 如果非空,则只有在目标层有此瓦片列表中的任意一个时才生成
|
||||
[JsonProperty("conditionLayer")]
|
||||
public string ConditionLayer { get; set; } = null; // 检查条件瓦片的层
|
||||
// ====== 区域配置 ======
|
||||
[JsonProperty("positionX", Required = Required.Always)]
|
||||
public int PositionX { get; set; } = 0; // 起始位置X
|
||||
[JsonProperty("positionY", Required = Required.Always)]
|
||||
public int PositionY { get; set; } = 0; // 起始位置Y
|
||||
[JsonProperty("width", Required = Required.Always)]
|
||||
public int Width { get; set; } = 1; // 区域宽度
|
||||
[JsonProperty("height", Required = Required.Always)]
|
||||
public int Height { get; set; } = 1; // 区域高度
|
||||
// ====== 柏林噪声配置 ======
|
||||
[JsonProperty("usePerlinNoise")]
|
||||
public bool UsePerlinNoise { get; set; } = false;
|
||||
[JsonProperty("perlinScale")]
|
||||
public float PerlinScale { get; set; } = 10f; // 柏林噪声频率,数值越大越粗糙
|
||||
[JsonProperty("perlinThreshold")]
|
||||
public float PerlinThreshold { get; set; } = 0.5f; // 柏林噪声阈值,只有噪声值高于此值才生成瓦片
|
||||
[JsonProperty("perlinOffsetX")]
|
||||
public float PerlinOffsetX { get; set; } = 0f; // 柏林噪声X偏移
|
||||
[JsonProperty("perlinOffsetY")]
|
||||
public float PerlinOffsetY { get; set; } = 0f; // 柏林噪声Y偏移
|
||||
}
|
||||
|
||||
public class ConditionalTileGenerator : MapGeneratorWorkClassBase
|
||||
{
|
||||
private ConditionalTileGeneratorConfig _config;
|
||||
private TileBase _outputTileBase; // 缓存要生成的实际 TileBase 资源
|
||||
private HashSet<string> _conditionTileBaseNames; // 缓存条件瓦片的运行时名称集合
|
||||
/// <summary>
|
||||
/// 初始化条件瓦片生成器,解析JSON配置并预加载资源。
|
||||
/// </summary>
|
||||
/// <param name="value">包含ConditionalTileGeneratorConfig的JSON字符串。</param>
|
||||
public override void Init(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
Debug.LogError("ConditionalTileGenerator: Init received empty or null JSON value.");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
_config = JsonConvert.DeserializeObject<ConditionalTileGeneratorConfig>(value);
|
||||
if (_config == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
"ConditionalTileGenerator: Failed to deserialize ConditionalTileGeneratorConfig from JSON.");
|
||||
return;
|
||||
}
|
||||
// 预加载输出瓦片
|
||||
if (!string.IsNullOrEmpty(_config.OutputTileDefName))
|
||||
{
|
||||
_outputTileBase = TileManager.Instance.GetTile(_config.OutputTileDefName);
|
||||
if (_outputTileBase == null)
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"ConditionalTileGenerator: Output tile asset for '{_config.OutputTileDefName}' not found via TileManager. Generation may fail.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError(
|
||||
"ConditionalTileGenerator: OutputTileDefName is required but not provided in config.");
|
||||
return;
|
||||
}
|
||||
// 预加载条件瓦片的名称集合 (如果指定)
|
||||
if (_config.ConditionTileDefNames != null && _config.ConditionTileDefNames.Count > 0)
|
||||
{
|
||||
_conditionTileBaseNames = new HashSet<string>();
|
||||
foreach (var defName in _config.ConditionTileDefNames)
|
||||
{
|
||||
if (string.IsNullOrEmpty(defName))
|
||||
{
|
||||
Debug.LogWarning("ConditionalTileGenerator: Found empty or null condition tile definition name in config. Skipping.");
|
||||
continue;
|
||||
}
|
||||
var conditionAsset = TileManager.Instance.GetTile(defName);
|
||||
if (conditionAsset != null)
|
||||
{
|
||||
_conditionTileBaseNames.Add(conditionAsset.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning(
|
||||
$"ConditionalTileGenerator: Condition tile asset for '{defName}' not found via TileManager. This specific condition tile will be ignored in checks.");
|
||||
}
|
||||
}
|
||||
// 如果最终没有有效条件瓦片被加载,则将HashSet设为null,表示不进行此条件检查
|
||||
if (_conditionTileBaseNames.Count == 0)
|
||||
{
|
||||
_conditionTileBaseNames = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果配置中未指定条件瓦片列表,或者列表为空,显式设置为null以表示无需检查此条件
|
||||
_conditionTileBaseNames = null;
|
||||
}
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Debug.LogError($"ConditionalTileGenerator: JSON deserialization error: {ex.Message}\nJSON: {value}");
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取条件瓦片生成器将影响的区域大小。
|
||||
/// </summary>
|
||||
/// <returns>表示区域宽和高的Vector2Int。</returns>
|
||||
public override Vector2Int GetSize()
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
return Vector2Int.zero;
|
||||
}
|
||||
return new Vector2Int(_config.Width, _config.Height);
|
||||
}
|
||||
/// <summary>
|
||||
/// 执行条件瓦片生成逻辑。
|
||||
/// </summary>
|
||||
/// <param name="landform">MapGenerator实例,提供Tilemap和坐标转换功能。</param>
|
||||
public override async Task Process(Landform landform)
|
||||
{
|
||||
if (_config == null || _outputTileBase == null)
|
||||
{
|
||||
Debug.LogError("ConditionalTileGenerator: Configuration or output tile not properly initialized.");
|
||||
return;
|
||||
}
|
||||
if (landform == null || landform.baseTilemap == null || landform.buildingTilemap == null || landform.plantTilemap == null)
|
||||
{
|
||||
Debug.LogError("ConditionalTileGenerator: MapGenerator or its Tilemaps are not assigned.");
|
||||
return;
|
||||
}
|
||||
// 获取输出层 Tilemap
|
||||
var outputTilemap = GetTilemapByName(landform, _config.OutputLayer);
|
||||
if (outputTilemap == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"ConditionalTileGenerator: Output Tilemap '{_config.OutputLayer}' not found or invalid.");
|
||||
return;
|
||||
}
|
||||
// 获取条件层 Tilemap (如果需要检查条件瓦片)
|
||||
Tilemap conditionTilemap = null;
|
||||
// 只有当有实际的条件瓦片名称列表配置时才尝试获取条件层Tilemap
|
||||
bool hasConditionTileNamesConfigured = (_conditionTileBaseNames != null && _conditionTileBaseNames.Count > 0);
|
||||
if (hasConditionTileNamesConfigured)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_config.ConditionLayer))
|
||||
{
|
||||
Debug.LogWarning(
|
||||
"ConditionalTileGenerator: 'ConditionTileDefNames' specified, but 'ConditionLayer' is not. Condition check related to specific tiles will implicitly fail for the tile conditions.");
|
||||
// 此时不设置 conditionTilemap,会在循环内部触发 conditionsMet = false
|
||||
}
|
||||
else
|
||||
{
|
||||
conditionTilemap = GetTilemapByName(landform, _config.ConditionLayer);
|
||||
if (conditionTilemap == null)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"ConditionalTileGenerator: Condition Tilemap '{_config.ConditionLayer}' not found or invalid. Condition check related to specific tiles will implicitly fail for the tile conditions.");
|
||||
// 此时不设置 conditionTilemap,会在循环内部触发 conditionsMet = false
|
||||
}
|
||||
}
|
||||
}
|
||||
for (var y = 0; y < _config.Height; y++)
|
||||
{
|
||||
for (var x = 0; x < _config.Width; x++)
|
||||
{
|
||||
var currentGridPos = new Vector3Int(_config.PositionX + x, _config.PositionY + y, 0);
|
||||
var conditionsMet = true;
|
||||
// 1. 检查条件瓦片 (仅当有有效的条件瓦片和条件层Tilemap时执行)
|
||||
if (conditionsMet && hasConditionTileNamesConfigured) // 只有配置了条件瓦片才进行进一步检查
|
||||
{
|
||||
if (conditionTilemap != null) // 确保条件层Tilemap有效
|
||||
{
|
||||
var tileOnConditionLayer = conditionTilemap.GetTile(currentGridPos);
|
||||
// 如果当前位置没有瓦片,或者瓦片名称不在允许的条件列表中,则条件不满足
|
||||
if (tileOnConditionLayer == null || !_conditionTileBaseNames.Contains(tileOnConditionLayer.name))
|
||||
{
|
||||
conditionsMet = false; // 条件不满足
|
||||
// Debug.Log($"Skipping {currentGridPos}: Condition tile not found or not in allowed list.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果 hasConditionTileNamesConfigured 为 true, 但 conditionTilemap 为 null (因为_config.ConditionLayer为空或GetTilemapByName失败)
|
||||
// 那么条件瓦片检查无法进行,应该视为条件不满足
|
||||
conditionsMet = false;
|
||||
}
|
||||
}
|
||||
// 2. 检查柏林噪声
|
||||
if (conditionsMet && _config.UsePerlinNoise)
|
||||
{
|
||||
var sampleX = (currentGridPos.x + _config.PerlinOffsetX) / _config.PerlinScale;
|
||||
var sampleY = (currentGridPos.y + _config.PerlinOffsetY) / _config.PerlinScale;
|
||||
var perlinValue = Mathf.PerlinNoise(sampleX, sampleY);
|
||||
if (perlinValue < _config.PerlinThreshold)
|
||||
{
|
||||
conditionsMet = false; // 噪声条件不满足
|
||||
// Debug.Log($"Skipping {currentGridPos}: Perlin noise {perlinValue:F2} below threshold {_config.PerlinThreshold:F2}.");
|
||||
}
|
||||
}
|
||||
// 如果所有条件都满足,则放置瓦片
|
||||
if (conditionsMet)
|
||||
{
|
||||
outputTilemap.SetTile(currentGridPos, _outputTileBase);
|
||||
// Debug.Log($"ConditionalTileGenerator: Placed '{_config.OutputTileDefName}' at {currentGridPos}.");
|
||||
}
|
||||
}
|
||||
await Task.Yield(); // 允许在每行结束时暂停,避免阻塞
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// 根据名称字符串获取 MapGenerator 中的对应 Tilemap。
|
||||
/// </summary>
|
||||
private Tilemap GetTilemapByName(Landform landform, string tilemapName)
|
||||
{
|
||||
switch (tilemapName?.ToLower()) // 使用ToLower以便不区分大小写
|
||||
{
|
||||
case "base":
|
||||
return landform.baseTilemap;
|
||||
case "building":
|
||||
return landform.buildingTilemap;
|
||||
case "plant":
|
||||
return landform.plantTilemap;
|
||||
default:
|
||||
Debug.LogWarning($"ConditionalTileGenerator: Unknown Tilemap name '{tilemapName}'.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9913f61982bf4891ac673f7d464a7b31
|
||||
timeCreated: 1758365053
|
||||
@@ -17,14 +17,18 @@ namespace Map
|
||||
[Tooltip("此维度的唯一标识符。如果为空,将使用GameObject的名称。")]
|
||||
private string _dimensionId;
|
||||
|
||||
[SerializeField] public MapGenerator mapGenerator;
|
||||
[SerializeField] public Landform landform;
|
||||
|
||||
public Vector3 cameraPosition;
|
||||
public Vector3 cameraPosition = new(0, 0, -10);
|
||||
|
||||
public Entity.Entity focusEntity;
|
||||
|
||||
public string mapGeneratorId;
|
||||
public DimensionDef dimensionDefinition;
|
||||
|
||||
public Vector2Int DimensionSize { get;private set; }
|
||||
|
||||
public event System.Action<Dimension> OnDimensionLoaded;
|
||||
/// <summary>
|
||||
/// 获取此维度的唯一标识符。
|
||||
/// </summary>
|
||||
@@ -50,26 +54,22 @@ namespace Map
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
|
||||
// 1. 确保 DimensionId 已初始化,这会触发 DimensionId 属性的 getter 逻辑
|
||||
var id = DimensionId;
|
||||
// 2. 创建一个用于存放此维度下所有实体的根GameObject,方便管理
|
||||
var rootObj = new GameObject($"_Entities_{id}");
|
||||
rootObj.transform.SetParent(this.transform); // 将其作为Dimension对象的子对象
|
||||
DimensionRoot = rootObj.transform;
|
||||
Program.Instance.RegisterDimension(this);
|
||||
|
||||
mapGenerator.Init();
|
||||
var size = mapGenerator.GetSize();
|
||||
cameraPosition = new Vector3(size.x / 2f, size.y / 2f, -10)+transform.position;
|
||||
|
||||
// 5. 处理 defaultOpen 逻辑,设置Program的焦点维度
|
||||
// 确保在自身注册到 Program 之后再设置焦点,这样 Program 内部才能找到它
|
||||
Program.Instance.RegisterDimension(this);
|
||||
landform.Init();
|
||||
if (defaultOpen)
|
||||
{
|
||||
Program.Instance.SetFocusedDimension(_dimensionId);
|
||||
}
|
||||
|
||||
mapGeneratorId = Configs.ConfigManager.Instance.GetValue<string>(mapGeneratorId);
|
||||
mapGeneratorId = Configs.ConfigManager.Instance.GetValue<string>(DimensionId);
|
||||
if (!string.IsNullOrEmpty(mapGeneratorId))
|
||||
{
|
||||
dimensionDefinition=DefineManager.Instance.FindDefine<DimensionDef>(mapGeneratorId);
|
||||
@@ -78,6 +78,7 @@ namespace Map
|
||||
_=ApplyDimensionDef(dimensionDefinition);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -92,14 +93,32 @@ namespace Map
|
||||
|
||||
private async Task ApplyDimensionDef(DimensionDef def)
|
||||
{
|
||||
var maxWidth = DimensionSize.x; // 初始化为当前的维度尺寸
|
||||
var maxHeight = DimensionSize.y; // 初始化为当前的维度尺寸
|
||||
|
||||
if (def.mapGenerators != null)
|
||||
{
|
||||
foreach (var defMapGenerator in def.mapGenerators)
|
||||
{
|
||||
await TileManager.Instance.ApplyMapGenerator(defMapGenerator.defName, mapGenerator);
|
||||
var workClass = TileManager.Instance.GetMapGeneratorWorkClass(defMapGenerator.defName);
|
||||
if (workClass == null) continue;
|
||||
|
||||
var requiredSize = workClass.GetSize();
|
||||
maxWidth = Mathf.Max(maxWidth, requiredSize.x);
|
||||
maxHeight = Mathf.Max(maxHeight, requiredSize.y);
|
||||
}
|
||||
|
||||
DimensionSize = new Vector2Int(maxWidth, maxHeight);
|
||||
cameraPosition = new Vector3(DimensionSize.x / 2f, DimensionSize.y / 2f, -10)+transform.position;
|
||||
foreach (var defMapGenerator in def.mapGenerators)
|
||||
{
|
||||
await TileManager.Instance.ApplyMapGenerator(defMapGenerator.defName, landform);
|
||||
}
|
||||
}
|
||||
|
||||
OnDimensionLoaded.Invoke(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
159
Client/Assets/Scripts/Map/Landform.cs
Normal file
159
Client/Assets/Scripts/Map/Landform.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Data; // For TileDef
|
||||
using System.Collections.Generic;
|
||||
using Managers; // For Dictionary
|
||||
|
||||
namespace Map
|
||||
{
|
||||
public class Landform : MonoBehaviour
|
||||
{
|
||||
public Tilemap baseTilemap;
|
||||
public Tilemap buildingTilemap;
|
||||
public Tilemap plantTilemap;
|
||||
|
||||
public Dimension dimension;
|
||||
|
||||
// 定义瓦片状态结构体
|
||||
public struct TileState
|
||||
{
|
||||
public Vector3Int position; // 瓦片的世界坐标
|
||||
public Tile.ColliderType colliderType;
|
||||
public float travelCost;
|
||||
|
||||
public TileState(Vector3Int pos, Tile.ColliderType collider, float cost)
|
||||
{
|
||||
position = pos;
|
||||
colliderType = collider;
|
||||
travelCost = cost;
|
||||
}
|
||||
}
|
||||
|
||||
public void Init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public string GetDimensionID()
|
||||
{
|
||||
return dimension.DimensionId;
|
||||
}
|
||||
|
||||
public Vector3Int GetSize()
|
||||
{
|
||||
return baseTilemap.size;
|
||||
}
|
||||
|
||||
public Vector3 GetWorldCoordinates(Vector3Int coord)
|
||||
{
|
||||
return baseTilemap.CellToWorld(coord);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
baseTilemap.ClearAllTiles();
|
||||
buildingTilemap.ClearAllTiles();
|
||||
plantTilemap.ClearAllTiles();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定格子坐标上的瓦片状态,包括碰撞体类型和通行成本。
|
||||
/// 优先级:buildingTilemap > plantTilemap > baseTilemap。
|
||||
/// </summary>
|
||||
/// <param name="cellPosition">要查询的格子坐标。</param>
|
||||
/// <returns>包含瓦片碰撞体类型和通行成本的TileState结构。</returns>
|
||||
public TileState GetTileState(Vector3Int cellPosition)
|
||||
{
|
||||
Tile.ColliderType finalColliderType = Tile.ColliderType.None;
|
||||
float finalTravelCost = 0f; // 默认通行成本
|
||||
|
||||
// 瓦片层级优先级:Building > Plant > Base
|
||||
// 存储查询到的瓦片定义及其所属Tilemap,用于后续决定通行成本
|
||||
Dictionary<Tilemap, (TileBase tile, TileDef def)> activeTiles = new Dictionary<Tilemap, (TileBase, TileDef)>();
|
||||
|
||||
// 1. 尝试从 Building 层获取瓦片信息
|
||||
TileBase buildingTile = buildingTilemap.GetTile(cellPosition);
|
||||
if (buildingTile != null && DefineManager.Instance != null) // 确保DefineManager已实例化
|
||||
{
|
||||
TileDef buildingTileDef = DefineManager.Instance.FindDefine<TileDef>(buildingTile.name);
|
||||
if (buildingTileDef != null)
|
||||
{
|
||||
activeTiles[buildingTilemap] = (buildingTile, buildingTileDef);
|
||||
if (buildingTileDef.collider != Tile.ColliderType.None)
|
||||
{
|
||||
finalColliderType = buildingTileDef.collider; // Building层的碰撞体优先级最高
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 尝试从 Plant 层获取瓦片信息 (只有当 Building 层没有提供碰撞体时才考虑)
|
||||
if (finalColliderType == Tile.ColliderType.None)
|
||||
{
|
||||
TileBase plantTile = plantTilemap.GetTile(cellPosition);
|
||||
if (plantTile != null && DefineManager.Instance != null)
|
||||
{
|
||||
TileDef plantTileDef = DefineManager.Instance.FindDefine<TileDef>(plantTile.name);
|
||||
if (plantTileDef != null)
|
||||
{
|
||||
activeTiles[plantTilemap] = (plantTile, plantTileDef);
|
||||
if (plantTileDef.collider != Tile.ColliderType.None)
|
||||
{
|
||||
finalColliderType = plantTileDef.collider; // Plant层的碰撞体优先级次之
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 尝试从 Base 层获取瓦片信息 (只有当 Building 和 Plant 层都没有提供碰撞体时才考虑)
|
||||
if (finalColliderType == Tile.ColliderType.None)
|
||||
{
|
||||
TileBase baseTile = baseTilemap.GetTile(cellPosition);
|
||||
if (baseTile != null && DefineManager.Instance != null)
|
||||
{
|
||||
TileDef baseTileDef = DefineManager.Instance.FindDefine<TileDef>(baseTile.name);
|
||||
if (baseTileDef != null)
|
||||
{
|
||||
activeTiles[baseTilemap] = (baseTile, baseTileDef);
|
||||
if (baseTileDef.collider != Tile.ColliderType.None)
|
||||
{
|
||||
finalColliderType = baseTileDef.collider; // Base层的碰撞体优先级最低
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 根据最终碰撞体类型确定通行成本
|
||||
if (finalColliderType != Tile.ColliderType.None)
|
||||
{
|
||||
finalTravelCost = 1f; // 有碰撞体时,通行成本为1(不可通过)
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果没有碰撞体,则取优先级最高的瓦片的tileCost作为最终通行成本。
|
||||
// 优先级 Building > Plant > Base
|
||||
TileDef highestPriorityDefWithCost = null;
|
||||
|
||||
if (activeTiles.ContainsKey(buildingTilemap))
|
||||
{
|
||||
highestPriorityDefWithCost = activeTiles[buildingTilemap].def;
|
||||
}
|
||||
else if (activeTiles.ContainsKey(plantTilemap))
|
||||
{
|
||||
highestPriorityDefWithCost = activeTiles[plantTilemap].def;
|
||||
}
|
||||
else if (activeTiles.ContainsKey(baseTilemap))
|
||||
{
|
||||
highestPriorityDefWithCost = activeTiles[baseTilemap].def;
|
||||
}
|
||||
|
||||
if (highestPriorityDefWithCost != null)
|
||||
{
|
||||
finalTravelCost = highestPriorityDefWithCost.tileCost;
|
||||
}
|
||||
// 如果任何层都没有有效瓦片(或DefineManager未实例化),则保持 finalTravelCost 为默认值 0f
|
||||
}
|
||||
|
||||
return new TileState(cellPosition, finalColliderType, finalTravelCost);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using Managers;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Utils;
|
||||
|
||||
namespace Map
|
||||
{
|
||||
public class MapGenerator : MonoBehaviour
|
||||
{
|
||||
public Tilemap baseTilemap;
|
||||
public Tilemap buildingTilemap;
|
||||
public Tilemap plantTilemap;
|
||||
public void Init()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public Vector2Int GetSize()
|
||||
{
|
||||
return new Vector2Int(100, 100);
|
||||
}
|
||||
|
||||
public Vector2 GetWorldCoordinates(Vector2Int coord)
|
||||
{
|
||||
return transform.position + new Vector3(coord.x, coord.y);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
baseTilemap.ClearAllTiles();
|
||||
buildingTilemap.ClearAllTiles();
|
||||
plantTilemap.ClearAllTiles();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Map
|
||||
{
|
||||
public abstract class MapGeneratorWorkClassBase
|
||||
{
|
||||
public abstract void Init(string value);
|
||||
|
||||
// 将 void 改为 Task
|
||||
public abstract Task Process(MapGenerator map);
|
||||
public abstract Task Process(Landform landform);
|
||||
public abstract Vector2Int GetSize();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using UnityEngine;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks; // 引入 System.Threading.Tasks
|
||||
using Managers;
|
||||
using Utils;
|
||||
@@ -17,46 +18,55 @@ namespace Map
|
||||
/// </summary>
|
||||
[JsonProperty("tileDefName", Required = Required.Always)]
|
||||
public string TileDefName { get; set; } = "DefaultPlantTile";
|
||||
|
||||
/// <summary>
|
||||
/// 柏林噪声的缩放因子。
|
||||
/// </summary>
|
||||
[JsonProperty("scale")]
|
||||
public double Scale { get; set; } = 0.08;
|
||||
|
||||
/// <summary>
|
||||
/// 柏林噪声阈值,高于此值才可能生成植物 (-1到1之间)。
|
||||
/// </summary>
|
||||
[JsonProperty("threshold")]
|
||||
public double Threshold { get; set; } = 0.3;
|
||||
|
||||
/// <summary>
|
||||
/// 植物生成概率 (0-1之间),控制稀疏度。
|
||||
/// </summary>
|
||||
[JsonProperty("density")]
|
||||
public double Density { get; set; } = 0.6;
|
||||
|
||||
/// <summary>
|
||||
/// 柏林噪声的X轴偏移。
|
||||
/// </summary>
|
||||
[JsonProperty("offsetX")]
|
||||
public double OffsetX { get; set; } = 0.0;
|
||||
public double OffsetX { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 柏林噪声的Y轴偏移。
|
||||
/// </summary>
|
||||
[JsonProperty("offsetY")]
|
||||
public double OffsetY { get; set; } = 0.0;
|
||||
public double OffsetY { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 植物必须生长在其上的基础瓦片名称 (可空)。
|
||||
/// 植物必须生长在其上的基础瓦片名称列表 (满足其中任意一个即可,可空)。
|
||||
/// </summary>
|
||||
[JsonProperty("requiredBaseTileDefName")]
|
||||
public string RequiredBaseTileDefName { get; set; } = null;
|
||||
[JsonProperty("requiredBaseTileDefNames")] // 属性名改为复数形式
|
||||
public List<string> RequiredBaseTileDefNames { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 地图生成区域的宽度(单元格数量)。
|
||||
/// </summary>
|
||||
[JsonProperty("mapCellSizeX")]
|
||||
public int MapCellSizeX { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 地图生成区域的高度(单元格数量)。
|
||||
/// </summary>
|
||||
[JsonProperty("mapCellSizeY")]
|
||||
public int MapCellSizeY { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// 是否避免植物瓦片互相重叠。
|
||||
/// </summary>
|
||||
@@ -67,6 +77,7 @@ namespace Map
|
||||
public class PlantMapGenerator : MapGeneratorWorkClassBase
|
||||
{
|
||||
private PlantGeneratorConfig _config;
|
||||
private HashSet<string> _requiredBaseTileNames; // 缓存所需基底瓦片的运行时名称集合
|
||||
|
||||
/// <summary>
|
||||
/// 初始化植物生成器,解析JSON配置字符串。
|
||||
@@ -77,22 +88,24 @@ namespace Map
|
||||
try
|
||||
{
|
||||
_config = JsonConvert.DeserializeObject<PlantGeneratorConfig>(value);
|
||||
|
||||
if (_config == null)
|
||||
{
|
||||
Debug.LogError("植物地图生成器:配置反序列化为空。请检查JSON格式。将使用默认配置。");
|
||||
_config = new PlantGeneratorConfig();
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(_config.TileDefName))
|
||||
{
|
||||
Debug.LogWarning("植物地图生成器:瓦片定义名称为空。将使用默认值 'DefaultPlantTile'。");
|
||||
_config.TileDefName = "DefaultPlantTile";
|
||||
}
|
||||
|
||||
if (_config.Scale <= 0)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:缩放值必须为正数。将设置为默认值 0.08。当前值: {_config.Scale}");
|
||||
_config.Scale = 0.08;
|
||||
}
|
||||
|
||||
// Unity的Mathf.PerlinNoise输出0-1,如果PerlinNoise.Instance.Noise也如此,这里需要修改范围
|
||||
// 假设 PerlinNoise.Instance.Noise() 返回值在 -1 到 1 之间。
|
||||
if (_config.Threshold < -1.0 || _config.Threshold > 1.0)
|
||||
@@ -100,21 +113,61 @@ namespace Map
|
||||
Debug.LogWarning($"植物地图生成器:阈值 ({_config.Threshold}) 超出柏林噪声典型范围 (-1.0到1.0)。已钳制到 0.3。");
|
||||
_config.Threshold = 0.3;
|
||||
}
|
||||
|
||||
if (_config.Density < 0 || _config.Density > 1)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:密度 ({_config.Density}) 必须在 0 到 1 之间。已钳制到 0.6。");
|
||||
_config.Density = Mathf.Clamp((float)_config.Density, 0f, 1f);
|
||||
}
|
||||
|
||||
if (_config.MapCellSizeX <= 0)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:地图宽度 ({_config.MapCellSizeX}) 必须为正数。将设置为默认值 100。");
|
||||
_config.MapCellSizeX = 100;
|
||||
}
|
||||
|
||||
if (_config.MapCellSizeY <= 0)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:地图高度 ({_config.MapCellSizeY}) 必须为正数。将设置为默认值 100。");
|
||||
_config.MapCellSizeY = 100;
|
||||
}
|
||||
|
||||
// 预加载所需的基础瓦片名称集合
|
||||
if (_config.RequiredBaseTileDefNames != null && _config.RequiredBaseTileDefNames.Count > 0)
|
||||
{
|
||||
_requiredBaseTileNames = new HashSet<string>();
|
||||
foreach (var defName in _config.RequiredBaseTileDefNames)
|
||||
{
|
||||
if (string.IsNullOrEmpty(defName))
|
||||
{
|
||||
Debug.LogWarning("植物地图生成器:配置中包含空的基底瓦片定义名称。已跳过。");
|
||||
continue;
|
||||
}
|
||||
|
||||
var baseAsset = TileManager.Instance.GetTile(defName);
|
||||
if (baseAsset != null)
|
||||
{
|
||||
_requiredBaseTileNames.Add(baseAsset.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 注意:在Init阶段我们只检查资产是否存在,不检查Tilemap。
|
||||
Debug.LogWarning(
|
||||
$"植物地图生成器:无法获取所需的基础瓦片 '{defName}' 的资产。该瓦片将不作为有效基底条件。");
|
||||
}
|
||||
}
|
||||
|
||||
// 如果最终没有有效基底瓦片被加载,则将HashSet设为null,表示不进行此条件检查
|
||||
if (_requiredBaseTileNames.Count == 0)
|
||||
{
|
||||
_requiredBaseTileNames = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 如果配置中未指定基底瓦片列表,或者列表为空,显式设置为null以表示无需检查此条件
|
||||
_requiredBaseTileNames = null;
|
||||
}
|
||||
}
|
||||
catch (JsonSerializationException ex)
|
||||
{
|
||||
@@ -132,8 +185,8 @@ namespace Map
|
||||
/// 根据柏林噪声和配置参数在 `MapGenerator` 的 `plantTilemap` 上生成植物。
|
||||
/// 此方法现在是异步的,以避免阻塞主线程。
|
||||
/// </summary>
|
||||
/// <param name="map">MapGenerator实例,包含要操作的Tilemap。</param>
|
||||
public override async Task Process(MapGenerator map) // 标记为 async Task
|
||||
/// <param name="landform">MapGenerator实例,包含要操作的Tilemap。</param>
|
||||
public override async Task Process(Landform landform) // 标记为 async Task
|
||||
{
|
||||
if (_config == null)
|
||||
{
|
||||
@@ -141,9 +194,10 @@ namespace Map
|
||||
return; // async Task 方法直接 return; 意味着返回 Task.CompletedTask
|
||||
}
|
||||
|
||||
if (map == null || map.plantTilemap == null)
|
||||
// 检查 MapGenerator 和所有需要的 Tilemap 是否都已赋值
|
||||
if (landform == null || landform.plantTilemap == null || landform.baseTilemap == null)
|
||||
{
|
||||
Debug.LogError("植物地图生成器:MapGenerator或植物瓦片地图为空。无法生成植物。中止。");
|
||||
Debug.LogError("植物地图生成器:MapGenerator或植物瓦片地图或基础瓦片地图为空。无法生成植物。中止。");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,44 +208,36 @@ namespace Map
|
||||
return;
|
||||
}
|
||||
|
||||
// 缓存 requiredBaseTile 的引用,避免在循环中重复查询 TileManager
|
||||
UnityEngine.Tilemaps.TileBase requiredBaseTile = null;
|
||||
if (!string.IsNullOrEmpty(_config.RequiredBaseTileDefName))
|
||||
// 判断是否有基底瓦片条件被配置
|
||||
bool hasRequiredBaseTilesConfigured = (_requiredBaseTileNames != null && _requiredBaseTileNames.Count > 0);
|
||||
// 如果配置了基底瓦片要求,但基础瓦片地图不可用,则无法进行检查,视为条件无法满足,直接中止生成
|
||||
// (这里的 map.baseTilemap == null 已经在函数开头检查过,所以这里逻辑上它不会是null)
|
||||
if (hasRequiredBaseTilesConfigured && landform.baseTilemap == null) // 理论上这个条件不会再发生
|
||||
{
|
||||
if (map.baseTilemap == null)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:已配置基础瓦片名称 ('{_config.RequiredBaseTileDefName}') 但基础瓦片地图为空。将忽略基础地形要求。");
|
||||
}
|
||||
else
|
||||
{
|
||||
requiredBaseTile = TileManager.Instance.GetTile(_config.RequiredBaseTileDefName);
|
||||
if (requiredBaseTile == null)
|
||||
{
|
||||
Debug.LogWarning($"植物地图生成器:无法获取所需的基础瓦片 '{_config.RequiredBaseTileDefName}'。将忽略基础地形要求。");
|
||||
}
|
||||
}
|
||||
Debug.LogWarning($"植物地图生成器:已配置基础瓦片名称列表但基础瓦片地图为空。植物生成将无法满足基础地形要求,因此不会生成任何受此条件限制的植物。");
|
||||
// 此时应该终止,因为不可能满足条件
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
for (var x = 0; x < _config.MapCellSizeX; x++)
|
||||
{
|
||||
for (var y = 0; y < _config.MapCellSizeY; y++)
|
||||
{
|
||||
var cellPosition = new Vector3Int(x, y, 0);
|
||||
|
||||
var sampleX = (x / (double)_config.MapCellSizeX) * _config.Scale + _config.OffsetX;
|
||||
var sampleY = (y / (double)_config.MapCellSizeY) * _config.Scale + _config.OffsetY;
|
||||
var noiseValue = PerlinNoise.Instance.Noise(sampleX, sampleY);
|
||||
|
||||
if (noiseValue < _config.Threshold)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查所需的基础瓦片
|
||||
if (requiredBaseTile != null)
|
||||
// 检查所需的基础瓦片 (仅当有条件被配置时)
|
||||
if (hasRequiredBaseTilesConfigured)
|
||||
{
|
||||
var baseTileOnMap = map.baseTilemap.GetTile(cellPosition);
|
||||
if (baseTileOnMap == null || baseTileOnMap.name != requiredBaseTile.name)
|
||||
var baseTileOnMap = landform.baseTilemap.GetTile(cellPosition);
|
||||
// 如果当前位置没有瓦片، 或瓦片名称不在允许的条件列表中، 则条件不满足
|
||||
if (baseTileOnMap == null || !_requiredBaseTileNames.Contains(baseTileOnMap.name))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -200,7 +246,7 @@ namespace Map
|
||||
// 检查是否重叠
|
||||
if (_config.PreventOverlap)
|
||||
{
|
||||
var existingPlant = map.plantTilemap.GetTile(cellPosition);
|
||||
var existingPlant = landform.plantTilemap.GetTile(cellPosition);
|
||||
if (existingPlant != null)
|
||||
{
|
||||
continue;
|
||||
@@ -213,11 +259,18 @@ namespace Map
|
||||
continue;
|
||||
}
|
||||
|
||||
map.plantTilemap.SetTile(cellPosition, plantTile);
|
||||
landform.plantTilemap.SetTile(cellPosition, plantTile);
|
||||
}
|
||||
|
||||
// 每处理完一列,就让出控制权给主线程
|
||||
await Task.Yield();
|
||||
}
|
||||
}
|
||||
|
||||
public override Vector2Int GetSize()
|
||||
{
|
||||
if (_config == null) return Vector2Int.zero;
|
||||
return new Vector2Int(_config.MapCellSizeX, _config.MapCellSizeY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user