摄像机区域的架构改动

This commit is contained in:
2026-05-15 14:47:24 +08:00
parent 1b37297585
commit f264329751
3591 changed files with 1687228 additions and 446503 deletions

View File

@@ -0,0 +1,21 @@
{
"excludePlatforms": [],
"allowUnsafeCode": false,
"precompiledReferences": [],
"name": "BaseGames.Tutorial",
"defineConstraints": [],
"noEngineReferences": false,
"versionDefines": [],
"rootNamespace": "BaseGames.Tutorial",
"references": [
"BaseGames.Core.Events",
"BaseGames.Core.Save",
"BaseGames.World",
"BaseGames.Player",
"BaseGames.Localization",
"Unity.TextMeshPro"
],
"autoReferenced": true,
"overrideReferences": false,
"includePlatforms": []
}

View File

@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 69e13da9aec864e4dad1e8db7d75f12d
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using UnityEngine;
using BaseGames.Core;
using BaseGames.Player;
using BaseGames.Localization;
namespace BaseGames.Tutorial
{
/// <summary>
/// 情境化教程触发器(架构 21_LiquidPuzzleModule §18
/// 当玩家进入 Trigger 碰撞体范围时,向 TutorialManager 请求显示提示。
/// 若 _requiresAbility = true只在玩家拥有 _requiredAbility 后才触发。
/// 每个触发器只触发一次(触发后禁用自身)。
/// </summary>
[RequireComponent(typeof(Collider2D))]
public class ContextualHintTrigger : MonoBehaviour
{
[Header("提示配置")]
[SerializeField] private string _hintId = "";
[SerializeField] private string _hintLocKey = ""; // 本地化 KeyTable_UI
[SerializeField] private float _displayDuration = 4f;
[Header("能力门控")]
[SerializeField] private bool _requiresAbility = false;
[SerializeField] private AbilityType _requiredAbility = AbilityType.None;
private void Awake()
{
var col = GetComponent<Collider2D>();
col.isTrigger = true;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (!other.CompareTag("Player")) return;
var tm = ServiceLocator.GetOrDefault<ITutorialService>();
if (tm == null) return;
// 能力门控检查
if (_requiresAbility)
{
var stats = other.GetComponent<PlayerStats>();
if (stats == null || !stats.HasAbility(_requiredAbility)) return;
}
string text = LocalizationManager.Get(_hintLocKey, "UI");
tm.ShowHint(_hintId, text, _displayDuration);
tm.CompleteHint(_hintId); // 写入 SaveData防止场景重载后重复触发
// 单次触发后禁用(避免重复弹出)
gameObject.SetActive(false);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0f67c909769494146962903acf3a0a45
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,17 @@
namespace BaseGames.Tutorial
{
/// <summary>
/// 教程提示服务接口。通过 ServiceLocator 注册,供触发器和测试使用。
/// </summary>
public interface ITutorialService
{
/// <summary>显示提示。若 hintId 已完成则忽略。</summary>
void ShowHint(string hintId, string text, float duration = 4f);
/// <summary>标记提示为已完成并隐藏当前显示中的提示。</summary>
void CompleteHint(string hintId);
/// <summary>查询指定提示是否已完成。</summary>
bool IsHintCompleted(string hintId);
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 648235b6e78784a43b04c1eff4889fcc
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,54 @@
using System.Collections;
using UnityEngine;
using TMPro;
namespace BaseGames.Tutorial
{
/// <summary>
/// 教程提示 UI 组件(架构 21_LiquidPuzzleModule §17
/// 负责显示/隐藏提示面板和文字duration ≤ 0 时不自动消隐。
/// </summary>
public class TutorialHintUI : MonoBehaviour
{
[SerializeField] private GameObject _panel;
[SerializeField] private TMP_Text _label;
#pragma warning disable CS0414
[SerializeField] private float _fadeSpeed = 4f;
#pragma warning restore CS0414
private Coroutine _autoHideCoroutine;
// ── 公共 API ──────────────────────────────────────────────────────
/// <summary>显示提示文字。duration ≤ 0 时不自动消隐。</summary>
public void Show(string text, float duration = 0f)
{
if (_panel != null) _panel.SetActive(true);
if (_label != null) _label.text = text;
if (_autoHideCoroutine != null)
StopCoroutine(_autoHideCoroutine);
if (duration > 0f)
_autoHideCoroutine = StartCoroutine(AutoHideRoutine(duration));
}
/// <summary>立即隐藏提示面板。</summary>
public void Hide()
{
if (_autoHideCoroutine != null)
{
StopCoroutine(_autoHideCoroutine);
_autoHideCoroutine = null;
}
if (_panel != null) _panel.SetActive(false);
}
// ── 内部 ──────────────────────────────────────────────────────────
private IEnumerator AutoHideRoutine(float duration)
{
yield return new WaitForSeconds(duration);
Hide();
_autoHideCoroutine = null;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 73f4b9a32b1daa14fa837c1938b34310
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core.Save;
namespace BaseGames.Tutorial
{
/// <summary>
/// 教程提示管理器(架构 21_LiquidPuzzleModule §17
/// 通过 ServiceLocator 注册,实现 ISaveable 将已完成提示 ID 持久化。
/// ShowHint若 ID 已完成则跳过;否则显示,并在 duration 秒后自动隐藏。
/// CompleteHint标记为已完成写回 SaveData。
/// </summary>
public class TutorialManager : MonoBehaviour, ISaveable, ITutorialService
{
[SerializeField] private TutorialHintUI _hintUI;
private readonly HashSet<string> _completedHints = new();
// ── Unity 生命周期 ────────────────────────────────────
private void Awake()
{
if (BaseGames.Core.ServiceLocator.GetOrDefault<ITutorialService>() != null) { Destroy(gameObject); return; }
BaseGames.Core.ServiceLocator.Register<ITutorialService>(this);
// 生命周期由 Persistent 场景根 GameObject 统一管理,不在此调用 DontDestroyOnLoad
}
private void OnDestroy()
{
BaseGames.Core.ServiceLocator.Unregister<ITutorialService>(this);
}
private void OnEnable()
{
BaseGames.Core.ServiceLocator.GetOrDefault<BaseGames.Core.Save.ISaveableRegistry>()?.Register(this);
}
private void OnDisable()
{
BaseGames.Core.ServiceLocator.GetOrDefault<BaseGames.Core.Save.ISaveableRegistry>()?.Unregister(this);
}
// ── 公共 API ──────────────────────────────────────────────────────
/// <summary>
/// 显示提示。若 hintId 已完成则忽略。
/// <paramref name="hintId"/>: 唯一提示 ID可作为去重键和保存键
/// <paramref name="text"/>: 本地化后的显示文字。
/// <paramref name="duration"/>: 自动消隐秒数(≤ 0 则不自动消隐)。
/// </summary>
public void ShowHint(string hintId, string text, float duration = 4f)
{
if (_completedHints.Contains(hintId)) return;
if (_hintUI == null) return;
_hintUI.Show(text, duration);
}
/// <summary>标记提示为已完成,并自动隐藏当前显示中的提示。</summary>
public void CompleteHint(string hintId)
{
if (_completedHints.Add(hintId))
{
_hintUI?.Hide();
// 持久化由 ISaveable.OnSave 统一处理,此处仅更新内存状态
}
}
/// <summary>查询指定提示是否已完成。</summary>
public bool IsHintCompleted(string hintId) => _completedHints.Contains(hintId);
// ── ISaveable ─────────────────────────────────────────────────────
public void OnSave(SaveData data)
{
if (data?.Tutorial == null) return;
data.Tutorial.CompletedHintIds.Clear();
data.Tutorial.CompletedHintIds.AddRange(_completedHints);
}
public void OnLoad(SaveData data)
{
_completedHints.Clear();
if (data?.Tutorial == null) return;
foreach (var id in data.Tutorial.CompletedHintIds)
_completedHints.Add(id);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b6f587df338c06c4184832700fc8101d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: