revert(enemy): 收掉死亡层——死亡归物理层,AI 只需一个终态
This commit is contained in:
@@ -1,72 +0,0 @@
|
|||||||
using NUnit.Framework;
|
|
||||||
using BaseGames.AI;
|
|
||||||
using BaseGames.Enemies;
|
|
||||||
|
|
||||||
namespace BaseGames.Tests.EditMode.AI
|
|
||||||
{
|
|
||||||
public class DeathModuleTests
|
|
||||||
{
|
|
||||||
// 从一个活着的态出发,发 Died 信号进入死亡链。
|
|
||||||
static AiRuntime RunWithDeath(IDeathModule m, FakeAiContext ctx)
|
|
||||||
{
|
|
||||||
var b = new BrainBuilder();
|
|
||||||
AiStateFragments.Locomotion(b, "Alive", LocomotionMode.Patrol);
|
|
||||||
m.Build(b);
|
|
||||||
b.Entry("Alive");
|
|
||||||
b.Global().To(m.EntryState).OnEvent(AiSignal.Died);
|
|
||||||
return new AiRuntime(b.Build(), ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TerminalDeath_EntersAndStays_NoAbility()
|
|
||||||
{
|
|
||||||
var ctx = new FakeAiContext();
|
|
||||||
var rt = RunWithDeath(new TerminalDeath(), ctx);
|
|
||||||
rt.Send(AiSignal.Died);
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
|
||||||
Assert.AreEqual(0, ctx.C.Used.Count); // 演出走物理状态机,AI 不触发能力
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(TerminalDeath.Death, rt.CurrentStateName);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void AbilityDeath_TriggersOnce_DoesNotLoop()
|
|
||||||
{
|
|
||||||
var ctx = new FakeAiContext();
|
|
||||||
var rt = RunWithDeath(new AbilityDeath("die"), ctx);
|
|
||||||
rt.Send(AiSignal.Died);
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(1, ctx.C.Used.Count);
|
|
||||||
ctx.C.Running = null; // 演出播完
|
|
||||||
rt.Tick(0.1f); rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(1, ctx.C.Used.Count); // 不重播
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TwoStageDeath_PreThenFinal()
|
|
||||||
{
|
|
||||||
var ctx = new FakeAiContext();
|
|
||||||
var rt = RunWithDeath(new TwoStageDeath("die_pre", "die"), ctx);
|
|
||||||
rt.Send(AiSignal.Died);
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(TwoStageDeath.DeathPre, rt.CurrentStateName);
|
|
||||||
CollectionAssert.Contains(ctx.C.Used, "die_pre");
|
|
||||||
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(TwoStageDeath.DeathPre, rt.CurrentStateName); // 前段还在跑
|
|
||||||
|
|
||||||
ctx.C.Running = null; // 前段结束
|
|
||||||
rt.Tick(0.1f);
|
|
||||||
Assert.AreEqual(TwoStageDeath.Death, rt.CurrentStateName);
|
|
||||||
CollectionAssert.Contains(ctx.C.Used, "die");
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void AbilityDeath_Build_Throws_WhenAbilityMissing()
|
|
||||||
{
|
|
||||||
var b = new BrainBuilder();
|
|
||||||
Assert.Throws<System.InvalidOperationException>(() => new AbilityDeath().Build(b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 444e860b590a7814988a418d9df17c95
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: e1631ed6da4d4c14bb29e845f5065f51
|
|
||||||
folderAsset: yes
|
|
||||||
DefaultImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using BaseGames.AI;
|
|
||||||
using BaseGames.Enemies.Abilities;
|
|
||||||
|
|
||||||
namespace BaseGames.Enemies
|
|
||||||
{
|
|
||||||
/// <summary>单段死亡能力:进入即触发一次,不重播。</summary>
|
|
||||||
[Serializable]
|
|
||||||
public sealed class AbilityDeath : IDeathModule
|
|
||||||
{
|
|
||||||
public const string Death = "Death";
|
|
||||||
|
|
||||||
[Tooltip("死亡演出能力")]
|
|
||||||
[SerializeField] AbilityRef _ability;
|
|
||||||
|
|
||||||
public AbilityDeath() { }
|
|
||||||
public AbilityDeath(AbilityRef ability) { _ability = ability; }
|
|
||||||
|
|
||||||
public string EntryState => Death;
|
|
||||||
|
|
||||||
public void Build(BrainBuilder b)
|
|
||||||
{
|
|
||||||
string id = _ability.Id;
|
|
||||||
if (string.IsNullOrEmpty(id))
|
|
||||||
throw new InvalidOperationException("AbilityDeath:未配置死亡能力。若死亡演出走物理状态机,请改用 TerminalDeath。");
|
|
||||||
AiStateFragments.AbilityOnce(b, Death, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 1a3f1cc9d7026a840a01ab2dc951006b
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
using System;
|
|
||||||
using BaseGames.AI;
|
|
||||||
|
|
||||||
namespace BaseGames.Enemies
|
|
||||||
{
|
|
||||||
/// <summary>死亡演出交给物理状态机(EnemyBase.PerformDeath):AI 只需要一个无行为终态。</summary>
|
|
||||||
[Serializable]
|
|
||||||
public sealed class TerminalDeath : IDeathModule
|
|
||||||
{
|
|
||||||
public const string Death = "Death";
|
|
||||||
public string EntryState => Death;
|
|
||||||
public void Build(BrainBuilder b) => AiStateFragments.Terminal(b, Death);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: efb16f225a6b3b846b117409a3dfb620
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
using System;
|
|
||||||
using UnityEngine;
|
|
||||||
using BaseGames.AI;
|
|
||||||
using BaseGames.Enemies.Abilities;
|
|
||||||
|
|
||||||
namespace BaseGames.Enemies
|
|
||||||
{
|
|
||||||
/// <summary>两段死亡:前段(挣扎 / 膨胀)播完接后段(爆体,可带生成物)。</summary>
|
|
||||||
[Serializable]
|
|
||||||
public sealed class TwoStageDeath : IDeathModule
|
|
||||||
{
|
|
||||||
public const string DeathPre = "Death_Pre";
|
|
||||||
public const string Death = "Death";
|
|
||||||
|
|
||||||
[Tooltip("前段演出能力(挣扎 / 膨胀)")]
|
|
||||||
[SerializeField] AbilityRef _preAbility;
|
|
||||||
[Tooltip("后段演出能力(爆体 / 生成物)")]
|
|
||||||
[SerializeField] AbilityRef _ability;
|
|
||||||
|
|
||||||
public TwoStageDeath() { }
|
|
||||||
public TwoStageDeath(AbilityRef preAbility, AbilityRef ability)
|
|
||||||
{ _preAbility = preAbility; _ability = ability; }
|
|
||||||
|
|
||||||
public string EntryState => DeathPre;
|
|
||||||
|
|
||||||
public void Build(BrainBuilder b)
|
|
||||||
{
|
|
||||||
string preId = _preAbility.Id;
|
|
||||||
string id = _ability.Id;
|
|
||||||
if (string.IsNullOrEmpty(preId)) throw new InvalidOperationException("TwoStageDeath:未配置前段能力。");
|
|
||||||
if (string.IsNullOrEmpty(id)) throw new InvalidOperationException("TwoStageDeath:未配置后段能力。");
|
|
||||||
|
|
||||||
AiStateFragments.AbilityOnce(b, DeathPre, preId)
|
|
||||||
.To(Death).When(x => !x.Combat.IsAbilityRunning(preId), "preDone");
|
|
||||||
AiStateFragments.AbilityOnce(b, Death, id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: 0ebca92989544c9449248e635c9a6103
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
using BaseGames.AI;
|
|
||||||
|
|
||||||
namespace BaseGames.Enemies
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 死亡层:单态终结(演出交给物理状态机)/ 单段死亡能力 / 两段演出(可带生成物)。
|
|
||||||
/// 骨架只挂一条 Global → EntryState 的 Died 事件边,链条内容全归本模块。
|
|
||||||
///
|
|
||||||
/// 同 IEngagementModule:骨架只把全局 Died 边指向 EntryState,不在死亡链内部挂边,
|
|
||||||
/// 故单阶段 Build 足够。
|
|
||||||
/// </summary>
|
|
||||||
public interface IDeathModule
|
|
||||||
{
|
|
||||||
void Build(BrainBuilder b);
|
|
||||||
|
|
||||||
/// <summary>骨架全局死亡边指向的态。</summary>
|
|
||||||
string EntryState { get; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
fileFormatVersion: 2
|
|
||||||
guid: ca95968c577c65244bfb6c5a9a908ef4
|
|
||||||
MonoImporter:
|
|
||||||
externalObjects: {}
|
|
||||||
serializedVersion: 2
|
|
||||||
defaultReferences: []
|
|
||||||
executionOrder: 0
|
|
||||||
icon: {instanceID: 0}
|
|
||||||
userData:
|
|
||||||
assetBundleName:
|
|
||||||
assetBundleVariant:
|
|
||||||
Reference in New Issue
Block a user