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

206 lines
9.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Data;
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;
}
// 确保依赖的 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++)
{
// 使用 TryAdd 避免重复添加,并处理潜在的定义冲突
if (!tileID.TryAdd(tileDefs[i].name, i))
{
Debug.LogWarning($"<color=orange>瓦片定义 '{tileDefs[i].name}' 的名称重复。</color> 将忽略后续定义。");
}
}
// 处理瓦片纹理映射表定义
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;
}
// 创建瓦片实例并存储到映射表中
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;
}
}
}
}
/// <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);
}
}
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;
}
}
}