feat(enemy): EnemyAbilityBase 加 ResolveConfig<T> 类型化配置解析(不匹配显式报错) + EditMode 测试
This commit is contained in:
@@ -0,0 +1,8 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6af97638caa36fc469e07c93d25f2031
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
using System.Collections;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
using BaseGames.Enemies.Abilities;
|
||||||
|
|
||||||
|
namespace BaseGames.Tests.EditMode.Abilities
|
||||||
|
{
|
||||||
|
public class AbilityConfigResolveTests
|
||||||
|
{
|
||||||
|
// 测试专用 SO 子类型(不依赖真实 8 个子类,保持本 Task 自包含)
|
||||||
|
private sealed class ProbeConfigSO : EnemyAbilitySO { }
|
||||||
|
private sealed class OtherConfigSO : EnemyAbilitySO { }
|
||||||
|
|
||||||
|
// 测试探针:跳过基类 Awake 依赖装配,暴露 protected 成员
|
||||||
|
private sealed class ProbeAbility : EnemyAbilityBase
|
||||||
|
{
|
||||||
|
protected override void Awake() { /* 跳过依赖装配,避免测试噪声 */ }
|
||||||
|
protected override IEnumerator ExecuteCoroutine() { yield break; }
|
||||||
|
public void SetConfig(EnemyAbilitySO c) => _config = c;
|
||||||
|
public T Probe<T>() where T : EnemyAbilitySO => ResolveConfig<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProbeAbility Make() => new GameObject("probe").AddComponent<ProbeAbility>();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveConfig_CorrectType_ReturnsInstance()
|
||||||
|
{
|
||||||
|
var probe = Make();
|
||||||
|
var cfg = ScriptableObject.CreateInstance<ProbeConfigSO>();
|
||||||
|
probe.SetConfig(cfg);
|
||||||
|
Assert.AreSame(cfg, probe.Probe<ProbeConfigSO>());
|
||||||
|
Object.DestroyImmediate(cfg);
|
||||||
|
Object.DestroyImmediate(probe.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveConfig_WrongType_LogsError_ReturnsNull()
|
||||||
|
{
|
||||||
|
var probe = Make();
|
||||||
|
probe.SetConfig(ScriptableObject.CreateInstance<OtherConfigSO>());
|
||||||
|
LogAssert.Expect(LogType.Error, new Regex("_config 需为"));
|
||||||
|
Assert.IsNull(probe.Probe<ProbeConfigSO>());
|
||||||
|
Object.DestroyImmediate(probe.gameObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void ResolveConfig_Null_LogsError_ReturnsNull()
|
||||||
|
{
|
||||||
|
var probe = Make();
|
||||||
|
probe.SetConfig(null);
|
||||||
|
LogAssert.Expect(LogType.Error, new Regex("_config 需为"));
|
||||||
|
Assert.IsNull(probe.Probe<ProbeConfigSO>());
|
||||||
|
Object.DestroyImmediate(probe.gameObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 02af4081300f9164f95b2b22339e5911
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
@@ -155,6 +155,15 @@ namespace BaseGames.Enemies.Abilities
|
|||||||
if (target == null || _enemy == null) return;
|
if (target == null || _enemy == null) return;
|
||||||
_enemy.FaceTarget(target.position);
|
_enemy.FaceTarget(target.position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>把 _config 解析为期望的子类型;类型不符/为空时显式报错(不回退)。</summary>
|
||||||
|
protected T ResolveConfig<T>() where T : EnemyAbilitySO
|
||||||
|
{
|
||||||
|
if (_config is T typed) return typed;
|
||||||
|
Debug.LogError($"[{GetType().Name}] _config 需为 {typeof(T).Name}," +
|
||||||
|
$"实际 = {(_config != null ? _config.GetType().Name : "null")}。请重建为正确的子类 SO。", this);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>WaitForSeconds 池(架构 §10 GC 优化)。能力协程统一通过此获取等待指令。</summary>
|
/// <summary>WaitForSeconds 池(架构 §10 GC 优化)。能力协程统一通过此获取等待指令。</summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user