mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-09-27 10:46:17 +00:00
提交
This commit is contained in:
239
JNFrame2/Assets/HotScripts/JNGame/Runtime/GAS/CHANGELOG.md
Normal file
239
JNFrame2/Assets/HotScripts/JNGame/Runtime/GAS/CHANGELOG.md
Normal file
@@ -0,0 +1,239 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.8] - 2024-07-30
|
||||
|
||||
进行了一系列的优化。(From: BCC @kenkinky)
|
||||
|
||||
### Changed
|
||||
|
||||
- 进行了一系列的优化
|
||||
|
||||
## [1.1.6] - 2024-06-26
|
||||
|
||||
修复了AbilitySpec中CheckCost时,modifier为减法时的计算错误;追加了Attribute的钳制功能。
|
||||
|
||||
### Changed
|
||||
|
||||
- 追加了Attribute的钳制功能(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了AbilitySpec中CheckCost时,modifier为减法时的计算错误。
|
||||
|
||||
|
||||
## [1.1.6] - 2024-06-26
|
||||
|
||||
修复了由于优化GE创建流程时导致的Granted Ability生成错误;优化了period的边界问题
|
||||
|
||||
### Changed
|
||||
|
||||
- 优化了period的边界问题(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了由于优化GE创建流程时导致的Granted Ability生成错误。
|
||||
|
||||
|
||||
## [1.1.5] - 2024-06-19
|
||||
|
||||
修复了AttrBasedMMC的快照读取错误;Modifier新增了减法,除法操作类型。
|
||||
|
||||
### Changed
|
||||
|
||||
- Modifier新增了减法,除法操作类型。(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了AttrBasedMMC的快照读取错误。
|
||||
|
||||
|
||||
## [1.1.4] - 2024-06-14
|
||||
|
||||
重新整理了ASC的ApplyGameplayEffect方法的逻辑,现在GE的Tag相关判断是在实例化之后。允许用户在GameplayEffectSpec生效前对GE进行修改和操作。
|
||||
|
||||
### Changed
|
||||
|
||||
- 重新整理了ASC的ApplyGameplayEffectTo(GameplayEffect gameplayEffect, AbilitySystemComponent target)方法的逻辑
|
||||
- ASC新增:ApplyGameplayEffectTo(GameplayEffectSpec gameplayEffectSpec, AbilitySystemComponent target) 和
|
||||
ApplyGameplayEffectToSelf(GameplayEffectSpec gameplayEffectSpec)
|
||||
|
||||
## [1.1.3] - 2024-06-13
|
||||
|
||||
添加了带level形参的ApplyGE方法。
|
||||
|
||||
### Changed
|
||||
|
||||
- 添加了带level形参的ApplyGE方法。
|
||||
|
||||
|
||||
## [1.1.2] - 2024-06-12
|
||||
|
||||
修复了部分bug。编辑器界面部分优化
|
||||
|
||||
### Changed
|
||||
|
||||
- 编辑器界面部分优化(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了时间轴能力的durational cue重复调用OnRemove()的错误
|
||||
- 修复了时间轴编辑器的TargetCatcher的Inspector不更新的错误
|
||||
|
||||
|
||||
## [1.1.1] - 2024-05-31
|
||||
|
||||
补充了Stacking相关功能。
|
||||
|
||||
### Changed
|
||||
|
||||
- 添加Stack相关MMC
|
||||
- 补充stack刷新计算current value逻辑
|
||||
- 添加stack count变化监听事件
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了Attribute Aggregator的事件注册逻辑错误。
|
||||
|
||||
## [1.1.0] - 2024-05-30
|
||||
|
||||
补充了Granted Ability和GameplayEffect Stacking两个功能;优化了部分GC;优化了编辑器界面操作等;修复了部分bug。
|
||||
|
||||
### Changed
|
||||
|
||||
- 补充了Granted Ability,详情可见README文档的2.8.c
|
||||
- 补充了GameplayEffect Stacking,详情可见README文档的2.7中Stacking部分
|
||||
- 优化了部分GC。(From: BCC @kenkinky)
|
||||
- 优化了部分执行逻辑,增强了代码可读性。
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了部分逻辑bug。(From: BCC @kenkinky)
|
||||
|
||||
## [1.0.9] - 2024-04-25
|
||||
|
||||
优化type查找,优化GAS的项目级配置文件管理。
|
||||
|
||||
### Changed
|
||||
|
||||
- 新增TimelineAbilityT, 方便继承和扩展TimelineAbility.(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修正TryAddDynamicAddedTag添加不同类型Source时类型转换失败异常(From: BCC @kenkinky)
|
||||
- 修复了Setting中生成配置目录后,未调用AssetDatabase.Refresh()导致配置文件目录未及时更新的问题。
|
||||
## [1.0.8] - 2024-04-23
|
||||
|
||||
优化了部分GC。
|
||||
|
||||
### Fixed
|
||||
|
||||
- AttributeSetContainer的TryGetAttributeSet方法中,Type.Name存在GC。
|
||||
- 新增了预缓存接口:GasCache.CacheAttributeSetName。
|
||||
- 使用方法:在GAS初始化时,调用GasCache.CacheAttributeSetName(GAttrSetLib.TypeToName);
|
||||
- GameplayTagAggregator的Tag判断相关方法存在GC。GC来源是LINQ表达式的过程匿名方法产生的GC。已经把LINQ表达式改成了普通循环做法。
|
||||
- 新增了Pool工具类,优化了部分GC。(From: BCC @kenkinky)
|
||||
|
||||
## [1.0.7] - 2024-04-17
|
||||
|
||||
修复全局配置保存失败问题;修复Editor代码不该编译问题
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复全局配置保存失败问题,Tag,Attribute,AttributeSet,Setting的配置文件保存不该使用AssetDataBase。
|
||||
- 修正无法打包编译异常 #11 (From: BCC @kenkinky)
|
||||
|
||||
## [1.0.6] - 2024-04-16
|
||||
|
||||
优化type查找,优化GAS的项目级配置文件管理。
|
||||
|
||||
### Changed
|
||||
|
||||
- 修改了Tag,Attribute,AttributeSet,Setting的配置文件路径,调整至ProjectSettings,并且为单例配置文件。
|
||||
- 优化了TypeUtil,Editor环境下类型查找范围改为全程序集。
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复一个严重bug: 修复AttributeBasedModCalculation不能正确保存的问题, 还有一些小优化.(From: BCC @kenkinky)
|
||||
|
||||
## [1.0.5] - 2024-04-12
|
||||
|
||||
修复了部分bug;优化编辑器操作。
|
||||
|
||||
### Added
|
||||
|
||||
- 优化编辑器操作。(From: BCC @kenkinky)
|
||||
|
||||
### Fixed
|
||||
|
||||
- 修复了TryActivateAbility的返回值逻辑错误。
|
||||
|
||||
|
||||
## [1.0.4] - 2024-04-11
|
||||
|
||||
修复了部分bug;测试通过了推导属性设计;优化了GE容器的管理,增强代码可读性。
|
||||
|
||||
### Added
|
||||
|
||||
- 添加了GAS内部的子Event系统,为方便之后用上事件系统做准备。
|
||||
|
||||
### Fixed
|
||||
|
||||
- 推导属性的实时更新错误。补上了AttributeBasedMMC的Track类修改器属性变化监听。
|
||||
- 修复GASHost销毁时的错误逻辑,Host的静态单例改为饿汉式,同步GAS的初始化只会执行一次。
|
||||
|
||||
### Changed
|
||||
|
||||
- 优化GameplayEffectContainer结构,现在只维护一个GameplayEffect列表
|
||||
|
||||
### Removed
|
||||
|
||||
- 移除DerivedAttribute和MetaAttribute脚本,弃用。这两个属性式设计方式,而不是实际存在的类。
|
||||
|
||||
## [1.0.3] - 2024-04-09
|
||||
|
||||
删除SetByCallerModCalculation,弃用。
|
||||
|
||||
### Removed
|
||||
|
||||
- 删除SetByCallerModCalculation,弃用。
|
||||
|
||||
## [1.0.2] - 2024-04-08
|
||||
|
||||
优化Editor使用体验(From: BCC @kenkinky)
|
||||
|
||||
### Changed
|
||||
|
||||
- 优化Editor使用体验(From: BCC @kenkinky)
|
||||
|
||||
|
||||
## [1.0.1] - 2024-03-29
|
||||
|
||||
删除Instant类型GameplayCue的Apply Target参数。
|
||||
|
||||
### Removed
|
||||
|
||||
- Instant类型GameplayCue的Apply Target弃用。
|
||||
|
||||
## [1.0.0] - 2024-03-13
|
||||
|
||||
EX-GAS 1.0.0 发布
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
### Fixed
|
||||
|
||||
- none
|
||||
|
||||
### Changed
|
||||
|
||||
- none
|
||||
|
||||
### Removed
|
||||
|
||||
- none
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34ae97a0d741c5844a4fac12ff8e1c45
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f87f52c980e4d1ba211174b7e0ce100
|
||||
timeCreated: 1702438660
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5b22c58989b9453f804027e8b5abe655
|
||||
timeCreated: 1710383267
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
[Serializable]
|
||||
public class JsonData
|
||||
{
|
||||
public string Type;
|
||||
public string Data;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7a7cb94b9dba4808bbaa27c7ddfe6f37
|
||||
timeCreated: 1709455860
|
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public class ObservableValue<T>
|
||||
{
|
||||
private T _value;
|
||||
|
||||
public ObservableValue(T initialValue)
|
||||
{
|
||||
_value = initialValue;
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => _value;
|
||||
set
|
||||
{
|
||||
var oldValue = _value;
|
||||
_value = value;
|
||||
OnValueChanged?.Invoke(oldValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<T, T> OnValueChanged;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 49847b3f12774f21ba8dbb0970c7dcb6
|
||||
timeCreated: 1702438524
|
@@ -0,0 +1,184 @@
|
||||
namespace GAS.General
|
||||
{
|
||||
public static class GASTextDefine
|
||||
{
|
||||
public const string TITLE_SETTING = "设置";
|
||||
public const string TITLE_PATHS = "路径";
|
||||
public const string TITLE_BASE_INFO = "基本信息";
|
||||
public const string TITLE_DESCRIPTION = "描述";
|
||||
|
||||
|
||||
#region GASSettingAsset
|
||||
|
||||
public const string TIP_CREATE_GEN_AscUtilCode =
|
||||
"<color=white><size=15>生成ASC拓展类之前,一定要保证Ability,AttributeSet的集合工具类已经生成。因为ASC拓展类依赖于此</size></color>";
|
||||
|
||||
public const string TIP_CREATE_FOLDERS =
|
||||
"<color=white><size=15>如果你修改了EX-GAS的配置Asset路径,请点击这个按钮来确保所有子文件夹和基础配置文件正确生成。</size></color>";
|
||||
|
||||
public const string LABEL_OF_CodeGeneratePath = "脚本生成路径";
|
||||
public const string LABEL_OF_GASConfigAssetPath = "配置文件Asset路径";
|
||||
public const string BUTTON_CheckAllPathFolderExist = " 检查子目录和基础配置";
|
||||
public const string BUTTON_GenerateAscExtensionCode = " 生成AbilitySystemComponentExtension类脚本";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Tag
|
||||
|
||||
public const string BUTTON_ExpandAllTag = "展开全部";
|
||||
public const string BUTTON_CollapseAllTag = "折叠全部";
|
||||
public const string BUTTON_AddTag = "添加Tag";
|
||||
public const string BUTTON_RemoveTag = "移除Tag";
|
||||
public const string BUTTON_GenTagCode = "生成TagLib";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Attribute
|
||||
public const string TIP_Warning_EmptyAttribute =
|
||||
"<size=13><color=yellow><color=orange>Attribute名</color>不准为<color=red><b>空</b></color>! " +
|
||||
"Please check!</color></size>";
|
||||
public const string BUTTON_GenerateAttributeCollection = " 生成AttrLib";
|
||||
|
||||
public const string TIP_Warning_DuplicatedAttribute =
|
||||
"<size=13><color=yellow>The <color=orange>Attribute名</color> 禁止 <color=red><b>重复</b></color>!\n" +
|
||||
"重复的Attributes名:<size=15><b><color=white> {0} </color></b></size>.</color></size>";
|
||||
#endregion
|
||||
|
||||
|
||||
#region AttributeSet
|
||||
|
||||
public const string ERROR_DuplicatedAttribute = "<size=16><b>存在重复Attribute!</b></size>";
|
||||
public const string ERROR_Empty = "<size=16><b>AttributeSet至少要有一个Attribute!</b></size>";
|
||||
public const string ERROR_EmptyName = "<size=16><b>AttributeSet名不可以为空!</b></size>";
|
||||
public const string ERROR_InElements = "<size=16><b><color=orange>请先修复AttributeSet的提示错误!</color></b></size>";
|
||||
|
||||
public const string ERROR_DuplicatedAttributeSet = "<size=16><b><color=orange>存在重复AttributeSet!\n" +
|
||||
"<color=white> -> {0}</color></color></b></size>";
|
||||
public const string BUTTON_GenerateAttributeSetCode = " 生成AttrSetLib";
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region GameplayEffect
|
||||
|
||||
public const string TIP_BASE_INFO="仅用于描述,方便理解。";
|
||||
public const string TIP_GE_POLICY="Instant=瞬时,Duration=持续性,Infinite=永久";
|
||||
public const string LABLE_GE_NAME = "效果名称";
|
||||
public const string LABLE_GE_DESCRIPTION = "效果描述";
|
||||
public const string TITLE_GE_POLICY="Gameplay Effect实施策略";
|
||||
public const string LABLE_GE_POLICY = "时限策略";
|
||||
public const string LABLE_GE_DURATION = "持续时间";
|
||||
public const string LABLE_GE_INTERVAL = "间隔时间";
|
||||
public const string LABLE_GE_EXEC = "间隔效果";
|
||||
public const string LABLE_GE_SnapshotPolicy = "快照策略";
|
||||
public const string TITLE_GE_GrantedAbilities = "授予能力(Ability)";
|
||||
public const string TITLE_GE_MOD = "修改器Modifier";
|
||||
public const string TITLE_GE_TAG = "标签Tag";
|
||||
public const string TITLE_GE_CUE = "提示Cue";
|
||||
|
||||
public const string TITLE_GE_TAG_AssetTags = "AssetTags - 该[游戏效果]自身的标签";
|
||||
public const string TIP_GE_TAG_AssetTags = "AssetTags: 标签用于描述[游戏效果]自身的特定属性,包括但不限于伤害、治疗、控制等效果类型。\n这些标签有助于区分和定义[游戏效果]的作用和表现。\n可配合RemoveGameplayEffectsWithTags食用。";
|
||||
public const string TITLE_GE_TAG_GrantedTags = "GrantedTags - 授予目标单位的标签";
|
||||
public const string TIP_GE_TAG_GrantedTags = "GrantedTags: 当[游戏效果]生效时,标签会被添加到目标单位上,并在[游戏效果]失效时移除。\n该标签对即时型(Instant)[游戏效果]的无效。";
|
||||
public const string TITLE_GE_TAG_ApplicationRequiredTags = "ApplicationRequiredTags - 应用该[游戏效果]的【全部】前提";
|
||||
public const string TIP_GE_TAG_ApplicationRequiredTags = "ApplicationRequiredTags: [游戏效果]的目标单位必须具备【所有】这些标签才能应用于目标单位。\n如果想表达【任一】标签不可作用于目标,应该使用ApplicationImmunityTags标签。";
|
||||
public const string TITLE_GE_TAG_OngoingRequiredTags = "OngoingRequiredTags - 激活该[游戏效果]的【全部】前提";
|
||||
public const string TIP_GE_TAG_OngoingRequiredTags = "OngoingRequiredTags: [游戏效果]的目标单位必须具备【全部】这些标签,否则该效果不会触发。\n一旦[游戏效果]被施加,如果目标单位在效果持续期间标签发生变化,导致不再具备【全部】这些标签,效果将失效;反之,如果满足条件,效果将被激活。\n该标签对即时型(Instant)[游戏效果]的无效。";
|
||||
public const string TITLE_GE_TAG_RemoveGameplayEffectsWithTags = "RemoveGameplayEffectsWithTags - 移除具有【任一】标签的[游戏效果]";
|
||||
public const string TIP_GE_TAG_RemoveGameplayEffectsWithTags = "RemoveGameplayEffectsWithTags: [游戏效果]的目标单位当前持有的所有[游戏效果]中,其AssetTags或GrantedTags中具有【任一】这些标签的[游戏效果]将被移除。";
|
||||
public const string TITLE_GE_TAG_ApplicationImmunityTags = "ApplicationImmunityTags - 无法应用于具有【任一】标签的目标";
|
||||
public const string TIP_GE_TAG_ApplicationImmunityTags = "ApplicationImmunityTags: 该[游戏效果]无法作用于拥有【任一】这些标签的目标单位。";
|
||||
|
||||
public const string TITLE_GE_CUE_CueOnExecute = "CueOnExecute - 执行时触发";
|
||||
public const string TITLE_GE_CUE_CueDurational = "CueDurational - 存在时持续触发";
|
||||
public const string TITLE_GE_CUE_CueOnAdd = "CueOnAdd - 添加时触发";
|
||||
public const string TITLE_GE_CUE_CueOnRemove = "CueOnRemove - 移除时触发";
|
||||
public const string TITLE_GE_CUE_CueOnActivate = "CueOnActivate - 激活时触发";
|
||||
public const string TITLE_GE_CUE_CueOnDeactivate = "CueOnDeactivate - 失活时触发";
|
||||
|
||||
public const string LABEL_GRANT_ABILITY = "授予能力";
|
||||
public const string LABEL_GRANT_ABILITY_LEVEL = "能力等级";
|
||||
public const string LABEL_GRANT_ABILITY_ACTIVATION_POLICY = "激活策略";
|
||||
public const string LABEL_GRANT_ABILITY_DEACTIVATION_POLICY = "失活策略";
|
||||
public const string LABEL_GRANT_ABILITY_REMOVE_POLICY = "移除策略";
|
||||
public const string TIP_GRANT_ABILITY_ACTIVATION_POLICY = "None = 不激活,需要用户手动调用ASC相关接口激活; " +
|
||||
"WhenAdded = 添加时就激活;" +
|
||||
"SyncWithEffect = 同步GE,GE激活时激活";
|
||||
public const string TIP_GRANT_ABILITY_DEACTIVATION_POLICY = "None = 无相关取消激活逻辑, 需要用户调用ASC取消激活; " +
|
||||
"SyncWithEffect = 同步GE,GE失活时取消激活";
|
||||
public const string TIP_GRANT_ABILITY_REMOVE_POLICY = "None = 不移除能力;" +
|
||||
"SyncWithEffect = 同步GE,GE移除时移除" +
|
||||
"WhenEnd = 能力结束时,移除自身;" +
|
||||
"WhenCancel = 能力取消时,移除自身;" +
|
||||
"WhenCancelOrEnd = 能力取消或结束时,移除自身";
|
||||
|
||||
public const string TITLE_GE_STACKING = "堆叠配置";
|
||||
public const string LABEL_GE_STACKING_CODENAME = "堆叠GE识别码";
|
||||
public const string LABEL_GE_STACKING_TYPE = "堆叠类型";
|
||||
public const string LABEL_GE_STACKING_COUNT = "堆叠上限";
|
||||
public const string LABEL_GE_STACKING_DURATION_REFRESH_POLICY = "持续时间刷新策略";
|
||||
public const string LABEL_GE_STACKING_PERIOD_RESET_POLICY = "周期重置策略";
|
||||
public const string LABEL_GE_STACKING_EXPIRATION_POLICY = "持续时间结束策略";
|
||||
public const string LABEL_GE_STACKING_DENY_OVERFLOW_APPLICATION = "溢出的GE不生效";
|
||||
public const string LABEL_GE_STACKING_CLEAR_STACK_ON_OVERFLOW = "溢出时清空堆叠";
|
||||
public const string LABEL_GE_STACKING_CLEAR_OVERFLOW_EFFECTS = "溢出时触发的GE";
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ability
|
||||
|
||||
public const string ABILITY_BASEINFO="基本信息";
|
||||
public const string TIP_UNAME =
|
||||
"<size=12><b><color=white><color=orange>U-Name非常重要!</color>" +
|
||||
"GAS会使用U-Name作为Ability的标识符。" +
|
||||
"所以你必须保证U-Name的唯一性。" +
|
||||
"别担心,生成AbilityLib时工具会提醒你这一点。</color></b></size>";
|
||||
public const string ABILITY_CD_TIME="冷却时长";
|
||||
public const string ABILITY_EFFECT_CD="冷却效果";
|
||||
public const string ABILITY_EFFECT_COST="消耗效果";
|
||||
public const string ABILITY_MANUAL_ENDABILITY = "手动结束能力";
|
||||
public const string BUTTON_CHECK_TIMELINE_ABILITY = "查看/编辑能力时间轴";
|
||||
public const string ABILITY_PLAY_RATE = "播放速率";
|
||||
|
||||
#endregion
|
||||
|
||||
#region ASC
|
||||
|
||||
public const string TIP_ASC_BASEINFO="基本信息只用于描述这个ASC,方便策划阅读理解该ASC。";
|
||||
public const string ASC_BASE_TAG="固有标签";
|
||||
public const string ASC_BASE_ABILITY="固有能力";
|
||||
public const string ASC_AttributeSet="属性集";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Watcher
|
||||
|
||||
public const string TIP_WATCHER = "该窗口用于监视GAS运行状态,建议在调试GAS的角色能力,效果时打开该窗口。";
|
||||
public const string TIP_WATCHER_OnlyForGameRunning =
|
||||
"<size=20><b><color=yellow>监视器只在游戏运行时可用.</color></b></size>";
|
||||
|
||||
#endregion
|
||||
|
||||
#region Gameplay Cue
|
||||
|
||||
public const string CUE_ANIMATION_PATH = "动画机相对路径";
|
||||
public const string CUE_ANIMATION_INCLUDE_CHILDREN = "包括子节点";
|
||||
public const string CUE_ANIMATION_INCLUDE_CHILDREN_ANIMATOR_TIP = "在自身及孩子节点中查找动画机, 当你的动画机路径不能完全确定时(例如: 不同的皮肤节点不一致), 可勾选此项";
|
||||
public const string CUE_ANIMATION_STATE = "Animation State名";
|
||||
public const string CUE_ANIMATION_PATH_TIP = "为空表示物体根节点,示例:'A'=根节点下名为'A'的子物体,'A/B'='A'节点下名为'B'的子物体";
|
||||
|
||||
public const string CUE_SOUND_EFFECT = "音效源";
|
||||
public const string CUE_ATTACH_TO_OWNER = "是否附加到Owner";
|
||||
|
||||
public const string CUE_VFX_PREFAB = "特效预制体";
|
||||
public const string CUE_VFX_OFFSET = "特效偏移";
|
||||
public const string CUE_VFX_ROTATION = "特效旋转";
|
||||
public const string CUE_VFX_SCALE = "特效缩放";
|
||||
public const string CUE_VFX_ACTIVE_WHEN_ADDED = "是否在添加时就被激活";
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 40ecb12e788648c49235e3b85f466825
|
||||
timeCreated: 1706946276
|
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public class GASTimer
|
||||
{
|
||||
// TODO 矫正时间差(服务器客户端时间差/暂停游戏导致的时间差)
|
||||
static int _deltaTime;
|
||||
|
||||
public static long Timestamp() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + _deltaTime;
|
||||
|
||||
public static long TimestampSeconds() => Timestamp() / 1000;
|
||||
|
||||
private static int _currentFrameCount;
|
||||
public static int CurrentFrameCount => _currentFrameCount;
|
||||
public static void UpdateCurrentFrameCount()
|
||||
{
|
||||
_currentFrameCount = Mathf.FloorToInt((Timestamp() - _startTimestamp) / 1000f * FrameRate);
|
||||
}
|
||||
|
||||
private static long _startTimestamp;
|
||||
public static long StartTimestamp => _startTimestamp;
|
||||
public static void InitStartTimestamp()
|
||||
{
|
||||
_startTimestamp = Timestamp();
|
||||
}
|
||||
|
||||
|
||||
private static long _pauseTimestamp;
|
||||
public static void Pause()
|
||||
{
|
||||
_pauseTimestamp = Timestamp();
|
||||
}
|
||||
|
||||
public static void Unpause()
|
||||
{
|
||||
_deltaTime -= (int)(Timestamp() - _pauseTimestamp);
|
||||
}
|
||||
|
||||
private static int _frameRate = 60;
|
||||
public static int FrameRate => _frameRate;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9378cf08054e430f8fd59ca809378c8a
|
||||
timeCreated: 1702545659
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ecd3bc3b1a964d4bb6a2fd56126fbefc
|
||||
timeCreated: 1709716270
|
@@ -0,0 +1,506 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public static class DebugExtension
|
||||
{
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DebugBox(Vector2 center, Vector2 size, float angle, Color color, float showTime)
|
||||
{
|
||||
var halfSize = size * 0.5f;
|
||||
var p1 = new Vector2(center.x - halfSize.x, center.y - halfSize.y);
|
||||
var p2 = new Vector2(center.x + halfSize.x, center.y - halfSize.y);
|
||||
var p3 = new Vector2(center.x + halfSize.x, center.y + halfSize.y);
|
||||
var p4 = new Vector2(center.x - halfSize.x, center.y + halfSize.y);
|
||||
// p1 绕center旋转angle角度
|
||||
p1 = RotatePoint(center, p1, angle);
|
||||
p2 = RotatePoint(center, p2, angle);
|
||||
p3 = RotatePoint(center, p3, angle);
|
||||
p4 = RotatePoint(center, p4, angle);
|
||||
DrawLine(p1, p2, color, showTime);
|
||||
DrawLine(p2, p3, color, showTime);
|
||||
DrawLine(p3, p4, color, showTime);
|
||||
DrawLine(p4, p1, color, showTime);
|
||||
}
|
||||
|
||||
public static Vector2 RotatePoint(Vector2 center, Vector2 point, float angle)
|
||||
{
|
||||
var cos = Mathf.Cos(angle * Mathf.Deg2Rad);
|
||||
var sin = Mathf.Sin(angle * Mathf.Deg2Rad);
|
||||
var x = cos * (point.x - center.x) - sin * (point.y - center.y) + center.x;
|
||||
var y = sin * (point.x - center.x) + cos * (point.y - center.y) + center.y;
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DebugDrawCircle(Vector2 center, float radius, Color color, float showTime, float segments = 120)
|
||||
{
|
||||
var step = 360f / segments;
|
||||
var from = center + new Vector2(radius, 0);
|
||||
for (var i = 0; i < segments; i++)
|
||||
{
|
||||
var to = center + new Vector2(radius * Mathf.Cos((i + 1) * step * Mathf.Deg2Rad),
|
||||
radius * Mathf.Sin((i + 1) * step * Mathf.Deg2Rad));
|
||||
DrawLine(from, to, color, showTime);
|
||||
from = to;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个圆形
|
||||
/// </summary>
|
||||
/// <param name="position">位置, 圆心</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="radius">半径</param>
|
||||
/// <param name="segments">分段数(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCircle(in Vector3 position, in Quaternion rotation, float radius, int segments = 36,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawArc(position, rotation, radius, 360, segments, color, duration);
|
||||
}
|
||||
|
||||
public static void DrawCircle(in Vector3 position, in Vector3 forward, float radius, int segments = 36,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawCircle(position, Quaternion.LookRotation(forward), radius, segments, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个圆弧
|
||||
/// </summary>
|
||||
/// <param name="position">位置</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="radius">半径</param>
|
||||
/// <param name="angle">角度</param>
|
||||
/// <param name="segments">分段数(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArc(in Vector3 position, in Quaternion rotation, float radius, float angle, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
if (angle <= 0) return;
|
||||
if (radius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
var angleStep = angle / segments;
|
||||
var lastPoint = position + rotation * (Vector3.right * radius);
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var currentAngle = i * angleStep;
|
||||
var nextPoint = position + rotation * (Quaternion.Euler(0, currentAngle, 0) * Vector3.right * radius);
|
||||
DrawLine(lastPoint, nextPoint, color, duration);
|
||||
lastPoint = nextPoint;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArc(in Vector3 position, in Vector3 forward, float radius, float angle, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawArc(position, Quaternion.LookRotation(forward), radius, angle, segments, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个圆环
|
||||
/// </summary>
|
||||
/// <param name="position">位置, 圆心</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="outerRadius">外圈半径</param>
|
||||
/// <param name="innerRadius">内圈半径</param>
|
||||
/// <param name="segments">分段数(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawRing(in Vector3 position, in Quaternion rotation, float outerRadius, float innerRadius, int segments = 36,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
if (outerRadius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
innerRadius = Mathf.Clamp(innerRadius, 0, outerRadius);
|
||||
|
||||
// 计算圆的每个点的位置
|
||||
var angleStep = 360f / segments;
|
||||
var lastOuterPoint = position + rotation * (Vector3.right * outerRadius);
|
||||
var lastInnerPoint = position + rotation * (Vector3.right * innerRadius);
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var angle = i * angleStep;
|
||||
var nextOuterPoint = position + rotation * (Quaternion.Euler(0, angle, 0) * Vector3.right * outerRadius);
|
||||
var nextInnerPoint = position + rotation * (Quaternion.Euler(0, angle, 0) * Vector3.right * innerRadius);
|
||||
DrawLine(lastOuterPoint, nextOuterPoint, color, duration);
|
||||
DrawLine(lastInnerPoint, nextInnerPoint, color, duration);
|
||||
DrawLine(nextOuterPoint, nextInnerPoint, color, duration);
|
||||
lastOuterPoint = nextOuterPoint;
|
||||
lastInnerPoint = nextInnerPoint;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawRing(in Vector3 position, in Vector3 forward, float outerRadius, float innerRadius, int segments = 36,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawRing(position, Quaternion.LookRotation(forward), outerRadius, innerRadius, segments, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个矩形
|
||||
/// </summary>
|
||||
/// <param name="position">位置, 矩形中心点</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="size">矩形的宽长, 宽:左右(垂直于朝向), 长:前后(与朝向一致)</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawRectangle(in Vector3 position, in Quaternion rotation, in Vector2 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
// 计算矩形的四个角点在局部坐标系中的位置
|
||||
var halfSize = new Vector3(size.x * 0.5f, 0, size.y * 0.5f);
|
||||
var corners = new Vector3[4];
|
||||
corners[0] = new Vector3(-halfSize.x, 0, -halfSize.z); // 左下角
|
||||
corners[1] = new Vector3(halfSize.x, 0, -halfSize.z); // 右下角
|
||||
corners[2] = new Vector3(halfSize.x, 0, halfSize.z); // 右上角
|
||||
corners[3] = new Vector3(-halfSize.x, 0, halfSize.z); // 左上角
|
||||
|
||||
// 旋转角点并转换到世界坐标系
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
corners[i] = position + rotation * corners[i];
|
||||
}
|
||||
|
||||
// 绘制矩形的四条边
|
||||
DrawLine(corners[0], corners[1], color, duration); // 底边
|
||||
DrawLine(corners[1], corners[2], color, duration); // 右边
|
||||
DrawLine(corners[2], corners[3], color, duration); // 顶边
|
||||
DrawLine(corners[3], corners[0], color, duration); // 左边
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawRectangle(in Vector3 position, in Vector3 forward, in Vector2 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawRectangle(position, Quaternion.LookRotation(forward), size, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 基于位置和朝向, 在其前方绘制一个矩形(如: 基于技能释放者前方的矩形攻击范围)
|
||||
/// </summary>
|
||||
/// <param name="position">位置</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="size">矩形的宽长(宽:左右, 长:前后)</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawFrontRectangle(in Vector3 position, in Quaternion rotation, in Vector2 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
// 计算矩形的四个角点在局部坐标系中的位置
|
||||
var halfX = size.x * 0.5f;
|
||||
var corners = new Vector3[4];
|
||||
corners[0] = new Vector3(-halfX, 0, 0); // 左下角
|
||||
corners[1] = new Vector3(halfX, 0, 0); // 右下角
|
||||
corners[2] = new Vector3(halfX, 0, size.y); // 右上角
|
||||
corners[3] = new Vector3(-halfX, 0, size.y); // 左上角
|
||||
|
||||
// 旋转角点并转换到世界坐标系
|
||||
for (int i = 0; i < corners.Length; i++)
|
||||
{
|
||||
corners[i] = position + rotation * corners[i];
|
||||
}
|
||||
|
||||
// 绘制矩形的四条边
|
||||
DrawLine(corners[0], corners[1], color, duration); // 底边
|
||||
DrawLine(corners[1], corners[2], color, duration); // 右边
|
||||
DrawLine(corners[2], corners[3], color, duration); // 顶边
|
||||
DrawLine(corners[3], corners[0], color, duration); // 左边
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawFrontRectangle(in Vector3 position, in Vector3 forward, in Vector2 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawFrontRectangle(position, Quaternion.LookRotation(forward), size, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个立方体
|
||||
/// </summary>
|
||||
/// <param name="position">立方体中心点</param>
|
||||
/// <param name="rotation">立方体的朝向</param>
|
||||
/// <param name="size">立方体的尺寸</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCube(in Vector3 position, in Quaternion rotation, in Vector3 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
// 计算立方体的八个顶点在局部坐标系中的位置
|
||||
var halfSize = size * 0.5f;
|
||||
var vertices = new Vector3[8];
|
||||
|
||||
// 下底面四个顶点
|
||||
vertices[0] = new Vector3(-halfSize.x, -halfSize.y, -halfSize.z);
|
||||
vertices[1] = new Vector3(halfSize.x, -halfSize.y, -halfSize.z);
|
||||
vertices[2] = new Vector3(halfSize.x, -halfSize.y, halfSize.z);
|
||||
vertices[3] = new Vector3(-halfSize.x, -halfSize.y, halfSize.z);
|
||||
|
||||
// 上顶面四个顶点
|
||||
vertices[4] = new Vector3(-halfSize.x, halfSize.y, -halfSize.z);
|
||||
vertices[5] = new Vector3(halfSize.x, halfSize.y, -halfSize.z);
|
||||
vertices[6] = new Vector3(halfSize.x, halfSize.y, halfSize.z);
|
||||
vertices[7] = new Vector3(-halfSize.x, halfSize.y, halfSize.z);
|
||||
|
||||
// 旋转顶点并转换到世界坐标系
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
vertices[i] = position + rotation * vertices[i];
|
||||
}
|
||||
|
||||
// 绘制立方体的十二条边
|
||||
DrawLine(vertices[0], vertices[1], color, duration); // 底面边
|
||||
DrawLine(vertices[1], vertices[2], color, duration);
|
||||
DrawLine(vertices[2], vertices[3], color, duration);
|
||||
DrawLine(vertices[3], vertices[0], color, duration);
|
||||
|
||||
DrawLine(vertices[4], vertices[5], color, duration); // 顶面边
|
||||
DrawLine(vertices[5], vertices[6], color, duration);
|
||||
DrawLine(vertices[6], vertices[7], color, duration);
|
||||
DrawLine(vertices[7], vertices[4], color, duration);
|
||||
|
||||
DrawLine(vertices[0], vertices[4], color, duration); // 竖向边
|
||||
DrawLine(vertices[1], vertices[5], color, duration);
|
||||
DrawLine(vertices[2], vertices[6], color, duration);
|
||||
DrawLine(vertices[3], vertices[7], color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCube(in Vector3 position, in Vector3 forward, in Vector3 size,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawCube(position, Quaternion.LookRotation(forward), size, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个圆柱体
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="rotation">朝向, 以圆柱的侧面为forward/right, 上下面为up</param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="segments"> </param>
|
||||
/// <param name="color"></param>
|
||||
/// <param name="duration"></param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCylinder(in Vector3 position, in Quaternion rotation, float radius, float height, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
var offset = rotation * Vector3.up * (height * 0.5f);
|
||||
var topCenter = position + offset;
|
||||
var bottomCenter = position - offset;
|
||||
DrawCylinder(topCenter, bottomCenter, radius, segments, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个圆柱体
|
||||
/// </summary>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="forward">朝向, forward是圆柱体的侧面(其实对于圆柱体而言forward/right都一样)</param>
|
||||
/// <param name="radius"></param>
|
||||
/// <param name="height"></param>
|
||||
/// <param name="segments"></param>
|
||||
/// <param name="color"></param>
|
||||
/// <param name="duration"></param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCylinder(in Vector3 position, in Vector3 forward, float radius, float height, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawCylinder(position, Quaternion.LookRotation(forward), radius, height, segments, in color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCylinder(in Vector3 topCenter, in Vector3 bottomCenter, float radius, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
if (radius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
if (Vector3.Distance(topCenter, bottomCenter) <= Mathf.Epsilon) return;
|
||||
|
||||
var direction = (topCenter - bottomCenter).normalized;
|
||||
|
||||
// 计算一个垂直于direction的向量作为forward
|
||||
var forward = Vector3.Cross(direction, Vector3.up).normalized;
|
||||
|
||||
// 如果forward和direction平行(即direction和Vector3.up共线),则选择一个不同的方向作为forward
|
||||
if (forward == Vector3.zero)
|
||||
{
|
||||
forward = Vector3.Cross(direction, Vector3.right).normalized;
|
||||
}
|
||||
|
||||
var rotation = Quaternion.LookRotation(forward, direction);
|
||||
|
||||
var angleStep = 360f / segments;
|
||||
var offset = rotation * (Vector3.right * radius);
|
||||
var lastTopPoint = topCenter + offset;
|
||||
var lastBottomPoint = bottomCenter + offset;
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var currentAngle = i * angleStep;
|
||||
offset = rotation * (Quaternion.Euler(0, currentAngle, 0) * (Vector3.right * radius));
|
||||
var nextTopPoint = topCenter + offset;
|
||||
var nextBottomPoint = bottomCenter + offset;
|
||||
DrawLine(lastTopPoint, nextTopPoint, color, duration);
|
||||
DrawLine(lastBottomPoint, nextBottomPoint, color, duration);
|
||||
DrawLine(nextTopPoint, nextBottomPoint, color, duration);
|
||||
lastTopPoint = nextTopPoint;
|
||||
lastBottomPoint = nextBottomPoint;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个扇形
|
||||
/// </summary>
|
||||
/// <param name="position">位置, 扇形所在圆的圆心</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="radius">扇形的半径</param>
|
||||
/// <param name="angle">扇形的角度(度)</param>
|
||||
/// <param name="segments">分段数(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawSector(Vector3 position, Quaternion rotation, float radius, float angle, int segments = 12,
|
||||
Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawAnnularSector(position, rotation, radius, 0, angle, segments, color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawSector(in Vector3 position, in Vector3 forward, float radius, float angle, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawSector(position, Quaternion.LookRotation(forward), radius, angle, segments, color, duration);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个环形扇区
|
||||
/// </summary>
|
||||
/// <param name="position">位置, 扇形所在圆的圆心</param>
|
||||
/// <param name="rotation">旋转</param>
|
||||
/// <param name="outerRadius">扇形的外半径</param>
|
||||
/// <param name="innerRadius">扇形的内半径, 为0时为标准扇形</param>
|
||||
/// <param name="angle">扇形的角度(度)</param>
|
||||
/// <param name="segments">分段数(建议与角度适配, 如每10°分一段: segments = Mathf.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawAnnularSector(in Vector3 position, in Quaternion rotation, float outerRadius, float innerRadius, float angle, int segments = 12,
|
||||
in Color? color = null, float duration = 0f)
|
||||
{
|
||||
if (outerRadius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
if (angle <= 0) return;
|
||||
|
||||
innerRadius = Mathf.Clamp(innerRadius, 0, outerRadius);
|
||||
|
||||
var angleStep = angle / segments;
|
||||
var currentAngle = -angle * 0.5f;
|
||||
|
||||
var outerPoints = new Vector3[segments + 1];
|
||||
var innerPoints = new Vector3[segments + 1];
|
||||
|
||||
// 计算内外扇形的点
|
||||
for (var i = 0; i <= segments; i++)
|
||||
{
|
||||
var currentDirection = rotation * Quaternion.Euler(0, currentAngle, 0) * Vector3.forward;
|
||||
outerPoints[i] = position + currentDirection * outerRadius;
|
||||
innerPoints[i] = position + currentDirection * innerRadius;
|
||||
currentAngle += angleStep;
|
||||
}
|
||||
|
||||
// 绘制内外扇形的边缘
|
||||
for (var i = 0; i <= segments; i++)
|
||||
{
|
||||
// 绘制内扇形的边缘
|
||||
if (i > 0)
|
||||
{
|
||||
DrawLine(innerPoints[i - 1], innerPoints[i], color, duration);
|
||||
DrawLine(outerPoints[i - 1], outerPoints[i], color, duration);
|
||||
}
|
||||
|
||||
// 绘制从中心到内外扇形点的线段
|
||||
DrawLine(innerPoints[i], outerPoints[i], color, duration);
|
||||
}
|
||||
|
||||
// 绘制内扇形的两侧边缘
|
||||
DrawLine(position, innerPoints[0], color, duration);
|
||||
DrawLine(position, innerPoints[segments], color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawAnnularSector(in Vector3 position, in Vector3 forward, float outerRadius, float innerRadius, float angle, int segments = 12, in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawAnnularSector(position, Quaternion.LookRotation(forward), outerRadius, innerRadius, angle, segments, color, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一条线段
|
||||
/// </summary>
|
||||
/// <param name="start"></param>
|
||||
/// <param name="end"></param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawLine(in Vector3 start, in Vector3 end, in Color? color = null, float duration = 0f)
|
||||
{
|
||||
Debug.DrawLine(start, end, color ?? Color.cyan, duration);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个箭头
|
||||
/// </summary>
|
||||
/// <param name="origin"></param>
|
||||
/// <param name="destination"></param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArrow(in Vector3 origin, in Vector3 destination, in Color? color = null, float duration = 0f)
|
||||
{
|
||||
DrawLine(origin, destination, color, duration);
|
||||
var direction = destination - origin;
|
||||
var right = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + 30, 0) * Vector3.forward;
|
||||
var left = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - 30, 0) * Vector3.forward;
|
||||
DrawLine(destination, destination + right, color, duration);
|
||||
DrawLine(destination, destination + left, color, duration);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 绘制一个箭头
|
||||
/// </summary>
|
||||
/// <param name="origin"></param>
|
||||
/// <param name="rotation"></param>
|
||||
/// <param name="length">长度</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArrow(in Vector3 origin, in Quaternion rotation, float length, in Color? color = null, float duration = 0f)
|
||||
{
|
||||
var direction = rotation * Vector3.forward;
|
||||
var destination = origin + direction * length;
|
||||
DrawArrow(origin, destination, color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArrow(in Vector3 origin, in Vector3 direction, float length, in Color? color = null, float duration = 0f)
|
||||
{
|
||||
var destination = origin + direction.normalized * length;
|
||||
DrawArrow(origin, destination, color, duration);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 517b2cdf2c8042bda08d8f5d0d4d8995
|
||||
timeCreated: 1709821901
|
@@ -0,0 +1,90 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
#endif
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public static class GASAnimatorUtil
|
||||
{
|
||||
/// <summary>
|
||||
/// Only For Editor
|
||||
/// </summary>
|
||||
/// <param name="animator"></param>
|
||||
/// <param name="layerIndex"></param>
|
||||
/// <returns></returns>
|
||||
public static Dictionary<string, AnimationClip> GetAllAnimationState(this Animator animator, int layerIndex = 0)
|
||||
{
|
||||
#pragma warning disable 162
|
||||
#if UNITY_EDITOR
|
||||
var result = new Dictionary<string, AnimationClip>();
|
||||
|
||||
var runtimeController = animator.runtimeAnimatorController;
|
||||
if (runtimeController == null)
|
||||
{
|
||||
Debug.LogError("RuntimeAnimatorController must not be null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (animator.runtimeAnimatorController is AnimatorOverrideController)
|
||||
{
|
||||
var overrideController =
|
||||
AssetDatabase.LoadAssetAtPath<AnimatorOverrideController>(
|
||||
AssetDatabase.GetAssetPath(runtimeController));
|
||||
if (overrideController == null)
|
||||
{
|
||||
Debug.LogErrorFormat("AnimatorOverrideController must not be null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var controller =
|
||||
AssetDatabase.LoadAssetAtPath<AnimatorController>(
|
||||
AssetDatabase.GetAssetPath(overrideController.runtimeAnimatorController));
|
||||
var overrides = new List<KeyValuePair<AnimationClip, AnimationClip>>();
|
||||
overrideController.GetOverrides(overrides);
|
||||
// 获取 Layer 的状态机
|
||||
var stateMachine = controller.layers[layerIndex].stateMachine;
|
||||
// 遍历所有状态并打印名称
|
||||
foreach (var state in stateMachine.states)
|
||||
{
|
||||
if (state.state.motion is AnimationClip clip)
|
||||
{
|
||||
foreach (var pair in overrides)
|
||||
{
|
||||
if (pair.Key.name == clip.name)
|
||||
{
|
||||
result.Add(state.state.name, pair.Value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.ContainsKey(state.state.name)) result.Add(state.state.name, clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var controller =
|
||||
AssetDatabase.LoadAssetAtPath<AnimatorController>(AssetDatabase.GetAssetPath(runtimeController));
|
||||
if (controller == null)
|
||||
{
|
||||
Debug.LogErrorFormat("AnimatorController must not be null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取第一个 Layer 的状态机
|
||||
var stateMachine = controller.layers[layerIndex].stateMachine;
|
||||
// 遍历所有状态并打印名称
|
||||
foreach (var state in stateMachine.states)
|
||||
result.Add(state.state.name, state.state.motion as AnimationClip);
|
||||
}
|
||||
|
||||
return result;
|
||||
#endif
|
||||
return null;
|
||||
#pragma warning restore 162
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 709355e72df34ede8668a186cad79f12
|
||||
timeCreated: 1709716282
|
@@ -0,0 +1,13 @@
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace System.Runtime.CompilerServices
|
||||
{
|
||||
/// <summary>
|
||||
/// 让Unity支持init setter
|
||||
/// <see href="https://docs.unity.cn/cn/2022.3/Manual/CSharpCompiler.html">unity文档</see>。
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public class IsExternalInit
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac5d19bb315743de9f460aae665ccd42
|
||||
timeCreated: 1723619186
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6f50d4142b94bc3b37233ff4806abf6
|
||||
timeCreated: 1723102748
|
@@ -0,0 +1,127 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public class ArrayPool<T>
|
||||
{
|
||||
private readonly int _maxCapacity;
|
||||
private readonly ConcurrentDictionary<int, Pool> _items = new();
|
||||
|
||||
public ArrayPool(int maxCapacity = 1024)
|
||||
{
|
||||
if (maxCapacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxCapacity), "maxCapacity must be greater than 0");
|
||||
}
|
||||
|
||||
_maxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
public T[] Fetch(int length)
|
||||
{
|
||||
return _items.TryGetValue(length, out var pool) ? pool.Get() : new T[length];
|
||||
}
|
||||
|
||||
public bool Recycle(T[] obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning("ArrayPool<T>.Recycle: obj is null");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_items.TryGetValue(obj.Length, out var queue))
|
||||
{
|
||||
Profiler.BeginSample("ArrayPoolEx<T>.CreateInstance");
|
||||
queue = _items[obj.Length] = new Pool(obj.Length, _maxCapacity);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
|
||||
return queue.Return(obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class Pool
|
||||
{
|
||||
private readonly int _arrayLength;
|
||||
private readonly int _maxCapacity;
|
||||
private int _numItems;
|
||||
private readonly ConcurrentQueue<T[]> _items = new();
|
||||
private T[] _fastItem;
|
||||
|
||||
public Pool(int arrayLength, int maxCapacity = 128)
|
||||
{
|
||||
if (_arrayLength < 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(arrayLength), "arrayLength must be greater than or equal to 0");
|
||||
}
|
||||
|
||||
if (maxCapacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxCapacity), "maxCapacity must be greater than 0");
|
||||
}
|
||||
|
||||
_arrayLength = arrayLength;
|
||||
_maxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
public T[] Get()
|
||||
{
|
||||
var item = _fastItem;
|
||||
if (item == null || Interlocked.CompareExchange(ref _fastItem, null, item) != item)
|
||||
{
|
||||
if (_items.TryDequeue(out item))
|
||||
{
|
||||
Interlocked.Decrement(ref _numItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
Profiler.BeginSample("ArrayPool<T>.Pool<TT>.CreateInstance");
|
||||
item = new T[_arrayLength];
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public bool Return(T[] obj)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj.Length != _arrayLength)
|
||||
{
|
||||
Debug.LogWarning($"Array length({obj.Length}) not match({_arrayLength})");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_fastItem != null || Interlocked.CompareExchange(ref _fastItem, obj, null) != null)
|
||||
{
|
||||
if (Interlocked.Increment(ref _numItems) <= _maxCapacity)
|
||||
{
|
||||
_items.Enqueue(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref _numItems);
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning($"ArrayPool<{typeof(T).FullName}>.Return: Exceed max capacity({_maxCapacity}), consider increase max capacity.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ce03b164d35f457b9ce6ff277db4e735
|
||||
timeCreated: 1723102741
|
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public interface IPool
|
||||
{
|
||||
bool IsFromPool { get; set; }
|
||||
}
|
||||
|
||||
public class JexGasObjectPool
|
||||
{
|
||||
private static JexGasObjectPool _singleton;
|
||||
|
||||
public static JexGasObjectPool Instance => _singleton ??= new JexGasObjectPool();
|
||||
|
||||
private readonly ConcurrentDictionary<Type, Pool> _objPool = new();
|
||||
|
||||
private readonly Func<Type, Pool> _addPoolFunc = type => new Pool(type, 1024);
|
||||
|
||||
public static void Awake()
|
||||
{
|
||||
_singleton = null;
|
||||
_singleton = new JexGasObjectPool();
|
||||
}
|
||||
|
||||
public static void Destroy()
|
||||
{
|
||||
_singleton = null;
|
||||
}
|
||||
|
||||
public T Fetch<T>() where T : class
|
||||
{
|
||||
var type = typeof(T);
|
||||
var obj = Fetch(type);
|
||||
return obj as T;
|
||||
}
|
||||
|
||||
public object Fetch(Type type, bool isFromPool = true)
|
||||
{
|
||||
object obj;
|
||||
|
||||
if (!isFromPool)
|
||||
{
|
||||
Profiler.BeginSample("ObjectPool.CreateInstance");
|
||||
obj = Activator.CreateInstance(type);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
else
|
||||
{
|
||||
var pool = GetPool(type);
|
||||
obj = pool.Get();
|
||||
if (obj is IPool p)
|
||||
{
|
||||
p.IsFromPool = true;
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public void Recycle(object obj)
|
||||
{
|
||||
if (obj is IPool p)
|
||||
{
|
||||
if (!p.IsFromPool)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// 防止多次入池
|
||||
p.IsFromPool = false;
|
||||
}
|
||||
|
||||
var type = obj.GetType();
|
||||
var pool = GetPool(type);
|
||||
pool.Return(obj);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private Pool GetPool(Type type)
|
||||
{
|
||||
return _objPool.GetOrAdd(type, _addPoolFunc);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 线程安全的无锁对象池
|
||||
/// </summary>
|
||||
private class Pool
|
||||
{
|
||||
private readonly Type _objectType;
|
||||
private readonly int _maxCapacity;
|
||||
private int _numItems;
|
||||
private readonly ConcurrentQueue<object> _items = new();
|
||||
private object _fastItem;
|
||||
|
||||
public Pool(Type objectType, int maxCapacity)
|
||||
{
|
||||
if (maxCapacity <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(maxCapacity), "maxCapacity must be greater than 0");
|
||||
}
|
||||
|
||||
_objectType = objectType;
|
||||
_maxCapacity = maxCapacity;
|
||||
}
|
||||
|
||||
public object Get()
|
||||
{
|
||||
object item = _fastItem;
|
||||
if (item == null || Interlocked.CompareExchange(ref _fastItem, null, item) != item)
|
||||
{
|
||||
if (_items.TryDequeue(out item))
|
||||
{
|
||||
Interlocked.Decrement(ref _numItems);
|
||||
}
|
||||
else
|
||||
{
|
||||
Profiler.BeginSample("Pool.CreateInstance");
|
||||
item = Activator.CreateInstance(_objectType);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
|
||||
public bool Return(object obj)
|
||||
{
|
||||
if (_fastItem != null || Interlocked.CompareExchange(ref _fastItem, obj, null) != null)
|
||||
{
|
||||
if (Interlocked.Increment(ref _numItems) <= _maxCapacity)
|
||||
{
|
||||
_items.Enqueue(obj);
|
||||
return true;
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref _numItems);
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning($"Pool<{_objectType.FullName}>.Return: Exceed max capacity({_maxCapacity}), consider increase max capacity.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d258d352f22a4281ab0968530960bee5
|
||||
timeCreated: 1713765955
|
@@ -0,0 +1,350 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
/// <summary>
|
||||
/// 按优先级取值
|
||||
/// <para>可以动态修改默认优先级和默认值, 适用于延迟初始化的情况</para>
|
||||
/// <para>通过设置默认值, 配合RemoveAll()使用可立刻恢复默认值</para>
|
||||
/// <para>一般来说默认值的优先级应该是最低的, 但是允许默认值的优先级高于其他值</para>
|
||||
/// <para>优先级可以相同, 则取优先级为: 默认值 > 先添加的值 > 后添加的值</para>
|
||||
/// </summary>
|
||||
public sealed class PriorityValue<T> : IDisposable
|
||||
{
|
||||
#if false //C# 10.0+
|
||||
public readonly record struct Data(string Key, int Priority, T Value);
|
||||
#else
|
||||
public record Data(string Key, int Priority, T Value)
|
||||
{
|
||||
public string Key { get; } = Key;
|
||||
public int Priority { get; } = Priority;
|
||||
public T Value { get; } = Value;
|
||||
}
|
||||
#endif
|
||||
|
||||
private readonly List<Data> _datas; // 按优先级降序排列
|
||||
private readonly object _lock;
|
||||
|
||||
public string CurrentKey
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _currentData.Key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int CurrentPriority
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _currentData.Priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T CurrentValue
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _currentData.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public string DefaultKey => "@default";
|
||||
|
||||
public int DefaultPriority
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _defaultData.Priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T DefaultValue
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
return _defaultData.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Data _defaultData;
|
||||
private Data _currentData;
|
||||
|
||||
public delegate void PostValueChanged(T oldValue, T newValue);
|
||||
|
||||
private event PostValueChanged OnPostValueChanged;
|
||||
|
||||
public PriorityValue() : this(default)
|
||||
{
|
||||
}
|
||||
|
||||
public PriorityValue(T defaultValue) :
|
||||
this(int.MinValue, defaultValue)
|
||||
{
|
||||
}
|
||||
|
||||
public PriorityValue(int defaultPriority, T defaultValue)
|
||||
{
|
||||
_defaultData = new(DefaultKey, defaultPriority, defaultValue);
|
||||
_currentData = _defaultData;
|
||||
_datas = new();
|
||||
_lock = new();
|
||||
}
|
||||
|
||||
public T Add(string key, int priority, T value)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
|
||||
|
||||
var index = _datas.FindIndex(data => data.Key == key);
|
||||
if (index < 0)
|
||||
{
|
||||
Insert(key, priority, value);
|
||||
Refresh();
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ArgumentException($"Key {key} already exists.");
|
||||
}
|
||||
|
||||
return CurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
public T AddOrSet(string key, int priority, T value)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(key)) throw new ArgumentNullException(nameof(key));
|
||||
|
||||
var index = _datas.FindIndex(data => data.Key == key);
|
||||
if (index >= 0)
|
||||
{
|
||||
if (_datas[index].Priority == priority)
|
||||
{
|
||||
_datas[index] = new(key, priority, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_datas.RemoveAt(index);
|
||||
Insert(key, priority, value);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Insert(key, priority, value);
|
||||
}
|
||||
|
||||
Refresh();
|
||||
|
||||
return CurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
private void Insert(string key, int priority, T value)
|
||||
{
|
||||
var data = new Data(key, priority, value);
|
||||
var insertIndex = _datas.Count;
|
||||
for (var i = 0; i < _datas.Count; i++)
|
||||
{
|
||||
if (priority > _datas[i].Priority)
|
||||
{
|
||||
insertIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_datas.Insert(insertIndex, data);
|
||||
}
|
||||
|
||||
public T SetDefault(int priority, T value)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_defaultData.Priority != priority || !EqualityComparer<T>.Default.Equals(_defaultData.Value, value))
|
||||
{
|
||||
_defaultData = new(DefaultKey, priority, value);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
return CurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
public T SetDefaultPriority(int priority)
|
||||
{
|
||||
return SetDefault(priority, DefaultValue);
|
||||
}
|
||||
|
||||
public T SetDefaultValue(T value)
|
||||
{
|
||||
return SetDefault(DefaultPriority, value);
|
||||
}
|
||||
|
||||
public T Remove(string key)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var index = _datas.FindIndex(data => data.Key == key);
|
||||
if (index >= 0)
|
||||
{
|
||||
_datas.RemoveAt(index);
|
||||
Refresh();
|
||||
}
|
||||
|
||||
return CurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
public T RemoveAll()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (_datas.Count > 0)
|
||||
{
|
||||
_datas.Clear();
|
||||
Refresh();
|
||||
}
|
||||
|
||||
return CurrentValue;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_currentData = _defaultData;
|
||||
_datas.Clear();
|
||||
OnPostValueChanged = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void Refresh()
|
||||
{
|
||||
var oldValue = _currentData.Value;
|
||||
|
||||
_currentData = _defaultData;
|
||||
|
||||
if (_datas.Count > 0)
|
||||
{
|
||||
var data = _datas[0];
|
||||
if (data.Priority > _defaultData.Priority)
|
||||
{
|
||||
_currentData = data;
|
||||
}
|
||||
}
|
||||
|
||||
if (EqualityComparer<T>.Default.Equals(oldValue, _currentData.Value) == false)
|
||||
{
|
||||
OnPostValueChanged?.Invoke(oldValue, _currentData.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void RegisterPostValueChanged(PostValueChanged listener)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
OnPostValueChanged += listener;
|
||||
}
|
||||
}
|
||||
|
||||
public void UnregisterPostValueChanged(PostValueChanged listener)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
OnPostValueChanged -= listener;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过Enable控制PriorityValue的某个key是否生效, 省去了手动管理Add/Remove等繁琐操作
|
||||
/// </summary>
|
||||
public sealed class PriorityValueToggle<T> : IDisposable
|
||||
{
|
||||
private PriorityValue<T>.Data _data;
|
||||
public PriorityValue<T> PriorityValue { get; }
|
||||
|
||||
public string Key => _data.Key;
|
||||
|
||||
public int Priority
|
||||
{
|
||||
get => _data.Priority;
|
||||
set
|
||||
{
|
||||
if (_data.Priority != value)
|
||||
{
|
||||
_data = new(Key, value, Value);
|
||||
if (Enabled)
|
||||
{
|
||||
PriorityValue?.AddOrSet(Key, Priority, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public T Value
|
||||
{
|
||||
get => _data.Value;
|
||||
set
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(_data.Value, value))
|
||||
{
|
||||
_data = new(Key, Priority, value);
|
||||
if (Enabled)
|
||||
{
|
||||
PriorityValue?.AddOrSet(Key, Priority, Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enabled;
|
||||
|
||||
public bool Enabled
|
||||
{
|
||||
get => _enabled;
|
||||
set
|
||||
{
|
||||
if (_enabled != value)
|
||||
{
|
||||
_enabled = value;
|
||||
if (_enabled) PriorityValue?.AddOrSet(Key, Priority, Value);
|
||||
else PriorityValue?.Remove(Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public PriorityValueToggle(PriorityValue<T> priorityValue, string key, int priority, T value, bool enableImmediately = false)
|
||||
{
|
||||
PriorityValue = priorityValue;
|
||||
_data = new(key, priority, value);
|
||||
|
||||
if (enableImmediately)
|
||||
Enabled = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fa92b69503043a58c6b9b79b15b8f2c
|
||||
timeCreated: 1719295275
|
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public static class TypeUtil
|
||||
{
|
||||
public static Type[] GetAllSonTypesOf(Type parentType)
|
||||
{
|
||||
List<Type> sonTypes = new List<Type>();
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
sonTypes.AddRange(types.Where(type => type.IsSubclassOf(parentType) && !type.IsAbstract));
|
||||
}
|
||||
catch (ReflectionTypeLoadException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
return sonTypes.ToArray();
|
||||
}
|
||||
|
||||
public static Type FindTypeInAllAssemblies(string typeName)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
Type type = assembly.GetType(typeName);
|
||||
if (type != null)
|
||||
{
|
||||
// 如果找到了类型,返回它
|
||||
return type;
|
||||
}
|
||||
}
|
||||
catch (ReflectionTypeLoadException)
|
||||
{
|
||||
// 忽略因无法加载类型而引发的异常
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到类型,返回null
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取类型的继承链
|
||||
/// </summary>
|
||||
public static string[] GetInheritanceChain(this Type type, bool fullName = true)
|
||||
{
|
||||
var inheritanceChain = new List<string>();
|
||||
var currentType = type;
|
||||
|
||||
while (currentType != null)
|
||||
{
|
||||
var name = fullName ? currentType.FullName : currentType.Name;
|
||||
inheritanceChain.Add(name);
|
||||
currentType = currentType.BaseType;
|
||||
}
|
||||
|
||||
return inheritanceChain.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d45910d5e40e47ac99874b673b71078d
|
||||
timeCreated: 1709442037
|
@@ -0,0 +1,45 @@
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace GAS.General.Validation
|
||||
{
|
||||
public readonly struct ValidationResult
|
||||
{
|
||||
public readonly bool IsValid;
|
||||
public readonly string Message;
|
||||
|
||||
public ValidationResult(bool isValid, string message)
|
||||
{
|
||||
IsValid = isValid;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static readonly ValidationResult Valid = new ValidationResult(true, null);
|
||||
public static ValidationResult Invalid(string message) => new ValidationResult(false, message);
|
||||
}
|
||||
|
||||
public delegate ValidationResult ValidationDelegate(string input);
|
||||
|
||||
public static class Validations
|
||||
{
|
||||
// https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/coding-style/identifier-names
|
||||
// 可以在标识符上使用 @ 前缀来声明与 C# 关键字匹配的标识符。 @ 不是标识符名称的一部分。 例如,@if 声明名为 if 的标识符。
|
||||
// 因此类似 @123abc 这样的标识符是不合法的。因为抛开@之后, 它实际上是以数字开头。
|
||||
private const string VariableNamePattern = @"^@?[a-zA-Z_][a-zA-Z0-9_]*$";
|
||||
public static readonly Regex VariableNameRegex = new Regex(VariableNamePattern);
|
||||
|
||||
public static ValidationResult ValidateVariableName(string name)
|
||||
{
|
||||
if (string.IsNullOrEmpty(name))
|
||||
return ValidationResult.Invalid("The name is empty!");
|
||||
|
||||
return VariableNameRegex.IsMatch(name)
|
||||
? ValidationResult.Valid
|
||||
: ValidationResult.Invalid($"The name(\"{name}\") is invalid!");
|
||||
}
|
||||
|
||||
public static bool IsValidVariableName(string name)
|
||||
{
|
||||
return ValidateVariableName(name).IsValid;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51efeef0278341a9ab883d49a20bd485
|
||||
timeCreated: 1711514345
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cddbd5a2fd644ea68b4cf1d44faea8bf
|
||||
timeCreated: 1701928931
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9aa0e5d7c3f14b199f81ae0c71c9f494
|
||||
timeCreated: 1701938781
|
@@ -0,0 +1,12 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public enum AbilityActivateResult
|
||||
{
|
||||
Success,
|
||||
FailHasActivated,
|
||||
FailTagRequirement,
|
||||
FailCost,
|
||||
FailCooldown,
|
||||
FailOtherReason
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c4e3a515a6c4662a1d3a3abd8aad589
|
||||
timeCreated: 1711357854
|
@@ -0,0 +1,156 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using GAS.General;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class AbilityAsset : ScriptableObject
|
||||
{
|
||||
protected const int WIDTH_LABEL = 70;
|
||||
|
||||
public abstract Type AbilityType();
|
||||
|
||||
[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;
|
||||
|
||||
[TabGroup("Base/H1/V2", "General", SdfIconType.AwardFill, TextColor = "#FF7F00", Order = 2)]
|
||||
[LabelText("所属能力", SdfIconType.FileCodeFill)]
|
||||
[LabelWidth(WIDTH_LABEL)]
|
||||
[ShowInInspector]
|
||||
[ValidateInput("@AbilityType() != null", "Ability Class is NULL!!! Please check.")]
|
||||
[PropertyOrder(-1)]
|
||||
public string InstanceAbilityClassFullName => AbilityType() != null ? AbilityType().FullName : null;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
[TabGroup("Base/H1/V2", "General")]
|
||||
[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
|
||||
|
||||
[TabGroup("Base/H1/V2", "General", SdfIconType.AwardFill)]
|
||||
[InfoBox(GASTextDefine.TIP_UNAME, InfoMessageType.None)]
|
||||
[LabelText("U-Name", SdfIconType.Fingerprint)]
|
||||
[LabelWidth(WIDTH_LABEL)]
|
||||
[ValidateInput("@GAS.General.Validation.Validations.IsValidVariableName($value)", "无效的名字 - 不符合C#标识符命名规则")]
|
||||
[InlineButton("@UniqueName = name", "Auto", Icon = SdfIconType.Hammer)]
|
||||
public string UniqueName;
|
||||
|
||||
[TabGroup("Base/H1/V2", "General")]
|
||||
[Title("消耗&冷却", bold: true)]
|
||||
[LabelWidth(WIDTH_LABEL)]
|
||||
[AssetSelector]
|
||||
[LabelText(SdfIconType.HeartHalf, Text = GASTextDefine.ABILITY_EFFECT_COST)]
|
||||
public GameplayEffectAsset Cost;
|
||||
|
||||
[TabGroup("Base/H1/V2", "General")]
|
||||
[LabelWidth(WIDTH_LABEL)]
|
||||
[AssetSelector]
|
||||
[LabelText(SdfIconType.StopwatchFill, Text = GASTextDefine.ABILITY_EFFECT_CD)]
|
||||
public GameplayEffectAsset Cooldown;
|
||||
|
||||
[TabGroup("Base/H1/V2", "General")]
|
||||
[LabelWidth(WIDTH_LABEL)]
|
||||
[LabelText(SdfIconType.ClockFill, Text = GASTextDefine.ABILITY_CD_TIME)]
|
||||
[Unit(Units.Second)]
|
||||
public float CooldownTime;
|
||||
|
||||
// Tags
|
||||
[TabGroup("Base/H1/V3", "Tags", SdfIconType.TagsFill, TextColor = "#45B1FF", Order = 3)]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@AssetTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[Tooltip("描述性质的标签,用来描述Ability的特性表现,比如伤害、治疗、控制等。")]
|
||||
[FormerlySerializedAs("AssetTag")]
|
||||
public GameplayTag[] AssetTags;
|
||||
|
||||
|
||||
[Space]
|
||||
[TabGroup("Base/H1/V3", "Tags")]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@CancelAbilityTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[LabelText("CancelAbility With Tags ")]
|
||||
[Tooltip("Ability激活时,Ability持有者当前持有的所有Ability中,拥有【任意】这些标签的Ability会被取消。")]
|
||||
public GameplayTag[] CancelAbilityTags;
|
||||
|
||||
[Space]
|
||||
[TabGroup("Base/H1/V3", "Tags")]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@BlockAbilityTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[LabelText("BlockAbility With Tags ")]
|
||||
[Tooltip("Ability激活时,Ability持有者当前持有的所有Ability中,拥有【任意】这些标签的Ability会被阻塞激活。")]
|
||||
public GameplayTag[] BlockAbilityTags;
|
||||
|
||||
[Space]
|
||||
[TabGroup("Base/H1/V3", "Tags")]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@ActivationOwnedTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[Tooltip("Ability激活时,持有者会获得这些标签,Ability被失活时,这些标签也会被移除。")]
|
||||
[FormerlySerializedAs("ActivationOwnedTag")]
|
||||
public GameplayTag[] ActivationOwnedTags;
|
||||
|
||||
[Space]
|
||||
[TabGroup("Base/H1/V3", "Tags")]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@ActivationRequiredTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[Tooltip("Ability只有在其拥有者拥有【所有】这些标签时才可激活。")]
|
||||
public GameplayTag[] ActivationRequiredTags;
|
||||
|
||||
[Space]
|
||||
[TabGroup("Base/H1/V3", "Tags")]
|
||||
[ListDrawerSettings(ShowFoldout = true, ShowItemCount = false, DraggableItems = false)]
|
||||
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
|
||||
[CustomContextMenu("排序", "@ActivationBlockedTags = TagHelper.Sort($value)")]
|
||||
[ValueDropdown("@ValueDropdownHelper.GameplayTagChoices", IsUniqueList = true, HideChildProperties = true)]
|
||||
[Tooltip("Ability在其拥有者拥有【任意】这些标签时不能被激活。")]
|
||||
public GameplayTag[] ActivationBlockedTags;
|
||||
// public GameplayTag[] SourceRequiredTags;
|
||||
// public GameplayTag[] SourceBlockedTags;
|
||||
// public GameplayTag[] TargetRequiredTags;
|
||||
// public GameplayTag[] TargetBlockedTags;
|
||||
}
|
||||
|
||||
|
||||
public abstract class AbilityAssetT<T> : AbilityAsset where T : class
|
||||
{
|
||||
public sealed override Type AbilityType()
|
||||
{
|
||||
return typeof(T);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 544108d1f0e34b7fb2a77be04ed89be6
|
||||
timeCreated: 1703762750
|
@@ -0,0 +1,121 @@
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class AbilityContainer
|
||||
{
|
||||
private readonly AbilitySystemComponent _owner;
|
||||
private readonly Dictionary<string, AbilitySpec> _abilities = new();
|
||||
|
||||
public AbilityContainer(AbilitySystemComponent owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
var abilitySpecs = JexGasObjectPool.Instance.Fetch<List<AbilitySpec>>();
|
||||
abilitySpecs.AddRange(_abilities.Values);
|
||||
|
||||
foreach (var abilitySpec in abilitySpecs)
|
||||
{
|
||||
abilitySpec.Tick();
|
||||
}
|
||||
|
||||
abilitySpecs.Clear();
|
||||
JexGasObjectPool.Instance.Recycle(abilitySpecs);
|
||||
}
|
||||
|
||||
public void GrantAbility(AbstractAbility ability)
|
||||
{
|
||||
if (_abilities.ContainsKey(ability.Name)) return;
|
||||
var abilitySpec = ability.CreateSpec(_owner);
|
||||
_abilities.Add(ability.Name, abilitySpec);
|
||||
}
|
||||
|
||||
public void RemoveAbility(AbstractAbility ability)
|
||||
{
|
||||
RemoveAbility(ability.Name);
|
||||
}
|
||||
|
||||
public void RemoveAbility(string abilityName)
|
||||
{
|
||||
if (!_abilities.ContainsKey(abilityName)) return;
|
||||
|
||||
EndAbility(abilityName);
|
||||
_abilities[abilityName].Dispose();
|
||||
_abilities.Remove(abilityName);
|
||||
}
|
||||
|
||||
public bool TryActivateAbility(string abilityName, object arg = null, GameplayEffectSpec gameplayEffectSpec = null)
|
||||
{
|
||||
if (!_abilities.ContainsKey(abilityName))
|
||||
{
|
||||
// 开发指南:
|
||||
// 如果你的Preset里配置了固有技能却没该技能(甚至_abilities里一个技能都没有)
|
||||
// 可能是你忘记调用ASC::Init(), 请检查AbilitySystemComponent的初始化
|
||||
// 通常我们使用ASC::InitWithPreset()来间接调用ASC::Init()执行初始化
|
||||
#if UNITY_EDITOR
|
||||
// 这个输出可以删掉, 某些情况下确实会尝试激活不存在的技能(失败了也无所谓), 但是对开发期间的调试有帮助
|
||||
Debug.LogWarning(
|
||||
$"you are trying to activate an ability that does not exist: " +
|
||||
$"abilityName=\"{abilityName}\", GameObject=\"{_owner.Id}\", " +
|
||||
$"Preset={(_owner.Preset != null ? _owner.Preset.name : "null")}");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_abilities[abilityName].TryActivateAbility(arg, gameplayEffectSpec)) return false;
|
||||
|
||||
var tags = _abilities[abilityName].Ability.Tag.CancelAbilitiesWithTags;
|
||||
foreach (var kv in _abilities)
|
||||
{
|
||||
var abilityTag = kv.Value.Ability.Tag;
|
||||
if (abilityTag.AssetTag.HasAnyTags(tags))
|
||||
{
|
||||
_abilities[kv.Key].TryCancelAbility();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void EndAbility(string abilityName)
|
||||
{
|
||||
if (!_abilities.ContainsKey(abilityName)) return;
|
||||
_abilities[abilityName].TryEndAbility();
|
||||
}
|
||||
|
||||
public void CancelAbility(string abilityName)
|
||||
{
|
||||
if (!_abilities.ContainsKey(abilityName)) return;
|
||||
_abilities[abilityName].TryCancelAbility();
|
||||
}
|
||||
|
||||
private void CancelAbilitiesByTag(in GameplayTagSet tags)
|
||||
{
|
||||
foreach (var kv in _abilities)
|
||||
{
|
||||
var abilityTag = kv.Value.Ability.Tag;
|
||||
if (abilityTag.AssetTag.HasAnyTags(tags))
|
||||
{
|
||||
_abilities[kv.Key].TryCancelAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Dictionary<string, AbilitySpec> AbilitySpecs() => _abilities;
|
||||
|
||||
public void CancelAllAbilities()
|
||||
{
|
||||
foreach (var kv in _abilities)
|
||||
_abilities[kv.Key].TryCancelAbility();
|
||||
}
|
||||
|
||||
public bool HasAbility(string abilityName) => _abilities.ContainsKey(abilityName);
|
||||
|
||||
public bool HasAbility(AbstractAbility ability) => HasAbility(ability.Name);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 45b14d2f214f400ab305f80a6f8cb3a2
|
||||
timeCreated: 1703663556
|
@@ -0,0 +1,10 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public struct AbilityInstanceInfo
|
||||
{
|
||||
public AbilityAsset abilityAsset;
|
||||
public Type abilityType;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99ad85c3caba4604b9950ae1105e85b4
|
||||
timeCreated: 1706090943
|
@@ -0,0 +1,243 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class AbilitySpec
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取激活能力时传递给能力的参数。
|
||||
/// 在技能过程中不应该修改, 考虑使用UserData
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>旧版本为 object[] 类型, 有内存分配和装箱/拆箱问题, 应自行创建一个数据结构(推荐record)来传参.</para>
|
||||
/// </remarks>
|
||||
public object AbilityArgument { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 仅限GrantedAbility, 激活能力时传递给能力的效果规格。
|
||||
/// 可以通过给gameplayEffectSpec添加自定义数据(UserData)来传递更多信息。
|
||||
/// </summary>
|
||||
public EntityRef<GameplayEffectSpec> GameplayEffectSpec { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取或设置与能力关联的自定义数据。
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>此属性用于存储能力的自定义信息,以便在能力的不同任务之间共享数据。</para>
|
||||
/// <para>例如,可以在一个技能的任务(AbilityTask)中设置此数据,然后在同一个技能的另一个任务(AbilityTask)中检索和使用该数据。</para>
|
||||
/// </remarks>
|
||||
public object UserData { get; set; }
|
||||
|
||||
public AbilitySpec(AbstractAbility ability, AbilitySystemComponent owner)
|
||||
{
|
||||
Ability = ability;
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_onActivateResult = null;
|
||||
_onEndAbility = null;
|
||||
_onCancelAbility = null;
|
||||
}
|
||||
|
||||
public AbstractAbility Ability { get; }
|
||||
|
||||
public AbilitySystemComponent Owner { get; protected set; }
|
||||
|
||||
public int Level { get; protected set; }
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public int ActiveCount { get; private set; }
|
||||
protected event Action<AbilityActivateResult> _onActivateResult;
|
||||
protected event Action _onEndAbility;
|
||||
protected event Action _onCancelAbility;
|
||||
|
||||
public void RegisterActivateResult(Action<AbilityActivateResult> onActivateResult)
|
||||
{
|
||||
_onActivateResult += onActivateResult;
|
||||
}
|
||||
|
||||
public void UnregisterActivateResult(Action<AbilityActivateResult> onActivateResult)
|
||||
{
|
||||
_onActivateResult -= onActivateResult;
|
||||
}
|
||||
|
||||
public void RegisterEndAbility(Action onEndAbility)
|
||||
{
|
||||
_onEndAbility += onEndAbility;
|
||||
}
|
||||
|
||||
public void UnregisterEndAbility(Action onEndAbility)
|
||||
{
|
||||
_onEndAbility -= onEndAbility;
|
||||
}
|
||||
|
||||
public void RegisterCancelAbility(Action onCancelAbility)
|
||||
{
|
||||
_onCancelAbility += onCancelAbility;
|
||||
}
|
||||
|
||||
public void UnregisterCancelAbility(Action onCancelAbility)
|
||||
{
|
||||
_onCancelAbility -= onCancelAbility;
|
||||
}
|
||||
|
||||
public virtual void SetLevel(int level)
|
||||
{
|
||||
Level = level;
|
||||
}
|
||||
|
||||
public virtual AbilityActivateResult CanActivate()
|
||||
{
|
||||
if (IsActive) return AbilityActivateResult.FailHasActivated;
|
||||
if (!CheckGameplayTagsValidTpActivate()) return AbilityActivateResult.FailTagRequirement;
|
||||
if (!CheckCost()) return AbilityActivateResult.FailCost;
|
||||
if (CheckCooldown().TimeRemaining > 0) return AbilityActivateResult.FailCooldown;
|
||||
|
||||
return AbilityActivateResult.Success;
|
||||
}
|
||||
|
||||
private bool CheckGameplayTagsValidTpActivate()
|
||||
{
|
||||
var hasAllTags = Owner.HasAllTags(Ability.Tag.ActivationRequiredTags);
|
||||
var notHasAnyTags = !Owner.HasAnyTags(Ability.Tag.ActivationBlockedTags);
|
||||
var notBlockedByOtherAbility = true;
|
||||
|
||||
foreach (var kv in Owner.AbilityContainer.AbilitySpecs())
|
||||
{
|
||||
var abilitySpec = kv.Value;
|
||||
if (abilitySpec.IsActive)
|
||||
if (Ability.Tag.AssetTag.HasAnyTags(abilitySpec.Ability.Tag.BlockAbilitiesWithTags))
|
||||
{
|
||||
notBlockedByOtherAbility = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return hasAllTags && notHasAnyTags && notBlockedByOtherAbility;
|
||||
}
|
||||
|
||||
protected virtual bool CheckCost()
|
||||
{
|
||||
if (Ability.Cost == null) return true;
|
||||
var costSpec = Ability.Cost.CreateSpec(Owner, Owner, Level);
|
||||
if (costSpec.Value == null) return false;
|
||||
|
||||
if (Ability.Cost.DurationPolicy != EffectsDurationPolicy.Instant) return true;
|
||||
|
||||
foreach (var modifier in Ability.Cost.Modifiers)
|
||||
{
|
||||
// 常规来说消耗是减法, 但是加一个负数也应该被视为减法
|
||||
if (modifier.Operation != GEOperation.Add && modifier.Operation != GEOperation.Minus) continue;
|
||||
|
||||
var costValue = modifier.CalculateMagnitude(costSpec, modifier.ModiferMagnitude);
|
||||
var attributeCurrentValue =
|
||||
Owner.GetAttributeCurrentValue(modifier.AttributeSetName, modifier.AttributeShortName);
|
||||
|
||||
if (modifier.Operation == GEOperation.Add)
|
||||
if (attributeCurrentValue + costValue < 0)
|
||||
return false;
|
||||
|
||||
if (modifier.Operation == GEOperation.Minus)
|
||||
if (attributeCurrentValue - costValue < 0)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected virtual CooldownTimer CheckCooldown()
|
||||
{
|
||||
return Ability.Cooldown == null
|
||||
? new CooldownTimer { TimeRemaining = 0, Duration = Ability.CooldownTime }
|
||||
: Owner.CheckCooldownFromTags(Ability.Cooldown.TagContainer.GrantedTags);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Some skills include wind-up and follow-through, where the wind-up may be interrupted, causing the skill not to be
|
||||
/// successfully released.
|
||||
/// Therefore, the actual timing and logic of skill release (triggering costs and initiating cooldown) should be
|
||||
/// determined by developers within the AbilitySpec,
|
||||
/// rather than being systematically standardized.
|
||||
/// </summary>
|
||||
public void DoCost()
|
||||
{
|
||||
if (Ability.Cost != null) Owner.ApplyGameplayEffectToSelf(Ability.Cost);
|
||||
|
||||
if (Ability.Cooldown != null)
|
||||
{
|
||||
var cdSpec = Owner.ApplyGameplayEffectToSelf(Ability.Cooldown);
|
||||
cdSpec.Value.SetDuration(Ability.CooldownTime); // Actually, it should be set by the ability's cooldown time.
|
||||
}
|
||||
}
|
||||
|
||||
public virtual bool TryActivateAbility(object arg = null, GameplayEffectSpec gameplayEffectSpec = null)
|
||||
{
|
||||
AbilityArgument = arg;
|
||||
GameplayEffectSpec = gameplayEffectSpec;
|
||||
|
||||
var result = CanActivate();
|
||||
var success = result == AbilityActivateResult.Success;
|
||||
if (success)
|
||||
{
|
||||
IsActive = true;
|
||||
ActiveCount++;
|
||||
Owner.GameplayTagAggregator.ApplyGameplayAbilityDynamicTag(this);
|
||||
|
||||
ActivateAbility();
|
||||
}
|
||||
|
||||
_onActivateResult?.Invoke(result);
|
||||
return success;
|
||||
}
|
||||
|
||||
public virtual void TryEndAbility()
|
||||
{
|
||||
if (!IsActive) return;
|
||||
IsActive = false;
|
||||
Owner.GameplayTagAggregator.RestoreGameplayAbilityDynamicTags(this);
|
||||
EndAbility();
|
||||
_onEndAbility?.Invoke();
|
||||
}
|
||||
|
||||
public virtual void TryCancelAbility()
|
||||
{
|
||||
if (!IsActive) return;
|
||||
IsActive = false;
|
||||
|
||||
Owner.GameplayTagAggregator.RestoreGameplayAbilityDynamicTags(this);
|
||||
CancelAbility();
|
||||
_onCancelAbility?.Invoke();
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (IsActive)
|
||||
{
|
||||
AbilityTick();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AbilityTick()
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void ActivateAbility();
|
||||
|
||||
public abstract void CancelAbility();
|
||||
|
||||
public abstract void EndAbility();
|
||||
}
|
||||
|
||||
public abstract class AbilitySpec<T> : AbilitySpec where T : AbstractAbility
|
||||
{
|
||||
public T Data { get; private set; }
|
||||
|
||||
protected AbilitySpec(T ability, AbilitySystemComponent owner) : base(ability, owner)
|
||||
{
|
||||
Data = ability;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1dfde8ae93348678664a5e08769ffdb
|
||||
timeCreated: 1702894219
|
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com/BillEliot/GASDocumentation_Chinese?tab=readme-ov-file#4610-gameplay-ability-spec
|
||||
/// goto 4.6.9 Ability Tag
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public struct AbilityTagContainer
|
||||
{
|
||||
public GameplayTagSet AssetTag;
|
||||
|
||||
public GameplayTagSet CancelAbilitiesWithTags;
|
||||
public GameplayTagSet BlockAbilitiesWithTags;
|
||||
|
||||
public GameplayTagSet ActivationOwnedTag;
|
||||
|
||||
public GameplayTagSet ActivationRequiredTags;
|
||||
public GameplayTagSet ActivationBlockedTags;
|
||||
|
||||
// // TODO
|
||||
// public GameplayTagSet SourceRequiredTags;
|
||||
// public GameplayTagSet SourceBlockedTags;
|
||||
//
|
||||
// // TODO
|
||||
// public GameplayTagSet TargetRequiredTags;
|
||||
// public GameplayTagSet TargetBlockedTags;
|
||||
|
||||
public AbilityTagContainer(
|
||||
GameplayTag[] assetTags,
|
||||
GameplayTag[] cancelAbilityTags,
|
||||
GameplayTag[] blockAbilityTags,
|
||||
GameplayTag[] activationOwnedTag,
|
||||
GameplayTag[] activationRequiredTags,
|
||||
GameplayTag[] activationBlockedTags)
|
||||
{
|
||||
AssetTag = new GameplayTagSet(assetTags);
|
||||
CancelAbilitiesWithTags = new GameplayTagSet(cancelAbilityTags);
|
||||
BlockAbilitiesWithTags = new GameplayTagSet(blockAbilityTags);
|
||||
ActivationOwnedTag = new GameplayTagSet(activationOwnedTag);
|
||||
ActivationRequiredTags = new GameplayTagSet(activationRequiredTags);
|
||||
ActivationBlockedTags = new GameplayTagSet(activationBlockedTags);
|
||||
// SourceRequiredTags = new GameplayTagSet(sourceRequiredTags);
|
||||
// SourceBlockedTags = new GameplayTagSet(sourceBlockedTags);
|
||||
// TargetRequiredTags = new GameplayTagSet(targetRequiredTags);
|
||||
// TargetBlockedTags = new GameplayTagSet(targetBlockedTags);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a29c341083404ab0bbaa4d8feb6d3f05
|
||||
timeCreated: 1703496738
|
@@ -0,0 +1,76 @@
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class AbstractAbility
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly AbilityAsset DataReference;
|
||||
|
||||
// TODO : AbilityTask
|
||||
// public List<OngoingAbilityTask> OngoingAbilityTasks=new List<OngoingAbilityTask>();
|
||||
// public List<AsyncAbilityTask> AsyncAbilityTasks = new List<AsyncAbilityTask>();
|
||||
|
||||
public AbilityTagContainer Tag { get; protected set; }
|
||||
|
||||
public GameplayEffect Cooldown { get; protected set; }
|
||||
|
||||
public float CooldownTime { get; protected set; }
|
||||
|
||||
public GameplayEffect Cost { get; protected set; }
|
||||
|
||||
public AbstractAbility(AbilityAsset abilityAsset)
|
||||
{
|
||||
DataReference = abilityAsset;
|
||||
|
||||
Name = DataReference.UniqueName;
|
||||
Tag = new AbilityTagContainer(
|
||||
DataReference.AssetTags, DataReference.CancelAbilityTags, DataReference.BlockAbilityTags,
|
||||
DataReference.ActivationOwnedTags, DataReference.ActivationRequiredTags, DataReference.ActivationBlockedTags);
|
||||
Cooldown = DataReference.Cooldown ? DataReference.Cooldown.SharedInstance : default;
|
||||
Cost = DataReference.Cost ? DataReference.Cost.SharedInstance: default;
|
||||
|
||||
CooldownTime = DataReference.CooldownTime;
|
||||
}
|
||||
|
||||
public abstract AbilitySpec CreateSpec(AbilitySystemComponent owner);
|
||||
|
||||
public void SetCooldown(GameplayEffect coolDown)
|
||||
{
|
||||
if (coolDown.DurationPolicy == EffectsDurationPolicy.Duration)
|
||||
{
|
||||
Cooldown = coolDown;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError("[EX] Cooldown must be duration policy!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void SetCost(GameplayEffect cost)
|
||||
{
|
||||
if (cost.DurationPolicy == EffectsDurationPolicy.Instant)
|
||||
{
|
||||
Cost = cost;
|
||||
}
|
||||
#if UNITY_EDITOR
|
||||
else
|
||||
{
|
||||
UnityEngine.Debug.LogError("[EX] Cost must be instant policy!");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class AbstractAbility<T> : AbstractAbility where T : AbilityAsset
|
||||
{
|
||||
public T AbilityAsset => DataReference as T;
|
||||
|
||||
protected AbstractAbility(T abilityAsset) : base(abilityAsset)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5e9b50ca889544518c8aeb712988a44e
|
||||
timeCreated: 1701938761
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b3dda2fee2e4fbe83d66dc080bde768
|
||||
timeCreated: 1709451484
|
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using GAS.Runtime;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class CatchAreaBase : TargetCatcherBase
|
||||
{
|
||||
public LayerMask checkLayer;
|
||||
|
||||
public void Init(AbilitySystemComponent owner, LayerMask checkLayer)
|
||||
{
|
||||
base.Init(owner);
|
||||
this.checkLayer = checkLayer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58b4935f7a5a43d69506e28666032462
|
||||
timeCreated: 1709451834
|
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public sealed class CatchSelf : TargetCatcherBase
|
||||
{
|
||||
protected override void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results)
|
||||
{
|
||||
results.Add(Owner);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be9321649ff24febb2f8a1c8e0ec2e4c
|
||||
timeCreated: 1709451646
|
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public sealed class CatchTarget : TargetCatcherBase
|
||||
{
|
||||
protected override void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results)
|
||||
{
|
||||
results.Add(mainTarget);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42c991b40b9d414fb7b87d2c7d44c078
|
||||
timeCreated: 1709534433
|
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class TargetCatcherBase
|
||||
{
|
||||
public AbilitySystemComponent Owner;
|
||||
|
||||
protected TargetCatcherBase()
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void Init(AbilitySystemComponent owner)
|
||||
{
|
||||
Owner = owner;
|
||||
}
|
||||
|
||||
[Obsolete("请使用CatchTargetsNonAlloc方法来避免产生垃圾收集(GC)。")]
|
||||
public List<AbilitySystemComponent> CatchTargets(AbilitySystemComponent mainTarget)
|
||||
{
|
||||
var result = new List<AbilitySystemComponent>();
|
||||
|
||||
CatchTargetsNonAlloc(mainTarget, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void CatchTargetsNonAllocSafe(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results)
|
||||
{
|
||||
results.Clear();
|
||||
|
||||
CatchTargetsNonAlloc(mainTarget, results);
|
||||
}
|
||||
|
||||
protected abstract void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public virtual void OnEditorPreview(GameObject obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7720c3db62774f24943b28e0160c291c
|
||||
timeCreated: 1709451499
|
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6ac50e393ed67b746ac26401c5f59f8c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 459d02062f69400c85df659d87d8ed9a
|
||||
timeCreated: 1701938994
|
@@ -0,0 +1,12 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class AbilityTaskBase
|
||||
{
|
||||
protected AbilitySpec _spec;
|
||||
public AbilitySpec Spec => _spec;
|
||||
public virtual void Init(AbilitySpec spec)
|
||||
{
|
||||
_spec = spec;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b4936b5b85e340a0ae770d010de0b8d6
|
||||
timeCreated: 1702888759
|
@@ -0,0 +1,21 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class InstantAbilityTask : AbilityTaskBase
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器预览用
|
||||
/// 【注意】 覆写时,记得用UNITY_EDITOR宏包裹,这是预览表现用的函数,不该被编译。
|
||||
/// </summary>
|
||||
public virtual void OnEditorPreview()
|
||||
{
|
||||
}
|
||||
#endif
|
||||
public abstract void OnExecute();
|
||||
}
|
||||
|
||||
public abstract class InstantAbilityTaskT<T> : InstantAbilityTask where T : AbilitySpec
|
||||
{
|
||||
public new T Spec => (T)_spec;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1002d6c36c354bf1845dd82d12e4aca1
|
||||
timeCreated: 1702889566
|
@@ -0,0 +1,28 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class OngoingAbilityTask : AbilityTaskBase
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器预览用
|
||||
/// 【注意】 覆写时,记得用UNITY_EDITOR宏包裹,这是预览表现用的函数,不该被编译。
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
/// <param name="startFrame"></param>
|
||||
/// <param name="endFrame"></param>
|
||||
public virtual void OnEditorPreview(int frame, int startFrame, int endFrame)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
public abstract void OnStart(int startFrame);
|
||||
|
||||
public abstract void OnEnd(int endFrame);
|
||||
|
||||
public abstract void OnTick(int frameIndex, int startFrame, int endFrame);
|
||||
}
|
||||
|
||||
public abstract class OngoingAbilityTaskT<T> : OngoingAbilityTask where T : AbilitySpec
|
||||
{
|
||||
public new T Spec => (T)_spec;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cdf6d03d4434baf93a42aaea7e839be
|
||||
timeCreated: 1702889536
|
@@ -0,0 +1,37 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Ability激活期,总是执行的AbilityTask
|
||||
/// </summary>
|
||||
public abstract class PassiveAbilityTask : AbilityTaskBase
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器预览用
|
||||
/// 【注意】 覆写时,记得用UNITY_EDITOR宏包裹,这是预览表现用的函数,不该被编译。
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
/// <param name="startFrame"></param>
|
||||
/// <param name="endFrame"></param>
|
||||
public virtual void OnEditorPreview(int frame, int startFrame, int endFrame)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
/// <summary>
|
||||
/// 开始执行,仍是Timeline的触发点
|
||||
/// </summary>
|
||||
public abstract void OnStart();
|
||||
|
||||
/// <summary>
|
||||
/// 结束执行,Ability结束激活时,而非Timeline结束时
|
||||
/// </summary>
|
||||
public abstract void OnEnd();
|
||||
|
||||
/// <summary>
|
||||
/// 每帧Tick
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public abstract void OnTick(int deltaTime);
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a9d88b958e546c7aaef67ab354c71a4
|
||||
timeCreated: 1729189790
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bbeb02116afc4e53b4325da574d435c7
|
||||
timeCreated: 1709537763
|
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class AbilityTaskData
|
||||
{
|
||||
public JsonData TaskData;
|
||||
|
||||
public virtual AbilityTaskBase Create(AbilitySpec abilitySpec)
|
||||
{
|
||||
var task = Load();
|
||||
task.Init(abilitySpec);
|
||||
return task;
|
||||
}
|
||||
|
||||
public void Save(AbilityTaskBase task)
|
||||
{
|
||||
var jsonData = JsonUtility.ToJson(task);
|
||||
var dataType = task.GetType().FullName;
|
||||
TaskData = new JsonData
|
||||
{
|
||||
Type = dataType,
|
||||
Data = jsonData
|
||||
};
|
||||
}
|
||||
|
||||
public abstract AbilityTaskBase Load();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94b0d93e175744309457ec20f699f54d
|
||||
timeCreated: 1709537306
|
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class InstantTaskData : AbilityTaskData
|
||||
{
|
||||
public InstantTaskData()
|
||||
{
|
||||
TaskData = new JsonData()
|
||||
{
|
||||
Type = typeof(DefaultInstantAbilityTask).FullName,
|
||||
};
|
||||
}
|
||||
|
||||
public InstantAbilityTask CreateTask(AbilitySpec abilitySpec)
|
||||
{
|
||||
var task = base.Create(abilitySpec);
|
||||
var instantAbilityTask = task as InstantAbilityTask;
|
||||
return instantAbilityTask;
|
||||
}
|
||||
|
||||
public override AbilityTaskBase Load()
|
||||
{
|
||||
InstantAbilityTask task = null;
|
||||
var jsonData = TaskData.Data;
|
||||
var dataType = string.IsNullOrEmpty(TaskData.Type) ? typeof(DefaultInstantAbilityTask).FullName : TaskData.Type;
|
||||
|
||||
var type = InstantTaskSonTypes.FirstOrDefault(sonType => sonType.FullName == dataType);
|
||||
if (type == null)
|
||||
{
|
||||
Debug.LogError("[EX] InstantAbilityTask SonType not found: " + dataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonData))
|
||||
task = Activator.CreateInstance(type) as InstantAbilityTask;
|
||||
else
|
||||
task = JsonUtility.FromJson(jsonData, type) as InstantAbilityTask;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
#region SonTypes
|
||||
|
||||
private static Type[] _instantTaskSonTypes;
|
||||
|
||||
public static Type[] InstantTaskSonTypes =>
|
||||
_instantTaskSonTypes ??= TypeUtil.GetAllSonTypesOf(typeof(InstantAbilityTask));
|
||||
|
||||
public static List<string> InstantTaskSonTypeChoices
|
||||
{
|
||||
get
|
||||
{
|
||||
return InstantTaskSonTypes.Select(sonType => sonType.FullName).ToList();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 474a7b1c8cea4801abb10dccf9a04a9a
|
||||
timeCreated: 1709537777
|
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class OngoingTaskData : AbilityTaskData
|
||||
{
|
||||
public OngoingTaskData()
|
||||
{
|
||||
TaskData = new JsonData()
|
||||
{
|
||||
Type = typeof(DefaultOngoingAbilityTask).FullName,
|
||||
};
|
||||
}
|
||||
|
||||
public OngoingAbilityTask CreateTask(AbilitySpec abilitySpec)
|
||||
{
|
||||
var task = base.Create(abilitySpec);
|
||||
var ongoingAbilityTask = task as OngoingAbilityTask;
|
||||
return ongoingAbilityTask;
|
||||
}
|
||||
|
||||
public override AbilityTaskBase Load()
|
||||
{
|
||||
OngoingAbilityTask task = null;
|
||||
var jsonData = TaskData.Data;
|
||||
var dataType = string.IsNullOrEmpty(TaskData.Type) ? typeof(DefaultOngoingAbilityTask).FullName : TaskData.Type;
|
||||
|
||||
var type = OngoingTaskSonTypes.FirstOrDefault(sonType => sonType.FullName == dataType);
|
||||
if (type == null)
|
||||
{
|
||||
Debug.LogError("[EX] OngoingAbilityTask SonType not found: " + dataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonData))
|
||||
task = Activator.CreateInstance(type) as OngoingAbilityTask;
|
||||
else
|
||||
task = JsonUtility.FromJson(jsonData, type) as OngoingAbilityTask;
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
#region SonTypes
|
||||
|
||||
private static Type[] _ongoingTaskSonTypes;
|
||||
|
||||
public static Type[] OngoingTaskSonTypes =>
|
||||
_ongoingTaskSonTypes ??= TypeUtil.GetAllSonTypesOf(typeof(OngoingAbilityTask));
|
||||
|
||||
public static List<string> OngoingTaskSonTypeChoices
|
||||
{
|
||||
get
|
||||
{
|
||||
return OngoingTaskSonTypes.Select(sonType => sonType.FullName).ToList();
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6237a13b66514fb98982b19e91b75259
|
||||
timeCreated: 1709538170
|
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class PassiveTaskData : AbilityTaskData
|
||||
{
|
||||
public PassiveTaskData()
|
||||
{
|
||||
TaskData = new JsonData()
|
||||
{
|
||||
Type = typeof(DefaultPassiveAbilityTask).FullName,
|
||||
};
|
||||
}
|
||||
|
||||
public PassiveAbilityTask CreateTask(AbilitySpec abilitySpec)
|
||||
{
|
||||
var task = base.Create(abilitySpec);
|
||||
var passiveAbilityTask = task as PassiveAbilityTask;
|
||||
return passiveAbilityTask;
|
||||
}
|
||||
|
||||
public override AbilityTaskBase Load()
|
||||
{
|
||||
|
||||
|
||||
PassiveAbilityTask task = null;
|
||||
var jsonData = TaskData.Data;
|
||||
var dataType = string.IsNullOrEmpty(TaskData.Type) ? typeof(DefaultPassiveAbilityTask).FullName : TaskData.Type;
|
||||
|
||||
var type = PassiveTaskSonTypes.FirstOrDefault(sonType => sonType.FullName == dataType);
|
||||
|
||||
if (type == null)
|
||||
{
|
||||
Debug.LogError("[EX] PassiveAbilityTask SonType not found: {0}" + dataType);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(jsonData))
|
||||
{
|
||||
task = Activator.CreateInstance(type) as PassiveAbilityTask;
|
||||
}
|
||||
else
|
||||
{
|
||||
task = JsonUtility.FromJson(jsonData, type) as PassiveAbilityTask;
|
||||
}
|
||||
}
|
||||
|
||||
return task;
|
||||
}
|
||||
|
||||
#region SonTypes
|
||||
|
||||
private static Type[] _passiveTaskSonTypes;
|
||||
|
||||
public static Type[] PassiveTaskSonTypes =>
|
||||
_passiveTaskSonTypes ??= TypeUtil.GetAllSonTypesOf(typeof(PassiveAbilityTask));
|
||||
|
||||
public static List<string> PassiveTaskSonTypeChoices
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<String>();
|
||||
foreach (var sonType in PassiveTaskSonTypes)
|
||||
{
|
||||
list.Add(sonType.FullName);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dce896cd86840d68e56efd1948114f5
|
||||
timeCreated: 1729189740
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a388877ce48c4d0d80d3318fd7b9c0f6
|
||||
timeCreated: 1709277110
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class ApplyCostAndCoolDown : InstantAbilityTask
|
||||
{
|
||||
public override void OnExecute()
|
||||
{
|
||||
_spec.DoCost();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c8689e959d44db393a7821d0923f750
|
||||
timeCreated: 1709277143
|
@@ -0,0 +1,9 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultInstantAbilityTask : InstantAbilityTask
|
||||
{
|
||||
public override void OnExecute()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a33a8f839e2749f3820f585110ffbd58
|
||||
timeCreated: 1709538427
|
@@ -0,0 +1,17 @@
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultOngoingAbilityTask : OngoingAbilityTask
|
||||
{
|
||||
public override void OnStart(int startFrame)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnd(int endFrame)
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnTick(int frameIndex, int startFrame, int endFrame)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fb71402b7b0843079876bc5f37e12a11
|
||||
timeCreated: 1709538476
|
@@ -0,0 +1,18 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultPassiveAbilityTask : PassiveAbilityTask
|
||||
{
|
||||
public override void OnStart()
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnEnd()
|
||||
{
|
||||
}
|
||||
|
||||
public override void OnTick(int deltaTime)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3826985009604c178d6623947f501916
|
||||
timeCreated: 1729189761
|
@@ -0,0 +1,91 @@
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class TimelineAbilityT<T> : AbstractAbility<T> where T : TimelineAbilityAssetBase
|
||||
{
|
||||
protected TimelineAbilityT(T abilityAsset) : base(abilityAsset)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class TimelineAbilitySpecT<AbilityT, AssetT> : AbilitySpec<AbilityT> where AbilityT : TimelineAbilityT<AssetT> where AssetT : TimelineAbilityAssetBase
|
||||
{
|
||||
protected TimelineAbilityPlayer<AbilityT, AssetT> _player;
|
||||
|
||||
public int FrameCount => _player.FrameCount;
|
||||
public int FrameRate => _player.FrameRate;
|
||||
|
||||
/// <summary>
|
||||
/// 不受播放速率影响的总时间
|
||||
/// </summary>
|
||||
public float TotalTime => _player.TotalTime;
|
||||
|
||||
/// <summary>
|
||||
/// 向性技能的作用目标
|
||||
/// </summary>
|
||||
public AbilitySystemComponent Target { get; private set; }
|
||||
|
||||
protected TimelineAbilitySpecT(AbilityT ability, AbilitySystemComponent owner) : base(ability, owner)
|
||||
{
|
||||
_player = new(this);
|
||||
}
|
||||
|
||||
public void SetAbilityTarget(AbilitySystemComponent mainTarget)
|
||||
{
|
||||
Target = mainTarget;
|
||||
}
|
||||
|
||||
public override void ActivateAbility()
|
||||
{
|
||||
_player.Play();
|
||||
}
|
||||
|
||||
public virtual float GetPlaySpeed()
|
||||
{
|
||||
return Data.AbilityAsset.Speed;
|
||||
}
|
||||
|
||||
public override void CancelAbility()
|
||||
{
|
||||
_player.Stop();
|
||||
}
|
||||
|
||||
public override void EndAbility()
|
||||
{
|
||||
_player.Stop();
|
||||
}
|
||||
|
||||
protected override void AbilityTick()
|
||||
{
|
||||
Profiler.BeginSample("TimelineAbilitySpecT<T>::AbilityTick()");
|
||||
_player.Tick();
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这是一个最朴素的TimelineAbility实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT和TimelineAbilitySpecT为基类
|
||||
/// </summary>
|
||||
public sealed class TimelineAbility : TimelineAbilityT<TimelineAbilityAsset>
|
||||
{
|
||||
public TimelineAbility(TimelineAbilityAsset abilityAsset) : base(abilityAsset)
|
||||
{
|
||||
}
|
||||
|
||||
public override AbilitySpec CreateSpec(AbilitySystemComponent owner)
|
||||
{
|
||||
return new TimelineAbilitySpec(this, owner);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这是一个最朴素的TimelineAbilitySpec实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT和TimelineAbilitySpecT为基类
|
||||
/// </summary>
|
||||
public sealed class TimelineAbilitySpec : TimelineAbilitySpecT<TimelineAbilityT<TimelineAbilityAsset>, TimelineAbilityAsset>
|
||||
{
|
||||
public TimelineAbilitySpec(TimelineAbilityT<TimelineAbilityAsset> ability, AbilitySystemComponent owner) : base(ability, owner)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5e9c13b27a641ee81a8faa360da21e2
|
||||
timeCreated: 1708250442
|
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using GAS.General;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class TimelineAbilityAssetBase : AbilityAsset
|
||||
{
|
||||
[TitleGroup("Data")]
|
||||
[HorizontalGroup("Data/H1", 1 / 3f)]
|
||||
[TabGroup("Data/H1/V1", "Timeline", SdfIconType.ClockHistory, TextColor = "#00FF00")]
|
||||
[Button("查看/编辑能力时间轴", ButtonSizes.Large, Icon = SdfIconType.Hammer)]
|
||||
[PropertyOrder(-1)]
|
||||
private void EditAbilityTimeline()
|
||||
{
|
||||
try
|
||||
{
|
||||
var assembly = Assembly.Load("JNGame.Editor");
|
||||
var type = assembly.GetType("GAS.Editor.AbilityTimelineEditorWindow");
|
||||
var methodInfo = type.GetMethod("ShowWindow", BindingFlags.Public | BindingFlags.Static);
|
||||
methodInfo!.Invoke(null, new object[] { this });
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(
|
||||
$"调用\"GAS.Editor.AbilityTimelineEditorWindow\"类的静态方法ShowWindow(TimelineAbilityAsset asset)失败, 代码可能被重构了: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放速率, 常用于加速或减速播放(例如基于攻击速度的技能, 播放速率随攻击速度变化)
|
||||
/// </summary>
|
||||
[TabGroup("Data/H1/V1", "Timeline")]
|
||||
[LabelText(GASTextDefine.ABILITY_PLAY_RATE)]
|
||||
[LabelWidth(100)]
|
||||
[MinValue(0)]
|
||||
public float Speed = 1.0f;
|
||||
|
||||
[TabGroup("Data/H1/V1", "Timeline")]
|
||||
[LabelText(GASTextDefine.ABILITY_MANUAL_ENDABILITY)]
|
||||
[LabelWidth(100)]
|
||||
[FormerlySerializedAs("manualEndAbility")]
|
||||
public bool ManualEndAbility;
|
||||
|
||||
[HideInInspector]
|
||||
public int FrameCount; // 能力结束时间
|
||||
|
||||
[HideInInspector]
|
||||
public List<DurationalCueTrackData> DurationalCues = new List<DurationalCueTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<InstantCueTrackData> InstantCues = new List<InstantCueTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<ReleaseGameplayEffectTrackData> ReleaseGameplayEffect = new List<ReleaseGameplayEffectTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<BuffGameplayEffectTrackData> BuffGameplayEffects = new List<BuffGameplayEffectTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<TaskMarkEventTrackData> InstantTasks = new List<TaskMarkEventTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<TaskClipEventTrackData> OngoingTasks = new List<TaskClipEventTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<PassiveGameplayEffectTrackData> PassiveGameplayEffects = new List<PassiveGameplayEffectTrackData>();
|
||||
|
||||
[HideInInspector]
|
||||
public List<PassiveTaskClipEventTrackData> PassiveTasks = new List<PassiveTaskClipEventTrackData>();
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public void Save()
|
||||
{
|
||||
EditorUtility.SetDirty(this);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public abstract class TimelineAbilityAssetT<T> : TimelineAbilityAssetBase where T : class
|
||||
{
|
||||
public sealed override Type AbilityType()
|
||||
{
|
||||
return typeof(T);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这是一个最朴素的TimelineAbilityAsset实现, 如果要实现更复杂的TimelineAbilityAsset, 请用TimelineAbilityAssetBase或TimelineAbilityAssetT为基类
|
||||
/// </summary>
|
||||
public sealed class TimelineAbilityAsset : TimelineAbilityAssetT<TimelineAbility>
|
||||
{
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d763af469c524557946c477b9bea3a46
|
||||
timeCreated: 1708250516
|
@@ -0,0 +1,428 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
//using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
internal abstract class RuntimeClipInfo
|
||||
{
|
||||
public int endFrame;
|
||||
public int startFrame;
|
||||
}
|
||||
|
||||
internal class RuntimeDurationCueClip : RuntimeClipInfo
|
||||
{
|
||||
public GameplayCueDurationalSpec cueSpec;
|
||||
}
|
||||
|
||||
internal class RuntimeBuffClip : RuntimeClipInfo
|
||||
{
|
||||
public GameplayEffect buff;
|
||||
public EntityRef<GameplayEffectSpec> buffSpec;
|
||||
}
|
||||
|
||||
internal class RuntimeTaskClip : RuntimeClipInfo
|
||||
{
|
||||
public OngoingAbilityTask task;
|
||||
}
|
||||
|
||||
internal class RuntimeTaskMark
|
||||
{
|
||||
public int startFrame;
|
||||
public InstantAbilityTask task;
|
||||
}
|
||||
|
||||
public class TimelineAbilityPlayer<AbilityT, AssetT> where AssetT : TimelineAbilityAssetBase where AbilityT : TimelineAbilityT<AssetT>
|
||||
{
|
||||
private readonly TimelineAbilitySpecT<AbilityT, AssetT> _abilitySpec;
|
||||
private readonly List<RuntimeBuffClip> _cacheBuffGameplayEffectTrack = new();
|
||||
|
||||
private readonly List<RuntimeDurationCueClip> _cacheDurationalCueTrack = new();
|
||||
|
||||
private readonly List<InstantCueMarkEvent> _cacheInstantCues = new();
|
||||
|
||||
private readonly List<RuntimeTaskMark> _cacheInstantTasks = new();
|
||||
private readonly List<RuntimeTaskClip> _cacheOngoingTaskTrack = new();
|
||||
|
||||
private readonly List<ReleaseGameplayEffectMarkEvent> _cacheReleaseGameplayEffect = new();
|
||||
|
||||
// cache for target catcher, avoid new in TickFrame
|
||||
// 这个是一个泛型类, 这个变量就不作为static了
|
||||
private readonly List<AbilitySystemComponent> _targets = new();
|
||||
|
||||
private int _currentFrame;
|
||||
private float _playTotalTime;
|
||||
|
||||
public TimelineAbilityPlayer(TimelineAbilitySpecT<AbilityT, AssetT> abilitySpec)
|
||||
{
|
||||
_abilitySpec = abilitySpec;
|
||||
Cache();
|
||||
}
|
||||
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
public AssetT AbilityAsset => _abilitySpec.Data.AbilityAsset;
|
||||
public int FrameCount => AbilityAsset.FrameCount;
|
||||
public int FrameRate => GASTimer.FrameRate;
|
||||
|
||||
/// <summary>
|
||||
/// 不受播放速率影响的总时间
|
||||
/// </summary>
|
||||
public float TotalTime => (float)FrameCount / FrameRate;
|
||||
|
||||
private void Cache()
|
||||
{
|
||||
Cache_InstantCues();
|
||||
Cache_ReleaseGameplayEffects();
|
||||
Cache_InstantTasks();
|
||||
Cache_DurationalGameplayCues();
|
||||
Cache_BuffGameplayEffects();
|
||||
Cache_OngoingTasks();
|
||||
}
|
||||
|
||||
private void Cache_InstantCues()
|
||||
{
|
||||
_cacheInstantCues.Clear();
|
||||
foreach (var trackData in AbilityAsset.InstantCues)
|
||||
{
|
||||
_cacheInstantCues.AddRange(trackData.markEvents);
|
||||
}
|
||||
|
||||
_cacheInstantCues.Sort((a, b) => a.startFrame.CompareTo(b.startFrame));
|
||||
}
|
||||
|
||||
private void Cache_ReleaseGameplayEffects()
|
||||
{
|
||||
_cacheReleaseGameplayEffect.Clear();
|
||||
foreach (var trackData in AbilityAsset.ReleaseGameplayEffect)
|
||||
{
|
||||
_cacheReleaseGameplayEffect.AddRange(trackData.markEvents);
|
||||
}
|
||||
|
||||
_cacheReleaseGameplayEffect.Sort((a, b) => a.startFrame.CompareTo(b.startFrame));
|
||||
foreach (var releaseGameplayEffectMarkEvent in _cacheReleaseGameplayEffect)
|
||||
{
|
||||
releaseGameplayEffectMarkEvent.CacheTargetCatcher();
|
||||
}
|
||||
}
|
||||
|
||||
private void Cache_InstantTasks()
|
||||
{
|
||||
_cacheInstantTasks.Clear();
|
||||
foreach (var trackData in AbilityAsset.InstantTasks)
|
||||
{
|
||||
foreach (var markEvent in trackData.markEvents)
|
||||
{
|
||||
foreach (var taskData in markEvent.InstantTasks)
|
||||
{
|
||||
var runtimeTaskMark = new RuntimeTaskMark
|
||||
{
|
||||
startFrame = markEvent.startFrame,
|
||||
task = taskData.CreateTask(_abilitySpec)
|
||||
};
|
||||
_cacheInstantTasks.Add(runtimeTaskMark);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_cacheInstantTasks.Sort((a, b) => a.startFrame.CompareTo(b.startFrame));
|
||||
}
|
||||
|
||||
private void Cache_DurationalGameplayCues()
|
||||
{
|
||||
_cacheDurationalCueTrack.Clear();
|
||||
foreach (var track in AbilityAsset.DurationalCues)
|
||||
{
|
||||
foreach (var clipEvent in track.clipEvents)
|
||||
{
|
||||
var cueSpec = clipEvent.cue.ApplyFrom(_abilitySpec);
|
||||
if (cueSpec == null) continue;
|
||||
var runtimeDurationCueClip = new RuntimeDurationCueClip
|
||||
{
|
||||
startFrame = clipEvent.startFrame,
|
||||
endFrame = clipEvent.EndFrame,
|
||||
cueSpec = cueSpec
|
||||
};
|
||||
_cacheDurationalCueTrack.Add(runtimeDurationCueClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Cache_BuffGameplayEffects()
|
||||
{
|
||||
_cacheBuffGameplayEffectTrack.Clear();
|
||||
foreach (var track in AbilityAsset.BuffGameplayEffects)
|
||||
{
|
||||
foreach (var clipEvent in track.clipEvents)
|
||||
{
|
||||
// 只有持续型的GameplayEffect可视作buff
|
||||
if (clipEvent.gameplayEffect.DurationPolicy is EffectsDurationPolicy.Duration
|
||||
or EffectsDurationPolicy.Infinite)
|
||||
{
|
||||
var runtimeBuffClip = new RuntimeBuffClip
|
||||
{
|
||||
startFrame = clipEvent.startFrame,
|
||||
endFrame = clipEvent.EndFrame,
|
||||
buff = clipEvent.gameplayEffect.SharedInstance,
|
||||
buffSpec = default
|
||||
};
|
||||
_cacheBuffGameplayEffectTrack.Add(runtimeBuffClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void Cache_OngoingTasks()
|
||||
{
|
||||
_cacheOngoingTaskTrack.Clear();
|
||||
foreach (var track in AbilityAsset.OngoingTasks)
|
||||
{
|
||||
foreach (var clip in track.clipEvents)
|
||||
{
|
||||
var runtimeTaskClip = new RuntimeTaskClip
|
||||
{
|
||||
startFrame = clip.startFrame,
|
||||
endFrame = clip.EndFrame,
|
||||
task = clip.ongoingTask.CreateTask(_abilitySpec)
|
||||
};
|
||||
_cacheOngoingTaskTrack.Add(runtimeTaskClip);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void Prepare()
|
||||
{
|
||||
foreach (var runtimeBuffClip in _cacheBuffGameplayEffectTrack)
|
||||
{
|
||||
runtimeBuffClip.buffSpec.Value?.Recycle();
|
||||
runtimeBuffClip.buffSpec = default;
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
_currentFrame = -1; // 为了播放第0帧
|
||||
_playTotalTime = 0;
|
||||
IsPlaying = true;
|
||||
Prepare();
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
|
||||
foreach (var clip in _cacheDurationalCueTrack)
|
||||
{
|
||||
if (_currentFrame <= clip.endFrame)
|
||||
clip.cueSpec.OnRemove(_currentFrame,clip.startFrame,clip.endFrame);
|
||||
}
|
||||
|
||||
foreach (var clip in _cacheBuffGameplayEffectTrack)
|
||||
{
|
||||
var spec = clip.buffSpec.Value;
|
||||
if (spec != null)
|
||||
{
|
||||
_abilitySpec.Owner.RemoveGameplayEffect(spec);
|
||||
spec.Recycle();
|
||||
}
|
||||
|
||||
clip.buffSpec = default;
|
||||
}
|
||||
|
||||
foreach (var clip in _cacheOngoingTaskTrack)
|
||||
{
|
||||
clip.task.OnEnd(clip.endFrame);
|
||||
}
|
||||
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
public void Tick()
|
||||
{
|
||||
if (!IsPlaying) return;
|
||||
|
||||
var speed = _abilitySpec.GetPlaySpeed();
|
||||
speed = Math.Max(0, speed);
|
||||
_playTotalTime += Time.deltaTime * speed;
|
||||
var targetFrame = (int)(_playTotalTime * FrameRate);
|
||||
|
||||
// 追帧
|
||||
while (_currentFrame < targetFrame)
|
||||
{
|
||||
_currentFrame++;
|
||||
TickFrame(_currentFrame);
|
||||
}
|
||||
|
||||
if (_currentFrame >= FrameCount)
|
||||
{
|
||||
_currentFrame++; //确保不重复触发cue的onRemove
|
||||
OnPlayEnd();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 播放结束
|
||||
/// </summary>
|
||||
private void OnPlayEnd()
|
||||
{
|
||||
IsPlaying = false;
|
||||
|
||||
if (!AbilityAsset.ManualEndAbility)
|
||||
_abilitySpec.TryEndAbility();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当前帧的事件
|
||||
/// </summary>
|
||||
/// <param name="frame"></param>
|
||||
private void TickFrame(int frame)
|
||||
{
|
||||
TickFrame_InstantGameplayCues(frame);
|
||||
TickFrame_ReleaseGameplayEffects(frame);
|
||||
TickFrame_InstantTasks(frame);
|
||||
TickFrame_DurationalGameplayCues(frame);
|
||||
TickFrame_BuffGameplayEffects(frame);
|
||||
TickFrame_OngoingTasks(frame);
|
||||
}
|
||||
|
||||
private void TickFrame_InstantGameplayCues(int frame)
|
||||
{
|
||||
foreach (var cueMark in _cacheInstantCues)
|
||||
{
|
||||
if (frame == cueMark.startFrame)
|
||||
{
|
||||
foreach (var cue in cueMark.cues)
|
||||
{
|
||||
cue.ApplyFrom(_abilitySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickFrame_ReleaseGameplayEffects(int frame)
|
||||
{
|
||||
foreach (var mark in _cacheReleaseGameplayEffect)
|
||||
{
|
||||
if (frame == mark.startFrame)
|
||||
{
|
||||
var catcher = mark.TargetCatcher;
|
||||
catcher.Init(_abilitySpec.Owner);
|
||||
|
||||
catcher.CatchTargetsNonAllocSafe(_abilitySpec.Target, _targets);
|
||||
|
||||
foreach (var asc in _targets)
|
||||
{
|
||||
foreach (var gea in mark.gameplayEffectAssets)
|
||||
{
|
||||
var ge = gea.SharedInstance;
|
||||
_abilitySpec.Owner.ApplyGameplayEffectTo(ge, asc);
|
||||
}
|
||||
}
|
||||
|
||||
_targets.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickFrame_InstantTasks(int frame)
|
||||
{
|
||||
foreach (var instantTask in _cacheInstantTasks)
|
||||
{
|
||||
if (frame == instantTask.startFrame)
|
||||
{
|
||||
instantTask.task.OnExecute();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickFrame_DurationalGameplayCues(int frame)
|
||||
{
|
||||
foreach (var cueClip in _cacheDurationalCueTrack)
|
||||
{
|
||||
if (frame == cueClip.startFrame)
|
||||
{
|
||||
cueClip.cueSpec.OnAdd(frame,cueClip.startFrame,cueClip.endFrame);
|
||||
}
|
||||
|
||||
if (frame >= cueClip.startFrame && frame <= cueClip.endFrame)
|
||||
{
|
||||
cueClip.cueSpec.OnTick(frame,cueClip.startFrame,cueClip.endFrame);
|
||||
}
|
||||
|
||||
if (frame == cueClip.endFrame)
|
||||
{
|
||||
cueClip.cueSpec.OnRemove(frame,cueClip.startFrame,cueClip.endFrame);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickFrame_BuffGameplayEffects(int frame)
|
||||
{
|
||||
// buff持续时间以Timeline配置时间为准(执行策略全部改为Infinite)
|
||||
// Profiler.BeginSample("TickFrame_BuffGameplayEffects");
|
||||
// {
|
||||
foreach (var buffClip in _cacheBuffGameplayEffectTrack)
|
||||
{
|
||||
if (frame == buffClip.startFrame)
|
||||
{
|
||||
//Profiler.BeginSample("buffGameplayEffect.Start");
|
||||
var buffSpec = _abilitySpec.Owner.ApplyGameplayEffectToSelf(buffClip.buff);
|
||||
buffSpec.Value.SetDurationPolicy(EffectsDurationPolicy.Infinite);
|
||||
buffClip.buffSpec = buffSpec;
|
||||
//Profiler.EndSample();
|
||||
}
|
||||
|
||||
if (frame == buffClip.endFrame)
|
||||
{
|
||||
var spec = buffClip.buffSpec.Value;
|
||||
if (spec != null)
|
||||
{
|
||||
//Profiler.BeginSample("buffGameplayEffect.End");
|
||||
_abilitySpec.Owner.RemoveGameplayEffect(spec);
|
||||
//Profiler.EndSample();
|
||||
spec.Recycle();
|
||||
}
|
||||
|
||||
buffClip.buffSpec = default;
|
||||
}
|
||||
}
|
||||
// }
|
||||
// Profiler.EndSample();
|
||||
}
|
||||
|
||||
private void TickFrame_OngoingTasks(int frame)
|
||||
{
|
||||
// Profiler.BeginSample("TickFrame_OngoingTasks");
|
||||
// {
|
||||
foreach (var taskClip in _cacheOngoingTaskTrack)
|
||||
{
|
||||
if (frame == taskClip.startFrame)
|
||||
{
|
||||
//Profiler.BeginSample("Ongoing Task.OnStart()");
|
||||
taskClip.task.OnStart(frame);
|
||||
//Profiler.EndSample();
|
||||
}
|
||||
|
||||
if (frame >= taskClip.startFrame && frame <= taskClip.endFrame)
|
||||
{
|
||||
//Profiler.BeginSample("Ongoing Task.OnTick()");
|
||||
taskClip.task.OnTick(frame, taskClip.startFrame, taskClip.endFrame);
|
||||
//Profiler.EndSample();
|
||||
}
|
||||
|
||||
if (frame == taskClip.endFrame)
|
||||
{
|
||||
//Profiler.BeginSample("Ongoing Task.OnEnd()");
|
||||
taskClip.task.OnEnd(frame);
|
||||
//Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
// }
|
||||
// Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4b17a33afb534946b70239808a6015eb
|
||||
timeCreated: 1708672766
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3cfd0d21847f461d96661a737d97d32e
|
||||
timeCreated: 1708504113
|
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class BuffGameplayEffectTrackData:TrackDataBase
|
||||
{
|
||||
public List<BuffGameplayEffectClipEvent> clipEvents = new List<BuffGameplayEffectClipEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.BuffGameplayEffects.Add(this);
|
||||
}
|
||||
|
||||
public override void DefaultInit()
|
||||
{
|
||||
base.DefaultInit();
|
||||
trackName = "Buff";
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class BuffGameplayEffectClipEvent : ClipEventBase
|
||||
{
|
||||
public BuffTarget buffTarget;
|
||||
[FormerlySerializedAs("gameplayEffects")] public GameplayEffectAsset gameplayEffect;
|
||||
}
|
||||
|
||||
public enum BuffTarget
|
||||
{
|
||||
Self,
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5742faeb4aee4f058f69e6b0922a540d
|
||||
timeCreated: 1709018398
|
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class DurationalCueTrackData:TrackDataBase
|
||||
{
|
||||
public List<DurationalCueClipEvent> clipEvents = new List<DurationalCueClipEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.DurationalCues.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class DurationalCueClipEvent : ClipEventBase
|
||||
{
|
||||
public GameplayCueDurational cue;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eda585ff2c7143d8bfcb34857b7cc068
|
||||
timeCreated: 1709017864
|
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class InstantCueTrackData:TrackDataBase
|
||||
{
|
||||
public List<InstantCueMarkEvent> markEvents = new List<InstantCueMarkEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.InstantCues.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class InstantCueMarkEvent:MarkEventBase
|
||||
{
|
||||
public List<GameplayCueInstant> cues = new List<GameplayCueInstant>();
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a2f9017c20dc4fc5b36fa15ba97371df
|
||||
timeCreated: 1709106070
|
@@ -0,0 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PassiveGameplayEffectTrackData : TrackDataBase
|
||||
{
|
||||
public List<PassiveGameplayEffectClipEvent> clipEvents = new List<PassiveGameplayEffectClipEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.PassiveGameplayEffects.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PassiveGameplayEffectClipEvent : ClipEventBase
|
||||
{
|
||||
[FormerlySerializedAs("GameplayEffectValueId")]
|
||||
public int gameplayEffectValueId;
|
||||
public GameplayEffectAsset gameplayEffect;
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ece82d688852440699c4a50ee47a671e
|
||||
timeCreated: 1729189527
|
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public sealed class PassiveTaskClipEventTrackData : TrackDataBase
|
||||
{
|
||||
public List<PassiveTaskClipEvent> clipEvents = new List<PassiveTaskClipEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.PassiveTasks.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public sealed class PassiveTaskClipEvent : ClipEventBase
|
||||
{
|
||||
public PassiveTaskData passiveTask;
|
||||
|
||||
public PassiveAbilityTask Load()
|
||||
{
|
||||
return passiveTask.Load() as PassiveAbilityTask;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6a98a9a0278441cada7de695771be21
|
||||
timeCreated: 1729189725
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user