提交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,8 @@
namespace GAS.Runtime
{
public struct CooldownTimer
{
public float TimeRemaining;
public float Duration;
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 267174a9e0a0449e95cafbf86d476175
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c2be86a570234c578d2de13b42a0fd1d
timeCreated: 1702540748

View File

@@ -0,0 +1,7 @@
namespace GAS.Runtime
{
public class ExecutionCalculation
{
// TODO
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0af87f769c39444aa661e9f5859b72e9
timeCreated: 1702453899

View File

@@ -0,0 +1,182 @@
using System;
using System.Collections.Generic;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
public enum EffectsDurationPolicy
{
[LabelText("瞬时(Instant)", SdfIconType.LightningCharge)]
Instant = 1,
[LabelText("永久(Infinite)", SdfIconType.Infinity)]
Infinite,
[LabelText("限时(Duration)", SdfIconType.HourglassSplit)]
Duration
}
public enum GameplayEffectSnapshotPolicy
{
[LabelText("配置指定", SdfIconType.UiChecksGrid)]
Specified = 0,
[LabelText("施法者全部", SdfIconType.Magic)]
AllOfSource = 1,
[LabelText("持有者全部", SdfIconType.Person)]
AllOfTarget = 2,
[LabelText("我全都要", SdfIconType.People)]
AllOfBoth = 3,
}
public class GameplayEffect
{
public readonly string GameplayEffectName;
public readonly EffectsDurationPolicy DurationPolicy;
public readonly float Duration; // -1 represents infinite duration
public readonly float Period;
public readonly GameplayEffect PeriodExecution;
public readonly GameplayEffectTagContainer TagContainer;
// Snapshot
public readonly GameplayEffectSnapshotPolicy SnapshotPolicy;
public readonly GameplayEffectSpecifiedSnapshotConfig[] SpecifiedSnapshotConfigs;
// Cues
public readonly GameplayCueInstant[] CueOnExecute;
public readonly GameplayCueInstant[] CueOnRemove;
public readonly GameplayCueInstant[] CueOnAdd;
public readonly GameplayCueInstant[] CueOnActivate;
public readonly GameplayCueInstant[] CueOnDeactivate;
public readonly GameplayCueDurational[] CueDurational;
// Modifiers
public readonly GameplayEffectModifier[] Modifiers;
public readonly ExecutionCalculation[] Executions; // TODO: this should be a list of execution calculations
// Granted Ability
public readonly GrantedAbilityFromEffect[] GrantedAbilities;
//Stacking
public readonly GameplayEffectStacking Stacking;
// TODO: Expiration Effects
public readonly GameplayEffect[] PrematureExpirationEffect;
public readonly GameplayEffect[] RoutineExpirationEffectClasses;
public EntityRef<GameplayEffectSpec> CreateSpec(
AbilitySystemComponent creator,
AbilitySystemComponent owner,
float level = 1,
object userData = null)
{
var spec = ObjectPool.Instance.Fetch<GameplayEffectSpec>();
spec.Awake(this, userData);
spec.Init(creator, owner, level);
return spec;
}
/// <summary>
/// 分离GameplayEffectSpec的实例化过程为实例 + 数据初始化
/// </summary>
/// <returns></returns>
public EntityRef<GameplayEffectSpec> CreateSpec(object userData = null)
{
var spec = ObjectPool.Instance.Fetch<GameplayEffectSpec>();
spec.Awake(this, userData);
return spec;
}
public GameplayEffect(IGameplayEffectData data)
{
if (data is null)
{
throw new Exception($"GE data can't be null!");
}
GameplayEffectName = data.GetDisplayName();
DurationPolicy = data.GetDurationPolicy();
Duration = data.GetDuration();
Period = data.GetPeriod();
SnapshotPolicy = data.GetSnapshotPolicy();
SpecifiedSnapshotConfigs = data.GetSpecifiedSnapshotConfigs();
TagContainer = new GameplayEffectTagContainer(data);
var periodExecutionGe = data.GetPeriodExecution();
#if UNITY_EDITOR
if (periodExecutionGe != null && periodExecutionGe.GetDurationPolicy() != EffectsDurationPolicy.Instant)
{
Debug.LogError($"PeriodExecution of {GameplayEffectName} should be Instant type.");
}
#endif
PeriodExecution = periodExecutionGe != null ? new GameplayEffect(periodExecutionGe) : null;
CueOnExecute = data.GetCueOnExecute();
CueOnRemove = data.GetCueOnRemove();
CueOnAdd = data.GetCueOnAdd();
CueOnActivate = data.GetCueOnActivate();
CueOnDeactivate = data.GetCueOnDeactivate();
CueDurational = data.GetCueDurational();
Modifiers = data.GetModifiers();
Executions = data.GetExecutions();
GrantedAbilities = GetGrantedAbilities(data.GetGrantedAbilities());
Stacking = data.GetStacking();
}
public void Release()
{
PeriodExecution?.Release();
GrantedAbilityFromEffectArrayPool.Recycle(GrantedAbilities);
}
private static readonly ArrayPool<GrantedAbilityFromEffect> GrantedAbilityFromEffectArrayPool = new();
private static GrantedAbilityFromEffect[] GetGrantedAbilities(IReadOnlyCollection<GrantedAbilityConfig> grantedAbilities)
{
if (grantedAbilities.Count == 0)
{
return Array.Empty<GrantedAbilityFromEffect>();
}
var grantedAbilityFromEffects = ObjectPool.Instance.Fetch<List<GrantedAbilityFromEffect>>();
foreach (var grantedAbilityConfig in grantedAbilities)
{
if (grantedAbilityConfig.AbilityAsset != null)
grantedAbilityFromEffects.Add(new GrantedAbilityFromEffect(grantedAbilityConfig));
}
var ret = GrantedAbilityFromEffectArrayPool.Fetch(grantedAbilityFromEffects.Count);
grantedAbilityFromEffects.CopyTo(ret);
grantedAbilityFromEffects.Clear();
ObjectPool.Instance.Recycle(grantedAbilityFromEffects);
return ret;
}
public bool CanApplyTo(IAbilitySystemComponent target)
{
return target.HasAllTags(TagContainer.ApplicationRequiredTags);
}
public bool CanRunning(IAbilitySystemComponent target)
{
return target.HasAllTags(TagContainer.OngoingRequiredTags);
}
public bool IsImmune(IAbilitySystemComponent target)
{
return target.HasAnyTags(TagContainer.ApplicationImmunityTags);
}
public bool StackEqual(GameplayEffect effect)
{
if (Stacking.stackingType == StackingType.None) return false;
if (effect.Stacking.stackingType == StackingType.None) return false;
if (string.IsNullOrEmpty(Stacking.stackingCodeName)) return false;
if (string.IsNullOrEmpty(effect.Stacking.stackingCodeName)) return false;
return Stacking.stackingHashCode == effect.Stacking.stackingHashCode;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 0b37ff3cd6874a05b8fccbb09d2d17f4
timeCreated: 1701944543

View File

@@ -0,0 +1,397 @@
using System.Linq;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu(fileName = "GameplayEffect", menuName = "GAS/GameplayEffect")]
public class GameplayEffectAsset : ScriptableObject, IGameplayEffectData
{
private const string GRP_BASE = "Base";
private const string GRP_BASE_H = "Base/H";
private const string GRP_BASE_H_LEFT = "Base/H/Left";
private const string GRP_BASE_H_RIGHT = "Base/H/Right";
private const string GRP_DATA = "Data";
private const string GRP_DATA_H = "Data/H";
private const string GRP_DATA_TAG = "Data/H/Tags";
private const string GRP_DATA_CUE = "Data/H/Cues";
private const string GRP_DATA_H2 = "Data/H2";
private const string GRP_DATA_Snapshot = "Data/H2/Snapshot";
private const string GRP_DATA_MOD = "Data/H2/Modifiers";
private const string GRP_DATA_H3 = "Data/H3";
private const string GRP_DATA_STACK = "Data/H3/Stack";
private const string GRP_DATA_GRANTED_ABILITIES = "Data/H3/GrantedAbilities";
private const int WIDTH_LABEL = 70;
private const string ERROR_DURATION = "Duration must be > 0.";
private const string ERROR_GRANTED_ABILITY_INVALID = "存在无效的Ability!";
#region Base Info
[TitleGroup(GRP_BASE)]
[HorizontalGroup(GRP_BASE_H, Width = 1 - 0.618f)]
[TabGroup(GRP_BASE_H_LEFT, "Summary", SdfIconType.InfoSquareFill, TextColor = "#0BFFC5")]
[HideLabel]
[MultiLineProperty(5)]
public string Description;
#endregion Base Info
#region Policy
[HorizontalGroup(GRP_BASE_H)]
[TabGroup(GRP_BASE_H_RIGHT, "Policy", SdfIconType.AwardFill, TextColor = "#FF7F00")]
[PropertyOrder(1)]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_POLICY, SdfIconType.Diagram3Fill)]
[EnumToggleButtons]
public EffectsDurationPolicy DurationPolicy = EffectsDurationPolicy.Instant;
[TabGroup(GRP_BASE_H_RIGHT, "Policy")]
[PropertyOrder(2)]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_DURATION, SdfIconType.HourglassSplit)]
[EnableIf("@DurationPolicy == EffectsDurationPolicy.Duration")]
[Unit(Units.Second)]
[ValidateInput("@DurationPolicy != EffectsDurationPolicy.Duration || Duration > 0", ERROR_DURATION)]
public float Duration;
[ShowIf("@DurationPolicy != EffectsDurationPolicy.Duration")]
[TabGroup(GRP_BASE_H_RIGHT, "Policy")]
[PropertyOrder(3)]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_INTERVAL, SdfIconType.AlarmFill)]
[EnableIf("IsDurationalPolicy")]
[Unit(Units.Second)]
[ValidateInput("@DurationPolicy != EffectsDurationPolicy.Infinite || Period <= 0 || Period >= 0.01f", "Period < 0.01", InfoMessageType.Warning)]
public float Period;
[ShowIf("@DurationPolicy == EffectsDurationPolicy.Duration"),]
[TabGroup(GRP_BASE_H_RIGHT, "Policy")]
[PropertyOrder(3)]
[ShowInInspector]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_INTERVAL, SdfIconType.AlarmFill)]
[EnableIf("IsDurationalPolicy")]
[Unit(Units.Second)]
[PropertyRange(0, "@Duration")]
[ValidateInput("@DurationPolicy != EffectsDurationPolicy.Duration || Period <= 0 || Period >= 0.01f", "Period < 0.01", InfoMessageType.Warning)]
// 这个Property是为了给"限时型"效果绘制一个范围滑动条
public float PeriodForDurational
{
get => Period;
set => Period = value;
}
[TabGroup(GRP_BASE_H_RIGHT, "Policy")]
[PropertyOrder(4)]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_EXEC, SdfIconType.Magic)]
[EnableIf("IsPeriodic")]
[AssetSelector]
[ValidateInput("@IsPeriodic() ? (PeriodExecution != null && PeriodExecution.DurationPolicy == EffectsDurationPolicy.Instant) : true", "非空, 瞬时效果")]
public GameplayEffectAsset PeriodExecution;
#endregion Policy
#region Stack
[TitleGroup(GRP_DATA)]
[HorizontalGroup(GRP_DATA_H3, order: 3, Width = 1 - 0.618f)]
[TabGroup(GRP_DATA_STACK, "Stacking", SdfIconType.Stack, TextColor = "#9B4AE3", Order = 1)]
[HideLabel]
[EnableIf("IsDurationalPolicy")]
[InfoBox("瞬时效果无法叠加", InfoMessageType.None, VisibleIf = "@IsInstantPolicy()")]
public GameplayEffectStackingConfig Stacking;
#if UNITY_EDITOR
[TabGroup(GRP_DATA_STACK, "Stacking")]
[ShowIf("@IsDurationalPolicy() && Stacking.stackingType != StackingType.None")]
[Button("使用资产名称作为堆叠识别码", ButtonSizes.Medium, Icon = SdfIconType.Hammer)]
private void SetStackingCodeNameAsAssetName()
{
var stacking = Stacking;
stacking.stackingCodeName = name;
Stacking = stacking;
}
#endif
#endregion Stack
#region Granted Abilities
[TabGroup(GRP_DATA_GRANTED_ABILITIES, "Granted Abilities", SdfIconType.YinYang, TextColor = "#D6626E", Order = 2)]
[EnableIf("IsDurationalPolicy")]
[InfoBox("瞬时效果无法赋予能力", InfoMessageType.None, VisibleIf = "@IsInstantPolicy()")]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@SortGrantedAbilities()")]
[ValidateInput("@IsGrantedAbilitiesInvalid() ? false : true", ERROR_GRANTED_ABILITY_INVALID)]
public GrantedAbilityConfig[] GrantedAbilities;
private void SortGrantedAbilities()
{
GrantedAbilities = GrantedAbilities?.OrderBy(abilityConfig => abilityConfig.AbilityAsset.name).ToArray();
}
#endregion Granted Abilities
#region Modifiers
[TabGroup(GRP_DATA_MOD, "Modifiers", SdfIconType.CalculatorFill, TextColor = "#FFE60B", Order = 2)]
[LabelText(@"@IsInstantPolicy() ? ""依次执行(仅在成功应用时)"" : ""依次执行(每次激活时)""")]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false)]
[ValidateInput("@ValidateInput_Modifiers()", "瞬时效果只能修改Stacking类型的属性")]
public GameplayEffectModifier[] Modifiers;
bool ValidateInput_Modifiers()
{
if (!IsInstantPolicy()) return true;
if (Modifiers == null) return true;
return Modifiers.All(modifier =>
{
var attributeBase = ReflectionHelper.GetAttribute(modifier.AttributeName);
if (attributeBase != null)
{
return attributeBase.CalculateMode == CalculateMode.Stacking;
}
return true;
});
}
#endregion Modifiers
#region Tags
[ShowIf("IsDurationalPolicy")]
[HorizontalGroup(GRP_DATA_H, order: 1, Width = 1 - 0.618f)]
[TabGroup(GRP_DATA_TAG, "Tags", SdfIconType.TagsFill, TextColor = "#45B1FF", Order = 1)]
[LabelText(GASTextDefine.TITLE_GE_TAG_AssetTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_AssetTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@AssetTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] AssetTags;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_TAG, "Tags")]
[LabelText(GASTextDefine.TITLE_GE_TAG_GrantedTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_GrantedTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@GrantedTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] GrantedTags;
[Space]
[TabGroup(GRP_DATA_TAG, "Tags")]
[LabelText(GASTextDefine.TITLE_GE_TAG_ApplicationRequiredTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_ApplicationRequiredTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@ApplicationRequiredTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] ApplicationRequiredTags;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_TAG, "Tags")]
[LabelText(GASTextDefine.TITLE_GE_TAG_OngoingRequiredTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_OngoingRequiredTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@OngoingRequiredTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] OngoingRequiredTags;
[Space]
[TabGroup(GRP_DATA_TAG, "Tags")]
[LabelText(GASTextDefine.TITLE_GE_TAG_RemoveGameplayEffectsWithTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_RemoveGameplayEffectsWithTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@RemoveGameplayEffectsWithTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] RemoveGameplayEffectsWithTags;
[Space]
[TabGroup(GRP_DATA_TAG, "Tags")]
[LabelText(GASTextDefine.TITLE_GE_TAG_ApplicationImmunityTags)]
[Tooltip(GASTextDefine.TIP_GE_TAG_ApplicationImmunityTags)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@ApplicationImmunityTags = TagHelper.Sort($value)")]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
public GameplayTag[] ApplicationImmunityTags;
#endregion Tags
#region Cues
[ShowIf("IsInstantPolicy")]
[TabGroup(GRP_DATA_CUE, "Cues", SdfIconType.Stars, TextColor = "#00FFFF", Order = 3)]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueOnExecute)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueInstant[] CueOnExecute;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_CUE, "Cues")]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueDurational)]
[Tooltip("生命周期完全和GameplayEffect同步")]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueDurational[] CueDurational;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_CUE, "Cues")]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueOnAdd)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueInstant[] CueOnAdd;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_CUE, "Cues")]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueOnRemove)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueInstant[] CueOnRemove;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_CUE, "Cues")]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueOnActivate)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueInstant[] CueOnActivate;
[ShowIf("IsDurationalPolicy")]
[Space]
[TabGroup(GRP_DATA_CUE, "Cues")]
[LabelText(GASTextDefine.TITLE_GE_CUE_CueOnDeactivate)]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[AssetSelector]
public GameplayCueInstant[] CueOnDeactivate;
#endregion Cues
#region Snapshot
[HorizontalGroup(GRP_DATA_H2, order: 2, Width = 1 - 0.618f)]
[TabGroup(GRP_DATA_Snapshot, "Snapshots", SdfIconType.Camera, TextColor = "#FF7F00", Order = 1)]
[LabelWidth(WIDTH_LABEL)]
[LabelText(GASTextDefine.LABLE_GE_SnapshotPolicy, SdfIconType.Camera)]
[EnumToggleButtons]
public GameplayEffectSnapshotPolicy SnapshotPolicy = GameplayEffectSnapshotPolicy.Specified;
[ShowIf("@SnapshotPolicy == GameplayEffectSnapshotPolicy.Specified")]
[TabGroup(GRP_DATA_Snapshot, "Snapshots")]
[LabelText("需要快照的属性")]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
public GameplayEffectSpecifiedSnapshotConfig[] SpecifiedSnapshotConfigs;
#endregion Snapshot
// TODO
[HideInInspector]
public ExecutionCalculation[] Executions;
bool IsPeriodic() => IsDurationalPolicy() && Period > 0;
bool IsDurationalPolicy() => DurationPolicy == EffectsDurationPolicy.Duration || DurationPolicy == EffectsDurationPolicy.Infinite;
bool IsInstantPolicy() => DurationPolicy == EffectsDurationPolicy.Instant;
bool IsGrantedAbilitiesInvalid() => IsDurationalPolicy() && GrantedAbilities != null && GrantedAbilities.Any(abilityConfig => abilityConfig.AbilityAsset == null);
#region IGameplayEffectData
public string GetDisplayName() => name;
public EffectsDurationPolicy GetDurationPolicy() => DurationPolicy;
public float GetDuration() => Duration;
public float GetPeriod() => Period;
public IGameplayEffectData GetPeriodExecution() => PeriodExecution;
public GameplayEffectSnapshotPolicy GetSnapshotPolicy() => SnapshotPolicy;
public GameplayEffectSpecifiedSnapshotConfig[] GetSpecifiedSnapshotConfigs() => SpecifiedSnapshotConfigs;
public GameplayTag[] GetAssetTags() => AssetTags;
public GameplayTag[] GetGrantedTags() => GrantedTags;
public GameplayTag[] GetApplicationRequiredTags() => ApplicationRequiredTags;
public GameplayTag[] GetOngoingRequiredTags() => OngoingRequiredTags;
public GameplayTag[] GetRemoveGameplayEffectsWithTags() => RemoveGameplayEffectsWithTags;
public GameplayTag[] GetApplicationImmunityTags() => ApplicationImmunityTags;
public GameplayCueInstant[] GetCueOnExecute() => CueOnExecute;
public GameplayCueInstant[] GetCueOnRemove() => CueOnRemove;
public GameplayCueInstant[] GetCueOnAdd() => CueOnAdd;
public GameplayCueInstant[] GetCueOnActivate() => CueOnActivate;
public GameplayCueInstant[] GetCueOnDeactivate() => CueOnDeactivate;
public GameplayCueDurational[] GetCueDurational() => CueDurational;
public GameplayEffectModifier[] GetModifiers() => Modifiers;
public ExecutionCalculation[] GetExecutions() => Executions;
public GrantedAbilityConfig[] GetGrantedAbilities() => GrantedAbilities;
public GameplayEffectStacking GetStacking() => Stacking.ToRuntimeData();
#endregion IGameplayEffectData
#region shared GameplayEffect instance
/// <summary>
/// 共享实例, 一个GameplayEffectAsset对应一个共享实例, 首次访问时创建
/// <remarks>
/// <para>优点: 通过共享实例, 可以减少GameplayEffect的实例化次数, 减少内存开销, 同时也可以减少GC的产生, 提高性能</para>
/// <para>缺点: Editor下实时修改GameplayEffectAsset无法实时生效, 因为共享实例一旦创建, 就不会再改变, 可以设置GasRuntimeSettings.DisableGameplayEffectSharedInstance来禁用Editor模式下的SharedInstance</para>
/// </remarks>
/// </summary>
public GameplayEffect SharedInstance
{
get
{
#if UNITY_EDITOR
if (GasRuntimeSettings.DisableGameplayEffectSharedInstance)
return new GameplayEffect(this);
#endif
return _sharedInstance ??= new GameplayEffect(this);
}
}
private GameplayEffect _sharedInstance;
#endregion shared GameplayEffect instance
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b9395a0be3e547f48bd1c8320edc6c58
timeCreated: 1703260426

