(client) feat:实现技能树界面,实现地图生成器,实现维度指定,实现规则瓦片定义,实现逃跑逻辑,实现消息定义,实现武器动画,实现受击动画 fix: 修复单攻击子弹击中多个目标,修复人物属性计算错误 (#56)

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/56
This commit is contained in:
2025-09-19 08:26:54 +08:00
parent 78849e0cc5
commit 87a8abe86c
282 changed files with 19364 additions and 8824 deletions

View File

@@ -0,0 +1,174 @@
using UnityEngine;
using UnityEngine.Tilemaps;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks; // 引入 System.Threading.Tasks
using Managers;
using Utils;
namespace Map
{
public class BasicTerrainGeneratorConfig
{
[JsonProperty("tileDefName", Required = Required.Always)]
public string TileDefName { get; set; } = "DefaultTerrainTile"; // 瓦片的定义名称
// 将 Threshold 改为 MinThreshold并保持旧的JsonProperty名称以兼容旧JSON
[JsonProperty("threshold")]
public double MinThreshold { get; set; } = 0.0; // 柏林噪声的最小阈值。0到1之间。将其默认值改为0.0以适应-1到1的噪声范围并提供更灵活的区间开始。
[JsonProperty("maxThreshold")]
public double MaxThreshold { get; set; } = 1.0; // 柏林噪声的最大阈值。0到1之间。默认为1.0以兼容旧配置。
[JsonProperty("elseTileDefName")]
public string ElseTileDefName { get; set; } = null; // 不满足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("mapCellSizeX")] public int MapCellSizeX { get; set; } = 100; // 地图生成区域的宽度(单元格数量)
[JsonProperty("mapCellSizeY")] public int MapCellSizeY { get; set; } = 100; // 地图生成区域的高度(单元格数量)
}
// BasicTerrainGeneratorConfig 应该在同一个文件或者它自己的文件中定义,
// 这里为了清晰展示修改,假定它在 Map 命名空间下可用。
public class BasicTerrainMapGenerator : MapGeneratorWorkClassBase
{
private BasicTerrainGeneratorConfig _config;
/// <summary>
/// 初始化地形生成器解析JSON配置字符串。
/// </summary>
/// <param name="value">包含地形生成参数的JSON字符串。</param>
public override void Init(string value)
{
try
{
_config = JsonConvert.DeserializeObject<BasicTerrainGeneratorConfig>(value);
// 参数有效性检查
if (_config == null)
{
Debug.LogError("BasicTerrainMapGenerator: Configuration deserialized to null. Check JSON format.");
_config = new BasicTerrainGeneratorConfig(); // 提供默认配置以防崩溃
}
if (string.IsNullOrWhiteSpace(_config.TileDefName))
{
Debug.LogWarning(
"BasicTerrainMapGenerator: TileDefName is empty. Using default 'DefaultTerrainTile'.");
_config.TileDefName = "DefaultTerrainTile";
}
if (_config.Scale <= 0)
{
Debug.LogWarning(
$"BasicTerrainMapGenerator: Scale must be positive. Setting to default 0.1. Current: {_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})");
_config.MapCellSizeX = 100;
_config.MapCellSizeY = 100;
}
// 新增MinThreshold 和 MaxThreshold 的校验
// 确保阈值在柏林噪声的合理范围内 (-1.0 到 1.0),假定 PerlinNoise.Instance.Noise 返回此范围
_config.MinThreshold = Math.Max(-1.0, Math.Min(1.0, _config.MinThreshold));
_config.MaxThreshold = Math.Max(-1.0, Math.Min(1.0, _config.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.MaxThreshold, _config.MinThreshold); // 交换值
}
}
catch (JsonSerializationException ex)
{
Debug.LogError($"BasicTerrainMapGenerator: JSON deserialization error: {ex.Message}. Input: {value}");
_config = new BasicTerrainGeneratorConfig(); // 失败时使用默认配置
}
catch (Exception ex)
{
Debug.LogError(
$"BasicTerrainMapGenerator: An unexpected error occurred during Init: {ex.Message}. Input: {value}");
_config = new BasicTerrainGeneratorConfig(); // 失败时使用默认配置
}
}
/// <summary>
/// 根据柏林噪声生成地形。此方法现在是异步的,以避免阻塞主线程。
/// </summary>
/// <param name="map">MapGenerator实例包含要操作的Tilemap。</param>
public override async Task Process(MapGenerator map) // 标记为 async Task
{
if (_config == null)
{
Debug.LogError(
"BasicTerrainMapGenerator: Process called before Init or Init failed. Using default configuration.");
_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.");
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.");
}
}
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);
// 根据噪声值和阈值范围设置瓦片
if (noiseValue >= _config.MinThreshold && noiseValue <= _config.MaxThreshold)
{
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), terrainTile);
}
else if (elseTile != null) // 如果定义了elseTile且成功获取
{
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), elseTile);
}
else
{
// 如果不满足条件且没有elseTile定义则不放置任何瓦片 (兼容旧逻辑,即留空)
map.baseTilemap.SetTile(new Vector3Int(x, y, 0), null);
}
}
// 在处理完每一列或每行暂停允许Unity渲染帧并处理其他事件。
// 这可以防止长时间的计算阻塞主线程,提高应用程序的响应性。
await Task.Yield();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 572a7d9814174139aaf3d774711089f3
timeCreated: 1758019342

