Files
zeling_v2/Assets/_Game/Scripts/World/Map/MapPin.cs
Joywayer f74d7f1877 Add independent review reports for Minimap system (Rounds 8, 9, and 26)
- Round 8 report highlights improvements in architecture, editor usability, and data robustness, with a total score of 80/100.
- Round 9 report focuses on editor extension capabilities, identifying issues with room data indexing and layout editing, resulting in a score of 76/100.
- Round 26 report evaluates the system against commercial standards, noting new issues and confirming previous fixes, with a score of 95.8/100.
2026-05-25 23:15:12 +08:00

132 lines
5.5 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.
// NOTE: 此文件包含 MapPinManager 类,但文件名为 MapPin.cs历史遗留Unity .meta 绑定限制不可安全重命名)。
// 如需搜索,请搜索 "MapPinManager" 类名,而非文件名。
// PinSpriteEntry 已迁移到 MapPinConfigSO.csR12-N3
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Save;
namespace BaseGames.World.Map
{
/// <summary>
/// 地图自定义标记管理器(架构 15_MapShopModule §1.5)。
/// 实现 <see cref="ISaveable"/> 和 <see cref="IPinService"/>,通过 ServiceLocator 对外暴露。
/// <para>
/// <see cref="PinsVersion"/> 每次 Pin 集合变化时自增外部消费方MapPanel
/// 可通过版本号判断是否需要重绘,避免无效 Instantiate。
/// </para>
/// MapPin/PinType 数据类定义在 SaveData.csBaseGames.Core.Save避免循环依赖。
/// </summary>
[DefaultExecutionOrder(-500)] // 晚于 MapPlayerTracker(-600),早于默认 0确保 IPinService 在 UI SubscribeServices 前已注册
public class MapPinManager : MonoBehaviour, ISaveable, IPinService
{
private List<MapPin> _pins = new();
public IReadOnlyList<MapPin> Pins => _pins;
/// <summary>每次 Pin 集合发生变化时自增;外部消费方通过此版本号实现脏检查。</summary>
public int PinsVersion { get; private set; }
private IMapService _mapSvc; // Start 中缓存,避免 CreatePin 每次调用 ServiceLocator
private bool _isDuplicate;
private void Awake()
{
if (ServiceLocator.GetOrDefault<IPinService>() != null)
{
_isDuplicate = true;
Destroy(gameObject);
return;
}
// 服务注册迁移到 Awake/OnDestroy与 MapManager / MapPlayerTracker 对齐)
// 避免 OnEnable/OnDisable 模式下反复 Register/Unregister 导致其他模块持有过期引用
ServiceLocator.Register<IPinService>(this);
}
private void Start()
{
if (_isDuplicate) return;
_mapSvc = ServiceLocator.GetOrDefault<IMapService>();
}
private void OnEnable()
{
if (_isDuplicate) return;
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
}
private void OnDisable()
{
if (_isDuplicate) return;
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
}
private void OnDestroy()
{
if (_isDuplicate) return;
ServiceLocator.Unregister<IPinService>(this);
}
// ── 公共 API ──────────────────────────────────────────────────────────
public void AddPin(MapPin pin)
{
if (pin != null) { _pins.Add(pin); PinsVersion++; }
}
public void RemovePin(MapPin pin)
{
if (_pins.Remove(pin)) PinsVersion++;
}
/// <summary>
/// 便捷方法:用枚举类型创建并添加标记。
/// <para>
/// 参数会做安全校验roomId 为空时返回 nullnormX/normY 自动 Clamp01
/// note 限制 64 字符;可选检查 roomId 是否存在于数据库(不存在时 Warning但仍允许创建以兼容运行时尚未加载的数据库场景
/// </para>
/// </summary>
public MapPin CreatePin(string roomId, float normX, float normY,
PinType type = PinType.Marker, string note = "")
{
if (string.IsNullOrEmpty(roomId))
{
Debug.LogWarning("[MapPinManager] CreatePin 失败roomId 为空。");
return null;
}
// 可选 RoomId 存在性验证:数据库已就绪时检查,未就绪时跳过(不阻塞)
if (_mapSvc?.Database != null && _mapSvc.Database.GetRoom(roomId) == null)
Debug.LogWarning($"[MapPinManager] CreatePin: roomId '{roomId}' 在数据库中不存在,标记将不会渲染。");
if (!string.IsNullOrEmpty(note) && note.Length > 64)
note = note.Substring(0, 64);
var pin = new MapPin
{
RoomId = roomId,
NormalizedPosX = Mathf.Clamp01(normX),
NormalizedPosY = Mathf.Clamp01(normY),
PinTypeInt = (int)type,
Note = note,
};
AddPin(pin);
return pin;
}
// ── ISaveable ─────────────────────────────────────────────────────────
// 拷贝 List 而非直接共享引用,避免序列化期间 AddPin/RemovePin 修改集合产生并发问题
public void OnSave(SaveData data) => data.Map.Pins = new List<MapPin>(_pins);
public void OnLoad(SaveData data)
{
// R11-N3 防御性拷贝:避免与 SaveData.Map.Pins 共享同一引用,
// 防止调用方后续修改 data 时污染 _pins与 OnSave 的方向对称)
_pins = data.Map.Pins != null ? new List<MapPin>(data.Map.Pins) : new List<MapPin>();
PinsVersion++; // 加载存档后通知消费方重绘
}
}
}