View File

@@ -0,0 +1,330 @@
using System;
using System.Collections.Generic;
using GAS.General;
using UnityEngine;
namespace GAS.Runtime
{
public class GameplayEffectContainer
{
private readonly AbilitySystemComponent _owner;
private readonly List<GameplayEffectSpec> _gameplayEffectSpecs = new();
public GameplayEffectContainer(AbilitySystemComponent owner)
{
_owner = owner;
}
private event Action OnGameplayEffectContainerIsDirty;
public List<GameplayEffectSpec> GameplayEffects()
{
return _gameplayEffectSpecs;
}
public void Tick()
{
var gameplayEffectSpecs = ObjectPool.Instance.Fetch<List<GameplayEffectSpec>>();
gameplayEffectSpecs.AddRange(_gameplayEffectSpecs);
foreach (var gameplayEffectSpec in gameplayEffectSpecs)
{
if (gameplayEffectSpec.IsActive)
{
gameplayEffectSpec.Tick();
}
}
gameplayEffectSpecs.Clear();
ObjectPool.Instance.Recycle(gameplayEffectSpecs);
}
public void RegisterOnGameplayEffectContainerIsDirty(Action action)
{
OnGameplayEffectContainerIsDirty += action;
}
public void UnregisterOnGameplayEffectContainerIsDirty(Action action)
{
OnGameplayEffectContainerIsDirty -= action;
}
public void RemoveGameplayEffectWithAnyTags(in GameplayTagSet tags)
{
if (tags.Empty) return;
var removeList = ObjectPool.Instance.Fetch<List<GameplayEffectSpec>>();
foreach (var gameplayEffectSpec in _gameplayEffectSpecs)
{
var assetTags = gameplayEffectSpec.GameplayEffect.TagContainer.AssetTags;
if (!assetTags.Empty && assetTags.HasAnyTags(tags))
{
removeList.Add(gameplayEffectSpec);
continue;
}
var grantedTags = gameplayEffectSpec.GameplayEffect.TagContainer.GrantedTags;
if (!grantedTags.Empty && grantedTags.HasAnyTags(tags)) removeList.Add(gameplayEffectSpec);
}
foreach (var gameplayEffectSpec in removeList) RemoveGameplayEffectSpec(gameplayEffectSpec);
removeList.Clear();
ObjectPool.Instance.Recycle(removeList);
}
/// <summary>
/// </summary>
/// <returns>
/// 返回实际的GameplayEffectSpec(不一定是传入的Spec, 如果堆叠成功返回的是被堆叠的初始Spec),
/// 如果返回null, 要么是一次性生效的, 要么是应用失败的(比如被免疫)
/// </returns>
public EntityRef<GameplayEffectSpec> AddGameplayEffectSpec(AbilitySystemComponent source, EntityRef<GameplayEffectSpec> effectSpecRef, bool overwriteEffectLevel = false, int effectLevel = 0)
{
var effectSpec = effectSpecRef.Value;
if (effectSpec == null)
return null;
if (!effectSpec.GameplayEffect.CanApplyTo(_owner))
{
effectSpec.Recycle();
return null;
}
if (effectSpec.GameplayEffect.IsImmune(_owner))
{
var level = overwriteEffectLevel ? effectLevel : source.Level;
effectSpec.Init(source, _owner, level);
effectSpec.TriggerOnImmunity();
effectSpec.Recycle();
return null;
}
if (effectSpec.DurationPolicy == EffectsDurationPolicy.Instant)
{
var level = overwriteEffectLevel ? effectLevel : source.Level;
effectSpec.Init(source, _owner, level);
effectSpec.TriggerOnExecute();
effectSpec.Recycle();
return null;
}
// Check GE Stacking
// 处理GE堆叠
switch (effectSpec.Stacking.stackingType)
{
case StackingType.None:
{
Operation_AddNewGameplayEffectSpec(source, ref effectSpecRef, overwriteEffectLevel, effectLevel);
return effectSpecRef;
}
case StackingType.AggregateByTarget:
{
GetStackingEffectSpecByData(effectSpec.GameplayEffect, out var geSpec);
if (geSpec == null)
{
Operation_AddNewGameplayEffectSpec(source, ref effectSpecRef, overwriteEffectLevel, effectLevel);
return effectSpecRef;
}
bool stackCountChange = geSpec.RefreshStack();
if (stackCountChange) OnRefreshStackCountMakeContainerDirty();
effectSpec.Recycle();
return geSpec.IsApplied ? geSpec : null;
}
case StackingType.AggregateBySource:
{
GetStackingEffectSpecByDataFrom(effectSpec.GameplayEffect, source, out var geSpec);
if (geSpec == null)
{
Operation_AddNewGameplayEffectSpec(source, ref effectSpecRef, overwriteEffectLevel, effectLevel);
return effectSpecRef;
}
bool stackCountChange = geSpec.RefreshStack();
if (stackCountChange) OnRefreshStackCountMakeContainerDirty();
effectSpec.Recycle();
return geSpec.IsApplied ? geSpec : null;
}
default:
{
Debug.LogError("Unsupported StackingType: " + effectSpec.Stacking.stackingType);
effectSpec.Recycle();
return null;
}
}
}
public void RemoveGameplayEffectSpec(GameplayEffectSpec spec)
{
if (spec == null)
{
#if UNITY_EDITOR
Debug.LogWarning("the GameplayEffectSpec you want to remove is null!");
#endif
return;
}
spec.DisApply();
spec.TriggerOnRemove();
_gameplayEffectSpecs.Remove(spec);
spec.Recycle();
OnGameplayEffectContainerIsDirty?.Invoke();
}
public void RemoveGameplayEffectSpec(in EntityRef<GameplayEffectSpec> gameplayEffectSpecRef)
{
var gameplayEffectSpec = gameplayEffectSpecRef.Value;
if (gameplayEffectSpec == null)
{
#if UNITY_EDITOR
Debug.LogWarning("the EntityRef of GameplayEffectSpec is Invalid!");
#endif
return;
}
RemoveGameplayEffectSpec(gameplayEffectSpec);
}
public void RefreshGameplayEffectState()
{
bool isDirty = false;
foreach (var gameplayEffectSpec in _gameplayEffectSpecs)
{
if (gameplayEffectSpec.IsApplied)
{
if (gameplayEffectSpec.IsActive)
{
if (!gameplayEffectSpec.GameplayEffect.CanRunning(_owner))
{
isDirty = true;
gameplayEffectSpec.Deactivate();
}
}
else
{
if (gameplayEffectSpec.GameplayEffect.CanRunning(_owner))
{
isDirty = true;
gameplayEffectSpec.Activate();
}
}
}
}
if (isDirty)
{
OnGameplayEffectContainerIsDirty?.Invoke();
}
}
public CooldownTimer CheckCooldownFromTags(in GameplayTagSet tags)
{
float longestCooldown = 0;
float maxDuration = 0;
// Check if the cooldown tag is granted to the player, and if so, capture the remaining duration for that tag
foreach (var spec in _gameplayEffectSpecs)
{
if (spec.IsActive)
{
var grantedTags = spec.GameplayEffect.TagContainer.GrantedTags;
if (grantedTags.Empty) continue;
foreach (var t in grantedTags.Tags)
foreach (var targetTag in tags.Tags)
{
if (t != targetTag) continue;
// If this is an infinite GE, then return null to signify this is on CD
if (spec.GameplayEffect.DurationPolicy == EffectsDurationPolicy.Infinite)
return new CooldownTimer { TimeRemaining = -1, Duration = 0 };
var durationRemaining = spec.DurationRemaining();
if (!(durationRemaining > longestCooldown)) continue;
longestCooldown = durationRemaining;
maxDuration = spec.Duration;
}
}
}
return new CooldownTimer { TimeRemaining = longestCooldown, Duration = maxDuration };
}
public void ClearGameplayEffect()
{
bool isDirty = _gameplayEffectSpecs.Count > 0;
foreach (var gameplayEffectSpec in _gameplayEffectSpecs)
{
gameplayEffectSpec.DisApply();
gameplayEffectSpec.TriggerOnRemove();
gameplayEffectSpec.Recycle();
}
_gameplayEffectSpecs.Clear();
if (isDirty)
{
OnGameplayEffectContainerIsDirty?.Invoke();
}
}
private void GetStackingEffectSpecByData(GameplayEffect effect, out GameplayEffectSpec spec)
{
foreach (var gameplayEffectSpec in _gameplayEffectSpecs)
if (gameplayEffectSpec.GameplayEffect.StackEqual(effect))
{
spec = gameplayEffectSpec;
return;
}
spec = null;
}
private void GetStackingEffectSpecByDataFrom(GameplayEffect effect, AbilitySystemComponent source,
out GameplayEffectSpec spec)
{
foreach (var gameplayEffectSpec in _gameplayEffectSpecs)
if (gameplayEffectSpec.Source == source &&
gameplayEffectSpec.GameplayEffect.StackEqual(effect))
{
spec = gameplayEffectSpec;
return;
}
spec = null;
}
private void OnRefreshStackCountMakeContainerDirty()
{
OnGameplayEffectContainerIsDirty?.Invoke();
}
private void Operation_AddNewGameplayEffectSpec(AbilitySystemComponent source, ref EntityRef<GameplayEffectSpec> effectSpecRef, bool overwriteEffectLevel, int effectLevel)
{
var effectSpec = effectSpecRef.Value;
var level = overwriteEffectLevel ? effectLevel : source.Level;
effectSpec.Init(source, _owner, level);
_gameplayEffectSpecs.Add(effectSpec);
effectSpec.TriggerOnAdd();
effectSpec.Apply();
// If the gameplay effect was removed immediately after being applied, return false
if (effectSpec.IsApplied == false)
{
#if UNITY_EDITOR
Debug.LogWarning(
$"GameplayEffect {effectSpec.GameplayEffect.GameplayEffectName} was removed immediately after being applied. This may indicate a problem with the RemoveGameplayEffectsWithTags.");
#endif
// No need to trigger OnGameplayEffectContainerIsDirty, it has already been triggered when it was removed.
return;
}
OnGameplayEffectContainerIsDirty?.Invoke();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4fd84391fd9443178481ac7d664cbcc1
timeCreated: 1703237216

View File

@@ -0,0 +1,127 @@
using System;
namespace GAS.Runtime
{
public class InstantGameplayEffectData : IGameplayEffectData
{
private string Name { get; }
public GameplayEffectSnapshotPolicy SnapshotPolicy { get; set; } = GameplayEffectSnapshotPolicy.Specified;
public GameplayTag[] ApplicationRequiredTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayTag[] ApplicationImmunityTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayTag[] RemoveGameplayEffectsWithTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayCueInstant[] CueOnExecute { get; set; } = Array.Empty<GameplayCueInstant>();
public GameplayEffectModifier[] Modifiers { get; set; } = Array.Empty<GameplayEffectModifier>();
public GameplayEffectSpecifiedSnapshotConfig[] SpecifiedSnapshotConfigs { get; set; } = Array.Empty<GameplayEffectSpecifiedSnapshotConfig>();
public InstantGameplayEffectData(string name) => Name = name;
public string GetDisplayName() => Name;
public virtual EffectsDurationPolicy GetDurationPolicy() => EffectsDurationPolicy.Instant;
public virtual float GetDuration() => -1;
public virtual float GetPeriod() => 0;
public virtual IGameplayEffectData GetPeriodExecution() => null;
public GameplayEffectSnapshotPolicy GetSnapshotPolicy() => SnapshotPolicy;
public GameplayEffectSpecifiedSnapshotConfig[] GetSpecifiedSnapshotConfigs() => SpecifiedSnapshotConfigs;
public virtual GameplayTag[] GetAssetTags() => Array.Empty<GameplayTag>();
public virtual GameplayTag[] GetGrantedTags() => Array.Empty<GameplayTag>();
public GameplayTag[] GetRemoveGameplayEffectsWithTags() => RemoveGameplayEffectsWithTags;
public GameplayTag[] GetApplicationRequiredTags() => ApplicationRequiredTags;
public GameplayTag[] GetApplicationImmunityTags() => ApplicationImmunityTags;
public virtual GameplayTag[] GetOngoingRequiredTags() => Array.Empty<GameplayTag>();
public GameplayCueInstant[] GetCueOnExecute() => CueOnExecute;
public virtual GameplayCueInstant[] GetCueOnRemove() => Array.Empty<GameplayCueInstant>();
public virtual GameplayCueInstant[] GetCueOnAdd() => Array.Empty<GameplayCueInstant>();
public virtual GameplayCueInstant[] GetCueOnActivate() => Array.Empty<GameplayCueInstant>();
public virtual GameplayCueInstant[] GetCueOnDeactivate() => Array.Empty<GameplayCueInstant>();
public virtual GameplayCueDurational[] GetCueDurational() => Array.Empty<GameplayCueDurational>();
public GameplayEffectModifier[] GetModifiers() => Modifiers;
public virtual ExecutionCalculation[] GetExecutions() => Array.Empty<ExecutionCalculation>();
public virtual GrantedAbilityConfig[] GetGrantedAbilities() => Array.Empty<GrantedAbilityConfig>();
public virtual GameplayEffectStacking GetStacking() => GameplayEffectStacking.None;
}
public class InfiniteGameplayEffectData : InstantGameplayEffectData
{
public float Period { get; }
public IGameplayEffectData PeriodExecution { get; set; } = null;
public GameplayTag[] AssetTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayTag[] GrantedTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayTag[] OngoingRequiredTags { get; set; } = Array.Empty<GameplayTag>();
public GameplayCueInstant[] CueOnRemove { get; set; } = Array.Empty<GameplayCueInstant>();
public GameplayCueInstant[] CueOnAdd { get; set; } = Array.Empty<GameplayCueInstant>();
public GameplayCueInstant[] CueOnActivate { get; set; } = Array.Empty<GameplayCueInstant>();
public GameplayCueInstant[] CueOnDeactivate { get; set; } = Array.Empty<GameplayCueInstant>();
public GameplayCueDurational[] CueDurational { get; set; } = Array.Empty<GameplayCueDurational>();
public ExecutionCalculation[] Executions { get; set; } = Array.Empty<ExecutionCalculation>();
public GrantedAbilityConfig[] GrantedAbilities { get; set; } = Array.Empty<GrantedAbilityConfig>();
public GameplayEffectStacking Stacking { get; set; } = GameplayEffectStacking.None;
public InfiniteGameplayEffectData(string name, float period) : base(name) => Period = period;
public override EffectsDurationPolicy GetDurationPolicy() => EffectsDurationPolicy.Infinite;
public override float GetPeriod() => Period;
public override IGameplayEffectData GetPeriodExecution() => PeriodExecution;
public override GameplayTag[] GetAssetTags() => AssetTags;
public override GameplayTag[] GetGrantedTags() => GrantedTags;
public override GameplayTag[] GetOngoingRequiredTags() => OngoingRequiredTags;
public override GameplayCueInstant[] GetCueOnRemove() => CueOnRemove;
public override GameplayCueInstant[] GetCueOnAdd() => CueOnAdd;
public override GameplayCueInstant[] GetCueOnActivate() => CueOnActivate;
public override GameplayCueInstant[] GetCueOnDeactivate() => CueOnDeactivate;
public override GameplayCueDurational[] GetCueDurational() => CueDurational;
public override ExecutionCalculation[] GetExecutions() => Executions;
public override GrantedAbilityConfig[] GetGrantedAbilities() => GrantedAbilities;
public override GameplayEffectStacking GetStacking() => Stacking;
}
public class DurationalGameplayEffectData : InfiniteGameplayEffectData
{
public float Duration { get; }
public DurationalGameplayEffectData(string name, float period, float duration) : base(name, period) => Duration = duration;
public override EffectsDurationPolicy GetDurationPolicy() => EffectsDurationPolicy.Duration;
public override float GetDuration() => Duration;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: eb31bfbad2914a539f07afdfb9d6afda
timeCreated: 1715604792

View File

@@ -0,0 +1,113 @@
using UnityEngine;
namespace GAS.Runtime
{
internal sealed class GameplayEffectPeriodTicker : IEntity
{
public ulong InstanceId { get; private set; }
private float _periodRemaining;
private GameplayEffectSpec _spec;
public void Awake(GameplayEffectSpec spec)
{
InstanceId = IdGenerator.Next;
_spec = spec;
_periodRemaining = Period;
}
public void Release()
{
InstanceId = default;
_spec = default;
_periodRemaining = default;
}
private float Period => _spec.GameplayEffect.Period;
public void Tick()
{
_spec.TriggerOnTick();
UpdatePeriod();
if (_spec.DurationPolicy == EffectsDurationPolicy.Duration && _spec.DurationRemaining() <= 0)
{
// 处理STACKING
if (_spec.GameplayEffect.Stacking.stackingType == StackingType.None)
{
_spec.RemoveSelf();
}
else
{
if (_spec.GameplayEffect.Stacking.expirationPolicy == ExpirationPolicy.ClearEntireStack)
{
_spec.RemoveSelf();
}
else if (_spec.GameplayEffect.Stacking.expirationPolicy ==
ExpirationPolicy.RemoveSingleStackAndRefreshDuration)
{
if (_spec.StackCount > 1)
{
_spec.RefreshStack(_spec.StackCount - 1);
_spec.RefreshDuration();
}
else
{
_spec.RemoveSelf();
}
}
else if (_spec.GameplayEffect.Stacking.expirationPolicy == ExpirationPolicy.RefreshDuration)
{
//持续时间结束时,再次刷新Duration这相当于无限Duration
_spec.RefreshDuration();
}
}
}
}
/// <summary>
/// 注意: Period 小于 0.01f 可能出现误差, 基本够用了
/// </summary>
private void UpdatePeriod()
{
// 前提: Period不会动态修改
if (Period <= 0) return;
var actualDuration = Time.time - _spec.ActivationTime;
if (actualDuration < Mathf.Epsilon)
{
// 第一次执行
return;
}
var dt = Time.deltaTime;
if (_spec.DurationPolicy == EffectsDurationPolicy.Duration)
{
var excessDuration = actualDuration - _spec.Duration;
if (excessDuration >= 0)
{
// 如果超出了持续时间,就减去超出的时间, 此时应该是最后一次执行
dt -= excessDuration;
// 为了避免误差, 保证最后一次边界得到执行机会
dt += 0.0001f;
}
}
_periodRemaining -= dt;
while (_periodRemaining < 0)
{
// 不能直接将_periodRemaining重置为Period, 这将累计误差
_periodRemaining += Period;
_spec.PeriodExecution.Value?.TriggerOnExecute();
}
}
public void ResetPeriod()
{
_periodRemaining = Period;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f128ba6062db4f6d9a7b3169faaf9846
timeCreated: 1702545119

View File

@@ -0,0 +1,702 @@
using System;
using System.Collections.Generic;
using GAS.General;
using UnityEngine;
namespace GAS.Runtime
{
/// <summary>
/// 注意: 永远不要直接持有对GameplayEffectSpec的引用, 用EntityRef代替, 否则当它回收入池再次使用时会出现问题
/// </summary>
public sealed class GameplayEffectSpec : IEntity, IPool
{
private Dictionary<GameplayTag, float> _valueMapWithTag;
private Dictionary<string, float> _valueMapWithName;
private List<GameplayCueDurationalSpec> _cueDurationalSpecs;
public object UserData { get; set; }
private event Action<AbilitySystemComponent, GameplayEffectSpec> OnImmunityBlock;
private event Action<int, int> OnStackChanged;
public ulong InstanceId { get; private set; }
public bool IsFromPool { get; set; }
public void Awake(GameplayEffect gameplayEffect, object userData = null)
{
InstanceId = IdGenerator.Next;
GameplayEffect = gameplayEffect;
UserData = userData;
Duration = GameplayEffect.Duration;
DurationPolicy = GameplayEffect.DurationPolicy;
Stacking = GameplayEffect.Stacking;
Modifiers = GameplayEffect.Modifiers;
if (gameplayEffect.DurationPolicy != EffectsDurationPolicy.Instant)
{
var periodTicker = ObjectPool.Instance.Fetch<GameplayEffectPeriodTicker>();
periodTicker.Awake(this);
// EntityRef之前必须确定InstanceId的值
PeriodTicker = periodTicker;
}
}
public void Recycle()
{
if (InstanceId != 0)
{
InstanceId = 0;
GameplayEffect = default;
ActivationTime = default;
Level = default;
Source = default;
Owner = default;
IsApplied = default;
IsActive = default;
var gameplayEffectPeriodTicker = PeriodTicker.Value;
if (gameplayEffectPeriodTicker != null)
{
gameplayEffectPeriodTicker.Release();
ObjectPool.Instance.Recycle(gameplayEffectPeriodTicker);
}
PeriodTicker = default;
Duration = default;
DurationPolicy = default;
PeriodExecution.Value?.Recycle();
PeriodExecution = default;
Modifiers = default;
if (GrantedAbilitiesSpecFromEffect != null)
{
foreach (GrantedAbilitySpecFromEffect grantedAbilitySpecFromEffect in GrantedAbilitiesSpecFromEffect)
{
if (grantedAbilitySpecFromEffect != null)
{
grantedAbilitySpecFromEffect.Release();
ObjectPool.Instance.Recycle(grantedAbilitySpecFromEffect);
}
}
GrantedAbilitiesSpecFromEffect.Clear();
ObjectPool.Instance.Recycle(GrantedAbilitiesSpecFromEffect);
GrantedAbilitiesSpecFromEffect = default;
}
Stacking = default;
// 注意: SnapshotSourceAttributes 和 SnapshotTargetAttributes 可能是同一个对象
if (SnapshotSourceAttributes != null)
{
SnapshotSourceAttributes.Clear();
ObjectPool.Instance.Recycle(SnapshotSourceAttributes);
}
if (SnapshotTargetAttributes != null && SnapshotSourceAttributes != SnapshotTargetAttributes)
{
SnapshotTargetAttributes.Clear();
ObjectPool.Instance.Recycle(SnapshotTargetAttributes);
}
SnapshotSourceAttributes = null;
SnapshotTargetAttributes = null;
StackCount = 1;
if (_valueMapWithTag != null)
{
_valueMapWithTag.Clear();
ObjectPool.Instance.Recycle(_valueMapWithTag);
_valueMapWithTag = null;
}
if (_valueMapWithName != null)
{
_valueMapWithName.Clear();
ObjectPool.Instance.Recycle(_valueMapWithName);
_valueMapWithName = null;
}
ReleaseCueDurationalSpecs();
OnImmunityBlock = default;
OnStackChanged = default;
}
ObjectPool.Instance.Recycle(this);
}
public void Init(AbilitySystemComponent source, AbilitySystemComponent owner, float level = 1)
{
Source = source;
Owner = owner;
Level = level;
if (GameplayEffect.DurationPolicy != EffectsDurationPolicy.Instant)
{
if (GameplayEffect.PeriodExecution is not null)
{
PeriodExecution = GameplayEffect.PeriodExecution.CreateSpec(source, owner);
}
SetGrantedAbility(GameplayEffect.GrantedAbilities);
}
CaptureAttributesSnapshot();
}
public GameplayEffect GameplayEffect { get; private set; }
public float ActivationTime { get; private set; }
public float Level { get; private set; }
public AbilitySystemComponent Source { get; private set; }
public AbilitySystemComponent Owner { get; private set; }
public bool IsApplied { get; private set; }
public bool IsActive { get; private set; }
internal EntityRef<GameplayEffectPeriodTicker> PeriodTicker { get; private set; }
public float Duration { get; private set; }
public EffectsDurationPolicy DurationPolicy { get; private set; }
public EntityRef<GameplayEffectSpec> PeriodExecution { get; private set; }
public GameplayEffectModifier[] Modifiers { get; private set; }
public List<EntityRef<GrantedAbilitySpecFromEffect>> GrantedAbilitiesSpecFromEffect { get; private set; }
public GameplayEffectStacking Stacking { get; private set; }
public GameplayEffectSnapshotPolicy SnapshotPolicy => GameplayEffect.SnapshotPolicy;
public Dictionary<string, float> SnapshotSourceAttributes { get; private set; }
public Dictionary<string, float> SnapshotTargetAttributes { get; private set; }
/// <summary>
/// 堆叠数
/// </summary>
public int StackCount { get; private set; } = 1;
public float DurationRemaining()
{
if (DurationPolicy == EffectsDurationPolicy.Infinite)
return -1;
return Mathf.Max(0, Duration - (Time.time - ActivationTime));
}
public void SetLevel(float level)
{
Level = level;
}
public void SetActivationTime(float activationTime)
{
ActivationTime = activationTime;
}
public void SetDuration(float duration)
{
Duration = duration;
}
public void SetDurationPolicy(EffectsDurationPolicy durationPolicy)
{
DurationPolicy = durationPolicy;
}
public void SetPeriodExecution(GameplayEffectSpec periodExecution)
{
PeriodExecution.Value?.Recycle();
PeriodExecution = periodExecution;
}
public void SetModifiers(GameplayEffectModifier[] modifiers)
{
Modifiers = modifiers;
}
public void SetGrantedAbility(GrantedAbilityFromEffect[] grantedAbilityFromEffects)
{
ReleaseGrantedAbilitiesSpecFromEffect();
if (grantedAbilityFromEffects is null) return;
if (grantedAbilityFromEffects.Length == 0) return;
GrantedAbilitiesSpecFromEffect = ObjectPool.Instance.Fetch<List<EntityRef<GrantedAbilitySpecFromEffect>>>();
foreach (var grantedAbilityFromEffect in grantedAbilityFromEffects)
{
GrantedAbilitiesSpecFromEffect.Add(grantedAbilityFromEffect.CreateSpec(this));
}
}
private void ReleaseGrantedAbilitiesSpecFromEffect()
{
if (GrantedAbilitiesSpecFromEffect == null) return;
foreach (var grantedAbilitySpecFromEffectRef in GrantedAbilitiesSpecFromEffect)
{
var grantedAbilitySpecFromEffect = grantedAbilitySpecFromEffectRef.Value;
if (grantedAbilitySpecFromEffect != null)
{
grantedAbilitySpecFromEffect.Release();
ObjectPool.Instance.Recycle(grantedAbilitySpecFromEffect);
}
}
GrantedAbilitiesSpecFromEffect.Clear();
ObjectPool.Instance.Recycle(GrantedAbilitiesSpecFromEffect);
}
public void SetStacking(GameplayEffectStacking stacking)
{
Stacking = stacking;
}
public void Apply()
{
if (IsApplied) return;
IsApplied = true;
if (GameplayEffect.CanRunning(Owner))
{
Activate();
}
}
public void DisApply()
{
if (!IsApplied) return;
IsApplied = false;
Deactivate();
}
public void Activate()
{
if (IsActive) return;
IsActive = true;
ActivationTime = Time.time;
TriggerOnActivation();
}
public void Deactivate()
{
if (!IsActive) return;
IsActive = false;
TriggerOnDeactivation();
}
public void Tick()
{
PeriodTicker.Value?.Tick();
}
void TriggerInstantCues(GameplayCueInstant[] cues)
{
try
{
foreach (var cue in cues) cue.ApplyFrom(this);
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private void TriggerCueOnExecute()
{
if (GameplayEffect.CueOnExecute == null || GameplayEffect.CueOnExecute.Length <= 0) return;
TriggerInstantCues(GameplayEffect.CueOnExecute);
}
private void TriggerCueOnAdd()
{
if (GameplayEffect.CueOnAdd is { Length: > 0 })
TriggerInstantCues(GameplayEffect.CueOnAdd);
try
{
ReleaseCueDurationalSpecs();
if (GameplayEffect.CueDurational is { Length: > 0 })
{
_cueDurationalSpecs = ObjectPool.Instance.Fetch<List<GameplayCueDurationalSpec>>();
foreach (var cueDurational in GameplayEffect.CueDurational)
{
var cueSpec = cueDurational.ApplyFrom(this);
if (cueSpec != null) _cueDurationalSpecs.Add(cueSpec);
}
foreach (var cue in _cueDurationalSpecs)
cue.OnAdd();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private void TriggerCueOnRemove()
{
if (GameplayEffect.CueOnRemove is { Length: > 0 })
TriggerInstantCues(GameplayEffect.CueOnRemove);
try
{
if (_cueDurationalSpecs != null)
{
foreach (var cue in _cueDurationalSpecs)
cue.OnRemove();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
finally
{
ReleaseCueDurationalSpecs();
}
}
private void TriggerCueOnActivation()
{
if (GameplayEffect.CueOnActivate is { Length: > 0 })
TriggerInstantCues(GameplayEffect.CueOnActivate);
try
{
if (_cueDurationalSpecs != null)
{
foreach (var cue in _cueDurationalSpecs)
cue.OnGameplayEffectActivate();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private void TriggerCueOnDeactivation()
{
if (GameplayEffect.CueOnDeactivate is { Length: > 0 })
TriggerInstantCues(GameplayEffect.CueOnDeactivate);
try
{
if (_cueDurationalSpecs != null)
{
foreach (var cue in _cueDurationalSpecs)
cue.OnGameplayEffectDeactivate();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
private void CueOnTick()
{
try
{
if (_cueDurationalSpecs != null)
{
foreach (var cue in _cueDurationalSpecs)
cue.OnTick();
}
}
catch (Exception e)
{
Debug.LogError(e);
}
}
public void TriggerOnExecute()
{
Owner.GameplayEffectContainer.RemoveGameplayEffectWithAnyTags(GameplayEffect.TagContainer
.RemoveGameplayEffectsWithTags);
Owner.ApplyModFromInstantGameplayEffect(this);
TriggerCueOnExecute();
}
public void TriggerOnAdd()
{
TriggerCueOnAdd();
}
public void TriggerOnRemove()
{
TryRemoveGrantedAbilities();
TriggerCueOnRemove();
}
private void TriggerOnActivation()
{
Owner.GameplayTagAggregator.ApplyGameplayEffectDynamicTag(this);
Owner.GameplayEffectContainer.RemoveGameplayEffectWithAnyTags(GameplayEffect.TagContainer.RemoveGameplayEffectsWithTags);
TryActivateGrantedAbilities();
TriggerCueOnActivation();
}
private void TriggerOnDeactivation()
{
Owner.GameplayTagAggregator.RestoreGameplayEffectDynamicTags(this);
TryDeactivateGrantedAbilities();
TriggerCueOnDeactivation();
}
public void TriggerOnTick()
{
if (DurationPolicy is EffectsDurationPolicy.Duration or EffectsDurationPolicy.Infinite)
CueOnTick();
}
public void TriggerOnImmunity()
{
OnImmunityBlock?.Invoke(Owner, this);
OnImmunityBlock = null;
}
public void RegisterOnImmunityBlock(Action<AbilitySystemComponent, GameplayEffectSpec> callback)
{
OnImmunityBlock += callback;
}
public void UnregisterOnImmunityBlock(Action<AbilitySystemComponent, GameplayEffectSpec> callback)
{
OnImmunityBlock -= callback;
}
public void RemoveSelf()
{
Owner.GameplayEffectContainer.RemoveGameplayEffectSpec(this);
}
private void CaptureAttributesSnapshot()
{
switch (SnapshotPolicy)
{
case GameplayEffectSnapshotPolicy.Specified:
if (GameplayEffect.SpecifiedSnapshotConfigs != null)
{
foreach (var config in GameplayEffect.SpecifiedSnapshotConfigs)
{
switch (config.SnapshotTarget)
{
case GameplayEffectSpecifiedSnapshotConfig.ESnapshotTarget.Source:
{
SnapshotSourceAttributes ??= ObjectPool.Instance.Fetch<Dictionary<string, float>>();
var attribute = Source.AttributeSetContainer.GetAttributeAttributeValue(config.AttributeSetName, config.AttributeShortName);
if (attribute != null)
{
SnapshotSourceAttributes[config.AttributeName] = attribute.Value.CurrentValue;
}
else
{
Debug.LogError($"Snapshot Source Attribute \"{config.AttributeName}\" not found in AttributeSet \"{config.AttributeSetName}\"");
}
break;
}
case GameplayEffectSpecifiedSnapshotConfig.ESnapshotTarget.Target:
{
SnapshotTargetAttributes ??= ObjectPool.Instance.Fetch<Dictionary<string, float>>();
var attribute = Owner.AttributeSetContainer.GetAttributeAttributeValue(config.AttributeSetName, config.AttributeShortName);
if (attribute != null)
{
SnapshotTargetAttributes[config.AttributeName] = attribute.Value.CurrentValue;
}
else
{
Debug.LogError($"Snapshot Target Attribute {config.AttributeName} not found in AttributeSet {config.AttributeSetName}");
}
break;
}
default:
throw new ArgumentOutOfRangeException();
}
}
}
break;
case GameplayEffectSnapshotPolicy.AllOfSource:
SnapshotSourceAttributes = Source.DataSnapshot();
break;
case GameplayEffectSnapshotPolicy.AllOfTarget:
SnapshotTargetAttributes = Owner.DataSnapshot();
break;
case GameplayEffectSnapshotPolicy.AllOfBoth:
SnapshotSourceAttributes = Source.DataSnapshot();
SnapshotTargetAttributes = Source == Owner ? SnapshotSourceAttributes : Owner.DataSnapshot();
break;
default:
Debug.LogError($"Unsupported SnapshotPolicy: {SnapshotPolicy}, GameplayEffect: {GameplayEffect.GameplayEffectName}");
break;
}
}
public void RegisterValue(in GameplayTag tag, float value)
{
_valueMapWithTag ??= ObjectPool.Instance.Fetch<Dictionary<GameplayTag, float>>();
_valueMapWithTag[tag] = value;
}
public void RegisterValue(string name, float value)
{
_valueMapWithName ??= ObjectPool.Instance.Fetch<Dictionary<string, float>>();
_valueMapWithName[name] = value;
}
public bool UnregisterValue(in GameplayTag tag)
{
if (_valueMapWithTag == null) return false;
return _valueMapWithTag.Remove(tag);
}
public bool UnregisterValue(string name)
{
if (_valueMapWithName == null) return false;
return _valueMapWithName.Remove(name);
}
public float? GetMapValue(in GameplayTag tag)
{
if (_valueMapWithTag == null) return null;
return _valueMapWithTag.TryGetValue(tag, out var value) ? value : (float?)null;
}
public float? GetMapValue(string name)
{
if (_valueMapWithName == null) return null;
return _valueMapWithName.TryGetValue(name, out var value) ? value : (float?)null;
}
private void TryActivateGrantedAbilities()
{
if (GrantedAbilitiesSpecFromEffect == null) return;
foreach (GrantedAbilitySpecFromEffect grantedAbilitySpec in GrantedAbilitiesSpecFromEffect)
{
if (grantedAbilitySpec is { ActivationPolicy: GrantedAbilityActivationPolicy.SyncWithEffect })
{
Owner.TryActivateAbility(grantedAbilitySpec.AbilityName);
}
}
}
private void TryDeactivateGrantedAbilities()
{
if (GrantedAbilitiesSpecFromEffect == null) return;
foreach (GrantedAbilitySpecFromEffect grantedAbilitySpec in GrantedAbilitiesSpecFromEffect)
{
if (grantedAbilitySpec is { DeactivationPolicy: GrantedAbilityDeactivationPolicy.SyncWithEffect })
{
Owner.TryEndAbility(grantedAbilitySpec.AbilityName);
}
}
}
private void TryRemoveGrantedAbilities()
{
if (GrantedAbilitiesSpecFromEffect == null) return;
foreach (GrantedAbilitySpecFromEffect grantedAbilitySpec in GrantedAbilitiesSpecFromEffect)
{
if (grantedAbilitySpec is { RemovePolicy: GrantedAbilityRemovePolicy.SyncWithEffect })
{
Owner.TryCancelAbility(grantedAbilitySpec.AbilityName);
Owner.RemoveAbility(grantedAbilitySpec.AbilityName);
}
}
}
private void ReleaseCueDurationalSpecs()
{
if (_cueDurationalSpecs != null)
{
_cueDurationalSpecs.Clear();
ObjectPool.Instance.Recycle(_cueDurationalSpecs);
_cueDurationalSpecs = null;
}
}
#region ABOUT STACKING
/// <summary>
///
/// </summary>
/// <returns>Stack Count是否变化</returns>
public bool RefreshStack()
{
var oldStackCount = StackCount;
RefreshStack(StackCount + 1);
OnStackCountChange(oldStackCount, StackCount);
return oldStackCount != StackCount;
}
public void RefreshStack(int stackCount)
{
if (stackCount <= Stacking.limitCount)
{
// 更新栈数
StackCount = Mathf.Max(1, stackCount); // 最小层数为1
// 是否刷新Duration
if (Stacking.durationRefreshPolicy == DurationRefreshPolicy.RefreshOnSuccessfulApplication)
{
RefreshDuration();
}
// 是否重置Period
if (Stacking.periodResetPolicy == PeriodResetPolicy.ResetOnSuccessfulApplication)
{
PeriodTicker.Value.ResetPeriod();
}
}
else
{
// 溢出GE生效
foreach (var overflowEffect in Stacking.overflowEffects)
Owner.ApplyGameplayEffectToSelf(overflowEffect);
if (Stacking.durationRefreshPolicy == DurationRefreshPolicy.RefreshOnSuccessfulApplication)
{
if (Stacking.denyOverflowApplication)
{
//当DenyOverflowApplication为True是才有效当Overflow时是否直接删除所有层数
if (Stacking.clearStackOnOverflow)
{
RemoveSelf();
}
}
else
{
RefreshDuration();
}
}
}
}
public void RefreshDuration()
{
ActivationTime = Time.time;
}
private void OnStackCountChange(int oldStackCount, int newStackCount)
{
OnStackChanged?.Invoke(oldStackCount, newStackCount);
}
public void RegisterOnStackCountChanged(Action<int, int> callback)
{
OnStackChanged += callback;
}
public void UnregisterOnStackCountChanged(Action<int, int> callback)
{
OnStackChanged -= callback;
}
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7de9dd9a5bc342e8b99747bb0882b6a4
timeCreated: 1702535736

View File

@@ -0,0 +1,57 @@
using System;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[Serializable]
public struct GameplayEffectSpecifiedSnapshotConfig
{
public enum ESnapshotTarget
{
[LabelText("施法者", SdfIconType.Magic)]
Source,
[LabelText("持有者", SdfIconType.Person)]
Target
}
private const int LABEL_WIDTH = 70;
[LabelText("目标", SdfIconType.PersonBadge)]
[LabelWidth(LABEL_WIDTH)]
[EnumToggleButtons]
public ESnapshotTarget SnapshotTarget;
[LabelText("属性", SdfIconType.Fingerprint)]
[LabelWidth(LABEL_WIDTH)]
[OnValueChanged("OnAttributeChanged")]
[ValueDropdown("@ValueDropdownHelper.AttributeChoices", IsUniqueList = true)]
[ValidateInput("@AttributeValidator.IsValidAttributeName($value)", "属性名无效")]
public string AttributeName;
[HideInInspector]
public string AttributeSetName;
[HideInInspector]
public string AttributeShortName;
public GameplayEffectSpecifiedSnapshotConfig(ESnapshotTarget snapshotTarget, string attributeName)
{
SnapshotTarget = snapshotTarget;
AttributeName = attributeName;
AttributeSetName = string.Empty;
AttributeShortName = string.Empty;
OnAttributeChanged();
}
private void OnAttributeChanged()
{
var split = AttributeName.Split('.');
AttributeSetName = split[0];
AttributeShortName = split[1];
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4d74a27d498d4489acbd9a280a3e92f6
timeCreated: 1723697861

View File

@@ -0,0 +1,239 @@
using System;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
public enum StackingType
{
[LabelText("独立", SdfIconType.XCircleFill)]
None, //不会叠加如果多次释放则每个Effect相当于单个Effect
[LabelText("来源", SdfIconType.Magic)]
AggregateBySource, //目标(Target)上的每个源(Source)ASC都有一个单独的堆栈实例, 每个源(Source)可以应用堆栈中的X个GameplayEffect.
[LabelText("目标", SdfIconType.Person)]
AggregateByTarget //目标(Target)上只有一个堆栈实例而不管源(Source)如何, 每个源(Source)都可以在共享堆栈限制(Shared Stack Limit)内应用堆栈.
}
public enum DurationRefreshPolicy
{
[LabelText("NeverRefresh - 不刷新Effect的持续时间", SdfIconType.XCircleFill)]
NeverRefresh, //不刷新Effect的持续时间
[LabelText(
"RefreshOnSuccessfulApplication - 每次apply成功后刷新持续时间",
SdfIconType.HourglassTop)]
RefreshOnSuccessfulApplication //每次apply成功后刷新Effect的持续时间, denyOverflowApplication如果为True则多余的Apply不会刷新Duration
}
public enum PeriodResetPolicy
{
[LabelText("NeverReset - 不重置Effect的周期计时", SdfIconType.XCircleFill)]
NeverRefresh, //不重置Effect的周期计时
[LabelText("ResetOnSuccessfulApplication - 每次apply成功后重置Effect的周期计时", SdfIconType.HourglassTop)]
ResetOnSuccessfulApplication //每次apply成功后重置Effect的周期计时
}
public enum ExpirationPolicy
{
[LabelText("ClearEntireStack - 持续时间结束时, 清除所有层数", SdfIconType.TrashFill)]
ClearEntireStack, //持续时间结束时,清除所有层数
[LabelText("RemoveSingleStackAndRefreshDuration - 持续时间结束时减少一层然后重新经历一个Duration", SdfIconType.EraserFill)]
RemoveSingleStackAndRefreshDuration, //持续时间结束时减少一层然后重新经历一个Duration一直持续到层数减为0
[LabelText("RefreshDuration - 持续时间结束时,再次刷新Duration", SdfIconType.HourglassTop)]
RefreshDuration //持续时间结束时,再次刷新Duration这相当于无限Duration
//TODO :可以通过调用GameplayEffectsContainer的OnStackCountChange(GameplayEffect ActiveEffect, int OldStackCount, int NewStackCount)来处理层数,
//TODO :可以达到Duration结束时减少两层并刷新Duration这样复杂的效果。
}
// GE堆栈数据结构
public struct GameplayEffectStacking
{
public string stackingCodeName; // 实际允许不会使用而是使用stackingCodeName的hash值, 即stackingHashCode
public int stackingHashCode;
public StackingType stackingType;
public int limitCount;
public DurationRefreshPolicy durationRefreshPolicy;
public PeriodResetPolicy periodResetPolicy;
public ExpirationPolicy expirationPolicy;
// Overflow 溢出逻辑处理
public bool denyOverflowApplication; //对应于StackDurationRefreshPolicy如果为True则多余的Apply不会刷新Duration
public bool clearStackOnOverflow; //当DenyOverflowApplication为True是才有效当Overflow时是否直接删除所有层数
public GameplayEffect[] overflowEffects; // 超过StackLimitCount数量的Effect被Apply时将会调用该OverflowEffects
public void SetStackingCodeName(string stackingCodeName)
{
this.stackingCodeName = stackingCodeName;
this.stackingHashCode = stackingCodeName?.GetHashCode() ?? 0; // 兼容旧的SO数据
}
public void SetStackingHashCode(int stackingHashCode)
{
this.stackingHashCode = stackingHashCode;
}
public void SetStackingType(StackingType stackingType)
{
this.stackingType = stackingType;
}
public void SetLimitCount(int limitCount)
{
this.limitCount = limitCount;
}
public void SetDurationRefreshPolicy(DurationRefreshPolicy durationRefreshPolicy)
{
this.durationRefreshPolicy = durationRefreshPolicy;
}
public void SetPeriodResetPolicy(PeriodResetPolicy periodResetPolicy)
{
this.periodResetPolicy = periodResetPolicy;
}
public void SetExpirationPolicy(ExpirationPolicy expirationPolicy)
{
this.expirationPolicy = expirationPolicy;
}
public void SetOverflowEffects(GameplayEffect[] overflowEffects)
{
this.overflowEffects = overflowEffects;
}
public void SetOverflowEffects(GameplayEffectAsset[] overflowEffectAssets)
{
overflowEffects = new GameplayEffect[overflowEffectAssets.Length];
for (var i = 0; i < overflowEffectAssets.Length; ++i)
{
overflowEffects[i] = overflowEffectAssets[i].SharedInstance;
}
}
public void SetDenyOverflowApplication(bool denyOverflowApplication)
{
this.denyOverflowApplication = denyOverflowApplication;
}
public void SetClearStackOnOverflow(bool clearStackOnOverflow)
{
this.clearStackOnOverflow = clearStackOnOverflow;
}
public static GameplayEffectStacking None
{
get
{
var stack = new GameplayEffectStacking();
stack.SetStackingType(StackingType.None);
return stack;
}
}
}
[Serializable]
public sealed class GameplayEffectStackingConfig
{
private const int LABEL_WIDTH = 100;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_TYPE)]
[EnumToggleButtons]
public StackingType stackingType;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[HideIf("IsNoStacking")]
[LabelText(GASTextDefine.LABEL_GE_STACKING_CODENAME)]
[CustomContextMenu("清除","@stackingCodeName = \"\"")]
public string stackingCodeName;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_COUNT)]
[HideIf("IsNoStacking")]
[CustomContextMenu("设为0","@limitCount = 0")]
[CustomContextMenu("设为1","@limitCount = 1")]
[CustomContextMenu("设为最大值","@limitCount = int.MaxValue")]
[MinValue(0),MaxValue(int.MaxValue)]
public int limitCount;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_DURATION_REFRESH_POLICY)]
[HideIf("IsNoStacking")]
[InfoBox(GASTextDefine.LABEL_GE_STACKING_DENY_OVERFLOW_APPLICATION+"为True时多余的Apply不会刷新Duration", InfoMessageType.None,
VisibleIf =
"@durationRefreshPolicy == DurationRefreshPolicy.RefreshOnSuccessfulApplication && denyOverflowApplication")]
public DurationRefreshPolicy durationRefreshPolicy;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_PERIOD_RESET_POLICY)]
[HideIf("IsNoStacking")]
public PeriodResetPolicy periodResetPolicy;
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_EXPIRATION_POLICY)]
[HideIf("IsNoStacking")]
public ExpirationPolicy expirationPolicy;
// Overflow 溢出逻辑处理
[LabelWidth(LABEL_WIDTH)]
[VerticalGroup]
[LabelText(GASTextDefine.LABEL_GE_STACKING_DENY_OVERFLOW_APPLICATION)]
[HideIf("@IsNoStacking() || IsNeverRefreshDuration()")]
public bool denyOverflowApplication;
[VerticalGroup]
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GE_STACKING_CLEAR_STACK_ON_OVERFLOW)]
[ShowIf("IsDenyOverflowApplication")]
public bool clearStackOnOverflow;
[VerticalGroup]
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GE_STACKING_CLEAR_OVERFLOW_EFFECTS)]
[HideIf("IsNoStacking")]
public GameplayEffectAsset[] overflowEffects;
/// <summary>
/// 转换为运行时数据
/// </summary>
/// <returns></returns>
public GameplayEffectStacking ToRuntimeData()
{
var stack = new GameplayEffectStacking();
stack.SetStackingCodeName(stackingCodeName);
stack.SetStackingType(stackingType);
stack.SetLimitCount(limitCount);
stack.SetDurationRefreshPolicy(durationRefreshPolicy);
stack.SetPeriodResetPolicy(periodResetPolicy);
stack.SetExpirationPolicy(expirationPolicy);
stack.SetOverflowEffects(overflowEffects);
stack.SetDenyOverflowApplication(denyOverflowApplication);
stack.SetClearStackOnOverflow(clearStackOnOverflow);
return stack;
}
#region UTIL FUNCTION FOR ODIN INSPECTOR
public bool IsNoStacking() => stackingType == StackingType.None;
public bool IsNeverRefreshDuration() =>
IsNoStacking() || durationRefreshPolicy == DurationRefreshPolicy.NeverRefresh;
public bool IsDenyOverflowApplication() => !IsNoStacking() && denyOverflowApplication;
#endregion
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 736f28f3bd804444a0fb873c73c1cb58
timeCreated: 1716344872

View File

@@ -0,0 +1,101 @@
using System;
namespace GAS.Runtime
{
/// <summary>
/// https://github.com/BillEliot/GASDocumentation_Chinese?tab=readme-ov-file#457-gameplayeffect%E6%A0%87%E7%AD%BE
/// </summary>
public struct GameplayEffectTagContainer
{
/// <summary>
/// 游戏效果(GE)拥有的标签, 它们自身没有任何功能且只用于描述GameplayEffect。
/// 此标签集合用于RemoveGameplayEffectsWithTags的匹配条件, 因此对于Instant型GE没有意义。
/// 注意GrantedTags也会被用于RemoveGameplayEffectsWithTags的匹配。
/// </summary>
public GameplayTagSet AssetTags;
/// <summary>
/// 当游戏效果(GE)处于激活状态时,赋予目标的标签集合。
/// 存于GameplayEffect中且又用于GameplayEffect所应用ASC的标签.
/// 当GameplayEffect移除时它们也会从ASC中移除. 该标签只作用于持续(Duration)和无限(Infinite)GameplayEffect, 对于Instant型GE没有意义.
/// 当GE处于非激活状态时这些标签将被临时移除直到GE再次激活。
/// 这些标签同样用于RemoveGameplayEffectsWithTags的匹配。
/// </summary>
public GameplayTagSet GrantedTags;
/// <summary>
/// 当GameplayEffect成功应用后, 如果位于目标上的该GameplayEffect在其Asset Tags或Granted Tags中有任意一个本标签的话, 其就会自目标上移除.
/// 匹配判断发生在:
/// 1. Instant GE被应用时
/// 2. 非Instant GE每次被激活时
/// 3. Period Execution GE(非Instant GE中的PeriodExecution)的每个周期到期时。
/// </summary>
public GameplayTagSet RemoveGameplayEffectsWithTags;
/// <summary>
/// ApplicationRequiredTags和ApplicationImmunityTags是一对条件
/// 游戏效果能够应用于目标的前提是:
/// 1. 目标必须拥有ApplicationRequiredTags中的所有标签
/// 2. 目标不能拥有ApplicationImmunityTags中的任意标签。
/// </summary>
public GameplayTagSet ApplicationRequiredTags;
/// <summary>
/// ApplicationRequiredTags和ApplicationImmunityTags是一对条件
/// 游戏效果能够应用于目标的前提是:
/// 1. 目标必须拥有ApplicationRequiredTags中的所有标签
/// 2. 目标不能拥有ApplicationImmunityTags中的任意标签。
/// </summary>
public GameplayTagSet ApplicationImmunityTags;
/// <summary>
/// 游戏效果(GE)激活所需的标签集合。
/// 该标签只作用于持续(Duration)和无限(Infinite)GameplayEffect, 对于Instant型GE没有意义.
/// 一旦GameplayEffect应用后, 这些标签将决定GameplayEffect是开启还是关闭. GameplayEffect可以是关闭但仍然是应用的.
/// 如果某个GameplayEffect由于不符合Ongoing Tag Requirements而关闭, 但是之后又满足需求了, 那么该GameplayEffect会重新打开并重新应用它的Modifier.
/// 使用场景包括:
/// 1. GE应用时如果满足条件则激活GE否则不执行任何操作
/// 2. 标签发生变化时如果满足条件则激活GE否则使GE失效。
/// </summary>
public GameplayTagSet OngoingRequiredTags;
public GameplayEffectTagContainer(IGameplayEffectData data) : this(
data.GetAssetTags(),
data.GetGrantedTags(),
data.GetApplicationRequiredTags(),
data.GetOngoingRequiredTags(),
data.GetRemoveGameplayEffectsWithTags(),
data.GetApplicationImmunityTags()
)
{
}
public GameplayEffectTagContainer(
GameplayTag[] assetTags,
GameplayTag[] grantedTags,
GameplayTag[] applicationRequiredTags,
GameplayTag[] ongoingRequiredTags,
GameplayTag[] removeGameplayEffectsWithTags,
GameplayTag[] applicationImmunityTags)
{
AssetTags = new GameplayTagSet(assetTags);
GrantedTags = new GameplayTagSet(grantedTags);
ApplicationRequiredTags = new GameplayTagSet(applicationRequiredTags);
OngoingRequiredTags = new GameplayTagSet(ongoingRequiredTags);
RemoveGameplayEffectsWithTags = new GameplayTagSet(removeGameplayEffectsWithTags);
ApplicationImmunityTags = new GameplayTagSet(applicationImmunityTags);
}
public static GameplayEffectTagContainer CreateEmpty()
{
return new GameplayEffectTagContainer(
Array.Empty<GameplayTag>(),
Array.Empty<GameplayTag>(),
Array.Empty<GameplayTag>(),
Array.Empty<GameplayTag>(),
Array.Empty<GameplayTag>(),
Array.Empty<GameplayTag>()
);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 716961a2572d4f56a4e967fee9074de7
timeCreated: 1702623396

View File

@@ -0,0 +1,222 @@
using System;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
/// <summary>
/// 授予能力的激活策略
/// </summary>
public enum GrantedAbilityActivationPolicy
{
/// <summary>
/// 不激活, 等待用户调用ASC激活
/// </summary>
[LabelText("None - 不激活, 等待用户调用ASC激活", SdfIconType.Joystick)]
None,
/// <summary>
/// 能力添加时激活GE添加时激活
/// </summary>
[LabelText("WhenAdded - 能力添加时激活GE添加时激活", SdfIconType.LightningChargeFill)]
WhenAdded,
/// <summary>
/// 同步GE激活时激活
/// </summary>
[LabelText("SyncWithEffect - 同步GE激活时激活", SdfIconType.Robot)]
SyncWithEffect,
}
/// <summary>
/// 授予能力的取消激活策略
/// </summary>
public enum GrantedAbilityDeactivationPolicy
{
/// <summary>
/// 无相关取消激活逻辑, 需要用户调用ASC取消激活
/// </summary>
[LabelText("None - 无相关取消激活逻辑, 需要用户调用ASC取消激活", SdfIconType.Joystick)]
None,
/// <summary>
/// 同步GEGE失活时取消激活
/// </summary>
[LabelText("SyncWithEffect - 同步GEGE失活时取消激活", SdfIconType.Robot)]
SyncWithEffect,
}
/// <summary>
/// 授予能力的移除策略
/// </summary>
public enum GrantedAbilityRemovePolicy
{
/// <summary>
/// 不移除
/// </summary>
[LabelText("None - 不移除", SdfIconType.Joystick)]
None,
/// <summary>
/// 同步GEGE移除时移除
/// </summary>
[LabelText("SyncWithEffect - 同步GEGE移除时移除", SdfIconType.Robot)]
SyncWithEffect,
/// <summary>
/// 能力结束时自己移除
/// </summary>
[LabelText("WhenEnd - 能力结束时自己移除", SdfIconType.LightningChargeFill)]
WhenEnd,
/// <summary>
/// 能力取消时自己移除
/// </summary>
[LabelText("WhenCancel - 能力取消时自己移除", SdfIconType.LightningChargeFill)]
WhenCancel,
/// <summary>
/// 能力结束或取消时自己移除
/// </summary>
[LabelText("WhenCancelOrEnd - 能力结束或取消时自己移除", SdfIconType.LightningChargeFill)]
WhenCancelOrEnd,
}
[Serializable]
public struct GrantedAbilityConfig
{
private const int LABEL_WIDTH = 50;
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GRANT_ABILITY)]
[AssetSelector]
public AbilityAsset AbilityAsset;
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GRANT_ABILITY_LEVEL)]
public int AbilityLevel;
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GRANT_ABILITY_ACTIVATION_POLICY)]
[Tooltip(GASTextDefine.TIP_GRANT_ABILITY_ACTIVATION_POLICY)]
public GrantedAbilityActivationPolicy ActivationPolicy;
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GRANT_ABILITY_DEACTIVATION_POLICY)]
[Tooltip(GASTextDefine.TIP_GRANT_ABILITY_DEACTIVATION_POLICY)]
public GrantedAbilityDeactivationPolicy DeactivationPolicy;
[LabelWidth(LABEL_WIDTH)]
[LabelText(GASTextDefine.LABEL_GRANT_ABILITY_REMOVE_POLICY)]
[Tooltip(GASTextDefine.TIP_GRANT_ABILITY_REMOVE_POLICY)]
public GrantedAbilityRemovePolicy RemovePolicy;
}
public class GrantedAbilityFromEffect
{
public readonly AbstractAbility Ability;
public readonly int AbilityLevel;
public readonly GrantedAbilityActivationPolicy ActivationPolicy;
public readonly GrantedAbilityDeactivationPolicy DeactivationPolicy;
public readonly GrantedAbilityRemovePolicy RemovePolicy;
public GrantedAbilityFromEffect(GrantedAbilityConfig config)
{
Ability =
Activator.CreateInstance(config.AbilityAsset.AbilityType(), args: config.AbilityAsset) as
AbstractAbility;
AbilityLevel = config.AbilityLevel;
ActivationPolicy = config.ActivationPolicy;
DeactivationPolicy = config.DeactivationPolicy;
RemovePolicy = config.RemovePolicy;
}
public GrantedAbilityFromEffect(
AbstractAbility ability,
int abilityLevel,
GrantedAbilityActivationPolicy activationPolicy,
GrantedAbilityDeactivationPolicy deactivationPolicy,
GrantedAbilityRemovePolicy removePolicy)
{
Ability = ability;
AbilityLevel = abilityLevel;
ActivationPolicy = activationPolicy;
DeactivationPolicy = deactivationPolicy;
RemovePolicy = removePolicy;
}
public GrantedAbilitySpecFromEffect CreateSpec(GameplayEffectSpec sourceEffectSpec)
{
var grantedAbilitySpecFromEffect = ObjectPool.Instance.Fetch<GrantedAbilitySpecFromEffect>();
grantedAbilitySpecFromEffect.Awake(this, sourceEffectSpec);
return grantedAbilitySpecFromEffect;
}
}
public class GrantedAbilitySpecFromEffect : IEntity
{
public ulong InstanceId { get; private set; }
public GrantedAbilityFromEffect GrantedAbility { get; private set; }
public GameplayEffectSpec SourceEffectSpec { get; private set; }
public AbilitySystemComponent Owner { get; private set; }
public string AbilityName { get; private set; }
public int AbilityLevel => GrantedAbility.AbilityLevel;
public GrantedAbilityActivationPolicy ActivationPolicy => GrantedAbility.ActivationPolicy;
public GrantedAbilityDeactivationPolicy DeactivationPolicy => GrantedAbility.DeactivationPolicy;
public GrantedAbilityRemovePolicy RemovePolicy => GrantedAbility.RemovePolicy;
public AbilitySpec AbilitySpec => Owner.AbilityContainer.AbilitySpecs()[AbilityName];
public void Awake(GrantedAbilityFromEffect grantedAbility, GameplayEffectSpec sourceEffectSpec)
{
InstanceId = IdGenerator.Next;
GrantedAbility = grantedAbility;
SourceEffectSpec = sourceEffectSpec;
AbilityName = GrantedAbility.Ability.Name;
Owner = SourceEffectSpec.Owner;
if (Owner.AbilityContainer.HasAbility(AbilityName))
{
Debug.LogError($"GrantedAbilitySpecFromEffect: {Owner.name} already has ability {AbilityName}");
}
Owner.GrantAbility(GrantedAbility.Ability);
AbilitySpec.SetLevel(AbilityLevel);
// 是否添加时激活
if (ActivationPolicy == GrantedAbilityActivationPolicy.WhenAdded)
{
Owner.TryActivateAbility(AbilityName, sourceEffectSpec);
}
switch (RemovePolicy)
{
case GrantedAbilityRemovePolicy.WhenEnd:
AbilitySpec.RegisterEndAbility(RemoveSelf);
break;
case GrantedAbilityRemovePolicy.WhenCancel:
AbilitySpec.RegisterCancelAbility(RemoveSelf);
break;
case GrantedAbilityRemovePolicy.WhenCancelOrEnd:
AbilitySpec.RegisterEndAbility(RemoveSelf);
AbilitySpec.RegisterCancelAbility(RemoveSelf);
break;
}
}
public void Release()
{
InstanceId = default;
GrantedAbility = default;
Owner = default;
AbilityName = default;
SourceEffectSpec = default;
}
private void RemoveSelf()
{
Owner.RemoveAbility(AbilityName);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd642ab66f914c8e8af10d569b9eae37
timeCreated: 1714320038

View File

@@ -0,0 +1,42 @@
namespace GAS.Runtime
{
public interface IGameplayEffectData
{
string GetDisplayName();
EffectsDurationPolicy GetDurationPolicy();
float GetDuration();
float GetPeriod();
GameplayEffectSnapshotPolicy GetSnapshotPolicy();
GameplayEffectSpecifiedSnapshotConfig[] GetSpecifiedSnapshotConfigs();
/// <summary>
/// 必须是Instant型的GameplayEffect
/// </summary>
IGameplayEffectData GetPeriodExecution();
GameplayTag[] GetAssetTags();
GameplayTag[] GetGrantedTags();
GameplayTag[] GetRemoveGameplayEffectsWithTags();
GameplayTag[] GetApplicationRequiredTags();
GameplayTag[] GetApplicationImmunityTags();
GameplayTag[] GetOngoingRequiredTags();
// Cues
GameplayCueInstant[] GetCueOnExecute();
GameplayCueInstant[] GetCueOnRemove();
GameplayCueInstant[] GetCueOnAdd();
GameplayCueInstant[] GetCueOnActivate();
GameplayCueInstant[] GetCueOnDeactivate();
GameplayCueDurational[] GetCueDurational();
// Modifiers
GameplayEffectModifier[] GetModifiers();
ExecutionCalculation[] GetExecutions();
// Granted Ability
GrantedAbilityConfig[] GetGrantedAbilities();
//Stacking
GameplayEffectStacking GetStacking();
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 900156ce5c8140cf81212a877be13b4c
timeCreated: 1715604764

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5375b45c42364cffb54cb4d972235f75
timeCreated: 1702439022

View File

@@ -0,0 +1,72 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
/// <summary>
/// 基于属性混合GE堆栈的MMC
/// </summary>
[CreateAssetMenu(fileName = "AttrBasedWithStackModCalculation", menuName = "GAS/MMC/AttrBasedWithStackModCalculation")]
public class AttrBasedWithStackModCalculation:AttributeBasedModCalculation
{
public enum StackMagnitudeOperation
{
Add,
Multiply
}
[InfoBox(" 公式StackCount * sK + sB")]
[TabGroup("Default", "AttributeBasedModCalculation")]
[Title("堆叠幅值计算")]
[LabelText("系数(sK)")]
public float sK = 1;
[TabGroup("Default", "AttributeBasedModCalculation")]
[LabelText("常量(sB)")]
public float sB = 0;
[TabGroup("Default", "AttributeBasedModCalculation")]
[Title("最终结果")]
[InfoBox(" 最终公式: \n" +
"Add:(AttributeValue * k + b)+(StackCount * sK + sB); \n" +
"Multiply:(AttributeValue * k + b)*(StackCount * sK + sB)")]
[LabelText("Stack幅值与Attr幅值计算方式")]
public StackMagnitudeOperation stackMagnitudeOperation;
[TabGroup("Default", "AttributeBasedModCalculation")]
[LabelText("最终公式")]
[ShowInInspector]
[DisplayAsString(TextAlignment.Left, true)]
public string FinalFormulae
{
get
{
var formulae = stackMagnitudeOperation switch
{
StackMagnitudeOperation.Add => $"({attributeName} * {k} + {b}) + (StackCount * {sK} + {sB})",
StackMagnitudeOperation.Multiply => $"({attributeName} * {k} + {b}) * (StackCount * {sK} + {sB})",
_ => ""
};
return $"<size=15><b><color=green>{formulae}</color></b></size>";
}
}
public override float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude)
{
var attrMagnitude = base.CalculateMagnitude(spec, modifierMagnitude);
if (spec.Stacking.stackingType == StackingType.None) return attrMagnitude;
var stackMagnitude = spec.StackCount * sK + sB;
return stackMagnitudeOperation switch
{
StackMagnitudeOperation.Add => attrMagnitude + stackMagnitude,
StackMagnitudeOperation.Multiply => attrMagnitude * stackMagnitude,
_ => attrMagnitude + stackMagnitude
};
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e8a883d343584d7d9b8a711cf045ca84
timeCreated: 1717082065

View File

@@ -0,0 +1,135 @@
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu(fileName = "AttributeBasedModCalculation", menuName = "GAS/MMC/AttributeBasedModCalculation")]
public class AttributeBasedModCalculation : ModifierMagnitudeCalculation
{
public enum AttributeFrom
{
[LabelText("来源(Source)", SdfIconType.Magic)]
Source,
[LabelText("目标(Target)", SdfIconType.Person)]
Target
}
public enum GEAttributeCaptureType
{
[LabelText("快照(SnapShot)", SdfIconType.Camera)]
SnapShot,
[LabelText("实时(Track)", SdfIconType.Speedometer2)]
Track
}
[TabGroup("Default", "AttributeBasedModCalculation", SdfIconType.PersonBoundingBox, TextColor = "blue")]
[InfoBox(" 以什么方式(Capture Type)从谁身上(Attribute From)捕获哪个属性的值(Attribute Name)。")]
[EnumToggleButtons]
[LabelText("捕获方式(Capture Type)")]
public GEAttributeCaptureType captureType;
[TabGroup("Default", "AttributeBasedModCalculation")]
[EnumToggleButtons]
[LabelText("捕获目标(Attribute From)")]
public AttributeFrom attributeFromType;
[TabGroup("Default", "AttributeBasedModCalculation")]
[ValueDropdown("@ValueDropdownHelper.AttributeChoices", IsUniqueList = true)]
[LabelText("属性的名称(Attribute Name)")]
[OnValueChanged("@OnAttributeNameChanged()")]
[ValidateInput("@AttributeValidator.IsValidAttributeName($value)", "属性名无效")]
public string attributeName;
[TabGroup("Default", "Details", SdfIconType.Bug, TextColor = "orange")]
[ReadOnly]
public string attributeSetName;
[TabGroup("Default", "Details")]
[ReadOnly]
public string attributeShortName;
[InfoBox("计算逻辑与ScalableFloatModCalculation一致, 公式AttributeValue * k + b")]
[TabGroup("Default", "AttributeBasedModCalculation")]
[LabelText("系数(k)")]
public float k = 1;
[TabGroup("Default", "AttributeBasedModCalculation")]
[LabelText("常量(b)")]
public float b = 0;
public override float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude)
{
float attributeValue;
if (attributeFromType == AttributeFrom.Source)
{
if (captureType == GEAttributeCaptureType.SnapShot)
{
var snapShot = spec.SnapshotSourceAttributes;
if (snapShot == null || snapShot.TryGetValue(attributeName, out attributeValue) == false)
{
Debug.LogError($"Source snapshot Attribute '{attributeName}' not found in source snapshot for spec: '{spec.GameplayEffect.GameplayEffectName}'.");
attributeValue = 1;
}
}
else
{
var attributeCurrentValue = spec.Source.GetAttributeCurrentValue(attributeSetName, attributeShortName);
if (attributeCurrentValue == null)
{
Debug.LogError($"Source Attribute '{attributeName}' not found in source for spec: '{spec.GameplayEffect.GameplayEffectName}'.");
attributeValue = 1;
}
else
{
attributeValue = attributeCurrentValue.Value;
}
}
}
else
{
if (captureType == GEAttributeCaptureType.SnapShot)
{
var snapShot = spec.SnapshotTargetAttributes;
if (snapShot == null || snapShot.TryGetValue(attributeName, out attributeValue) == false)
{
Debug.LogError($"Target snapshot Attribute '{attributeName}' not found in target snapshot for spec: '{spec.GameplayEffect.GameplayEffectName}'.");
attributeValue = 1;
}
}
else
{
var attributeCurrentValue = spec.Owner.GetAttributeCurrentValue(attributeSetName, attributeShortName);
if (attributeCurrentValue == null)
{
Debug.LogError($"Source Attribute '{attributeName}' not found in source for spec: '{spec.GameplayEffect.GameplayEffectName}'.");
attributeValue = 1;
}
else
{
attributeValue = attributeCurrentValue.Value;
}
}
}
return attributeValue * k + b;
}
private void OnAttributeNameChanged()
{
if (!string.IsNullOrWhiteSpace(attributeName))
{
var split = attributeName.Split('.');
attributeSetName = split[0];
attributeShortName = split[1];
}
else
{
attributeSetName = null;
attributeShortName = null;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c712e05924784c5593896e5c7c1d2708
timeCreated: 1703494252

View File

@@ -0,0 +1,134 @@
using System;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
public enum GEOperation
{
[LabelText(SdfIconType.PlusLg, Text = "加")]
Add = 0,
[LabelText(SdfIconType.DashLg, Text = "减")]
Minus = 3,
[LabelText(SdfIconType.XLg, Text = "乘")]
Multiply = 1,
[LabelText(SdfIconType.SlashLg, Text = "除")]
Divide = 4,
[LabelText(SdfIconType.Pencil, Text = "替")]
Override = 2,
}
[Flags]
public enum SupportedOperation : byte
{
None = 0,
[LabelText(SdfIconType.PlusLg, Text = "加")]
Add = 1 << GEOperation.Add,
[LabelText(SdfIconType.DashLg, Text = "减")]
Minus = 1 << GEOperation.Minus,
[LabelText(SdfIconType.XLg, Text = "乘")]
Multiply = 1 << GEOperation.Multiply,
[LabelText(SdfIconType.SlashLg, Text = "除")]
Divide = 1 << GEOperation.Divide,
[LabelText(SdfIconType.Pencil, Text = "替")]
Override = 1 << GEOperation.Override,
All = Add | Minus | Multiply | Divide | Override
}
[Serializable]
public struct GameplayEffectModifier
{
private const int LABEL_WIDTH = 70;
[LabelText("修改属性", SdfIconType.Fingerprint)]
[LabelWidth(LABEL_WIDTH)]
[OnValueChanged("OnAttributeChanged")]
[ValueDropdown("@ValueDropdownHelper.AttributeChoices", IsUniqueList = true)]
[ValidateInput("@ReflectionHelper.GetAttribute($value) != null", "无效属性")]
[Tooltip("指的是GameplayEffect作用对象被修改的属性。")]
[SuffixLabel("@ReflectionHelper.GetAttribute($value)?.CalculateMode")]
[PropertyOrder(1)]
public string AttributeName;
[HideInInspector]
public string AttributeSetName;
[HideInInspector]
public string AttributeShortName;
[LabelText("运算参数", SdfIconType.Activity)]
[LabelWidth(LABEL_WIDTH)]
[Tooltip("修改器的基础数值。这个数值如何使用由MMC的运行逻辑决定。\nMMC未指定时直接使用这个值。")]
[ValidateInput("@Operation != GEOperation.Divide || ModiferMagnitude != 0", "除数不能为零")]
[PropertyOrder(3)]
public float ModiferMagnitude;
[LabelText("运算法则", SdfIconType.PlusSlashMinus)]
[LabelWidth(LABEL_WIDTH)]
[EnumToggleButtons]
[PropertyOrder(2)]
[ValidateInput("@ReflectionHelper.GetAttribute(AttributeName) == null || ReflectionHelper.GetAttribute(AttributeName).IsSupportOperation($value)", "非法运算: 该属性不支持的此运算法则")]
public GEOperation Operation;
[LabelText("参数修饰", SdfIconType.CpuFill)]
[LabelWidth(LABEL_WIDTH)]
[AssetSelector]
[Tooltip("ModifierMagnitudeCalculation修改器负责GAS中Attribute的数值计算逻辑。\n可以为空(不对\"计算参数\"做任何修改)。")]
[PropertyOrder(4)]
public ModifierMagnitudeCalculation MMC;
// TODO
// public readonly GameplayTagSet SourceTag;
// TODO
// public readonly GameplayTagSet TargetTag;
public GameplayEffectModifier(
string attributeName,
float modiferMagnitude,
GEOperation operation,
ModifierMagnitudeCalculation mmc = null)
{
AttributeName = attributeName;
var splits = attributeName.Split('.');
AttributeSetName = splits[0];
AttributeShortName = splits[1];
ModiferMagnitude = modiferMagnitude;
Operation = operation;
MMC = mmc;
}
public float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude)
{
return MMC == null ? ModiferMagnitude : MMC.CalculateMagnitude(spec, modifierMagnitude);
}
public void SetModiferMagnitude(float value)
{
ModiferMagnitude = value;
}
void OnAttributeChanged()
{
var split = AttributeName.Split('.');
AttributeSetName = split[0];
AttributeShortName = split[1];
if (ReflectionHelper.GetAttribute(AttributeName)?.CalculateMode !=
CalculateMode.Stacking)
{
Operation = GEOperation.Override;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b0b20897a5ad4adc9ac45f147879fee1
timeCreated: 1702376348

View File

@@ -0,0 +1,56 @@
using System.Linq;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
public abstract class ModifierMagnitudeCalculation : ScriptableObject
{
protected const int WIDTH_LABEL = 70;
[TitleGroup("Base")]
[HorizontalGroup("Base/H1", width: 1 - 0.618f)]
[TabGroup("Base/H1/V1", "Summary", SdfIconType.InfoSquareFill, TextColor = "#0BFFC5", Order = 1)]
[HideLabel]
[MultiLineProperty(10)]
public string Description;
#if UNITY_EDITOR
[TabGroup("Base/H1/V2", "General", SdfIconType.AwardFill, TextColor = "#FF7F00", Order = 2)]
[TabGroup("Base/H1/V2", "Detail", SdfIconType.TicketDetailedFill, TextColor = "#BC2FDE")]
[LabelText("类型名称", SdfIconType.FileCodeFill)]
[LabelWidth(WIDTH_LABEL)]
[ShowInInspector]
[PropertyOrder(-1)]
public string TypeName => GetType().Name;
[TabGroup("Base/H1/V2", "Detail")]
[LabelText("类型全名", SdfIconType.FileCodeFill)]
[LabelWidth(WIDTH_LABEL)]
[ShowInInspector]
[PropertyOrder(-1)]
public string TypeFullName => GetType().FullName;
[TabGroup("Base/H1/V2", "Detail")]
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, ShowPaging = false)]
[ShowInInspector]
[LabelText("继承关系")]
[LabelWidth(WIDTH_LABEL)]
[PropertyOrder(-1)]
public string[] InheritanceChain => GetType().GetInheritanceChain().Reverse().ToArray();
#endif
public abstract float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude);
#if UNITY_EDITOR
private void OnValidate()
{
// if(Application.isPlaying) return;
// EditorUtility.SetDirty(this);
// AssetDatabase.SaveAssets();
// AssetDatabase.Refresh();
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a19086b31e7847ccaa9ec35657197357
timeCreated: 1702624467

View File

@@ -0,0 +1,24 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu(fileName = "ScalableFloatModCalculation", menuName = "GAS/MMC/ScalableFloatModCalculation")]
public class ScalableFloatModCalculation : ModifierMagnitudeCalculation
{
private const string Desc = "计算公式ModifierMagnitude * k + b";
private const string Detail =
"ScalableFloatModCalculation可缩放浮点数计算\n该类型是根据Magnitude计算Modifier模值的计算公式为ModifierMagnitude * k + b 实际上就是一个线性函数k和b为可编辑参数可以在编辑器中设置。";
[DetailedInfoBox(Desc, Detail, InfoMessageType.Info)] [SerializeField]
private float k = 1f;
[SerializeField] private float b = 0f;
public override float CalculateMagnitude(GameplayEffectSpec spec, float input)
{
return input * k + b;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b127451ee4eb4575be21eb8e5744a5da
timeCreated: 1703493960

View File

@@ -0,0 +1,18 @@
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu(fileName = "SetByCallerFromName", menuName = "GAS/MMC/SetByCallerFromNameModCalculation")]
public class SetByCallerFromNameModCalculation : ModifierMagnitudeCalculation
{
[SerializeField] private string valueName;
public override float CalculateMagnitude(GameplayEffectSpec spec,float input)
{
var value = spec.GetMapValue(valueName);
#if UNITY_EDITOR
if(value==null) Debug.LogWarning($"[EX] SetByCallerModCalculation: GE's '{valueName}' value(name map) is not set");
#endif
return value ?? 0;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 46c94907308d49a9965a836e3447b1a4
timeCreated: 1706243411

View File

@@ -0,0 +1,23 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu(fileName = "SetByCallerFromTag", menuName = "GAS/MMC/SetByCallerFromTagModCalculation")]
public class SetByCallerFromTagModCalculation : ModifierMagnitudeCalculation
{
[SerializeField]
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", HideChildProperties = true)]
private GameplayTag _tag;
public override float CalculateMagnitude(GameplayEffectSpec spec, float input)
{
var value = spec.GetMapValue(_tag);
#if UNITY_EDITOR
if (value == null)
Debug.LogWarning($"[EX] SetByCallerModCalculation: GE's '{_tag.Name}' value(tag map) is not set");
#endif
return value ?? 0;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 235035fa9abf451594198fe2ed2b2e28
timeCreated: 1706240763

View File

@@ -0,0 +1,26 @@
using Sirenix.OdinInspector;
using UnityEngine;
namespace GAS.Runtime
{
[CreateAssetMenu( fileName = "StackModCalculation", menuName = "GAS/MMC/StackModCalculation" )]
public class StackModCalculation:ModifierMagnitudeCalculation
{
[InfoBox("计算逻辑与ScalableFloatModCalculation一致, 公式:(StackCount) * k + b")]
[TabGroup("Default", "StackModCalculation")]
[LabelText("系数(k)")]
public float k = 1;
[TabGroup("Default", "StackModCalculation")]
[LabelText("常量(b)")]
public float b = 0;
public override float CalculateMagnitude(GameplayEffectSpec spec, float modifierMagnitude)
{
if (spec.Stacking.stackingType == StackingType.None) return 0;
var stackCount = spec.StackCount;
return stackCount * k + b;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21d2ef437f1e4ec9900650b169b66e77
timeCreated: 1717073260