mirror of
http://47.107.252.169:3000/Roguelite-Game-Developing-Team/Gen_Hack-and-Slash-Roguelite.git
synced 2025-11-20 04:17:13 +08:00
(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:
@@ -1,205 +1,189 @@
|
||||
// Managers/TileManager.cs
|
||||
|
||||
using System;
|
||||
using Data;
|
||||
using Utils;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Map;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Tilemaps;
|
||||
using Utils;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace Managers
|
||||
{
|
||||
/// <summary>
|
||||
/// 瓦片管理器,用于加载、初始化和管理瓦片资源。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 实现 <see cref="ILaunchManager"/> 接口,以便由 Launcher 统一管理生命周期。
|
||||
/// </remarks>
|
||||
public class TileManager : Singleton<TileManager>, ILaunchManager
|
||||
{
|
||||
// ------------- ILaunchManager 接口实现 -------------
|
||||
public string StepDescription => "正在加载瓦片";
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前瓦片管理器加载步骤的描述。
|
||||
/// </summary>
|
||||
public string StepDescription => "正在切割瓦片";
|
||||
// 缓存所有根据定义生成的RuleTile实例
|
||||
private Dictionary<string, RuleTile> _cachedTiles = new();
|
||||
|
||||
// 地图生成器工作类映射,Key: MapGeneratorDef.defName, Value: MapGeneratorWorkClassBase
|
||||
private Dictionary<string,MapGeneratorWorkClassBase> _mapGeneratorWorkClassMap = new();
|
||||
|
||||
/// <summary>
|
||||
/// 初始化瓦片管理器。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 加载所有瓦片定义、纹理映射表,并生成对应的瓦片对象。
|
||||
/// 此方法是幂等的,多次调用只会在第一次执行实际初始化逻辑。
|
||||
/// </remarks>
|
||||
public void Init()
|
||||
{
|
||||
// 如果已经被初始化,则直接返回
|
||||
if (tileToTileBaseMapping.Count > 0)
|
||||
{
|
||||
// 如果已缓存瓦片,则直接返回
|
||||
if (_cachedTiles.Any())
|
||||
return;
|
||||
}
|
||||
|
||||
// 确保依赖的 PackagesImageManager 已初始化。
|
||||
// 虽然 Launcher 会按顺序初始化,但这里做一次检查和调用,
|
||||
// 可以防止其他地方直接调用 TileManager.Instance.Init() 时,
|
||||
// 其依赖未准备好的情况。PackagesImageManager 也应该是幂等的。
|
||||
PackagesImageManager.Instance.Init();
|
||||
|
||||
var imagePack = Managers.PackagesImageManager.Instance;
|
||||
|
||||
// 获取所有瓦片定义
|
||||
|
||||
var tileDefs = DefineManager.Instance.QueryDefinesByType<TileDef>();
|
||||
for (var i = 0; i < tileDefs.Length; i++)
|
||||
|
||||
// 第一次遍历:创建RuleTile实例,并填充除特定瓦片引用(specificTileName)外的所有简单字段
|
||||
foreach (var def in tileDefs)
|
||||
{
|
||||
// 使用 TryAdd 避免重复添加,并处理潜在的定义冲突
|
||||
if (!tileID.TryAdd(tileDefs[i].name, i))
|
||||
if (string.IsNullOrEmpty(def.defName))
|
||||
{
|
||||
Debug.LogWarning($"<color=orange>瓦片定义 '{tileDefs[i].name}' 的名称重复。</color> 将忽略后续定义。");
|
||||
Debug.LogWarning($"发现未定义名称或defName的TileDef,已跳过。");
|
||||
continue;
|
||||
}
|
||||
var tileName = def.defName;
|
||||
var ruleTile = ScriptableObject.CreateInstance<RuleTile>();
|
||||
ruleTile.name = tileName; // 设置ScriptableObject的名称
|
||||
|
||||
ruleTile.m_DefaultColliderType = def.collider;
|
||||
ruleTile.m_DefaultSprite = PackagesImageManager.Instance.GetSprite(def.texture);
|
||||
ruleTile.m_TilingRules = GetTileRules(def.rules);
|
||||
|
||||
_cachedTiles[tileName] = ruleTile;
|
||||
}
|
||||
|
||||
// 处理瓦片纹理映射表定义
|
||||
var tileTextureMappingDefs = DefineManager.Instance.QueryDefinesByType<TileMappingTableDef>();
|
||||
foreach (var mappingTableDef in tileTextureMappingDefs)
|
||||
var generatorDefs = DefineManager.Instance.QueryDefinesByType<MapGeneratorDef>();
|
||||
foreach (var mapGeneratorDef in generatorDefs)
|
||||
{
|
||||
var packName = DefineManager.Instance.GetDefinePackageName(mappingTableDef);
|
||||
foreach (var keyVal in mappingTableDef.tileDict)
|
||||
var workClass = StringUtils.CreateMapGeneratorInstance(mapGeneratorDef.workClass);
|
||||
if(workClass == null)
|
||||
{
|
||||
var compositeKey = keyVal.Key; // 例如 "Dirt_Grass_Dirt_Dirt"
|
||||
var spriteName = keyVal.Value; // 例如 "Dirt_Sprite_001"
|
||||
var parts = compositeKey.Split('_');
|
||||
if (parts.Length != 4)
|
||||
{
|
||||
Debug.LogError($"<color=red>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{compositeKey}' 格式不合法!</color>\n应为 '[瓦片名称_瓦片名称_瓦片名称_瓦片名称]' 的格式。");
|
||||
continue;
|
||||
}
|
||||
// 尝试获取四个部分的瓦片ID
|
||||
if (!tileID.TryGetValue(parts[0], out var k1) ||
|
||||
!tileID.TryGetValue(parts[1], out var k2) ||
|
||||
!tileID.TryGetValue(parts[2], out var k3) ||
|
||||
!tileID.TryGetValue(parts[3], out var k4))
|
||||
{
|
||||
Debug.LogError($"<color=red>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{compositeKey}' 中存在未定义的瓦片名称。</color>");
|
||||
continue;
|
||||
}
|
||||
// 获取对应精灵
|
||||
var sprite = imagePack.GetSprite(mappingTableDef.packID, spriteName);
|
||||
if (sprite == null)
|
||||
{
|
||||
Debug.LogError($"<color=red>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{spriteName}' 中存在未定义的图片名称。</color>");
|
||||
continue;
|
||||
}
|
||||
var tileKey = (k1, k2, k3, k4);
|
||||
// 检查是否存在重复索引
|
||||
if (tileToTileBaseMapping.ContainsKey(tileKey))
|
||||
{
|
||||
Debug.LogWarning($"<color=orange>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{tileKey}' (对应原始键 '{compositeKey}') 存在重复索引,将忽略重复项。</color>");
|
||||
continue;
|
||||
}
|
||||
// 创建瓦片实例并存储到映射表中
|
||||
var newTile = CreateTileInstance(sprite);
|
||||
tileToTileBaseMapping[tileKey] = newTile;
|
||||
|
||||
// 同样检查 tileBaseMapping 的重复性
|
||||
if (tileBaseMapping.ContainsKey(spriteName))
|
||||
{
|
||||
Debug.LogWarning($"<color=orange>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{spriteName}' 在 tileBaseMapping 中存在重复。</color> 仅保留第一个实例。");
|
||||
}
|
||||
else
|
||||
{
|
||||
tileBaseMapping[spriteName] = newTile;
|
||||
}
|
||||
Debug.LogWarning($"无法为地图生成器 '{mapGeneratorDef.defName}' 创建工作类 '{mapGeneratorDef.workClass}',已跳过。");
|
||||
continue;
|
||||
}
|
||||
workClass.Init(mapGeneratorDef.value);
|
||||
_mapGeneratorWorkClassMap[mapGeneratorDef.defName] = workClass;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清理瓦片管理器,释放所有加载的瓦片资源和数据。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 用于重载游戏时彻底重置瓦片系统。
|
||||
/// </remarks>
|
||||
public void Clear()
|
||||
{
|
||||
// 销毁所有动态创建的 Tile ScriptableObject
|
||||
foreach (var tileBase in tileToTileBaseMapping.Values)
|
||||
foreach (var tile in _cachedTiles.Values)
|
||||
{
|
||||
if (tileBase is Tile tile) // 确认是 Unity.Tilemap.Tile 类型
|
||||
if (tile != null)
|
||||
{
|
||||
// 在运行时,直接 Destroy 会导致一些警告,但对于动态创建的 ScriptableObject,
|
||||
// 这是清理内存的正确方法。如果此方法在编辑器模式且不在Play模式下调用,
|
||||
// 应该使用 DestroyImmediate。考虑到此方法通常由 Launcher 在Play模式下调用,
|
||||
// Destroy 即可。
|
||||
Object.Destroy(tile);
|
||||
Object.Destroy(tile);
|
||||
}
|
||||
}
|
||||
foreach (var tileBase in tileBaseMapping.Values)
|
||||
_cachedTiles.Clear();
|
||||
_mapGeneratorWorkClassMap.Clear(); // 清空地图生成器工作类映射
|
||||
}
|
||||
|
||||
private static List<RuleTile.TilingRule> GetTileRules(RuleTileRuleDef[] rules)
|
||||
{
|
||||
return rules.Select(GetTileRule).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将自定义的 RuleTileRuleDef 转换为 Unity 的 RuleTile.TilingRule。
|
||||
/// </summary>
|
||||
/// <param name="ruleDef">自定义的 RuleTileRuleDef 规则定义。</param>
|
||||
/// <returns>转换后的 RuleTile.TilingRule 实例。</returns>
|
||||
private static RuleTile.TilingRule GetTileRule(RuleTileRuleDef ruleDef)
|
||||
{
|
||||
var tilingRule = new RuleTile.TilingRule
|
||||
{
|
||||
if (tileBase is Tile tile && !tileToTileBaseMapping.ContainsValue(tile))
|
||||
m_Sprites = PackagesImageManager.Instance.GetSprites(ruleDef.animationTextures.ToArray()),
|
||||
m_Output = ruleDef.outputType,
|
||||
m_ColliderType = ruleDef.outputCollider,
|
||||
m_PerlinScale = ruleDef.chance,
|
||||
m_MaxAnimationSpeed = ruleDef.animationSpeed,
|
||||
m_MinAnimationSpeed = ruleDef.animationSpeed,
|
||||
m_RuleTransform = ruleDef.transform
|
||||
};
|
||||
|
||||
var tilePositions = new List<Vector3Int>();
|
||||
for (var i = 0; i < ruleDef.neighborConditions.Length && i < 8; i++)
|
||||
{
|
||||
switch(ruleDef.neighborConditions[i])
|
||||
{
|
||||
// 销毁可能在 tileBaseMapping 中但不在 tileToTileBaseMapping 中的额外 Tile 实例
|
||||
// (尽管根据 Init 逻辑,它们应该是指向相同的实例)
|
||||
Object.Destroy(tile);
|
||||
case RuleTileRuleDef.NeighborConditionType.NotThis:
|
||||
tilingRule.m_Neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.NotThis);
|
||||
break;
|
||||
case RuleTileRuleDef.NeighborConditionType.This:
|
||||
tilingRule.m_Neighbors.Add(RuleTile.TilingRuleOutput.Neighbor.This);
|
||||
break;
|
||||
case RuleTileRuleDef.NeighborConditionType.Any:
|
||||
default:
|
||||
// 忽略 Any 类型,因为其不会添加到m_Neighbors中
|
||||
continue;
|
||||
}
|
||||
|
||||
tilePositions.Add(tilingRule.m_NeighborPositions[i]);
|
||||
}
|
||||
tilingRule.m_NeighborPositions = tilePositions;
|
||||
return tilingRule;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据名称获取一个 RuleTile 实例。
|
||||
/// </summary>
|
||||
/// <param name="tileName">瓦片的名称 (TileDef.defName)</param>
|
||||
/// <returns>对应的 RuleTile 实例,如果找不到则返回 null。</returns>
|
||||
public TileBase GetTile(string tileName)
|
||||
{
|
||||
if (_cachedTiles.TryGetValue(tileName, out var tile))
|
||||
{
|
||||
return tile;
|
||||
}
|
||||
Debug.LogWarning($"瓦片 '{tileName}' 未在TileManager缓存中找到。");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 应用指定的地图生成器,现在是非阻塞的异步操作。
|
||||
/// </summary>
|
||||
/// <param name="generatorName">要应用的生成器名称。</param>
|
||||
/// <param name="mapGenerator">可选:指定要使用的 MapGenerator 实例。如果为 null,将尝试从 Program.Instance.FocusedDimension 获取。</param>
|
||||
/// <returns>一个表示异步操作完成的 Task。</returns>
|
||||
public async Task ApplyMapGenerator(string generatorName, MapGenerator mapGenerator = null) // <-- 返回 Task,并标记为 async
|
||||
{
|
||||
if (mapGenerator == null)
|
||||
{
|
||||
// 尝试从 Program.Instance.FocusedDimension 获取 mapGenerator
|
||||
// 假设 Program 和 FocusedDimension 结构存在
|
||||
mapGenerator = Program.Instance?.FocusedDimension?.mapGenerator;
|
||||
if (mapGenerator == null)
|
||||
{
|
||||
Debug.LogError($"ApplyMapGenerator: 无法找到地图生成器 '{generatorName}' 对应的 mapGenerator。Program.Instance、FocusedDimension 或其 mapGenerator 可能为空。");
|
||||
return; // async Task 方法直接 return; 意味着返回 Task.CompletedTask
|
||||
}
|
||||
}
|
||||
|
||||
tileBaseMapping.Clear();
|
||||
tileToTileBaseMapping.Clear();
|
||||
tileID.Clear();
|
||||
}
|
||||
// ------------- ILaunchManager 接口实现结束 -------------
|
||||
|
||||
/// <summary>
|
||||
/// 存储瓦片名称与 <see cref="TileBase"/> 对象的映射关系。
|
||||
/// </summary>
|
||||
public Dictionary<string, TileBase> tileBaseMapping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 存储瓦片组合索引与 <see cref="TileBase"/> 对象的映射关系。
|
||||
/// 索引由四个整数组成,表示瓦片的组合方式。
|
||||
/// </summary>
|
||||
public Dictionary<(int, int, int, int), TileBase> tileToTileBaseMapping = new();
|
||||
|
||||
/// <summary>
|
||||
/// 存储瓦片名称与唯一 ID 的映射关系。
|
||||
/// </summary>
|
||||
public Dictionary<string, int> tileID = new();
|
||||
|
||||
// 移除了 TileManager 内部的 Reload() 方法,因为它将被 Launcher 的 Clear() + Init() 流程取代。
|
||||
|
||||
/// <summary>
|
||||
/// 将精灵加载为新的 <see cref="Tile"/> ScriptableObject 实例。
|
||||
/// 这是一个内部辅助方法。
|
||||
/// </summary>
|
||||
/// <param name="sprite">要加载为瓦片的精灵。</param>
|
||||
/// <param name="colliderType">瓦片的碰撞体类型,默认为 <see cref="Tile.ColliderType.None"/>。</param>
|
||||
/// <returns>返回新创建的 <see cref="TileBase"/> 对象。</returns>
|
||||
private TileBase CreateTileInstance(Sprite sprite, Tile.ColliderType colliderType = Tile.ColliderType.None)
|
||||
{
|
||||
var newTile = ScriptableObject.CreateInstance<Tile>();
|
||||
newTile.sprite = sprite;
|
||||
newTile.color = Color.white;
|
||||
newTile.colliderType = colliderType;
|
||||
return newTile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定组合索引的瓦片对象。
|
||||
/// </summary>
|
||||
/// <param name="tileKey">由四个整数组成的瓦片组合索引。</param>
|
||||
/// <returns>对应的 <see cref="TileBase"/> 对象,如果不存在则返回 null。</returns>
|
||||
public TileBase GetTile((int, int, int, int) tileKey)
|
||||
{
|
||||
tileToTileBaseMapping.TryGetValue(tileKey, out var tile);
|
||||
return tile;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定精灵名称的瓦片对象。
|
||||
/// </summary>
|
||||
/// <param name="spriteName">精灵的名称。</param>
|
||||
/// <returns>对应的 <see cref="TileBase"/> 对象,如果不存在则返回 null。</returns>
|
||||
public TileBase GetTileBySpriteName(string spriteName)
|
||||
{
|
||||
tileBaseMapping.TryGetValue(spriteName, out var tile);
|
||||
return tile;
|
||||
if (_mapGeneratorWorkClassMap == null)
|
||||
{
|
||||
Debug.LogError($"ApplyMapGenerator: _mapGeneratorWorkClassMap 未初始化,无法找到生成器 '{generatorName}'。");
|
||||
return;
|
||||
}
|
||||
if (_mapGeneratorWorkClassMap.TryGetValue(generatorName, out var mapGeneratorWorkClass))
|
||||
{
|
||||
if (mapGeneratorWorkClass == null)
|
||||
{
|
||||
Debug.LogError($"ApplyMapGenerator: 为生成器 '{generatorName}' 找到了一个空的 IMapGeneratorWorkClass 实例。这表明 _mapGeneratorWorkClassMap 配置存在问题。");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
await mapGeneratorWorkClass.Process(mapGenerator); // <-- 使用 await 调用异步 Process 方法
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"ApplyMapGenerator: 地图生成器 '{generatorName}' 异步处理过程中发生错误: {ex.Message}");
|
||||
// 可以在这里根据需要决定是否重新抛出异常
|
||||
throw; // 抛出异常以通知调用者,或者根据实际情况选择捕获并完全处理
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"ApplyMapGenerator: 未找到名为 '{generatorName}' 的 IMapGeneratorWorkClass。请确保该生成器已在 _mapGeneratorWorkClassMap 中注册。");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user