chore: initial commit

This commit is contained in:
2026-05-08 11:04:00 +08:00
commit f55d2a57c3
6278 changed files with 866081 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f475eab1c9fa38649bf17b8b68d06d68
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View 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兼容直接使用静态常量的调用方
/// </summary>
public static string Resolve(string key)
{
return _registry.TryGetValue(key, out var address) ? address : key;
}
/// <summary>
/// 尝试解析 key成功时 out address 为注册的地址字符串,返回 true。
/// 若 key 未注册则返回 falseaddress 为 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;
}
}

View File

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

View File

@@ -0,0 +1,52 @@
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 PrefabCollectibleGeo = "COL_Geo";
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";
// ── Labels批量加载用──────────────────────────────────────────
public const string LabelEnemy = "Enemy";
public const string LabelPoolable = "Poolable";
public const string LabelBGM = "BGM";
public const string LabelCharms = "Charms";
}
}

View File

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

View 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();
}
}
}

View File

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

View File

@@ -0,0 +1,46 @@
using UnityEngine;
using BaseGames.Core.Events;
using BaseGames.Core.Assets;
using BaseGames.Core.Pool;
namespace BaseGames.Core
{
/// <summary>
/// 资产释放跟踪器。
/// 事件驱动:监听 EVT_SceneLoadRequest在新场景加载前清理旧场景的对象池缓存。
/// ⚠️ 不使用显式注册 APIGlobalObjectPool.ClearPool 在场景切换时批量清理。
/// </summary>
public class AssetReleaseTracker : MonoBehaviour
{
[Header("Event Channels")]
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
private string _lastLoadedScene;
private void OnEnable()
{
if (_onSceneLoadRequest != null)
_onSceneLoadRequest.OnEventRaised += OnSceneLoadRequested;
}
private void OnDisable()
{
if (_onSceneLoadRequest != null)
_onSceneLoadRequest.OnEventRaised -= OnSceneLoadRequested;
}
private void OnSceneLoadRequested(SceneLoadRequest req)
{
if (string.IsNullOrEmpty(_lastLoadedScene)) { _lastLoadedScene = req.SceneName; return; }
// 清除旧场景的敌人对象池缓存(按需扩展)
if (GlobalObjectPool.Instance != null)
{
GlobalObjectPool.Instance.ClearPool(AddressKeys.PrefabEnemyGrunt);
GlobalObjectPool.Instance.ClearPool(AddressKeys.PrefabEnemySkullArch);
}
_lastLoadedScene = req.SceneName;
}
}
}

View File

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

View 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": []
}

View File

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

View 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) { }
}
}

View File

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

View File

@@ -0,0 +1,78 @@
using System.Collections;
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>
/// 死亡/复活流程独立服务Phase 0 骨架Phase 1 完整实现)。
/// </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;
[Header("Event Channels - Listen")]
[SerializeField] private VoidEventChannelSO _onDeathScreenConfirmed;
private bool _deathConfirmed;
private void OnEnable()
{
if (_onDeathScreenConfirmed != null)
_onDeathScreenConfirmed.OnEventRaised += HandleDeathScreenConfirmed;
}
private void OnDisable()
{
if (_onDeathScreenConfirmed != null)
_onDeathScreenConfirmed.OnEventRaised -= HandleDeathScreenConfirmed;
}
private void HandleDeathScreenConfirmed() => _deathConfirmed = true;
public IEnumerator StartDeathSequenceCoroutine()
{
yield return new WaitForSeconds(_deathAnimDuration);
yield return new WaitForSeconds(_deathScreenDelay);
_deathConfirmed = false;
yield return new WaitUntil(() => _deathConfirmed);
}
public IEnumerator StartRespawnCoroutine()
{
_onRespawnStarted?.Raise();
yield return new WaitForSeconds(_respawnFadeDuration);
// Phase 1加载存档场景TODO
yield return new WaitForSeconds(_respawnFadeDuration);
_onRespawnCompleted?.Raise();
}
public IEnumerator StartGameOverCoroutine()
{
// Phase 1SteelSoul 清档并返回主菜单TODO
yield return null;
}
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3771751cae7bcd04b96f7d9026a962aa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

