多轮审查和修复
This commit is contained in:
170
Assets/Scripts/Progression/AchievementManager.cs
Normal file
170
Assets/Scripts/Progression/AchievementManager.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
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;
|
||||
|
||||
// ── 运行时状态 ─────────────────────────────────────────────────────────
|
||||
private readonly Dictionary<string, AchievementRuntimeState> _states = new();
|
||||
private SaveData _saveRef;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (ServiceLocator.GetOrDefault<IAchievementService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<IAchievementService>(this);
|
||||
InitStates();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
_saveRef = 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
|
||||
|
||||
// 若成就授予凹槽,写入存档数值
|
||||
if (state.Achievement.grantsNotch && _saveRef != null)
|
||||
{
|
||||
_saveRef.Equipment.MaxNotches++;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user