using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.World.Map { /// /// 运行时地图管理器(架构 15_MapShopModule §1.2)。 /// 挂在 Persistent 场景 [GameManagers] 下,通过事件驱动记录已探索/已完整地图的房间。 /// 实现 ISaveable 持久化探索进度。 /// [DefaultExecutionOrder(-700)] public class MapManager : MonoBehaviour, ISaveable, IMapService { [SerializeField] private MapDatabaseSO _database; [Header("Event Channels")] [SerializeField] private StringEventChannelSO _onRoomEntered; // 订阅 EVT_RoomEntered [Tooltip("房间被探索/标记时广播(Explored/Mapped);地图 UI 已改用 C# 事件,此通道保留供地图外部系统订阅(如成就、音效)。")] [SerializeField] private StringEventChannelSO _onMapUpdated; // 发布:房间发现时(地图 UI 内当前无订阅者) [SerializeField] private StringEventChannelSO _onRegionChanged; // 发布:玩家首次进入新区域时(EVT_RegionChanged) // 三级可见性: // Unknown → 未进入过(默认) // Explored → 进入过(显示轮廓/格子) // Mapped → 完整地图信息(购买 MapFragment 或存档点揭示) private HashSet _exploredRooms = new(); private HashSet _mappedRooms = new(); private string _currentRegionId; private int _totalRoomCount = -1; // -1 = 未缓存;OnLoad 后重置 private Dictionary _regionCache; // R11-N6 GetRoomsByRegion 结果缓存 private bool _isDuplicate; // Awake 检测到重复实例时置位,OnEnable/OnDisable 提前 return private readonly CompositeDisposable _subs = new(); private void Awake() { if (ServiceLocator.GetOrDefault() != null) { _isDuplicate = true; Destroy(gameObject); return; } ServiceLocator.Register(this); } private void OnEnable() { if (_isDuplicate) return; _onRoomEntered?.Subscribe(OnRoomEntered).AddTo(_subs); ServiceLocator.GetOrDefault()?.Register(this); } private void OnDisable() { if (_isDuplicate) return; _subs.Clear(); ServiceLocator.GetOrDefault()?.Unregister(this); } // ── ISaveable ───────────────────────────────────────────────────────── public void OnSave(SaveData data) { data.Map.ExploredRooms = new HashSet(_exploredRooms); data.Map.MappedRooms = new HashSet(_mappedRooms); data.Map.LastRegionId = _currentRegionId; } public void OnLoad(SaveData data) { _exploredRooms = data.Map.ExploredRooms != null ? new HashSet(data.Map.ExploredRooms) : new HashSet(); _mappedRooms = data.Map.MappedRooms != null ? new HashSet(data.Map.MappedRooms) : new HashSet(); _currentRegionId = data.Map.LastRegionId; // 恢复区域 ID,避免读档后首次进房误触发 EVT_RegionChanged _totalRoomCount = -1; // 强制下次调用 GetExplorationProgress 时重新计数 // 读档后广播:UI 仅需轻量刷新(不重建结构);订阅 OnExplorationChanged 的 UI 会 RefreshAllCells OnExplorationChanged?.Invoke(); } // ── 事件驱动房间发现 ────────────────────────────────────────────────── private void OnRoomEntered(string roomId) { if (string.IsNullOrEmpty(roomId)) return; bool changed = _exploredRooms.Add(roomId); if (changed) { _onMapUpdated?.Raise(roomId); OnExplorationChanged?.Invoke(); } // 区域变化检测:RegionId 非空且与上一次不同时广播 EVT_RegionChanged var regionId = _database?.GetRoom(roomId)?.RegionId; if (!string.IsNullOrEmpty(regionId) && regionId != _currentRegionId) { _currentRegionId = regionId; _onRegionChanged?.Raise(regionId); } } /// /// 标记为已获取地图碎片信息(购买 MapFragment SO 触发)。 /// 仅写入 _mappedRooms;若玩家尚未亲自踏入该房间则显示为灰色轮廓(Mapped), /// 亲自踏入后(Explored)优先级更高,显示为白色。 /// public void SetMapped(string roomId) { if (string.IsNullOrEmpty(roomId)) return; if (_mappedRooms.Add(roomId)) { _onMapUpdated?.Raise(roomId); OnRoomMapped?.Invoke(roomId); OnExplorationChanged?.Invoke(); } } /// public void SetMappedBatch(IEnumerable roomIds) { if (roomIds == null) return; bool anyAdded = false; foreach (var roomId in roomIds) { if (string.IsNullOrEmpty(roomId)) continue; if (_mappedRooms.Add(roomId)) { _onMapUpdated?.Raise(roomId); OnRoomMapped?.Invoke(roomId); anyAdded = true; } } if (anyAdded) OnExplorationChanged?.Invoke(); } // ── 查询 API ────────────────────────────────────────────────────────── public bool IsExplored(string roomId) => _exploredRooms.Contains(roomId); public bool IsMapped(string roomId) => _mappedRooms.Contains(roomId); public string CurrentRegionId => _currentRegionId; public MapDatabaseSO Database => _database; public int ExploredRoomCount => _exploredRooms.Count; public float GetExplorationProgress() { if (_database?.AllRooms == null || _database.AllRooms.Length == 0) return 0f; if (_totalRoomCount < 0) _totalRoomCount = _database.AllRooms.Count(r => r != null); return _totalRoomCount > 0 ? Mathf.Clamp01((float)_exploredRooms.Count / _totalRoomCount) : 0f; } public MapRoomDataSO[] GetRoomsByRegion(string regionId) { if (string.IsNullOrEmpty(regionId) || _database?.AllRooms == null) return System.Array.Empty(); // R11-N6 懒加载缓存,避免每帧 LINQ 扫描全量房间数组 _regionCache ??= new Dictionary(); if (!_regionCache.TryGetValue(regionId, out var cached)) { cached = _database.AllRooms.Where(r => r != null && r.RegionId == regionId).ToArray(); _regionCache[regionId] = cached; } return cached; } // ── 数据库热更事件 ──────────────────────────────────────────────────── /// public event Action OnDatabaseChanged; /// public event Action OnExplorationChanged; /// public event Action OnRoomMapped; /// public void NotifyDatabaseChanged() { _database?.InvalidateIndex(); _totalRoomCount = -1; _regionCache = null; // R11-N6 数据库变更时清空区域缓存 OnDatabaseChanged?.Invoke(); } private void OnDestroy() { if (_isDuplicate) return; ServiceLocator.Unregister(this); } } }