View File

@@ -0,0 +1,61 @@
using System;
using UnityEngine;
namespace BaseGames.Core.Events
{
/// <summary>
/// 泛型 SO 事件频道基类。T 为负载类型。
/// </summary>
public abstract class BaseEventChannelSO<T> : ScriptableObject
{
[Multiline] public string description;
public event Action<T> OnEventRaised;
public void Raise(T value)
{
#if UNITY_EDITOR
EventBusMonitor.Record(name, value?.ToString() ?? "null",
OnEventRaised?.GetInvocationList().Length ?? 0);
#endif
OnEventRaised?.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;
public event Action OnEventRaised;
public void Raise()
{
#if UNITY_EDITOR
EventBusMonitor.Record(name, "<void>",
OnEventRaised?.GetInvocationList().Length ?? 0);
#endif
OnEventRaised?.Invoke();
}
/// <summary>
/// 订阅并返回可 Dispose 的订阅句柄。
/// </summary>
public EventSubscription Subscribe(Action callback)
{
OnEventRaised += callback;
return new EventSubscription(() => OnEventRaised -= callback);
}
}
}

View File

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

View File

@@ -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
}

View File

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

View 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;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/BossPhase")]
public class BossPhaseEventChannelSO : BaseEventChannelSO<BossPhaseEvent> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/BossSkill")]
public class BossSkillEventChannelSO : BaseEventChannelSO<BossSkillEvent> { }
}

View File

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

View 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
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/ColorblindMode")]
public class ColorblindModeEventChannelSO : BaseEventChannelSO<ColorblindMode> { }
}

View File

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

View File

@@ -0,0 +1,9 @@
// 此文件已废弃。DamageInfo / DamageInfoEventChannelSO 已迁移至
// Assets/Scripts/Combat/DamageInfo.cs (namespace BaseGames.Combat)
// 程序集 BaseGames.Combat.asmdef
namespace BaseGames.Core.Events
{
// 保留空命名空间,避免 .meta 文件冲突。
// ReSharper disable once EmptyNamespace
}

View File

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

View File

@@ -0,0 +1,12 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
/// <summary>
/// 难度变更事件频道。Phase 2 难度系统使用。
/// 发布DifficultyScalerSO / SettingsManager
/// 订阅:所有需要感知当前难度的系统
/// </summary>
[CreateAssetMenu(menuName = "Events/DifficultyChanged")]
public class DifficultyChangedEventChannel : BaseEventChannelSO<DifficultyLevel> { }
}

View File

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

View File

@@ -0,0 +1,13 @@
namespace BaseGames.Core.Events
{
/// <summary>
/// 难度等级枚举。
/// </summary>
public enum DifficultyLevel
{
Easy = 0,
Normal = 1,
Hard = 2,
SteelSoul = 3 // 一命模式
}
}

View File

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

View File

@@ -0,0 +1,39 @@
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 System.DateTime Timestamp;
}
private static readonly System.Collections.Generic.Queue<EventRecord> _records
= new System.Collections.Generic.Queue<EventRecord>(256);
public static System.Collections.Generic.IEnumerable<EventRecord> Records => _records;
public static void Record(string channelName, string payload, int listenerCount)
{
if (_records.Count >= 256) _records.Dequeue();
_records.Enqueue(new EventRecord
{
ChannelName = channelName,
Payload = payload,
ListenerCount = listenerCount,
Timestamp = System.DateTime.Now
});
}
public static void Clear() => _records.Clear();
#else
public static void Record(string channelName, string payload, int listenerCount) { }
#endif
}
}

View File

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

View File

