摄像机区域的架构改动

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

View File

@@ -0,0 +1,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 作为 Addressable 地址使用。
/// </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,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";
}
}
}

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

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

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: 7982848c7ca0270419ab1c0a32fde9a0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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

View File

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

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

View File

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

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 20984324b3111c5489d9c4c3e55ec4f6
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

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

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d5c798758acf2c64097cf4ff3b088530
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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,2 @@
// DamageInfo 及 DamageInfoEventChannelSO 定义位于 Assets/Scripts/Combat/DamageInfo.cs。
namespace BaseGames.Core.Events { }

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>
/// 难度变更事件频道。
/// 发布DifficultyScalerSO / SettingsManager
/// 订阅:所有需要感知当前难度的系统
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/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,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
}
}

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 147bb5b987a0a244ba3a39c71852ca51
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 = "BaseGames/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 = "BaseGames/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,2 @@
// HitInfo 及 HitConfirmedEventChannelSO 定义位于 Assets/Scripts/Combat/HitInfo.cs。
namespace BaseGames.Core.Events { }

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

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a5f886592eceaa74b8b3e489e6b669b4
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 = "BaseGames/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>液体类型(枚举直接比较,无字符串转换)。</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
/// 发布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,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 处理)
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6cb1f6ef04d8c734ea4870b74030d4ea
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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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,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
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89a62da88ff5fbf46872aaaf10f111e1
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>支付价格LingZhu</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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/Events/Void")]
public class VoidEventChannelSO : VoidBaseEventChannelSO { }
}

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