引入属性、伤害管线
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
using System;
|
||||
using SepCore.Definition;
|
||||
using SepCore.Entity;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -7,85 +5,32 @@ namespace SepCore.Components
|
||||
{
|
||||
public class AbsorbComponent : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private int _maxDetectCount = 64;
|
||||
|
||||
private Player _player;
|
||||
private Transform _target;
|
||||
private StatComponent _statComponent;
|
||||
private Collider[] _buffer;
|
||||
|
||||
private StatProperty _absorbRangeStat;
|
||||
private Action<modifier, bool> _absorbRangeStatCallback;
|
||||
|
||||
private float AbsorbRangeBase => GameEntry.Luban.Global.DefaultAbsorbRange / 1000f;
|
||||
private float AbsorbSpeed => GameEntry.Luban.Global.AbsorbSpeed / 1000f;
|
||||
private float CollectDistance => GameEntry.Luban.Global.CollectDistance / 1000f;
|
||||
|
||||
private float AbsorbRange => Mathf.Max(0f, (AbsorbRangeBase + _absorbRangeStat.Value) * _absorbRangeStat.Percent);
|
||||
private float AbsorbRange => AbsorbRangeBase;
|
||||
|
||||
public void OnInit(Player player, StatComponent statComponent)
|
||||
{
|
||||
_player = player;
|
||||
_target = player.CachedTransform;
|
||||
_statComponent = statComponent;
|
||||
_buffer = new Collider[Mathf.Max(1, _maxDetectCount)];
|
||||
|
||||
_absorbRangeStat = _statComponent.GetStat(StatType.AbsorbRange);
|
||||
_absorbRangeStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_absorbRangeStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.AbsorbRange, _absorbRangeStatCallback);
|
||||
}
|
||||
|
||||
public void OnUpdate(float elapseSeconds, float realElapseSeconds)
|
||||
{
|
||||
if (!_player.Available) return;
|
||||
|
||||
int hitCount = Physics.OverlapSphereNonAlloc(
|
||||
_target.position,
|
||||
AbsorbRange,
|
||||
_buffer,
|
||||
Constant.Layer.DropLayerMask
|
||||
);
|
||||
|
||||
for (int i = 0; i < hitCount; i++)
|
||||
{
|
||||
Collider hit = _buffer[i];
|
||||
if (hit == null) continue;
|
||||
|
||||
CoinEntity coin = hit.GetComponent<CoinEntity>();
|
||||
if (coin != null && coin.Available)
|
||||
{
|
||||
AbsorbDrop(coin.CachedTransform, elapseSeconds);
|
||||
coin.TryCollect(_player, CollectDistance);
|
||||
continue;
|
||||
}
|
||||
|
||||
ExpEntity exp = hit.GetComponent<ExpEntity>();
|
||||
if (exp != null && exp.Available)
|
||||
{
|
||||
AbsorbDrop(exp.CachedTransform, elapseSeconds);
|
||||
exp.TryCollect(_player, CollectDistance);
|
||||
}
|
||||
}
|
||||
GameEntry.SimulationWorld.SyncPlayerAbsorbSettings(AbsorbRange, AbsorbSpeed, CollectDistance);
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.AbsorbRange, _absorbRangeStatCallback);
|
||||
_absorbRangeStatCallback = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
_target = null;
|
||||
_player = null;
|
||||
_buffer = null;
|
||||
}
|
||||
|
||||
private void AbsorbDrop(Transform drop, float deltaTime)
|
||||
{
|
||||
drop.position = Vector3.MoveTowards(drop.position, _target.position, AbsorbSpeed * deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,23 +13,17 @@ namespace SepCore.Components
|
||||
|
||||
private StatComponent _statComponent;
|
||||
|
||||
private StatProperty _maxHealthStat;
|
||||
private Action<modifier, bool> _maxHealthStatCallback;
|
||||
public StatProperty MaxHealthStat => _maxHealthStat;
|
||||
|
||||
private StatProperty _defenseStat;
|
||||
private Action<modifier, bool> _defenseStatCallback;
|
||||
public StatProperty DefenseStat => _defenseStat;
|
||||
|
||||
private StatProperty _dodgeStat;
|
||||
private Action<modifier, bool> _dodgeStatCallback;
|
||||
public StatProperty DodgeStat => _dodgeStat;
|
||||
private Action<StatType, float> _onStatChangedAction;
|
||||
private float _lastLifeStealTime = -1f;
|
||||
public const float LifeStealIcd = 0.1f;
|
||||
|
||||
private bool _isPlayer => _statComponent != null;
|
||||
|
||||
public float HealthRatio => MaxHealth > 0 ? (float)CurrentHealth / MaxHealth : 0f;
|
||||
|
||||
public int MaxHealth => Mathf.CeilToInt((_maxHealthBase + _maxHealthStat.Value) * _maxHealthStat.Percent);
|
||||
public int MaxHealth => _statComponent != null
|
||||
? Mathf.CeilToInt(_statComponent.GetStatValue(StatType.MaxHp))
|
||||
: _maxHealthBase;
|
||||
|
||||
public int CurrentHealth
|
||||
{
|
||||
@@ -46,82 +40,115 @@ namespace SepCore.Components
|
||||
_statComponent = statComponent;
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_maxHealthStat = _statComponent.GetStat(StatType.MaxHealth);
|
||||
_maxHealthStatCallback = (modifier, isApply) =>
|
||||
{
|
||||
_statComponent.UpdateStat(_maxHealthStat, modifier, isApply);
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(0, CurrentHealth, MaxHealth));
|
||||
};
|
||||
_statComponent.Subscribe(StatType.MaxHealth, _maxHealthStatCallback);
|
||||
|
||||
_defenseStat = _statComponent.GetStat(StatType.Defense);
|
||||
_defenseStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_defenseStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.Defense, _defenseStatCallback);
|
||||
|
||||
_dodgeStat = _statComponent.GetStat(StatType.Dodge);
|
||||
_dodgeStatCallback = (modifier, isApply) =>
|
||||
_statComponent.UpdateStat(_dodgeStat, modifier, isApply);
|
||||
_statComponent.Subscribe(StatType.Dodge, _dodgeStatCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_maxHealthStat = new StatProperty();
|
||||
_defenseStat = new StatProperty();
|
||||
_dodgeStat = new StatProperty();
|
||||
_statComponent.Pipeline.SetBaseValue(StatType.MaxHp, maxHealth);
|
||||
_onStatChangedAction = OnStatChanged;
|
||||
_statComponent.Pipeline.OnStatChanged += _onStatChangedAction;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
if (_statComponent != null)
|
||||
if (_statComponent != null && _onStatChangedAction != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.MaxHealth, _maxHealthStatCallback);
|
||||
_maxHealthStatCallback = null;
|
||||
|
||||
_statComponent.Unsubscribe(StatType.Defense, _defenseStatCallback);
|
||||
_defenseStatCallback = null;
|
||||
|
||||
_statComponent.Unsubscribe(StatType.Dodge, _dodgeStatCallback);
|
||||
_dodgeStatCallback = null;
|
||||
_statComponent.Pipeline.OnStatChanged -= _onStatChangedAction;
|
||||
_onStatChangedAction = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
_lastLifeStealTime = -1f;
|
||||
}
|
||||
|
||||
private void OnStatChanged(StatType statType, float newValue)
|
||||
{
|
||||
if (statType == StatType.MaxHp)
|
||||
{
|
||||
if (_currentHealth > MaxHealth)
|
||||
{
|
||||
_currentHealth = MaxHealth;
|
||||
}
|
||||
|
||||
if (GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(0, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
public void TakeDamage(int damage)
|
||||
public void TakeDamage(int damage, bool isCrit = false, bool isDodge = false, Vector3? hitPosition = null)
|
||||
{
|
||||
if (_isDead) return;
|
||||
|
||||
float maxDodge = GameEntry.Luban.Global.MaxDodgeRate / 1000f;
|
||||
if (UnityEngine.Random.value < Mathf.Clamp(_dodgeStat.Value, 0f, maxDodge))
|
||||
if (isDodge)
|
||||
{
|
||||
damage = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
int defenseValue = (int)_defenseStat.Value;
|
||||
float defensePercent = _defenseStat.Percent;
|
||||
damage -= defenseValue;
|
||||
damage = Mathf.CeilToInt(damage / defensePercent);
|
||||
int minDamage = GameEntry.Luban.Global.MinDamage;
|
||||
if (damage < minDamage) damage = minDamage;
|
||||
float yOffset = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.DamageTextYOffset / 1000f
|
||||
: 1f;
|
||||
Vector3 pos = hitPosition ?? (transform.position + Vector3.up * yOffset);
|
||||
if (GameEntry.DamageText != null)
|
||||
{
|
||||
GameEntry.DamageText.ShowDamage(pos, 0, false, true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
_currentHealth -= damage;
|
||||
CauseDamage(damage);
|
||||
if (_isPlayer)
|
||||
if (damage <= 0) return;
|
||||
|
||||
_currentHealth = Mathf.Max(0, _currentHealth - damage);
|
||||
CauseDamage(damage, isCrit, hitPosition);
|
||||
|
||||
if (_isPlayer && GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(damage, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
|
||||
private void CauseDamage(int damage)
|
||||
public void TakeDamage(int damage)
|
||||
{
|
||||
TakeDamage(damage, false, false, null);
|
||||
}
|
||||
|
||||
public void Heal(int healAmount)
|
||||
{
|
||||
if (_isDead || healAmount <= 0) return;
|
||||
|
||||
int oldHealth = _currentHealth;
|
||||
_currentHealth = Mathf.Min(_currentHealth + healAmount, MaxHealth);
|
||||
int healed = _currentHealth - oldHealth;
|
||||
|
||||
if (healed > 0 && _isPlayer && GameEntry.Event != null)
|
||||
{
|
||||
GameEntry.Event.Fire(this, PlayerHealthChangeEventArgs.Create(-healed, CurrentHealth, MaxHealth));
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryTriggerLifeSteal(int healAmount, float? customTime = null)
|
||||
{
|
||||
if (_isDead || healAmount <= 0) return false;
|
||||
float currentTime = customTime ?? Time.time;
|
||||
if (_lastLifeStealTime >= 0f && currentTime - _lastLifeStealTime < LifeStealIcd)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_lastLifeStealTime = currentTime;
|
||||
Heal(healAmount);
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CauseDamage(int damage, bool isCrit = false, Vector3? hitPosition = null)
|
||||
{
|
||||
if (damage <= 0) return;
|
||||
float yOffset = GameEntry.Luban.Global.DamageTextYOffset / 1000f;
|
||||
GameEntry.DamageText.ShowDamage(transform.position + Vector3.up * yOffset, damage);
|
||||
float yOffset = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.DamageTextYOffset / 1000f
|
||||
: 1f;
|
||||
Vector3 pos = hitPosition ?? (transform.position + Vector3.up * yOffset);
|
||||
if (GameEntry.DamageText != null)
|
||||
{
|
||||
GameEntry.DamageText.ShowDamage(pos, damage, isCrit, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace SepCore.Components
|
||||
[SerializeField] private float _enemyBodyRadius;
|
||||
[SerializeField] private int _separationIterations;
|
||||
|
||||
public float Speed => (_speedBase + _movementStat.Value) * _movementStat.Percent;
|
||||
public float Speed => _statComponent != null ? _statComponent.GetStatValue(StatType.MoveSpeed) : _speedBase;
|
||||
public bool AvoidEnemyOverlap => _avoidEnemyOverlap;
|
||||
public float EnemyBodyRadius => _enemyBodyRadius;
|
||||
public int SeparationIterations => _separationIterations;
|
||||
@@ -28,9 +28,7 @@ namespace SepCore.Components
|
||||
private StatComponent _statComponent;
|
||||
private Player _player;
|
||||
private EnemyBase _enemy;
|
||||
|
||||
private StatProperty _movementStat;
|
||||
private Action<modifier, bool> _movementStatCallback;
|
||||
private Action<StatType, float> _onStatChangedAction;
|
||||
|
||||
public void OnInit(float speed, Transform target, StatComponent statComponent = null,
|
||||
bool avoidEnemyOverlap = false, float? enemyBodyRadius = null, int? separationIterations = null)
|
||||
@@ -39,8 +37,15 @@ namespace SepCore.Components
|
||||
_cachedTransform = target;
|
||||
_direction = Vector3.forward;
|
||||
_avoidEnemyOverlap = avoidEnemyOverlap;
|
||||
_enemyBodyRadius = Mathf.Max(0.01f, enemyBodyRadius ?? (GameEntry.Luban.Global.EnemyBodyRadius / 1000f));
|
||||
_separationIterations = Mathf.Max(1, separationIterations ?? GameEntry.Luban.Global.EnemySeparationIterations);
|
||||
float defaultRadius = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemyBodyRadius / 1000f
|
||||
: 0.5f;
|
||||
int defaultIterations = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemySeparationIterations
|
||||
: 4;
|
||||
|
||||
_enemyBodyRadius = Mathf.Max(0.01f, enemyBodyRadius ?? defaultRadius);
|
||||
_separationIterations = Mathf.Max(1, separationIterations ?? defaultIterations);
|
||||
|
||||
TryGetComponent(out _player);
|
||||
TryGetComponent(out _enemy);
|
||||
@@ -48,17 +53,9 @@ namespace SepCore.Components
|
||||
_statComponent = statComponent;
|
||||
if (_statComponent != null)
|
||||
{
|
||||
_movementStat = _statComponent.GetStat(StatType.MovementSpeed);
|
||||
_movementStatCallback = (modifier, isApply) =>
|
||||
{
|
||||
_statComponent.UpdateStat(_movementStat, modifier, isApply);
|
||||
SyncToSimulationWorld();
|
||||
};
|
||||
_statComponent.Subscribe(StatType.MovementSpeed, _movementStatCallback);
|
||||
}
|
||||
else
|
||||
{
|
||||
_movementStat = new StatProperty();
|
||||
_statComponent.Pipeline.SetBaseValue(StatType.MoveSpeed, speed);
|
||||
_onStatChangedAction = OnStatChanged;
|
||||
_statComponent.Pipeline.OnStatChanged += _onStatChangedAction;
|
||||
}
|
||||
|
||||
SyncToSimulationWorld();
|
||||
@@ -73,25 +70,32 @@ namespace SepCore.Components
|
||||
{
|
||||
Transform transformToUnregister = _cachedTransform;
|
||||
|
||||
float defaultRadius = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemyBodyRadius / 1000f
|
||||
: 0.5f;
|
||||
int defaultIterations = GameEntry.Luban != null && GameEntry.Luban.IsReady
|
||||
? GameEntry.Luban.Global.EnemySeparationIterations
|
||||
: 4;
|
||||
|
||||
_speedBase = 0;
|
||||
_cachedTransform = null;
|
||||
_direction = Vector3.zero;
|
||||
_isMoving = false;
|
||||
_avoidEnemyOverlap = false;
|
||||
_enemyBodyRadius = GameEntry.Luban.Global.EnemyBodyRadius / 1000f;
|
||||
_separationIterations = GameEntry.Luban.Global.EnemySeparationIterations;
|
||||
_enemyBodyRadius = defaultRadius;
|
||||
_separationIterations = defaultIterations;
|
||||
_player = null;
|
||||
_enemy = null;
|
||||
|
||||
if (_statComponent != null)
|
||||
if (_statComponent != null && _onStatChangedAction != null)
|
||||
{
|
||||
_statComponent.Unsubscribe(StatType.MovementSpeed, _movementStatCallback);
|
||||
_movementStatCallback = null;
|
||||
_statComponent.Pipeline.OnStatChanged -= _onStatChangedAction;
|
||||
_onStatChangedAction = null;
|
||||
}
|
||||
|
||||
_statComponent = null;
|
||||
|
||||
if (transformToUnregister != null)
|
||||
if (transformToUnregister != null && GameEntry.SimulationWorld != null)
|
||||
{
|
||||
GameEntry.SimulationWorld.UnregisterPlayerMovement(transformToUnregister);
|
||||
}
|
||||
@@ -113,7 +117,7 @@ namespace SepCore.Components
|
||||
{
|
||||
using (CustomProfilerMarker.Movement_Update.Auto())
|
||||
{
|
||||
if (_cachedTransform == null)
|
||||
if (_cachedTransform == null || GameEntry.SimulationWorld == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -138,5 +142,13 @@ namespace SepCore.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnStatChanged(StatType statType, float newValue)
|
||||
{
|
||||
if (statType == StatType.MoveSpeed)
|
||||
{
|
||||
SyncToSimulationWorld();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using SepCore.Battle;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -7,8 +8,12 @@ namespace SepCore.Components
|
||||
{
|
||||
public class StatComponent : MonoBehaviour
|
||||
{
|
||||
private readonly Dictionary<StatType, List<modifier>> _statModifiers = new();
|
||||
private readonly Dictionary<StatType, Action<modifier, bool>> _statModifierCallbacks = new();
|
||||
private readonly AttributePipeline _pipeline = new();
|
||||
|
||||
/// <summary>
|
||||
/// 纯逻辑属性修饰器管线。
|
||||
/// </summary>
|
||||
public AttributePipeline Pipeline => _pipeline;
|
||||
|
||||
public void OnInit()
|
||||
{
|
||||
@@ -16,82 +21,42 @@ namespace SepCore.Components
|
||||
|
||||
public void OnReset()
|
||||
{
|
||||
_statModifiers.Clear();
|
||||
_statModifierCallbacks.Clear();
|
||||
_pipeline.Clear();
|
||||
}
|
||||
|
||||
public void AddModifier(modifier modifier)
|
||||
public void AddModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (!_statModifiers.TryGetValue(modifier.StatType, out var modifiers))
|
||||
{
|
||||
modifiers = new List<modifier>();
|
||||
_statModifiers.Add(modifier.StatType, modifiers);
|
||||
}
|
||||
modifiers.Add(modifier);
|
||||
|
||||
if (_statModifierCallbacks.TryGetValue(modifier.StatType, out var callbacks))
|
||||
callbacks?.Invoke(modifier, true);
|
||||
_pipeline.AddModifier(statType, modifier);
|
||||
}
|
||||
|
||||
public void RemoveModifier(modifier modifier)
|
||||
public bool RemoveModifier(StatType statType, StatModifier modifier)
|
||||
{
|
||||
if (_statModifiers.TryGetValue(modifier.StatType, out var modifiers))
|
||||
{
|
||||
modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
if (_statModifierCallbacks.TryGetValue(modifier.StatType, out var callbacks))
|
||||
callbacks?.Invoke(modifier, false);
|
||||
return _pipeline.RemoveModifier(statType, modifier);
|
||||
}
|
||||
|
||||
public void Subscribe(StatType statType, Action<modifier, bool> callback)
|
||||
public void AddModifier(StatModifierConfig statModifierConfig, object source = null)
|
||||
{
|
||||
if (!_statModifierCallbacks.TryAdd(statType, callback))
|
||||
{
|
||||
_statModifierCallbacks[statType] += callback;
|
||||
}
|
||||
_pipeline.AddModifier(statModifierConfig, source);
|
||||
}
|
||||
|
||||
public void Unsubscribe(StatType statType, Action<modifier, bool> callback)
|
||||
public void RemoveModifier(StatModifierConfig statModifierConfig, object source = null)
|
||||
{
|
||||
if (!_statModifierCallbacks.TryGetValue(statType, out var callbacks))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
callbacks -= callback;
|
||||
if (callbacks == null)
|
||||
{
|
||||
_statModifierCallbacks.Remove(statType);
|
||||
}
|
||||
_pipeline.RemoveModifier(statModifierConfig, source);
|
||||
}
|
||||
|
||||
public StatProperty GetStat(StatType statType)
|
||||
public int RemoveAllModifiersFromSource(object source)
|
||||
{
|
||||
var result = new StatProperty();
|
||||
|
||||
if (!_statModifiers.TryGetValue(statType, out var modifiers)) return result;
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
if (modifier.IsPercent) result.Percent += modifier.RealValue;
|
||||
else result.Value += modifier.RealValue;
|
||||
}
|
||||
|
||||
return result;
|
||||
return _pipeline.RemoveAllModifiersFromSource(source);
|
||||
}
|
||||
|
||||
public void UpdateStat(StatProperty stat, modifier modifier, bool isApply)
|
||||
public float GetStatValue(StatType statType)
|
||||
{
|
||||
float coef = isApply ? 1f : -1f;
|
||||
if (modifier.IsPercent)
|
||||
{
|
||||
stat.Percent += modifier.RealValue * coef;
|
||||
}
|
||||
else
|
||||
{
|
||||
stat.Value += modifier.RealValue * coef;
|
||||
}
|
||||
return _pipeline.GetValue(statType);
|
||||
}
|
||||
|
||||
public CharacterStat GetCharacterStat(StatType statType)
|
||||
{
|
||||
return _pipeline.Get(statType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user