@@ -0,0 +1,54 @@
using System.Collections.Generic;
using UnityEngine;
namespace BaseGames.Core.Events
{
/// <summary>
/// 运行时事件频道注册表。
/// 在 Persistent 场景 Awake 时由 EventChannelRegistrar 注册所有频道 SO
/// 供不能持有 [SerializeField] 的动态对象CharmEffect SO 等)按类型名查找频道。
/// </summary>
public class EventChannelRegistry : MonoBehaviour, IEventChannelRegistry
{
public static EventChannelRegistry Instance { get; private set; }
private readonly Dictionary<string, ScriptableObject> _channels = new();
private void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
}
/// <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;
}
}
}

View File

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

View 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 调用 SubscribeOnDisable 调用 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;
}
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Float")]
public class FloatEventChannelSO : BaseEventChannelSO<float> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/GameState")]
public class GameStateEventChannelSO : BaseEventChannelSO<GameStateId> { }
}

View File

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

View 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);
}
}

View File

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

View File

@@ -0,0 +1,9 @@
// 此文件已废弃。HitInfo / HitConfirmedEventChannelSO 已迁移至
// Assets/Scripts/Combat/HitInfo.cs (namespace BaseGames.Combat)
// 程序集 BaseGames.Combat.asmdef
namespace BaseGames.Core.Events
{
// 保留空命名空间,避免 .meta 文件冲突。
// ReSharper disable once EmptyNamespace
}

View File

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

View 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;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Int")]
public class IntEventChannelSO : BaseEventChannelSO<int> { }
}

View File

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

View 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>液体类型(如 "Water" / "Acid" / "Lava")。</summary>
public readonly string LiquidType;
public LiquidEvent(string zoneId, string liquidType)
{
ZoneId = zoneId;
LiquidType = liquidType;
}
public override string ToString() => $"LiquidEvent(Zone={ZoneId}, Type={LiquidType})";
}
/// <summary>
/// 液体进出事件频道EVT_LiquidEntered / EVT_LiquidExited
/// 发布LiquidZoneOnTriggerEnter2D / OnTriggerExit2D
/// 订阅PlayerController切换游泳状态、DrownSystem、AudioManager
/// </summary>
[UnityEngine.CreateAssetMenu(menuName = "Events/LiquidEvent")]
public class LiquidEventChannelSO : BaseEventChannelSO<LiquidEvent> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Bool")]
public class BoolEventChannelSO : BaseEventChannelSO<bool> { }
}

View File

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

View 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;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/QuestObjective")]
public class QuestObjectiveEventChannelSO : BaseEventChannelSO<QuestObjectiveEvent> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/QuestStateChanged")]
public class QuestStateChangedEventChannel : BaseEventChannelSO<QuestStateChangedEvent> { }
}

View File

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

View 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;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/SceneLoadRequest")]
public class SceneLoadRequestEventChannelSO : BaseEventChannelSO<SceneLoadRequest> { }
}

View File

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

View 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>支付价格Geo</summary>
public int Price;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/ShopPurchase")]
public class ShopPurchaseEventChannelSO : BaseEventChannelSO<ShopPurchaseEvent> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/StatusEffect")]
public class StatusEffectEventChannelSO : BaseEventChannelSO<StatusEffectType> { }
}

View File

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

View 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 // 液体相关(用于液体+火=蒸汽等组合效果)
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/String")]
public class StringEventChannelSO : BaseEventChannelSO<string> { }
}

View File

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

View 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;
}
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Progression/ToolUsed")]
public class ToolUsedEventChannelSO : BaseEventChannelSO<ToolUsedPayload> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Transform")]
public class TransformEventChannelSO : BaseEventChannelSO<Transform> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Vector2")]
public class Vector2EventChannelSO : BaseEventChannelSO<Vector2> { }
}

View File

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

View File

@@ -0,0 +1,7 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
[CreateAssetMenu(menuName = "Events/Void")]
public class VoidEventChannelSO : VoidBaseEventChannelSO { }
}

View File

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

View File

