Refactor event channels and update layer specifications

- Removed StatusEffectEventChannelSO and its associated meta file.
- Updated CreateEventChannelAssets to handle new shield-related events.
- Added CharmModule for managing charm assets in the DataHub.
- Introduced CharmEventChannelSO for charm equipped/unequipped events.
- Changed layer references from "Ground" to "Platform" in various scripts.
- Updated documentation to reflect changes in layer specifications.
- Created new event assets for ShieldBroken, ShieldRestored, StatusEffectApplied, and StatusEffectExpired.
This commit is contained in:
2026-05-21 11:08:14 +08:00
parent 280c7b22f5
commit d09bc95c3f
32 changed files with 1151 additions and 52 deletions

View File

@@ -2,6 +2,7 @@ using UnityEngine;
using UnityEditor;
using BaseGames.Core.Events;
using BaseGames.Combat;
using BaseGames.Combat.StatusEffects;
using BaseGames.Equipment;
using BaseGames.Parry;
using BaseGames.Player;
@@ -54,6 +55,8 @@ namespace BaseGames.Editor
CreateAsset<VoidEventChannelSO> ("Combat", "EVT_CheckpointRespawn");
CreateAsset<StatusEffectEventChannelSO> ("Combat", "EVT_StatusEffectApplied");
CreateAsset<StatusEffectEventChannelSO> ("Combat", "EVT_StatusEffectExpired");
CreateAsset<VoidEventChannelSO> ("Combat", "EVT_ShieldBroken");
CreateAsset<VoidEventChannelSO> ("Combat", "EVT_ShieldRestored");
// ── Boss ──────────────────────────────────────────────────────────
CreateAsset<BossSkillEventChannelSO> ("Boss", "EVT_BossSkill");
@@ -180,6 +183,13 @@ namespace BaseGames.Editor
return;
}
// 存在但类型不匹配(如旧版残留),先删除再重建
if (AssetDatabase.LoadMainAssetAtPath(fullPath) != null)
{
AssetDatabase.DeleteAsset(fullPath);
Debug.Log($"[CreateEventChannelAssets] 已删除旧类型资产: {fullPath}");
}
T asset = ScriptableObject.CreateInstance<T>();
AssetDatabase.CreateAsset(asset, fullPath);
Debug.Log($"[CreateEventChannelAssets] 已创建: {fullPath}");

View File

@@ -75,6 +75,7 @@ namespace BaseGames.Editor
_modules.Add(new EnemyModule());
_modules.Add(new FormModule());
_modules.Add(new BossSkillModule());
_modules.Add(new CharmModule());
}
// ── 布局 ─────────────────────────────────────────────────────────────

View File

