Files
zeling_v2/Assets/_Game/Scripts/Progression/AchievementManager.cs

174 lines
6.9 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 System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Save;
using BaseGames.Platform;
namespace BaseGames.Progression
{
/// <summary>
/// 成就运行时状态(不存档,仅运行时追踪)。
/// </summary>
public class AchievementRuntimeState
{
public AchievementSO Achievement;
public bool IsUnlocked;
public float Progress; // 0-1用于 UI 进度条
}
/// <summary>
/// 成就管理器(架构 16_SupportingModules §2.3)。
/// 负责加载所有 AchievementSO、轮询条件、解锁成就并同步到平台服务。
/// </summary>
public class AchievementManager : MonoBehaviour, ISaveable, IAchievementService
{
[Header("成就资产")]
[Tooltip("将所有 AchievementSO 拖入此列表")]
[SerializeField] private AchievementSO[] _achievements;
[Header("事件频道")]
[SerializeField] private AchievementEventChannelSO _onAchievementUnlocked;
[Tooltip("成就授予凹槽时发布EquipmentManager 订阅后调用 IncreaseNotches(1)。")]
[SerializeField] private VoidEventChannelSO _onNotchGranted;
// ── 运行时状态 ─────────────────────────────────────────────────────────
private readonly Dictionary<string, AchievementRuntimeState> _states = new();
private void Awake()
{
if (ServiceLocator.GetOrDefault<IAchievementService>() != null) { Destroy(gameObject); return; }
ServiceLocator.Register<IAchievementService>(this);
InitStates();
}
private void OnEnable() => ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
private void OnDisable() => ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
private void OnDestroy()
{
ServiceLocator.Unregister<IAchievementService>(this);
}
private void InitStates()
{
_states.Clear();
if (_achievements == null) return;
foreach (var ach in _achievements)
{
if (ach == null || string.IsNullOrEmpty(ach.achievementId)) continue;
_states[ach.achievementId] = new AchievementRuntimeState
{
Achievement = ach,
IsUnlocked = false,
Progress = 0f,
};
}
}
// ── ISaveable ─────────────────────────────────────────────────────────
public void OnSave(SaveData saveData)
{
if (saveData?.Achievements == null) return;
saveData.Achievements.Unlocked.Clear();
saveData.Achievements.Progress.Clear();
foreach (var kv in _states)
{
if (kv.Value.IsUnlocked)
saveData.Achievements.Unlocked.Add(kv.Key);
else if (kv.Value.Progress > 0f)
saveData.Achievements.Progress[kv.Key] = new AchievementProgress { Percent = kv.Value.Progress };
}
}
public void OnLoad(SaveData saveData)
{
if (saveData?.Achievements == null) return;
foreach (var id in saveData.Achievements.Unlocked)
{
if (_states.TryGetValue(id, out var state))
state.IsUnlocked = true;
}
foreach (var kv in saveData.Achievements.Progress)
{
if (_states.TryGetValue(kv.Key, out var state))
state.Progress = kv.Value.Percent;
}
}
// ── 轮询检查 ──────────────────────────────────────────────────────────
/// <summary>
/// 使用最新存档数据检查所有未解锁成就的条件。
/// 由 SaveManager.AfterLoad 或外部定期调用(例如每次进入房间)。
/// </summary>
public void EvaluateAll(SaveData save)
{
foreach (var state in _states.Values)
{
if (state.IsUnlocked) continue;
EvaluateSingle(state, save);
}
}
private void EvaluateSingle(AchievementRuntimeState state, SaveData save)
{
if (state.Achievement.conditions == null || state.Achievement.conditions.Length == 0) return;
bool allMet = true;
float totalProgress = 0f;
foreach (var cond in state.Achievement.conditions)
{
if (cond == null) continue;
if (!cond.IsMet(save)) { allMet = false; }
totalProgress += cond.GetProgress(save);
}
int condCount = state.Achievement.conditions.Length;
state.Progress = condCount > 0 ? totalProgress / condCount : 0f;
if (allMet)
Unlock(state);
}
// ── 主动解锁 ──────────────────────────────────────────────────────────
/// <summary>直接解锁成就(用于剧情触发等无条件解锁场景)。</summary>
public void UnlockById(string achievementId)
{
if (_states.TryGetValue(achievementId, out var state) && !state.IsUnlocked)
Unlock(state);
}
private void Unlock(AchievementRuntimeState state)
{
state.IsUnlocked = true;
state.Progress = 1f;
_onAchievementUnlocked?.Raise(state.Achievement);
#if STEAMWORKS_NET
ServiceLocator.Get<IPlatformService>()?.UnlockAchievement(state.Achievement.achievementId);
#endif
// 若成就授予凹槽,通过事件通知 EquipmentManager 更新运行时状态;
// 不直接写 SaveData避免与 EquipmentManager.OnSave 的数据竞争。
if (state.Achievement.grantsNotch)
_onNotchGranted?.Raise();
Debug.Log($"[Achievement] 解锁:{state.Achievement.displayName} ({state.Achievement.achievementId})");
}
// ── 查询接口 ──────────────────────────────────────────────────────────
public bool IsUnlocked(string id)
=> _states.TryGetValue(id, out var s) && s.IsUnlocked;
public float GetProgress(string id)
=> _states.TryGetValue(id, out var s) ? s.Progress : 0f;
public IEnumerable<AchievementRuntimeState> GetAllStates()
=> _states.Values;
}
}