using UnityEngine; using UnityEditor; using BaseGames.Core.Events; using BaseGames.Combat; using BaseGames.Combat.StatusEffects; using BaseGames.Equipment; using BaseGames.Parry; using BaseGames.Player; using BaseGames.Dialogue; using BaseGames.Progression; using System.IO; namespace BaseGames.Editor { /// /// Editor 工具:一键在 Assets/_Game/Data/Events/ 下生成所有全局事件频道 .asset 资产。 /// 菜单:BaseGames → Tools → Create Event Channel Assets /// 已存在的资产会自动跳过(幂等)。 /// public static class CreateEventChannelAssets { private const string RootPath = "Assets/_Game/Data/Events"; [MenuItem("BaseGames/Events/Create Event Channels")] public static void CreateAll() { // ── Core 原始类型频道 ────────────────────────────────────────────── CreateAsset ("Core", "EVT_Void"); CreateAsset ("Core", "EVT_Bool"); CreateAsset ("Core", "EVT_Int"); CreateAsset ("Core", "EVT_Float"); CreateAsset ("Core", "EVT_String"); CreateAsset ("Core", "EVT_Vector2"); CreateAsset ("Core", "EVT_Transform"); CreateAsset ("Core", "EVT_GameStateChanged"); CreateAsset("Core", "EVT_SceneLoadRequest"); CreateAsset ("Core", "EVT_SceneLoaded"); CreateAsset ("Core", "EVT_FadeInRequest"); CreateAsset ("Core", "EVT_FadeOutRequest"); CreateAsset ("Core", "EVT_SceneWorldStateRestored"); // 场景加载完毕、世界状态恢复后触发;场景物体在此订阅并应用存档状态,淡入前保证画面正确 // ── 难度 ────────────────────────────────────────────────────────── CreateAsset("Difficulty", "EVT_DifficultyChanged"); // ── 战斗 ────────────────────────────────────────────────────────── CreateAsset ("Combat", "EVT_DamageDealt"); CreateAsset ("Combat", "EVT_HitConfirmed"); CreateAsset ("Combat", "EVT_NailClash"); CreateAsset ("Combat", "EVT_PlayerDied"); CreateAsset ("Combat", "EVT_DeathScreenConfirmed"); CreateAsset ("Combat", "EVT_EnemyDied"); CreateAsset ("Combat", "EVT_ParrySuccess"); CreateAsset ("Combat", "EVT_PlayerRespawned"); CreateAsset ("Combat", "EVT_RespawnStarted"); CreateAsset ("Combat", "EVT_RespawnCompleted"); CreateAsset ("Combat", "EVT_CheckpointRespawn"); CreateAsset ("Combat", "EVT_StatusEffectApplied"); CreateAsset ("Combat", "EVT_StatusEffectExpired"); CreateAsset ("Combat", "EVT_ShieldBroken"); CreateAsset ("Combat", "EVT_ShieldRestored"); // ── Boss ────────────────────────────────────────────────────────── CreateAsset ("Boss", "EVT_BossSkill"); CreateAsset ("Boss", "EVT_BossPhase"); CreateAsset ("Boss", "EVT_BossPhaseChanged"); CreateAsset ("Boss", "EVT_BossDefeated"); CreateAsset ("Boss", "EVT_StatusEffect"); CreateAsset ("Boss", "EVT_BossFightStarted"); CreateAsset ("Boss", "EVT_BossFightEnded"); // ── 任务 ────────────────────────────────────────────────────────── CreateAsset("Quest", "EVT_QuestStateChanged"); CreateAsset ("Quest", "EVT_QuestObjective"); // ── UI ──────────────────────────────────────────────────────────── CreateAsset ("UI", "EVT_PauseRequested"); CreateAsset ("UI", "EVT_PauseResumed"); CreateAsset ("UI", "EVT_UICancelPressed"); // ESC / 手柄 B·Circle 全局关闭栈顶面板 CreateAsset ("UI", "EVT_FastTravelOpen"); CreateAsset ("UI", "EVT_ShopOpen"); CreateAsset ("UI", "EVT_MapOpen"); CreateAsset ("UI", "EVT_ColorblindMode"); CreateAsset ("UI", "EVT_InputDeviceChanged"); CreateAsset ("UI", "EVT_SaveIndicatorVisible"); // ── UI / 背包菜单(InventoryHub Tab 容器)────── CreateAsset ("UI/Inventory", "EVT_InventoryOpen"); // 请求打开统一背包菜单 CreateAsset ("UI/Inventory", "EVT_InventoryTabChanged"); // 当前激活 Tab 索引变化 CreateAsset ("UI/Inventory", "EVT_InventoryTabNext"); // L/R 肩键:切换到下一 Tab CreateAsset ("UI/Inventory", "EVT_InventoryTabPrev"); // L/R 肩键:切换到上一 Tab CreateAsset ("UI/Inventory", "EVT_ItemAcquired"); // 道具首次获得(itemId) CreateAsset ("UI/Inventory", "EVT_InventoryChanged"); // 背包内容变化(无负载) // ── 启动流程 / Splash ───────────────────────────────────────────── CreateAsset ("UI/Splash", "EVT_SplashStartRequest"); CreateAsset ("UI/Splash", "EVT_SplashComplete"); // ── 启动流程 / Loading 画面 ─────────────────────────────────────── CreateAsset ("UI/Loading", "EVT_LoadingStarted"); CreateAsset ("UI/Loading", "EVT_LoadingComplete"); CreateAsset ("UI/Loading", "EVT_LoadingProgressUpdated"); CreateAsset ("UI/Loading", "EVT_LoadingOverlay"); // ── 启动流程 / 主菜单 ───────────────────────────────────────────── CreateAsset ("UI/MainMenu", "EVT_SlotConfirmed"); // ── World ───────────────────────────────────────────────────────── CreateAsset ("World", "EVT_SavePointActivated"); CreateAsset ("World", "EVT_CheckpointReached"); CreateAsset ("World", "EVT_DoorOpened"); // 开门/交互机关(钥匙、机关等)触发自动存档 CreateAsset ("World", "EVT_ItemPickup"); // 道具/收集品获取(itemId) CreateAsset ("World", "EVT_CollectiblePickup"); // 关键物品拾取(护符、道具等)触发存档;AutoSaveService / QuestManager / EventChainManager 监听 CreateAsset ("World", "EVT_CollectibleSaved"); // 持久化记录收集品(collectibleId) CreateAsset ("World", "EVT_RoomEntered"); // 玩家进入新房间(roomId) CreateAsset ("World", "EVT_RegionChanged"); // 玩家首次进入新区域(regionId) CreateAsset ("World", "EVT_RevealRegion"); // 触发地图区域揭露(regionId) CreateAsset ("World", "EVT_MapUpdated"); // 房间首次探索/标注时刷新(roomId);MapManager 发布,MapPanel 订阅 CreateAsset ("World", "EVT_ChallengeCompleted"); // 挑战房间通关(challengeId) CreateAsset ("World", "EVT_ChallengeFailed"); // 挑战房间失败(challengeId) CreateAsset ("World", "EVT_LiquidEntered"); // 玩家进入液体区域 CreateAsset ("World", "EVT_LiquidExited"); // 玩家离开液体区域 CreateAsset("World", "EVT_WorldMarkerActivated"); // 导航标记点激活(地图图标显示) CreateAsset("World", "EVT_WorldMarkerDeactivated"); // 导航标记点失活(地图图标隐藏) // ── 对话/商店 ───────────────────────────────────────────────────── CreateAsset ("Dialogue", "EVT_ShopPurchase"); CreateAsset ("Dialogue", "EVT_DialogueStartRequest"); CreateAsset ("Dialogue", "EVT_DialogueEnded"); CreateAsset ("Dialogue", "EVT_ShopClosed"); // ── 玩家能力 ────────────────────────────────────────────────────── CreateAsset ("Player", "EVT_PlayerSpawned"); // 状态值频道开启粘性:延迟订阅的 HUD 等 UI 立即获得当前 HP/灵珠/形态等 CreateAsset ("Player", "EVT_HPChanged", stickyReplay: true); CreateAsset ("Player", "EVT_MaxHPChanged", stickyReplay: true); CreateAsset ("Player", "EVT_SoulPowerChanged", stickyReplay: true); CreateAsset ("Player", "EVT_SpiritPowerChanged", stickyReplay: true); CreateAsset ("Player", "EVT_SpringChargesChanged", stickyReplay: true); CreateAsset ("Player", "EVT_LingZhuChanged", stickyReplay: true); CreateAsset ("Player", "EVT_AbilityUnlocked"); CreateAsset ("Player", "EVT_AbilityUnlockedStr"); CreateAsset ("Player", "EVT_FormChanged", stickyReplay: true); CreateAsset ("Player", "EVT_SkillSetChanged"); // ── 音频 ────────────────────────────────────────────────────────── CreateAsset ("Audio", "EVT_BGMRequest"); CreateAsset ("Audio", "EVT_BGMStop"); // ── 进度/成就 ───────────────────────────────────────────────────── CreateAsset ("Progression", "EVT_ToolUsed"); CreateAsset ("Progression", "EVT_AchievementUnlocked"); CreateAsset ("Progression", "EVT_MaxHPContainerPickedUp"); CreateAsset ("Progression", "EVT_AchievementNotchGranted"); CreateAsset ("Progression", "EVT_CharmEquipped"); CreateAsset ("Progression", "EVT_CharmUnequipped"); CreateAsset ("Progression", "EVT_EquipmentChanged"); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log("[CreateEventChannelAssets] 所有事件频道资产生成完毕。"); } [MenuItem("BaseGames/Events/Reimport Event Channels")] public static void ReimportAllEventAssets() { if (!AssetDatabase.IsValidFolder(RootPath)) { Debug.LogWarning($"[CreateEventChannelAssets] 未找到目录: {RootPath}"); return; } string absoluteRoot = Path.Combine(Directory.GetCurrentDirectory(), RootPath); string[] files = Directory.GetFiles(absoluteRoot, "*.asset", SearchOption.AllDirectories); int count = 0; foreach (string file in files) { string relativePath = "Assets" + file.Replace('\\', '/').Substring(Directory.GetCurrentDirectory().Length); AssetDatabase.ImportAsset(relativePath, ImportAssetOptions.ForceUpdate); count++; } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"[CreateEventChannelAssets] 已重导入 {count} 个事件资产。"); } private static void CreateAsset(string subfolder, string assetName, bool stickyReplay = false) where T : ScriptableObject { string folderPath = $"{RootPath}/{subfolder}"; EnsureDirectory(folderPath); string fullPath = $"{folderPath}/{assetName}.asset"; var existing = AssetDatabase.LoadAssetAtPath(fullPath); if (existing != null) { if (stickyReplay) ApplyStickyReplay(existing); // 已存在也确保粘性正确(幂等) Debug.Log($"[CreateEventChannelAssets] 已跳过(已存在): {fullPath}"); return; } // 存在但类型不匹配(如旧版残留),先删除再重建 if (AssetDatabase.LoadMainAssetAtPath(fullPath) != null) { AssetDatabase.DeleteAsset(fullPath); Debug.Log($"[CreateEventChannelAssets] 已删除旧类型资产: {fullPath}"); } T asset = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(asset, fullPath); if (stickyReplay) ApplyStickyReplay(asset); Debug.Log($"[CreateEventChannelAssets] 已创建: {fullPath}"); } /// /// 对"状态值"频道开启粘性回放(BaseEventChannelSO._replayLastValueToNewSubscribers=true)。 /// 使延迟启用的 UI(如 HUD 在加载阶段未启用、进入 Gameplay 后才订阅)订阅时立即收到当前值。 /// private static void ApplyStickyReplay(UnityEngine.Object asset) { var so = new SerializedObject(asset); var prop = so.FindProperty("_replayLastValueToNewSubscribers"); if (prop != null && !prop.boolValue) { prop.boolValue = true; so.ApplyModifiedProperties(); EditorUtility.SetDirty(asset); } } /// 递归创建所有缺失的中间文件夹(使用 AssetDatabase API)。 private static void EnsureDirectory(string path) { if (AssetDatabase.IsValidFolder(path)) return; string[] parts = path.Split('/'); string current = parts[0]; for (int i = 1; i < parts.Length; i++) { string next = $"{current}/{parts[i]}"; if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(current, parts[i]); current = next; } } } }