using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using BaseGames.Core.Events; namespace BaseGames.World.Map { /// /// 单个房间的地图数据 SO(架构 15_MapShopModule §1.1)。 /// 资产路径: Assets/_Game/Data/Map/Rooms/Room_{RoomId}.asset /// [CreateAssetMenu(menuName = "BaseGames/World/Map/RoomData")] public class MapRoomDataSO : ScriptableObject { [Header("基础信息")] public string RoomId; // 与场景名一致,如 "Room_Forest_01" public string RegionId; // 所属区域,如 "Forest" public string DisplayName; // 可选,地图 Tooltip [Header("地图布局(格子坐标,单位:格)")] public Vector2Int GridPosition; // 左下角坐标 public Vector2Int GridSize; // 宽×高(格) [Header("房间轮廓纹理")] public Texture2D RoomOutlineTex; // 用于地图 UI 显示房间形状(可空,回退到矩形格子) [Header("出口信息")] public RoomExitData[] Exits; // 该房间所有出口定义 [Header("特殊标记")] public bool IsBossRoom; public bool IsSavePoint; public bool IsShop; public Sprite MapIconOverride; // null = 按 isXxx 自动选择图标 [Header("流式加载")] [Tooltip("此房间场景资产的预估内存(KB)。\n" + "在 Profiler 中测量场景实际内存后填入,供流式管理器执行内存预算检查使用。\n" + "建议在关卡内容基本定型后更新此值。0 = 未填写,将跳过内存预算检查。")] [Min(0)] public int EstimatedMemoryKB; private void OnValidate() { // 保证 GridSize 每轴最小为 1,防止零尺寸房间导致碰撞和渲染异常 GridSize = new Vector2Int(Mathf.Max(1, GridSize.x), Mathf.Max(1, GridSize.y)); } } [Serializable] public struct RoomExitData { public string TargetRoomId; // 连接的目标房间 ID public Vector2Int ExitGridPos; // 出口在格子地图上的位置 public ExitDirection Direction; // 出口方向 [Tooltip("此出口触发的过渡类型。\n" + "Seamless:无缝切换(同区域相邻房间首选);\n" + "AtmosphericFade:短暂淡出 + 区域名提示(跨大区域边界首选)。")] public TransitionType PreferredTransitionType; } public enum ExitDirection { Up, Down, Left, Right } // ─── 全局地图数据库 ────────────────────────────────────────────────────────── /// /// 全局地图数据库 SO(编辑器配置一次;架构 15_MapShopModule §1.1)。 /// 资产路径: Assets/_Game/Data/Map/MapDatabase.asset /// [CreateAssetMenu(menuName = "BaseGames/World/Map/MapDatabase")] public class MapDatabaseSO : ScriptableObject { public MapRoomDataSO[] AllRooms; private Dictionary _index; /// 运行时快速查找(首次调用时建立索引)。 public MapRoomDataSO GetRoom(string roomId) { if (_index == null) { if (AllRooms == null) return null; _index = AllRooms.Where(r => r != null) .ToDictionary(r => r.RoomId); } _index.TryGetValue(roomId, out var r); return r; } private void OnDisable() => _index = null; // SO 卸载时清理缓存 private void OnValidate() => _index = null; // 编辑器中修改 AllRooms 后强制重建索引 // ── 配置验证 ────────────────────────────────────────────────────────── /// /// 检查数据库中的常见配置错误(RoomId 重复、格子重叠、出口悬空)。 /// 编辑器侧调用;运行时不应调用(有 O(N²) 开销)。 /// 返回错误描述列表;空列表表示无错误。 /// public List ValidateAll() { var errors = new List(); if (AllRooms == null) return errors; // ① null / 空 RoomId for (int i = 0; i < AllRooms.Length; i++) { if (AllRooms[i] == null) { errors.Add($"AllRooms[{i}] 为 null"); continue; } if (string.IsNullOrEmpty(AllRooms[i].RoomId)) errors.Add($"AllRooms[{i}]({AllRooms[i].name})RoomId 为空"); } // ② RoomId 重复 var seenIds = new Dictionary(); foreach (var room in AllRooms) { if (room == null || string.IsNullOrEmpty(room.RoomId)) continue; if (seenIds.TryGetValue(room.RoomId, out var first)) errors.Add($"RoomId '{room.RoomId}' 重复({first} 与 {room.name})"); else seenIds[room.RoomId] = room.name; } // ③ 格子重叠 var cellOwner = new Dictionary(); foreach (var room in AllRooms) { if (room == null) continue; for (int x = 0; x < room.GridSize.x; x++) for (int y = 0; y < room.GridSize.y; y++) { var cell = new Vector2Int(room.GridPosition.x + x, room.GridPosition.y + y); if (cellOwner.TryGetValue(cell, out var other)) errors.Add($"'{room.RoomId}' 与 '{other}' 在格子 {cell} 重叠"); else cellOwner[cell] = room.RoomId; } } // ④ 出口目标不存在(单向验证) var validIds = new HashSet(seenIds.Keys); foreach (var room in AllRooms) { if (room?.Exits == null) continue; foreach (var exit in room.Exits) if (!string.IsNullOrEmpty(exit.TargetRoomId) && !validIds.Contains(exit.TargetRoomId)) errors.Add($"'{room.RoomId}' 出口指向不存在的房间 '{exit.TargetRoomId}'"); } return errors; } } }