@@ -0,0 +1,31 @@
using UnityEngine;
namespace BaseGames.Core.Events
{
/// <summary>
/// 世界导航标记事件负载。用于激活/失活地图标记点。
/// </summary>
public readonly struct WorldMarkerEvent
{
/// <summary>标记点唯一标识符。</summary>
public readonly string MarkerId;
/// <summary>标记点世界坐标。</summary>
public readonly Vector3 Position;
public WorldMarkerEvent(string markerId, Vector3 position)
{
MarkerId = markerId;
Position = position;
}
public override string ToString() => $"WorldMarkerEvent(Id={MarkerId}, Pos={Position})";
}
/// <summary>
/// 导航标记激活/失活事件频道EVT_WorldMarkerActivated / EVT_WorldMarkerDeactivated
/// 发布WorldMarkerOnTriggerEnter2D、EventChainNode
/// 订阅MapController更新迷雾地图标记
/// </summary>
[CreateAssetMenu(menuName = "Events/WorldMarker")]
public class WorldMarkerEventChannelSO : BaseEventChannelSO<WorldMarkerEvent> { }
}

View File

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

View File

@@ -0,0 +1,144 @@
using System.Collections;
using UnityEngine;
using BaseGames.Core.Events;
using BaseGames.Core.States;
namespace BaseGames.Core
{
/// <summary>
/// 全局游戏管理器。
/// 生命周期Persistent → 第一个执行DefaultExecutionOrder(-1000))。
/// 持有 <see cref="GameStateMachine"/> 并协调所有顶层服务。
/// </summary>
[DefaultExecutionOrder(-1000)]
public class GameManager : MonoBehaviour
{
// ── 单例 ──────────────────────────────────────────────────────────
public static GameManager Instance { get; private set; }
// ── Inspector 引用 ────────────────────────────────────────────────
[Header("Managers")]
[SerializeField] private SettingsManager _settingsManager;
[SerializeField] private DeathRespawnService _deathRespawnService;
[SerializeField] private SceneService _sceneService;
[Header("Event Channels - Listen")]
[SerializeField] private VoidEventChannelSO _onPlayerDied;
[SerializeField] private VoidEventChannelSO _onPauseRequested;
[SerializeField] private VoidEventChannelSO _onResumeRequested;
[SerializeField] private StringEventChannelSO _onBossFightStarted;
[SerializeField] private BoolEventChannelSO _onBossFightEnded;
[SerializeField] private VoidEventChannelSO _onDeathScreenConfirmed;
[Header("Event Channels - Raise")]
[SerializeField] private BaseEventChannelSO<GameStateId> _onGameStateChanged;
[SerializeField] private VoidEventChannelSO _onPlayerRespawned;
// ── 状态机 ────────────────────────────────────────────────────────
private readonly GameStateMachine _fsm = new GameStateMachine();
public GameStateId CurrentState => _fsm.CurrentStateId;
// ──────────────────────────────────────────────────────────────────
private void Awake()
{
if (Instance != null && Instance != this) { Destroy(gameObject); return; }
Instance = this;
DontDestroyOnLoad(gameObject);
RegisterServices();
RegisterStates();
_settingsManager?.Initialize();
_fsm.TransitionTo(GameStates.Initializing, out _);
}
private void OnEnable()
{
if (_onPlayerDied) _onPlayerDied.OnEventRaised += HandlePlayerDied;
if (_onPauseRequested) _onPauseRequested.OnEventRaised += HandlePauseRequested;
if (_onResumeRequested) _onResumeRequested.OnEventRaised += HandleResumeRequested;
if (_onBossFightStarted) _onBossFightStarted.OnEventRaised += HandleBossFightStarted;
if (_onBossFightEnded) _onBossFightEnded.OnEventRaised += HandleBossFightEnded;
if (_onDeathScreenConfirmed) _onDeathScreenConfirmed.OnEventRaised += HandleDeathScreenConfirmed;
}
private void OnDisable()
{
if (_onPlayerDied) _onPlayerDied.OnEventRaised -= HandlePlayerDied;
if (_onPauseRequested) _onPauseRequested.OnEventRaised -= HandlePauseRequested;
if (_onResumeRequested) _onResumeRequested.OnEventRaised -= HandleResumeRequested;
if (_onBossFightStarted) _onBossFightStarted.OnEventRaised -= HandleBossFightStarted;
if (_onBossFightEnded) _onBossFightEnded.OnEventRaised -= HandleBossFightEnded;
if (_onDeathScreenConfirmed) _onDeathScreenConfirmed.OnEventRaised -= HandleDeathScreenConfirmed;
}
private void Update() => _fsm.Tick(Time.deltaTime);
// ── 初始化 ────────────────────────────────────────────────────────
private void RegisterServices()
{
if (_deathRespawnService)
ServiceLocator.Register<IDeathRespawnService>(_deathRespawnService);
if (_sceneService)
ServiceLocator.Register<ISceneService>(_sceneService);
}
private void RegisterStates()
{
_fsm.Register(new InitializingState());
_fsm.Register(new MainMenuState());
_fsm.Register(new LoadingSceneState());
_fsm.Register(new GameplayState());
_fsm.Register(new BossFightState());
_fsm.Register(new PausedState());
_fsm.Register(new DeadState());
_fsm.Register(new CutsceneState());
_fsm.Register(new GameOverState());
}
// ── 状态转换公共 API ──────────────────────────────────────────────
public bool RequestTransition(GameStateId nextState)
{
if (_fsm.TransitionTo(nextState, out string error))
{
// 通知 UI 等监听者GameStateId in Core.Events 是 string-based
_onGameStateChanged?.Raise(new Events.GameStateId(nextState.Id));
return true;
}
Debug.LogWarning($"[GameManager] {error}");
return false;
}
// ── 事件处理 ──────────────────────────────────────────────────────
private void HandlePlayerDied() => StartCoroutine(DeathFlow());
private void HandlePauseRequested() => RequestTransition(GameStates.Paused);
private void HandleResumeRequested() => RequestTransition(GameStates.Gameplay);
private void HandleBossFightStarted(string bossId)
=> RequestTransition(GameStates.BossFight);
private void HandleBossFightEnded(bool victory)
{
if (victory) RequestTransition(GameStates.Gameplay);
else RequestTransition(GameStates.GameOver);
}
private bool _deathScreenConfirmed;
private void HandleDeathScreenConfirmed() => _deathScreenConfirmed = true;
private IEnumerator DeathFlow()
{
RequestTransition(GameStates.Dead);
var deathService = ServiceLocator.Get<IDeathRespawnService>();
yield return deathService.StartDeathSequenceCoroutine();
// 等待玩家在死亡画面点击重试
_deathScreenConfirmed = false;
yield return new WaitUntil(() => _deathScreenConfirmed);
yield return deathService.StartRespawnCoroutine();
RequestTransition(GameStates.LoadingScene);
_onPlayerRespawned?.Raise();
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.Core
{
/// <summary>
/// 在 Awake 时(最早执行)向 ServiceLocator 注册所有服务。
/// 挂载在 Persistent 场景的根 GameObject 上。
/// </summary>
[DefaultExecutionOrder(-2000)]
public class GameServiceRegistrar : MonoBehaviour
{
[SerializeField] private DeathRespawnService _deathRespawnService;
[SerializeField] private SceneService _sceneService;
[SerializeField] private EventChannelRegistry _eventChannelRegistry;
private void Awake()
{
// 注册 NullAudioService 作为兜底Phase 2 Audio 模块 Awake 后会用真实实现覆盖
ServiceLocator.RegisterIfAbsent<IAudioService>(new NullAudioService());
if (_deathRespawnService)
ServiceLocator.Register<IDeathRespawnService>(_deathRespawnService);
if (_sceneService)
ServiceLocator.Register<ISceneService>(_sceneService);
if (_eventChannelRegistry)
ServiceLocator.Register<IEventChannelRegistry>(_eventChannelRegistry);
}
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using BaseGames.Core.Events;
namespace BaseGames.Core
{
/// <summary>内置游戏状态常量。</summary>
public static class GameStates
{
public static readonly GameStateId Initializing = new GameStateId("Initializing");
public static readonly GameStateId MainMenu = new GameStateId("MainMenu");
public static readonly GameStateId LoadingScene = new GameStateId("LoadingScene");
public static readonly GameStateId Gameplay = new GameStateId("Gameplay");
public static readonly GameStateId BossFight = new GameStateId("BossFight");
public static readonly GameStateId Paused = new GameStateId("Paused");
public static readonly GameStateId Dead = new GameStateId("Dead");
public static readonly GameStateId Cutscene = new GameStateId("Cutscene");
public static readonly GameStateId GameOver = new GameStateId("GameOver");
}
}

View File

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

View File

@@ -0,0 +1,45 @@
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.Core
{
/// <summary>
/// 状态机核心,持有状态注册表与当前状态。
/// GameManager 持有一个实例;状态对象在 Awake 注入。
/// </summary>
public class GameStateMachine
{
private readonly Dictionary<GameStateId, IGameState> _states = new();
private IGameState _current;
public GameStateId CurrentStateId => _current?.Id ?? default;
/// <summary>注册状态实现(同 Id 注册多次以最后一次为准)。</summary>
public void Register(IGameState state) => _states[state.Id] = state;
/// <summary>转换到目标状态。失败时返回 false 并填充 error 字符串。</summary>
public bool TransitionTo(GameStateId nextId, out string error)
{
if (!_states.TryGetValue(nextId, out var next))
{
error = $"[GameStateMachine] 未知状态 '{nextId}'";
return false;
}
if (_current != null && !_current.ValidNextStates.Contains(nextId))
{
error = $"[GameStateMachine] 非法转换 {_current.Id} → {nextId}";
return false;
}
var prev = _current?.Id ?? default;
_current?.OnExit(nextId);
_current = next;
_current.OnEnter(prev);
error = null;
return true;
}
public void Tick(float deltaTime) => _current?.Tick(deltaTime);
}
}

View File

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

View File

@@ -0,0 +1,57 @@
using UnityEngine;
using BaseGames.Core.Events;
namespace BaseGames.Core
{
/// <summary>
/// 游戏全局设置数据(运行时值)。
/// </summary>
[System.Serializable]
public class GlobalSettingsData
{
public float MasterVolume = 1f;
public float BGMVolume = 0.8f;
public float SFXVolume = 1f;
public float AmbientVolume = 0.8f;
public bool VSync = true;
public int TargetFPS = 60;
public bool FullScreen = true;
public string Language = "zh-CN";
}
/// <summary>
/// 全局设置默认值 SO资产Assets/Data/Settings/SET_GlobalSettings.asset
/// </summary>
[CreateAssetMenu(menuName = "Settings/GlobalSettings")]
public class GlobalSettingsSO : ScriptableObject
{
[Header("Audio")]
public float DefaultMasterVolume = 1f;
public float DefaultBGMVolume = 0.8f;
public float DefaultSFXVolume = 1f;
public float DefaultAmbientVolume = 0.8f;
[Header("Display")]
public bool DefaultVSync = true;
public int DefaultTargetFPS = 60;
public bool DefaultFullScreen = true;
[Header("Language")]
public string DefaultLanguage = "zh-CN";
/// <summary>将 SO 默认值填入 GlobalSettingsData。</summary>
public GlobalSettingsData CreateDefault() => new GlobalSettingsData
{
MasterVolume = DefaultMasterVolume,
BGMVolume = DefaultBGMVolume,
SFXVolume = DefaultSFXVolume,
AmbientVolume = DefaultAmbientVolume,
VSync = DefaultVSync,
TargetFPS = DefaultTargetFPS,
FullScreen = DefaultFullScreen,
Language = DefaultLanguage,
};
}
}

Some files were not shown because too many files have changed in this diff Show More