重构清理敌人类

This commit is contained in:
2026-09-15 11:07:01 +08:00
parent 421f8bf619
commit 88ed35e757
34 changed files with 568 additions and 457 deletions
@@ -1,61 +1,124 @@
using Cysharp.Threading.Tasks;
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.Components;
using SepCore.Definition;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
public abstract class EnemyBase : TargetableObject
namespace SepCore.Entity
{
protected Transform _target;
public abstract override ImpactData GetImpactData();
public virtual float AttackRange => 1f;
public virtual void SetTarget(Transform target) => _target = target;
protected EnemyData _enemyData;
protected override void OnShow(object userData)
public abstract class EnemyBase : TargetableObject
{
base.OnShow(userData);
protected Transform _target;
if (userData is EnemyData enemyData)
public EnemyData EnemyData { get; private set; }
public MovementComponent Movement { get; private set; }
protected override TargetableObjectData _targetableObjectData => EnemyData;
public virtual float AttackRange => EnemyData.AttackRange;
public float AttackCooldown => EnemyData.AttackCooldown;
public int AttackDamage => EnemyData.AttackDamage;
public override ImpactData GetImpactData()
{
ApplyVisualParams(enemyData);
return new ImpactData(EnemyData.Camp, EnemyData.AttackDamage);
}
}
protected override void OnDead(EntityBase attacker)
{
if (Random.value < _enemyData.DropPercent)
public virtual void SetTarget(Transform target) => _target = target;
#region Lifecycle
protected override void OnInit(object userData)
{
var data = new CoinData(_enemyData.DropCoin, GameEntry.Entity.NextId())
base.OnInit(userData);
Movement = GetComponent<MovementComponent>();
_healthComponent = GetComponent<HealthComponent>();
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (userData is EnemyData enemyData)
{
Position = this.CachedTransform.position
};
GameEntry.Entity.ShowCoinAsync(data).Forget();
}
EnemyData = enemyData;
_healthComponent.OnInit(enemyData.MaxHealthBase);
Movement.OnInit(enemyData.SpeedBase, CachedTransform, null, true);
Movement.SetMove(true);
if (Random.value < _enemyData.DropPercent)
{
var data = new ExpData(_enemyData.DropExp, GameEntry.Entity.NextId())
StartAttackCooldown(enemyData.AttackCooldown);
CachedTransform.position = enemyData.Position;
ApplyVisualParams(enemyData);
}
else
{
Position = this.CachedTransform.position
};
GameEntry.Entity.ShowExpAsync(data).Forget();
Log.Error("Invalid enemy data type: {0}", userData?.GetType());
}
}
base.OnDead(attacker);
}
protected override void OnHide(bool isShutdown, object userData)
{
Movement.OnReset();
_healthComponent.OnReset();
CancelAttackCooldown();
_target = null;
private void ApplyVisualParams(EnemyData data)
{
CachedTransform.localScale = Vector3.one * Mathf.Max(0.1f, data.Scale);
base.OnHide(isShutdown, userData);
}
var renderer = GetComponentInChildren<Renderer>();
var block = new MaterialPropertyBlock();
block.SetColor("_BaseColor", data.Color);
renderer.SetPropertyBlock(block);
protected override void OnDead(EntityBase attacker)
{
if (Random.value < EnemyData.DropPercent)
{
var data = new CoinData(EnemyData.DropCoin, GameEntry.Entity.NextId())
{
Position = CachedTransform.position
};
GameEntry.Entity.ShowCoinAsync(data).Forget();
}
if (Random.value < EnemyData.DropPercent)
{
var data = new ExpData(EnemyData.DropExp, GameEntry.Entity.NextId())
{
Position = CachedTransform.position
};
GameEntry.Entity.ShowExpAsync(data).Forget();
}
base.OnDead(attacker);
}
#endregion
protected Vector3 GetTargetDirection()
{
if (_target == null)
{
return Vector3.zero;
}
return new Vector3(
_target.position.x - CachedTransform.position.x,
0f,
_target.position.z - CachedTransform.position.z
).normalized;
}
private void ApplyVisualParams(EnemyData data)
{
CachedTransform.localScale = Vector3.one * data.Scale;
var renderer = GetComponentInChildren<Renderer>();
var block = new MaterialPropertyBlock();
block.SetColor("_BaseColor", data.Color);
renderer.SetPropertyBlock(block);
}
}
}
@@ -1,5 +1,4 @@
using SepCore.Definition;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
@@ -1,83 +1,24 @@
using SepCore.Components;
using SepCore.CustomUtility;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity
{
public class MeleeEnemy : EnemyBase
{
private enum AttackStateType
{
Idle,
Check_OutRange,
Check_InRange,
Attack
}
private MovementComponent _movementComponent;
private float _attackRange = 1f;
private float _attackCooldown = 1f;
private int _attackDamage = 1;
private float _sqrAttackRange;
private AttackStateType _attackState = AttackStateType.Idle;
private EnemyData _meleeEnemyData
{
get => _enemyData;
set => _enemyData = value;
}
private EnemyStateType enemyState = EnemyStateType.Idle;
private TargetableObject _targetableTarget;
protected override TargetableObjectData _targetableObjectData => _meleeEnemyData;
public override float AttackRange => _attackRange;
public override ImpactData GetImpactData()
{
return new ImpactData(_meleeEnemyData.Camp, _attackDamage);
}
#region FSM
protected override void OnInit(object userData)
{
base.OnInit(userData);
_movementComponent = GetComponent<MovementComponent>();
_healthComponent = GetComponent<HealthComponent>();
}
#region Lifecycle
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (userData is EnemyData enemyData)
{
_meleeEnemyData = enemyData;
_healthComponent.OnInit(enemyData.MaxHealthBase);
_movementComponent.OnInit(_meleeEnemyData.SpeedBase, this.CachedTransform, null, true);
_movementComponent.SetMove(true);
_attackRange = Mathf.Max(0.1f, _meleeEnemyData.AttackRange);
_attackCooldown = Mathf.Max(0.01f, _meleeEnemyData.AttackCooldown);
_attackDamage = Mathf.Max(1, _meleeEnemyData.AttackDamage);
_sqrAttackRange = _attackRange * _attackRange;
StartAttackCooldown(_attackCooldown);
_attackState = AttackStateType.Idle;
_targetableTarget = null;
this.CachedTransform.position = enemyData.Position;
}
else
{
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
}
_sqrAttackRange = AttackRange * AttackRange;
enemyState = EnemyStateType.Idle;
_targetableTarget = null;
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -89,11 +30,8 @@ namespace SepCore.Entity
protected override void OnHide(bool isShutdown, object userData)
{
_movementComponent.OnReset();
_healthComponent.OnReset();
_targetableTarget = null;
CancelAttackCooldown();
_attackState = AttackStateType.Idle;
enemyState = EnemyStateType.Idle;
base.OnHide(isShutdown, userData);
}
@@ -108,74 +46,73 @@ namespace SepCore.Entity
private void UpdateAttackState(float elapseSeconds)
{
switch (_attackState)
switch (enemyState)
{
case AttackStateType.Idle:
SetMove(false);
case EnemyStateType.Idle:
Movement.SetMove(false);
if (HasValidTarget())
{
TransitionTo(AttackStateType.Check_OutRange);
TransitionTo(EnemyStateType.Seek);
}
break;
case AttackStateType.Check_OutRange:
case EnemyStateType.Seek:
if (!HasValidTarget())
{
TransitionTo(AttackStateType.Idle);
TransitionTo(EnemyStateType.Idle);
return;
}
if (IsTargetInRange())
{
TransitionTo(AttackStateType.Check_InRange);
TransitionTo(EnemyStateType.Chase);
return;
}
SetMove(true);
_movementComponent.SetDirection(GetTargetDirection());
Movement.SetMove(true);
Movement.SetDirection(GetTargetDirection());
break;
case AttackStateType.Check_InRange:
case EnemyStateType.Chase:
if (!HasValidTarget())
{
TransitionTo(AttackStateType.Idle);
TransitionTo(EnemyStateType.Idle);
return;
}
SetMove(false);
Movement.SetMove(false);
if (!IsTargetInRange())
{
TransitionTo(AttackStateType.Check_OutRange);
TransitionTo(EnemyStateType.Seek);
return;
}
if (_canAttack)
{
ResetAttackCooldown(_attackCooldown);
TransitionTo(AttackStateType.Attack);
ResetAttackCooldown(AttackCooldown);
TransitionTo(EnemyStateType.Attack);
}
break;
}
}
private void TransitionTo(AttackStateType newState)
private void TransitionTo(EnemyStateType newState)
{
_attackState = newState;
enemyState = newState;
if (_attackState == AttackStateType.Check_InRange)
if (enemyState == EnemyStateType.Chase)
{
SetMove(false);
Movement.SetMove(false);
}
if (_attackState == AttackStateType.Attack)
if (enemyState == EnemyStateType.Attack)
{
SetMove(false);
Movement.SetMove(false);
ExecuteAttack();
TransitionTo(AttackStateType.Check_InRange);
TransitionTo(EnemyStateType.Chase);
}
}
private bool HasValidTarget()
{
if (_target == null) return false;
@@ -192,7 +129,7 @@ namespace SepCore.Entity
{
if (_target == null) return false;
Vector3 delta = _target.position - this.CachedTransform.position;
Vector3 delta = _target.position - CachedTransform.position;
delta.y = 0f;
return delta.sqrMagnitude <= _sqrAttackRange;
}
@@ -208,31 +145,9 @@ namespace SepCore.Entity
return;
}
int damage = AIUtility.CalcDamageHP(_attackDamage, null, targetImpactData.DefenseStat,
int damage = AIUtility.CalcDamageHP(AttackDamage, null, targetImpactData.DefenseStat,
targetImpactData.DodgeStat);
_targetableTarget.ApplyDamage(this, damage);
}
private void SetMove(bool value)
{
if (_movementComponent != null)
{
_movementComponent.SetMove(value);
}
}
private Vector3 GetTargetDirection()
{
if (_target == null)
{
return Vector3.zero;
}
return new Vector3(
_target.position.x - this.CachedTransform.position.x,
0f,
_target.position.z - this.CachedTransform.position.z
).normalized;
}
}
}
@@ -1,8 +1,7 @@
using SepCore.Components;
using Cysharp.Threading.Tasks;
using SepCore.AsyncTask;
using SepCore.CustomUtility;
using SepCore.Definition;
using Cysharp.Threading.Tasks;
using UnityEngine;
using UnityGameFramework.Runtime;
@@ -10,83 +9,13 @@ namespace SepCore.Entity
{
public class RemoteEnemy : EnemyBase
{
private const string EnemyProjectileGroupName = "Bullet";
private const float EnemyProjectileGroupAutoReleaseInterval = 60f;
private const int EnemyProjectileGroupCapacity = 64;
private const float EnemyProjectileGroupExpireTime = 60f;
private const int EnemyProjectileGroupPriority = 0;
private const string ProjectileSpeedParamKey = "ProjectileSpeed";
private const string ProjectileLifeTimeParamKey = "ProjectileLifeTime";
private const string ProjectileSpawnForwardOffsetParamKey = "ProjectileSpawnForwardOffset";
private const string ProjectileSpawnHeightOffsetParamKey = "ProjectileSpawnHeightOffset";
private const string ProjectileAssetNameParamKey = "ProjectileAssetName";
private MovementComponent _movementComponent;
private float _attackRange = 1f;
private float _attackRangeSquared;
private float _attackCooldown = 1f;
private int _attackDamage = 1;
[SerializeField] private float _projectileSpeed = 12f;
[SerializeField] private float _projectileLifeTime = 3f;
[SerializeField] private float _projectileSpawnForwardOffset = 0.7f;
[SerializeField] private float _projectileSpawnHeightOffset = 0.6f;
[SerializeField] private string _projectileAssetName = "BulletHandgun";
private EnemyData _remoteEnemyData
{
get => _enemyData;
set => _enemyData = value;
}
protected override TargetableObjectData _targetableObjectData => _remoteEnemyData;
public override float AttackRange => _attackRange;
public override ImpactData GetImpactData()
{
return new ImpactData(_remoteEnemyData.Camp, _attackDamage);
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
_movementComponent = GetComponent<MovementComponent>();
_healthComponent = GetComponent<HealthComponent>();
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
if (userData is EnemyData enemyData)
{
_remoteEnemyData = enemyData;
_healthComponent.OnInit(enemyData.MaxHealthBase);
_movementComponent.OnInit(_remoteEnemyData.SpeedBase, this.CachedTransform, null, true);
_movementComponent.SetMove(true);
_attackRange = Mathf.Max(0.1f, _remoteEnemyData.AttackRange);
_attackRangeSquared = _attackRange * _attackRange;
_attackCooldown = Mathf.Max(0.01f, _remoteEnemyData.AttackCooldown);
_attackDamage = Mathf.Max(1, _remoteEnemyData.AttackDamage);
_projectileSpeed = ReadPositiveParam(ProjectileSpeedParamKey, _projectileSpeed);
_projectileLifeTime = ReadPositiveParam(ProjectileLifeTimeParamKey, _projectileLifeTime);
_projectileSpawnForwardOffset = ReadPositiveParam(ProjectileSpawnForwardOffsetParamKey,
_projectileSpawnForwardOffset);
_projectileSpawnHeightOffset = ReadPositiveParam(ProjectileSpawnHeightOffsetParamKey,
_projectileSpawnHeightOffset);
_projectileAssetName = ReadStringParam(ProjectileAssetNameParamKey, _projectileAssetName);
StartAttackCooldown(_attackCooldown);
this.CachedTransform.position = enemyData.Position;
}
else
{
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
}
_attackRangeSquared = AttackRange * AttackRange;
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -95,50 +24,45 @@ namespace SepCore.Entity
if (_target == null)
{
_movementComponent.SetMove(false);
Movement.SetMove(false);
return;
}
Vector3 toTarget = _target.position - this.CachedTransform.position;
Vector3 toTarget = _target.position - CachedTransform.position;
toTarget.y = 0f;
float distanceSquared = toTarget.sqrMagnitude;
if (distanceSquared <= _attackRangeSquared)
{
_movementComponent.SetMove(false);
Movement.SetMove(false);
if (_canAttack)
{
TryFireProjectile();
ResetAttackCooldown(_attackCooldown);
ResetAttackCooldown(AttackCooldown);
}
}
else
{
_movementComponent.SetMove(true);
_movementComponent.SetDirection(GetTargetDirection());
Movement.SetMove(true);
Movement.SetDirection(GetTargetDirection());
}
}
protected override void OnHide(bool isShutdown, object userData)
{
_movementComponent.OnReset();
_healthComponent.OnReset();
CancelAttackCooldown();
base.OnHide(isShutdown, userData);
}
private void TryFireProjectile()
{
if (!EnsureEnemyProjectileGroup()) return;
string groupName = GameEntry.Luban.Global.ProjectileGroupName;
if (!GameEntry.Entity.HasEntityGroup(groupName))
{
Log.Error("Entity group '{0}' does not exist.", groupName);
return;
}
Vector3 spawnPosition = this.CachedTransform.position +
this.CachedTransform.forward * _projectileSpawnForwardOffset +
Vector3.up * _projectileSpawnHeightOffset;
float spawnHeight = GameEntry.Luban.Global.ProjectileHeightOffset / 1000f;
Vector3 spawnPosition = CachedTransform.position + Vector3.up * spawnHeight;
Vector3 direction = _target.position - spawnPosition;
direction.y = 0f;
if (direction.sqrMagnitude <= Mathf.Epsilon)
{
direction = this.CachedTransform.forward;
direction = CachedTransform.forward;
direction.y = 0f;
}
@@ -151,84 +75,7 @@ namespace SepCore.Entity
direction.Normalize();
}
int projectileEntityId = GameEntry.Entity.NextId();
var projectileData = new EnemyProjectileData(projectileEntityId, Id, _remoteEnemyData.Camp,
_attackDamage, _projectileSpeed, _projectileLifeTime, direction)
{
Position = spawnPosition,
Rotation = Quaternion.LookRotation(direction, Vector3.up)
};
GameEntry.Entity.ShowEntityAsync<EnemyProjectile>(
projectileEntityId,
AssetUtility.GetEntityAsset(_projectileAssetName),
EnemyProjectileGroupName,
Constant.AssetPriority.BulletAsset,
projectileData).Forget();
}
private static bool EnsureEnemyProjectileGroup()
{
var entityComponent = GameEntry.Entity;
if (entityComponent == null) return false;
if (entityComponent.HasEntityGroup(EnemyProjectileGroupName))
{
return true;
}
bool addResult = entityComponent.AddEntityGroup(
EnemyProjectileGroupName,
EnemyProjectileGroupAutoReleaseInterval,
EnemyProjectileGroupCapacity,
EnemyProjectileGroupExpireTime,
EnemyProjectileGroupPriority);
if (!addResult)
{
Log.Warning("Can not create entity group '{0}'.", EnemyProjectileGroupName);
return false;
}
return true;
}
private float ReadPositiveParam(string paramName, float defaultValue)
{
if (_remoteEnemyData != null &&
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
float.TryParse(rawValue, out float parsedValue))
{
return Mathf.Max(0.01f, parsedValue);
}
return Mathf.Max(0.01f, defaultValue);
}
private string ReadStringParam(string paramName, string defaultValue)
{
if (_remoteEnemyData != null &&
_remoteEnemyData.TryGetParam(paramName, out string rawValue) &&
!string.IsNullOrWhiteSpace(rawValue))
{
return rawValue;
}
return defaultValue;
}
private Vector3 GetTargetDirection()
{
if (_target == null)
{
return Vector3.zero;
}
return new Vector3(
_target.position.x - this.CachedTransform.position.x,
0f,
_target.position.z - this.CachedTransform.position.z
).normalized;
GameEntry.Entity.ShowProjectile(Id, EnemyData.Camp, AttackDamage, spawnPosition, direction).Forget();
}
}
}