@@ -0,0 +1,211 @@
using System;
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.UIElements;
using BaseGames.Equipment;
namespace BaseGames.Editor.Modules
{
/// <summary>
/// DataHub 护符模块 —— Tab 切换管理 CharmCatalogSO目录和 CharmSO护符资产。
/// </summary>
public class CharmModule : IDataModule
{
private const string CharmFolder = "Assets/_Game/Data/Progression/Charms";
private const string CatalogPrefix = "CHM_Catalog";
private const string CharmPrefix = "CHM_";
public string ModuleId => "charm";
public string DisplayName => "护符";
public string IconName => null;
private int _activeTab = 0; // 0 = 目录, 1 = 护符
private SoListPane<CharmCatalogSO> _catalogPane;
private SoListPane<CharmSO> _charmPane;
private Action<UnityEngine.Object> _onSelected;
private DetailHeader _header;
private CharmCatalogSO _selectedCatalog;
private CharmSO _selectedCharm;
public void Initialize()
{
_catalogPane = new SoListPane<CharmCatalogSO>(CharmFolder, CatalogPrefix);
_catalogPane.SelectionChanged = s => { _selectedCatalog = s; _onSelected?.Invoke(s); };
_charmPane = new SoListPane<CharmSO>(CharmFolder, CharmPrefix,
c => $"{c.notchCost}格");
_charmPane.SelectionChanged = s => { _selectedCharm = s; _onSelected?.Invoke(s); };
}
public void BuildListPane(VisualElement container, Action<UnityEngine.Object> onSelected)
{
_onSelected = onSelected;
container.style.flexDirection = FlexDirection.Column;
// Tab bar
var tabBar = new VisualElement();
tabBar.style.flexDirection = FlexDirection.Row;
tabBar.style.borderBottomWidth = 1;
tabBar.style.borderBottomColor = new StyleColor(new Color(0.5f, 0.5f, 0.5f, 0.3f));
container.Add(tabBar);
var btnCatalog = BuildTabBtn("目录", 0, tabBar);
var btnCharms = BuildTabBtn("护符", 1, tabBar);
var listArea = new VisualElement();
listArea.style.flexGrow = 1;
container.Add(listArea);
var tabs = new[] { btnCatalog, btnCharms };
ShowTab(0, listArea, tabs);
btnCatalog.clicked += () => ShowTab(0, listArea, tabs);
btnCharms.clicked += () => ShowTab(1, listArea, tabs);
_catalogPane.Refresh();
_charmPane.Refresh();
}
public void BuildDetailPane(VisualElement container, UnityEngine.Object selected)
{
_header = new DetailHeader();
_header.SetAsset(selected);
_header.RenameRequested += name => OnRenameRequested(selected, name);
container.Add(_header);
if (selected == null) return;
if (selected is CharmCatalogSO catalog)
{
container.Add(BuildCatalogActionBar(catalog));
container.Add(SkillModule.MakeDivider());
container.Add(new InspectorElement(catalog));
}
else if (selected is CharmSO charm)
{
container.Add(BuildCharmCard(charm));
container.Add(BuildCharmActionBar(charm));
container.Add(SkillModule.MakeDivider());
container.Add(new InspectorElement(charm));
}
}
public void OnActivated()
{
_catalogPane?.Refresh();
_charmPane?.Refresh();
}
// ── Tab UI ────────────────────────────────────────────────────────────
private Button BuildTabBtn(string text, int tabIdx, VisualElement bar)
{
var btn = new Button { text = text };
btn.style.flexGrow = 1;
btn.style.paddingTop = 5;
btn.style.paddingBottom = 5;
btn.style.borderTopLeftRadius = 0;
btn.style.borderTopRightRadius = 0;
btn.style.borderBottomLeftRadius = 0;
btn.style.borderBottomRightRadius = 0;
btn.style.borderLeftWidth = 0;
btn.style.borderRightWidth = 0;
btn.style.borderTopWidth = 0;
btn.style.borderBottomWidth = 0;
btn.style.backgroundColor = new StyleColor(Color.clear);
btn.userData = tabIdx;
bar.Add(btn);
return btn;
}
private void ShowTab(int tab, VisualElement area, Button[] tabBtns)
{
_activeTab = tab;
area.Clear();
for (int i = 0; i < tabBtns.Length; i++)
{
if (i == tab)
{
tabBtns[i].style.borderBottomWidth = 2;
tabBtns[i].style.borderBottomColor = new StyleColor(new Color(0.4f, 0.65f, 1f, 1f));
tabBtns[i].style.opacity = 1f;
}
else
{
tabBtns[i].style.borderBottomWidth = 0;
tabBtns[i].style.opacity = 0.65f;
}
}
if (tab == 0) { _catalogPane.style.flexGrow = 1; area.Add(_catalogPane); }
else { _charmPane.style.flexGrow = 1; area.Add(_charmPane); }
}
// ── 重命名 ────────────────────────────────────────────────────────────
private void OnRenameRequested(UnityEngine.Object asset, string newName)
{
var (ok, err) = AssetOperations.Rename(asset, newName);
if (!ok) EditorUtility.DisplayDialog("重命名失败", err, "确定");
else
{
_header.SetAsset(asset);
if (_activeTab == 0) _catalogPane.Invalidate();
else _charmPane.Invalidate();
}
}
// ── CharmCatalogSO 详情 ───────────────────────────────────────────────
private VisualElement BuildCatalogActionBar(CharmCatalogSO catalog)
{
var bar = SkillModule.MakeActionBar();
new Button(() => { EditorGUIUtility.PingObject(catalog); Selection.activeObject = catalog; })
{ text = "定位" }.AlsoAddTo(bar);
return bar;
}
// ── CharmSO 详情 ──────────────────────────────────────────────────────
private static VisualElement BuildCharmCard(CharmSO c)
{
var card = SkillModule.MakeCard();
SkillModule.AddChip(card, "ID", string.IsNullOrEmpty(c.charmId) ? "-" : c.charmId);
SkillModule.AddChip(card, "格数", $"{c.notchCost}");
SkillModule.AddChip(card, "效果数", $"{c.effects?.Count ?? 0}");
if (c.isUnique)
SkillModule.AddChip(card, "限定", "唯一");
return card;
}
private VisualElement BuildCharmActionBar(CharmSO charm)
{
var bar = SkillModule.MakeActionBar();
new Button(() => { EditorGUIUtility.PingObject(charm); Selection.activeObject = charm; })
{ text = "定位" }.AlsoAddTo(bar);
new Button(() => { var c = AssetOperations.Clone(charm, CharmFolder); if (c != null) _charmPane.Refresh(c); })
{ text = "克隆..." }.AlsoAddTo(bar);
var del = new Button(() => { if (AssetOperations.Delete(charm)) _charmPane.Refresh(null); }) { text = "删除" };
ApplyDeleteStyle(del);
del.AlsoAddTo(bar);
return bar;
}
// ── 共用 ─────────────────────────────────────────────────────────────
private static void ApplyDeleteStyle(Button btn)
{
var c = new StyleColor(new Color(0.8f, 0.3f, 0.3f, 0.6f));
btn.style.borderLeftColor = c;
btn.style.borderRightColor = c;
btn.style.borderTopColor = c;
btn.style.borderBottomColor = c;
btn.style.borderLeftWidth = 1;
btn.style.borderRightWidth = 1;
btn.style.borderTopWidth = 1;
btn.style.borderBottomWidth = 1;
btn.style.marginLeft = 8;
}
}
}

View File

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

View File

@@ -95,7 +95,7 @@ namespace BaseGames.Editor
Transform groundCheckT = GetOrCreateChild(root.transform, "GroundCheck");
groundCheckT.localPosition = new Vector3(0f, -0.75f, 0f);
AssignReference(playerMovement, "_groundCheck", groundCheckT, report);
AssignLayerMask(playerMovement, "_groundLayer", "Ground", report);
AssignLayerMask(playerMovement, "_groundLayer", "Platform", report);
// ── SkillHitBox_Slot 子节点(技能 HitBox 实例化挂点)────────────────
Transform skillSocketT = GetOrCreateChild(root.transform, "SkillHitBox_Slot");
@@ -129,11 +129,11 @@ namespace BaseGames.Editor
AssignReference(skillManager, "_modifiers", GetOrAddComponent<SkillModifierRegistry>(root), report);
AssignReference(skillManager, "_skillSocket", skillSocketT, report);
// PlayerWallDetector 墙壁检测层Wall + Ground 组合)
// PlayerWallDetector 墙壁检测层Wall + Platform 组合)
{
int wallMask = 0;
int wallL = LayerMask.NameToLayer("Wall");
int groundL = LayerMask.NameToLayer("Ground");
int groundL = LayerMask.NameToLayer("Platform");
if (wallL != -1) wallMask |= 1 << wallL;
if (groundL != -1) wallMask |= 1 << groundL;
if (wallMask != 0)
@@ -143,7 +143,7 @@ namespace BaseGames.Editor
if (wsp != null) { wsp.intValue = wallMask; wso.ApplyModifiedPropertiesWithoutUndo(); }
}
else
report.Add("★ Layer 'Wall'/'Ground' 不存在PlayerWallDetector._wallLayer 未赋值。");
report.Add("★ Layer 'Wall'/'Platform' 不存在PlayerWallDetector._wallLayer 未赋值。");
}
// ── 事件频道(可选,缺失时跳过) ───────────────────────────────────
@@ -169,6 +169,8 @@ namespace BaseGames.Editor
AssignAsset(equipmentManager, "_onAchievementNotchGranted", report, false, "EVT_AchievementNotchGranted");
AssignAsset(statusEffectManager, "_onStatusEffectApplied", report, false, "EVT_StatusEffectApplied");
AssignAsset(statusEffectManager, "_onStatusEffectExpired", report, false, "EVT_StatusEffectExpired");
AssignAsset(shield, "_onShieldBrokenChannel", report, false, "EVT_ShieldBroken");
AssignAsset(shield, "_onShieldRestoredChannel", report, false, "EVT_ShieldRestored");
// ── Config SO 自动查找(资产存在时自动绑定)──────────────────────
Object statsConfig = FindFirstAsset("PLY_PlayerStats");
@@ -178,7 +180,7 @@ namespace BaseGames.Editor
Object shieldConfig = FindFirstAsset("PLY_ShieldConfig");
Object inputReader = FindFirstAsset("InputReader");
Object equipmentConfig = FindFirstAsset("PLY_EquipmentConfig");
Object charmCatalog = FindFirstAsset("PLY_CharmCatalog");
Object charmCatalog = FindFirstAsset("CHM_Catalog");
Object animConfig = FindFirstAsset("PLY_PlayerAnimationConfig");
if (statsConfig != null) AssignReference(playerStats, "_config", statsConfig, report);
@@ -752,7 +754,7 @@ namespace BaseGames.Editor
GameObject go = new GameObject("GroundPlatform");
Undo.RegisterCreatedObjectUndo(go, "Place Ground Platform");
go.transform.position = GetDropPosition();
SetLayer(go, "Ground", report);
SetLayer(go, "Platform", report);
// 2D Sprite用 localScale 设定尺寸,让 SpriteRenderer 和 BoxCollider2D 同步缩放
go.transform.localScale = new Vector3(8f, 0.5f, 1f);
@@ -774,7 +776,7 @@ namespace BaseGames.Editor
GameObject go = new GameObject("MovingPlatform");
Undo.RegisterCreatedObjectUndo(go, "Place Moving Platform");
go.transform.position = GetDropPosition();
SetLayer(go, "Ground", report);
SetLayer(go, "Platform", report);
Rigidbody2D rb = GetOrAddComponent<Rigidbody2D>(go);
rb.bodyType = RigidbodyType2D.Kinematic;
@@ -820,7 +822,7 @@ namespace BaseGames.Editor
GetOrAddComponent<Grid>(gridGo);
GameObject groundGo = GetOrCreateChild(gridGo.transform, "Ground").gameObject;
SetLayer(groundGo, "Ground", report);
SetLayer(groundGo, "Platform", report);
GetOrAddComponent<Tilemap>(groundGo);
GetOrAddComponent<TilemapRenderer>(groundGo);
TilemapCollider2D tilemapCollider = GetOrAddComponent<TilemapCollider2D>(groundGo);
@@ -859,7 +861,7 @@ namespace BaseGames.Editor
GameObject go = new GameObject("Obstacle");
Undo.RegisterCreatedObjectUndo(go, "Place Obstacle");
go.transform.position = GetDropPosition();
SetLayer(go, "Ground", report);
SetLayer(go, "Platform", report);
// 2D Sprite用 localScale 设定尺寸,让 SpriteRenderer 和 BoxCollider2D 同步缩放
go.transform.localScale = new Vector3(1f, 1f, 1f);

