Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Managers/TileManager.cs

206 lines
9.3 KiB
C#
Raw Normal View History

using Data;
2025-08-28 16:20:24 +08:00
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Tilemaps;
using Utils;
namespace Managers
{
/// <summary>
/// 瓦片管理器,用于加载、初始化和管理瓦片资源。
/// </summary>
/// <remarks>
/// 实现 <see cref="ILaunchManager"/> 接口,以便由 Launcher 统一管理生命周期。
/// </remarks>
public class TileManager : Singleton<TileManager>, ILaunchManager
{
// ------------- ILaunchManager 接口实现 -------------
/// <summary>
/// 获取当前瓦片管理器加载步骤的描述。
/// </summary>
public string StepDescription => "正在切割瓦片";
/// <summary>
/// 初始化瓦片管理器。
/// </summary>
/// <remarks>
/// 加载所有瓦片定义、纹理映射表,并生成对应的瓦片对象。
/// 此方法是幂等的,多次调用只会在第一次执行实际初始化逻辑。
/// </remarks>
public void Init()
{
// 如果已经被初始化,则直接返回
if (tileToTileBaseMapping.Count > 0)
{
return;
}
2025-08-28 16:20:24 +08:00
// 确保依赖的 PackagesImageManager 已初始化。
// 虽然 Launcher 会按顺序初始化,但这里做一次检查和调用,
// 可以防止其他地方直接调用 TileManager.Instance.Init() 时,
// 其依赖未准备好的情况。PackagesImageManager 也应该是幂等的。
PackagesImageManager.Instance.Init();
2025-08-28 16:20:24 +08:00
var imagePack = Managers.PackagesImageManager.Instance;
2025-08-28 16:20:24 +08:00
// 获取所有瓦片定义
var tileDefs = DefineManager.Instance.QueryDefinesByType<TileDef>();
for (var i = 0; i < tileDefs.Length; i++)
{
// 使用 TryAdd 避免重复添加,并处理潜在的定义冲突
if (!tileID.TryAdd(tileDefs[i].name, i))
{
Debug.LogWarning($"<color=orange>瓦片定义 '{tileDefs[i].name}' 的名称重复。</color> 将忽略后续定义。");
}
}
2025-08-28 16:20:24 +08:00
// 处理瓦片纹理映射表定义
var tileTextureMappingDefs = DefineManager.Instance.QueryDefinesByType<TileMappingTableDef>();
foreach (var mappingTableDef in tileTextureMappingDefs)
{
var packName = DefineManager.Instance.GetDefinePackageName(mappingTableDef);
foreach (var keyVal in mappingTableDef.tileDict)
{
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;
}
// 创建瓦片实例并存储到映射表中
2025-08-28 16:20:24 +08:00
var newTile = CreateTileInstance(sprite);
tileToTileBaseMapping[tileKey] = newTile;
2025-08-28 16:20:24 +08:00
// 同样检查 tileBaseMapping 的重复性
if (tileBaseMapping.ContainsKey(spriteName))
{
Debug.LogWarning($"<color=orange>来自 '{packName}' 定义的 TileMappingTableDef 键值 '{spriteName}' 在 tileBaseMapping 中存在重复。</color> 仅保留第一个实例。");
}
else
{
tileBaseMapping[spriteName] = newTile;
}
}
}
}
/// <summary>
/// 清理瓦片管理器,释放所有加载的瓦片资源和数据。
/// </summary>
/// <remarks>
/// 用于重载游戏时彻底重置瓦片系统。
/// </remarks>
public void Clear()
{
// 销毁所有动态创建的 Tile ScriptableObject
foreach (var tileBase in tileToTileBaseMapping.Values)
{
if (tileBase is Tile tile) // 确认是 Unity.Tilemap.Tile 类型
{
// 在运行时,直接 Destroy 会导致一些警告,但对于动态创建的 ScriptableObject
// 这是清理内存的正确方法。如果此方法在编辑器模式且不在Play模式下调用
// 应该使用 DestroyImmediate。考虑到此方法通常由 Launcher 在Play模式下调用
// Destroy 即可。
Object.Destroy(tile);
}
}
foreach (var tileBase in tileBaseMapping.Values)
{
if (tileBase is Tile tile && !tileToTileBaseMapping.ContainsValue(tile))
{
// 销毁可能在 tileBaseMapping 中但不在 tileToTileBaseMapping 中的额外 Tile 实例
// (尽管根据 Init 逻辑,它们应该是指向相同的实例)
Object.Destroy(tile);
}
}
2025-08-28 16:20:24 +08:00
tileBaseMapping.Clear();
tileToTileBaseMapping.Clear();
tileID.Clear();
}
// ------------- ILaunchManager 接口实现结束 -------------
2025-08-28 16:20:24 +08:00
/// <summary>
/// 存储瓦片名称与 <see cref="TileBase"/> 对象的映射关系。
/// </summary>
public Dictionary<string, TileBase> tileBaseMapping = new();
2025-08-28 16:20:24 +08:00
/// <summary>
/// 存储瓦片组合索引与 <see cref="TileBase"/> 对象的映射关系。
/// 索引由四个整数组成,表示瓦片的组合方式。
/// </summary>
public Dictionary<(int, int, int, int), TileBase> tileToTileBaseMapping = new();
2025-08-28 16:20:24 +08:00
/// <summary>
/// 存储瓦片名称与唯一 ID 的映射关系。
/// </summary>
public Dictionary<string, int> tileID = new();
2025-08-28 16:20:24 +08:00
// 移除了 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;
}
}
}