添加 SepCore.Runtime 和 SepCore.Base 程序集

- 暂时将 Entity 全部归进 Runtime 程序集里
- 调整部分 UI 的 UseCase/RawData/Context 之间的关系,让 RawData 更接近业务原始数据,Context 更接近 UI 直接可读的数据
- 调整其他程序集的引用关系
This commit is contained in:
SepComet
2026-06-03 11:15:58 +08:00
parent d5893ac52b
commit 91f70dd783
176 changed files with 245 additions and 231 deletions
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e185f5edf2aad9e47be7d8a4b4d04966
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 58efe0589d7e90f498c8bcbbf94c6be4
folderAsset: yes
timeCreated: 1528026151
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using SepCore.Definition;
using StarForce;
using UnityEngine;
namespace Entity.EntityData
{
[Serializable]
public abstract class AccessoryObjectData : EntityDataBase
{
[SerializeField]
private int m_OwnerId = 0;
[SerializeField]
private CampType m_OwnerCamp = CampType.Unknown;
public AccessoryObjectData(int entityId, int typeId, int ownerId, CampType ownerCamp)
: base(entityId, typeId)
{
m_OwnerId = ownerId;
m_OwnerCamp = ownerCamp;
}
/// <summary>
/// 拥有者编号。
/// </summary>
public int OwnerId
{
get
{
return m_OwnerId;
}
}
/// <summary>
/// 拥有者阵营。
/// </summary>
public CampType OwnerCamp
{
get
{
return m_OwnerCamp;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 83cb841b7411a844581691d5547bf866
timeCreated: 1528026151
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
namespace Entity.EntityData
{
public class CoinData : EntityDataBase
{
private int _value;
public CoinData(int value, int entityId, int typeId) : base(entityId, typeId)
{
_value = value;
}
public int Value
{
get => _value;
set => _value = value;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d9caee22d67a42018113acc77155d6ba
timeCreated: 1770292597
@@ -0,0 +1,33 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using UnityEngine;
namespace Entity.EntityData
{
[Serializable]
public class EffectData : EntityDataBase
{
[SerializeField]
private float m_KeepTime = 0f;
public EffectData(int entityId, int typeId)
: base(entityId, typeId)
{
m_KeepTime = 3f;
}
public float KeepTime
{
get
{
return m_KeepTime;
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e5b93dcbe30421a429c196bda0af31ed
timeCreated: 1528026152
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 611437bd292819b439512f684e13c7e3
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using SepCore.DataTable;
using SepCore.Definition;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity.EntityData
{
[Serializable]
public class EnemyData : TargetableObjectData
{
[SerializeField] private DREnemy _drEnemy;
public EnemyData(int entityId, EnemyType enemyType, int level) : base(
entityId, (int)enemyType, CampType.Enemy)
{
DREnemy enemyRow = GameEntry.DataTable.GetDataTableRow<DREnemy>((int)enemyType);
if (enemyRow == null)
{
throw new Exception($"Enemy data table row is missing, EnemyType='{enemyType}'.");
}
else
{
_drEnemy = enemyRow;
}
int effectiveLevel = Mathf.Max(1, level);
MaxHealthBase = enemyRow.MaxHealth + enemyRow.HpAddPerLevel * (effectiveLevel - 1);
}
public EnemyType EnemyType => (EnemyType)_drEnemy.Id;
public int EntityTypeId => _drEnemy.EntityTypeId;
public override int MaxHealthBase { get; }
public int AttackDamage => _drEnemy.AttackDamage;
public float AttackCooldown => _drEnemy.AttackCooldown;
public float AttackRange => _drEnemy.AttackRange;
public float SpeedBase => _drEnemy.Speed;
public int DropCoin => _drEnemy.DropCoin;
public int DropExp => _drEnemy.DropExp;
public float DropPercent => _drEnemy.DropPercent;
public IReadOnlyDictionary<string, string> Params => _drEnemy.Params;
public bool TryGetParam(string key, out string value)
{
value = null;
if (string.IsNullOrEmpty(key) || _drEnemy?.Params == null)
{
return false;
}
return _drEnemy.Params.TryGetValue(key, out value);
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1b802c30cc274e04929b8659ec631e84
timeCreated: 1768890974
@@ -0,0 +1,29 @@
using System;
using SepCore.Definition;
using UnityEngine;
namespace Entity.EntityData
{
[Serializable]
public class EnemyProjectileData : EntityDataBase
{
public EnemyProjectileData(int entityId, int ownerEntityId, CampType ownerCamp, int attackDamage,
float speed, float lifeTime, Vector3 direction)
: base(entityId, 0)
{
OwnerEntityId = ownerEntityId;
OwnerCamp = ownerCamp;
AttackDamage = attackDamage;
Speed = speed;
LifeTime = lifeTime;
Direction = direction;
}
public int OwnerEntityId { get; }
public CampType OwnerCamp { get; }
public int AttackDamage { get; }
public float Speed { get; }
public float LifeTime { get; }
public Vector3 Direction { get; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52b38f83ab6c4029803d40c189db47c7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,62 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using UnityEngine;
namespace Entity.EntityData
{
[Serializable]
public abstract class EntityDataBase
{
[SerializeField]
private int m_Id = 0;
[SerializeField]
private int m_TypeId = 0;
[SerializeField]
private Vector3 m_Position = Vector3.zero;
[SerializeField]
private Quaternion m_Rotation = Quaternion.identity;
public EntityDataBase(int entityId, int typeId)
{
m_Id = entityId;
m_TypeId = typeId;
}
/// <summary>
/// 实体编号。
/// </summary>
public int Id => m_Id;
/// <summary>
/// 实体类型编号。
/// </summary>
public int TypeId => m_TypeId;
/// <summary>
/// 实体位置。
/// </summary>
public Vector3 Position
{
get => m_Position;
set => m_Position = value;
}
/// <summary>
/// 实体朝向。
/// </summary>
public Quaternion Rotation
{
get => m_Rotation;
set => m_Rotation = value;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: afb91fe3ed8105e4a96b7bd5917ecc2b
timeCreated: 1528026152
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,18 @@
namespace Entity.EntityData
{
public class ExpData : EntityDataBase
{
private int _value;
public ExpData(int value, int entityId, int typeId) : base(entityId, typeId)
{
_value = value;
}
public int Value
{
get => _value;
set => _value = value;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e0f6c8e117da49f1ad04fd8810882043
timeCreated: 1770292932
@@ -0,0 +1,19 @@
using System;
using SepCore.DataTable;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public class PlayerData : TargetableObjectData
{
private int _maxExp;
public PlayerData(int entityId, int typeId) : base(entityId, typeId, CampType.Player)
{
MaxHealthBase = MaxHealthBase;
}
public override int MaxHealthBase { get; }
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b69d3168ac4f442994f8dc4d906daa60
timeCreated: 1768890638
@@ -0,0 +1,40 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using System;
using SepCore.Definition;
using StarForce;
using UnityEngine;
namespace Entity.EntityData
{
[Serializable]
public abstract class TargetableObjectData : EntityDataBase
{
[SerializeField]
private CampType _camp = CampType.Unknown;
public TargetableObjectData(int entityId, int typeId, CampType camp)
: base(entityId, typeId)
{
_camp = camp;
}
/// <summary>
/// 角色阵营。
/// </summary>
public CampType Camp => _camp;
/// <summary>
/// 最大生命。
/// </summary>
public abstract int MaxHealthBase
{
get;
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: f866c15cf3f2a45429251d4a66f70c77
timeCreated: 1528026152
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d8b2591453b7f0841940c8e45528f465
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using SepCore.DataTable;
using SepCore.Definition;
using GameFramework;
namespace Entity.EntityData
{
[Serializable]
public class WeaponData : AccessoryObjectData
{
private DRWeapon _drWeapon;
private int _entityTypeId = 0;
public WeaponData(int entityId, WeaponType weaponType, int ownerId, CampType ownerCamp)
: base(entityId, (int)weaponType, ownerId, ownerCamp)
{
_drWeapon = GameEntry.DataTable.GetDataTableRow<DRWeapon>((int)weaponType);
if (_drWeapon == null)
{
throw new Exception($"Weapon data table row is missing, WeaponType='{weaponType}'.");
}
_entityTypeId = _drWeapon.EntityTypeId;
}
public WeaponType WeaponType => (WeaponType)_drWeapon.Id;
public bool TryGetParam(string key, out string value)
{
value = null;
if (string.IsNullOrEmpty(key) || Params == null)
{
return false;
}
return Params.TryGetValue(key, out value);
}
protected TParams ParseParams<TParams>() where TParams : new()
{
if (string.IsNullOrWhiteSpace(_drWeapon.ParamsJson))
{
return new TParams();
}
try
{
TParams parsed = Utility.Json.ToObject<TParams>(_drWeapon.ParamsJson);
return parsed ?? new TParams();
}
catch (Exception exception)
{
throw new Exception(
$"Failed to parse weapon params, WeaponType='{WeaponType}', Json='{_drWeapon.ParamsJson}'.",
exception);
}
}
/// <summary>
/// 攻击力。
/// </summary>
public int Attack => _drWeapon.Attack;
public int EntityTypeId => _entityTypeId;
/// <summary>
/// 武器名称。
/// </summary>
public string Title => _drWeapon.Title;
/// <summary>
/// 图标资源名称。
/// </summary>
public string IconAssetName => _drWeapon.IconAssetName;
public ItemRarity Rarity => _drWeapon.Rarity;
public int Price => _drWeapon.Price;
/// <summary>
/// 攻击间隔。
/// </summary>
public float Cooldown => _drWeapon.Cooldown;
/// <summary>
/// 攻击范围。
/// </summary>
public float AttackRange => _drWeapon.AttackRange;
/// <summary>
/// 攻击音效。
/// </summary>
public int AttackSoundId => _drWeapon.AttackSoundId;
/// <summary>
/// 额外参数。
/// </summary>
public Dictionary<string, string> Params => _drWeapon.Pramas;
public string ParamsJson => _drWeapon.ParamsJson;
/// <summary>
/// 额外属性。
/// </summary>
public StatModifier[] Modifiers => _drWeapon.Modifiers;
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: e813e3219579f834d8e619c17a8083d4
timeCreated: 1528026153
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,21 @@
using System;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public sealed class WeaponHandgunParamsData
{
}
public class WeaponHandgunData : WeaponData
{
public WeaponHandgunParamsData ParamsData { get; }
public WeaponHandgunData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponHandgun, ownerId, ownerCamp)
{
ParamsData = ParseParams<WeaponHandgunParamsData>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 574d105bf89bf7546a682ed407708034
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public sealed class WeaponKnifeParamsData
{
public float HitRadius { get; set; }
}
public class WeaponKnifeData : WeaponData
{
public WeaponKnifeParamsData ParamsData { get; }
public WeaponKnifeData(int entityId, int ownerId, CampType ownerCamp) : base(entityId, WeaponType.WeaponKnife,
ownerId, ownerCamp)
{
ParamsData = ParseParams<WeaponKnifeParamsData>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: da25d8e0669f0194d8f56f4283c67b38
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,71 @@
using System;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public sealed class WeaponLanceParamsData
{
/// <summary>
/// 横向半宽,表示前戳矩形判定的一半宽度。
/// </summary>
public float HitHalfWidth { get; set; }
/// <summary>
/// 旧字段兼容,未配置 HitHalfWidth 时回退使用。
/// </summary>
public float HitRadius { get; set; }
/// <summary>
/// 前戳判定盒体的总高度。
/// </summary>
public float HitHeight { get; set; }
/// <summary>
/// 判定盒体中心相对战斗平面的高度偏移。
/// </summary>
public float HitCenterYOffset { get; set; }
/// <summary>
/// 前刺距离,同时驱动武器位移和命中长度。
/// </summary>
public float PierceLength { get; set; }
/// <summary>
/// 旧字段兼容,未配置 PierceLength 时回退使用。
/// </summary>
public float ThrustDistance { get; set; }
/// <summary>
/// 判定起点相对武器当前位置的前置偏移。
/// </summary>
public float ForwardOffset { get; set; }
/// <summary>
/// 追踪目标时的转向速度。
/// </summary>
public float RotateSpeed { get; set; }
/// <summary>
/// 向前突刺阶段耗时。
/// </summary>
public float AttackDuration { get; set; }
/// <summary>
/// 收枪返回阶段耗时。
/// </summary>
public float ReturnDuration { get; set; }
}
[Serializable]
public class WeaponLanceData : WeaponData
{
public WeaponLanceParamsData ParamsData { get; }
public WeaponLanceData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponLance, ownerId, ownerCamp)
{
ParamsData = ParseParams<WeaponLanceParamsData>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4d1a821e8ee1a9a4b912b70b0a1616eb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,23 @@
using System;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public sealed class WeaponLightningParamsData
{
public float HitRadius { get; set; }
public float HoverHeight { get; set; }
}
public class WeaponLightningData : WeaponData
{
public WeaponLightningParamsData ParamsData { get; }
public WeaponLightningData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponLightning, ownerId, ownerCamp)
{
ParamsData = ParseParams<WeaponLightningParamsData>();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25b0006918fd46959c7f6b8ec1bbc8ab
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,22 @@
using System;
using SepCore.Definition;
namespace Entity.EntityData
{
[Serializable]
public sealed class WeaponSlashParamsData
{
public float SectorAngle { get; set; }
}
public class WeaponSlashData : WeaponData
{
public WeaponSlashParamsData ParamsData { get; }
public WeaponSlashData(int entityId, int ownerId, CampType ownerCamp)
: base(entityId, WeaponType.WeaponSlash, ownerId, ownerCamp)
{
ParamsData = ParseParams<WeaponSlashParamsData>();
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4aa6473525dc4c62aeee02ff562ec453
timeCreated: 1771484661
@@ -0,0 +1,9 @@
fileFormatVersion: 2
guid: 74fbdf043ed3bc141a0a0b09756bc7d2
folderAsset: yes
timeCreated: 1528026153
licenseType: Pro
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,50 @@
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity
{
public class CoinEntity : EntityBase
{
private CoinData _coinData;
private bool _isCollected;
protected override void OnShow(object userData)
{
base.OnShow(userData);
_isCollected = false;
if (userData is CoinData coinData)
{
_coinData = coinData;
}
else
{
Log.Error("CoinEntity.OnShow() called with wrong type.");
}
this.CachedTransform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
}
public bool TryCollect(Player player, float collectDistance = 0f)
{
if (_isCollected || player == null || !player.Available || _coinData == null) return false;
if (collectDistance > 0f)
{
float distance = Vector3.Distance(player.CachedTransform.position, CachedTransform.position);
if (distance > collectDistance) return false;
}
_isCollected = true;
player.Coin += _coinData.Value;
GameEntry.Entity.HideEntity(this);
return true;
}
private void OnTriggerEnter(Collider other)
{
TryCollect(other.GetComponent<Player>());
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0878ab0829c34f44a83d5e5a1eb586ea
timeCreated: 1770292173
@@ -0,0 +1,58 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using Entity;
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity
{
/// <summary>
/// 特效类。
/// </summary>
public class Effect : EntityBase
{
[SerializeField]
private EffectData m_EffectData = null;
private float m_ElapseSeconds = 0f;
#if UNITY_2017_3_OR_NEWER
protected override void OnShow(object userData)
#else
protected internal override void OnShow(object userData)
#endif
{
base.OnShow(userData);
m_EffectData = userData as EffectData;
if (m_EffectData == null)
{
Log.Error("Effect data is invalid.");
return;
}
m_ElapseSeconds = 0f;
}
#if UNITY_2017_3_OR_NEWER
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
#else
protected internal override void OnUpdate(float elapseSeconds, float realElapseSeconds)
#endif
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
m_ElapseSeconds += elapseSeconds;
if (m_ElapseSeconds >= m_EffectData.KeepTime)
{
GameEntry.Entity.HideEntity(this);
}
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: c3e408851e9ea064bb8bc08fbb10b33f
timeCreated: 1528026154
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 81c9b48d9ff1bce45b91ffa040b1db2a
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,14 @@
using SepCore.Definition;
using Entity;
using UnityEngine;
public abstract class EnemyBase : TargetableObject
{
protected Transform _target;
public abstract override ImpactData GetImpactData();
public virtual float AttackRange => 1f;
public virtual void SetTarget(Transform target) => _target = target;
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d33d6ac2d1874d4418780e3354e6b4e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
using SepCore.Definition;
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity
{
public class EnemyProjectile : EntityBase
{
private EnemyProjectileData _projectileData;
private Vector3 _direction = Vector3.forward;
private bool _isActive;
private ImpactData _impactData;
private Collider[] _cachedColliders;
public bool IsActive => _isActive;
public ImpactData GetImpactData() => _impactData;
protected override void OnShow(object userData)
{
base.OnShow(userData);
_projectileData = userData as EnemyProjectileData;
if (_projectileData == null)
{
Log.Error("Enemy projectile data is invalid.");
_isActive = false;
GameEntry.Entity.HideEntity(this);
return;
}
_isActive = true;
_impactData = new ImpactData(_projectileData.OwnerCamp, _projectileData.AttackDamage);
_direction = _projectileData.Direction;
_direction.y = 0f;
if (_direction.sqrMagnitude <= Mathf.Epsilon)
{
_direction = CachedTransform.forward;
_direction.y = 0f;
}
if (_direction.sqrMagnitude <= Mathf.Epsilon)
{
_direction = Vector3.forward;
}
else
{
_direction.Normalize();
}
CachedTransform.rotation = Quaternion.LookRotation(_direction, Vector3.up);
if (_projectileData.OwnerCamp == CampType.Player)
{
gameObject.layer = LayerMask.NameToLayer("PlayerWeapon");
}
else if (_projectileData.OwnerCamp == CampType.Enemy)
{
gameObject.layer = LayerMask.NameToLayer("EnemyWeapon");
}
SetColliderEnabled(false);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
if (!_isActive || _projectileData == null) return;
}
protected override void OnHide(bool isShutdown, object userData)
{
_isActive = false;
_projectileData = null;
_impactData = default;
_direction = Vector3.forward;
base.OnHide(isShutdown, userData);
}
public void Expire()
{
if (!_isActive) return;
_isActive = false;
GameEntry.Entity.HideEntity(this);
}
private void SetColliderEnabled(bool enabled)
{
_cachedColliders ??= GetComponentsInChildren<Collider>(true);
if (_cachedColliders == null) return;
for (int i = 0; i < _cachedColliders.Length; i++)
{
Collider collider = _cachedColliders[i];
if (collider == null)
{
continue;
}
collider.enabled = enabled;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 22624f81b9364c8681b32d993f5e618f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,264 @@
using Components;
using CustomUtility;
using SepCore.Definition;
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace 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 float _currAttackTimer;
private AttackStateType _attackState = AttackStateType.Idle;
private EnemyData _meleeEnemyData;
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>();
}
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;
_currAttackTimer = 0f;
_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)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
UpdateAttackState(elapseSeconds);
}
protected override void OnDead(EntityBase attacker)
{
if (Random.value < _meleeEnemyData.DropPercent)
{
var data = new CoinData(_meleeEnemyData.DropCoin, GameEntry.Entity.GenerateSerialId(), 10001)
{
Position = this.CachedTransform.position
};
GameEntry.Entity.ShowCoin(data);
}
if (Random.value < _meleeEnemyData.DropPercent)
{
var data = new ExpData(_meleeEnemyData.DropExp, GameEntry.Entity.GenerateSerialId(), 10002)
{
Position = this.CachedTransform.position
};
GameEntry.Entity.ShowExp(data);
}
base.OnDead(attacker);
}
protected override void OnHide(bool isShutdown, object userData)
{
_movementComponent.OnReset();
_healthComponent.OnReset();
_targetableTarget = null;
_currAttackTimer = 0f;
_attackState = AttackStateType.Idle;
base.OnHide(isShutdown, userData);
}
#endregion
public override void SetTarget(Transform target)
{
base.SetTarget(target);
_targetableTarget = target != null ? target.GetComponent<TargetableObject>() : null;
}
private void UpdateAttackState(float elapseSeconds)
{
_currAttackTimer += elapseSeconds;
switch (_attackState)
{
case AttackStateType.Idle:
SetMove(false);
if (HasValidTarget())
{
TransitionTo(AttackStateType.Check_OutRange);
}
break;
case AttackStateType.Check_OutRange:
if (!HasValidTarget())
{
TransitionTo(AttackStateType.Idle);
return;
}
if (IsTargetInRange())
{
TransitionTo(AttackStateType.Check_InRange);
return;
}
SetMove(true);
_movementComponent.SetDirection(GetTargetDirection());
break;
case AttackStateType.Check_InRange:
if (!HasValidTarget())
{
TransitionTo(AttackStateType.Idle);
return;
}
SetMove(false);
if (!IsTargetInRange())
{
TransitionTo(AttackStateType.Check_OutRange);
return;
}
if (_currAttackTimer >= _attackCooldown)
{
TransitionTo(AttackStateType.Attack);
}
break;
}
}
private void TransitionTo(AttackStateType newState)
{
_attackState = newState;
if (_attackState == AttackStateType.Check_InRange)
{
SetMove(false);
}
if (_attackState == AttackStateType.Check_InRange && _currAttackTimer >= _attackCooldown)
{
TransitionTo(AttackStateType.Attack);
return;
}
if (_attackState == AttackStateType.Attack)
{
SetMove(false);
ExecuteAttack();
_currAttackTimer = 0f;
TransitionTo(AttackStateType.Check_InRange);
}
}
private bool HasValidTarget()
{
if (_target == null) return false;
if (_targetableTarget == null || _targetableTarget.CachedTransform != _target)
{
_targetableTarget = _target.GetComponent<TargetableObject>();
}
return _targetableTarget != null && _targetableTarget.Available && !_targetableTarget.IsDead;
}
private bool IsTargetInRange()
{
if (_target == null) return false;
Vector3 delta = _target.position - this.CachedTransform.position;
delta.y = 0f;
return delta.sqrMagnitude <= _sqrAttackRange;
}
private void ExecuteAttack()
{
if (!HasValidTarget() || !IsTargetInRange()) return;
ImpactData targetImpactData = _targetableTarget.GetImpactData();
ImpactData selfImpactData = GetImpactData();
if (AIUtility.GetRelation(selfImpactData.Camp, targetImpactData.Camp) == RelationType.Friendly)
{
return;
}
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;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 112f2a17ab82438484782c9a763fdee1
timeCreated: 1768892870
@@ -0,0 +1,232 @@
using Components;
using CustomUtility;
using SepCore.Definition;
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace 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;
private float _currAttackTimer;
[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;
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);
_currAttackTimer = 0f;
this.CachedTransform.position = enemyData.Position;
}
else
{
Log.Error($"Invalid data type. Data type: {userData?.GetType()}");
}
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
_currAttackTimer += elapseSeconds;
if (_target == null)
{
_movementComponent.SetMove(false);
return;
}
Vector3 toTarget = _target.position - this.CachedTransform.position;
toTarget.y = 0f;
float distanceSquared = toTarget.sqrMagnitude;
if (distanceSquared <= _attackRangeSquared)
{
_movementComponent.SetMove(false);
TryFireProjectile();
}
else
{
_movementComponent.SetMove(true);
_movementComponent.SetDirection(GetTargetDirection());
}
}
protected override void OnHide(bool isShutdown, object userData)
{
_movementComponent.OnReset();
_healthComponent.OnReset();
_currAttackTimer = 0f;
base.OnHide(isShutdown, userData);
}
private void TryFireProjectile()
{
if (_currAttackTimer < _attackCooldown || _target == null) return;
if (!EnsureEnemyProjectileGroup()) return;
Vector3 spawnPosition = this.CachedTransform.position +
this.CachedTransform.forward * _projectileSpawnForwardOffset +
Vector3.up * _projectileSpawnHeightOffset;
Vector3 direction = _target.position - spawnPosition;
direction.y = 0f;
if (direction.sqrMagnitude <= Mathf.Epsilon)
{
direction = this.CachedTransform.forward;
direction.y = 0f;
}
if (direction.sqrMagnitude <= Mathf.Epsilon)
{
direction = Vector3.forward;
}
else
{
direction.Normalize();
}
int projectileEntityId = GameEntry.Entity.GenerateSerialId();
var projectileData = new EnemyProjectileData(projectileEntityId, Id, _remoteEnemyData.Camp,
_attackDamage, _projectileSpeed, _projectileLifeTime, direction)
{
Position = spawnPosition,
Rotation = Quaternion.LookRotation(direction, Vector3.up)
};
GameEntry.Entity.ShowEntity(
entityId: projectileEntityId,
entityLogicType: typeof(EnemyProjectile),
entityAssetName: AssetUtility.GetEntityAsset(_projectileAssetName),
entityGroupName: EnemyProjectileGroupName,
priority: Constant.AssetPriority.BulletAsset,
userData: projectileData);
_currAttackTimer = 0f;
}
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;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b90e05d1e7a5475bb928b2445e113767
timeCreated: 1770209676
@@ -0,0 +1,75 @@
using Entity.EntityData;
using GameFramework;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity
{
public abstract class EntityBase : EntityLogic
{
[SerializeField] private EntityDataBase _entityData = null;
public int Id => Entity.Id;
public Animation CachedAnimation { get; private set; }
protected override void OnInit(object userData)
{
base.OnInit(userData);
CachedAnimation = GetComponent<Animation>();
}
protected override void OnRecycle()
{
base.OnRecycle();
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
_entityData = userData as EntityDataBase;
if (_entityData == null)
{
Log.Error("Entity data is invalid.");
return;
}
Name = Utility.Text.Format("[Entity {0}]", Id);
CachedTransform.localPosition = _entityData.Position;
CachedTransform.localRotation = _entityData.Rotation;
CachedTransform.localScale = Vector3.one;
}
protected override void OnHide(bool isShutdown, object userData)
{
base.OnHide(isShutdown, userData);
}
protected override void OnAttached(EntityLogic childEntity, Transform parentTransform, object userData)
{
base.OnAttached(childEntity, parentTransform, userData);
}
protected override void OnDetached(EntityLogic childEntity, object userData)
{
base.OnDetached(childEntity, userData);
}
protected override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
{
base.OnAttachTo(parentEntity, parentTransform, userData);
}
protected override void OnDetachFrom(EntityLogic parentEntity, object userData)
{
base.OnDetachFrom(parentEntity, userData);
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 878fbbe82b4bde944812bcb7c964cff8
timeCreated: 1528026154
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,49 @@
using Entity.EntityData;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity
{
public class ExpEntity:EntityBase
{
private ExpData _expData;
private bool _isCollected;
protected override void OnShow(object userData)
{
base.OnShow(userData);
_isCollected = false;
if (userData is ExpData expData)
{
_expData = expData;
}
else
{
Log.Error("expEntity.OnShow() called with wrong type.");
}
this.CachedTransform.localScale = new Vector3(0.3f, 0.3f, 0.3f);
}
public bool TryCollect(Player player, float collectDistance = 0f)
{
if (_isCollected || player == null || !player.Available || _expData == null) return false;
if (collectDistance > 0f)
{
float distance = Vector3.Distance(player.CachedTransform.position, CachedTransform.position);
if (distance > collectDistance) return false;
}
_isCollected = true;
player.Exp += _expData.Value;
GameEntry.Entity.HideEntity(this);
return true;
}
private void OnTriggerEnter(Collider other)
{
TryCollect(other.GetComponent<Player>());
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ec4ae4b0a327442a88463eafd59c5b6e
timeCreated: 1770293001
@@ -0,0 +1,245 @@
using System.Collections.Generic;
using Components;
using SepCore.Event;
using SepCore.DataTable;
using SepCore.Definition;
using Entity.EntityData;
using GameFramework.Event;
using UnityGameFramework.Runtime;
using Entity.Weapon;
namespace Entity
{
public class Player : TargetableObject
{
protected override TargetableObjectData _targetableObjectData => _playerData;
private InputComponent _inputComponent;
private MovementComponent _movementComponent;
private AbsorbComponent _absorbComponent;
private StatComponent _statComponent;
private BackpackComponent _backpackComponent;
private PlayerData _playerData;
/// <summary>
/// 当前金币。
/// </summary>
private int _coin;
/// <summary>
/// 当前经验。
/// </summary>
private int _exp;
/// <summary>
/// 每级所需的经验。
/// tip: _expRequires[1] 表示 Lv 0 -> Lv 1 需要的经验
/// </summary>
private int[] _expRequires;
/// <summary>
/// 当前的角色等级。
/// </summary>
private int _currentLevel;
/// <summary>
/// 玩家是否启用。
/// </summary>
private bool _enable;
private bool _weaponEnabled = true;
public int Coin
{
get => _coin;
set
{
GameEntry.Event.Fire(this, PlayerCoinChangeEventArgs.Create(value - _coin, value));
_coin = value;
}
}
public int Exp
{
get => _exp;
set
{
if (value >= _expRequires[_currentLevel + 1])
{
value -= _expRequires[_currentLevel + 1];
_currentLevel++;
PendingLevelPoints++;
GameEntry.Event.Fire(this, PlayerLevelUpEventArgs.Create());
}
GameEntry.Event.Fire(this, PlayerExpChangeEventArgs.Create(value, _expRequires[_currentLevel + 1]));
_exp = value;
}
}
public int CurrentLevel => _currentLevel;
public int PendingLevelPoints { get; set; }
public IReadOnlyList<WeaponBase> Weapons => _backpackComponent != null ? _backpackComponent.Weapons : null;
public IReadOnlyList<PropItem> Props => _backpackComponent != null ? _backpackComponent.Props : null;
public int WeaponCapacity => _backpackComponent != null ? _backpackComponent.WeaponCapacity : 0;
public bool AddProp(PropItem prop)
{
if (prop == null || _backpackComponent == null) return false;
return _backpackComponent.AttachProp(prop);
}
public bool RemoveWeapon(WeaponBase weapon)
{
if (weapon == null || _backpackComponent == null)
{
return false;
}
if (!_backpackComponent.DetachWeapon(weapon))
{
return false;
}
GameEntry.Entity.HideEntity(weapon);
return true;
}
public bool Enable
{
get => _enable;
set
{
if (value == _enable) return;
_enable = value;
_movementComponent.SetMove(value);
_backpackComponent.SetWeaponState(value && _weaponEnabled);
_inputComponent.SetListening(value);
}
}
public bool WeaponEnabled => _weaponEnabled;
public void SetWeaponEnabled(bool enabled)
{
_weaponEnabled = enabled;
_backpackComponent?.SetWeaponState(_enable && _weaponEnabled);
}
public void InitRole(DRRole role)
{
// MovementComponent
_movementComponent.OnInit(role.Speed, this.CachedTransform, _statComponent);
_movementComponent.SetMove(true);
// HealthComponent
_healthComponent.OnInit(role.MaxHealth, _statComponent);
// BackpackComponent
Coin = role.Coin;
_backpackComponent.OnInit(this, role.WeaponCapacity);
GameEntry.Entity.ShowWeapon(new WeaponKnifeData(GameEntry.Entity.GenerateSerialId(), Id, _playerData.Camp));
// StatComponent
foreach (var modifier in role.InitialProperties)
{
_statComponent.AddModifier(modifier);
}
_expRequires = role.ExpRequires;
// Fire Events
GameEntry.Event.Fire(this,
PlayerHealthChangeEventArgs.Create(0, _healthComponent.CurrentHealth, _healthComponent.MaxHealth));
GameEntry.Event.Fire(this, PlayerExpChangeEventArgs.Create(0, 100));
GameEntry.Event.Fire(this,
PlayerCoinChangeEventArgs.Create(Coin, Coin));
}
#region FSM
public override ImpactData GetImpactData()
{
return new ImpactData(_playerData.Camp, 0, null, _healthComponent.DefenseStat, _healthComponent.DodgeStat);
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
_inputComponent = GetComponent<InputComponent>();
_movementComponent = GetComponent<MovementComponent>();
_absorbComponent = GetComponent<AbsorbComponent>();
_statComponent = GetComponent<StatComponent>();
_healthComponent = GetComponent<HealthComponent>();
_backpackComponent = GetComponent<BackpackComponent>();
if (_absorbComponent == null)
{
_absorbComponent = gameObject.AddComponent<AbsorbComponent>();
}
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
GameEntry.Event.Subscribe(ShowEntitySuccessEventArgs.EventId, ShowEntitySuccess);
_statComponent.OnInit();
if (userData is PlayerData playerData)
{
_playerData = playerData;
}
_inputComponent.OnInit();
_inputComponent.SetListening(true);
_absorbComponent.OnInit(this, _statComponent);
_weaponEnabled = true;
_currentLevel = 0;
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
_inputComponent.OnUpdate(elapseSeconds, realElapseSeconds);
_movementComponent.SetDirection(_inputComponent.Direction);
_absorbComponent.OnUpdate(elapseSeconds, realElapseSeconds);
}
protected override void OnHide(bool isShutdown, object userData)
{
_inputComponent.OnReset();
_movementComponent.OnReset();
_healthComponent.OnReset();
_statComponent.OnReset();
_backpackComponent.OnReset();
_absorbComponent.OnReset();
base.OnHide(isShutdown, userData);
}
#endregion
#region Event Handlers
private void ShowEntitySuccess(object sender, GameEventArgs e)
{
if (!(e is ShowEntitySuccessEventArgs args)) return;
if (args.Entity.Logic is WeaponBase weapon)
{
_backpackComponent.AttachWeapon(weapon);
}
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3979cb10abff0944aad09de04ec7813a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,81 @@
//------------------------------------------------------------
// Game Framework
// Copyright © 2013-2021 Jiang Yin. All rights reserved.
// Homepage: https://gameframework.cn/
// Feedback: mailto:ellan@gameframework.cn
//------------------------------------------------------------
using Components;
using SepCore.Definition;
using Entity.EntityData;
using CustomUtility;
using UnityEngine;
namespace Entity
{
/// <summary>
/// 可作为目标的实体类。
/// </summary>
public abstract class TargetableObject : EntityBase
{
protected abstract TargetableObjectData _targetableObjectData { get; }
public bool IsDead => _healthComponent.CurrentHealth <= 0;
protected HealthComponent _healthComponent;
public abstract ImpactData GetImpactData();
public void ApplyDamage(EntityBase attacker, int damageHP)
{
float fromHPRatio = _healthComponent.HealthRatio;
_healthComponent.TakeDamage(damageHP);
float toHPRatio = _healthComponent.HealthRatio;
if (fromHPRatio > toHPRatio)
{
GameEntry.HPBar.ShowHPBar(this, fromHPRatio, toHPRatio);
}
if (_healthComponent.CurrentHealth <= 0)
{
OnDead(attacker);
}
}
protected override void OnInit(object userData)
{
base.OnInit(userData);
_healthComponent = GetComponent<HealthComponent>();
}
protected override void OnShow(object userData)
{
base.OnShow(userData);
}
protected virtual void OnDead(EntityBase attacker)
{
GameEntry.Entity.HideEntity(this);
}
private void OnTriggerEnter(Collider other)
{
EntityBase entity = other.GetComponentInParent<EntityBase>();
if (entity == null || entity == this)
{
return;
}
if (entity is TargetableObject && entity.Id < Id)
{
// 碰撞事件由 Id 大的一方处理
// 在这里约定 Enemy 的 Id 为非负数(通常从 0 开始)
// 而其他的 Entity (Player, Weapon, Bullet) 的 Id 均小于 0
return;
}
AIUtility.PerformCollision(this, entity);
}
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 4a1fd351b05d994459122be1a39507b1
timeCreated: 1528026154
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b5f40df44fe005498fc03555871b8ad
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: abdf25a9782ebf844a65b2348f8f566d
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,124 @@
using GameFramework.ObjectPool;
using UnityEngine;
namespace Entity.Weapon
{
public sealed class HandgunHitMarkerAttackEffect : IWeaponAttackEffect
{
private const string PoolName = "Weapon.HandgunHitMarker";
private const float PoolAutoReleaseInterval = 60f;
private const int PoolCapacity = 128;
private const float PoolExpireTime = 120f;
private const int PoolPriority = 0;
private static IObjectPool<HandgunHitMarkerPoolObject> s_Pool;
private readonly float _size;
private readonly float _yOffset;
private readonly float _duration;
private readonly Color _color;
private Material _sharedMaterial;
public HandgunHitMarkerAttackEffect(float size, float yOffset, float duration, Color color)
{
_size = size;
_yOffset = yOffset;
_duration = duration;
_color = color;
}
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{
if (target == null) return;
if (!TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance))
{
return;
}
Transform targetTransform = target.CachedTransform;
Vector3 worldPosition = targetTransform != null ? targetTransform.position : position;
markerInstance.transform.SetParent(null, false);
markerInstance.transform.position = worldPosition + Vector3.up * _yOffset;
markerInstance.transform.localScale = Vector3.one * Mathf.Max(0.01f, _size);
markerInstance.ApplyMaterial(GetSharedMaterial());
markerInstance.Activate(Mathf.Max(0.01f, _duration), s_Pool);
}
private bool TrySpawnMarker(out HandgunHitMarkerPooledInstance markerInstance)
{
markerInstance = null;
IObjectPool<HandgunHitMarkerPoolObject> pool = EnsurePool();
if (pool == null)
{
return false;
}
HandgunHitMarkerPoolObject pooledObject = pool.Spawn();
if (pooledObject != null)
{
markerInstance = pooledObject.Target as HandgunHitMarkerPooledInstance;
if (markerInstance != null)
{
return true;
}
}
GameObject markerGameObject = GameObject.CreatePrimitive(PrimitiveType.Sphere);
markerGameObject.name = "HandgunHitMarker";
Collider collider = markerGameObject.GetComponent<Collider>();
if (collider != null)
{
Object.Destroy(collider);
}
markerInstance = markerGameObject.AddComponent<HandgunHitMarkerPooledInstance>();
markerGameObject.SetActive(false);
pool.Register(HandgunHitMarkerPoolObject.Create(markerInstance), true);
return true;
}
private static IObjectPool<HandgunHitMarkerPoolObject> EnsurePool()
{
var poolComponent = GameEntry.ObjectPool;
if (poolComponent == null)
{
return null;
}
if (s_Pool != null && poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName))
{
return s_Pool;
}
s_Pool = poolComponent.HasObjectPool<HandgunHitMarkerPoolObject>(PoolName)
? poolComponent.GetObjectPool<HandgunHitMarkerPoolObject>(PoolName)
: poolComponent.CreateSingleSpawnObjectPool<HandgunHitMarkerPoolObject>(
PoolName,
PoolAutoReleaseInterval,
PoolCapacity,
PoolExpireTime,
PoolPriority);
return s_Pool;
}
private Material GetSharedMaterial()
{
if (_sharedMaterial != null)
{
return _sharedMaterial;
}
Shader shader = Shader.Find("Sprites/Default");
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
_sharedMaterial = new Material(shader)
{
color = _color
};
return _sharedMaterial;
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d944f33383dc4c1ebe850a12d4211afa
timeCreated: 1771481130
@@ -0,0 +1,27 @@
using GameFramework;
using GameFramework.ObjectPool;
using UnityEngine;
namespace Entity.Weapon
{
public sealed class HandgunHitMarkerPoolObject : ObjectBase
{
public static HandgunHitMarkerPoolObject Create(object target)
{
HandgunHitMarkerPoolObject pooledObject = ReferencePool.Acquire<HandgunHitMarkerPoolObject>();
pooledObject.Initialize(target);
return pooledObject;
}
protected override void Release(bool isShutdown)
{
HandgunHitMarkerPooledInstance markerInstance = Target as HandgunHitMarkerPooledInstance;
if (markerInstance == null)
{
return;
}
Object.Destroy(markerInstance.gameObject);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 966d34a18f5d00c49bddea3c7c5ec13d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,78 @@
using GameFramework.ObjectPool;
using UnityEngine;
namespace Entity.Weapon
{
public sealed class HandgunHitMarkerPooledInstance : MonoBehaviour
{
private Renderer _renderer;
private IObjectPool<HandgunHitMarkerPoolObject> _ownerPool;
private float _expireTime;
private bool _isActive;
private void Awake()
{
_renderer = GetComponent<Renderer>();
}
private void Update()
{
if (!_isActive)
{
return;
}
if (Time.time < _expireTime)
{
return;
}
ReturnToPool();
}
public void ApplyMaterial(Material material)
{
if (_renderer == null)
{
_renderer = GetComponent<Renderer>();
}
if (_renderer == null)
{
return;
}
_renderer.sharedMaterial = material;
}
public void Activate(float duration, IObjectPool<HandgunHitMarkerPoolObject> pool)
{
_ownerPool = pool;
_expireTime = Time.time + Mathf.Max(0.01f, duration);
_isActive = true;
gameObject.SetActive(true);
}
private void OnDisable()
{
_isActive = false;
_ownerPool = null;
}
private void ReturnToPool()
{
_isActive = false;
IObjectPool<HandgunHitMarkerPoolObject> pool = _ownerPool;
_ownerPool = null;
transform.SetParent(null, false);
if (pool == null)
{
gameObject.SetActive(false);
return;
}
pool.Unspawn(this);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7ff8a67466d8f0643845c41fd5952f50
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
using UnityEngine;
namespace Entity.Weapon
{
public interface IWeaponAttackEffect
{
void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius);
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 231a935a484c4351b707b3e359074a47
timeCreated: 1771481068
@@ -0,0 +1,63 @@
using UnityEngine;
namespace Entity.Weapon
{
public sealed class KnifeRangeAttackEffect : IWeaponAttackEffect
{
private readonly float _duration = 0.2f;
private readonly float _yOffset = 0.05f;
private readonly float _lineWidth = 0.05f;
private readonly int _segments = 48;
private readonly Color _color = new(0.1f, 1f, 0.1f, 0.9f);
public KnifeRangeAttackEffect()
{
}
public KnifeRangeAttackEffect(float duration, float yOffset, float lineWidth, int segments, Color color)
{
_duration = duration;
_yOffset = yOffset;
_lineWidth = lineWidth;
_segments = Mathf.Max(8, segments);
_color = color;
}
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{
float safeRadius = Mathf.Max(0.1f, radius);
GameObject indicator = new GameObject("KnifeRangeIndicator");
indicator.transform.position = new Vector3(position.x, position.y + _yOffset, position.z);
LineRenderer line = indicator.AddComponent<LineRenderer>();
line.loop = true;
line.useWorldSpace = true;
line.positionCount = _segments;
line.startWidth = _lineWidth;
line.endWidth = _lineWidth;
line.startColor = _color;
line.endColor = _color;
Shader shader = Shader.Find("Sprites/Default");
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
Material material = new Material(shader);
material.color = _color;
line.material = material;
float step = Mathf.PI * 2f / _segments;
for (int i = 0; i < _segments; i++)
{
float angle = i * step;
Vector3 offset = new Vector3(Mathf.Cos(angle) * safeRadius, 0f, Mathf.Sin(angle) * safeRadius);
line.SetPosition(i, indicator.transform.position + offset);
}
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8f48328040644546bf0a866cc337d3b2
timeCreated: 1771481100
@@ -0,0 +1,82 @@
using UnityEngine;
namespace Entity.Weapon
{
public sealed class LanceThrustAttackEffect : IWeaponAttackEffect
{
private readonly float _duration = 0.18f;
private readonly float _yOffset = 0.06f;
private readonly float _lineWidth = 0.05f;
private readonly Color _color = new(0.2f, 0.95f, 0.7f, 0.92f);
public LanceThrustAttackEffect()
{
}
public LanceThrustAttackEffect(float duration, float yOffset, float lineWidth, Color color)
{
_duration = duration;
_yOffset = yOffset;
_lineWidth = lineWidth;
_color = color;
}
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{
if (weapon is not WeaponLance lance) return;
Vector3 forward = lance.StrikeDirection;
forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon)
{
forward = Vector3.forward;
}
forward.Normalize();
Vector3 right = Vector3.Cross(Vector3.up, forward);
float halfWidth = Mathf.Max(0.1f, lance.HitHalfWidth);
float halfLength = Mathf.Max(0.1f, lance.PierceLength * 0.5f);
Vector3 center = position + Vector3.up * _yOffset;
Vector3 frontCenter = center + forward * halfLength;
Vector3 backCenter = center - forward * halfLength;
Vector3[] corners =
{
frontCenter - right * halfWidth,
frontCenter + right * halfWidth,
backCenter + right * halfWidth,
backCenter - right * halfWidth,
};
GameObject indicator = new GameObject("LanceThrustIndicator");
indicator.transform.position = center;
LineRenderer line = indicator.AddComponent<LineRenderer>();
line.loop = true;
line.useWorldSpace = true;
line.positionCount = corners.Length;
line.startWidth = _lineWidth;
line.endWidth = _lineWidth;
line.startColor = _color;
line.endColor = _color;
Shader shader = Shader.Find("Sprites/Default");
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
Material material = new Material(shader);
material.color = _color;
line.material = material;
for (int i = 0; i < corners.Length; i++)
{
line.SetPosition(i, corners[i]);
}
Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8c77c590c7233e544a9295ed41ff8827
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,81 @@
using UnityEngine;
namespace Entity.Weapon
{
public sealed class LightningStrikeAttackEffect : IWeaponAttackEffect
{
private readonly float _duration = 0.22f;
private readonly float _yOffset = 0.2f;
private readonly float _ringLineWidth = 0.045f;
private readonly float _boltLineWidth = 0.08f;
private readonly int _ringSegments = 32;
private readonly float _boltHeight = 3f;
private readonly Color _ringColor = new(0.35f, 0.82f, 1f, 0.92f);
private readonly Color _boltColor = new(0.85f, 0.96f, 1f, 0.97f);
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{
float safeRadius = Mathf.Max(0.1f, radius);
Vector3 center = new Vector3(position.x, position.y + _yOffset, position.z);
GameObject root = new GameObject("LightningStrikeEffect");
root.transform.position = center;
Shader shader = Shader.Find("Sprites/Default");
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
Material ringMaterial = new Material(shader);
ringMaterial.color = _ringColor;
LineRenderer ring = root.AddComponent<LineRenderer>();
ring.loop = true;
ring.useWorldSpace = true;
ring.positionCount = _ringSegments;
ring.startWidth = _ringLineWidth;
ring.endWidth = _ringLineWidth;
ring.startColor = _ringColor;
ring.endColor = _ringColor;
ring.material = ringMaterial;
float step = Mathf.PI * 2f / _ringSegments;
for (int i = 0; i < _ringSegments; i++)
{
float angle = i * step;
Vector3 offset = new Vector3(Mathf.Cos(angle) * safeRadius, 0f, Mathf.Sin(angle) * safeRadius);
ring.SetPosition(i, center + offset);
}
GameObject bolt = new GameObject("Bolt");
bolt.transform.SetParent(root.transform, false);
Material boltMaterial = new Material(shader);
boltMaterial.color = _boltColor;
LineRenderer boltLine = bolt.AddComponent<LineRenderer>();
boltLine.loop = false;
boltLine.useWorldSpace = true;
boltLine.positionCount = 4;
boltLine.startWidth = _boltLineWidth;
boltLine.endWidth = _boltLineWidth * 0.55f;
boltLine.startColor = _boltColor;
boltLine.endColor = _boltColor;
boltLine.material = boltMaterial;
Vector3 top = center + Vector3.up * _boltHeight;
Vector3 middleA = center + Vector3.up * (_boltHeight * 0.66f) +
new Vector3(Random.Range(-0.18f, 0.18f), 0f, Random.Range(-0.18f, 0.18f));
Vector3 middleB = center + Vector3.up * (_boltHeight * 0.3f) +
new Vector3(Random.Range(-0.12f, 0.12f), 0f, Random.Range(-0.12f, 0.12f));
boltLine.SetPosition(0, top);
boltLine.SetPosition(1, middleA);
boltLine.SetPosition(2, middleB);
boltLine.SetPosition(3, center);
Object.Destroy(root, Mathf.Max(0.01f, _duration));
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 81cbdf8961ad419c91b989d56ca782d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,86 @@
using UnityEngine;
namespace Entity.Weapon
{
public sealed class SlashSectorAttackEffect : IWeaponAttackEffect
{
private readonly float _duration = 0.2f;
private readonly float _yOffset = 0.06f;
private readonly float _lineWidth = 0.05f;
private readonly int _segments = 24;
private readonly Color _color = new(1f, 0.45f, 0.2f, 0.9f);
public SlashSectorAttackEffect()
{
}
public SlashSectorAttackEffect(float duration, float yOffset, float lineWidth, int segments, Color color)
{
_duration = duration;
_yOffset = yOffset;
_lineWidth = lineWidth;
_segments = Mathf.Max(6, segments);
_color = color;
}
public void Play(WeaponBase weapon, Vector3 position, EntityBase target, float radius)
{
if (weapon is not WeaponSlash slash) return;
float safeRadius = Mathf.Max(0.1f, radius);
float sectorAngle = Mathf.Clamp(slash.SectorAngle, 1f, 360f);
GameObject indicator = new GameObject("SlashSectorIndicator");
Vector3 center = new Vector3(position.x, position.y + _yOffset, position.z);
indicator.transform.position = center;
LineRenderer line = indicator.AddComponent<LineRenderer>();
line.loop = false;
line.useWorldSpace = true;
line.startWidth = _lineWidth;
line.endWidth = _lineWidth;
line.startColor = _color;
line.endColor = _color;
Shader shader = Shader.Find("Sprites/Default");
if (shader == null)
{
shader = Shader.Find("Unlit/Color");
}
Material material = new Material(shader);
material.color = _color;
line.material = material;
float half = sectorAngle * 0.5f;
int arcPoints = _segments + 1;
int positionCount = arcPoints + 2;
line.positionCount = positionCount;
Vector3 forward = weapon.CachedTransform.forward;
forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon)
{
forward = Vector3.forward;
}
forward.Normalize();
Quaternion baseRotation = Quaternion.LookRotation(forward, Vector3.up);
line.SetPosition(0, center);
for (int i = 0; i < arcPoints; i++)
{
float t = arcPoints <= 1 ? 0f : i / (float)(arcPoints - 1);
float angle = Mathf.Lerp(-half, half, t);
Vector3 dir = Quaternion.Euler(0f, angle, 0f) * Vector3.forward;
Vector3 worldDir = baseRotation * dir;
line.SetPosition(i + 1, center + worldDir * safeRadius);
}
line.SetPosition(positionCount - 1, center);
UnityEngine.Object.Destroy(indicator, Mathf.Max(0.01f, _duration));
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 329af2c156b345f28a7465ef29c943da
timeCreated: 1771485834
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 371bc24a18a3447686043843fdd6fbe5
timeCreated: 1771427642
@@ -0,0 +1,49 @@
using System.Collections.Generic;
using Components;
using CustomUtility;
namespace Entity.Weapon
{
public sealed class HighestHealthTargetSelector : ITargetSelector
{
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
{
if (weapon == null || candidates == null)
{
return null;
}
EntityBase target = null;
float maxHp = float.MinValue;
float maxSqr = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
foreach (var candidate in candidates)
{
if (candidate == null || !candidate.Available) continue;
if (maxSqrRange > 0f && AIUtility.GetSqrMagnitudeXZ(weapon, candidate) >= maxSqr) continue;
if (!TryGetCurrentHealth(candidate, out var hp)) continue;
if (hp <= maxHp) continue;
maxHp = hp;
target = candidate;
}
return target;
}
private static bool TryGetCurrentHealth(EntityBase target, out float health)
{
health = 0f;
if (target == null) return false;
var healthComponent = target.GetComponent<HealthComponent>();
if (healthComponent == null) return false;
health = healthComponent.CurrentHealth;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54932952c9fa3fd4dbe6d21f2a484b4d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Entity.Weapon
{
public interface ITargetSelector
{
EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ed801e4816c03b4597756dc36d6aba8
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,53 @@
using System.Collections.Generic;
using Components;
using SepCore.Definition;
using CustomUtility;
namespace Entity.Weapon
{
public sealed class LowestHealthTargetSelector : ITargetSelector
{
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
{
if (weapon == null || candidates == null)
{
return null;
}
EntityBase target = null;
float minHp = float.MaxValue;
float maxSqr = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
foreach (var candidate in candidates)
{
if (candidate == null || !candidate.Available) continue;
if (maxSqrRange > 0f && AIUtility.GetSqrMagnitudeXZ(weapon, candidate) >= maxSqr) continue;
if (!TryGetHealth(candidate, out var hp)) continue;
if (hp >= minHp) continue;
minHp = hp;
target = candidate;
}
return target;
}
private static bool TryGetHealth(EntityBase target, out float health)
{
health = 0f;
if (target == null) return false;
var statComponent = target.GetComponent<StatComponent>();
if (statComponent == null) return false;
var hpStat = statComponent.GetStat(StatType.MaxHealth);
if (hpStat == null) return false;
health = hpStat.Value;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 081d67a491f5792479306d88091168e3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,68 @@
using System.Collections.Generic;
using CustomUtility;
using CustomComponent;
namespace Entity.Weapon
{
public sealed class NearestTargetSelector : ITargetSelector
{
public EntityBase SelectTarget(WeaponBase weapon, IEnumerable<EntityBase> candidates, float maxSqrRange)
{
if (weapon == null || candidates == null)
{
return null;
}
if (TrySelectFromSpatialIndex(weapon, maxSqrRange, out EntityBase indexedTarget))
{
return indexedTarget;
}
EntityBase target = null;
float minSqrMagnitude = maxSqrRange > 0f ? maxSqrRange : float.MaxValue;
foreach (var candidate in candidates)
{
if (candidate == null || !candidate.Available) continue;
float sqrMagnitude = AIUtility.GetSqrMagnitudeXZ(weapon, candidate);
if (sqrMagnitude >= minSqrMagnitude) continue;
minSqrMagnitude = sqrMagnitude;
target = candidate;
}
return target;
}
private static bool TrySelectFromSpatialIndex(WeaponBase weapon, float maxSqrRange, out EntityBase target)
{
target = null;
if (weapon == null || maxSqrRange <= 0f || weapon.CachedTransform == null)
{
return false;
}
var simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld == null)
{
return false;
}
if (!simulationWorld.TryGetNearestEnemyEntityId(weapon.CachedTransform.position, maxSqrRange,
out int entityId))
{
return false;
}
EnemyManagerComponent enemyManager = GameEntry.EnemyManager;
if (enemyManager == null || !enemyManager.TryGetEnemy(entityId, out EntityBase enemy))
{
return false;
}
target = enemy;
return true;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a70602535b318e74396a6c2ea282834d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
namespace Entity.Weapon
{
public enum TargetSelectorType
{
Nearest = 0,
HighestHealth = 1,
LowestHealth = 2
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4eb1e0b99139fdd4280c8616804a8f77
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,320 @@
using System.Collections.Generic;
using Components;
using SepCore.Definition;
using Entity.EntityData;
using GameFramework;
using CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity.Weapon
{
public enum WeaponStateType
{
Disabled,
Attack,
Idle,
Check_OutRange,
Check_InRange,
}
/// <summary>
/// 武器类。
/// </summary>
public abstract class WeaponBase : EntityBase
{
protected const string AttachPoint = "Weapon Point";
protected bool _isAttacking = false;
[SerializeField] protected bool _isEnabled = false;
public WeaponData WeaponData { get; protected set; }
public bool IsAttacking => _isAttacking;
protected ITargetSelector TargetSelector { get; set; }
protected Dictionary<WeaponStateType, WeaponStateBase> _states;
protected WeaponStateBase _currentState;
protected EntityBase _target;
protected float _currAttackTimer;
protected float _sqrRange;
protected StatProperty AttackStat { get; private set; } = new();
private StatComponent _attackStatComponent;
private System.Action<StatModifier, bool> _attackStatCallback;
private static readonly List<EntityBase> s_EmptyCandidates = new();
#region Lifecycle
protected override void OnInit(object userData)
{
base.OnInit(userData);
if (TargetSelector == null)
{
TargetSelector = CreateSelector(DefaultTargetSelectorType);
}
}
protected sealed override void OnShow(object userData)
{
base.OnShow(userData);
if (!OnWeaponShow(userData)) return;
EnsureStatesBuilt();
TransitionTo(InitialState);
}
protected sealed override void OnHide(bool isShutdown, object userData)
{
ReleaseAttackStatSubscription();
OnWeaponHide(userData);
base.OnHide(isShutdown, userData);
}
protected sealed override void OnAttachTo(EntityLogic parentEntity, Transform parentTransform, object userData)
{
base.OnAttachTo(parentEntity, parentTransform, userData);
Name = Utility.Text.Format("Weapon of {0}", parentEntity.Name);
CachedTransform.localPosition = Vector3.zero;
OnWeaponAttach(parentEntity, parentTransform, userData);
}
protected sealed override void OnDetachFrom(EntityLogic parentEntity, object userData)
{
OnWeaponDetach(parentEntity, userData);
base.OnDetachFrom(parentEntity, userData);
}
protected virtual bool OnWeaponShow(object userData) => true;
protected virtual void OnWeaponHide(object userData)
{
}
protected virtual void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
}
protected virtual void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
}
protected override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
base.OnUpdate(elapseSeconds, realElapseSeconds);
if (!_isEnabled) return;
_currentState?.OnUpdate(elapseSeconds, realElapseSeconds);
}
#endregion
public abstract ImpactData GetImpactData();
protected abstract void Attack();
protected abstract void Check();
protected abstract void BuildStates();
protected virtual WeaponStateType InitialState => WeaponStateType.Idle;
protected virtual TargetSelectorType DefaultTargetSelectorType => TargetSelectorType.Nearest;
protected void RegisterState(WeaponStateBase state)
{
if (state == null)
{
Log.Error("Weapon state is invalid.");
return;
}
state.OnInit(this);
_states[state.State] = state;
}
protected void EnsureStatesBuilt()
{
if (_states != null) return;
_states = new Dictionary<WeaponStateType, WeaponStateBase>();
BuildStates();
}
protected virtual void TransitionTo(WeaponStateType newState)
{
if (_states == null || !_states.TryGetValue(newState, out var nextState))
{
Log.Error("Weapon state not found: {0}", newState);
return;
}
_currentState?.OnLeave();
_currentState = nextState;
_currentState.OnEnter();
}
public void SetEnabled(bool value)
{
_isEnabled = value;
OnEnabledChanged(value);
if (_states == null || _states.Count == 0) return;
if (value)
{
TransitionTo(InitialState);
return;
}
if (_states.ContainsKey(WeaponStateType.Disabled))
{
TransitionTo(WeaponStateType.Disabled);
}
}
protected virtual void OnEnabledChanged(bool enabled)
{
}
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 virtual IEnumerable<EntityBase> GetCandidates()
{
var enemyManager = GameEntry.EnemyManager;
return enemyManager != null ? enemyManager.Enemies : s_EmptyCandidates;
}
protected EntityBase SelectTarget(float maxSqrRange)
{
return TargetSelector != null ? TargetSelector.SelectTarget(this, GetCandidates(), maxSqrRange) : null;
}
protected float GetSqrDistance(EntityBase target)
{
return target != null ? AIUtility.GetSqrMagnitudeXZ(this, target) : float.MaxValue;
}
protected bool IsInRange(EntityBase target, float sqrRange)
{
if (target == null) return false;
return AIUtility.GetSqrMagnitudeXZ(this, target) < sqrRange;
}
protected bool TryQueueAreaCollisionQuery(in Vector3 center, float radius, int maxTargets = 16)
{
var simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld == null)
{
return false;
}
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
return simulationWorld.TryRequestAreaCollision(Id, ownerEntityId, in center, radius, maxTargets);
}
protected bool TryQueueSectorCollisionQuery(in Vector3 center, float radius, in Vector3 direction,
float halfAngleDeg, int maxTargets = 16)
{
var simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld == null)
{
return false;
}
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
return simulationWorld.TryRequestSectorCollision(Id, ownerEntityId, in center, radius, in direction,
halfAngleDeg, maxTargets);
}
protected bool TryQueueRectangleCollisionQuery(in Vector3 center, float halfWidth, float halfLength,
in Vector3 direction, int maxTargets = 16)
{
var simulationWorld = GameEntry.SimulationWorld;
if (simulationWorld == null)
{
return false;
}
int ownerEntityId = WeaponData != null ? WeaponData.OwnerId : Id;
return simulationWorld.TryRequestRectangleCollision(Id, ownerEntityId, in center, halfWidth, halfLength,
in direction, maxTargets);
}
protected void SetTargetSelector(TargetSelectorType selectorType)
{
TargetSelector = CreateSelector(selectorType);
}
protected static ITargetSelector CreateSelector(TargetSelectorType selectorType)
{
switch (selectorType)
{
case TargetSelectorType.HighestHealth:
return new HighestHealthTargetSelector();
case TargetSelectorType.LowestHealth:
return new LowestHealthTargetSelector();
case TargetSelectorType.Nearest:
default:
return new NearestTargetSelector();
}
}
protected void BindAttackStatFromOwner(EntityLogic parentEntity)
{
ReleaseAttackStatSubscription();
AttackStat = new StatProperty();
if (parentEntity is not Player player) return;
var statComponent = player.GetComponent<StatComponent>();
if (statComponent == null) return;
AttackStat = statComponent.GetStat(StatType.Attack);
_attackStatComponent = statComponent;
_attackStatCallback = (modifier, isApply) =>
statComponent.UpdateStat(AttackStat, modifier, isApply);
statComponent.Subscribe(StatType.Attack, _attackStatCallback);
}
protected void ReleaseAttackStatSubscription()
{
if (_attackStatComponent != null && _attackStatCallback != null)
{
_attackStatComponent.Unsubscribe(StatType.Attack, _attackStatCallback);
}
_attackStatComponent = null;
_attackStatCallback = null;
AttackStat = new StatProperty();
}
}
public abstract class WeaponStateBase
{
public abstract WeaponStateType State { get; }
public abstract void OnInit(WeaponBase weapon);
public abstract void OnEnter();
public abstract void OnUpdate(float elapseSeconds, float realElapseSeconds);
public abstract void OnLeave();
public override string ToString() => State.ToString();
}
}
@@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fc18f1a1232bb9644be1191168741922
timeCreated: 1528026154
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b289e88dc5243aeb564df941485c5cf
timeCreated: 1771579168
@@ -0,0 +1,25 @@
namespace Entity.Weapon
{
public partial class WeaponHandgun
{
private class AttackState : WeaponStateBase
{
private WeaponHandgun _weapon;
public override WeaponStateType State => WeaponStateType.Attack;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
public override void OnEnter()
{
_weapon._currAttackTimer = 0f;
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
public override void OnLeave() { }
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4e72df067466464fa3be92bfd049dd0d
timeCreated: 1771579220
@@ -0,0 +1,46 @@
namespace Entity.Weapon
{
public partial class WeaponHandgun
{
private class CheckInRangeState : WeaponStateBase
{
private WeaponHandgun _weapon;
public override WeaponStateType State => WeaponStateType.Check_InRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
public override void OnEnter()
{
if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(elapseSeconds);
_weapon._currAttackTimer += elapseSeconds;
if (_weapon._target == null || !_weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Idle);
return;
}
if (!_weapon.IsInRange(_weapon._target, _weapon._sqrRange))
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
return;
}
if (_weapon._currAttackTimer >= _weapon._weaponData.Cooldown)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave() { }
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfd76c0fb7284ceaa28f78c702f595da
timeCreated: 1771579231
@@ -0,0 +1,33 @@
namespace Entity.Weapon
{
public partial class WeaponHandgun
{
private class CheckOutRangeState : WeaponStateBase
{
private WeaponHandgun _weapon;
public override WeaponStateType State => WeaponStateType.Check_OutRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
public override void OnEnter() { }
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(elapseSeconds);
_weapon._currAttackTimer += elapseSeconds;
if (_weapon._target == null || !_weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Idle);
return;
}
if (_weapon.IsInRange(_weapon._target, _weapon._sqrRange))
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave() { }
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 3a9f936d3c8643f7bf632734548f800c
timeCreated: 1771579241
@@ -0,0 +1,27 @@
namespace Entity.Weapon
{
public partial class WeaponHandgun
{
private class IdleState : WeaponStateBase
{
private WeaponHandgun _weapon;
public override WeaponStateType State => WeaponStateType.Idle;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponHandgun;
public override void OnEnter() { }
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToOrigin(elapseSeconds);
_weapon._currAttackTimer += elapseSeconds;
if (_weapon._target != null && _weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
}
}
public override void OnLeave() { }
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9c5341944d1a467b8553d31bc3d81d5b
timeCreated: 1771579252
@@ -0,0 +1,169 @@
using SepCore.Definition;
using Entity.EntityData;
using CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace Entity.Weapon
{
public partial class WeaponHandgun : WeaponBase
{
#region Property
private WeaponHandgunData _weaponData;
private Quaternion _cachedRotation;
[SerializeField] private float _rotateSpeed = 6f;
[SerializeField] private LayerMask _hitMask = ~0;
[SerializeField] private Vector3 _fireOriginOffset = Vector3.zero;
[SerializeField] private float _hitMarkerSize = 0.2f;
[SerializeField] private float _hitMarkerYOffset = 1.2f;
[SerializeField] private float _hitMarkerDuration = 0.15f;
[SerializeField] private Color _hitMarkerColor = new(1f, 0f, 0f, 0.95f);
private IWeaponAttackEffect _attackEffect;
#endregion
public override ImpactData GetImpactData()
{
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
}
protected override void BuildStates()
{
RegisterState(new IdleState());
RegisterState(new CheckOutRangeState());
RegisterState(new CheckInRangeState());
RegisterState(new AttackState());
}
protected override void Attack()
{
FaceTargetImmediately();
if (!TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition))
{
return;
}
_attackEffect?.Play(this, hitPosition, targetable, 0f);
_isAttacking = true;
AIUtility.PerformCollision(targetable, this);
_isAttacking = false;
}
protected override void Check()
{
_target = SelectTarget(_sqrRange);
}
private void RotateToTarget(float elapseSeconds)
{
if (_target == null || !_target.Available) return;
Vector3 directionToTarget = (_target.CachedTransform.position - CachedTransform.position).normalized;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget, Vector3.up);
CachedTransform.rotation = Quaternion.Slerp(CachedTransform.rotation, targetRotation,
_rotateSpeed * elapseSeconds);
}
private void RotateToOrigin(float elapseSeconds)
{
CachedTransform.rotation = Quaternion.Slerp(CachedTransform.rotation, _cachedRotation,
_rotateSpeed * elapseSeconds);
}
private void FaceTargetImmediately()
{
if (_target == null || !_target.Available) return;
Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
}
private bool TryResolveAttackTarget(out TargetableObject targetable, out Vector3 hitPosition)
{
targetable = _target as TargetableObject;
hitPosition = CachedTransform.position;
if (targetable == null || !targetable.Available || targetable.IsDead)
{
return false;
}
Transform targetTransform = targetable.CachedTransform;
if (targetTransform == null)
{
return false;
}
hitPosition = targetTransform.position;
Vector3 fireOrigin = CachedTransform.TransformPoint(_fireOriginOffset);
Vector3 directionToTarget = targetTransform.position - fireOrigin;
float maxDistance = Mathf.Max(0.1f, _weaponData.AttackRange);
if (directionToTarget.sqrMagnitude > Mathf.Epsilon &&
Physics.Raycast(fireOrigin, directionToTarget.normalized, out RaycastHit hit, maxDistance, _hitMask,
QueryTriggerInteraction.Collide))
{
TargetableObject raycastTarget = hit.collider.GetComponentInParent<TargetableObject>();
if (raycastTarget == targetable)
{
hitPosition = hit.point;
}
}
return true;
}
#region Lifecycle
protected override bool OnWeaponShow(object userData)
{
_weaponData = RequireWeaponData<WeaponHandgunData>(userData);
if (_weaponData == null) return false;
WeaponData = _weaponData;
_currAttackTimer = 0f;
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation;
_attackEffect = new HandgunHitMarkerAttackEffect(_hitMarkerSize, _hitMarkerYOffset, _hitMarkerDuration,
_hitMarkerColor);
if (_weaponData.OwnerCamp == CampType.Player)
{
gameObject.layer = LayerMask.NameToLayer("PlayerWeapon");
_hitMask = LayerMask.GetMask("Enemy");
}
else if (_weaponData.OwnerCamp == CampType.Enemy)
{
gameObject.layer = LayerMask.NameToLayer("EnemyWeapon");
_hitMask = LayerMask.GetMask("Player");
}
return true;
}
protected override void OnWeaponHide(object userData)
{
_attackEffect = null;
}
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
BindAttackStatFromOwner(parentEntity);
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
ReleaseAttackStatSubscription();
}
#endregion
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 44ae1bdd1d43e274980743df21658730
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f89893f21b3046b7a93a368cd688451e
timeCreated: 1771578597
@@ -0,0 +1,31 @@
namespace Entity.Weapon
{
public partial class WeaponKnife
{
private class AttackState : WeaponStateBase
{
private WeaponKnife _weapon;
public override WeaponStateType State => WeaponStateType.Attack;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponKnife;
public override void OnEnter()
{
_weapon._currAttackTimer = 0f;
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_weapon._isAttacking)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave()
{
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More