View File

@@ -0,0 +1,591 @@
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}) 处的放置。");
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a30dc148916d40fe828d028c131f7ee2
timeCreated: 1758021375

View File

@@ -1,3 +1,6 @@
using System.Threading.Tasks;
using Data;
using Managers;
using UnityEngine;
namespace Map
@@ -19,6 +22,9 @@ namespace Map
public Vector3 cameraPosition;
public Entity.Entity focusEntity;
public string mapGeneratorId;
public DimensionDef dimensionDefinition;
/// <summary>
/// 获取此维度的唯一标识符。
/// </summary>
@@ -53,7 +59,7 @@ namespace Map
Program.Instance.RegisterDimension(this);
mapGenerator.Init();
var size = mapGenerator.baseMap.GetSize();
var size = mapGenerator.GetSize();
cameraPosition = new Vector3(size.x / 2f, size.y / 2f, -10)+transform.position;
// 5. 处理 defaultOpen 逻辑设置Program的焦点维度
@@ -63,6 +69,15 @@ namespace Map
Program.Instance.SetFocusedDimension(_dimensionId);
}
mapGeneratorId = Configs.ConfigManager.Instance.GetValue<string>(mapGeneratorId);
if (!string.IsNullOrEmpty(mapGeneratorId))
{
dimensionDefinition=DefineManager.Instance.FindDefine<DimensionDef>(mapGeneratorId);
if (dimensionDefinition != null)
{
_=ApplyDimensionDef(dimensionDefinition);
}
}
}
@@ -74,6 +89,17 @@ namespace Map
Program.Instance.UnregisterDimension(this);
}
}
private async Task ApplyDimensionDef(DimensionDef def)
{
if (def.mapGenerators != null)
{
foreach (var defMapGenerator in def.mapGenerators)
{
await TileManager.Instance.ApplyMapGenerator(defMapGenerator.defName, mapGenerator);
}
}
}
}
}

View File

@@ -1,177 +0,0 @@
using Managers;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Tilemaps;
namespace Map
{
public class DoubleMap : MonoBehaviour
{
public List<List<int>> mapData = new();
public Tilemap textureLevel;
// public Vector2Int dataOffset = Vector2Int.zero; // 数据起始点偏移变量 - 已删除
// 初始化地图数据大小
public void InitializeData(int width, int height, int defaultValue = 0)
{
mapData.Clear();
for (var x = 0; x < width; x++)
{
var column = new List<int>();
for (var y = 0; y < height; y++)
{
column.Add(defaultValue);
}
mapData.Add(column);
}
}
// 设置指定数据坐标的瓦片值并刷新相关瓦片
public void SetTile(int dataX, int dataY, int value)
{
// 检查坐标是否有效
if (dataX < 0 || dataY < 0 || dataX >= mapData.Count || dataY >= mapData[0].Count)
{
Debug.LogError($"SetTile: 坐标({dataX},{dataY})超出范围");
return;
}
// 更新数据
mapData[dataX][dataY] = value;
// 刷新受影响的瓦片(当前点作为四个角影响的瓦片)
RefreshTile(dataX, dataY);
}
// 获取指定数据坐标的瓦片值
public int GetTile(int dataX, int dataY)
{
if (dataX < 0 || dataY < 0 || dataX >= mapData.Count || dataY >= mapData[0].Count)
{
Debug.LogError($"GetTile: 坐标({dataX},{dataY})超出范围");
return -1;
}
return mapData[dataX][dataY];
}
// 刷新指定数据点影响的瓦片
public void RefreshTile(int dataX, int dataY)
{
// 计算该数据点影响的四个瓦片位置(该点作为四个角)
var affectedTiles = new Vector2Int[]
{
new Vector2Int(dataX - 1, dataY - 1), // 作为右下角
new Vector2Int(dataX - 1, dataY), // 作为右上角
new Vector2Int(dataX, dataY - 1), // 作为左下角
new Vector2Int(dataX, dataY) // 作为左上角
};
foreach (var tilePos in affectedTiles)
{
UpdateTileAtTilemapPosition(tilePos.x, tilePos.y);
}
}
// 刷新整个瓦片地图
public void RefreshAllTiles()
{
if (mapData.Count == 0 || mapData[0].Count == 0) return;
// 计算瓦片地图的有效范围(考虑偏移)
var startX = 0; // dataOffset.x 已删除
var startY = 0; // dataOffset.y 已删除
var endX = startX + mapData.Count - 1;
var endY = startY + mapData[0].Count - 1;
// 遍历所有瓦片位置
for (var x = startX; x <= endX; x++)
{
for (var y = startY; y <= endY; y++)
{
UpdateTileAtTilemapPosition(x, y);
}
}
}
// 更新指定瓦片位置的显示
private void UpdateTileAtTilemapPosition(int tileX, int tileY)
{
// 计算对应的数据坐标(考虑偏移)
var dataX = tileX; // - dataOffset.x 已删除
var dataY = tileY; // - dataOffset.y 已删除
// 获取四个角的数据坐标
var topLeftX = dataX;
var topLeftY = dataY + 1;
var topRightX = dataX + 1;
var topRightY = dataY + 1;
var bottomLeftX = dataX;
var bottomLeftY = dataY;
var bottomRightX = dataX + 1;
var bottomRightY = dataY;
// 检查边界并获取值
var topLeft = GetDataValue(topLeftX, topLeftY);
var topRight = GetDataValue(topRightX, topRightY);
var bottomLeft = GetDataValue(bottomLeftX, bottomLeftY);
var bottomRight = GetDataValue(bottomRightX, bottomRightY);
// 获取对应的瓦片
var tile = GetTileFromManager(topLeft, topRight, bottomLeft, bottomRight);
// 设置到瓦片地图
var position = new Vector3Int(tileX, tileY, 0);
textureLevel.SetTile(position, tile);
}
// 安全获取数据值(处理边界)
private int GetDataValue(int dataX, int dataY)
{
if (dataX < 0 || dataY < 0 || dataX >= mapData.Count || dataY >= mapData[0].Count)
return 0; // 边界外返回默认值
return mapData[dataX][dataY];
}
// 从TileManager获取对应瓦片
private TileBase GetTileFromManager(int topLeft, int topRight, int bottomLeft, int bottomRight)
{
var manager = TileManager.Instance;
if (manager == null)
{
Debug.LogError("TileManager实例未找到");
return null;
}
// 尝试获取组合键对应的瓦片
var key = (topLeft, topRight, bottomLeft, bottomRight);
if (manager.tileToTileBaseMapping.TryGetValue(key, out var tile))
{
return tile;
}
// 备用方案:尝试获取默认瓦片
if (manager.tileBaseMapping.TryGetValue("Default", out var defaultTile))
{
return defaultTile;
}
Debug.LogError($"未找到对应瓦片: {key}");
return null;
}
public Vector2Int GetSize()
{
if (!mapData.Any() || mapData[0].Count == 0)
{
return new Vector2Int(0, 0);
}
return new Vector2Int(mapData.Count, mapData[0].Count);
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 5e7ec2149bca47f42965dd01c01116cd

View File

@@ -1,41 +1,36 @@
using System;
using Managers;
using UnityEngine;
using UnityEngine.Tilemaps;
using Utils;
namespace Map
{
public class MapGenerator : MonoBehaviour
{
public DoubleMap baseMap;
public Tilemap baseTilemap;
public Tilemap buildingTilemap;
public Tilemap plantTilemap;
public void Init()
{
Managers.DefineManager.Instance.Init();
Managers.PackagesImageManager.Instance.Init();
Managers.TileManager.Instance.Init();
var size = 100;
baseMap.InitializeData(size, size);
for (var i = 0; i < size; i++)
{
for (var j = 0; j < size; j++)
{
var dx = i - size / 2;
var dy = j - size / 2;
baseMap.mapData[i][j] = (int)(Math.Sqrt(dx * dx + dy * dy) / 5) & 1;
}
}
baseMap.RefreshAllTiles();
}
public Vector2Int GetSize()
{
return baseMap.GetSize();
return new Vector2Int(100, 100);
}
public Vector2Int GetWorldCoordinates(Vector2Int mapCoordinates)
public Vector2 GetWorldCoordinates(Vector2Int coord)
{
return new Vector2Int((int)transform.position.x, (int)transform.position.y) + mapCoordinates;
return transform.position + new Vector3(coord.x, coord.y);
}
public void Clear()
{
baseTilemap.ClearAllTiles();
buildingTilemap.ClearAllTiles();
plantTilemap.ClearAllTiles();
}
}
}

View File

@@ -0,0 +1,12 @@
using System.Threading.Tasks;
namespace Map
{
public abstract class MapGeneratorWorkClassBase
{
public abstract void Init(string value);
// 将 void 改为 Task
public abstract Task Process(MapGenerator map);
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fa82388c7b8948d8b36bf48e2c14a312
timeCreated: 1758016248

View File

@@ -0,0 +1,223 @@
using UnityEngine;
using Newtonsoft.Json;
using System;
using System.Threading.Tasks; // 引入 System.Threading.Tasks
using Managers;
using Utils;
namespace Map
{
/// <summary>
/// 植物生成器配置类。
/// </summary>
public class PlantGeneratorConfig
{
/// <summary>
/// 植物瓦片的定义名称。
/// </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;
/// <summary>
/// 柏林噪声的Y轴偏移。
/// </summary>
[JsonProperty("offsetY")]
public double OffsetY { get; set; } = 0.0;
/// <summary>
/// 植物必须生长在其上的基础瓦片名称 (可空)。
/// </summary>
[JsonProperty("requiredBaseTileDefName")]
public string RequiredBaseTileDefName { get; set; } = null;
/// <summary>
/// 地图生成区域的宽度(单元格数量)。
/// </summary>
[JsonProperty("mapCellSizeX")]
public int MapCellSizeX { get; set; } = 100;
/// <summary>
/// 地图生成区域的高度(单元格数量)。
/// </summary>
[JsonProperty("mapCellSizeY")]
public int MapCellSizeY { get; set; } = 100;
/// <summary>
/// 是否避免植物瓦片互相重叠。
/// </summary>
[JsonProperty("preventOverlap")]
public bool PreventOverlap { get; set; } = true;
}
public class PlantMapGenerator : MapGeneratorWorkClassBase
{
private PlantGeneratorConfig _config;
/// <summary>
/// 初始化植物生成器解析JSON配置字符串。
/// </summary>
/// <param name="value">包含植物生成参数的JSON字符串。</param>
public override void Init(string value)
{
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)
{
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;
}
}
catch (JsonSerializationException ex)
{
Debug.LogError($"植物地图生成器配置JSON反序列化错误{ex.Message}。输入JSON: '{value}'");
_config = new PlantGeneratorConfig();
}
catch (Exception ex)
{
Debug.LogError($"植物地图生成器:初始化过程中发生未预期错误:{ex.Message}。输入JSON: '{value}'");
_config = new PlantGeneratorConfig();
}
}
/// <summary>
/// 根据柏林噪声和配置参数在 `MapGenerator` 的 `plantTilemap` 上生成植物。
/// 此方法现在是异步的,以避免阻塞主线程。
/// </summary>
/// <param name="map">MapGenerator实例包含要操作的Tilemap。</param>
public override async Task Process(MapGenerator map) // 标记为 async Task
{
if (_config == null)
{
Debug.LogError("植物地图生成器在Init方法调用前或Init失败后调用Process。中止生成。");
return; // async Task 方法直接 return; 意味着返回 Task.CompletedTask
}
if (map == null || map.plantTilemap == null)
{
Debug.LogError("植物地图生成器MapGenerator或植物瓦片地图为空。无法生成植物。中止。");
return;
}
var plantTile = TileManager.Instance.GetTile(_config.TileDefName);
if (plantTile == null)
{
Debug.LogError($"植物地图生成器:无法获取名称为 '{_config.TileDefName}' 的瓦片。请确保瓦片管理器返回有效瓦片。中止生成。");
return;
}
// 缓存 requiredBaseTile 的引用,避免在循环中重复查询 TileManager
UnityEngine.Tilemaps.TileBase requiredBaseTile = null;
if (!string.IsNullOrEmpty(_config.RequiredBaseTileDefName))
{
if (map.baseTilemap == null)
{
Debug.LogWarning($"植物地图生成器:已配置基础瓦片名称 ('{_config.RequiredBaseTileDefName}') 但基础瓦片地图为空。将忽略基础地形要求。");
}
else
{
requiredBaseTile = TileManager.Instance.GetTile(_config.RequiredBaseTileDefName);
if (requiredBaseTile == null)
{
Debug.LogWarning($"植物地图生成器:无法获取所需的基础瓦片 '{_config.RequiredBaseTileDefName}'。将忽略基础地形要求。");
}
}
}
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)
{
var baseTileOnMap = map.baseTilemap.GetTile(cellPosition);
if (baseTileOnMap == null || baseTileOnMap.name != requiredBaseTile.name)
{
continue;
}
}
// 检查是否重叠
if (_config.PreventOverlap)
{
var existingPlant = map.plantTilemap.GetTile(cellPosition);
if (existingPlant != null)
{
continue;
}
}
// 检查密度
if (UnityEngine.Random.value >= _config.Density)
{
continue;
}
map.plantTilemap.SetTile(cellPosition, plantTile);
}
// 每处理完一列,就让出控制权给主线程
await Task.Yield();
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b91fa983e7324f6aa9399fa8aa1dc642
timeCreated: 1758020532