mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-11-11 16:48:30 +00:00
提交GAS 打算做一个帧同步的GAS
This commit is contained in:
8
JEX_GAS/Assets/GAS/Runtime/Effects/CooldownTimer.cs
Normal file
8
JEX_GAS/Assets/GAS/Runtime/Effects/CooldownTimer.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public struct CooldownTimer
|
||||
{
|
||||
public float TimeRemaining;
|
||||
public float Duration;
|
||||
}
|
||||
}
|
||||
11
JEX_GAS/Assets/GAS/Runtime/Effects/CooldownTimer.cs.meta
Normal file
11
JEX_GAS/Assets/GAS/Runtime/Effects/CooldownTimer.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 267174a9e0a0449e95cafbf86d476175
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
3
JEX_GAS/Assets/GAS/Runtime/Effects/Execution.meta
Normal file
3
JEX_GAS/Assets/GAS/Runtime/Effects/Execution.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2be86a570234c578d2de13b42a0fd1d
|
||||
timeCreated: 1702540748
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class ExecutionCalculation
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0af87f769c39444aa661e9f5859b72e9
|
||||
timeCreated: 1702453899
|
||||
182
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffect.cs
Normal file
182
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffect.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0b37ff3cd6874a05b8fccbb09d2d17f4
|
||||
timeCreated: 1701944543
|
||||
397
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectAsset.cs
Normal file
397
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectAsset.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b9395a0be3e547f48bd1c8320edc6c58
|
||||
timeCreated: 1703260426
|
||||
330
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectContainer.cs
Normal file
330
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectContainer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4fd84391fd9443178481ac7d664cbcc1
|
||||
timeCreated: 1703237216
|
||||
127
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectData.cs
Normal file
127
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectData.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eb31bfbad2914a539f07afdfb9d6afda
|
||||
timeCreated: 1715604792
|
||||
113
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectPeriodTicker.cs
Normal file
113
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectPeriodTicker.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f128ba6062db4f6d9a7b3169faaf9846
|
||||
timeCreated: 1702545119
|
||||
702
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectSpec.cs
Normal file
702
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectSpec.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7de9dd9a5bc342e8b99747bb0882b6a4
|
||||
timeCreated: 1702535736
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d74a27d498d4489acbd9a280a3e92f6
|
||||
timeCreated: 1723697861
|
||||
239
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectStacking.cs
Normal file
239
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectStacking.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 736f28f3bd804444a0fb873c73c1cb58
|
||||
timeCreated: 1716344872
|
||||
101
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectTagContainer.cs
Normal file
101
JEX_GAS/Assets/GAS/Runtime/Effects/GameplayEffectTagContainer.cs
Normal 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>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 716961a2572d4f56a4e967fee9074de7
|
||||
timeCreated: 1702623396
|
||||
222
JEX_GAS/Assets/GAS/Runtime/Effects/GrantedAbilityFromEffect.cs
Normal file
222
JEX_GAS/Assets/GAS/Runtime/Effects/GrantedAbilityFromEffect.cs
Normal 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>
|
||||
/// 同步GE,GE失活时取消激活
|
||||
/// </summary>
|
||||
[LabelText("SyncWithEffect - 同步GE,GE失活时取消激活", SdfIconType.Robot)]
|
||||
SyncWithEffect,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 授予能力的移除策略
|
||||
/// </summary>
|
||||
public enum GrantedAbilityRemovePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 不移除
|
||||
/// </summary>
|
||||
[LabelText("None - 不移除", SdfIconType.Joystick)]
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// 同步GE,GE移除时移除
|
||||
/// </summary>
|
||||
[LabelText("SyncWithEffect - 同步GE,GE移除时移除", 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd642ab66f914c8e8af10d569b9eae37
|
||||
timeCreated: 1714320038
|
||||
42
JEX_GAS/Assets/GAS/Runtime/Effects/IGameplayEffectData.cs
Normal file
42
JEX_GAS/Assets/GAS/Runtime/Effects/IGameplayEffectData.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 900156ce5c8140cf81212a877be13b4c
|
||||
timeCreated: 1715604764
|
||||
3
JEX_GAS/Assets/GAS/Runtime/Effects/Modifier.meta
Normal file
3
JEX_GAS/Assets/GAS/Runtime/Effects/Modifier.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5375b45c42364cffb54cb4d972235f75
|
||||
timeCreated: 1702439022
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e8a883d343584d7d9b8a711cf045ca84
|
||||
timeCreated: 1717082065
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c712e05924784c5593896e5c7c1d2708
|
||||
timeCreated: 1703494252
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0b20897a5ad4adc9ac45f147879fee1
|
||||
timeCreated: 1702376348
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a19086b31e7847ccaa9ec35657197357
|
||||
timeCreated: 1702624467
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b127451ee4eb4575be21eb8e5744a5da
|
||||
timeCreated: 1703493960
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 46c94907308d49a9965a836e3447b1a4
|
||||
timeCreated: 1706243411
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 235035fa9abf451594198fe2ed2b2e28
|
||||
timeCreated: 1706240763
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21d2ef437f1e4ec9900650b169b66e77
|
||||
timeCreated: 1717073260
|
||||
Reference in New Issue
Block a user