迁移数据表到 Luban,移除 JsonNet、EnumUtility

This commit is contained in:
2026-09-13 14:08:14 +08:00
parent 4003b3aaba
commit de53890de6
607 changed files with 18515 additions and 3257 deletions
@@ -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 SepCore.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 SepCore.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 SepCore.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 SepCore.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 SepCore.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 SepCore.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 SepCore.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 SepCore.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 SepCore.Components;
using SepCore.CustomUtility;
namespace SepCore.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 SepCore.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 SepCore.Components;
using SepCore.Definition;
using SepCore.CustomUtility;
namespace SepCore.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 SepCore.EnemyManager;
using SepCore.CustomUtility;
namespace SepCore.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 SepCore.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,342 @@
using System.Collections.Generic;
using SepCore.Components;
using SepCore.Definition;
using SepCore.Entity;
using GameFramework;
using SepCore.CustomUtility;
using SepCore.Timer;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.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 bool _canAttack;
protected TimerHandle _attackTimerHandle;
protected float _sqrRange;
protected StatProperty AttackStat { get; private set; } = new();
private StatComponent _attackStatComponent;
private System.Action<modifier, 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();
}
protected void StartAttackCooldown(float cooldown)
{
_canAttack = false;
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(cooldown, () => _canAttack = true, this);
}
protected void ResetAttackCooldown(float cooldown)
{
GameEntry.Timer.Cancel(_attackTimerHandle);
_canAttack = false;
_attackTimerHandle = GameEntry.Timer.ScheduleOnce(cooldown, () => _canAttack = true, this);
}
protected void CancelAttackCooldown()
{
GameEntry.Timer.Cancel(_attackTimerHandle);
_attackTimerHandle = TimerHandle.Invalid;
_canAttack = false;
}
}
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 SepCore.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.ResetAttackCooldown(_weapon._weaponData.Cooldown);
_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,45 @@
namespace SepCore.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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave() { }
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bfd76c0fb7284ceaa28f78c702f595da
timeCreated: 1771579231
@@ -0,0 +1,32 @@
namespace SepCore.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);
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,26 @@
namespace SepCore.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);
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,170 @@
using SepCore.Definition;
using SepCore.Entity;
using SepCore.CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.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;
StartAttackCooldown(_weaponData.Cooldown);
_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)
{
CancelAttackCooldown();
_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 SepCore.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.ResetAttackCooldown(_weapon._weaponData.Cooldown);
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_weapon._isAttacking)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 417516ffe5574e0c998105fcaa073de7
timeCreated: 1771578955
@@ -0,0 +1,48 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponKnife
{
private class CheckInRangeState : WeaponStateBase
{
private WeaponKnife _weapon;
public override WeaponStateType State => WeaponStateType.Check_InRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponKnife;
public override void OnEnter()
{
if (_weapon._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 59644093364644768c26261b24bdb820
timeCreated: 1771578916
@@ -0,0 +1,38 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponKnife
{
private class CheckOutRangeState : WeaponStateBase
{
private WeaponKnife _weapon;
public override WeaponStateType State => WeaponStateType.Check_OutRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponKnife;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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: d9e1a63869694e72a360afe29cc94cfb
timeCreated: 1771578994
@@ -0,0 +1,32 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponKnife
{
public class IdleState : WeaponStateBase
{
private WeaponKnife _weapon;
public override WeaponStateType State => WeaponStateType.Idle;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponKnife;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToOrigin(elapseSeconds);
if (_weapon._target != null && _weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b4d66ec44b064640aa95c6aa71ff3846
timeCreated: 1771578662
@@ -0,0 +1,229 @@
using System.Collections.Generic;
using SepCore.CustomUtility;
using SepCore.Definition;
using DG.Tweening;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity.Weapon
{
public partial class WeaponKnife : WeaponBase
{
private WeaponKnifeData _weaponData;
private Quaternion _cachedRotation;
[SerializeField] private float _rotateSpeed = 4f;
private Sequence _attackSequence;
private Transform _attackParent;
private Vector3 _attackCenter;
[SerializeField] private float attackDuration = 0.15f;
[SerializeField] private float returnDuration = 0.2f;
[SerializeField] private LayerMask _hitMask = ~0;
[SerializeField] private int _maxHitColliders = 32;
private IWeaponAttackEffect _attackEffect;
private Collider[] _hitResults;
private readonly HashSet<int> _hitEntityIds = new();
private float _hitRadius;
private float _hitRadiusSqr;
public override ImpactData GetImpactData()
{
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
}
protected override void BuildStates()
{
RegisterState(new IdleState());
RegisterState(new CheckInRangeState());
RegisterState(new CheckOutRangeState());
RegisterState(new AttackState());
}
protected override void Attack()
{
StopAttackTween(false);
FaceTargetImmediately();
_isAttacking = true;
Transform parentTransform = CachedTransform.parent;
_attackParent = parentTransform;
_attackCenter = _target != null ? _target.CachedTransform.position : CachedTransform.position;
CachedTransform.SetParent(null);
Vector3 targetPos = CachedTransform.position + CachedTransform.forward * _weaponData.AttackRange;
_attackSequence = DOTween.Sequence();
_attackSequence.Append(CachedTransform.DOMove(targetPos, attackDuration).SetEase(Ease.OutQuad));
_attackSequence.AppendCallback(() =>
{
_attackEffect?.Play(this, _attackCenter, null, _hitRadius);
ApplyGroundAreaDamage();
if (_attackParent != null)
{
CachedTransform.SetParent(_attackParent);
}
});
_attackSequence.Append(
CachedTransform.DOLocalMove(Vector3.zero, returnDuration * 0.5f).SetEase(Ease.InQuad));
_attackSequence.AppendCallback(() =>
{
_isAttacking = false;
_attackSequence = null;
_attackParent = null;
});
}
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;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
CachedTransform.rotation =
Quaternion.Slerp(CachedTransform.rotation, targetRotation, _rotateSpeed * elapseSeconds);
}
public 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 void ApplyGroundAreaDamage()
{
if (_hitRadius <= 0f) return;
if (TryQueueAreaCollisionQuery(_attackCenter, _hitRadius, Mathf.Max(1, _maxHitColliders)))
{
_hitEntityIds.Clear();
return;
}
if (_hitResults == null || _hitResults.Length == 0) return;
int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _hitRadius, _hitResults, _hitMask,
QueryTriggerInteraction.Collide);
_hitEntityIds.Clear();
for (int i = 0; i < hitCount; i++)
{
Collider collider = _hitResults[i];
if (collider == null) continue;
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
if (!_hitEntityIds.Add(targetable.Id)) continue;
Vector3 delta = targetable.CachedTransform.position - _attackCenter;
delta.y = 0f;
if (delta.sqrMagnitude > _hitRadiusSqr) continue;
AIUtility.PerformCollision(targetable, this);
}
}
protected override bool OnWeaponShow(object userData)
{
_weaponData = RequireWeaponData<WeaponKnifeData>(userData);
if (_weaponData == null) return false;
WeaponData = _weaponData;
StartAttackCooldown(_weaponData.Cooldown);
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation;
float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f;
_hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange;
_hitRadiusSqr = _hitRadius * _hitRadius;
_attackEffect = new KnifeRangeAttackEffect();
int colliderCapacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != colliderCapacity)
{
_hitResults = new Collider[colliderCapacity];
}
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)
{
StopAttackTween(true);
CancelAttackCooldown();
_attackEffect = null;
}
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
BindAttackStatFromOwner(parentEntity);
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
StopAttackTween(true);
ReleaseAttackStatSubscription();
}
protected override void OnEnabledChanged(bool enabled)
{
if (!enabled)
{
StopAttackTween(true);
}
}
private void StopAttackTween(bool resetTransform)
{
if (_attackSequence != null)
{
_attackSequence.Kill();
_attackSequence = null;
}
_isAttacking = false;
if (resetTransform && _attackParent != null)
{
CachedTransform.SetParent(_attackParent);
CachedTransform.localPosition = Vector3.zero;
}
_attackParent = null;
_hitEntityIds.Clear();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bbf2e0cccf970934e82f8006d5be7846
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aee5d5037e73e894cac11712e321b930
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,31 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLance
{
private class AttackState : WeaponStateBase
{
private WeaponLance _weapon;
public override WeaponStateType State => WeaponStateType.Attack;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance;
public override void OnEnter()
{
_weapon.ResetAttackCooldown(_weapon._weaponData.Cooldown);
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_weapon._isAttacking)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d9991ee189b733b4e9d40395357f5ef2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLance
{
private class CheckInRangeState : WeaponStateBase
{
private WeaponLance _weapon;
public override WeaponStateType State => WeaponStateType.Check_InRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance;
public override void OnEnter()
{
if (_weapon._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 01edc8bb0ff46a14d8028e03af932b52
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLance
{
private class CheckOutRangeState : WeaponStateBase
{
private WeaponLance _weapon;
public override WeaponStateType State => WeaponStateType.Check_OutRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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,11 @@
fileFormatVersion: 2
guid: 8009ad2c332a956438e3357ed5e30eb6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLance
{
public class IdleState : WeaponStateBase
{
private WeaponLance _weapon;
public override WeaponStateType State => WeaponStateType.Idle;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLance;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToOrigin(elapseSeconds);
if (_weapon._target != null && _weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4c9e98f5381e579469dcdac7bf3e1e25
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,359 @@
using System.Collections.Generic;
using SepCore.Components;
using SepCore.CustomUtility;
using SepCore.Definition;
using DG.Tweening;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity.Weapon
{
public partial class WeaponLance : WeaponBase
{
// 长枪专用数据,包含强类型 ParamsData。
[SerializeField] private WeaponLanceData _weaponData;
private Quaternion _cachedRotation;
// 朝向目标时的旋转速度,可被 ParamsData.RotateSpeed 覆盖。
[SerializeField] private float _rotateSpeed = 5f;
private Sequence _attackSequence;
private Transform _attackParent;
// 前刺动画耗时。
[SerializeField] private float _attackDuration = 0.12f;
// 收枪返回耗时。
[SerializeField] private float _returnDuration = 0.18f;
[SerializeField] private LayerMask _hitMask = ~0;
[SerializeField] private int _maxHitColliders = 32;
private IWeaponAttackEffect _attackEffect;
private Collider[] _hitResults;
private readonly HashSet<int> _hitEntityIds = new();
// 前戳矩形判定的横向半宽。
private float _hitHalfWidth;
// 前戳矩形判定盒体的总高度。
private float _hitHeight;
// 盒体中心相对战斗平面的高度偏移。
private float _hitCenterYOffset;
// 从判定起点向前延伸的有效刺击长度。
private float _pierceLength;
// 判定起点相对武器当前位置的前移量。
private float _forwardOffset;
// 本次攻击锁定的前戳方向,避免受位移动画中的武器位置影响。
private Vector3 _strikeDirection = Vector3.forward;
// 本次攻击锁定的矩形判定中心。
private Vector3 _strikeCenter;
public override ImpactData GetImpactData()
{
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
}
protected override void BuildStates()
{
RegisterState(new IdleState());
RegisterState(new CheckInRangeState());
RegisterState(new CheckOutRangeState());
RegisterState(new AttackState());
}
protected override void Attack()
{
StopAttackTween(false);
FaceTargetImmediately();
CacheStrikeSnapshot();
_isAttacking = true;
_attackParent = CachedTransform.parent;
CachedTransform.SetParent(null);
Vector3 targetPos = CachedTransform.position + _strikeDirection * _pierceLength;
_attackSequence = DOTween.Sequence();
_attackSequence.Append(CachedTransform.DOMove(targetPos, _attackDuration).SetEase(Ease.OutQuad));
_attackSequence.AppendCallback(() =>
{
_attackEffect?.Play(this, _strikeCenter, _target, _hitHalfWidth);
ApplyPierceDamage();
if (_attackParent != null)
{
CachedTransform.SetParent(_attackParent);
}
});
_attackSequence.Append(CachedTransform.DOLocalMove(Vector3.zero, _returnDuration).SetEase(Ease.InQuad));
_attackSequence.AppendCallback(() =>
{
_isAttacking = false;
_attackSequence = null;
_attackParent = null;
});
}
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;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, 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;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
}
private void CacheStrikeSnapshot()
{
_strikeDirection = ResolvePlanarForward();
Vector3 strikeStart = CachedTransform.position;
strikeStart.y = ResolveStrikePlaneY();
strikeStart += _strikeDirection * _forwardOffset;
_strikeCenter = strikeStart + _strikeDirection * (_pierceLength * 0.5f);
_strikeCenter.y += _hitCenterYOffset;
}
private Vector3 ResolvePlanarForward()
{
Vector3 forward = CachedTransform.forward;
forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon)
{
forward = Vector3.forward;
}
forward.Normalize();
return forward;
}
private void ApplyPierceDamage()
{
if (_hitHalfWidth <= 0f || _pierceLength <= 0f) return;
Vector3 strikeDirection = _strikeDirection;
if (strikeDirection.sqrMagnitude <= Mathf.Epsilon) return;
Vector3 broadPhaseCenter = _strikeCenter;
Quaternion broadPhaseRotation = Quaternion.LookRotation(strikeDirection, Vector3.up);
if (TryQueueRectangleCollisionQuery(broadPhaseCenter, _hitHalfWidth, _pierceLength * 0.5f,
strikeDirection, Mathf.Max(1, _maxHitColliders)))
{
_hitEntityIds.Clear();
return;
}
int capacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != capacity)
{
_hitResults = new Collider[capacity];
}
Vector3 halfExtents = new(_hitHalfWidth, _hitHeight * 0.5f, _pierceLength * 0.5f);
int hitCount = Physics.OverlapBoxNonAlloc(broadPhaseCenter, halfExtents, _hitResults, broadPhaseRotation,
_hitMask, QueryTriggerInteraction.Collide);
_hitEntityIds.Clear();
for (int i = 0; i < hitCount; i++)
{
Collider collider = _hitResults[i];
if (collider == null) continue;
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
if (!_hitEntityIds.Add(targetable.Id)) continue;
if (!IsTargetInsidePierce(targetable, broadPhaseCenter, strikeDirection)) continue;
AIUtility.PerformCollision(targetable, this);
}
}
private bool IsTargetInsidePierce(TargetableObject targetable, Vector3 strikeCenter, Vector3 strikeDirection)
{
Vector3 delta = targetable.CachedTransform.position - strikeCenter;
delta.y = 0f;
Vector3 right = Vector3.Cross(Vector3.up, strikeDirection);
float forwardDistance = Vector3.Dot(delta, strikeDirection);
float lateralDistance = Vector3.Dot(delta, right);
float targetRadius = ResolveTargetCollisionRadius(targetable);
return Mathf.Abs(forwardDistance) <= _pierceLength * 0.5f + targetRadius &&
Mathf.Abs(lateralDistance) <= _hitHalfWidth + targetRadius;
}
private static float ResolveTargetCollisionRadius(TargetableObject targetable)
{
if (targetable == null)
{
return 0f;
}
MovementComponent movementComponent = targetable.GetComponent<MovementComponent>();
return movementComponent != null ? Mathf.Max(0f, movementComponent.EnemyBodyRadius) : 0f;
}
private float ResolveStrikePlaneY()
{
if (_target != null && _target.Available)
{
return _target.CachedTransform.position.y;
}
if (_attackParent != null)
{
return _attackParent.position.y;
}
if (CachedTransform.parent != null)
{
return CachedTransform.parent.position.y;
}
return CachedTransform.position.y;
}
protected override bool OnWeaponShow(object userData)
{
_weaponData = RequireWeaponData<WeaponLanceData>(userData);
if (_weaponData == null) return false;
WeaponData = _weaponData;
StartAttackCooldown(_weaponData.Cooldown);
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation;
WeaponLanceParamsData paramsData = _weaponData.ParamsData;
float configuredHalfWidth = paramsData != null && paramsData.HitHalfWidth > 0f
? paramsData.HitHalfWidth
: paramsData != null && paramsData.HitRadius > 0f
? paramsData.HitRadius
: 0.45f;
_hitHalfWidth = Mathf.Max(0.1f, configuredHalfWidth);
_hitHeight = paramsData != null && paramsData.HitHeight > 0f
? Mathf.Max(0.2f, paramsData.HitHeight)
: 4f;
_hitCenterYOffset = paramsData != null ? paramsData.HitCenterYOffset : 0f;
_pierceLength = paramsData != null && paramsData.PierceLength > 0f
? paramsData.PierceLength
: paramsData != null && paramsData.ThrustDistance > 0f
? paramsData.ThrustDistance
: _weaponData.AttackRange;
_forwardOffset = paramsData != null && paramsData.ForwardOffset > 0f
? paramsData.ForwardOffset
: 0f;
if (paramsData != null && paramsData.RotateSpeed > 0f)
{
_rotateSpeed = paramsData.RotateSpeed;
}
if (paramsData != null && paramsData.AttackDuration > 0f)
{
_attackDuration = paramsData.AttackDuration;
}
if (paramsData != null && paramsData.ReturnDuration > 0f)
{
_returnDuration = paramsData.ReturnDuration;
}
int colliderCapacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != colliderCapacity)
{
_hitResults = new Collider[colliderCapacity];
}
_attackEffect = new LanceThrustAttackEffect();
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)
{
StopAttackTween(true);
CancelAttackCooldown();
_attackEffect = null;
}
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
BindAttackStatFromOwner(parentEntity);
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
StopAttackTween(true);
ReleaseAttackStatSubscription();
}
protected override void OnEnabledChanged(bool enabled)
{
if (!enabled)
{
StopAttackTween(true);
}
}
private void StopAttackTween(bool resetTransform)
{
if (_attackSequence != null)
{
_attackSequence.Kill();
_attackSequence = null;
}
_isAttacking = false;
if (resetTransform && _attackParent != null)
{
CachedTransform.SetParent(_attackParent);
CachedTransform.localPosition = Vector3.zero;
CachedTransform.rotation = _cachedRotation;
}
_attackParent = null;
_hitEntityIds.Clear();
}
public float HitHalfWidth => _hitHalfWidth;
public float PierceLength => _pierceLength;
public Vector3 StrikeDirection => _strikeDirection;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f90612be7695ee549b39473c9b706122
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5fded69986744ded97edb5f2ac06304f
timeCreated: 1771578597
@@ -0,0 +1,31 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLightning
{
private class AttackState : WeaponStateBase
{
private WeaponLightning _weapon;
public override WeaponStateType State => WeaponStateType.Attack;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
public override void OnEnter()
{
_weapon.ResetAttackCooldown(_weapon._weaponData.Cooldown);
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_weapon._isAttacking)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25854832a7d3416dacab67bcfef2a2fa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,48 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLightning
{
private class CheckInRangeState : WeaponStateBase
{
private WeaponLightning _weapon;
public override WeaponStateType State => WeaponStateType.Check_InRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
public override void OnEnter()
{
if (_weapon._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 25404ad172434ba38609e797b6d96919
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,38 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLightning
{
private class CheckOutRangeState : WeaponStateBase
{
private WeaponLightning _weapon;
public override WeaponStateType State => WeaponStateType.Check_OutRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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,11 @@
fileFormatVersion: 2
guid: f3aa479abb79491d83e8de76806faf7a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,32 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponLightning
{
private class IdleState : WeaponStateBase
{
private WeaponLightning _weapon;
public override WeaponStateType State => WeaponStateType.Idle;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponLightning;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToOrigin(elapseSeconds);
if (_weapon._target != null && _weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 3464037c29774c3ea326dc93fe33551e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,264 @@
using System.Collections.Generic;
using SepCore.CustomUtility;
using SepCore.Definition;
using DG.Tweening;
using SepCore.Entity;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity.Weapon
{
public partial class WeaponLightning : WeaponBase
{
private WeaponLightningData _weaponData;
private Quaternion _cachedRotation;
[SerializeField] private float _rotateSpeed = 5f;
[SerializeField] private float _takeOffDuration = 0.3f;
[SerializeField] private float _flyToTargetDuration = 0.2f;
[SerializeField] private float _strikeDuration = 0.1f;
[SerializeField] private float _returnDuration = 0.22f;
[SerializeField] private float _hoverHeight = 15f;
[SerializeField] private LayerMask _hitMask = ~0;
[SerializeField] private int _maxHitColliders = 32;
private Sequence _attackSequence;
private Transform _attackParent;
private Vector3 _lockedStrikePoint;
private Collider[] _hitResults;
private readonly HashSet<int> _hitEntityIds = new();
private float _hitRadius;
private float _hitRadiusSqr;
private IWeaponAttackEffect _attackEffect;
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()
{
StopAttackTween(false);
FaceTargetImmediately();
_isAttacking = true;
_lockedStrikePoint = ResolveStrikePoint();
_attackParent = CachedTransform.parent;
CachedTransform.SetParent(null);
Vector3 takeOffPosition = CachedTransform.position;
Vector3 hoverPosition = _lockedStrikePoint + Vector3.up * Mathf.Max(0.1f, _hoverHeight);
_attackSequence = DOTween.Sequence();
_attackSequence.Append(CachedTransform.DOMove(takeOffPosition, _takeOffDuration).SetEase(Ease.OutQuad));
_attackSequence.Append(CachedTransform.DOMove(hoverPosition, _flyToTargetDuration).SetEase(Ease.OutSine));
_attackSequence.AppendCallback(() => CachedTransform.LookAt(_lockedStrikePoint));
_attackSequence.Append(CachedTransform.DOMove(_lockedStrikePoint, _strikeDuration).SetEase(Ease.InQuad));
_attackSequence.AppendCallback(() =>
{
_attackEffect?.Play(this, _lockedStrikePoint, _target, _hitRadius);
ApplyGroundAreaDamage();
if (_attackParent != null)
{
CachedTransform.SetParent(_attackParent);
}
});
_attackSequence.Append(CachedTransform.DOLocalMove(Vector3.zero, _returnDuration).SetEase(Ease.OutSine));
_attackSequence.AppendCallback(() =>
{
_isAttacking = false;
_attackSequence = null;
_attackParent = null;
});
}
protected override void Check()
{
_target = SelectTarget(_sqrRange);
}
private Vector3 ResolveStrikePoint()
{
if (_target != null && _target.Available)
{
return _target.CachedTransform.position;
}
return CachedTransform.position;
}
private void ApplyGroundAreaDamage()
{
if (_hitRadius <= 0f) return;
if (TryQueueAreaCollisionQuery(_lockedStrikePoint, _hitRadius, Mathf.Max(1, _maxHitColliders)))
{
_hitEntityIds.Clear();
return;
}
if (_hitResults == null || _hitResults.Length == 0) return;
int hitCount = Physics.OverlapSphereNonAlloc(_lockedStrikePoint, _hitRadius, _hitResults, _hitMask,
QueryTriggerInteraction.Collide);
_hitEntityIds.Clear();
for (int i = 0; i < hitCount; i++)
{
Collider collider = _hitResults[i];
if (collider == null) continue;
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
if (!_hitEntityIds.Add(targetable.Id)) continue;
Vector3 delta = targetable.CachedTransform.position - _lockedStrikePoint;
delta.y = 0f;
if (delta.sqrMagnitude > _hitRadiusSqr) continue;
AIUtility.PerformCollision(targetable, this);
}
}
private void RotateToTarget(float elapseSeconds)
{
if (_target == null || !_target.Available) return;
Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, 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;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
}
protected override bool OnWeaponShow(object userData)
{
_weaponData = RequireWeaponData<WeaponLightningData>(userData);
if (_weaponData == null) return false;
WeaponData = _weaponData;
StartAttackCooldown(_weaponData.Cooldown);
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation;
float configuredHitRadius = _weaponData.ParamsData != null ? _weaponData.ParamsData.HitRadius : 0f;
_hitRadius = configuredHitRadius > 0f ? Mathf.Max(0.1f, configuredHitRadius) : _weaponData.AttackRange;
_hitRadiusSqr = _hitRadius * _hitRadius;
float configuredHoverHeight = _weaponData.ParamsData != null ? _weaponData.ParamsData.HoverHeight : 0f;
if (configuredHoverHeight > 0f)
{
_hoverHeight = Mathf.Max(0.1f, configuredHoverHeight);
}
int colliderCapacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != colliderCapacity)
{
_hitResults = new Collider[colliderCapacity];
}
_attackEffect = new LightningStrikeAttackEffect();
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)
{
StopAttackTween(true);
CancelAttackCooldown();
_attackEffect = null;
}
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
BindAttackStatFromOwner(parentEntity);
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
StopAttackTween(true);
ReleaseAttackStatSubscription();
}
protected override void OnEnabledChanged(bool enabled)
{
if (!enabled)
{
StopAttackTween(true);
}
}
private void StopAttackTween(bool resetTransform)
{
if (_attackSequence != null)
{
_attackSequence.Kill();
_attackSequence = null;
}
_isAttacking = false;
if (resetTransform)
{
if (_attackParent != null)
{
CachedTransform.SetParent(_attackParent);
CachedTransform.localPosition = Vector3.zero;
}
else if (CachedTransform.parent != null)
{
CachedTransform.localPosition = Vector3.zero;
}
CachedTransform.rotation = _cachedRotation;
}
_attackParent = null;
_hitEntityIds.Clear();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8315d9e60c4434ebde9b23c853f27d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9289c110a12b456aabb374427caa9e1a
timeCreated: 1771579177
@@ -0,0 +1,30 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponSlash
{
private class AttackState : WeaponStateBase
{
private WeaponSlash _weapon;
public override WeaponStateType State => WeaponStateType.Attack;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponSlash;
public override void OnEnter()
{
_weapon.ResetAttackCooldown(_weapon._weaponData.Cooldown);
_weapon.Attack();
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
if (!_weapon._isAttacking)
{
_weapon.TransitionTo(WeaponStateType.Check_InRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 96b136ec326941a0821f66ddf750de29
timeCreated: 1771579419
@@ -0,0 +1,47 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponSlash
{
private class CheckInRangeState : WeaponStateBase
{
private WeaponSlash _weapon;
public override WeaponStateType State => WeaponStateType.Check_InRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponSlash;
public override void OnEnter()
{
if (_weapon._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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._canAttack)
{
_weapon.TransitionTo(WeaponStateType.Attack);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 57a936178b5a4ff2938df369b77631e6
timeCreated: 1771579430
@@ -0,0 +1,37 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponSlash
{
private class CheckOutRangeState : WeaponStateBase
{
private WeaponSlash _weapon;
public override WeaponStateType State => WeaponStateType.Check_OutRange;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponSlash;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToTarget(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: 89988d7be9104b0f83a423d1de2398b1
timeCreated: 1771579440
@@ -0,0 +1,31 @@
namespace SepCore.Entity.Weapon
{
public partial class WeaponSlash
{
private class IdleState : WeaponStateBase
{
private WeaponSlash _weapon;
public override WeaponStateType State => WeaponStateType.Idle;
public override void OnInit(WeaponBase weapon) => _weapon = weapon as WeaponSlash;
public override void OnEnter()
{
}
public override void OnUpdate(float elapseSeconds, float realElapseSeconds)
{
_weapon.Check();
_weapon.RotateToOrigin(elapseSeconds);
if (_weapon._target != null && _weapon._target.Available)
{
_weapon.TransitionTo(WeaponStateType.Check_OutRange);
}
}
public override void OnLeave()
{
}
}
}
}
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bec8a4a6cb974111bbd70072754f9fdb
timeCreated: 1771579452
@@ -0,0 +1,258 @@
using System.Collections.Generic;
using SepCore.Definition;
using DG.Tweening;
using SepCore.Entity;
using SepCore.CustomUtility;
using UnityEngine;
using UnityGameFramework.Runtime;
namespace SepCore.Entity.Weapon
{
public partial class WeaponSlash : WeaponBase
{
#region Property
private WeaponSlashData _weaponData;
private Quaternion _cachedRotation;
[SerializeField] private float _rotateSpeed = 4f;
private Sequence _attackSequence;
private Vector3 _attackCenter;
private Quaternion _cachedAttackRotation;
private IWeaponAttackEffect _attackEffect;
[SerializeField] private float attackDuration = 0.4f;
[SerializeField] private float returnDuration = 0.4f;
[SerializeField] private float _swingAngleScale = 1f;
[SerializeField] private LayerMask _hitMask = ~0;
[SerializeField] private int _maxHitColliders = 32;
private Collider[] _hitResults;
private readonly HashSet<int> _hitEntityIds = new();
private float _attackRadius;
private float _attackRadiusSqr;
private float _sectorAngle;
#endregion
public override ImpactData GetImpactData()
{
return new ImpactData(_weaponData.OwnerCamp, _weaponData.Attack, AttackStat);
}
protected override void BuildStates()
{
RegisterState(new IdleState());
RegisterState(new CheckInRangeState());
RegisterState(new CheckOutRangeState());
RegisterState(new AttackState());
}
protected override void Attack()
{
StopAttackTween(false);
FaceTargetImmediately();
_isAttacking = true;
_attackCenter = CachedTransform.position;
_cachedAttackRotation = CachedTransform.rotation;
float swingAngle = Mathf.Clamp(_sectorAngle * Mathf.Max(0.1f, _swingAngleScale), 15f, 170f);
Quaternion startRotation = Quaternion.AngleAxis(-swingAngle * 0.5f, Vector3.up) * _cachedAttackRotation;
Quaternion endRotation = Quaternion.AngleAxis(swingAngle * 0.5f, Vector3.up) * _cachedAttackRotation;
CachedTransform.rotation = startRotation;
_attackSequence = DOTween.Sequence();
_attackSequence.Append(
CachedTransform.DORotateQuaternion(endRotation, attackDuration).SetEase(Ease.OutSine));
_attackSequence.InsertCallback(attackDuration * 0.5f, () =>
{
_attackEffect?.Play(this, _attackCenter, _target, _attackRadius);
ApplySectorDamage();
});
_attackSequence.Append(CachedTransform.DORotateQuaternion(_cachedAttackRotation, returnDuration)
.SetEase(Ease.InSine));
_attackSequence.AppendCallback(() =>
{
_isAttacking = false;
_attackSequence = null;
});
}
protected override void Check()
{
_target = SelectTarget(_sqrRange);
}
private void ApplySectorDamage()
{
if (_attackRadius <= 0f) return;
Vector3 forward = CachedTransform.forward;
forward.y = 0f;
if (forward.sqrMagnitude <= Mathf.Epsilon)
{
forward = Vector3.forward;
}
forward.Normalize();
float halfAngle = _sectorAngle * 0.5f;
if (TryQueueSectorCollisionQuery(_attackCenter, _attackRadius, in forward, halfAngle,
Mathf.Max(1, _maxHitColliders)))
{
_hitEntityIds.Clear();
return;
}
if (_hitResults == null || _hitResults.Length == 0) return;
int hitCount = Physics.OverlapSphereNonAlloc(_attackCenter, _attackRadius, _hitResults, _hitMask,
QueryTriggerInteraction.Collide);
_hitEntityIds.Clear();
for (int i = 0; i < hitCount; i++)
{
Collider collider = _hitResults[i];
if (collider == null) continue;
TargetableObject targetable = collider.GetComponentInParent<TargetableObject>();
if (targetable == null || !targetable.Available || targetable.IsDead) continue;
if (!_hitEntityIds.Add(targetable.Id)) continue;
Vector3 toTarget = targetable.CachedTransform.position - _attackCenter;
toTarget.y = 0f;
float sqrDistance = toTarget.sqrMagnitude;
if (sqrDistance <= Mathf.Epsilon || sqrDistance > _attackRadiusSqr) continue;
float angle = Vector3.Angle(forward, toTarget.normalized);
if (angle > halfAngle) continue;
AIUtility.PerformCollision(targetable, this);
}
}
private void RotateToTarget(float elapseSeconds)
{
if (_target == null || !_target.Available) return;
Vector3 directionToTarget = _target.CachedTransform.position - CachedTransform.position;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
Quaternion targetRotation = Quaternion.LookRotation(directionToTarget.normalized, 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;
directionToTarget.y = 0f;
if (directionToTarget.sqrMagnitude <= Mathf.Epsilon) return;
CachedTransform.rotation = Quaternion.LookRotation(directionToTarget.normalized, Vector3.up);
}
#region Lifecycle
protected override bool OnWeaponShow(object userData)
{
_weaponData = RequireWeaponData<WeaponSlashData>(userData);
if (_weaponData == null) return false;
WeaponData = _weaponData;
StartAttackCooldown(_weaponData.Cooldown);
_sqrRange = _weaponData.AttackRange * _weaponData.AttackRange;
_cachedRotation = CachedTransform.rotation;
_attackRadius = Mathf.Max(0.1f, _weaponData.AttackRange);
_attackRadiusSqr = _attackRadius * _attackRadius;
float configuredSectorAngle = _weaponData.ParamsData != null ? _weaponData.ParamsData.SectorAngle : 0f;
_sectorAngle = configuredSectorAngle > 0f ? Mathf.Clamp(configuredSectorAngle, 1f, 360f) : 90f;
int capacity = Mathf.Max(1, _maxHitColliders);
if (_hitResults == null || _hitResults.Length != capacity)
{
_hitResults = new Collider[capacity];
}
_attackEffect = new SlashSectorAttackEffect();
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)
{
StopAttackTween(true);
CancelAttackCooldown();
_attackEffect = null;
}
protected override void OnWeaponAttach(EntityLogic parentEntity, Transform parentTransform, object userData)
{
BindAttackStatFromOwner(parentEntity);
}
protected override void OnWeaponDetach(EntityLogic parentEntity, object userData)
{
StopAttackTween(true);
ReleaseAttackStatSubscription();
}
protected override void OnEnabledChanged(bool enabled)
{
if (!enabled)
{
StopAttackTween(true);
}
}
#endregion
private void StopAttackTween(bool resetTransform)
{
if (_attackSequence != null)
{
_attackSequence.Kill();
_attackSequence = null;
}
_isAttacking = false;
if (resetTransform)
{
CachedTransform.rotation = _cachedAttackRotation;
}
_hitEntityIds.Clear();
}
public float SectorAngle => _sectorAngle;
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7a9d2a4fb0fc0254d85c5de012d2aff4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: