提交GAS 打算做一个帧同步的GAS

This commit is contained in:
DESKTOP-5RP3AKU\Jisol
2024-10-18 03:16:09 +08:00
parent b0a2e4a900
commit d9b0c78827
726 changed files with 76601 additions and 0 deletions

View 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类的MMCTrack类属性变化时
/// </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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b29927aa24734d46ab17f5ecdba14b7f
timeCreated: 1703647437

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d8de88e6f2be49758995d88978dfabee
timeCreated: 1702375393

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0f19c43eecd14c528e88f86e19fddde2
timeCreated: 1702438986

View 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;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a08a20a74acc4f82b9de05a0bdeab4bc
timeCreated: 1702436929