feat(enemy): EnemyAbilityBase 加 ResolveConfig<T> 类型化配置解析(不匹配显式报错) + EditMode 测试

This commit is contained in:
2026-07-21 11:14:28 +08:00
parent 0ad4acc484
commit ca36974ec3
4 changed files with 86 additions and 0 deletions
+8
View File
@@ -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;
_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>