摄像机区域的架构改动
This commit is contained in:
8
Assets/_Game/Scripts/Core/Assets.meta
Normal file
8
Assets/_Game/Scripts/Core/Assets.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f475eab1c9fa38649bf17b8b68d06d68
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
84
Assets/_Game/Scripts/Core/Assets/AddressKeyRegistry.cs
Normal file
84
Assets/_Game/Scripts/Core/Assets/AddressKeyRegistry.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时 Addressable Key 注册层(架构 13_AssetPoolModule §9)。
|
||||
/// 供 DLC / 扩展包在运行时动态注册额外地址键,不修改编译期常量类 <see cref="AddressKeys"/>。
|
||||
///
|
||||
/// 用法:
|
||||
/// // 注册(DLC 模块 Awake 时)
|
||||
/// AddressKeyRegistry.TryRegister("DLC_WeaponScythe", "DLC/WPN_Scythe");
|
||||
///
|
||||
/// // 查询(GlobalObjectPool.SpawnInternal 内部调用)
|
||||
/// if (AddressKeyRegistry.TryResolve(key, out var addr)) { ... }
|
||||
/// </summary>
|
||||
public static class AddressKeyRegistry
|
||||
{
|
||||
private static readonly Dictionary<string, string> _registry = new();
|
||||
|
||||
/// <summary>
|
||||
/// 注册一个运行时 key → Addressable 地址映射。
|
||||
/// 若 key 已存在则跳过,返回 false。
|
||||
/// </summary>
|
||||
public static bool TryRegister(string key, string address)
|
||||
{
|
||||
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(address))
|
||||
{
|
||||
Debug.LogWarning($"[AddressKeyRegistry] TryRegister: key 或 address 不能为空。key={key}, address={address}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_registry.ContainsKey(key))
|
||||
{
|
||||
Debug.LogWarning($"[AddressKeyRegistry] key 已存在,跳过注册:{key}");
|
||||
return false;
|
||||
}
|
||||
|
||||
_registry[key] = address;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强制覆盖注册(用于测试 / 热更新覆盖)。
|
||||
/// </summary>
|
||||
public static void ForceRegister(string key, string address)
|
||||
{
|
||||
_registry[key] = address;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解析 key,返回对应的 Addressable 地址字符串。
|
||||
/// 若 key 未注册,直接将 key 作为 Addressable 地址使用。
|
||||
/// </summary>
|
||||
public static string Resolve(string key)
|
||||
{
|
||||
return _registry.TryGetValue(key, out var address) ? address : key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 尝试解析 key,成功时 out address 为注册的地址字符串,返回 true。
|
||||
/// 若 key 未注册则返回 false,address 为 null。
|
||||
/// </summary>
|
||||
public static bool TryResolve(string key, out string address)
|
||||
{
|
||||
return _registry.TryGetValue(key, out address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除指定 key 的注册(场景卸载/DLC 卸载时调用)。
|
||||
/// </summary>
|
||||
public static void Unregister(string key) => _registry.Remove(key);
|
||||
|
||||
/// <summary>
|
||||
/// 清空所有注册(仅用于测试)。
|
||||
/// </summary>
|
||||
public static void Clear() => _registry.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// 返回当前注册的所有 key 数量(调试用)。
|
||||
/// </summary>
|
||||
public static int Count => _registry.Count;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Assets/AddressKeyRegistry.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Assets/AddressKeyRegistry.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a64caba864b15a499c94f65e356e14e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
59
Assets/_Game/Scripts/Core/Assets/AddressKeys.cs
Normal file
59
Assets/_Game/Scripts/Core/Assets/AddressKeys.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
namespace BaseGames.Core.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// 所有 Addressable 地址字符串的静态常量类。
|
||||
/// 禁止在代码中直接使用字面字符串,统一引用此处的 const。
|
||||
/// 与 Assets 的 Addressable 标签分配保持同步(AddressKeyValidator 工具验证)。
|
||||
/// </summary>
|
||||
public static class AddressKeys
|
||||
{
|
||||
// ── Scenes ──────────────────────────────────────────────────────
|
||||
public const string ScenePersistent = "Scene_Persistent";
|
||||
public const string SceneMainMenu = "Scene_MainMenu";
|
||||
|
||||
// ── Player ──────────────────────────────────────────────────────
|
||||
public const string PrefabPlayer = "PLY_Player";
|
||||
|
||||
// ── Enemies ─────────────────────────────────────────────────────
|
||||
public const string PrefabEnemyGrunt = "ENM_GruntWarrior";
|
||||
public const string PrefabEnemySkullArch = "ENM_SkullArcher";
|
||||
|
||||
// ── Projectiles ─────────────────────────────────────────────────
|
||||
public const string PrefabProjArrow = "PROJ_Arrow";
|
||||
public const string PrefabProjFireball = "PROJ_Fireball";
|
||||
public const string PrefabProjSoulBall = "PROJ_SoulBall";
|
||||
|
||||
// ── VFX ─────────────────────────────────────────────────────────
|
||||
public const string PrefabVFXHitSpark = "VFX_HitSpark";
|
||||
public const string PrefabVFXBloodSplat = "VFX_BloodSplat";
|
||||
public const string PrefabVFXExplosion = "VFX_Explosion";
|
||||
|
||||
// ── UI ───────────────────────────────────────────────────────────
|
||||
public const string PrefabUIFloatingDmgText = "UI_FloatingDamageText";
|
||||
|
||||
// ── Collectibles ─────────────────────────────────────────────────
|
||||
public const string PrefabCollectibleLingZhu = "COL_LingZhu";
|
||||
public const string PrefabCollectibleItem = "COL_Item";
|
||||
public const string PrefabCollectibleHPOrb = "COL_HPOrb";
|
||||
|
||||
// ── Weapons ──────────────────────────────────────────────────────
|
||||
public const string PrefabWeaponSkyBlade = "WPN_SkyBlade";
|
||||
public const string PrefabWeaponEarthClaw = "WPN_EarthClaw";
|
||||
public const string PrefabWeaponSoulStaff = "WPN_SoulStaff";
|
||||
|
||||
// ── Config ScriptableObjects ─────────────────────────────────────
|
||||
public const string DataFootstepCatalog = "Config/FootstepCatalog";
|
||||
|
||||
/// <summary>
|
||||
/// Addressable 标签常量(用于批量加载)。
|
||||
/// 注意:这里是标签名称而非资产地址,不会被 AddressKeyValidator 校验。
|
||||
/// </summary>
|
||||
public static class Labels
|
||||
{
|
||||
public const string Enemy = "Enemy";
|
||||
public const string Poolable = "Poolable";
|
||||
public const string BGM = "BGM";
|
||||
public const string Charms = "Charms";
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Assets/AddressKeys.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Assets/AddressKeys.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: faaab8ae1b4f5584688d2c294fcffa1a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
53
Assets/_Game/Scripts/Core/Assets/AssetLoader.cs
Normal file
53
Assets/_Game/Scripts/Core/Assets/AssetLoader.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using UnityEngine.AddressableAssets;
|
||||
using UnityEngine.ResourceManagement.AsyncOperations;
|
||||
|
||||
namespace BaseGames.Core.Assets
|
||||
{
|
||||
/// <summary>
|
||||
/// Addressables 运行时加载工具(薄封装)。
|
||||
/// 场景卸载时配合 <see cref="AssetReleaseTracker"/> 批量 Release。
|
||||
/// </summary>
|
||||
public static class AssetLoader
|
||||
{
|
||||
/// <summary>异步加载资产,返回 handle 供 Release 使用。</summary>
|
||||
public static async Task<(T asset, AsyncOperationHandle<T> handle)> LoadAsync<T>(string addressKey)
|
||||
{
|
||||
var handle = Addressables.LoadAssetAsync<T>(addressKey);
|
||||
var result = await handle.Task;
|
||||
return (result, handle);
|
||||
}
|
||||
|
||||
/// <summary>释放一个已加载的 handle(引用计数 -1)。</summary>
|
||||
public static void Release<T>(AsyncOperationHandle<T> handle)
|
||||
{
|
||||
if (handle.IsValid()) Addressables.Release(handle);
|
||||
}
|
||||
|
||||
/// <summary>释放一个 GameObject 实例(Addressables.ReleaseInstance)。</summary>
|
||||
public static bool ReleaseInstance(GameObject go)
|
||||
=> Addressables.ReleaseInstance(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景卸载时批量释放由 Addressables 加载的资产句柄。
|
||||
/// 挂在场景的根 GameObject 上;OnDestroy 时自动 Release 注册的所有 handle。
|
||||
/// </summary>
|
||||
public class AssetReleaseTracker : MonoBehaviour
|
||||
{
|
||||
private readonly List<AsyncOperationHandle> _handles = new();
|
||||
|
||||
public void Track<T>(AsyncOperationHandle<T> handle)
|
||||
=> _handles.Add(handle);
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
foreach (var h in _handles)
|
||||
if (h.IsValid()) Addressables.Release(h);
|
||||
_handles.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Assets/AssetLoader.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Assets/AssetLoader.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b53704ca12a83b479fc3967704c642b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
38
Assets/_Game/Scripts/Core/Assets/AssetReleaseTracker.cs
Normal file
38
Assets/_Game/Scripts/Core/Assets/AssetReleaseTracker.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Assets;
|
||||
using BaseGames.Core.Pool;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 资产释放跟踪器。
|
||||
/// 事件驱动:监听 EVT_SceneLoadRequest,在新场景加载前清理旧场景的对象池缓存。
|
||||
/// </summary>
|
||||
public class AssetReleaseTracker : MonoBehaviour
|
||||
{
|
||||
[Header("Event Channels")]
|
||||
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
|
||||
|
||||
private string _lastLoadedScene;
|
||||
private readonly CompositeDisposable _subs = new();
|
||||
|
||||
private void OnEnable() => _onSceneLoadRequest?.Subscribe(OnSceneLoadRequested).AddTo(_subs);
|
||||
private void OnDisable() => _subs.Clear();
|
||||
|
||||
private void OnSceneLoadRequested(SceneLoadRequest req)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_lastLoadedScene)) { _lastLoadedScene = req.SceneName; return; }
|
||||
|
||||
// 清除旧场景的敌人对象池缓存(按需扩展)
|
||||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool != null)
|
||||
{
|
||||
pool.ClearPool(AddressKeys.PrefabEnemyGrunt);
|
||||
pool.ClearPool(AddressKeys.PrefabEnemySkullArch);
|
||||
}
|
||||
|
||||
_lastLoadedScene = req.SceneName;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Assets/AssetReleaseTracker.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Assets/AssetReleaseTracker.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba91049904d9daa43a59032bc1481cd1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Assets/_Game/Scripts/Core/BaseGames.Core.asmdef
Normal file
19
Assets/_Game/Scripts/Core/BaseGames.Core.asmdef
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.Core",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.Core",
|
||||
"references": [
|
||||
"BaseGames.Core.Events",
|
||||
"BaseGames.Core.Save",
|
||||
"Unity.Addressables",
|
||||
"Unity.ResourceManager"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
7
Assets/_Game/Scripts/Core/BaseGames.Core.asmdef.meta
Normal file
7
Assets/_Game/Scripts/Core/BaseGames.Core.asmdef.meta
Normal file
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e41e18c796a92334c8eb801039fc7440
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
141
Assets/_Game/Scripts/Core/BuiltinGameStates.cs
Normal file
141
Assets/_Game/Scripts/Core/BuiltinGameStates.cs
Normal file
@@ -0,0 +1,141 @@
|
||||
using System.Collections.Generic;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Core.States
|
||||
{
|
||||
/// <summary>初始化状态(应用启动时的第一个状态)。</summary>
|
||||
public class InitializingState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.Initializing;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.MainMenu };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>主菜单状态。</summary>
|
||||
public class MainMenuState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.MainMenu;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.LoadingScene };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>场景加载中状态。</summary>
|
||||
public class LoadingSceneState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.LoadingScene;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.MainMenu, GameStates.Gameplay };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>正常游玩状态。</summary>
|
||||
public class GameplayState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.Gameplay;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId>
|
||||
{
|
||||
GameStates.LoadingScene,
|
||||
GameStates.BossFight,
|
||||
GameStates.Paused,
|
||||
GameStates.Dead,
|
||||
GameStates.Cutscene,
|
||||
};
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>Boss 战状态。</summary>
|
||||
public class BossFightState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.BossFight;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId>
|
||||
{
|
||||
GameStates.LoadingScene,
|
||||
GameStates.Gameplay,
|
||||
GameStates.Paused,
|
||||
GameStates.Dead,
|
||||
};
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>暂停状态。</summary>
|
||||
public class PausedState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.Paused;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId>
|
||||
{
|
||||
GameStates.Gameplay,
|
||||
GameStates.BossFight,
|
||||
GameStates.MainMenu,
|
||||
};
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>死亡状态。</summary>
|
||||
public class DeadState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.Dead;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.LoadingScene, GameStates.GameOver };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>过场动画状态。</summary>
|
||||
public class CutsceneState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.Cutscene;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.Gameplay };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
|
||||
/// <summary>Game Over 状态(SteelSoul 清档用)。</summary>
|
||||
public class GameOverState : IGameState
|
||||
{
|
||||
public GameStateId Id => GameStates.GameOver;
|
||||
|
||||
public IReadOnlyCollection<GameStateId> ValidNextStates { get; } =
|
||||
new HashSet<GameStateId> { GameStates.MainMenu };
|
||||
|
||||
public void OnEnter(GameStateId prev) { }
|
||||
public void OnExit(GameStateId next) { }
|
||||
public void Tick(float dt) { }
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/BuiltinGameStates.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/BuiltinGameStates.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9bcf1a51cdbfa94a8e3d63c25626d34
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
83
Assets/_Game/Scripts/Core/DeathRespawnService.cs
Normal file
83
Assets/_Game/Scripts/Core/DeathRespawnService.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
using System.Collections;
|
||||
using System.Threading.Tasks;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 死亡/复活流程服务接口。
|
||||
/// </summary>
|
||||
public interface IDeathRespawnService
|
||||
{
|
||||
/// <summary>玩家死亡时由 GameManager 调用,启动死亡演出流程。</summary>
|
||||
IEnumerator StartDeathSequenceCoroutine();
|
||||
|
||||
/// <summary>DeathScreen 确认按钮点击后调用,执行复活流程。</summary>
|
||||
IEnumerator StartRespawnCoroutine();
|
||||
|
||||
/// <summary>SteelSoul 模式:HP 归零后直接清档并返回主菜单。</summary>
|
||||
IEnumerator StartGameOverCoroutine();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 死亡/复活流程独立服务。
|
||||
/// </summary>
|
||||
public class DeathRespawnService : MonoBehaviour, IDeathRespawnService
|
||||
{
|
||||
[Header("Config")]
|
||||
[SerializeField] private float _deathAnimDuration = 1.2f;
|
||||
[SerializeField] private float _deathScreenDelay = 0.5f;
|
||||
[SerializeField] private float _respawnFadeDuration = 0.4f;
|
||||
|
||||
[Header("Event Channels - Raise")]
|
||||
[SerializeField] private VoidEventChannelSO _onRespawnStarted;
|
||||
[SerializeField] private VoidEventChannelSO _onRespawnCompleted;
|
||||
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
|
||||
|
||||
[Header("Event Channels - Listen")]
|
||||
[SerializeField] private VoidEventChannelSO _onDeathScreenConfirmed;
|
||||
|
||||
public IEnumerator StartDeathSequenceCoroutine()
|
||||
{
|
||||
yield return new WaitForSeconds(_deathAnimDuration);
|
||||
yield return new WaitForSeconds(_deathScreenDelay);
|
||||
// 确认等待由 GameManager.DeathFlow 统一处理,此处仅负责动画延迟
|
||||
}
|
||||
|
||||
public IEnumerator StartRespawnCoroutine()
|
||||
{
|
||||
_onRespawnStarted?.Raise();
|
||||
yield return new WaitForSeconds(_respawnFadeDuration);
|
||||
|
||||
// 通过 SceneLoadRequest 频道触发场景重载,复用 SceneService / RoomTransition 路径
|
||||
var sm = ServiceLocator.GetOrDefault<ISaveService>();
|
||||
_onSceneLoadRequest?.Raise(new SceneLoadRequest
|
||||
{
|
||||
SceneName = sm?.LastCheckpointScene,
|
||||
EntryTransitionId = sm?.LastCheckpointSpawnId,
|
||||
ShowLoadingScreen = true,
|
||||
IsRespawn = true,
|
||||
});
|
||||
|
||||
yield return new WaitForSeconds(_respawnFadeDuration);
|
||||
_onRespawnCompleted?.Raise();
|
||||
}
|
||||
|
||||
public IEnumerator StartGameOverCoroutine()
|
||||
{
|
||||
// 1. 删除当前存档槽
|
||||
var saveManager = ServiceLocator.GetOrDefault<ISaveService>();
|
||||
if (saveManager != null)
|
||||
{
|
||||
var task = saveManager.DeleteSlotAsync(saveManager.ActiveSlot);
|
||||
yield return new WaitUntil(() => task.IsCompleted);
|
||||
}
|
||||
|
||||
// 2. 返回主菜单
|
||||
var sceneService = ServiceLocator.GetOrDefault<ISceneService>();
|
||||
if (sceneService != null)
|
||||
yield return sceneService.LoadMainMenuCoroutine();
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/DeathRespawnService.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/DeathRespawnService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 843f5718ab6dbb7418fa7a036a83efc9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/_Game/Scripts/Core/Difficulty.meta
Normal file
8
Assets/_Game/Scripts/Core/Difficulty.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7982848c7ca0270419ab1c0a32fde9a0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
79
Assets/_Game/Scripts/Core/Difficulty/DifficultyManager.cs
Normal file
79
Assets/_Game/Scripts/Core/Difficulty/DifficultyManager.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度管理器(单例 MonoBehaviour)。
|
||||
/// 统一管理当前难度档位,并通过事件频道广播变化。
|
||||
/// 订阅者通过 <see cref="CurrentScaler"/> 获取具体缩放数值。
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-900)]
|
||||
public class DifficultyManager : MonoBehaviour, ISaveable, IDifficultyService
|
||||
{
|
||||
[SerializeField] private DifficultyScalerSO[] _allScalers;
|
||||
[SerializeField] private DifficultyChangedEventChannel _onDifficultyChanged;
|
||||
|
||||
public DifficultyLevel CurrentLevel { get; private set; } = DifficultyLevel.Normal;
|
||||
public DifficultyScalerSO CurrentScaler { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (ServiceLocator.GetOrDefault<IDifficultyService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<IDifficultyService>(this);
|
||||
Apply(DifficultyLevel.Normal);
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ServiceLocator.Unregister<IDifficultyService>(this);
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 在游戏流程中切换难度。
|
||||
/// SteelSoul 模式一旦选定,中途无法降级。
|
||||
/// </summary>
|
||||
public void ChangeDifficulty(DifficultyLevel level)
|
||||
{
|
||||
if (CurrentLevel == DifficultyLevel.SteelSoul && level != DifficultyLevel.SteelSoul)
|
||||
{
|
||||
Debug.LogWarning("[DifficultyManager] SteelSoul 模式无法在游戏中途降级。");
|
||||
return;
|
||||
}
|
||||
Apply(level);
|
||||
}
|
||||
|
||||
/// <summary>按档位查找对应的缩放器,未配置时返回 null。</summary>
|
||||
public DifficultyScalerSO GetScaler(DifficultyLevel level)
|
||||
{
|
||||
if (_allScalers == null) return null;
|
||||
foreach (var s in _allScalers)
|
||||
if (s != null && s.Level == level) return s;
|
||||
return null;
|
||||
}
|
||||
|
||||
private void Apply(DifficultyLevel level)
|
||||
{
|
||||
CurrentLevel = level;
|
||||
CurrentScaler = GetScaler(level);
|
||||
_onDifficultyChanged?.Raise(CurrentLevel);
|
||||
}
|
||||
|
||||
// ── ISaveable ────────────────────────────────────────────────────────
|
||||
|
||||
public void OnSave(SaveData saveData)
|
||||
{
|
||||
if (saveData?.Meta != null)
|
||||
saveData.Meta.IsSteelSoul = CurrentLevel == DifficultyLevel.SteelSoul;
|
||||
}
|
||||
|
||||
public void OnLoad(SaveData saveData)
|
||||
{
|
||||
if (saveData?.Meta != null && saveData.Meta.IsSteelSoul)
|
||||
Apply(DifficultyLevel.SteelSoul);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a810da0a9739024d90a4f7415aeb2a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
41
Assets/_Game/Scripts/Core/Difficulty/DifficultyScalerSO.cs
Normal file
41
Assets/_Game/Scripts/Core/Difficulty/DifficultyScalerSO.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度缩放配置 ScriptableObject。每个难度档位对应一个实例。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "BaseGames/Core/DifficultyScaler")]
|
||||
public class DifficultyScalerSO : ScriptableObject
|
||||
{
|
||||
[Header("标识")]
|
||||
public DifficultyLevel Level;
|
||||
|
||||
[Header("玩家")]
|
||||
public float PlayerMaxHPMultiplier = 1.0f;
|
||||
public float PlayerDamageMultiplier = 1.0f;
|
||||
public float InvincibilityFrameScale = 1.0f;
|
||||
|
||||
[Header("敌人")]
|
||||
public float EnemyDamageMultiplier = 1.0f;
|
||||
public float EnemyHPMultiplier = 1.0f;
|
||||
public float BossDamageMultiplier = 1.0f;
|
||||
public float BossHPMultiplier = 1.0f;
|
||||
|
||||
[Header("经济")]
|
||||
public float ShopPriceMultiplier = 1.0f;
|
||||
public float LingZhuDropMultiplier = 1.0f;
|
||||
|
||||
[Header("机制")]
|
||||
public bool CanReviveWithLingZhuLoss = true;
|
||||
public bool InstantDeathOnZeroHP = false;
|
||||
public bool LingZhuPenaltyOnDeath = true;
|
||||
|
||||
[Header("AI")]
|
||||
public float EnemyAttackIntervalScale = 1.0f;
|
||||
public float EnemyAggroRangeScale = 1.0f;
|
||||
public float EnemyReactionTimeScale = 1.0f;
|
||||
public int EnemyAggressionLevel = 2;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7e8ad48b35397348a800079849ee535
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
24
Assets/_Game/Scripts/Core/Difficulty/IDifficultyService.cs
Normal file
24
Assets/_Game/Scripts/Core/Difficulty/IDifficultyService.cs
Normal file
@@ -0,0 +1,24 @@
|
||||
using BaseGames.Core.Events;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度服务接口(架构 §DifficultyModule)。
|
||||
/// 提供当前难度档位及缩放参数,供跨程序集的游戏系统(战斗、商店、掉落等)查询,
|
||||
/// 无需直接依赖 <see cref="DifficultyManager"/> 具体类型。
|
||||
/// </summary>
|
||||
public interface IDifficultyService
|
||||
{
|
||||
/// <summary>当前难度档位。</summary>
|
||||
DifficultyLevel CurrentLevel { get; }
|
||||
|
||||
/// <summary>当前档位对应的缩放配置。未配置时返回 null。</summary>
|
||||
DifficultyScalerSO CurrentScaler { get; }
|
||||
|
||||
/// <summary>切换到指定难度。SteelSoul 模式一旦选定不可降级。</summary>
|
||||
void ChangeDifficulty(DifficultyLevel level);
|
||||
|
||||
/// <summary>按档位查找缩放器,未配置时返回 null。</summary>
|
||||
DifficultyScalerSO GetScaler(DifficultyLevel level);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 20984324b3111c5489d9c4c3e55ec4f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/_Game/Scripts/Core/Events.meta
Normal file
8
Assets/_Game/Scripts/Core/Events.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3771751cae7bcd04b96f7d9026a962aa
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
105
Assets/_Game/Scripts/Core/Events/BaseEventChannelSO.cs
Normal file
105
Assets/_Game/Scripts/Core/Events/BaseEventChannelSO.cs
Normal file
@@ -0,0 +1,105 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 泛型 SO 事件频道基类。T 为负载类型。
|
||||
/// </summary>
|
||||
public abstract class BaseEventChannelSO<T> : ScriptableObject
|
||||
{
|
||||
[Multiline] public string description;
|
||||
|
||||
private event Action<T> _onEventRaisedBacking;
|
||||
#if UNITY_EDITOR
|
||||
private int _subscriberCount;
|
||||
#endif
|
||||
|
||||
public event Action<T> OnEventRaised
|
||||
{
|
||||
add
|
||||
{
|
||||
_onEventRaisedBacking += value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount++;
|
||||
#endif
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onEventRaisedBacking -= value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount--;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Raise(T value)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EventBusMonitor.Record(name, value?.ToString() ?? "null",
|
||||
_subscriberCount,
|
||||
Time.frameCount);
|
||||
#endif
|
||||
_onEventRaisedBacking?.Invoke(value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅并返回可 Dispose 的订阅句柄,配合 CompositeDisposable 使用。
|
||||
/// </summary>
|
||||
public EventSubscription Subscribe(Action<T> callback)
|
||||
{
|
||||
OnEventRaised += callback;
|
||||
return new EventSubscription(() => OnEventRaised -= callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 无负载事件频道基类。
|
||||
/// </summary>
|
||||
public abstract class VoidBaseEventChannelSO : ScriptableObject
|
||||
{
|
||||
[Multiline] public string description;
|
||||
|
||||
private event Action _onEventRaisedBacking;
|
||||
#if UNITY_EDITOR
|
||||
private int _subscriberCount;
|
||||
#endif
|
||||
|
||||
public event Action OnEventRaised
|
||||
{
|
||||
add
|
||||
{
|
||||
_onEventRaisedBacking += value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount++;
|
||||
#endif
|
||||
}
|
||||
remove
|
||||
{
|
||||
_onEventRaisedBacking -= value;
|
||||
#if UNITY_EDITOR
|
||||
_subscriberCount--;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void Raise()
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
EventBusMonitor.Record(name, "<void>",
|
||||
_subscriberCount,
|
||||
Time.frameCount);
|
||||
#endif
|
||||
_onEventRaisedBacking?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 订阅并返回可 Dispose 的订阅句柄。
|
||||
/// </summary>
|
||||
public EventSubscription Subscribe(Action callback)
|
||||
{
|
||||
OnEventRaised += callback;
|
||||
return new EventSubscription(() => OnEventRaised -= callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/BaseEventChannelSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/BaseEventChannelSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: adcb05b71d0e3f94f8c4446dee4c253b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "BaseGames.Core.Events",
|
||||
"rootNamespace": "BaseGames.Core.Events",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": false
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bf44e3f184fd4214eb09a80e6e04d7df
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/BoolEventChannelSO.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/BoolEventChannelSO.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Bool")]
|
||||
public class BoolEventChannelSO : BaseEventChannelSO<bool> { }
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/BoolEventChannelSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/BoolEventChannelSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d5c798758acf2c64097cf4ff3b088530
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
22
Assets/_Game/Scripts/Core/Events/BossEvents.cs
Normal file
22
Assets/_Game/Scripts/Core/Events/BossEvents.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// Boss 技能事件负载。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct BossSkillEvent
|
||||
{
|
||||
public string BossId;
|
||||
public string SkillId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Boss 阶段切换事件负载。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct BossPhaseEvent
|
||||
{
|
||||
public string BossId;
|
||||
public int Phase;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/BossEvents.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/BossEvents.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac3dc174ca0e12544a31550b5f61e70b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/BossPhase")]
|
||||
public class BossPhaseEventChannelSO : BaseEventChannelSO<BossPhaseEvent> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 647b6596e515ba64483b7ff337c76699
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/BossSkill")]
|
||||
public class BossSkillEventChannelSO : BaseEventChannelSO<BossSkillEvent> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8230eab2acba8c24499b2d20df81adb7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/_Game/Scripts/Core/Events/ColorblindMode.cs
Normal file
14
Assets/_Game/Scripts/Core/Events/ColorblindMode.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 色觉辅助模式枚举(16_SupportingModules §AccessibilityManager)。
|
||||
/// </summary>
|
||||
public enum ColorblindMode
|
||||
{
|
||||
None = 0,
|
||||
Protanopia = 1,
|
||||
Deuteranopia = 2,
|
||||
Tritanopia = 3,
|
||||
Achromatopsia = 4
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/ColorblindMode.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/ColorblindMode.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f7f46e87b21f6a045bc324853efecf9c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/ColorblindMode")]
|
||||
public class ColorblindModeEventChannelSO : BaseEventChannelSO<ColorblindMode> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9227f70cba3e7949a837c0622653f33
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2
Assets/_Game/Scripts/Core/Events/DamageInfo.cs
Normal file
2
Assets/_Game/Scripts/Core/Events/DamageInfo.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
// DamageInfo 及 DamageInfoEventChannelSO 定义位于 Assets/Scripts/Combat/DamageInfo.cs。
|
||||
namespace BaseGames.Core.Events { }
|
||||
11
Assets/_Game/Scripts/Core/Events/DamageInfo.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/DamageInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94ca97e41a3560141a991928af7c1beb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度变更事件频道。
|
||||
/// 发布:DifficultyScalerSO / SettingsManager
|
||||
/// 订阅:所有需要感知当前难度的系统
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/DifficultyChanged")]
|
||||
public class DifficultyChangedEventChannel : BaseEventChannelSO<DifficultyLevel> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b3f48b426d66154797d7c9af9c6b02f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
13
Assets/_Game/Scripts/Core/Events/DifficultyLevel.cs
Normal file
13
Assets/_Game/Scripts/Core/Events/DifficultyLevel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 难度等级枚举。
|
||||
/// </summary>
|
||||
public enum DifficultyLevel
|
||||
{
|
||||
Easy = 0,
|
||||
Normal = 1,
|
||||
Hard = 2,
|
||||
SteelSoul = 3 // 一命模式
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/DifficultyLevel.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/DifficultyLevel.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aa2ae8b438a843b458548868d0cfef3a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
63
Assets/_Game/Scripts/Core/Events/EventBusMonitor.cs
Normal file
63
Assets/_Game/Scripts/Core/Events/EventBusMonitor.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时事件总线监控(仅 Editor 下记录调用信息)。
|
||||
/// </summary>
|
||||
public static class EventBusMonitor
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
public struct EventRecord
|
||||
{
|
||||
public string ChannelName;
|
||||
public string Payload;
|
||||
public int ListenerCount;
|
||||
public int FrameCount;
|
||||
public System.DateTime Timestamp;
|
||||
}
|
||||
|
||||
private const int Capacity = 256;
|
||||
|
||||
// 固定大小环形缓冲区,避免 Queue.Enqueue/Dequeue 产生的 GC 分配
|
||||
private static readonly EventRecord[] _buffer = new EventRecord[Capacity];
|
||||
private static int _head = 0; // 下一条写入的位置
|
||||
private static int _count = 0; // 当前有效记录数(≤ Capacity)
|
||||
|
||||
/// <summary>按时间顺序(最旧→最新)枚举所有已记录事件。</summary>
|
||||
public static System.Collections.Generic.IEnumerable<EventRecord> Records
|
||||
{
|
||||
get
|
||||
{
|
||||
int start = _count < Capacity ? 0 : _head;
|
||||
int total = System.Math.Min(_count, Capacity);
|
||||
for (int i = 0; i < total; i++)
|
||||
yield return _buffer[(start + i) % Capacity];
|
||||
}
|
||||
}
|
||||
|
||||
// frameCount 使用 int(与 Time.frameCount 类型一致)。
|
||||
// 在 @1000fps 持续运行约 24.8 天后发生有符号溢出(C# 默认 unchecked,环绕为负数)。
|
||||
// 对于 Editor 调试工具此溢出无实际影响,此处不做处理。
|
||||
public static void Record(string channelName, string payload, int listenerCount, int frameCount)
|
||||
{
|
||||
_buffer[_head] = new EventRecord
|
||||
{
|
||||
ChannelName = channelName,
|
||||
Payload = payload,
|
||||
ListenerCount = listenerCount,
|
||||
FrameCount = frameCount,
|
||||
Timestamp = System.DateTime.Now
|
||||
};
|
||||
_head = (_head + 1) % Capacity;
|
||||
if (_count < Capacity) _count++;
|
||||
}
|
||||
|
||||
public static void Clear()
|
||||
{
|
||||
_head = 0;
|
||||
_count = 0;
|
||||
}
|
||||
#else
|
||||
public static void Record(string channelName, string payload, int listenerCount, int frameCount) { }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/EventBusMonitor.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/EventBusMonitor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d580e5431c172b34d8fc0252ff72774a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/_Game/Scripts/Core/Events/EventChannelRegistry.cs
Normal file
51
Assets/_Game/Scripts/Core/Events/EventChannelRegistry.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时事件频道注册表。
|
||||
/// 在 Persistent 场景 Awake 时由 EventChannelRegistrar 注册所有频道 SO,
|
||||
/// 供不能持有 [SerializeField] 的动态对象(CharmEffect SO 等)按类型名查找频道。
|
||||
/// </summary>
|
||||
public class EventChannelRegistry : MonoBehaviour, IEventChannelRegistry
|
||||
{
|
||||
private readonly Dictionary<string, ScriptableObject> _channels = new();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (BaseGames.Core.ServiceLocator.GetOrDefault<IEventChannelRegistry>() != null)
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
BaseGames.Core.ServiceLocator.Register<IEventChannelRegistry>(this);
|
||||
}
|
||||
|
||||
/// <summary>由 EventChannelRegistrar 在场景初始化时批量注册频道 SO。</summary>
|
||||
public void Register(string key, ScriptableObject channel)
|
||||
=> _channels[key] = channel;
|
||||
|
||||
/// <summary>
|
||||
/// 按 key 查找频道。key = SO 资产文件名(不含扩展名),如 "EVT_HitConfirmed"。
|
||||
/// </summary>
|
||||
public T Get<T>(string key) where T : ScriptableObject
|
||||
{
|
||||
if (_channels.TryGetValue(key, out var ch) && ch is T typed) return typed;
|
||||
Debug.LogError($"[EventChannelRegistry] Key '{key}' not found or wrong type ({typeof(T).Name}).");
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>尝试获取频道,不报错。</summary>
|
||||
public bool TryGet<T>(string key, out T channel) where T : ScriptableObject
|
||||
{
|
||||
if (_channels.TryGetValue(key, out var ch) && ch is T typed)
|
||||
{
|
||||
channel = typed;
|
||||
return true;
|
||||
}
|
||||
channel = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 147bb5b987a0a244ba3a39c71852ca51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
65
Assets/_Game/Scripts/Core/Events/EventSubscription.cs
Normal file
65
Assets/_Game/Scripts/Core/Events/EventSubscription.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 单条订阅的 Disposable 句柄。Dispose() 自动取消注册。
|
||||
/// </summary>
|
||||
public readonly struct EventSubscription : IDisposable
|
||||
{
|
||||
private readonly Action _unsubscribe;
|
||||
|
||||
public EventSubscription(Action unsubscribe)
|
||||
=> _unsubscribe = unsubscribe;
|
||||
|
||||
public void Dispose() => _unsubscribe?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量管理多条订阅,统一在 Dispose / Clear 时取消所有注册。
|
||||
/// 用法:OnEnable 调用 Subscribe,OnDisable 调用 Clear。
|
||||
/// </summary>
|
||||
public sealed class CompositeDisposable : IDisposable
|
||||
{
|
||||
private readonly List<IDisposable> _items = new();
|
||||
|
||||
public void Add(IDisposable item) => _items.Add(item);
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (var item in _items) item.Dispose();
|
||||
_items.Clear();
|
||||
}
|
||||
|
||||
public void Dispose() => Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="EventSubscription"/> 扩展方法(架构 02 §8)。
|
||||
/// </summary>
|
||||
public static class EventSubscriptionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 将订阅句柄添加到集合中,统一生命周期管理。
|
||||
/// 用法:channel.Subscribe(Handler).AddTo(_subscriptions);
|
||||
/// </summary>
|
||||
public static EventSubscription AddTo(this EventSubscription subscription,
|
||||
ICollection<IDisposable> collection)
|
||||
{
|
||||
collection.Add(subscription);
|
||||
return subscription;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将订阅句柄添加到 <see cref="CompositeDisposable"/> 中。
|
||||
/// </summary>
|
||||
public static EventSubscription AddTo(this EventSubscription subscription,
|
||||
CompositeDisposable compositeDisposable)
|
||||
{
|
||||
compositeDisposable.Add(subscription);
|
||||
return subscription;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/EventSubscription.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/EventSubscription.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15f50c6ab5d503b47bbcc8f73b32d3e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/FloatEventChannelSO.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/FloatEventChannelSO.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Float")]
|
||||
public class FloatEventChannelSO : BaseEventChannelSO<float> { }
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/FloatEventChannelSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/FloatEventChannelSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 62f2d5b0575789048b6badd7d1608db3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/GameEventChannels.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/GameEventChannels.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/GameState")]
|
||||
public class GameStateEventChannelSO : BaseEventChannelSO<GameStateId> { }
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/GameEventChannels.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/GameEventChannels.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c060c20fe37837c408cc5a628f6d8863
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
21
Assets/_Game/Scripts/Core/Events/GameStateId.cs
Normal file
21
Assets/_Game/Scripts/Core/Events/GameStateId.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 游戏状态 ID(值类型,替代旧版 GameState 枚举)。
|
||||
/// 具体实例由 GameStates 静态工厂创建。
|
||||
/// </summary>
|
||||
public readonly struct GameStateId : System.IEquatable<GameStateId>
|
||||
{
|
||||
public readonly string Id;
|
||||
|
||||
public GameStateId(string id) => Id = id;
|
||||
|
||||
public bool Equals(GameStateId other) => Id == other.Id;
|
||||
public override bool Equals(object obj) => obj is GameStateId g && Equals(g);
|
||||
public override int GetHashCode() => Id?.GetHashCode() ?? 0;
|
||||
public override string ToString() => Id ?? "<null>";
|
||||
|
||||
public static bool operator ==(GameStateId a, GameStateId b) => a.Equals(b);
|
||||
public static bool operator !=(GameStateId a, GameStateId b) => !a.Equals(b);
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/GameStateId.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/GameStateId.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af0ccb15630f6ff419a4c526e96459f7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
2
Assets/_Game/Scripts/Core/Events/HitInfo.cs
Normal file
2
Assets/_Game/Scripts/Core/Events/HitInfo.cs
Normal file
@@ -0,0 +1,2 @@
|
||||
// HitInfo 及 HitConfirmedEventChannelSO 定义位于 Assets/Scripts/Combat/HitInfo.cs。
|
||||
namespace BaseGames.Core.Events { }
|
||||
11
Assets/_Game/Scripts/Core/Events/HitInfo.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/HitInfo.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a04b32343a49fe4c9a273b4d0d51b60
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
19
Assets/_Game/Scripts/Core/Events/IEventChannelRegistry.cs
Normal file
19
Assets/_Game/Scripts/Core/Events/IEventChannelRegistry.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// SO 事件频道注册表接口。
|
||||
/// 允许 Core 层通过接口访问动态频道查找,而无需直接依赖 EventChannelRegistry 实现。
|
||||
/// </summary>
|
||||
public interface IEventChannelRegistry
|
||||
{
|
||||
/// <summary>注册一个事件频道 SO。key 通常为 SO 资产名(如 "EVT_HitConfirmed")。</summary>
|
||||
void Register(string key, ScriptableObject channel);
|
||||
|
||||
/// <summary>
|
||||
/// 按 key 查找事件频道。未找到或类型不匹配时返回 null 并输出错误日志。
|
||||
/// </summary>
|
||||
T Get<T>(string key) where T : ScriptableObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a7c6211f04a78764c978bbdb3c5fc708
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/_Game/Scripts/Core/Events/IRewardTarget.cs
Normal file
20
Assets/_Game/Scripts/Core/Events/IRewardTarget.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 奖励接收目标接口(架构 22_QuestChallengeModule §4)。
|
||||
/// 由 <see cref="RewardSO.Apply"/> 调用,解除 BaseGames.Quest 对 BaseGames.Player 的直接依赖。
|
||||
/// PlayerStats 实现此接口,QuestManager 持有 IRewardTarget 引用。
|
||||
/// 能力类型以 uint 位掩码传递(与 Player.AbilityType : uint 一致),避免跨程序集枚举引用。
|
||||
/// </summary>
|
||||
public interface IRewardTarget
|
||||
{
|
||||
/// <summary>增加 LingZhu(货币)。</summary>
|
||||
void AddLingZhu(int amount);
|
||||
|
||||
/// <summary>增加灵魂力量上限。</summary>
|
||||
void AddSoulPower(int amount);
|
||||
|
||||
/// <summary>解锁指定能力(abilityFlag 为 AbilityType 的 uint 位掩码值)。</summary>
|
||||
void UnlockAbilityFlag(uint abilityFlag);
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/IRewardTarget.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/IRewardTarget.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a5f886592eceaa74b8b3e489e6b669b4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/IntEventChannelSO.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/IntEventChannelSO.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Int")]
|
||||
public class IntEventChannelSO : BaseEventChannelSO<int> { }
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/IntEventChannelSO.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/IntEventChannelSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce6e33f4353535944a0d2573df0a08d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
29
Assets/_Game/Scripts/Core/Events/LiquidEvent.cs
Normal file
29
Assets/_Game/Scripts/Core/Events/LiquidEvent.cs
Normal file
@@ -0,0 +1,29 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 液体区域事件负载。用于玩家进入/离开液体区域时传递区域信息。
|
||||
/// </summary>
|
||||
public readonly struct LiquidEvent
|
||||
{
|
||||
/// <summary>液体区域标识符(对应 LiquidZone SO 的 zoneId)。</summary>
|
||||
public readonly string ZoneId;
|
||||
/// <summary>液体类型(枚举直接比较,无字符串转换)。</summary>
|
||||
public readonly LiquidType LiquidType;
|
||||
|
||||
public LiquidEvent(string zoneId, LiquidType liquidType)
|
||||
{
|
||||
ZoneId = zoneId;
|
||||
LiquidType = liquidType;
|
||||
}
|
||||
|
||||
public override string ToString() => $"LiquidEvent(Zone={ZoneId}, Type={LiquidType})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 液体进出事件频道(EVT_LiquidEntered / EVT_LiquidExited)。
|
||||
/// 发布:LiquidZone(OnTriggerEnter2D / OnTriggerExit2D)
|
||||
/// 订阅:PlayerController(切换游泳状态)、DrownSystem、AudioManager
|
||||
/// </summary>
|
||||
[UnityEngine.CreateAssetMenu(menuName = "Events/LiquidEvent")]
|
||||
public class LiquidEventChannelSO : BaseEventChannelSO<LiquidEvent> { }
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/LiquidEvent.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/LiquidEvent.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9cf4419a1f1358d4583f21618396ff9d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/_Game/Scripts/Core/Events/LiquidType.cs
Normal file
14
Assets/_Game/Scripts/Core/Events/LiquidType.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
// Assets/Scripts/Core/Events/LiquidType.cs
|
||||
// LiquidType 属于事件载荷定义层(与 LiquidEvent 同程序集),
|
||||
// 以消除 Core.Events → World.Liquid 的反向依赖。
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
public enum LiquidType
|
||||
{
|
||||
Water, // 可游泳(需 Swim 能力)
|
||||
ShallowWater, // 浅水(水中慢走,无需游泳能力,速度 ×0.65)
|
||||
Mud, // 泥水(移动极慢,无需游泳能力,速度 ×0.50)
|
||||
Acid, // 接触即死(HazardZone 处理)
|
||||
Lava, // 接触即死(HazardZone 处理)
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/LiquidType.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/LiquidType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6cb1f6ef04d8c734ea4870b74030d4ea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
36
Assets/_Game/Scripts/Core/Events/QuestEvents.cs
Normal file
36
Assets/_Game/Scripts/Core/Events/QuestEvents.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 任务状态枚举(22_QuestChallengeModule §QuestSO)。
|
||||
/// </summary>
|
||||
public enum QuestState
|
||||
{
|
||||
Unavailable = 0,
|
||||
Available = 1,
|
||||
Active = 2,
|
||||
Completed = 3,
|
||||
Failed = 4
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务状态变更事件负载。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct QuestStateChangedEvent
|
||||
{
|
||||
public string QuestId;
|
||||
public QuestState State;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 任务目标进度事件负载。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct QuestObjectiveEvent
|
||||
{
|
||||
public string QuestId;
|
||||
public string ObjectiveId;
|
||||
public int Progress;
|
||||
public int Required;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/QuestEvents.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/QuestEvents.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a4badb3a03cabf941b030e5c0c45a442
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/QuestObjective")]
|
||||
public class QuestObjectiveEventChannelSO : BaseEventChannelSO<QuestObjectiveEvent> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4a46bd0c0af9fcd4dae69a00c6565be5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/QuestStateChanged")]
|
||||
public class QuestStateChangedEventChannel : BaseEventChannelSO<QuestStateChangedEvent> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9eeac19c9884b3144b390577d1c0c99f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
20
Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs
Normal file
20
Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景加载请求。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct SceneLoadRequest
|
||||
{
|
||||
/// <summary>目标场景名称(Addressable Key)</summary>
|
||||
public string SceneName;
|
||||
/// <summary>进入点 ID(供 PlayerController 决定出生位置)</summary>
|
||||
public string EntryId;
|
||||
/// <summary>玩家出生点 Transition ID(具体过渡门 ID,可为 null)</summary>
|
||||
public string EntryTransitionId;
|
||||
/// <summary>是否显示加载画面</summary>
|
||||
public bool ShowLoadingScreen;
|
||||
/// <summary>死亡复活时为 true,不执行正常过渡动画</summary>
|
||||
public bool IsRespawn;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/SceneLoadRequest.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05c1eca4b1ee3d24e80a4f42e4a19276
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/SceneLoadRequest")]
|
||||
public class SceneLoadRequestEventChannelSO : BaseEventChannelSO<SceneLoadRequest> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7b5bc8e9a164cd54ea37027bc06c37ff
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
66
Assets/_Game/Scripts/Core/Events/ServiceLocator.cs
Normal file
66
Assets/_Game/Scripts/Core/Events/ServiceLocator.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// 轻量服务定位器。通过类型键注册/查找服务,支持接口类型注册(依赖倒置)。
|
||||
/// <para><b>线程安全:</b>仅在 Unity 主线程调用。异步上下文(Task/Thread)不得访问此类。</para>
|
||||
/// </summary>
|
||||
public static class ServiceLocator
|
||||
{
|
||||
private static readonly Dictionary<Type, object> _services = new();
|
||||
|
||||
/// <summary>以接口类型 TInterface 注册实现 impl。</summary>
|
||||
public static void Register<TInterface>(TInterface impl)
|
||||
=> _services[typeof(TInterface)] = impl;
|
||||
|
||||
/// <summary>仅当尚未注册时才注册(防多场景重复注册同一服务)。</summary>
|
||||
public static void RegisterIfAbsent<TInterface>(TInterface impl)
|
||||
{
|
||||
if (!_services.ContainsKey(typeof(TInterface)))
|
||||
_services[typeof(TInterface)] = impl;
|
||||
}
|
||||
|
||||
/// <summary>查找服务。未注册时抛出 InvalidOperationException。</summary>
|
||||
public static TInterface Get<TInterface>()
|
||||
{
|
||||
if (_services.TryGetValue(typeof(TInterface), out var svc) && svc is TInterface typed)
|
||||
return typed;
|
||||
throw new InvalidOperationException(
|
||||
$"[ServiceLocator] Service '{typeof(TInterface).Name}' is not registered. "
|
||||
+ "Ensure GameServiceRegistrar.Awake() has run before this call.");
|
||||
}
|
||||
|
||||
/// <summary>安全版 Get:未注册时返回 fallback,不报错(适用于可选服务)。</summary>
|
||||
public static TInterface GetOrDefault<TInterface>(TInterface fallback = default)
|
||||
=> _services.TryGetValue(typeof(TInterface), out var svc) && svc is TInterface typed
|
||||
? typed : fallback;
|
||||
|
||||
/// <summary>
|
||||
/// 注销服务。场景卸载或 Manager OnDestroy 时调用,防止持有已销毁对象的引用。
|
||||
/// </summary>
|
||||
public static void Unregister<TInterface>()
|
||||
=> _services.Remove(typeof(TInterface));
|
||||
|
||||
/// <summary>
|
||||
/// 安全版注销:仅当注册的实例与 <paramref name="impl"/> 相同时才移除,
|
||||
/// 避免后注册的新实例被前一个实例的 OnDestroy 错误清除。
|
||||
/// </summary>
|
||||
public static void Unregister<TInterface>(TInterface impl)
|
||||
{
|
||||
if (_services.TryGetValue(typeof(TInterface), out var svc) && ReferenceEquals(svc, impl))
|
||||
_services.Remove(typeof(TInterface));
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>单元测试中替换服务实现。</summary>
|
||||
public static void OverrideForTest<TInterface>(TInterface mock)
|
||||
=> _services[typeof(TInterface)] = mock;
|
||||
|
||||
/// <summary>清空所有注册(测试用)。</summary>
|
||||
public static void Reset() => _services.Clear();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/ServiceLocator.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/ServiceLocator.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 89a62da88ff5fbf46872aaaf10f111e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
14
Assets/_Game/Scripts/Core/Events/ShopPurchaseEvent.cs
Normal file
14
Assets/_Game/Scripts/Core/Events/ShopPurchaseEvent.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 商店购买事件负载(架构 15_MapShopModule §2.3)。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct ShopPurchaseEvent
|
||||
{
|
||||
/// <summary>购买的商品 ID</summary>
|
||||
public string ItemId;
|
||||
/// <summary>支付价格(LingZhu)</summary>
|
||||
public int Price;
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/ShopPurchaseEvent.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/ShopPurchaseEvent.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fa35acf32b4f9047af74c1514117951
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/ShopPurchase")]
|
||||
public class ShopPurchaseEventChannelSO : BaseEventChannelSO<ShopPurchaseEvent> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d3d88263c925f8347bf1e19b7fa71036
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/StatusEffect")]
|
||||
public class StatusEffectEventChannelSO : BaseEventChannelSO<StatusEffectType> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 933f8f3ce17ee54409502057bd1a8138
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
17
Assets/_Game/Scripts/Core/Events/StatusEffectType.cs
Normal file
17
Assets/_Game/Scripts/Core/Events/StatusEffectType.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 状态效果类型枚举(06_CombatModule §11)。
|
||||
/// </summary>
|
||||
public enum StatusEffectType
|
||||
{
|
||||
None = 0,
|
||||
Poison = 1,
|
||||
Burn = 2,
|
||||
Freeze = 3,
|
||||
Stun = 4,
|
||||
Stagger = 5,
|
||||
Bleed = 6,
|
||||
Wet = 7 // 液体相关(用于液体+火=蒸汽等组合效果)
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/StatusEffectType.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/StatusEffectType.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7c5ad21970f85f04494b553fc483546a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/StringEventChannelSO.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/StringEventChannelSO.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/String")]
|
||||
public class StringEventChannelSO : BaseEventChannelSO<string> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23dad55c2f7bcc54a92ed61cc6f27c5b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
18
Assets/_Game/Scripts/Core/Events/ToolEvents.cs
Normal file
18
Assets/_Game/Scripts/Core/Events/ToolEvents.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
/// <summary>
|
||||
/// 工具使用事件负载(架构 09_ProgressionModule §7.5)。
|
||||
/// Tool 用地址键字符串标识,避免 Core.Events 依赖高层程序集。
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public struct ToolUsedPayload
|
||||
{
|
||||
/// <summary>工具槽索引(0 = 主槽,1 = 副槽)。</summary>
|
||||
public int SlotIndex;
|
||||
/// <summary>工具的 Addressable 地址键(对应 AddressKeys 常量)。</summary>
|
||||
public string ToolId;
|
||||
}
|
||||
|
||||
}
|
||||
11
Assets/_Game/Scripts/Core/Events/ToolEvents.cs.meta
Normal file
11
Assets/_Game/Scripts/Core/Events/ToolEvents.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a5d53ae78d3ec74c9ec1981117953e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Progression/ToolUsed")]
|
||||
public class ToolUsedEventChannelSO : BaseEventChannelSO<ToolUsedPayload> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5ab7a70182c725f4fad23afda850433e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Transform")]
|
||||
public class TransformEventChannelSO : BaseEventChannelSO<Transform> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19580b3794783e647be1e1c0771814d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Vector2")]
|
||||
public class Vector2EventChannelSO : BaseEventChannelSO<Vector2> { }
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d28df1b130e12348a582259a89fa4e2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
7
Assets/_Game/Scripts/Core/Events/VoidEventChannelSO.cs
Normal file
7
Assets/_Game/Scripts/Core/Events/VoidEventChannelSO.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Core.Events
|
||||
{
|
||||
[CreateAssetMenu(menuName = "BaseGames/Events/Void")]
|
||||
public class VoidEventChannelSO : VoidBaseEventChannelSO { }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user