mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 02:36:14 +00:00
提交GAS 打算做一个帧同步的GAS
This commit is contained in:
302
JEX_GAS/Assets/GAS/Runtime/Attribute/AttributeAggregator.cs
Normal file
302
JEX_GAS/Assets/GAS/Runtime/Attribute/AttributeAggregator.cs
Normal file
@@ -0,0 +1,302 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class AttributeAggregator
|
||||
{
|
||||
private record ModifierSpec
|
||||
{
|
||||
public EntityRef<GameplayEffectSpec> SpecRef { get; private set; }
|
||||
public GameplayEffectModifier Modifier { get; private set; }
|
||||
|
||||
public void Init(EntityRef<GameplayEffectSpec> spec, GameplayEffectModifier modifier)
|
||||
{
|
||||
SpecRef = spec;
|
||||
Modifier = modifier;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
SpecRef = default;
|
||||
Modifier = default;
|
||||
}
|
||||
}
|
||||
|
||||
AttributeBase _processedAttribute;
|
||||
AbilitySystemComponent _owner;
|
||||
|
||||
/// <summary>
|
||||
/// modifiers的顺序很重要,因为modifiers的执行是按照顺序来的。
|
||||
/// </summary>
|
||||
private readonly List<ModifierSpec> _modifierCache = new();
|
||||
|
||||
public AttributeAggregator(AttributeBase attribute, AbilitySystemComponent owner)
|
||||
{
|
||||
_processedAttribute = attribute;
|
||||
_owner = owner;
|
||||
|
||||
// OnEnable();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
_processedAttribute.RegisterPostBaseValueChange(UpdateCurrentValueWhenBaseValueIsDirty);
|
||||
_owner.GameplayEffectContainer.RegisterOnGameplayEffectContainerIsDirty(RefreshModifierCache);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
_processedAttribute.UnregisterPostBaseValueChange(UpdateCurrentValueWhenBaseValueIsDirty);
|
||||
_owner.GameplayEffectContainer.UnregisterOnGameplayEffectContainerIsDirty(RefreshModifierCache);
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
{
|
||||
ReleaseModifiersCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// it's triggered only when the owner's gameplay effect is added or removed.
|
||||
/// </summary>
|
||||
void RefreshModifierCache()
|
||||
{
|
||||
// UnityEngine.Profiling.Profiler.BeginSample("AttributeAggregator.RefreshModifierCache");
|
||||
|
||||
var isDirty = _modifierCache.Count > 0;
|
||||
|
||||
// 注销属性变化监听回调
|
||||
UnregisterAttributeChangedListen();
|
||||
ReleaseModifiersCache();
|
||||
|
||||
var gameplayEffects = _owner.GameplayEffectContainer.GameplayEffects();
|
||||
foreach (var geSpec in gameplayEffects)
|
||||
{
|
||||
if (geSpec.IsActive)
|
||||
{
|
||||
foreach (var modifier in geSpec.Modifiers)
|
||||
{
|
||||
if (modifier.AttributeName == _processedAttribute.Name)
|
||||
{
|
||||
var modifierSpec = ObjectPool.Instance.Fetch<ModifierSpec>();
|
||||
modifierSpec.Init(geSpec, modifier);
|
||||
_modifierCache.Add(modifierSpec);
|
||||
TryRegisterAttributeChangedListen(geSpec, modifier);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
isDirty = isDirty || _modifierCache.Count > 0;
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
UpdateCurrentValueWhenModifierIsDirty();
|
||||
}
|
||||
|
||||
// UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
private void ReleaseModifiersCache()
|
||||
{
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
modifierSpec.Release();
|
||||
ObjectPool.Instance.Recycle(modifierSpec);
|
||||
}
|
||||
|
||||
_modifierCache.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为CurrentValue计算新值。 (BaseValue的变化依赖于instant型GameplayEffect.)
|
||||
/// 这个方法的触发时机为:
|
||||
/// 1._modifierCache变化时
|
||||
/// 2._processedAttribute的BaseValue变化时
|
||||
/// 3._modifierCache的AttributeBased类的MMC,Track类属性变化时
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
float CalculateNewValue()
|
||||
{
|
||||
switch (_processedAttribute.CalculateMode)
|
||||
{
|
||||
case CalculateMode.Stacking:
|
||||
{
|
||||
float newValue = _processedAttribute.BaseValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
switch (modifier.Operation)
|
||||
{
|
||||
case GEOperation.Add:
|
||||
newValue += magnitude;
|
||||
break;
|
||||
case GEOperation.Minus:
|
||||
newValue -= magnitude;
|
||||
break;
|
||||
case GEOperation.Multiply:
|
||||
newValue *= magnitude;
|
||||
break;
|
||||
case GEOperation.Divide:
|
||||
newValue /= magnitude;
|
||||
break;
|
||||
case GEOperation.Override:
|
||||
newValue = magnitude;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return newValue;
|
||||
}
|
||||
case CalculateMode.MinValueOnly:
|
||||
{
|
||||
var hasOverride = false;
|
||||
var min = float.MaxValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
if (modifier.Operation != GEOperation.Override)
|
||||
{
|
||||
throw new InvalidOperationException("MinValueOnly mode only support override operation.");
|
||||
}
|
||||
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
min = Mathf.Min(min, magnitude);
|
||||
hasOverride = true;
|
||||
}
|
||||
|
||||
return hasOverride ? min : _processedAttribute.BaseValue;
|
||||
}
|
||||
case CalculateMode.MaxValueOnly:
|
||||
{
|
||||
var hasOverride = false;
|
||||
var max = float.MinValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
if (modifier.Operation != GEOperation.Override)
|
||||
{
|
||||
throw new InvalidOperationException("MaxValueOnly mode only support override operation.");
|
||||
}
|
||||
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
max = Mathf.Max(max, magnitude);
|
||||
hasOverride = true;
|
||||
}
|
||||
|
||||
return hasOverride ? max : _processedAttribute.BaseValue;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateCurrentValueWhenBaseValueIsDirty(AttributeBase attribute, float oldBaseValue, float newBaseValue)
|
||||
{
|
||||
if (Mathf.Approximately(oldBaseValue, newBaseValue)) return;
|
||||
|
||||
float newValue = CalculateNewValue();
|
||||
_processedAttribute.SetCurrentValue(newValue);
|
||||
}
|
||||
|
||||
void UpdateCurrentValueWhenModifierIsDirty()
|
||||
{
|
||||
float newValue = CalculateNewValue();
|
||||
_processedAttribute.SetCurrentValue(newValue);
|
||||
}
|
||||
|
||||
private void UnregisterAttributeChangedListen()
|
||||
{
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
TryUnregisterAttributeChangedListen(modifierSpec.SpecRef, modifierSpec.Modifier);
|
||||
}
|
||||
|
||||
private void TryUnregisterAttributeChangedListen(GameplayEffectSpec ge, GameplayEffectModifier modifier)
|
||||
{
|
||||
if (modifier.MMC is AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc)
|
||||
{
|
||||
if (mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target)
|
||||
{
|
||||
if (ge.Owner != null)
|
||||
ge.Owner.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ge.Source != null)
|
||||
ge.Source.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryRegisterAttributeChangedListen(GameplayEffectSpec ge, GameplayEffectModifier modifier)
|
||||
{
|
||||
if (modifier.MMC is AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc)
|
||||
{
|
||||
if (mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target)
|
||||
{
|
||||
if (ge.Owner != null)
|
||||
ge.Owner.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ge.Source != null)
|
||||
ge.Source.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttributeChanged(AttributeBase attribute, float oldValue, float newValue)
|
||||
{
|
||||
if (IsTrackingAttribute(attribute))
|
||||
UpdateCurrentValueWhenModifierIsDirty();
|
||||
}
|
||||
|
||||
private bool IsTrackingAttribute(AttributeBase attribute)
|
||||
{
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
if (modifierSpec.Modifier.MMC is not AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc) continue;
|
||||
if (attribute.Name != mmc.attributeName) continue;
|
||||
var geSpec = modifierSpec.SpecRef.Value;
|
||||
if (geSpec == null) continue;
|
||||
if ((mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target && attribute.Owner == geSpec.Owner) ||
|
||||
(mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Source && attribute.Owner == geSpec.Source))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b29927aa24734d46ab17f5ecdba14b7f
|
||||
timeCreated: 1703647437
|
175
JEX_GAS/Assets/GAS/Runtime/Attribute/AttributeBase.cs
Normal file
175
JEX_GAS/Assets/GAS/Runtime/Attribute/AttributeBase.cs
Normal file
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class AttributeBase
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string SetName;
|
||||
public readonly string ShortName;
|
||||
protected event Action<AttributeBase, float, float> _onPostCurrentValueChange;
|
||||
protected event Action<AttributeBase, float, float> _onPostBaseValueChange;
|
||||
protected event Action<AttributeBase, float> _onPreCurrentValueChange;
|
||||
protected event Func<AttributeBase, float, float> _onPreBaseValueChange;
|
||||
protected IEnumerable<Func<AttributeBase, float, float>> _preBaseValueChangeListeners;
|
||||
|
||||
private AttributeValue _value;
|
||||
private AbilitySystemComponent _owner;
|
||||
public AbilitySystemComponent Owner => _owner;
|
||||
|
||||
public AttributeBase(string attrSetName, string attrName, float value = 0,
|
||||
CalculateMode calculateMode = CalculateMode.Stacking,
|
||||
SupportedOperation supportedOperation = SupportedOperation.All,
|
||||
float minValue = float.MinValue, float maxValue = float.MaxValue)
|
||||
{
|
||||
SetName = attrSetName;
|
||||
Name = $"{attrSetName}.{attrName}";
|
||||
ShortName = attrName;
|
||||
_value = new AttributeValue(value, calculateMode, supportedOperation, minValue, maxValue);
|
||||
}
|
||||
|
||||
|
||||
public AttributeValue Value => _value;
|
||||
public float BaseValue => _value.BaseValue;
|
||||
public float CurrentValue => _value.CurrentValue;
|
||||
|
||||
public float MinValue => _value.MinValue;
|
||||
public float MaxValue => _value.MaxValue;
|
||||
|
||||
public CalculateMode CalculateMode => _value.CalculateMode;
|
||||
public SupportedOperation SupportedOperation => _value.SupportedOperation;
|
||||
|
||||
public void SetOwner(AbilitySystemComponent owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetMinValue(float min)
|
||||
{
|
||||
_value.SetMinValue(min);
|
||||
}
|
||||
|
||||
public void SetMaxValue(float max)
|
||||
{
|
||||
_value.SetMaxValue(max);
|
||||
}
|
||||
|
||||
public void SetMinMaxValue(float min, float max)
|
||||
{
|
||||
_value.SetMinValue(min);
|
||||
_value.SetMaxValue(max);
|
||||
}
|
||||
|
||||
public bool IsSupportOperation(GEOperation operation)
|
||||
{
|
||||
return _value.IsSupportOperation(operation);
|
||||
}
|
||||
|
||||
public void Init(float baseValue)
|
||||
{
|
||||
SetBaseValue(baseValue);
|
||||
SetCurrentValue(baseValue);
|
||||
}
|
||||
|
||||
public void SetCurrentValue(float value)
|
||||
{
|
||||
value = Mathf.Clamp(value, _value.MinValue, _value.MaxValue);
|
||||
|
||||
_onPreCurrentValueChange?.Invoke(this, value);
|
||||
|
||||
var oldValue = CurrentValue;
|
||||
_value.SetCurrentValue(value);
|
||||
|
||||
if (!Mathf.Approximately(oldValue, value))
|
||||
_onPostCurrentValueChange?.Invoke(this, oldValue, value);
|
||||
}
|
||||
|
||||
public void SetBaseValue(float value)
|
||||
{
|
||||
if (_onPreBaseValueChange != null)
|
||||
{
|
||||
value = InvokePreBaseValueChangeListeners(value);
|
||||
}
|
||||
|
||||
var oldValue = _value.BaseValue;
|
||||
_value.SetBaseValue(value);
|
||||
|
||||
if (!Mathf.Approximately(oldValue, value))
|
||||
_onPostBaseValueChange?.Invoke(this, oldValue, value);
|
||||
}
|
||||
|
||||
public void SetCurrentValueWithoutEvent(float value)
|
||||
{
|
||||
_value.SetCurrentValue(value);
|
||||
}
|
||||
|
||||
public void SetBaseValueWithoutEvent(float value)
|
||||
{
|
||||
_value.SetBaseValue(value);
|
||||
}
|
||||
|
||||
public void RegisterPreBaseValueChange(Func<AttributeBase, float, float> func)
|
||||
{
|
||||
_onPreBaseValueChange += func;
|
||||
_preBaseValueChangeListeners =
|
||||
_onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, float, float>>();
|
||||
}
|
||||
|
||||
public void RegisterPostBaseValueChange(Action<AttributeBase, float, float> action)
|
||||
{
|
||||
_onPostBaseValueChange += action;
|
||||
}
|
||||
|
||||
public void RegisterPreCurrentValueChange(Action<AttributeBase, float> action)
|
||||
{
|
||||
_onPreCurrentValueChange += action;
|
||||
}
|
||||
|
||||
public void RegisterPostCurrentValueChange(Action<AttributeBase, float, float> action)
|
||||
{
|
||||
_onPostCurrentValueChange += action;
|
||||
}
|
||||
|
||||
public void UnregisterPreBaseValueChange(Func<AttributeBase, float, float> func)
|
||||
{
|
||||
_onPreBaseValueChange -= func;
|
||||
_preBaseValueChangeListeners =
|
||||
_onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, float, float>>();
|
||||
}
|
||||
|
||||
public void UnregisterPostBaseValueChange(Action<AttributeBase, float, float> action)
|
||||
{
|
||||
_onPostBaseValueChange -= action;
|
||||
}
|
||||
|
||||
public void UnregisterPreCurrentValueChange(Action<AttributeBase, float> action)
|
||||
{
|
||||
_onPreCurrentValueChange -= action;
|
||||
}
|
||||
|
||||
public void UnregisterPostCurrentValueChange(Action<AttributeBase, float, float> action)
|
||||
{
|
||||
_onPostCurrentValueChange -= action;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_onPreBaseValueChange = null;
|
||||
_onPostBaseValueChange = null;
|
||||
_onPreCurrentValueChange = null;
|
||||
_onPostCurrentValueChange = null;
|
||||
}
|
||||
|
||||
private float InvokePreBaseValueChangeListeners(float value)
|
||||
{
|
||||
if (_preBaseValueChangeListeners == null) return value;
|
||||
|
||||
foreach (var t in _preBaseValueChangeListeners)
|
||||
value = t.Invoke(this, value);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d8de88e6f2be49758995d88978dfabee
|
||||
timeCreated: 1702375393
|
3
JEX_GAS/Assets/GAS/Runtime/Attribute/Value.meta
Normal file
3
JEX_GAS/Assets/GAS/Runtime/Attribute/Value.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f19c43eecd14c528e88f86e19fddde2
|
||||
timeCreated: 1702438986
|
77
JEX_GAS/Assets/GAS/Runtime/Attribute/Value/AttributeValue.cs
Normal file
77
JEX_GAS/Assets/GAS/Runtime/Attribute/Value/AttributeValue.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using Sirenix.OdinInspector;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public enum CalculateMode
|
||||
{
|
||||
[LabelText(SdfIconType.Stack, Text = "叠加计算")]
|
||||
Stacking,
|
||||
|
||||
[LabelText(SdfIconType.GraphDownArrow, Text = "取最小值")]
|
||||
MinValueOnly,
|
||||
|
||||
[LabelText(SdfIconType.GraphUpArrow, Text = "取最大值")]
|
||||
MaxValueOnly,
|
||||
}
|
||||
|
||||
public struct AttributeValue
|
||||
{
|
||||
public AttributeValue(float baseValue,
|
||||
CalculateMode calculateMode = CalculateMode.Stacking,
|
||||
SupportedOperation supportedOperation = SupportedOperation.All,
|
||||
float minValue = float.MinValue, float maxValue = float.MaxValue)
|
||||
{
|
||||
BaseValue = baseValue;
|
||||
SupportedOperation = supportedOperation;
|
||||
CurrentValue = baseValue;
|
||||
CalculateMode = calculateMode;
|
||||
MinValue = minValue;
|
||||
MaxValue = maxValue;
|
||||
}
|
||||
|
||||
public CalculateMode CalculateMode { get; }
|
||||
public SupportedOperation SupportedOperation { get; }
|
||||
|
||||
public float BaseValue { get; private set; }
|
||||
public float CurrentValue { get; private set; }
|
||||
|
||||
public float MinValue { get; private set; }
|
||||
public float MaxValue { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// ignore min and max value, set current value directly
|
||||
/// </summary>
|
||||
public void SetCurrentValue(float value)
|
||||
{
|
||||
CurrentValue = value;
|
||||
}
|
||||
|
||||
public void SetBaseValue(float value)
|
||||
{
|
||||
BaseValue = value;
|
||||
}
|
||||
|
||||
public void SetMinValue(float min)
|
||||
{
|
||||
MinValue = min;
|
||||
}
|
||||
|
||||
public void SetMaxValue(float max)
|
||||
{
|
||||
MaxValue = max;
|
||||
}
|
||||
|
||||
public void SetMinMaxValue(float min, float max)
|
||||
{
|
||||
MinValue = min;
|
||||
MaxValue = max;
|
||||
}
|
||||
|
||||
public bool IsSupportOperation(GEOperation operation)
|
||||
{
|
||||
// var isSupportOperation = SupportedOperation.HasFlag((SupportedOperation)(1 << (int)operation)); // Enum.HasFlag() 有很严重的GC!!!
|
||||
var isSupportOperation = ((byte)SupportedOperation & (1 << (byte)operation)) != 0;
|
||||
return isSupportOperation;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a08a20a74acc4f82b9de05a0bdeab4bc
|
||||
timeCreated: 1702436929
|
Reference in New Issue
Block a user