View File

@@ -352,9 +352,9 @@ namespace BaseGames.Editor
GetOrAddComponent<Grid>(gridGo);
GameObject groundTileGo = GetOrCreateChild(gridGo.transform, "Ground").gameObject;
int groundLayer = LayerMask.NameToLayer("Ground");
int groundLayer = LayerMask.NameToLayer("Platform");
if (groundLayer >= 0) groundTileGo.layer = groundLayer;
else report.Add("Layer 'Ground' 不存在,请在 Tags and Layers 中创建。");
else report.Add("Layer 'Platform' 不存在,请在 Tags and Layers 中创建。");
GetOrAddComponent<Tilemap>(groundTileGo);
GetOrAddComponent<TilemapRenderer>(groundTileGo);

View File

@@ -14,14 +14,14 @@ namespace BaseGames.Editor
/// · PlayerHitBox ↔ EnemyHurtBox → 应碰撞(玩家攻击伤害敌人)
/// · EnemyHitBox ↔ PlayerHurtBox → 应碰撞(敌人攻击伤害玩家)
/// · EnemyHitBox ↔ EnemyHurtBox → 应碰撞敌人可互相伤害HitBox 运行时排除自身根节点)
/// · Player ↔ Ground → 应碰撞(玩家站在地面上)
/// · Enemy ↔ Ground → 应碰撞(敌人站在地面上)
/// · Player ↔ Platform → 应碰撞(玩家站在平台上)
/// · Enemy ↔ Platform → 应碰撞(敌人站在平台上)
/// · PlayerProjectile ↔ EnemyHurtBox → 应碰撞(玩家投射物伤害敌人)
/// · PlayerProjectile ↔ PlayerHurtBox → 应忽略(玩家投射物不自伤)
/// · PlayerProjectile ↔ Ground → 应碰撞(玩家投射物命中地形)
/// · PlayerProjectile ↔ Platform → 应碰撞(玩家投射物命中地形)
/// · EnemyProjectile ↔ PlayerHurtBox → 应碰撞(敌人投射物伤害玩家)
/// · EnemyProjectile ↔ EnemyHurtBox → 应忽略(敌人投射物不自伤)
/// · EnemyProjectile ↔ Ground → 应碰撞(敌人投射物命中地形)
/// · EnemyProjectile ↔ Platform → 应碰撞(敌人投射物命中地形)
/// · PlayerHitBox ↔ PlayerHurtBox → 应忽略(玩家不自伤)
/// · PlayerProjectile ↔ EnemyProjectile → 应忽略子弹不互相碰撞Clash 系统单独处理)
/// · HazardHitBox ↔ PlayerHurtBox → 应碰撞(环境危险伤害玩家)
@@ -37,14 +37,14 @@ namespace BaseGames.Editor
new("PlayerHitBox", "EnemyHurtBox", true, "玩家攻击伤害敌人"),
new("EnemyHitBox", "PlayerHurtBox", true, "敌人攻击伤害玩家"),
new("EnemyHitBox", "EnemyHurtBox", true, "敌人可互相伤害HitBox 运行时排除自身根节点)"),
new("Player", "Ground", true, "玩家站在地面上"),
new("Enemy", "Ground", true, "敌人站在地面上"),
new("Player", "Platform", true, "玩家站在平台上"),
new("Enemy", "Platform", true, "敌人站在平台上"),
new("PlayerProjectile", "EnemyHurtBox", true, "玩家投射物伤害敌人"),
new("PlayerProjectile", "PlayerHurtBox", false, "玩家投射物不自伤"),
new("PlayerProjectile", "Ground", true, "玩家投射物命中地形"),
new("PlayerProjectile", "Platform", true, "玩家投射物命中地形"),
new("EnemyProjectile", "PlayerHurtBox", true, "敌人投射物伤害玩家"),
new("EnemyProjectile", "EnemyHurtBox", false, "敌人投射物不自伤"),
new("EnemyProjectile", "Ground", true, "敌人投射物命中地形"),
new("EnemyProjectile", "Platform", true, "敌人投射物命中地形"),
new("PlayerHitBox", "PlayerHurtBox", false, "玩家不自伤"),
new("PlayerProjectile", "EnemyProjectile", false, "子弹不互相碰撞Clash 系统单独处理)"),
new("HazardHitBox", "PlayerHurtBox", true, "环境危险伤害玩家"),