refactor(enemy): 敌人专属子类改为零代码配置型行为组件
This commit is contained in:
@@ -414,6 +414,7 @@ GameObject:
|
||||
- component: {fileID: 3801052615690156945}
|
||||
- component: {fileID: 1497225151565698519}
|
||||
- component: {fileID: 1137051351926306612}
|
||||
- component: {fileID: 8183142527609344657}
|
||||
m_Layer: 13
|
||||
m_Name: ENM_YouZhi
|
||||
m_TagString: Untagged
|
||||
@@ -521,7 +522,7 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 6255869283652534460}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: d86a36c2999f88842a212d095749c349, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: 1a2dbfbcc31a4c34cbd3ac893f02e07d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_enemyId:
|
||||
@@ -547,7 +548,6 @@ MonoBehaviour:
|
||||
_dbg_LastKnownPos: {x: 0, y: 0}
|
||||
_dbg_BtTickInterval: 0
|
||||
_autoPlayPhaseAnimation: 1
|
||||
_activateOnSpawn: 1
|
||||
--- !u!114 &3136685549398515749
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -748,6 +748,20 @@ MonoBehaviour:
|
||||
obstructLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
--- !u!114 &8183142527609344657
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6255869283652534460}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 3198979f0bd38e1429478e7937b280b3, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_abilityId: e003_fall
|
||||
_executeOnSpawn: 1
|
||||
--- !u!1 &7519275599598288895
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -761,6 +761,7 @@ GameObject:
|
||||
- component: {fileID: 9180313924888131203}
|
||||
- component: {fileID: 1758362558550688781}
|
||||
- component: {fileID: 1140989549255509988}
|
||||
- component: {fileID: 4917575637238364831}
|
||||
m_Layer: 13
|
||||
m_Name: ENM_ZhiMu
|
||||
m_TagString: Untagged
|
||||
@@ -870,7 +871,7 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 7501196512915604413}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: cf8f8c7225dca9c42b5a451b177319b9, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: 1a2dbfbcc31a4c34cbd3ac893f02e07d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_enemyId:
|
||||
@@ -896,20 +897,6 @@ MonoBehaviour:
|
||||
_dbg_LastKnownPos: {x: 0, y: 0}
|
||||
_dbg_BtTickInterval: 0
|
||||
_autoPlayPhaseAnimation: 1
|
||||
_deathPreClip:
|
||||
_FadeDuration: 0.25
|
||||
_Speed: 1
|
||||
_Events:
|
||||
_NormalizedTimes: []
|
||||
_Callbacks: []
|
||||
_Names: []
|
||||
_Clip: {fileID: 0}
|
||||
_NormalizedStartTime: NaN
|
||||
_hurtBox: {fileID: 0}
|
||||
_deathPreDuration: 3
|
||||
references:
|
||||
version: 2
|
||||
RefIds: []
|
||||
--- !u!114 &7254417954483924161
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -1179,3 +1166,32 @@ MonoBehaviour:
|
||||
obstructLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
--- !u!114 &4917575637238364831
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 7501196512915604413}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4a4a8aad8881b4543a0918321e7efe3e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_deathPreClip:
|
||||
_FadeDuration: 0.25
|
||||
_Speed: 1
|
||||
_Events:
|
||||
_NormalizedTimes: []
|
||||
_Callbacks: []
|
||||
_Names: []
|
||||
_Clip: {fileID: 0}
|
||||
_NormalizedStartTime: NaN
|
||||
_duration: 3
|
||||
_hurtBoxesToDisable:
|
||||
- {fileID: 6064615757515489567}
|
||||
_stopBehaviorTree: 1
|
||||
_stopMovement: 1
|
||||
references:
|
||||
version: 2
|
||||
RefIds: []
|
||||
|
||||
@@ -267,6 +267,8 @@ GameObject:
|
||||
- component: {fileID: 7306072729481347792}
|
||||
- component: {fileID: 6593689935047063830}
|
||||
- component: {fileID: 7475404416877533072}
|
||||
- component: {fileID: 8876688377798292022}
|
||||
- component: {fileID: 1269485901178456362}
|
||||
m_Layer: 13
|
||||
m_Name: ENM_FeiZhi
|
||||
m_TagString: Untagged
|
||||
@@ -375,7 +377,7 @@ MonoBehaviour:
|
||||
m_GameObject: {fileID: 3986905312391723074}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: f2460e8735a4dc5409fe6b0949bd65c0, type: 3}
|
||||
m_Script: {fileID: 11500000, guid: 1a2dbfbcc31a4c34cbd3ac893f02e07d, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_enemyId:
|
||||
@@ -401,22 +403,6 @@ MonoBehaviour:
|
||||
_dbg_LastKnownPos: {x: 0, y: 0}
|
||||
_dbg_BtTickInterval: 0
|
||||
_autoPlayPhaseAnimation: 1
|
||||
_deathPreClip:
|
||||
_FadeDuration: 0.25
|
||||
_Speed: 1
|
||||
_Events:
|
||||
_NormalizedTimes: []
|
||||
_Callbacks: []
|
||||
_Names: []
|
||||
_Clip: {fileID: 0}
|
||||
_NormalizedStartTime: NaN
|
||||
_hurtBox: {fileID: 0}
|
||||
_deathPreDuration: 3
|
||||
_spawnCount: 3
|
||||
_spawnRadius: 1.5
|
||||
references:
|
||||
version: 2
|
||||
RefIds: []
|
||||
--- !u!114 &1218518018103485926
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -669,6 +655,51 @@ MonoBehaviour:
|
||||
obstructLayer:
|
||||
serializedVersion: 2
|
||||
m_Bits: 0
|
||||
--- !u!114 &8876688377798292022
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3986905312391723074}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4a4a8aad8881b4543a0918321e7efe3e, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_deathPreClip:
|
||||
_FadeDuration: 0.25
|
||||
_Speed: 1
|
||||
_Events:
|
||||
_NormalizedTimes: []
|
||||
_Callbacks: []
|
||||
_Names: []
|
||||
_Clip: {fileID: 0}
|
||||
_NormalizedStartTime: NaN
|
||||
_duration: 3
|
||||
_hurtBoxesToDisable:
|
||||
- {fileID: 9041154183844542258}
|
||||
_stopBehaviorTree: 1
|
||||
_stopMovement: 1
|
||||
references:
|
||||
version: 2
|
||||
RefIds: []
|
||||
--- !u!114 &1269485901178456362
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 3986905312391723074}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fd9abafdb6bedcc4b8fd011bde9208b6, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier:
|
||||
_payloadKey: spawn_e003
|
||||
_poolKey: ENM_YouZhi
|
||||
_count: 3
|
||||
_radius: 1.5
|
||||
--- !u!1 &5960285064422745315
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -289,8 +289,8 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1915171111}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -385,8 +385,8 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1915171111}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -3233,7 +3233,7 @@ MonoBehaviour:
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: 0
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
@@ -5509,7 +5509,7 @@ MonoBehaviour:
|
||||
m_PreInfinity: 2
|
||||
m_PostInfinity: 2
|
||||
m_RotationOrder: 4
|
||||
CustomBlends: {fileID: 0}
|
||||
CustomBlends: {fileID: 11400000, guid: 9906ca91115b34d498a040782ef36e92, type: 2}
|
||||
--- !u!81 &814710353
|
||||
AudioListener:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -5551,7 +5551,7 @@ Camera:
|
||||
height: 1
|
||||
near clip plane: 0.1
|
||||
far clip plane: 5000
|
||||
field of view: 40
|
||||
field of view: 10
|
||||
orthographic: 0
|
||||
orthographic size: 10
|
||||
m_Depth: 0
|
||||
@@ -5578,7 +5578,7 @@ Transform:
|
||||
m_GameObject: {fileID: 814710351}
|
||||
serializedVersion: 2
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalPosition: {x: -18.954266, y: 5.5, z: -64}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
@@ -6659,7 +6659,7 @@ MonoBehaviour:
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: 0
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
@@ -8402,7 +8402,7 @@ MonoBehaviour:
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: 0
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
@@ -11736,7 +11736,7 @@ MonoBehaviour:
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: 0
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
@@ -12817,8 +12817,8 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1915171111}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -15318,8 +15318,8 @@ RectTransform:
|
||||
m_Children: []
|
||||
m_Father: {fileID: 1915171111}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 0, y: 0}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 100, y: 100}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
@@ -15460,7 +15460,7 @@ MonoBehaviour:
|
||||
m_spriteAsset: {fileID: 0}
|
||||
m_tintAllSprites: 0
|
||||
m_StyleSheet: {fileID: 0}
|
||||
m_TextStyleHashCode: 0
|
||||
m_TextStyleHashCode: -1183493901
|
||||
m_overrideHtmlColors: 0
|
||||
m_faceColor:
|
||||
serializedVersion: 2
|
||||
|
||||
@@ -10,6 +10,7 @@ using BaseGames.Core.Pool;
|
||||
using BaseGames.Dialogue;
|
||||
using BaseGames.Enemies;
|
||||
using BaseGames.Enemies.Abilities;
|
||||
using BaseGames.Enemies.Behaviors;
|
||||
using BaseGames.Enemies.Boss;
|
||||
using BaseGames.Enemies.Navigation;
|
||||
using BaseGames.Enemies.Perception;
|
||||
@@ -638,7 +639,7 @@ namespace BaseGames.Editor
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr3 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E003_YouZhi enemyBase = GetOrAddComponent<E003_YouZhi>(go);
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
EnemyMovement movement = GetOrAddComponent<EnemyMovement>(go);
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
@@ -691,12 +692,17 @@ namespace BaseGames.Editor
|
||||
AssignAsset(fallAbility, "_config", report, false, "ABL_E003_Fall");
|
||||
AssignReference(fallAbility, "_contactDamage", bodyContact, report);
|
||||
|
||||
// 出生 / 外部触发执行下坠能力(零代码:替代过去的 E003_YouZhi 专属脚本)
|
||||
EnemyAbilityTrigger fallTrigger = GetOrAddComponent<EnemyAbilityTrigger>(go);
|
||||
AssignString(fallTrigger, "_abilityId", "e003_fall", report);
|
||||
AssignBool(fallTrigger, "_executeOnSpawn", true);
|
||||
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(contactHitBox, "_defaultSource", dmgSrc, report);
|
||||
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "los" }, report);
|
||||
report.Add("★ 将此对象放置于天花板下方,E003_YouZhi 会在 OnSpawn/ActivateFromCeiling 时执行下坠。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E003_YouZhi.asset。");
|
||||
report.Add("★ 将此对象放置于天花板下方;EnemyAbilityTrigger 在出生时(executeOnSpawn)自动、或被场景触发器调用 Trigger() 时执行下坠能力(e003_fall)。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定对应外部行为树资产。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
Selection.activeGameObject = go;
|
||||
@@ -731,7 +737,7 @@ namespace BaseGames.Editor
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr4 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E004_ZhiMu enemyBase = GetOrAddComponent<E004_ZhiMu>(go);
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
EnemyFeedback feedback = GetOrAddComponent<EnemyFeedback>(go);
|
||||
EnemyMovement movement = GetOrAddComponent<EnemyMovement>(go);
|
||||
@@ -819,10 +825,14 @@ namespace BaseGames.Editor
|
||||
AssignReference(slamHitBox, "_defaultSource", dmgSrc, report);
|
||||
}
|
||||
|
||||
// 死亡前摇无敌演出(零代码:替代过去的 E004_ZhiMu 专属 Die() 重写)
|
||||
EnemyDeathSequence deathSeq = GetOrAddComponent<EnemyDeathSequence>(go);
|
||||
AssignObjectArray(deathSeq, "_hurtBoxesToDisable", new Object[] { hurtBox }, report);
|
||||
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "sight" }, report);
|
||||
report.Add("★ AppearAbility._appearClip / FacePlayerAbility._faceClip 等动画 Clip 待美术接入后在 Inspector 指定。");
|
||||
report.Add("★ 在 E004_ZhiMu._deathPreClip 配置死亡前摇动画(两阶段死亡 Death_Pre 无敌)。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E004_ZhiMu.asset。");
|
||||
report.Add("★ 在 EnemyDeathSequence._deathPreClip 配置死亡前摇动画(两阶段死亡 Death_Pre 无敌)。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定对应外部行为树资产。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
Selection.activeGameObject = go;
|
||||
@@ -857,7 +867,7 @@ namespace BaseGames.Editor
|
||||
AnimancerComponent animancer = GetOrAddComponent<AnimancerComponent>(visual.gameObject);
|
||||
SpriteRenderer sr5 = SetupSpriteRenderer(visual.gameObject);
|
||||
|
||||
E005_FeiZhi enemyBase = GetOrAddComponent<E005_FeiZhi>(go);
|
||||
EnemyBase enemyBase = GetOrAddComponent<EnemyBase>(go);
|
||||
EnemyStats enemyStats = GetOrAddComponent<EnemyStats>(go);
|
||||
EnemyMovement movement = GetOrAddComponent<EnemyMovement>(go);
|
||||
GetOrAddComponent<EnemyNavAgent>(go);
|
||||
@@ -920,9 +930,16 @@ namespace BaseGames.Editor
|
||||
Object dmgSrc = FindFirstAsset("CMB_DS_EnemyBody");
|
||||
if (dmgSrc != null) AssignReference(biteHitBox, "_defaultSource", dmgSrc, report);
|
||||
|
||||
// 死亡前摇无敌演出 + 动画事件池生成幼蛭(零代码:替代过去的 E005_FeiZhi 专属 Die()/SpawnProjectile 重写)
|
||||
EnemyDeathSequence deathSeq = GetOrAddComponent<EnemyDeathSequence>(go);
|
||||
AssignObjectArray(deathSeq, "_hurtBoxesToDisable", new Object[] { hurtBox }, report);
|
||||
EnemySpawnerOnEvent spawner = GetOrAddComponent<EnemySpawnerOnEvent>(go);
|
||||
AssignString(spawner, "_payloadKey", "spawn_e003", report);
|
||||
AssignString(spawner, "_poolKey", "ENM_YouZhi", report);
|
||||
|
||||
SetupPerceptionSystemSlots(sensorHub, new[] { "aggro", "attack_melee", "attack_range", "los" }, report);
|
||||
report.Add("★ 在 E005_FeiZhi._deathPreClip 上添加 AnimationEvent 调用 SpawnProjectile(\"spawn_e003\")。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定 E005_FeiZhi.asset。");
|
||||
report.Add("★ 在 EnemyDeathSequence._deathPreClip 上添加 AnimationEvent 调用 SpawnProjectile(\"spawn_e003\"),由 EnemySpawnerOnEvent 从对象池生成幼蛭(ENM_YouZhi)。");
|
||||
report.Add("★ 挂载行为树 BehaviorTree 组件,指定对应外部行为树资产。");
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
Selection.activeGameObject = go;
|
||||
|
||||
8
Assets/_Game/Scripts/Enemies/Behaviors.meta
Normal file
8
Assets/_Game/Scripts/Enemies/Behaviors.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fddc5216bee45cf4188bb31675c201a7
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,59 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// 出生 / 外部时机触发执行指定能力(零代码替代每敌人专属的出生触发脚本)。
|
||||
/// <list type="bullet">
|
||||
/// <item>勾选 <c>executeOnSpawn</c>:对象池取出并完成 <see cref="EnemyBase.OnSpawn"/> 重置后自动执行能力
|
||||
/// (例如精英怪死亡时生成的小怪落地即下坠)。</item>
|
||||
/// <item>公共方法 <see cref="Trigger"/>:供场景战斗触发器 / UnityEvent / 动画事件在外部时机调用
|
||||
/// (例如场景预置的天花板敌人被战斗触发器激活下坠)。</item>
|
||||
/// </list>
|
||||
/// 能力本身由挂载的 <c>EnemyAbilityBase</c> 组件(按 <c>EnemyAbilitySO.abilityId</c> 注册)实现,
|
||||
/// 本组件只负责"在某个时机调用某个能力"。
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class EnemyAbilityTrigger : MonoBehaviour
|
||||
{
|
||||
[Header("能力")]
|
||||
[Tooltip("要执行的能力 Id(须与某个 EnemyAbilityBase 的 EnemyAbilitySO.abilityId 一致)")]
|
||||
[SerializeField] private string _abilityId = "ability_id";
|
||||
|
||||
[Header("触发时机")]
|
||||
[Tooltip("对象池生成 / OnSpawn 重置完成后自动执行(用于被生成出来即触发的敌人)")]
|
||||
[SerializeField] private bool _executeOnSpawn = true;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_enemy = GetComponentInParent<EnemyBase>();
|
||||
if (_enemy == null)
|
||||
Debug.LogError($"[EnemyAbilityTrigger] {name} 找不到 EnemyBase。", this);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_enemy != null) _enemy.Spawned += OnEnemySpawned;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_enemy != null) _enemy.Spawned -= OnEnemySpawned;
|
||||
}
|
||||
|
||||
private void OnEnemySpawned()
|
||||
{
|
||||
if (_executeOnSpawn) Trigger();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 立即执行配置的能力(外部时机触发:场景触发器 / UnityEvent / 动画事件调用)。
|
||||
/// </summary>
|
||||
public void Trigger()
|
||||
{
|
||||
_enemy?.Abilities.Get(_abilityId)?.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f2460e8735a4dc5409fe6b0949bd65c0
|
||||
guid: 3198979f0bd38e1429478e7937b280b3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,29 @@
|
||||
namespace BaseGames.Enemies.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// 死亡前摇演出组件接口(零代码替代每敌人专属的 <c>Die()</c> 重写)。
|
||||
/// <para>
|
||||
/// <see cref="EnemyBase.Die"/> 检测到子物体挂载此接口组件时,委托其播放无敌前摇演出,
|
||||
/// 演出结束后必须回调 <paramref name="onComplete"/>(即 <see cref="EnemyBase.PerformDeath"/>)
|
||||
/// 执行真正的死亡清理。演出期间 <see cref="EnemyBase.IsInvincible"/> 为 true。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IEnemyDeathSequence
|
||||
{
|
||||
/// <summary>开始播放死亡前摇演出;演出结束后必须调用 <paramref name="onComplete"/>。</summary>
|
||||
void Play(System.Action onComplete);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 动画事件生成处理器接口(零代码替代每敌人专属的 <c>SpawnProjectile()</c> 重写)。
|
||||
/// <para>
|
||||
/// <see cref="EnemyBase.SpawnProjectile"/> 会把 payload 路由到所有挂载的此接口组件,
|
||||
/// 由组件自行按 payload 匹配决定是否生成(如从对象池生成小怪 / 弹幕)。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public interface IEnemySpawnEventHandler
|
||||
{
|
||||
/// <summary>处理一次动画事件生成请求。<paramref name="payload"/> 为动画事件携带的配置 Id。</summary>
|
||||
void HandleSpawn(string payload);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d86a36c2999f88842a212d095749c349
|
||||
guid: 6757468022586aa41aab08314ca5a24f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
86
Assets/_Game/Scripts/Enemies/Behaviors/EnemyDeathSequence.cs
Normal file
86
Assets/_Game/Scripts/Enemies/Behaviors/EnemyDeathSequence.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// 死亡前摇无敌演出(零代码替代每敌人专属的 <c>Die()</c> 重写)。
|
||||
/// <para>
|
||||
/// <see cref="EnemyBase.Die"/> 会委托本组件:停行为树 / 停移动、停用受击框,播放前摇动画并等待,
|
||||
/// 期间敌人处于无敌(<see cref="EnemyBase.IsInvincible"/>),演出结束后回调真正的死亡清理。
|
||||
/// 前摇动画上可放置 SpawnProjectile 动画事件,配合 <see cref="EnemySpawnerOnEvent"/> 在演出中生成小怪。
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class EnemyDeathSequence : MonoBehaviour, IEnemyDeathSequence
|
||||
{
|
||||
[Header("前摇动画")]
|
||||
[Tooltip("死亡前摇动画(无敌演出);为空则跳过演出直接进入死亡清理")]
|
||||
[SerializeField] private ClipTransition _deathPreClip;
|
||||
[Tooltip("前摇演出时长(秒)")]
|
||||
[Min(0f)][SerializeField] private float _duration = 3f;
|
||||
|
||||
[Header("演出期间")]
|
||||
[Tooltip("演出期间停用的受击框(防止演出中被打断或二次受伤);对象池复用时 OnSpawn 自动恢复")]
|
||||
[SerializeField] private HurtBox[] _hurtBoxesToDisable;
|
||||
[Tooltip("演出开始时停止行为树(防止 BT 继续 Tick 覆盖演出动画)")]
|
||||
[SerializeField] private bool _stopBehaviorTree = true;
|
||||
[Tooltip("演出开始时停止移动")]
|
||||
[SerializeField] private bool _stopMovement = true;
|
||||
|
||||
private EnemyBase _enemy;
|
||||
private AnimancerComponent _animancer;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_enemy = GetComponentInParent<EnemyBase>();
|
||||
_animancer = _enemy != null ? _enemy.Animancer : GetComponentInParent<AnimancerComponent>();
|
||||
if (_enemy == null)
|
||||
Debug.LogError($"[EnemyDeathSequence] {name} 找不到 EnemyBase。", this);
|
||||
}
|
||||
|
||||
// 对象池复用:出生时恢复受击框(演出中曾被停用)
|
||||
private void OnEnable()
|
||||
{
|
||||
if (_enemy != null) _enemy.Spawned += RestoreHurtBoxes;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if (_enemy != null) _enemy.Spawned -= RestoreHurtBoxes;
|
||||
}
|
||||
|
||||
public void Play(Action onComplete)
|
||||
{
|
||||
StartCoroutine(Sequence(onComplete));
|
||||
}
|
||||
|
||||
private IEnumerator Sequence(Action onComplete)
|
||||
{
|
||||
if (_stopBehaviorTree) _enemy?.StopBehaviorTree();
|
||||
if (_stopMovement) _enemy?.StopMovement();
|
||||
|
||||
SetHurtBoxesEnabled(false);
|
||||
|
||||
if (_deathPreClip.Clip != null && _animancer != null)
|
||||
{
|
||||
_animancer.Play(_deathPreClip);
|
||||
if (_duration > 0f) yield return new WaitForSeconds(_duration);
|
||||
}
|
||||
|
||||
onComplete?.Invoke();
|
||||
}
|
||||
|
||||
private void RestoreHurtBoxes() => SetHurtBoxesEnabled(true);
|
||||
|
||||
private void SetHurtBoxesEnabled(bool enabled)
|
||||
{
|
||||
if (_hurtBoxesToDisable == null) return;
|
||||
foreach (var hb in _hurtBoxesToDisable)
|
||||
if (hb != null) hb.enabled = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf8f8c7225dca9c42b5a451b177319b9
|
||||
guid: 4a4a8aad8881b4543a0918321e7efe3e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -0,0 +1,46 @@
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Pool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies.Behaviors
|
||||
{
|
||||
/// <summary>
|
||||
/// 动画事件触发的对象池生成(零代码替代每敌人专属的 <c>SpawnProjectile()</c> 重写)。
|
||||
/// <para>
|
||||
/// <see cref="EnemyBase.SpawnProjectile"/> 会把 payload 路由到本组件;当 payload 与
|
||||
/// <c>payloadKey</c> 匹配时,在自身周围随机位置从对象池生成 <c>count</c> 个指定 key 的对象
|
||||
/// (例如精英怪死亡前摇中生成 N 个小怪)。
|
||||
/// </para>
|
||||
/// 使用方式:在动画(如死亡前摇)相应帧放置 SpawnProjectile 动画事件,payload 填 <c>payloadKey</c>。
|
||||
/// 同一敌人可挂多个本组件(不同 payloadKey)以生成不同对象。
|
||||
/// </summary>
|
||||
public class EnemySpawnerOnEvent : MonoBehaviour, IEnemySpawnEventHandler
|
||||
{
|
||||
[Header("触发匹配")]
|
||||
[Tooltip("匹配的动画事件 payload(如 \"spawn_e003\");留空则匹配任意 payload")]
|
||||
[SerializeField] private string _payloadKey = "";
|
||||
|
||||
[Header("生成")]
|
||||
[Tooltip("对象池 key(IObjectPoolService.Spawn 的 key,须经 AddressKeys 常量约定,如 \"ENM_YouZhi\")")]
|
||||
[SerializeField] private string _poolKey = "";
|
||||
[Tooltip("生成数量")]
|
||||
[Min(1)][SerializeField] private int _count = 3;
|
||||
[Tooltip("生成点相对自身的随机半径")]
|
||||
[Min(0f)][SerializeField] private float _radius = 1.5f;
|
||||
|
||||
public void HandleSpawn(string payload)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_payloadKey) && payload != _payloadKey) return;
|
||||
if (string.IsNullOrEmpty(_poolKey)) return;
|
||||
|
||||
var pool = ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool == null) return;
|
||||
|
||||
for (int i = 0; i < _count; i++)
|
||||
{
|
||||
Vector2 offset = Random.insideUnitCircle * _radius;
|
||||
pool.Spawn(_poolKey, (Vector2)transform.position + offset, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fd9abafdb6bedcc4b8fd011bde9208b6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,34 +0,0 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// E003 幼蛭。HP=1,一击即死。
|
||||
/// 支持双路初始化:
|
||||
/// - 预置路径:场景战斗触发器调用 <see cref="ActivateFromCeiling"/>
|
||||
/// - 对象池路径:E005 死亡时通过 <see cref="OnSpawn"/> 自动触发
|
||||
/// 能力脚本 <c>AnimatedCeilingDropAbility</c> 负责 Fall 动画 + 物理下落 + SetAiPhase(Patrol)。
|
||||
/// </summary>
|
||||
public class E003_YouZhi : EnemyBase
|
||||
{
|
||||
[Tooltip("对象池生成时是否立即执行下落能力(E005 触发生成路径)")]
|
||||
[SerializeField] private bool _activateOnSpawn = true;
|
||||
|
||||
public override void OnSpawn()
|
||||
{
|
||||
base.OnSpawn();
|
||||
if (_activateOnSpawn)
|
||||
Abilities.Get("e003_fall")?.Execute();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 场景预置路径:由场景触发器(EventTrigger / Animator Event)调用,触发天花板跌落。
|
||||
/// </summary>
|
||||
public void ActivateFromCeiling()
|
||||
{
|
||||
Abilities.Get("e003_fall")?.Execute();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// E004 蛭母(小Boss)。
|
||||
/// 特性:
|
||||
/// - 出场演出(AppearAbility)
|
||||
/// - 三技能(撕咬/头槌/酸液)+ 翻身(FacePlayerAbility)
|
||||
/// - 死亡两阶段:Death_Pre 无敌演出 → base.Die()
|
||||
/// </summary>
|
||||
public class E004_ZhiMu : EnemyBase
|
||||
{
|
||||
[Header("死亡演出")]
|
||||
[SerializeField] private ClipTransition _deathPreClip;
|
||||
[SerializeField] private HurtBox _hurtBox;
|
||||
[SerializeField] private float _deathPreDuration = 3f;
|
||||
|
||||
protected override void Die()
|
||||
{
|
||||
StartCoroutine(DeathSequence());
|
||||
}
|
||||
|
||||
private IEnumerator DeathSequence()
|
||||
{
|
||||
// 停止行为树防止覆盖演出动画
|
||||
StopBehaviorTree();
|
||||
StopMovement();
|
||||
|
||||
if (_hurtBox != null)
|
||||
_hurtBox.enabled = false;
|
||||
|
||||
if (_deathPreClip.Clip != null)
|
||||
{
|
||||
Animancer.Play(_deathPreClip);
|
||||
yield return new WaitForSeconds(_deathPreDuration);
|
||||
}
|
||||
|
||||
base.Die();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
using System.Collections;
|
||||
using Animancer;
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Pool;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.Enemies
|
||||
{
|
||||
/// <summary>
|
||||
/// E005 肥蛭(精英怪)。
|
||||
/// 特性:
|
||||
/// - 撕咬(MeleeVulnerabilityAbility)含后摇脆弱窗口
|
||||
/// - 死亡两阶段:Death_Pre 无敌演出(含 AnimationEvent spawn_e003)→ base.Die()
|
||||
/// </summary>
|
||||
public class E005_FeiZhi : EnemyBase
|
||||
{
|
||||
[Header("死亡演出")]
|
||||
[SerializeField] private ClipTransition _deathPreClip;
|
||||
[SerializeField] private HurtBox _hurtBox;
|
||||
[SerializeField] private float _deathPreDuration = 3f;
|
||||
|
||||
[Header("生成 E003(Death_Pre AnimationEvent 触发)")]
|
||||
[SerializeField] private int _spawnCount = 3;
|
||||
[SerializeField] private float _spawnRadius = 1.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Death_Pre 动画适当帧的 AnimationEvent 调用 SpawnProjectile("spawn_e003") 生成幼蛭。
|
||||
/// </summary>
|
||||
public override void SpawnProjectile(string payload)
|
||||
{
|
||||
if (payload != "spawn_e003") return;
|
||||
|
||||
var pool = BaseGames.Core.ServiceLocator.GetOrDefault<IObjectPoolService>();
|
||||
if (pool == null) return;
|
||||
|
||||
for (int i = 0; i < _spawnCount; i++)
|
||||
{
|
||||
Vector2 offset = Random.insideUnitCircle * _spawnRadius;
|
||||
pool.Spawn("ENM_YouZhi", (Vector2)transform.position + offset, Quaternion.identity);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Die()
|
||||
{
|
||||
StartCoroutine(DeathSequence());
|
||||
}
|
||||
|
||||
private IEnumerator DeathSequence()
|
||||
{
|
||||
StopBehaviorTree();
|
||||
StopMovement();
|
||||
|
||||
if (_hurtBox != null)
|
||||
_hurtBox.enabled = false;
|
||||
|
||||
if (_deathPreClip.Clip != null)
|
||||
{
|
||||
Animancer.Play(_deathPreClip);
|
||||
yield return new WaitForSeconds(_deathPreDuration);
|
||||
}
|
||||
|
||||
base.Die();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ namespace BaseGames.Enemies
|
||||
/// <summary>死亡时触发(ChallengeRoomManager 波次结算用)。</summary>
|
||||
public event System.Action OnDied;
|
||||
|
||||
/// <summary>
|
||||
/// 对象池取出并完成 <see cref="OnSpawn"/> 重置后触发。
|
||||
/// 配置型出生行为组件(如 EnemyAbilityTrigger)订阅此事件,实现零代码出生触发。
|
||||
/// </summary>
|
||||
public event System.Action Spawned;
|
||||
|
||||
[Header("配置 SO")]
|
||||
[SerializeField] protected EnemyStatsSO _statsSO;
|
||||
[SerializeField] protected EnemyAnimationConfigSO _animConfig;
|
||||
@@ -83,6 +89,14 @@ namespace BaseGames.Enemies
|
||||
/// </summary>
|
||||
private PooledObject _pooledObject;
|
||||
|
||||
// ── 配置型行为模块(零代码)─────────────────────────────────────────
|
||||
// 可选挂载的通用行为组件,替代过去的每敌人专属子类。Awake 时收集一次。
|
||||
private Behaviors.IEnemyDeathSequence _deathSequence;
|
||||
private readonly System.Collections.Generic.List<Behaviors.IEnemySpawnEventHandler> _spawnHandlers
|
||||
= new System.Collections.Generic.List<Behaviors.IEnemySpawnEventHandler>(2);
|
||||
// 死亡前摇演出进行中:纳入 IsInvincible,并阻止重复触发 Die。
|
||||
private bool _deathSequenceActive;
|
||||
|
||||
// ── 状态 ──────────────────────────────────────────────────────────
|
||||
private EnemyStateType _currentState;
|
||||
public EnemyStateType CurrentState => _currentState;
|
||||
@@ -129,7 +143,7 @@ namespace BaseGames.Enemies
|
||||
= new System.Collections.Generic.Dictionary<EnemyStateType, IEnemyState>();
|
||||
// ── IDamageable ───────────────────────────────────────────────────
|
||||
public bool IsAlive => _currentState != EnemyStateType.Dead;
|
||||
public virtual bool IsInvincible => _currentState == EnemyStateType.Dead;
|
||||
public virtual bool IsInvincible => _currentState == EnemyStateType.Dead || _deathSequenceActive;
|
||||
public int Defense => _stats != null ? _stats.Defense : 0;
|
||||
|
||||
public void TakeDamage(DamageInfo info)
|
||||
@@ -478,8 +492,17 @@ namespace BaseGames.Enemies
|
||||
|
||||
// ── 动画事件钩子(由 EnemyAnimationEvents 调用)────────────────────
|
||||
|
||||
/// <summary>生成弹幕 / 技能投射物。payload 为配置 Id,由子类查表实现。</summary>
|
||||
public virtual void SpawnProjectile(string payload) { }
|
||||
/// <summary>
|
||||
/// 生成弹幕 / 技能投射物(由动画事件 SpawnProjectile 触发)。
|
||||
/// 基类实现:路由到所有挂载的 <see cref="Behaviors.IEnemySpawnEventHandler"/> 组件
|
||||
/// (如 EnemySpawnerOnEvent),由组件按 payload 自行匹配并生成——实现零代码生成配置。
|
||||
/// 子类(如 RangedEnemy / ChaoFengBoss)可重写以自定义发射逻辑。
|
||||
/// </summary>
|
||||
public virtual void SpawnProjectile(string payload)
|
||||
{
|
||||
for (int i = 0; i < _spawnHandlers.Count; i++)
|
||||
_spawnHandlers[i]?.HandleSpawn(payload);
|
||||
}
|
||||
|
||||
/// <summary>切换二阶段形态(Boss 等特殊敌人重写此方法)。</summary>
|
||||
public virtual void TriggerPhaseTwo() { }
|
||||
@@ -560,6 +583,10 @@ namespace BaseGames.Enemies
|
||||
_abilities.CollectFrom(gameObject);
|
||||
_colliders = GetComponentsInChildren<Collider2D>(true);
|
||||
|
||||
// 收集配置型行为模块(零代码扩展点)
|
||||
_deathSequence = GetComponentInChildren<Behaviors.IEnemyDeathSequence>(true);
|
||||
GetComponentsInChildren(true, _spawnHandlers);
|
||||
|
||||
Debug.Assert(_statsSO != null, "[EnemyBase] _statsSO 未赋值,请在 Prefab Inspector 中指定 EnemyStatsSO。", this);
|
||||
Debug.Assert(_stats != null, "[EnemyBase] _stats 未绑定,请在 Prefab Inspector 中绑定 EnemyStats 组件。", this);
|
||||
Debug.Assert(_movement != null, "[EnemyBase] _movement 未找到,请确保同 GameObject 上挂有 EnemyMovement 组件。", this);
|
||||
@@ -667,19 +694,46 @@ namespace BaseGames.Enemies
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 停止行为树(子类 Die() 预演出阶段可调用,防止 BT 继续 Tick 覆盖演出逻辑)。
|
||||
/// 内部使用 #if GRAPH_DESIGNER 保护,子类无需处理条件编译。
|
||||
/// 停止行为树(死亡演出 / 出场演出等期间调用,防止 BT 继续 Tick 覆盖演出逻辑)。
|
||||
/// 内部使用 #if GRAPH_DESIGNER 保护,调用方无需处理条件编译。
|
||||
/// 供配置型行为组件(如 EnemyDeathSequence)调用,故为 public。
|
||||
/// </summary>
|
||||
protected void StopBehaviorTree()
|
||||
public void StopBehaviorTree()
|
||||
{
|
||||
#if GRAPH_DESIGNER
|
||||
_behaviorTree?.StopBehavior();
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 死亡入口。若挂载了 <see cref="Behaviors.IEnemyDeathSequence"/> 死亡演出组件,
|
||||
/// 则先委托其播放无敌前摇(期间 <see cref="IsInvincible"/> 为 true),演出结束后回调
|
||||
/// <see cref="PerformDeath"/> 执行真正的死亡清理;否则直接清理。
|
||||
/// 子类(如 BossBase)重写时仍调用 base.Die() 即可获得此委托行为。
|
||||
/// </summary>
|
||||
protected virtual void Die()
|
||||
{
|
||||
if (_currentState == EnemyStateType.Dead || _deathSequenceActive) return;
|
||||
|
||||
if (_deathSequence != null)
|
||||
{
|
||||
_deathSequenceActive = true; // 演出期间纳入 IsInvincible,阻止重复 Die
|
||||
_deathSequence.Play(PerformDeath);
|
||||
return;
|
||||
}
|
||||
|
||||
PerformDeath();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实际死亡清理:切 Dead 终态、清状态效果、中断能力、关碰撞体、播死亡动画、
|
||||
/// 归还对象池 / 销毁、广播死亡事件。由 <see cref="Die"/> 直接调用,
|
||||
/// 或由死亡演出组件在前摇结束后回调。
|
||||
/// </summary>
|
||||
protected void PerformDeath()
|
||||
{
|
||||
if (_currentState == EnemyStateType.Dead) return;
|
||||
_deathSequenceActive = false;
|
||||
ForceState(EnemyStateType.Dead);
|
||||
|
||||
// 死亡时清除所有状态效果
|
||||
@@ -733,6 +787,7 @@ namespace BaseGames.Enemies
|
||||
// 注意:_playerTransform 不重置(场景中玩家仍存在),只重置追踪历史
|
||||
LastKnownPlayerPosition = transform.position;
|
||||
_wasParried = false;
|
||||
_deathSequenceActive = false;
|
||||
|
||||
// 重置生命值
|
||||
if (_stats != null && _statsSO != null)
|
||||
@@ -744,6 +799,9 @@ namespace BaseGames.Enemies
|
||||
#if GRAPH_DESIGNER
|
||||
_behaviorTree?.StartBehavior();
|
||||
#endif
|
||||
|
||||
// 通知配置型出生行为组件(如 EnemyAbilityTrigger)执行出生触发逻辑
|
||||
Spawned?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -92,9 +92,12 @@ public class EnemyBase : MonoBehaviour, IDamageable
|
||||
|
||||
// ──── EnemyAnimationEvents 接口(虚方法,Boss 子类覆盖)──────────
|
||||
// 由 EnemyAnimationEvents.OnAnimationEvent 在对应动画帧触发时调用(见 24_AnimEventModule §6)
|
||||
public virtual void SpawnProjectile(string data) { }
|
||||
// SpawnProjectile 基类实现:路由到挂载的 IEnemySpawnEventHandler 组件(见 §8.5 配置型行为组件)
|
||||
public virtual void SpawnProjectile(string data) { /* routes to IEnemySpawnEventHandler */ }
|
||||
public virtual void TriggerPhaseTwo() { }
|
||||
public virtual void OnAnimationComplete(string data) { }
|
||||
// 对象池取出并完成 OnSpawn 重置后触发;配置型出生行为组件(EnemyAbilityTrigger)订阅
|
||||
public event System.Action Spawned;
|
||||
// ⚡ BD 每帧 Tick 调用 Blackboard 读写 SharedVariable;Awake 缓存、消除 GetComponent 热开销
|
||||
public BehaviorTree Blackboard => _behaviorTree;
|
||||
|
||||
@@ -426,6 +429,20 @@ public class EnemyAnimationConfigSO : ScriptableObject
|
||||
|
||||
---
|
||||
|
||||
## 8.5 配置型行为组件(零代码扩展点)
|
||||
|
||||
过去 E003/E004/E005 等敌人各写一个继承 `EnemyBase` 的专属子类,只为挂接少量生命周期钩子。现已抽成**通用、可配置的行为组件**——策划在 Prefab 上加组件、填字段即可,不再为单个敌人写脚本。`EnemyBase` 在 `Awake` 收集这些组件并在对应时机回调。
|
||||
|
||||
| 组件 | 命名空间 | 作用 | 关键字段 | 接入的 EnemyBase 钩子 |
|
||||
|------|---------|------|---------|---------------------|
|
||||
| `EnemyAbilityTrigger` | `BaseGames.Enemies.Behaviors` | 出生时(对象池)或外部时机执行某个能力 | `_abilityId`、`_executeOnSpawn` | 订阅 `Spawned` 事件;`public Trigger()` 供场景触发器/UnityEvent/动画事件调用 |
|
||||
| `EnemyDeathSequence` | 同上 | 死亡前摇无敌演出(停 BT/移动、关 HurtBox、播放前摇、等待后再真正死亡) | `_deathPreClip`、`_duration`、`_hurtBoxesToDisable` | 实现 `IEnemyDeathSequence`;`Die()` 委托其播放,期间 `IsInvincible=true`,结束回调 `PerformDeath()` |
|
||||
| `EnemySpawnerOnEvent` | 同上 | 动画事件触发的对象池生成(如死亡演出中生成小怪) | `_payloadKey`、`_poolKey`、`_count`、`_radius` | 实现 `IEnemySpawnEventHandler`;`SpawnProjectile(payload)` 路由匹配 |
|
||||
|
||||
扩展原则:新的"某时机做某事"需求优先做成此类通用组件(参考现有 `AssignReference`/`AssignAsset` 脚手架绑定模式接入 `SceneObjectPlacerTool`),而非新建 `EnemyBase` 子类——使敌人尽量通过配置而非代码创建。`RangedEnemy` / `BossBase` / `ChaoFengBoss` 等仍可重写 `SpawnProjectile`/`Die` 自定义逻辑(重写不调用 base 时即绕过路由/委托)。
|
||||
|
||||
---
|
||||
|
||||
## 9. BossBase
|
||||
|
||||
```csharp
|
||||
|
||||
@@ -404,7 +404,7 @@ Selector
|
||||
|
||||
```
|
||||
ENM_YouZhi (根节点)
|
||||
├── 组件:E003_YouZhi(C# 脚本)、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemyBase、EnemyAbilityTrigger、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemySensorHub
|
||||
├── 组件:Rigidbody2D(初始 Body Type=Kinematic,下落能力会自动切换为 Dynamic)
|
||||
├── HurtBox/
|
||||
@@ -426,8 +426,10 @@ ENM_YouZhi (根节点)
|
||||
| `_recoveryTime` | `0.1`(落地后短暂停顿) |
|
||||
| `_contactDamage` | 拖入 `ContactDamageZone/` 的 BodyContactDamage |
|
||||
|
||||
**E003_YouZhi 组件配置**:
|
||||
- `_activateOnSpawn`:勾选 ✓(对象池生成时自动触发下落;若是场景预置则由触发器调用 `ActivateFromCeiling()` 方法)
|
||||
**EnemyAbilityTrigger 组件配置**(零代码出生/触发执行能力,替代过去的 E003_YouZhi 专属脚本):
|
||||
- `_abilityId`:`e003_fall`(与 `ABL_E003_Fall.asset` 的 `abilityId` 一致)
|
||||
- `_executeOnSpawn`:勾选 ✓(对象池生成时自动触发下落)
|
||||
- 若是场景预置(非对象池),由场景战斗触发器 / UnityEvent / 动画事件调用本组件的 `Trigger()` 方法激活下落
|
||||
|
||||
**EnemySensorHub**:
|
||||
|
||||
@@ -481,7 +483,7 @@ Selector
|
||||
| `Skill02_End.anim` | 头槌收招(单次) | `RepeatSlamAbility._endClip` |
|
||||
| `Skill03.anim` | 吐酸液(单次) | `ProjectileAttackAbility.attackSequence[0/1].clip` |
|
||||
| `Flip.anim` | 翻身转向(单次) | `FacePlayerAbility._faceClip` |
|
||||
| `Death_Pre.anim` | 死亡前摇(循环,约3秒) | `E004_ZhiMu._deathPreClip` |
|
||||
| `Death_Pre.anim` | 死亡前摇(循环,约3秒) | `EnemyDeathSequence._deathPreClip` |
|
||||
| `Death.anim` | 死亡消散(单次) | AnimConfig.Dead |
|
||||
|
||||
### Step 2:ScriptableObject
|
||||
@@ -503,7 +505,7 @@ Selector
|
||||
|
||||
```
|
||||
ENM_ZhiMu (根节点)
|
||||
├── 组件:E004_ZhiMu(C# 脚本)、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemyBase、EnemyDeathSequence、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemySensorHub
|
||||
├── 组件:EnemyFeedback、Rigidbody2D
|
||||
├── HurtBox/ → 组件:HurtBox、Collider2D
|
||||
@@ -518,10 +520,11 @@ ENM_ZhiMu (根节点)
|
||||
└── Flip_Ability → 组件:FacePlayerAbility
|
||||
```
|
||||
|
||||
**E004_ZhiMu 组件配置**:
|
||||
**EnemyDeathSequence 组件配置**(零代码死亡前摇无敌演出,替代过去的 E004_ZhiMu 专属 Die() 重写):
|
||||
- `_deathPreClip`:拖入 `Death_Pre.anim`
|
||||
- `_hurtBox`:拖入 `HurtBox/` 组件
|
||||
- `_deathPreDuration`:`3`(与 Death_Pre 动画时长一致,策划调整)
|
||||
- `_duration`:`3`(与 Death_Pre 动画时长一致,策划调整)
|
||||
- `_hurtBoxesToDisable`:拖入 `HurtBox/` 组件(演出期间停用,对象池复用时出生自动恢复)
|
||||
- `_stopBehaviorTree` / `_stopMovement`:默认勾选(演出期间停 BT 与移动)
|
||||
|
||||
**AppearAbility**:
|
||||
- `_config`:`ABL_E004_Appear.asset`
|
||||
@@ -623,7 +626,7 @@ Selector
|
||||
| `Move.anim` | 移动(循环) | AnimConfig.Walk/Run |
|
||||
| `Skill01.anim` | 撕咬(单次) | `MeleeVulnerabilityAbility._attackClip` |
|
||||
| `Skill02.anim` | 吐酸液(单次,连续两轮复用) | `ProjectileAttackAbility.attackSequence[0/1].clip` |
|
||||
| `Death_Pre.anim` | 死亡前摇(含 AnimationEvent) | `E005_FeiZhi._deathPreClip` |
|
||||
| `Death_Pre.anim` | 死亡前摇(含 AnimationEvent) | `EnemyDeathSequence._deathPreClip` |
|
||||
| `Death.anim` | 死亡 | AnimConfig.Dead |
|
||||
|
||||
> ⚠️ **Death_Pre AnimationEvent 设置(关键步骤)**:
|
||||
@@ -647,7 +650,7 @@ Selector
|
||||
|
||||
```
|
||||
ENM_FeiZhi (根节点)
|
||||
├── 组件:E005_FeiZhi(C# 脚本)、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemyBase、EnemyDeathSequence、EnemySpawnerOnEvent、EnemyMovement、EnemyNavAgent、NavAgent、TransformBasedMovement
|
||||
├── 组件:EnemySensorHub
|
||||
├── 组件:Rigidbody2D
|
||||
├── HurtBox/ → 组件:HurtBox
|
||||
@@ -658,12 +661,16 @@ ENM_FeiZhi (根节点)
|
||||
└── Acid_Ability → 组件:ProjectileAttackAbility
|
||||
```
|
||||
|
||||
**E005_FeiZhi 配置**:
|
||||
**EnemyDeathSequence 配置**(零代码死亡前摇无敌演出):
|
||||
- `_deathPreClip`:`Death_Pre.anim`
|
||||
- `_hurtBox`:`HurtBox/` 组件
|
||||
- `_deathPreDuration`:与动画时长一致
|
||||
- `_spawnCount`:`3`(生成 E003 数量,策划调整)
|
||||
- `_spawnRadius`:`1.5`(生成散布半径)
|
||||
- `_duration`:与动画时长一致
|
||||
- `_hurtBoxesToDisable`:`HurtBox/` 组件
|
||||
|
||||
**EnemySpawnerOnEvent 配置**(零代码动画事件池生成,替代过去的 E005_FeiZhi 专属 SpawnProjectile 重写):
|
||||
- `_payloadKey`:`spawn_e003`(与 Death_Pre 动画事件 payload 一致)
|
||||
- `_poolKey`:`ENM_YouZhi`(对象池 key)
|
||||
- `_count`:`3`(生成幼蛭数量,策划调整)
|
||||
- `_radius`:`1.5`(生成散布半径)
|
||||
|
||||
**MeleeVulnerabilityAbility(撕咬)**:
|
||||
- `_config`:`ABL_E005_Bite.asset`
|
||||
@@ -1048,18 +1055,18 @@ Selector [嘲风根节点]
|
||||
**原因 A**:`AnimatedCeilingDropAbility._groundMask` 未设置,导致落地检测失败,能力超时后强制结束。
|
||||
**修复**:将 Ground Layer 加入 `_groundMask`。
|
||||
|
||||
**原因 B**:`E003_YouZhi._activateOnSpawn` 未勾选(对象池路径无法触发下落)。
|
||||
**修复**:在 Inspector 勾选 `_activateOnSpawn`。
|
||||
**原因 B**:`EnemyAbilityTrigger._executeOnSpawn` 未勾选(对象池路径无法触发下落),或 `_abilityId` 与 `ABL_E003_Fall.asset` 的 `abilityId`(`e003_fall`)不一致。
|
||||
**修复**:在 Inspector 勾选 `_executeOnSpawn` 并确认 `_abilityId` 拼写正确。
|
||||
|
||||
---
|
||||
|
||||
### ❌ E005 死亡后没有生成 E003
|
||||
|
||||
**原因 A**:`Death_Pre.anim` 中没有 AnimationEvent,或函数名/参数拼写错误。
|
||||
**修复**:打开动画编辑器,确认 AnimationEvent 的函数为 `SpawnProjectile`,参数为 `"spawn_e003"`(区分大小写)。
|
||||
**修复**:打开动画编辑器,确认 AnimationEvent 的函数为 `SpawnProjectile`,参数为 `"spawn_e003"`(区分大小写),且与 `EnemySpawnerOnEvent._payloadKey` 一致。
|
||||
|
||||
**原因 B**:`ENM_YouZhi` 未注册到 Addressables 或 Pool Key 拼写错误。
|
||||
**修复**:确认 Addressable Address 和对象池 Pool Key 均为 `ENM_YouZhi`(完全一致)。
|
||||
**原因 B**:`EnemySpawnerOnEvent._poolKey` 拼写错误,或 `ENM_YouZhi` 未注册到 Addressables。
|
||||
**修复**:确认 Addressable Address、对象池 Pool Key 与 `EnemySpawnerOnEvent._poolKey` 均为 `ENM_YouZhi`(完全一致)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ Selector
|
||||
├── Sequence [追击]: BD_IsSensorDetecting("aggro") → BD_ChasePlayer
|
||||
└── BD_Patrol
|
||||
```
|
||||
> 预置于天花板的 E003 由场景战斗触发器调用 `E003_YouZhi.ActivateFromCeiling()`;由 E005 死亡生成的走 `OnSpawn()`,均自动触发 `e003_fall`。
|
||||
> 预置于天花板的 E003 由场景战斗触发器调用 `EnemyAbilityTrigger.Trigger()`;由 E005 死亡生成的(对象池路径)经 `_executeOnSpawn` 在出生时自动触发,均执行 `e003_fall` 能力。
|
||||
|
||||
### E004 蛭母(出场→战斗循环:Flip/撕咬/头槌/酸液/靠近)
|
||||
```
|
||||
@@ -120,7 +120,7 @@ Selector
|
||||
### E005 肥蛭(近撕咬+后摇脆弱 / 远酸液 / 追击;死亡生成 E003)
|
||||
```
|
||||
Selector
|
||||
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement // 死亡生成 E003 由 E005_FeiZhi.SpawnProjectile(AnimationEvent) 处理,不走 BT
|
||||
├── Sequence [死亡]: BD_IsStateMatch(Dead) → BD_StopMovement // 死亡生成 E003 由 EnemySpawnerOnEvent(SpawnProjectile 动画事件路由)处理,不走 BT
|
||||
├── Sequence [撕咬]: BD_CanUseAbility("e005_bite", CheckRange) → BD_UseAbility("e005_bite")
|
||||
├── Sequence [酸液]: BD_CanUseAbility("e005_acid") → BD_UseAbility("e005_acid")
|
||||
└── BD_ChasePlayer
|
||||
|
||||
@@ -43,12 +43,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Core", "BaseGames
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp-Editor", "Assembly-CSharp-Editor.csproj", "{278D6C47-C52B-D206-DB1C-429D79FFAD5A}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Enemies", "BaseGames.Enemies.csproj", "{5E00F025-ED00-233A-3B2F-BAFF76D883F0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Assembly-CSharp", "Assembly-CSharp.csproj", "{BDE6E0A0-CE2D-39A5-53EB-DCA516DEF547}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Player.States", "BaseGames.Player.States.csproj", "{137EBC35-6D54-8E27-0DF7-C9F5F0E63705}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Enemies", "BaseGames.Enemies.csproj", "{5E00F025-ED00-233A-3B2F-BAFF76D883F0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Player", "BaseGames.Player.csproj", "{D9C8466E-50C7-502B-E60F-ECEEEE6609E5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaseGames.Audio", "BaseGames.Audio.csproj", "{F9D314BA-E5AD-4A05-04AB-93799B5E95AE}"
|
||||
@@ -215,10 +215,6 @@ Global
|
||||
{278D6C47-C52B-D206-DB1C-429D79FFAD5A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{278D6C47-C52B-D206-DB1C-429D79FFAD5A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{278D6C47-C52B-D206-DB1C-429D79FFAD5A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BDE6E0A0-CE2D-39A5-53EB-DCA516DEF547}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BDE6E0A0-CE2D-39A5-53EB-DCA516DEF547}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BDE6E0A0-CE2D-39A5-53EB-DCA516DEF547}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -227,6 +223,10 @@ Global
|
||||
{137EBC35-6D54-8E27-0DF7-C9F5F0E63705}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{137EBC35-6D54-8E27-0DF7-C9F5F0E63705}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{137EBC35-6D54-8E27-0DF7-C9F5F0E63705}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5E00F025-ED00-233A-3B2F-BAFF76D883F0}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{D9C8466E-50C7-502B-E60F-ECEEEE6609E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{D9C8466E-50C7-502B-E60F-ECEEEE6609E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{D9C8466E-50C7-502B-E60F-ECEEEE6609E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
||||
Reference in New Issue
Block a user