重构清理敌人类

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,3 +1,4 @@
FollowCameraFollowCamera.prefabPlayer␍Player.prefab FollowCameraFollowCamera.prefabPlayer␍Player.prefab
MeleeEnemyMeleeEnemy.prefab RemoteEnemyRemoteEnemy.prefab WeaponKnifeWeaponKnife.prefab␍WeaponHandgunWeaponHandgun.prefab WeaponSlashWeaponSlash.prefabWeaponLightningWeaponLightning.prefab WeaponLanceWeaponLance.prefab MeleeEnemyMeleeEnemy.prefab RemoteEnemyRemoteEnemy.prefab WeaponKnifeWeaponKnife.prefab␍WeaponHandgunWeaponHandgun.prefab WeaponSlashWeaponSlash.prefabWeaponLightningWeaponLightning.prefab WeaponLanceWeaponLance.prefab
CoinEntityCoinEntity.prefab ExpEntityExpEntity.prefab CoinEntityCoinEntity.prefab ExpEntityExpEntity.prefab
ProjectileProjectile.prefab
@@ -42,5 +42,9 @@
{ {
"assetName": "ExpEntity", "assetName": "ExpEntity",
"prefabPath": "ExpEntity.prefab" "prefabPath": "ExpEntity.prefab"
},
{
"assetName": "Projectile",
"prefabPath": "Projectile.prefab"
} }
] ]
Binary file not shown.
+16 -2
View File
@@ -2,14 +2,28 @@
{ {
"weaponCapacity": 6, "weaponCapacity": 6,
"propMaxCount": -1, "propMaxCount": -1,
"playerEntity": "Player",
"weaponRecycleRate": 300, "weaponRecycleRate": 300,
"shopGoodsCount": 4, "shopGoodsCount": 4,
"shopPriceRandomPercent": 50, "shopPriceRandomPercent": 50,
"shopMinPrice": 0, "shopMinPrice": 0,
"levelUpRefreshPriceBase": 2, "levelUpRefreshPriceBase": 2,
"levelUpRewardCount": 4, "levelUpRewardCount": 4,
"enemyGroupName": "Enemy",
"spawnEnemyMaxCount": 5000, "spawnEnemyMaxCount": 5000,
"spawnDistanceFromPlayer": 20000 "enemySpawnDistanceMin": 14000,
"enemySpawnDistanceMax": 18000,
"weaponRotateSpeed": 5000,
"playerGroupName": "Player",
"enemyGroupName": "Enemy",
"projectileGroupName": "Projectile",
"dropGroupName": "Drop",
"effectGroupNmae": "Effect",
"minDamage": 1,
"maxDodgeRate": 900,
"minSpawnRateScale": 100,
"projectileLifeTime": 3000,
"projectileHeightOffset": 600,
"projectileSpeed": 12000,
"projectileEntity": "Projectile"
} }
] ]
@@ -115,7 +115,7 @@ GameObject:
m_Component: m_Component:
- component: {fileID: 8253353784175897923} - component: {fileID: 8253353784175897923}
m_Layer: 0 m_Layer: 0
m_Name: BulletHandgun m_Name: Projectile
m_TagString: Untagged m_TagString: Untagged
m_Icon: {fileID: 0} m_Icon: {fileID: 0}
m_NavMeshLayer: 0 m_NavMeshLayer: 0
@@ -8,55 +8,47 @@ namespace SepCore.Entity
[Serializable] [Serializable]
public class EnemyData : TargetableObjectData public class EnemyData : TargetableObjectData
{ {
private readonly EnemyConfig _enemyConfig; public EnemyConfig Config { get; }
public EnemyData(int entityId, EnemyConfig enemyConfig, int level) public EnemyData(int entityId, EnemyConfig enemyConfig, int level)
: base(enemyConfig.Prefab, entityId, CampType.Enemy) : base(enemyConfig.Prefab, entityId, CampType.Enemy)
{ {
_enemyConfig = enemyConfig; Config = enemyConfig;
int effectiveLevel = Mathf.Max(1, level); int effectiveLevel = Mathf.Max(1, level);
MaxHealthBase = enemyConfig.MaxHealth + enemyConfig.HpAddPerLevel * (effectiveLevel - 1); MaxHealthBase = enemyConfig.MaxHealth + enemyConfig.HpAddPerLevel * (effectiveLevel - 1);
} }
public EnemyType EnemyType => _enemyConfig.EnemyType; public EnemyType EnemyType => Config.EnemyType;
public string LogicType => _enemyConfig.LogicType; public string LogicType => Config.LogicType;
public override int MaxHealthBase { get; } public override int MaxHealthBase { get; }
public int AttackDamage => _enemyConfig.AttackDamage; public int AttackDamage => Config.AttackDamage;
public float AttackCooldown => _enemyConfig.AttackCooldown; public float AttackCooldown => Config.AttackCooldown;
public float AttackRange => _enemyConfig.AttackRange; public float AttackRange => Config.AttackRange;
public float SpeedBase => _enemyConfig.Speed; public float SpeedBase => Config.Speed;
public int DropCoin => _enemyConfig.CoinDrop; public int DropCoin => Config.CoinDrop;
public int DropExp => _enemyConfig.ExpDrop; public int DropExp => Config.ExpDrop;
public float DropPercent => _enemyConfig.DropPercent; public float DropPercent => Config.DropPercent;
public float Scale => _enemyConfig.Scale; public float Scale => Config.Scale;
public Color Color public Color Color
{ {
get get
{ {
// 空/非法颜色值视为无染色(乘以白色)。 // 空/非法颜色值视为无染色(乘以白色)。
return ColorUtility.TryParseHtmlString(_enemyConfig.Color, out Color color) return ColorUtility.TryParseHtmlString(Config.Color, out Color color)
? color ? color
: Color.white; : Color.white;
} }
} }
public IReadOnlyDictionary<string, string> Params => null;
public bool TryGetParam(string key, out string value)
{
value = null;
return false;
}
} }
} }
@@ -8,7 +8,7 @@ namespace SepCore.Entity
public class EnemyProjectileData : EntityDataBase public class EnemyProjectileData : EntityDataBase
{ {
public EnemyProjectileData(int entityId, int ownerEntityId, CampType ownerCamp, int attackDamage, public EnemyProjectileData(int entityId, int ownerEntityId, CampType ownerCamp, int attackDamage,
float speed, float lifeTime, Vector3 direction, string assetName = "BulletHandgun") float speed, float lifeTime, Vector3 direction, string assetName = "Projectile")
: base(assetName, entityId) : base(assetName, entityId)
{ {
OwnerEntityId = ownerEntityId; OwnerEntityId = ownerEntityId;
@@ -0,0 +1,46 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SepCore.Definition
{
/// <summary>
/// 敌人状态
/// </summary>
public enum EnemyStateType : byte
{
/// <summary>
/// 无
/// </summary>
None = 0,
/// <summary>
/// 攻击
/// </summary>
Attack = 1,
/// <summary>
/// 待机
/// </summary>
Idle = 2,
/// <summary>
/// 搜索
/// </summary>
Seek = 3,
/// <summary>
/// 追逐
/// </summary>
Chase = 4,
/// <summary>
/// 死亡
/// </summary>
Dead = 5,
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d55e30fdec251d141bab19b1b87806b3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
+112 -10
View File
@@ -18,15 +18,31 @@ public sealed partial class GlobalConfig : Luban.BeanBase
{ {
WeaponCapacity = _buf.ReadInt(); WeaponCapacity = _buf.ReadInt();
PropMaxCount = _buf.ReadInt(); PropMaxCount = _buf.ReadInt();
PlayerEntity = _buf.ReadString();
PlayerEntity_Ref = null;
WeaponRecycleRate = _buf.ReadInt(); WeaponRecycleRate = _buf.ReadInt();
ShopGoodsCount = _buf.ReadInt(); ShopGoodsCount = _buf.ReadInt();
ShopPriceRandomPercent = _buf.ReadInt(); ShopPriceRandomPercent = _buf.ReadInt();
ShopMinPrice = _buf.ReadInt(); ShopMinPrice = _buf.ReadInt();
LevelUpRefreshPriceBase = _buf.ReadInt(); LevelUpRefreshPriceBase = _buf.ReadInt();
LevelUpRewardCount = _buf.ReadInt(); LevelUpRewardCount = _buf.ReadInt();
EnemyGroupName = _buf.ReadString();
SpawnEnemyMaxCount = _buf.ReadInt(); SpawnEnemyMaxCount = _buf.ReadInt();
SpawnDistanceFromPlayer = _buf.ReadInt(); EnemySpawnDistanceMin = _buf.ReadInt();
EnemySpawnDistanceMax = _buf.ReadInt();
WeaponRotateSpeed = _buf.ReadInt();
PlayerGroupName = _buf.ReadString();
EnemyGroupName = _buf.ReadString();
ProjectileGroupName = _buf.ReadString();
DropGroupName = _buf.ReadString();
EffectGroupNmae = _buf.ReadString();
MinDamage = _buf.ReadInt();
MaxDodgeRate = _buf.ReadInt();
MinSpawnRateScale = _buf.ReadInt();
ProjectileLifeTime = _buf.ReadInt();
ProjectileHeightOffset = _buf.ReadInt();
ProjectileSpeed = _buf.ReadInt();
ProjectileEntity = _buf.ReadString();
ProjectileEntity_Ref = null;
} }
public static GlobalConfig DeserializeGlobalConfig(ByteBuf _buf) public static GlobalConfig DeserializeGlobalConfig(ByteBuf _buf)
@@ -43,6 +59,11 @@ public sealed partial class GlobalConfig : Luban.BeanBase
/// </summary> /// </summary>
public readonly int PropMaxCount; public readonly int PropMaxCount;
/// <summary> /// <summary>
/// 玩家实体
/// </summary>
public readonly string PlayerEntity;
public EntityConfig PlayerEntity_Ref;
/// <summary>
/// 武器回收价比例 /// 武器回收价比例
/// </summary> /// </summary>
public readonly int WeaponRecycleRate; public readonly int WeaponRecycleRate;
@@ -67,17 +88,70 @@ public sealed partial class GlobalConfig : Luban.BeanBase
/// </summary> /// </summary>
public readonly int LevelUpRewardCount; public readonly int LevelUpRewardCount;
/// <summary> /// <summary>
/// 敌人对象池名
/// </summary>
public readonly string EnemyGroupName;
/// <summary>
/// 最大敌人数量 /// 最大敌人数量
/// </summary> /// </summary>
public readonly int SpawnEnemyMaxCount; public readonly int SpawnEnemyMaxCount;
/// <summary> /// <summary>
/// 敌人生成距离 /// 敌人生成最小距离
/// </summary> /// </summary>
public readonly int SpawnDistanceFromPlayer; public readonly int EnemySpawnDistanceMin;
/// <summary>
/// 敌人生成最大距离
/// </summary>
public readonly int EnemySpawnDistanceMax;
/// <summary>
/// 武器旋转速度
/// </summary>
public readonly int WeaponRotateSpeed;
/// <summary>
/// 玩家对象组名
/// </summary>
public readonly string PlayerGroupName;
/// <summary>
/// 敌人对象组名
/// </summary>
public readonly string EnemyGroupName;
/// <summary>
/// 投射物对象组名
/// </summary>
public readonly string ProjectileGroupName;
/// <summary>
/// 掉落物对象组名
/// </summary>
public readonly string DropGroupName;
/// <summary>
/// 特效对象组名
/// </summary>
public readonly string EffectGroupNmae;
/// <summary>
/// 最小伤害
/// </summary>
public readonly int MinDamage;
/// <summary>
/// 闪避率上限
/// </summary>
public readonly int MaxDodgeRate;
/// <summary>
/// 最小刷怪速度倍数
/// </summary>
public readonly int MinSpawnRateScale;
/// <summary>
/// 投射物生命周期
/// </summary>
public readonly int ProjectileLifeTime;
/// <summary>
/// 投射物生成高度
/// </summary>
public readonly int ProjectileHeightOffset;
/// <summary>
/// 投射物移动速度
/// </summary>
public readonly int ProjectileSpeed;
/// <summary>
/// 投射物实体
/// </summary>
public readonly string ProjectileEntity;
public EntityConfig ProjectileEntity_Ref;
public const int __ID__ = -958250779; public const int __ID__ = -958250779;
public override int GetTypeId() => __ID__; public override int GetTypeId() => __ID__;
@@ -86,6 +160,7 @@ public sealed partial class GlobalConfig : Luban.BeanBase
{ {
PlayerEntity_Ref = tables.TbEntityConfig.GetOrDefault(PlayerEntity);
@@ -95,6 +170,19 @@ public sealed partial class GlobalConfig : Luban.BeanBase
ProjectileEntity_Ref = tables.TbEntityConfig.GetOrDefault(ProjectileEntity);
} }
public override string ToString() public override string ToString()
@@ -102,15 +190,29 @@ public sealed partial class GlobalConfig : Luban.BeanBase
return "{ " return "{ "
+ "weaponCapacity:" + WeaponCapacity + "," + "weaponCapacity:" + WeaponCapacity + ","
+ "propMaxCount:" + PropMaxCount + "," + "propMaxCount:" + PropMaxCount + ","
+ "playerEntity:" + PlayerEntity + ","
+ "weaponRecycleRate:" + WeaponRecycleRate + "," + "weaponRecycleRate:" + WeaponRecycleRate + ","
+ "shopGoodsCount:" + ShopGoodsCount + "," + "shopGoodsCount:" + ShopGoodsCount + ","
+ "shopPriceRandomPercent:" + ShopPriceRandomPercent + "," + "shopPriceRandomPercent:" + ShopPriceRandomPercent + ","
+ "shopMinPrice:" + ShopMinPrice + "," + "shopMinPrice:" + ShopMinPrice + ","
+ "levelUpRefreshPriceBase:" + LevelUpRefreshPriceBase + "," + "levelUpRefreshPriceBase:" + LevelUpRefreshPriceBase + ","
+ "levelUpRewardCount:" + LevelUpRewardCount + "," + "levelUpRewardCount:" + LevelUpRewardCount + ","
+ "enemyGroupName:" + EnemyGroupName + ","
+ "spawnEnemyMaxCount:" + SpawnEnemyMaxCount + "," + "spawnEnemyMaxCount:" + SpawnEnemyMaxCount + ","
+ "spawnDistanceFromPlayer:" + SpawnDistanceFromPlayer + "," + "enemySpawnDistanceMin:" + EnemySpawnDistanceMin + ","
+ "enemySpawnDistanceMax:" + EnemySpawnDistanceMax + ","
+ "weaponRotateSpeed:" + WeaponRotateSpeed + ","
+ "playerGroupName:" + PlayerGroupName + ","
+ "enemyGroupName:" + EnemyGroupName + ","
+ "projectileGroupName:" + ProjectileGroupName + ","
+ "dropGroupName:" + DropGroupName + ","
+ "effectGroupNmae:" + EffectGroupNmae + ","
+ "minDamage:" + MinDamage + ","
+ "maxDodgeRate:" + MaxDodgeRate + ","
+ "minSpawnRateScale:" + MinSpawnRateScale + ","
+ "projectileLifeTime:" + ProjectileLifeTime + ","
+ "projectileHeightOffset:" + ProjectileHeightOffset + ","
+ "projectileSpeed:" + ProjectileSpeed + ","
+ "projectileEntity:" + ProjectileEntity + ","
+ "}"; + "}";
} }
} }
@@ -39,6 +39,10 @@ public partial class TbGlobalConfig
/// </summary> /// </summary>
public int PropMaxCount => _data.PropMaxCount; public int PropMaxCount => _data.PropMaxCount;
/// <summary> /// <summary>
/// 玩家实体
/// </summary>
public string PlayerEntity => _data.PlayerEntity;
/// <summary>
/// 武器回收价比例 /// 武器回收价比例
/// </summary> /// </summary>
public int WeaponRecycleRate => _data.WeaponRecycleRate; public int WeaponRecycleRate => _data.WeaponRecycleRate;
@@ -63,17 +67,69 @@ public partial class TbGlobalConfig
/// </summary> /// </summary>
public int LevelUpRewardCount => _data.LevelUpRewardCount; public int LevelUpRewardCount => _data.LevelUpRewardCount;
/// <summary> /// <summary>
/// 敌人对象池名
/// </summary>
public string EnemyGroupName => _data.EnemyGroupName;
/// <summary>
/// 最大敌人数量 /// 最大敌人数量
/// </summary> /// </summary>
public int SpawnEnemyMaxCount => _data.SpawnEnemyMaxCount; public int SpawnEnemyMaxCount => _data.SpawnEnemyMaxCount;
/// <summary> /// <summary>
/// 敌人生成距离 /// 敌人生成最小距离
/// </summary> /// </summary>
public int SpawnDistanceFromPlayer => _data.SpawnDistanceFromPlayer; public int EnemySpawnDistanceMin => _data.EnemySpawnDistanceMin;
/// <summary>
/// 敌人生成最大距离
/// </summary>
public int EnemySpawnDistanceMax => _data.EnemySpawnDistanceMax;
/// <summary>
/// 武器旋转速度
/// </summary>
public int WeaponRotateSpeed => _data.WeaponRotateSpeed;
/// <summary>
/// 玩家对象组名
/// </summary>
public string PlayerGroupName => _data.PlayerGroupName;
/// <summary>
/// 敌人对象组名
/// </summary>
public string EnemyGroupName => _data.EnemyGroupName;
/// <summary>
/// 投射物对象组名
/// </summary>
public string ProjectileGroupName => _data.ProjectileGroupName;
/// <summary>
/// 掉落物对象组名
/// </summary>
public string DropGroupName => _data.DropGroupName;
/// <summary>
/// 特效对象组名
/// </summary>
public string EffectGroupNmae => _data.EffectGroupNmae;
/// <summary>
/// 最小伤害
/// </summary>
public int MinDamage => _data.MinDamage;
/// <summary>
/// 闪避率上限
/// </summary>
public int MaxDodgeRate => _data.MaxDodgeRate;
/// <summary>
/// 最小刷怪速度倍数
/// </summary>
public int MinSpawnRateScale => _data.MinSpawnRateScale;
/// <summary>
/// 投射物生命周期
/// </summary>
public int ProjectileLifeTime => _data.ProjectileLifeTime;
/// <summary>
/// 投射物生成高度
/// </summary>
public int ProjectileHeightOffset => _data.ProjectileHeightOffset;
/// <summary>
/// 投射物移动速度
/// </summary>
public int ProjectileSpeed => _data.ProjectileSpeed;
/// <summary>
/// 投射物实体
/// </summary>
public string ProjectileEntity => _data.ProjectileEntity;
public void ResolveRef(Tables tables) public void ResolveRef(Tables tables)
{ {
@@ -62,7 +62,7 @@ public sealed partial class WeaponConfig : Luban.BeanBase
/// </summary> /// </summary>
public readonly int Cooldown; public readonly int Cooldown;
/// <summary> /// <summary>
/// 范围 /// 攻击范围
/// </summary> /// </summary>
public readonly int AttackRange; public readonly int AttackRange;
/// <summary> /// <summary>
@@ -6,6 +6,7 @@ using SepCore.EnemyManager;
using SepCore.Simulation; using SepCore.Simulation;
using SepCore.Timer; using SepCore.Timer;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Procedure namespace SepCore.Procedure
{ {
@@ -86,16 +87,9 @@ namespace SepCore.Procedure
// 停止玩家逻辑 // 停止玩家逻辑
Player.Enable = false; Player.Enable = false;
// 隐藏所有掉落物实体 // 隐藏所有掉落物与投射物实体
var entities = GameEntry.Entity.GetEntityGroup("Drop").GetAllEntities(); HideEntityGroup(GameEntry.Luban.Global.DropGroupName);
foreach (var entity in entities) HideEntityGroup(GameEntry.Luban.Global.ProjectileGroupName);
{
GameEntry.Entity.HideEntity(entity.Id);
}
HideEntityGroup("Bullet");
HideEntityGroup("Projectile");
HideEntityGroup("EnemyProjectile");
} }
public override void OnDestroy() public override void OnDestroy()
@@ -106,13 +100,14 @@ namespace SepCore.Procedure
private static void HideEntityGroup(string groupName) private static void HideEntityGroup(string groupName)
{ {
var entityGroup = GameEntry.Entity.GetEntityGroup(groupName); if (!GameEntry.Entity.HasEntityGroup(groupName))
var entities = entityGroup?.GetAllEntities();
if (entities == null)
{ {
Log.Error("Entity group '{0}' does not exist.", groupName);
return; return;
} }
var entityGroup = GameEntry.Entity.GetEntityGroup(groupName);
var entities = entityGroup.GetAllEntities();
foreach (var entity in entities) foreach (var entity in entities)
{ {
GameEntry.Entity.HideEntity(entity.Id); GameEntry.Entity.HideEntity(entity.Id);
@@ -5,6 +5,7 @@ using Cysharp.Threading.Tasks;
using SepCore.CustomUtility; using SepCore.CustomUtility;
using SepCore.Definition; using SepCore.Definition;
using SepCore.Entity; using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
using HideEntityCompleteEventArgs = UnityGameFramework.Runtime.HideEntityCompleteEventArgs; using HideEntityCompleteEventArgs = UnityGameFramework.Runtime.HideEntityCompleteEventArgs;
using ShowEntityFailureEventArgs = UnityGameFramework.Runtime.ShowEntityFailureEventArgs; using ShowEntityFailureEventArgs = UnityGameFramework.Runtime.ShowEntityFailureEventArgs;
@@ -149,7 +150,7 @@ namespace SepCore.AsyncTask
float timeout = 30f, float timeout = 30f,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return ShowEntityByDataAsync<Player>(entityComponent, typeof(Player), "Player", return ShowEntityByDataAsync<Player>(entityComponent, typeof(Player), GameEntry.Luban.Global.PlayerGroupName,
Constant.AssetPriority.PlayerAsset, data, timeout, cancellationToken); Constant.AssetPriority.PlayerAsset, data, timeout, cancellationToken);
} }
@@ -165,7 +166,7 @@ namespace SepCore.AsyncTask
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName); EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
return entityComponent.ShowEntityAsync( return entityComponent.ShowEntityAsync(
data.Id, enemyType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Enemy", data.Id, enemyType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), GameEntry.Luban.Global.EnemyGroupName,
Constant.AssetPriority.EnemyAsset, data, timeout, cancellationToken) Constant.AssetPriority.EnemyAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity.Logic as EnemyBase); .ContinueWith(entity => entity.Logic as EnemyBase);
} }
@@ -182,7 +183,7 @@ namespace SepCore.AsyncTask
EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName); EntityConfig entityConfig = GameEntry.Luban.Get<EntityConfig>(data.AssetName);
return entityComponent.ShowEntityAsync( return entityComponent.ShowEntityAsync(
data.Id, weaponType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), "Weapon", data.Id, weaponType, AssetUtility.GetEntityAsset(entityConfig.PrefabPath), GameEntry.Luban.Global.PlayerGroupName,
Constant.AssetPriority.WeaponAsset, data, timeout, cancellationToken) Constant.AssetPriority.WeaponAsset, data, timeout, cancellationToken)
.ContinueWith(entity => entity.Logic as WeaponBase); .ContinueWith(entity => entity.Logic as WeaponBase);
} }
@@ -195,7 +196,7 @@ namespace SepCore.AsyncTask
float timeout = 30f, float timeout = 30f,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return ShowEntityByDataAsync<Effect>(entityComponent, typeof(Effect), "Effect", return ShowEntityByDataAsync<Effect>(entityComponent, typeof(Effect), GameEntry.Luban.Global.EffectGroupNmae,
Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken); Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken);
} }
@@ -207,7 +208,7 @@ namespace SepCore.AsyncTask
float timeout = 30f, float timeout = 30f,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return ShowEntityByDataAsync<CoinEntity>(entityComponent, typeof(CoinEntity), "Drop", return ShowEntityByDataAsync<CoinEntity>(entityComponent, typeof(CoinEntity), GameEntry.Luban.Global.DropGroupName,
Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken); Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken);
} }
@@ -219,10 +220,76 @@ namespace SepCore.AsyncTask
float timeout = 30f, float timeout = 30f,
CancellationToken cancellationToken = default) CancellationToken cancellationToken = default)
{ {
return ShowEntityByDataAsync<ExpEntity>(entityComponent, typeof(ExpEntity), "Drop", return ShowEntityByDataAsync<ExpEntity>(entityComponent, typeof(ExpEntity), GameEntry.Luban.Global.DropGroupName,
Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken); Constant.AssetPriority.EffectAsset, data, timeout, cancellationToken);
} }
/// <summary>
/// 异步显示投射物实体。
/// </summary>
public static UniTask<EnemyProjectile> ShowProjectileAsync(this EntityComponent entityComponent,
EnemyProjectileData data,
float timeout = 30f,
CancellationToken cancellationToken = default)
{
return ShowEntityByDataAsync<EnemyProjectile>(entityComponent, typeof(EnemyProjectile),
GameEntry.Luban.Global.ProjectileGroupName,
Constant.AssetPriority.BulletAsset, data, timeout, cancellationToken);
}
/// <summary>
/// 异步显示投射物实体(直接入参快捷方法)。
/// </summary>
public static UniTask<EnemyProjectile> ShowProjectileAsync(this EntityComponent entityComponent,
int ownerEntityId,
CampType ownerCamp,
int attackDamage,
Vector3 position,
Vector3 direction,
float timeout = 30f,
CancellationToken cancellationToken = default)
{
int projectileEntityId = entityComponent.NextId();
float speed = GameEntry.Luban.Global.ProjectileSpeed / 1000f;
float lifeTime = GameEntry.Luban.Global.ProjectileLifeTime / 1000f;
string assetName = GameEntry.Luban.Global.ProjectileEntity;
var projectileData = new EnemyProjectileData(projectileEntityId, ownerEntityId, ownerCamp,
attackDamage, speed, lifeTime, direction, assetName)
{
Position = position,
Rotation = Quaternion.LookRotation(direction, Vector3.up)
};
return entityComponent.ShowProjectileAsync(projectileData, timeout, cancellationToken);
}
/// <summary>
/// 异步显示投射物实体。
/// </summary>
public static UniTask<EnemyProjectile> ShowProjectile(this EntityComponent entityComponent,
EnemyProjectileData data,
float timeout = 30f,
CancellationToken cancellationToken = default)
{
return entityComponent.ShowProjectileAsync(data, timeout, cancellationToken);
}
/// <summary>
/// 异步显示投射物实体(直接入参快捷方法)。
/// </summary>
public static UniTask<EnemyProjectile> ShowProjectile(this EntityComponent entityComponent,
int ownerEntityId,
CampType ownerCamp,
int attackDamage,
Vector3 position,
Vector3 direction,
float timeout = 30f,
CancellationToken cancellationToken = default)
{
return entityComponent.ShowProjectileAsync(ownerEntityId, ownerCamp, attackDamage, position, direction, timeout, cancellationToken);
}
/// <summary> /// <summary>
/// 异步隐藏实体 /// 异步隐藏实体
/// </summary> /// </summary>
@@ -22,7 +22,6 @@ namespace SepCore.EnemyManager
public IReadOnlyCollection<EntityBase> Enemies => _enemyRegistry.Enemies; public IReadOnlyCollection<EntityBase> Enemies => _enemyRegistry.Enemies;
private int _spawnEnemyMaxCount; private int _spawnEnemyMaxCount;
private int _spawnDistanceFromPlayer;
private int _currentSpawnEnemyId; private int _currentSpawnEnemyId;
private int _currentLevel; private int _currentLevel;
@@ -73,11 +72,18 @@ namespace SepCore.EnemyManager
return; return;
} }
if (!_entity.HasEntityGroup(EnemyGroupName))
{
Log.Error("Entity group '{0}' does not exist.", EnemyGroupName);
return;
}
_isStopped = false; _isStopped = false;
_spawnEnemyMaxCount = GameEntry.Luban.Global.SpawnEnemyMaxCount; _spawnEnemyMaxCount = GameEntry.Luban.Global.SpawnEnemyMaxCount;
_spawnDistanceFromPlayer = GameEntry.Luban.Global.SpawnDistanceFromPlayer / 1000; float minDistance = GameEntry.Luban.Global.EnemySpawnDistanceMin / 1000f;
float maxDistance = GameEntry.Luban.Global.EnemySpawnDistanceMax / 1000f;
_player = player.CachedTransform; _player = player.CachedTransform;
_spawnPositionStrategy = new RandomCircleSpawnStrategy(_spawnDistanceFromPlayer); _spawnPositionStrategy = new RandomCircleSpawnStrategy(minDistance, maxDistance);
_baseDuration = level.Duration; _baseDuration = level.Duration;
_duration = _baseDuration; _duration = _baseDuration;
@@ -8,7 +8,7 @@ namespace SepCore.EnemyManager
{ {
public class EnemySpawnScheduler public class EnemySpawnScheduler
{ {
private const float MinSpawnRateScale = 0.1f; private static float MinSpawnRateScale => GameEntry.Luban.Global.MinSpawnRateScale / 1000f;
private float _elapsedTime; private float _elapsedTime;
private float[] _baseIntervals; private float[] _baseIntervals;
@@ -5,19 +5,22 @@ namespace SepCore.EnemyManager
{ {
public class RandomCircleSpawnStrategy : ISpawnPositionStrategy public class RandomCircleSpawnStrategy : ISpawnPositionStrategy
{ {
private readonly float _spawnDistance; private readonly float _minDistance;
private readonly float _maxDistance;
public RandomCircleSpawnStrategy(float spawnDistance) public RandomCircleSpawnStrategy(float minDistance, float maxDistance)
{ {
_spawnDistance = spawnDistance; _minDistance = minDistance;
_maxDistance = maxDistance;
} }
public Vector3 GetSpawnPosition(Transform player) public Vector3 GetSpawnPosition(Transform player)
{ {
float x = Random.Range(-1f, 1f); float x = Random.Range(-1f, 1f);
float z = Random.Range(-1f, 1f); float z = Random.Range(-1f, 1f);
Vector3 dir = new Vector3(x, 0, z).normalized; Vector3 dir = new Vector3(x, 0f, z).normalized;
return player.position + dir * _spawnDistance; float distance = Random.Range(_minDistance, _maxDistance);
return player.position + dir * distance;
} }
} }
} }
@@ -9,11 +9,9 @@ namespace SepCore.Simulation
// Bridges entity show/hide events into simulation state registration. // Bridges entity show/hide events into simulation state registration.
public sealed class EntitySync public sealed class EntitySync
{ {
private const string EnemyGroupName = "Enemy"; private static string EnemyGroupName => GameEntry.Luban.Global.EnemyGroupName;
private const string DropGroupName = "Drop"; private static string DropGroupName => GameEntry.Luban.Global.DropGroupName;
private const string BulletGroupName = "Bullet"; private static string ProjectileGroupName => GameEntry.Luban.Global.ProjectileGroupName;
private const string ProjectileGroupName = "Projectile";
private const string EnemyProjectileGroupName = "EnemyProjectile";
private readonly SimulationWorld _world; private readonly SimulationWorld _world;
private bool _isEventSubscribed; private bool _isEventSubscribed;
@@ -74,8 +72,7 @@ namespace SepCore.Simulation
return; return;
} }
if ((groupName == BulletGroupName || groupName == ProjectileGroupName || if (groupName == ProjectileGroupName &&
groupName == EnemyProjectileGroupName) &&
args.Entity.Logic is EntityBase projectileEntity) args.Entity.Logic is EntityBase projectileEntity)
{ {
_world.RegisterProjectileLifecycle(projectileEntity, args.UserData); _world.RegisterProjectileLifecycle(projectileEntity, args.UserData);
@@ -101,8 +98,7 @@ namespace SepCore.Simulation
return; return;
} }
if (groupName == BulletGroupName || groupName == ProjectileGroupName || if (groupName == ProjectileGroupName)
groupName == EnemyProjectileGroupName)
{ {
_world.UnregisterProjectileLifecycle(args.EntityId); _world.UnregisterProjectileLifecycle(args.EntityId);
} }
@@ -1,61 +1,124 @@
using Cysharp.Threading.Tasks; using Cysharp.Threading.Tasks;
using SepCore.AsyncTask; using SepCore.AsyncTask;
using SepCore.Components;
using SepCore.Definition; using SepCore.Definition;
using SepCore.Entity;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime;
public abstract class EnemyBase : TargetableObject namespace SepCore.Entity
{ {
protected Transform _target; public abstract class EnemyBase : TargetableObject
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)
{ {
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) public virtual void SetTarget(Transform target) => _target = target;
{
if (Random.value < _enemyData.DropPercent) #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 EnemyData = enemyData;
}; _healthComponent.OnInit(enemyData.MaxHealthBase);
GameEntry.Entity.ShowCoinAsync(data).Forget(); Movement.OnInit(enemyData.SpeedBase, CachedTransform, null, true);
} Movement.SetMove(true);
if (Random.value < _enemyData.DropPercent) StartAttackCooldown(enemyData.AttackCooldown);
{ CachedTransform.position = enemyData.Position;
var data = new ExpData(_enemyData.DropExp, GameEntry.Entity.NextId()) ApplyVisualParams(enemyData);
}
else
{ {
Position = this.CachedTransform.position Log.Error("Invalid enemy data type: {0}", userData?.GetType());
}; }
GameEntry.Entity.ShowExpAsync(data).Forget();
} }
base.OnDead(attacker); protected override void OnHide(bool isShutdown, object userData)
} {
Movement.OnReset();
_healthComponent.OnReset();
CancelAttackCooldown();
_target = null;
private void ApplyVisualParams(EnemyData data) base.OnHide(isShutdown, userData);
{ }
CachedTransform.localScale = Vector3.one * Mathf.Max(0.1f, data.Scale);
var renderer = GetComponentInChildren<Renderer>(); protected override void OnDead(EntityBase attacker)
var block = new MaterialPropertyBlock(); {
block.SetColor("_BaseColor", data.Color); if (Random.value < EnemyData.DropPercent)
renderer.SetPropertyBlock(block); {
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.Definition;
using SepCore.Entity;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@@ -1,83 +1,24 @@
using SepCore.Components;
using SepCore.CustomUtility; using SepCore.CustomUtility;
using SepCore.Definition; using SepCore.Definition;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity namespace SepCore.Entity
{ {
public class MeleeEnemy : EnemyBase 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 float _sqrAttackRange;
private EnemyStateType enemyState = EnemyStateType.Idle;
private AttackStateType _attackState = AttackStateType.Idle;
private EnemyData _meleeEnemyData
{
get => _enemyData;
set => _enemyData = value;
}
private TargetableObject _targetableTarget; private TargetableObject _targetableTarget;
protected override TargetableObjectData _targetableObjectData => _meleeEnemyData; #region Lifecycle
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>();
}
protected override void OnShow(object userData) protected override void OnShow(object userData)
{ {
base.OnShow(userData); base.OnShow(userData);
if (userData is EnemyData enemyData) _sqrAttackRange = AttackRange * AttackRange;
{ enemyState = EnemyStateType.Idle;
_meleeEnemyData = enemyData; _targetableTarget = null;
_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()}");
}
} }
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -89,11 +30,8 @@ namespace SepCore.Entity
protected override void OnHide(bool isShutdown, object userData) protected override void OnHide(bool isShutdown, object userData)
{ {
_movementComponent.OnReset();
_healthComponent.OnReset();
_targetableTarget = null; _targetableTarget = null;
CancelAttackCooldown(); enemyState = EnemyStateType.Idle;
_attackState = AttackStateType.Idle;
base.OnHide(isShutdown, userData); base.OnHide(isShutdown, userData);
} }
@@ -108,74 +46,73 @@ namespace SepCore.Entity
private void UpdateAttackState(float elapseSeconds) private void UpdateAttackState(float elapseSeconds)
{ {
switch (_attackState) switch (enemyState)
{ {
case AttackStateType.Idle: case EnemyStateType.Idle:
SetMove(false); Movement.SetMove(false);
if (HasValidTarget()) if (HasValidTarget())
{ {
TransitionTo(AttackStateType.Check_OutRange); TransitionTo(EnemyStateType.Seek);
} }
break; break;
case AttackStateType.Check_OutRange: case EnemyStateType.Seek:
if (!HasValidTarget()) if (!HasValidTarget())
{ {
TransitionTo(AttackStateType.Idle); TransitionTo(EnemyStateType.Idle);
return; return;
} }
if (IsTargetInRange()) if (IsTargetInRange())
{ {
TransitionTo(AttackStateType.Check_InRange); TransitionTo(EnemyStateType.Chase);
return; return;
} }
SetMove(true); Movement.SetMove(true);
_movementComponent.SetDirection(GetTargetDirection()); Movement.SetDirection(GetTargetDirection());
break; break;
case AttackStateType.Check_InRange: case EnemyStateType.Chase:
if (!HasValidTarget()) if (!HasValidTarget())
{ {
TransitionTo(AttackStateType.Idle); TransitionTo(EnemyStateType.Idle);
return; return;
} }
SetMove(false); Movement.SetMove(false);
if (!IsTargetInRange()) if (!IsTargetInRange())
{ {
TransitionTo(AttackStateType.Check_OutRange); TransitionTo(EnemyStateType.Seek);
return; return;
} }
if (_canAttack) if (_canAttack)
{ {
ResetAttackCooldown(_attackCooldown); ResetAttackCooldown(AttackCooldown);
TransitionTo(AttackStateType.Attack); TransitionTo(EnemyStateType.Attack);
} }
break; 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(); ExecuteAttack();
TransitionTo(AttackStateType.Check_InRange); TransitionTo(EnemyStateType.Chase);
} }
} }
private bool HasValidTarget() private bool HasValidTarget()
{ {
if (_target == null) return false; if (_target == null) return false;
@@ -192,7 +129,7 @@ namespace SepCore.Entity
{ {
if (_target == null) return false; if (_target == null) return false;
Vector3 delta = _target.position - this.CachedTransform.position; Vector3 delta = _target.position - CachedTransform.position;
delta.y = 0f; delta.y = 0f;
return delta.sqrMagnitude <= _sqrAttackRange; return delta.sqrMagnitude <= _sqrAttackRange;
} }
@@ -208,31 +145,9 @@ namespace SepCore.Entity
return; return;
} }
int damage = AIUtility.CalcDamageHP(_attackDamage, null, targetImpactData.DefenseStat, int damage = AIUtility.CalcDamageHP(AttackDamage, null, targetImpactData.DefenseStat,
targetImpactData.DodgeStat); targetImpactData.DodgeStat);
_targetableTarget.ApplyDamage(this, damage); _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.AsyncTask;
using SepCore.CustomUtility; using SepCore.CustomUtility;
using SepCore.Definition; using SepCore.Definition;
using Cysharp.Threading.Tasks;
using UnityEngine; using UnityEngine;
using UnityGameFramework.Runtime; using UnityGameFramework.Runtime;
@@ -10,83 +9,13 @@ namespace SepCore.Entity
{ {
public class RemoteEnemy : EnemyBase 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 _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) protected override void OnShow(object userData)
{ {
base.OnShow(userData); base.OnShow(userData);
if (userData is EnemyData enemyData) _attackRangeSquared = AttackRange * AttackRange;
{
_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()}");
}
} }
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds) protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
@@ -95,50 +24,45 @@ namespace SepCore.Entity
if (_target == null) if (_target == null)
{ {
_movementComponent.SetMove(false); Movement.SetMove(false);
return; return;
} }
Vector3 toTarget = _target.position - this.CachedTransform.position; Vector3 toTarget = _target.position - CachedTransform.position;
toTarget.y = 0f; toTarget.y = 0f;
float distanceSquared = toTarget.sqrMagnitude; float distanceSquared = toTarget.sqrMagnitude;
if (distanceSquared <= _attackRangeSquared) if (distanceSquared <= _attackRangeSquared)
{ {
_movementComponent.SetMove(false); Movement.SetMove(false);
if (_canAttack) if (_canAttack)
{ {
TryFireProjectile(); TryFireProjectile();
ResetAttackCooldown(_attackCooldown); ResetAttackCooldown(AttackCooldown);
} }
} }
else else
{ {
_movementComponent.SetMove(true); Movement.SetMove(true);
_movementComponent.SetDirection(GetTargetDirection()); Movement.SetDirection(GetTargetDirection());
} }
} }
protected override void OnHide(bool isShutdown, object userData)
{
_movementComponent.OnReset();
_healthComponent.OnReset();
CancelAttackCooldown();
base.OnHide(isShutdown, userData);
}
private void TryFireProjectile() 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 + float spawnHeight = GameEntry.Luban.Global.ProjectileHeightOffset / 1000f;
this.CachedTransform.forward * _projectileSpawnForwardOffset + Vector3 spawnPosition = CachedTransform.position + Vector3.up * spawnHeight;
Vector3.up * _projectileSpawnHeightOffset;
Vector3 direction = _target.position - spawnPosition; Vector3 direction = _target.position - spawnPosition;
direction.y = 0f; direction.y = 0f;
if (direction.sqrMagnitude <= Mathf.Epsilon) if (direction.sqrMagnitude <= Mathf.Epsilon)
{ {
direction = this.CachedTransform.forward; direction = CachedTransform.forward;
direction.y = 0f; direction.y = 0f;
} }
@@ -151,84 +75,7 @@ namespace SepCore.Entity
direction.Normalize(); direction.Normalize();
} }
int projectileEntityId = GameEntry.Entity.NextId(); GameEntry.Entity.ShowProjectile(Id, EnemyData.Camp, AttackDamage, spawnPosition, direction).Forget();
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;
} }
} }
} }
@@ -19,8 +19,6 @@ namespace SepCore.Entity
public WeaponData WeaponData { get; protected set; } public WeaponData WeaponData { get; protected set; }
public float AttackCooldown => WeaponData.Cooldown;
public bool IsAttacking => _isAttacking; public bool IsAttacking => _isAttacking;
protected ITargetSelector TargetSelector { get; private set; } protected ITargetSelector TargetSelector { get; private set; }
@@ -29,7 +27,7 @@ namespace SepCore.Entity
protected EntityBase _target; protected EntityBase _target;
protected float _sqrRange; private float _sqrRange;
public EntityBase Target => _target; public EntityBase Target => _target;
@@ -40,8 +38,9 @@ namespace SepCore.Entity
private StatComponent _attackStatComponent; private StatComponent _attackStatComponent;
private System.Action<modifier, bool> _attackStatCallback; private System.Action<modifier, bool> _attackStatCallback;
[SerializeField] protected float _rotateSpeed = 5f; private static float _rotateSpeed => GameEntry.Luban.Global.WeaponRotateSpeed;
[SerializeField] protected LayerMask _hitMask = ~0;
protected LayerMask _hitMask;
protected Quaternion _cachedRotation; protected Quaternion _cachedRotation;
#region Lifecycle #region Lifecycle
@@ -57,8 +56,12 @@ namespace SepCore.Entity
{ {
base.OnShow(userData); base.OnShow(userData);
WeaponData = RequireWeaponData<WeaponData>(userData); WeaponData = (WeaponData)userData;
if (WeaponData == null) return; if (WeaponData == null)
{
Log.Error("WeaponData is Invalid!");
return;
}
StartAttackCooldown(WeaponData.Cooldown); StartAttackCooldown(WeaponData.Cooldown);
_sqrRange = WeaponData.AttackRange * WeaponData.AttackRange; _sqrRange = WeaponData.AttackRange * WeaponData.AttackRange;
@@ -230,17 +233,6 @@ namespace SepCore.Entity
{ {
} }
protected T RequireWeaponData<T>(object userData) where T : WeaponData
{
var data = userData as T;
if (data == null)
{
Log.Error("{0} data is invalid.", typeof(T).Name);
}
return data;
}
protected IEnumerable<EntityBase> GetCandidates() protected IEnumerable<EntityBase> GetCandidates()
{ {
return GameEntry.EnemyManager.Enemies; return GameEntry.EnemyManager.Enemies;
@@ -15,7 +15,7 @@ namespace SepCore.Entity
public override void OnEnter() public override void OnEnter()
{ {
_weapon.ResetAttackCooldown(_weapon.AttackCooldown); _weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
_weapon.Attack(); _weapon.Attack();
} }
@@ -15,7 +15,7 @@ namespace SepCore.Entity
public override void OnEnter() public override void OnEnter()
{ {
_weapon.ResetAttackCooldown(_weapon.AttackCooldown); _weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
_weapon.Attack(); _weapon.Attack();
} }
@@ -15,7 +15,7 @@ namespace SepCore.Entity
public override void OnEnter() public override void OnEnter()
{ {
_weapon.ResetAttackCooldown(_weapon.AttackCooldown); _weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
_weapon.Attack(); _weapon.Attack();
} }
@@ -15,7 +15,7 @@ namespace SepCore.Entity
public override void OnEnter() public override void OnEnter()
{ {
_weapon.ResetAttackCooldown(_weapon.AttackCooldown); _weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
_weapon.Attack(); _weapon.Attack();
} }
@@ -15,7 +15,7 @@ namespace SepCore.Entity
public override void OnEnter() public override void OnEnter()
{ {
_weapon.ResetAttackCooldown(_weapon.AttackCooldown); _weapon.ResetAttackCooldown(_weapon.WeaponData.Cooldown);
_weapon.Attack(); _weapon.Attack();
} }
@@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SepCore.Definition; using SepCore.Definition;
@@ -239,10 +239,11 @@ namespace SepCore.CustomUtility
public static int CalcDamageHP(int attack, StatProperty attackStat, StatProperty defenseStat, public static int CalcDamageHP(int attack, StatProperty attackStat, StatProperty defenseStat,
StatProperty dodgeStat) StatProperty dodgeStat)
{ {
// 1. 处理闪避(闪避率取值 (0, 0.9),不允许拉满闪避) // 1. 处理闪避(从全局表读取闪避率上限)
float maxDodge = GameEntry.Luban.Global.MaxDodgeRate / 1000f;
if (dodgeStat != null) if (dodgeStat != null)
{ {
if (Random.value < Mathf.Clamp(dodgeStat.Value, 0, 0.9f)) return 0; if (Random.value < Mathf.Clamp(dodgeStat.Value, 0f, maxDodge)) return 0;
} }
// 2. 处理攻击加成 最终伤害 = (基础伤害 + 伤害提升固定值) * 伤害提升率 // 2. 处理攻击加成 最终伤害 = (基础伤害 + 伤害提升固定值) * 伤害提升率
@@ -258,9 +259,10 @@ namespace SepCore.CustomUtility
damage = (damage - defenseStat.Value) / defenseStat.Percent; damage = (damage - defenseStat.Value) / defenseStat.Percent;
} }
// 将伤害限制为正整数,避免堆防御导致无法造成伤害 // 4. 将伤害限制为保底伤害,避免堆防御导致无法造成伤害
if (damage < 1) return 1; int minDamage = GameEntry.Luban.Global.MinDamage;
else return Mathf.CeilToInt(damage); if (damage < minDamage) return minDamage;
return Mathf.CeilToInt(damage);
} }
[StructLayout(LayoutKind.Auto)] [StructLayout(LayoutKind.Auto)]
+18 -18
View File
@@ -574,28 +574,28 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.size propertyPath: m_EntityGroups.Array.size
value: 6 value: 5
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[0].m_Name propertyPath: m_EntityGroups.Array.data[0].m_Name
value: Weapon value: Projectile
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[1].m_Name propertyPath: m_EntityGroups.Array.data[1].m_Name
value: Bullet
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[2].m_Name
value: Effect value: Effect
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_Name propertyPath: m_EntityGroups.Array.data[2].m_Name
value: Enemy value: Enemy
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_Name propertyPath: m_EntityGroups.Array.data[3].m_Name
value: Player value: Player
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_Name
value: Drop
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[5].m_Name propertyPath: m_EntityGroups.Array.data[5].m_Name
value: Drop value: Drop
@@ -614,7 +614,7 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[0].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[0].m_InstanceCapacity
value: 10 value: 16
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[1].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[1].m_InstanceCapacity
@@ -622,15 +622,15 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[2].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[2].m_InstanceCapacity
value: 16
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_InstanceCapacity
value: 10000 value: 10000
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_InstanceCapacity
value: 10
objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[4].m_InstanceCapacity
value: 1 value: 100
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[5].m_InstanceCapacity propertyPath: m_EntityGroups.Array.data[5].m_InstanceCapacity
@@ -658,11 +658,11 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[2].m_InstanceExpireTime propertyPath: m_EntityGroups.Array.data[2].m_InstanceExpireTime
value: 60 value: 120
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_InstanceExpireTime propertyPath: m_EntityGroups.Array.data[3].m_InstanceExpireTime
value: 120 value: 60
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_InstanceExpireTime propertyPath: m_EntityGroups.Array.data[4].m_InstanceExpireTime
@@ -694,11 +694,11 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[2].m_InstanceAutoReleaseInterval propertyPath: m_EntityGroups.Array.data[2].m_InstanceAutoReleaseInterval
value: 60 value: 30
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[3].m_InstanceAutoReleaseInterval propertyPath: m_EntityGroups.Array.data[3].m_InstanceAutoReleaseInterval
value: 30 value: 60
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3} - target: {fileID: 11494652, guid: adb3eb1c35fcff14f89fba7b05c9d71c, type: 3}
propertyPath: m_EntityGroups.Array.data[4].m_InstanceAutoReleaseInterval propertyPath: m_EntityGroups.Array.data[4].m_InstanceAutoReleaseInterval
Binary file not shown.
Binary file not shown.
Binary file not shown.