mirror of
https://gitee.com/jisol/jisol-game/
synced 2025-11-11 16:48:30 +00:00
简单提交
This commit is contained in:
@@ -1,239 +0,0 @@
|
||||
# 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
|
||||
@@ -1,7 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34ae97a0d741c5844a4fac12ff8e1c45
|
||||
TextScriptImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 390 KiB |
Binary file not shown.
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f87f52c980e4d1ba211174b7e0ce100
|
||||
timeCreated: 1702438660
|
||||
guid: bb1c30c31ad34ddbb692b946a977c4be
|
||||
timeCreated: 1715843648
|
||||
@@ -7,7 +7,7 @@
|
||||
public const string TITLE_BASE_INFO = "基本信息";
|
||||
public const string TITLE_DESCRIPTION = "描述";
|
||||
|
||||
|
||||
|
||||
#region GASSettingAsset
|
||||
|
||||
public const string TIP_CREATE_GEN_AscUtilCode =
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
|
||||
#region Tag
|
||||
|
||||
public const string BUTTON_ExpandAllTag = "展开全部";
|
||||
@@ -33,14 +33,14 @@
|
||||
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>";
|
||||
@@ -53,31 +53,30 @@
|
||||
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 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 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 - 授予目标单位的标签";
|
||||
@@ -90,14 +89,14 @@
|
||||
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 = "激活策略";
|
||||
@@ -105,15 +104,15 @@
|
||||
public const string LABEL_GRANT_ABILITY_REMOVE_POLICY = "移除策略";
|
||||
public const string TIP_GRANT_ABILITY_ACTIVATION_POLICY = "None = 不激活,需要用户手动调用ASC相关接口激活; " +
|
||||
"WhenAdded = 添加时就激活;" +
|
||||
"SyncWithEffect = 同步GE,GE激活时激活";
|
||||
"SyncWithEffect = 同步GE,GE激活时激活";
|
||||
public const string TIP_GRANT_ABILITY_DEACTIVATION_POLICY = "None = 无相关取消激活逻辑, 需要用户调用ASC取消激活; " +
|
||||
"SyncWithEffect = 同步GE,GE失活时取消激活";
|
||||
"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 = "堆叠类型";
|
||||
@@ -124,40 +123,39 @@
|
||||
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 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_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="属性集";
|
||||
|
||||
|
||||
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 =
|
||||
public const string TIP_WATCHER_OnlyForGameRunning =
|
||||
"<size=20><b><color=yellow>监视器只在游戏运行时可用.</color></b></size>";
|
||||
|
||||
#endregion
|
||||
@@ -165,14 +163,13 @@
|
||||
#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 = "特效旋转";
|
||||
|
||||
@@ -1,43 +1,50 @@
|
||||
using System;
|
||||
using GAS.Runtime;
|
||||
using JNGame.Math;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.General
|
||||
namespace GAS.General
|
||||
{
|
||||
public class GASTimer
|
||||
{
|
||||
// TODO 矫正时间差(服务器客户端时间差/暂停游戏导致的时间差)
|
||||
static int _deltaTime;
|
||||
// // TODO 矫正时间差(服务器客户端时间差/暂停游戏导致的时间差)
|
||||
// static int _deltaTime;
|
||||
|
||||
public static long Timestamp() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + _deltaTime;
|
||||
// public static long Timestamp() => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + _deltaTime;
|
||||
|
||||
public static long TimestampSeconds() => Timestamp() / 1000;
|
||||
// public static long TimestampSeconds() => Timestamp() / 1000;
|
||||
|
||||
private static int _currentFrameCount;
|
||||
public static int CurrentFrameCount => _currentFrameCount;
|
||||
public static void UpdateCurrentFrameCount()
|
||||
{
|
||||
_currentFrameCount = LMath.FloorToInt((Timestamp() - _startTimestamp) / LFloat.L1000 * JexGasManager.FrameRate);
|
||||
}
|
||||
// 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 _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 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;
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private static int _TimeLineAbilityTickTime = 20;
|
||||
|
||||
public static int TimeLineAbilityTickTime => _TimeLineAbilityTickTime;
|
||||
|
||||
public static int FrameRateValue => 1000 / _TimeLineAbilityTickTime;
|
||||
}
|
||||
}
|
||||
@@ -1,507 +1,47 @@
|
||||
using JNGame.Math;
|
||||
using UnityEngine;
|
||||
// using UnityEngine;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
public static class DebugExtension
|
||||
{
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DebugBox(LVector2 center, LVector2 size, LFloat angle, Color color, LFloat showTime)
|
||||
{
|
||||
var halfSize = size * LFloat.L0D5;
|
||||
var p1 = new LVector2(center.x - halfSize.x, center.y - halfSize.y);
|
||||
var p2 = new LVector2(center.x + halfSize.x, center.y - halfSize.y);
|
||||
var p3 = new LVector2(center.x + halfSize.x, center.y + halfSize.y);
|
||||
var p4 = new LVector2(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);
|
||||
}
|
||||
// namespace GAS.General
|
||||
// {
|
||||
// public static class DebugExtension
|
||||
// {
|
||||
// public static void DebugBox(Vector2 center, Vector2 size, float angle, Color color, float showTime)
|
||||
// {
|
||||
// var halfSize = size / 2;
|
||||
// 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);
|
||||
// Debug.DrawLine(p1, p2, color, showTime);
|
||||
// Debug.DrawLine(p2, p3, color, showTime);
|
||||
// Debug.DrawLine(p3, p4, color, showTime);
|
||||
// Debug.DrawLine(p4, p1, color, showTime);
|
||||
// }
|
||||
|
||||
public static LVector2 RotatePoint(LVector2 center, LVector2 point, LFloat angle)
|
||||
{
|
||||
var cos = LMath.Cos(angle * LMath.Deg2Rad);
|
||||
var sin = LMath.Sin(angle * LMath.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 LVector2(x, y);
|
||||
}
|
||||
// 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(LVector2 center, LFloat radius, Color color, LFloat showTime, LFloat segments = default) // segments = 120
|
||||
{
|
||||
var step = LFloat.L360 / segments;
|
||||
var from = center + new LVector2(radius, LFloat.L0);
|
||||
for (var i = 0; i < segments; i++)
|
||||
{
|
||||
var to = center + new LVector2(radius * LMath.Cos((i + 1) * step * LMath.Deg2Rad),
|
||||
radius * LMath.Sin((i + 1) * step * LMath.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 = LMath.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCircle(in LVector3 position, in LQuaternion rotation, LFloat radius, int segments = 36,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawArc(position, rotation, radius, 360, segments, color, duration);
|
||||
}
|
||||
|
||||
public static void DrawCircle(in LVector3 position, in LVector3 forward, LFloat radius, int segments = 36,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawCircle(position, LQuaternion.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 = LMath.CeilToInt(angle / 10))(建议与角度适配, 如每10°分一段: segments = LMath.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArc(in LVector3 position, in LQuaternion rotation, LFloat radius, LFloat angle, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
if (angle <= 0) return;
|
||||
if (radius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
var angleStep = angle / segments;
|
||||
var lastPoint = position + rotation * (LVector3.Right * radius);
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var currentAngle = i * angleStep;
|
||||
var nextPoint = position + rotation * (LQuaternion.Euler(0, currentAngle, 0) * LVector3.Right * radius);
|
||||
DrawLine(lastPoint, nextPoint, color, duration);
|
||||
lastPoint = nextPoint;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArc(in LVector3 position, in LVector3 forward, LFloat radius, LFloat angle, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawArc(position, LQuaternion.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 = LMath.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawRing(in LVector3 position, in LQuaternion rotation, LFloat outerRadius, LFloat innerRadius, int segments = 36,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
if (outerRadius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
innerRadius = LMath.Clamp(innerRadius, LFloat.L0, outerRadius);
|
||||
|
||||
// 计算圆的每个点的位置
|
||||
var angleStep = LFloat.L360 / segments;
|
||||
var lastOuterPoint = position + rotation * (LVector3.Right * outerRadius);
|
||||
var lastInnerPoint = position + rotation * (LVector3.Right * innerRadius);
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var angle = i * angleStep;
|
||||
var nextOuterPoint = position + rotation * (LQuaternion.Euler(0, angle, 0) * LVector3.Right * outerRadius);
|
||||
var nextInnerPoint = position + rotation * (LQuaternion.Euler(0, angle, 0) * LVector3.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 LVector3 position, in LVector3 forward, LFloat outerRadius, LFloat innerRadius, int segments = 36,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawRing(position, LQuaternion.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 LVector3 position, in LQuaternion rotation, in LVector2 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
// 计算矩形的四个角点在局部坐标系中的位置
|
||||
var halfSize = new LVector3(size.x * LFloat.L0D5, LFloat.L0, size.y * LFloat.L0D5);
|
||||
var corners = new LVector3[4];
|
||||
corners[0] = new LVector3(-halfSize.x, LFloat.L0, -halfSize.z); // 左下角
|
||||
corners[1] = new LVector3(halfSize.x, LFloat.L0, -halfSize.z); // 右下角
|
||||
corners[2] = new LVector3(halfSize.x, LFloat.L0, halfSize.z); // 右上角
|
||||
corners[3] = new LVector3(-halfSize.x, LFloat.L0, 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 LVector3 position, in LVector3 forward, in LVector2 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawRectangle(position, LQuaternion.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 LVector3 position, in LQuaternion rotation, in LVector2 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
// 计算矩形的四个角点在局部坐标系中的位置
|
||||
var halfX = size.x * LFloat.L0D5;
|
||||
var corners = new LVector3[4];
|
||||
corners[0] = new LVector3(-halfX, LFloat.L0, LFloat.L0); // 左下角
|
||||
corners[1] = new LVector3(halfX, LFloat.L0, LFloat.L0); // 右下角
|
||||
corners[2] = new LVector3(halfX, LFloat.L0, size.y); // 右上角
|
||||
corners[3] = new LVector3(-halfX, LFloat.L0, 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 LVector3 position, in LVector3 forward, in LVector2 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawFrontRectangle(position, LQuaternion.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 LVector3 position, in LQuaternion rotation, in LVector3 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
// 计算立方体的八个顶点在局部坐标系中的位置
|
||||
var halfSize = size * LFloat.L0D5;
|
||||
var vertices = new LVector3[8];
|
||||
|
||||
// 下底面四个顶点
|
||||
vertices[0] = new LVector3(-halfSize.x, -halfSize.y, -halfSize.z);
|
||||
vertices[1] = new LVector3(halfSize.x, -halfSize.y, -halfSize.z);
|
||||
vertices[2] = new LVector3(halfSize.x, -halfSize.y, halfSize.z);
|
||||
vertices[3] = new LVector3(-halfSize.x, -halfSize.y, halfSize.z);
|
||||
|
||||
// 上顶面四个顶点
|
||||
vertices[4] = new LVector3(-halfSize.x, halfSize.y, -halfSize.z);
|
||||
vertices[5] = new LVector3(halfSize.x, halfSize.y, -halfSize.z);
|
||||
vertices[6] = new LVector3(halfSize.x, halfSize.y, halfSize.z);
|
||||
vertices[7] = new LVector3(-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 LVector3 position, in LVector3 forward, in LVector3 size,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawCube(position, LQuaternion.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 LVector3 position, in LQuaternion rotation, LFloat radius, LFloat height, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
var offset = rotation * LVector3.Up * (height * LFloat.L0D5);
|
||||
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 LVector3 position, in LVector3 forward, LFloat radius, LFloat height, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawCylinder(position, LQuaternion.LookRotation(forward), radius, height, segments, in color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawCylinder(in LVector3 topCenter, in LVector3 bottomCenter, LFloat radius, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
if (radius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
|
||||
if (LVector3.Distance(topCenter, bottomCenter) <= LFloat.EPSILON) return;
|
||||
|
||||
var direction = (topCenter - bottomCenter).Normalized;
|
||||
|
||||
// 计算一个垂直于direction的向量作为forward
|
||||
var forward = LVector3.Cross(direction, LVector3.Up).Normalized;
|
||||
|
||||
// 如果forward和direction平行(即direction和LVector3.up共线),则选择一个不同的方向作为forward
|
||||
if (forward == LVector3.Zero)
|
||||
{
|
||||
forward = LVector3.Cross(direction, LVector3.Right).Normalized;
|
||||
}
|
||||
|
||||
var rotation = LQuaternion.LookRotation(forward, direction);
|
||||
|
||||
var angleStep = LFloat.L360 / segments;
|
||||
var offset = rotation * (LVector3.Right * radius);
|
||||
var lastTopPoint = topCenter + offset;
|
||||
var lastBottomPoint = bottomCenter + offset;
|
||||
for (int i = 1; i <= segments; i++)
|
||||
{
|
||||
var currentAngle = i * angleStep;
|
||||
offset = rotation * (LQuaternion.Euler(0, currentAngle, 0) * (LVector3.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 = LMath.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawSector(LVector3 position, LQuaternion rotation, LFloat radius, LFloat angle, int segments = 12,
|
||||
Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawAnnularSector(position, rotation, radius, 0, angle, segments, color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawSector(in LVector3 position, in LVector3 forward, LFloat radius, LFloat angle, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawSector(position, LQuaternion.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 = LMath.CeilToInt(angle / 10))</param>
|
||||
/// <param name="color">颜色</param>
|
||||
/// <param name="duration">绘制时长(0为1帧)</param>
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawAnnularSector(in LVector3 position, in LQuaternion rotation, LFloat outerRadius, LFloat innerRadius, LFloat angle, int segments = 12,
|
||||
in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
if (outerRadius <= 0) return;
|
||||
if (segments <= 0) return;
|
||||
if (angle <= 0) return;
|
||||
|
||||
innerRadius = LMath.Clamp(innerRadius, LFloat.L0, outerRadius);
|
||||
|
||||
var angleStep = angle / segments;
|
||||
var currentAngle = -angle * LFloat.L0D5;
|
||||
|
||||
var outerPoints = new LVector3[segments + 1];
|
||||
var innerPoints = new LVector3[segments + 1];
|
||||
|
||||
// 计算内外扇形的点
|
||||
for (var i = 0; i <= segments; i++)
|
||||
{
|
||||
var currentDirection = rotation * LQuaternion.Euler(0, currentAngle, 0) * LVector3.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 LVector3 position, in LVector3 forward, LFloat outerRadius, LFloat innerRadius, LFloat angle, int segments = 12, in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawAnnularSector(position, LQuaternion.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 LVector3 start, in LVector3 end, in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
Debug.DrawLine(start.ToVector3(), end.ToVector3(), 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 LVector3 origin, in LVector3 destination, in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
DrawLine(origin, destination, color, duration);
|
||||
var direction = destination - origin;
|
||||
var right = LQuaternion.LookRotation(direction) * LQuaternion.Euler(0, 180 + 30, 0) * LVector3.Forward;
|
||||
var left = LQuaternion.LookRotation(direction) * LQuaternion.Euler(0, 180 - 30, 0) * LVector3.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 LVector3 origin, in LQuaternion rotation, LFloat length, in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
var direction = rotation * LVector3.Forward;
|
||||
var destination = origin + direction * length;
|
||||
DrawArrow(origin, destination, color, duration);
|
||||
}
|
||||
|
||||
[System.Diagnostics.Conditional("UNITY_EDITOR")]
|
||||
public static void DrawArrow(in LVector3 origin, in LVector3 direction, LFloat length, in Color? color = null, LFloat duration = default) //duration = 0
|
||||
{
|
||||
var destination = origin + direction.Normalized * length;
|
||||
DrawArrow(origin, destination, color, duration);
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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));
|
||||
// Debug.DrawLine(from, to, color, showTime);
|
||||
// from = to;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
@@ -1,90 +1,89 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
#if UNITY_EDITOR
|
||||
using UnityEditor;
|
||||
using UnityEditor.Animations;
|
||||
#endif
|
||||
// using System.Collections.Generic;
|
||||
// using UnityEngine;
|
||||
|
||||
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>();
|
||||
// namespace GAS.General
|
||||
// {
|
||||
// #if UNITY_EDITOR
|
||||
// using UnityEditor;
|
||||
// using UnityEditor.Animations;
|
||||
// #endif
|
||||
|
||||
var runtimeController = animator.runtimeAnimatorController;
|
||||
if (runtimeController == null)
|
||||
{
|
||||
Debug.LogError("RuntimeAnimatorController must not be null.");
|
||||
return null;
|
||||
}
|
||||
// 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)
|
||||
// {
|
||||
// #if UNITY_EDITOR
|
||||
// var result = new Dictionary<string, AnimationClip>();
|
||||
|
||||
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 runtimeController = animator.runtimeAnimatorController;
|
||||
// if (runtimeController == null)
|
||||
// {
|
||||
// Debug.LogError("RuntimeAnimatorController 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 (animator.runtimeAnimatorController is AnimatorOverrideController)
|
||||
// {
|
||||
// var overrideController =
|
||||
// AssetDatabase.LoadAssetAtPath<AnimatorOverrideController>(
|
||||
// AssetDatabase.GetAssetPath(runtimeController));
|
||||
// if (overrideController == null)
|
||||
// {
|
||||
// Debug.LogErrorFormat("AnimatorOverrideController must not be null.");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
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;
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
// // 获取第一个 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
|
||||
}
|
||||
}
|
||||
}
|
||||
// return result;
|
||||
// #endif
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
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
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ac5d19bb315743de9f460aae665ccd42
|
||||
timeCreated: 1723619186
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d6f50d4142b94bc3b37233ff4806abf6
|
||||
timeCreated: 1723102748
|
||||
fileFormatVersion: 2
|
||||
guid: 4786837dcf7f48c8abd8fd5d6d92fda7
|
||||
timeCreated: 1723551728
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using JNGame.Runtime.Util;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
@@ -30,9 +30,7 @@ namespace GAS.General
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning("ArrayPool<T>.Recycle: obj is null");
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -115,9 +113,7 @@ namespace GAS.General
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using JNGame.Runtime.Util;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
|
||||
namespace GAS.General
|
||||
{
|
||||
@@ -12,20 +12,19 @@ namespace GAS.General
|
||||
bool IsFromPool { get; set; }
|
||||
}
|
||||
|
||||
public class JexGasObjectPool
|
||||
public class ObjectPool
|
||||
{
|
||||
private static JexGasObjectPool _singleton;
|
||||
|
||||
public static JexGasObjectPool Instance => _singleton ??= new JexGasObjectPool();
|
||||
private static ObjectPool _singleton;
|
||||
public static ObjectPool Instance => _singleton ??= new ObjectPool();
|
||||
|
||||
private readonly ConcurrentDictionary<Type, Pool> _objPool = new();
|
||||
|
||||
private readonly Func<Type, Pool> _addPoolFunc = type => new Pool(type, 1024);
|
||||
|
||||
public static void Awake()
|
||||
public static void Init()
|
||||
{
|
||||
_singleton = null;
|
||||
_singleton = new JexGasObjectPool();
|
||||
_singleton = new ObjectPool();
|
||||
}
|
||||
|
||||
public static void Destroy()
|
||||
@@ -90,7 +89,7 @@ namespace GAS.General
|
||||
/// <summary>
|
||||
/// 线程安全的无锁对象池
|
||||
/// </summary>
|
||||
private class Pool
|
||||
public class Pool
|
||||
{
|
||||
private readonly Type _objectType;
|
||||
private readonly int _maxCapacity;
|
||||
@@ -140,9 +139,7 @@ namespace GAS.General
|
||||
}
|
||||
|
||||
Interlocked.Decrement(ref _numItems);
|
||||
#if UNITY_EDITOR
|
||||
Debug.LogWarning($"Pool<{_objectType.FullName}>.Return: Exceed max capacity({_maxCapacity}), consider increase max capacity.");
|
||||
#endif
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1,350 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5fa92b69503043a58c6b9b79b15b8f2c
|
||||
timeCreated: 1719295275
|
||||
@@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
|
||||
namespace GAS.General
|
||||
@@ -17,8 +16,14 @@ namespace GAS.General
|
||||
try
|
||||
{
|
||||
var types = assembly.GetTypes();
|
||||
|
||||
sonTypes.AddRange(types.Where(type => type.IsSubclassOf(parentType) && !type.IsAbstract));
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (type.IsSubclassOf(parentType) && !type.IsAbstract)
|
||||
{
|
||||
sonTypes.Add(type);
|
||||
}
|
||||
}
|
||||
// sonTypes.AddRange(types.Where(type => type.IsSubclassOf(parentType) && !type.IsAbstract));
|
||||
}
|
||||
catch (ReflectionTypeLoadException)
|
||||
{
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 51efeef0278341a9ab883d49a20bd485
|
||||
timeCreated: 1711514345
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 207a4ba83f8f4b799e477e19a8d8b9d9
|
||||
timeCreated: 1729569447
|
||||
@@ -1,3 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cddbd5a2fd644ea68b4cf1d44faea8bf
|
||||
timeCreated: 1701928931
|
||||
guid: d02f2c4a6ac8451cbce922e1c3d29c9c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
|
||||
@@ -1,31 +1,29 @@
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class AbilityContainer
|
||||
{
|
||||
private readonly AbilitySystemComponent _owner;
|
||||
private AbilitySystemComponent _owner;
|
||||
private readonly Dictionary<string, AbilitySpec> _abilities = new();
|
||||
private readonly List<AbilitySpec> _cachedAbilities = new();
|
||||
|
||||
public AbilityContainer(AbilitySystemComponent owner)
|
||||
public void Awake(AbilitySystemComponent owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void Tick(int dt)
|
||||
public void Tick(int deltaTime)
|
||||
{
|
||||
var abilitySpecs = JexGasObjectPool.Instance.Fetch<List<AbilitySpec>>();
|
||||
abilitySpecs.AddRange(_abilities.Values);
|
||||
_cachedAbilities.AddRange(_abilities.Values);
|
||||
|
||||
foreach (var abilitySpec in abilitySpecs)
|
||||
foreach (var abilitySpec in _cachedAbilities)
|
||||
{
|
||||
abilitySpec.Tick(dt);
|
||||
abilitySpec.Tick(deltaTime);
|
||||
}
|
||||
|
||||
abilitySpecs.Clear();
|
||||
JexGasObjectPool.Instance.Recycle(abilitySpecs);
|
||||
_cachedAbilities.Clear();
|
||||
}
|
||||
|
||||
public void GrantAbility(AbstractAbility ability)
|
||||
@@ -49,25 +47,30 @@ namespace GAS.Runtime
|
||||
_abilities.Remove(abilityName);
|
||||
}
|
||||
|
||||
public bool TryActivateAbility(string abilityName, object arg = null, GameplayEffectSpec gameplayEffectSpec = null)
|
||||
/// <summary>
|
||||
/// 触发带有指定Tag的Ability
|
||||
/// </summary>
|
||||
/// <param name="tag"></param>
|
||||
public void TryActivateAbilityWithTag(in GameplayTag tag)
|
||||
{
|
||||
foreach (var kv in _abilities)
|
||||
{
|
||||
if (kv.Value.Ability.Tag.AssetTag.HasTag(tag))
|
||||
{
|
||||
TryActivateAbility(kv.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool TryActivateAbility(string abilityName, params object[] args)
|
||||
{
|
||||
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
|
||||
Debug.LogError($"you are trying to activate an ability that does not exist: {abilityName}");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_abilities[abilityName].TryActivateAbility(arg, gameplayEffectSpec)) return false;
|
||||
if (!_abilities[abilityName].TryActivateAbility(args)) return false;
|
||||
|
||||
var tags = _abilities[abilityName].Ability.Tag.CancelAbilitiesWithTags;
|
||||
foreach (var kv in _abilities)
|
||||
@@ -94,7 +97,7 @@ namespace GAS.Runtime
|
||||
_abilities[abilityName].TryCancelAbility();
|
||||
}
|
||||
|
||||
private void CancelAbilitiesByTag(in GameplayTagSet tags)
|
||||
public void CancelAbilitiesByTag(GameplayTagSet tags)
|
||||
{
|
||||
foreach (var kv in _abilities)
|
||||
{
|
||||
@@ -117,5 +120,29 @@ namespace GAS.Runtime
|
||||
public bool HasAbility(string abilityName) => _abilities.ContainsKey(abilityName);
|
||||
|
||||
public bool HasAbility(AbstractAbility ability) => HasAbility(ability.Name);
|
||||
|
||||
public AbilitySpec GetSpec(string abilityName)
|
||||
{
|
||||
_abilities.TryGetValue(abilityName, out AbilitySpec result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
_owner = default;
|
||||
_cachedAbilities.Clear();
|
||||
|
||||
foreach (var keyValuePair in _abilities)
|
||||
{
|
||||
_cachedAbilities.Add(keyValuePair.Value);
|
||||
}
|
||||
_abilities.Clear();
|
||||
|
||||
foreach (var cachedAbility in _cachedAbilities)
|
||||
{
|
||||
cachedAbility.Dispose();
|
||||
}
|
||||
_cachedAbilities.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public struct AbilityInstanceInfo
|
||||
{
|
||||
public AbilityAsset abilityAsset;
|
||||
public Type abilityType;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99ad85c3caba4604b9950ae1105e85b4
|
||||
timeCreated: 1706090943
|
||||
@@ -1,43 +1,22 @@
|
||||
using System;
|
||||
using System;
|
||||
using JNGame.Math;
|
||||
using UnityEngine;
|
||||
|
||||
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; }
|
||||
protected object[] _abilityArguments;
|
||||
|
||||
public AbilitySpec(AbstractAbility ability, AbilitySystemComponent owner)
|
||||
{
|
||||
Ability = ability;
|
||||
Owner = owner;
|
||||
Cooldown = new CooldownTimer { TimeRemaining = 0, Duration = Ability.CooldownTime };
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
{
|
||||
_onActivateResult = null;
|
||||
_onEndAbility = null;
|
||||
_onCancelAbility = null;
|
||||
}
|
||||
@@ -51,19 +30,16 @@ namespace GAS.Runtime
|
||||
public bool IsActive { get; private set; }
|
||||
|
||||
public int ActiveCount { get; private set; }
|
||||
protected event Action<AbilityActivateResult> _onActivateResult;
|
||||
|
||||
public bool IsCooling
|
||||
{
|
||||
get { return Cooldown.IsCooling; }
|
||||
}
|
||||
|
||||
protected event Action _onEndAbility;
|
||||
protected event Action _onCancelAbility;
|
||||
|
||||
public void RegisterActivateResult(Action<AbilityActivateResult> onActivateResult)
|
||||
{
|
||||
_onActivateResult += onActivateResult;
|
||||
}
|
||||
|
||||
public void UnregisterActivateResult(Action<AbilityActivateResult> onActivateResult)
|
||||
{
|
||||
_onActivateResult -= onActivateResult;
|
||||
}
|
||||
protected CooldownTimer Cooldown;
|
||||
|
||||
public void RegisterEndAbility(Action onEndAbility)
|
||||
{
|
||||
@@ -88,13 +64,12 @@ namespace GAS.Runtime
|
||||
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;
|
||||
@@ -110,75 +85,26 @@ namespace GAS.Runtime
|
||||
{
|
||||
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 != null && (attributeCurrentValue.Value + costValue) < LFloat.L0)
|
||||
return false;
|
||||
|
||||
if (modifier.Operation == GEOperation.Minus)
|
||||
if (attributeCurrentValue != null && (attributeCurrentValue.Value - costValue) < LFloat.L0)
|
||||
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);
|
||||
return Cooldown;
|
||||
}
|
||||
|
||||
/// <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()
|
||||
public virtual bool TryActivateAbility(params object[] args)
|
||||
{
|
||||
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;
|
||||
|
||||
_abilityArguments = args;
|
||||
var result = CanActivate();
|
||||
var success = result == AbilityActivateResult.Success;
|
||||
if (success)
|
||||
@@ -187,10 +113,10 @@ namespace GAS.Runtime
|
||||
ActiveCount++;
|
||||
Owner.GameplayTagAggregator.ApplyGameplayAbilityDynamicTag(this);
|
||||
|
||||
ActivateAbility();
|
||||
ActivateAbility(_abilityArguments);
|
||||
SetCooldown(Ability.CooldownTime);
|
||||
}
|
||||
|
||||
_onActivateResult?.Invoke(result);
|
||||
return success;
|
||||
}
|
||||
|
||||
@@ -213,32 +139,58 @@ namespace GAS.Runtime
|
||||
_onCancelAbility?.Invoke();
|
||||
}
|
||||
|
||||
public void Tick(int dt)
|
||||
public void Tick(int deltaTime)
|
||||
{
|
||||
if (IsActive)
|
||||
{
|
||||
AbilityTick(dt);
|
||||
AbilityTick(deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AbilityTick(int dt)
|
||||
public int GetCooldownRemaining()
|
||||
{
|
||||
return Cooldown.TimeRemaining;
|
||||
}
|
||||
|
||||
public int GetCooldownDuration()
|
||||
{
|
||||
return Cooldown.Duration;
|
||||
}
|
||||
|
||||
public void SetTotalCooldown(int total)
|
||||
{
|
||||
Cooldown.SetTotal(total);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 设置CD--同时修改总时长和当前剩余时间为传入值
|
||||
/// </summary>
|
||||
/// <param name="remain"></param>
|
||||
public void SetCooldown(int remain)
|
||||
{
|
||||
Cooldown.SetCooldown(remain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 减少CD
|
||||
/// </summary>
|
||||
/// <param name="cut"></param>
|
||||
public void SetCdCut(int cut)
|
||||
{
|
||||
if (Cooldown.IsCooling)
|
||||
{
|
||||
Cooldown.TimeRemaining -= Ability.CooldownTime * (cut.ToLFloat() / 10000.ToLFloat());
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void AbilityTick(int deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public abstract void ActivateAbility();
|
||||
public abstract void ActivateAbility(params object[] args);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +1,33 @@
|
||||
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 readonly int SkillId;
|
||||
public readonly IAbilityAsset DataReference;
|
||||
|
||||
public AbilityTagContainer Tag { get; protected set; }
|
||||
|
||||
public GameplayEffect Cooldown { get; protected set; }
|
||||
|
||||
public int CooldownTime { get; protected set; }
|
||||
|
||||
public GameplayEffect Cost { get; protected set; }
|
||||
|
||||
public AbstractAbility(AbilityAsset abilityAsset)
|
||||
public AbstractAbility(IAbilityAsset abilityAsset)
|
||||
{
|
||||
DataReference = abilityAsset;
|
||||
|
||||
Name = DataReference.UniqueName;
|
||||
SkillId = DataReference.SkillId;
|
||||
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)
|
||||
public void SetCooldown(int coolDownTime)
|
||||
{
|
||||
CooldownTime = coolDownTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 58b4935f7a5a43d69506e28666032462
|
||||
timeCreated: 1709451834
|
||||
@@ -1,12 +1,24 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public sealed class CatchSelf : TargetCatcherBase
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumTargetCatcherType.CatchSelf;
|
||||
|
||||
protected override void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results)
|
||||
{
|
||||
results.Add(Owner);
|
||||
results.Add(m_Owner);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,24 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public sealed class CatchTarget : TargetCatcherBase
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumTargetCatcherType.CatchTarget;
|
||||
|
||||
protected override void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results)
|
||||
{
|
||||
results.Add(mainTarget);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 目标捕获器类型枚举
|
||||
/// </summary>
|
||||
public enum EnumTargetCatcherType
|
||||
{
|
||||
CatchSelf = 1, // 捕获Ability的施法者自身
|
||||
CatchTarget, // 捕获Ability的AI目标
|
||||
|
||||
EnumBuiltinCount = 10, // 内建捕获器类型枚举的上限控制,上层业务派生由此开始
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f685a728eca9444a9e4c400403682e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class TargetCatcherBase
|
||||
public abstract class TargetCatcherBase : ISerializable
|
||||
{
|
||||
public AbilitySystemComponent Owner;
|
||||
protected AbilitySystemComponent m_Owner;
|
||||
|
||||
public virtual ushort TypeId => 0;
|
||||
|
||||
protected TargetCatcherBase()
|
||||
{
|
||||
@@ -14,7 +16,7 @@ namespace GAS.Runtime
|
||||
|
||||
public virtual void Init(AbilitySystemComponent owner)
|
||||
{
|
||||
Owner = owner;
|
||||
m_Owner = owner;
|
||||
}
|
||||
|
||||
[Obsolete("请使用CatchTargetsNonAlloc方法来避免产生垃圾收集(GC)。")]
|
||||
@@ -36,10 +38,15 @@ namespace GAS.Runtime
|
||||
|
||||
protected abstract void CatchTargetsNonAlloc(AbilitySystemComponent mainTarget, List<AbilitySystemComponent> results);
|
||||
|
||||
public abstract void Serialize(Serializer writer);
|
||||
|
||||
public abstract void Deserialize(Deserializer reader);
|
||||
|
||||
#if UNITY_EDITOR
|
||||
public virtual void OnEditorPreview(GameObject obj)
|
||||
public virtual void OnEditorPreview(UnityEngine.GameObject obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public partial class TargetCatcherFactory
|
||||
{
|
||||
public delegate TargetCatcherBase NodeCreateFunc();
|
||||
|
||||
private static readonly Dictionary<ushort, NodeCreateFunc> s_TypeId2FactoryFunc;
|
||||
|
||||
static TargetCatcherFactory()
|
||||
{
|
||||
s_TypeId2FactoryFunc = new Dictionary<ushort, NodeCreateFunc>();
|
||||
Register((ushort)EnumTargetCatcherType.CatchSelf, () => new CatchSelf());
|
||||
Register((ushort)EnumTargetCatcherType.CatchTarget, () => new CatchTarget());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册TargetCatcher构造方法
|
||||
/// </summary>
|
||||
/// <param name="typeId"></param>
|
||||
/// <param name="func"></param>
|
||||
public static void Register(ushort typeId, NodeCreateFunc func)
|
||||
{
|
||||
s_TypeId2FactoryFunc[typeId] = func;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建TargetCatcher
|
||||
/// </summary>
|
||||
/// <param name="typeId">Timeline节点类型</param>
|
||||
/// <returns></returns>
|
||||
public static TargetCatcherBase CreateNode(ushort typeId)
|
||||
{
|
||||
if (!s_TypeId2FactoryFunc.ContainsKey(typeId))
|
||||
{
|
||||
Debug.LogError("Can Not Find TargetCatcher Factory Function Id={typeId}");
|
||||
return null;
|
||||
}
|
||||
return s_TypeId2FactoryFunc[typeId]();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 953be87ac3b54214dade0088f592d40c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: af0af80f518a09a43bc4895a97f6d06d
|
||||
guid: a56af01f043a99d44b6b019b8b161c67
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline运行时节点枚举
|
||||
/// </summary>
|
||||
public enum EnumRuntimeClipNode : ushort
|
||||
{
|
||||
InstantGE = 1, // 直接添加一个GE效果,GE的时限效果由GE自己决定
|
||||
DurationalGE, // 持续性GE效果,Timeline控制其生命周期
|
||||
|
||||
InstantTask, // 瞬时的Task,Task可以理解为Condition+GE,判断条件,决定是否触发GE
|
||||
DurationalTask, // 持续一段时间的Task,Timeline控制其生命周期
|
||||
|
||||
PassiveGE, // 被动GE,Timeline只负责添加,不负责销毁,外部手动控制
|
||||
PassiveTask, // 被动Task,Timeline只负责添加,不负责销毁,外部手动控制
|
||||
|
||||
InstantCue, // 直接触发一个GameplayCue
|
||||
DurationalCue, // 持续型GameplayCue,Timeline控制其生命周期
|
||||
|
||||
EnumBuiltinCount = 100, // 内建行为树节点的类型上限,上层业务自行扩展的节点由此递增
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16f74976a1fc5ec43a86df42a97009e1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fa26d750811c9a44f88f4e478131110e
|
||||
guid: 00a222d3e7696384197e8aede030e7ba
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
@@ -0,0 +1,191 @@
|
||||
// Timeline节点的数据序列化
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public partial struct LinkGEAsset : ISerializable
|
||||
{
|
||||
public void Deserialize(Deserializer reader)
|
||||
{
|
||||
valueId = reader.ReadInt32();
|
||||
linkAssetLocation = reader.ReadString();
|
||||
}
|
||||
|
||||
public readonly void Serialize(Serializer writer)
|
||||
{
|
||||
writer.Write(valueId);
|
||||
writer.Write(linkAssetLocation);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class RuntimeClipNode : ISerializable
|
||||
{
|
||||
public virtual void Deserialize(Deserializer reader)
|
||||
{
|
||||
startFrame = reader.ReadInt32();
|
||||
durationalFrame = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public virtual void Serialize(Serializer writer)
|
||||
{
|
||||
writer.Write(startFrame);
|
||||
writer.Write(durationalFrame);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class InstantGEClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
// 目标捕获器
|
||||
ushort targetCatcherType = reader.ReadUInt16();
|
||||
targetCatcher = TargetCatcherFactory.CreateNode(targetCatcherType);
|
||||
targetCatcher.Deserialize(reader);
|
||||
// GE配置
|
||||
int length = reader.ReadInt32();
|
||||
linkGEAssets = new LinkGEAsset[length];
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
linkGEAssets[i].Deserialize(reader);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
// 目标捕获器
|
||||
writer.Write(targetCatcher.TypeId);
|
||||
targetCatcher.Serialize(writer);
|
||||
// GE配置
|
||||
writer.Write(linkGEAssets.Length);
|
||||
for (int i = 0; i < linkGEAssets.Length; ++i)
|
||||
{
|
||||
linkGEAssets[i].Serialize(writer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DurationalGEClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
linkGEAsset = new LinkGEAsset();
|
||||
linkGEAsset.Deserialize(reader);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
linkGEAsset.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class InstantTaskClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
ushort typeId = reader.ReadUInt16();
|
||||
instantTask = AbilityTaskFactory.CreateInstantTask(typeId);
|
||||
instantTask.Deserialize(reader);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(instantTask.TypeId);
|
||||
instantTask.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DurationalTaskClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
ushort typeId = reader.ReadUInt16();
|
||||
ongoingTask = AbilityTaskFactory.CreateOngoingTask(typeId);
|
||||
ongoingTask.Deserialize(reader);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(ongoingTask.TypeId);
|
||||
ongoingTask.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PassiveGEClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
linkGEAsset = new LinkGEAsset();
|
||||
linkGEAsset.Deserialize(reader);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
linkGEAsset.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class PassiveTaskClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
ushort typeId = reader.ReadUInt16();
|
||||
passiveTask = AbilityTaskFactory.CreatePassiveTask(typeId);
|
||||
passiveTask.Deserialize(reader);
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(passiveTask.TypeId);
|
||||
passiveTask.Serialize(writer);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class InstantCueClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
int count = reader.ReadUInt16();
|
||||
cueAssetLocations = new string[count];
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
cueAssetLocations[i] = reader.ReadString();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(cueAssetLocations);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class DurationalCueClipNode
|
||||
{
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
base.Deserialize(reader);
|
||||
cueAssetLocation = reader.ReadString();
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
base.Serialize(writer);
|
||||
writer.Write(cueAssetLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7137dd96f3d58434fa035d073b5881eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cfcc47cc435a1514c9cf242b88b4c813
|
||||
TextScriptImporter:
|
||||
guid: 81083f075918f2a48bdeba6f799e36a6
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
@@ -0,0 +1,48 @@
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 持续型Cue节点
|
||||
/// </summary>
|
||||
public partial class DurationalCueClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.DurationalCue;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
/// <summary>
|
||||
/// 关联的Cue资源
|
||||
/// </summary>
|
||||
public string cueAssetLocation;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 表现层返回的Cue实例索引ID,用于逻辑层通知表现层移除使用,与逻辑层业务逻辑无关
|
||||
/// </summary>
|
||||
private int m_CueIndex = -1;
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
if (workingContext == null || workingContext.OwnerAbility == null || workingContext.OwnerAbility.Owner == null)
|
||||
{
|
||||
Debug.Log("error");
|
||||
}
|
||||
m_CueIndex = workingContext.OwnerAbility.Owner.OnCueAdd(workingContext.OwnerAbility, cueAssetLocation);
|
||||
}
|
||||
|
||||
protected override void OnEnd(TimelineWorkingContext workingContext)
|
||||
{
|
||||
if (m_CueIndex < 0) { return; }
|
||||
workingContext.OwnerAbility.Owner.OnCueRemove(workingContext.OwnerAbility, m_CueIndex);
|
||||
m_CueIndex = -1;
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
OnEnd(workingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68623f73aa1255d419c86dc8d08d246c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 直接添加一个GE效果,GE的时限效果由GE自己决定
|
||||
/// </summary>
|
||||
public sealed partial class DurationalGEClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.DurationalGE;
|
||||
|
||||
#region 序列化数据
|
||||
|
||||
public LinkGEAsset linkGEAsset;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 持续性GE实例
|
||||
/// </summary>
|
||||
private GameplayEffectSpec m_EffectSpec;
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
IGameplayEffectData geAsset = workingContext.GetGASResourceService().GetGameplayEffectData(linkGEAsset.linkAssetLocation);
|
||||
if (geAsset == null) { return; }
|
||||
// 只有持续型的GameplayEffect可视作buff
|
||||
if (geAsset.DurationPolicy is EffectsDurationPolicy.Duration or EffectsDurationPolicy.Infinite)
|
||||
{
|
||||
GameplayEffect ge = new GameplayEffect(geAsset, linkGEAsset.valueId);
|
||||
GameplayEffectSpec buffSpec = workingContext.OwnerAbility.Owner.ApplyGameplayEffectToSelf(ge, workingContext.OwnerAbility.Ability.SkillId);
|
||||
if (buffSpec != null)
|
||||
{ // buff持续时间以Timeline配置时间为准(执行策略全部改为Infinite)
|
||||
buffSpec.SetDurationPolicy(EffectsDurationPolicy.Infinite);
|
||||
m_EffectSpec = buffSpec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnEnd(TimelineWorkingContext workingContext)
|
||||
{
|
||||
if (m_EffectSpec == null) { return; }
|
||||
workingContext.OwnerAbility.Owner.RemoveGameplayEffect(m_EffectSpec);
|
||||
m_EffectSpec = null;
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
// 防止Timeline被打断执行,没有执行OnEnd
|
||||
OnEnd(workingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73748f3e49399294c93c815c9bd90173
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 瞬时Task节点
|
||||
/// </summary>
|
||||
public partial class DurationalTaskClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.DurationalTask;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
public OngoingAbilityTask ongoingTask;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnAwake(TimelineWorkingContext workingContext)
|
||||
{
|
||||
ongoingTask.Init(workingContext.OwnerAbility);
|
||||
}
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
ongoingTask.Init(workingContext.OwnerAbility);
|
||||
ongoingTask?.OnStart(workingContext.CurrentFrame);
|
||||
}
|
||||
|
||||
protected override void OnUpdate(TimelineWorkingContext workingContext, int deltaTime)
|
||||
{
|
||||
ongoingTask?.OnTick(workingContext.CurrentFrame, startFrame, EndFrame);
|
||||
}
|
||||
|
||||
protected override void OnEnd(TimelineWorkingContext workingContext)
|
||||
{
|
||||
ongoingTask?.OnEnd(workingContext.CurrentFrame);
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
// 防止Timeline被打断执行,没有执行OnEnd
|
||||
ongoingTask?.OnEnd(EndFrame);
|
||||
ongoingTask?.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ccc58d735ca245c4dbc2545104b20811
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 瞬时Cue节点
|
||||
/// </summary>
|
||||
public partial class InstantCueClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.InstantCue;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
public string[] cueAssetLocations;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
workingContext.OwnerAbility.Owner.OnCueExecute(workingContext.OwnerAbility, cueAssetLocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7fa54cf9f74fa1c40be8ce5651dbab5d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 直接添加一个GE效果,GE的时限效果由GE自己决定
|
||||
/// </summary>
|
||||
public sealed partial class InstantGEClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.InstantGE;
|
||||
|
||||
#region 序列化数据
|
||||
|
||||
/// <summary>
|
||||
/// 关联的GE资源标识
|
||||
/// </summary>
|
||||
public LinkGEAsset[] linkGEAssets;
|
||||
|
||||
/// <summary>
|
||||
/// 目标捕获器
|
||||
/// </summary>
|
||||
public TargetCatcherBase targetCatcher;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
// 初始化目标捕获器,筛选GE的作用目标
|
||||
targetCatcher.Init(workingContext.OwnerAbility.Owner);
|
||||
targetCatcher.CatchTargetsNonAllocSafe(workingContext.OwnerAbility.Target, workingContext.Targets);
|
||||
// 添加GE
|
||||
foreach (var asc in workingContext.Targets)
|
||||
{
|
||||
foreach (var linkGEInfo in linkGEAssets)
|
||||
{
|
||||
IGameplayEffectData geAssetData = workingContext.GetGASResourceService().GetGameplayEffectData(linkGEInfo.linkAssetLocation);
|
||||
var ge = new GameplayEffect(geAssetData, linkGEInfo.valueId);
|
||||
workingContext.OwnerAbility.Owner.ApplyGameplayEffectTo(ge, asc, workingContext.OwnerAbility.Ability.SkillId);
|
||||
}
|
||||
}
|
||||
workingContext.Targets.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 558296a0eeb98f843aa2149d21834cca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 瞬时Task节点
|
||||
/// </summary>
|
||||
public partial class InstantTaskClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.InstantTask;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
public InstantAbilityTask instantTask;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnAwake(TimelineWorkingContext workingContext)
|
||||
{
|
||||
instantTask.Init(workingContext.OwnerAbility);
|
||||
}
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
instantTask?.OnExecute();
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
instantTask.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcf8748d66384df458536a0246f1858f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 被动持续的GE效果节点,不随Timline播放结束而结束
|
||||
/// </summary>
|
||||
public partial class PassiveGEClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.PassiveGE;
|
||||
|
||||
#region 序列化数据
|
||||
|
||||
/// <summary>
|
||||
/// 关联的GE配置
|
||||
/// </summary>
|
||||
public LinkGEAsset linkGEAsset;
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 持续性GE实例
|
||||
/// </summary>
|
||||
private GameplayEffectSpec m_EffectSpec;
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
IGameplayEffectData geAsset = workingContext.GetGASResourceService().GetGameplayEffectData(linkGEAsset.linkAssetLocation);
|
||||
if (geAsset == null) { return; }
|
||||
// 只有持续型的GameplayEffect可视作Passive
|
||||
if (geAsset.DurationPolicy is EffectsDurationPolicy.Duration or EffectsDurationPolicy.Infinite)
|
||||
{
|
||||
GameplayEffect ge = new GameplayEffect(geAsset, linkGEAsset.valueId);
|
||||
GameplayEffectSpec buffSpec = workingContext.OwnerAbility.Owner.ApplyGameplayEffectToSelf(ge, workingContext.OwnerAbility.Ability.SkillId);
|
||||
if (buffSpec != null)
|
||||
{ // passive在TimelineAbility激活时一直生效
|
||||
buffSpec.SetDurationPolicy(EffectsDurationPolicy.Infinite);
|
||||
m_EffectSpec = buffSpec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
if (m_EffectSpec == null) { return; }
|
||||
workingContext.OwnerAbility.Owner.RemoveGameplayEffect(m_EffectSpec);
|
||||
m_EffectSpec = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5784d2561744253419d2cd63af70f7a7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 被动持续的Task节点,不随Timline播放结束而结束
|
||||
/// </summary>
|
||||
public partial class PassiveTaskClipNode : RuntimeClipNode
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumRuntimeClipNode.PassiveTask;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
public PassiveAbilityTask passiveTask;
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnAwake(TimelineWorkingContext workingContext)
|
||||
{
|
||||
passiveTask.Init(workingContext.OwnerAbility);
|
||||
}
|
||||
|
||||
protected override void OnStart(TimelineWorkingContext workingContext)
|
||||
{
|
||||
passiveTask?.OnStart();
|
||||
}
|
||||
|
||||
protected override void OnUpdate(TimelineWorkingContext workingContext, int deltaTime)
|
||||
{
|
||||
passiveTask?.OnTick(deltaTime);
|
||||
}
|
||||
|
||||
protected override void OnDestroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
passiveTask?.OnEnd();
|
||||
passiveTask?.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b679e7a8b1b2e5f40925c990cd6d1c4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,88 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline节点基类
|
||||
/// </summary>
|
||||
public abstract partial class RuntimeClipNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline的节点类型
|
||||
/// </summary>
|
||||
public virtual ushort TypeId => 0;
|
||||
|
||||
#region 序列化字段
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行帧
|
||||
/// </summary>
|
||||
public int startFrame;
|
||||
|
||||
/// <summary>
|
||||
/// 持续帧长度
|
||||
/// </summary>
|
||||
public int durationalFrame;
|
||||
|
||||
/// <summary>
|
||||
/// 结束执行帧
|
||||
/// </summary>
|
||||
public int EndFrame => startFrame + durationalFrame;
|
||||
|
||||
#endregion
|
||||
|
||||
public void Awake(TimelineWorkingContext workingContext)
|
||||
{
|
||||
OnAwake(workingContext);
|
||||
}
|
||||
|
||||
public void Start(TimelineWorkingContext workingContext)
|
||||
{
|
||||
OnStart(workingContext);
|
||||
}
|
||||
|
||||
public void Update(TimelineWorkingContext workingContext, int deltaTime)
|
||||
{
|
||||
OnUpdate(workingContext, deltaTime);
|
||||
}
|
||||
|
||||
public void End(TimelineWorkingContext workingContext)
|
||||
{
|
||||
OnEnd(workingContext);
|
||||
}
|
||||
|
||||
public void Destroy(TimelineWorkingContext workingContext)
|
||||
{
|
||||
OnDestroy(workingContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeline节点初始化
|
||||
/// </summary>
|
||||
/// <param name="workingContext"></param>
|
||||
protected virtual void OnAwake(TimelineWorkingContext workingContext)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeline节点开始触发
|
||||
/// </summary>
|
||||
protected abstract void OnStart(TimelineWorkingContext workingContext);
|
||||
|
||||
/// <summary>
|
||||
/// Timeline节点持续Tick
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
protected virtual void OnUpdate(TimelineWorkingContext workingContext, int deltaTime) { }
|
||||
|
||||
/// <summary>
|
||||
/// Timeline节点结束
|
||||
/// </summary>
|
||||
protected virtual void OnEnd(TimelineWorkingContext workingContext) { }
|
||||
|
||||
/// <summary>
|
||||
/// Timeline执行结束,清理操作
|
||||
/// </summary>
|
||||
/// <param name="workingContext"></param>
|
||||
protected virtual void OnDestroy(TimelineWorkingContext workingContext) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03f418c0ff668c14985b4f2999e2236d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,98 @@
|
||||
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline运行结构描述,通过二进制文件反序列化而来
|
||||
/// </summary>
|
||||
public sealed class TimelineInfo : ISerializable
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline总帧长度
|
||||
/// </summary>
|
||||
public int totalFrame;
|
||||
|
||||
/// <summary>
|
||||
/// 是否手动结束Timeline
|
||||
/// </summary>
|
||||
public bool endManual;
|
||||
|
||||
/// <summary>
|
||||
/// Timeline节点的原生序列化数据
|
||||
/// </summary>
|
||||
public byte[] timelineBytes;
|
||||
|
||||
private Deserializer m_RealtimeReader;
|
||||
|
||||
public RuntimeClipNode[] CreateRealTimeClipNode()
|
||||
{
|
||||
m_RealtimeReader ??= new Deserializer();
|
||||
m_RealtimeReader.SetSource(timelineBytes);
|
||||
int length = m_RealtimeReader.ReadUInt16();
|
||||
RuntimeClipNode[] runtimeClipNodes = new RuntimeClipNode[length];
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
ushort typeId = m_RealtimeReader.ReadUInt16();
|
||||
RuntimeClipNode node = TimelineNodeFactory.CreateNode(typeId);
|
||||
node.Deserialize(m_RealtimeReader);
|
||||
runtimeClipNodes[i] = node;
|
||||
}
|
||||
|
||||
return runtimeClipNodes;
|
||||
}
|
||||
|
||||
public void Deserialize(Deserializer reader)
|
||||
{
|
||||
endManual = reader.ReadBoolean();
|
||||
totalFrame = reader.ReadInt32();
|
||||
timelineBytes = reader.ReadArray(timelineBytes);
|
||||
}
|
||||
|
||||
public void Serialize(Serializer writer)
|
||||
{
|
||||
writer.Write(endManual);
|
||||
writer.Write(totalFrame);
|
||||
writer.Write(timelineBytes);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// Timeline的节点,按时序排序,编辑器下检查序列化是否一致使用
|
||||
/// </summary>
|
||||
public RuntimeClipNode[] runtimeClipNodes;
|
||||
|
||||
public void DeserializeForEditor(Deserializer reader)
|
||||
{
|
||||
endManual = reader.ReadBoolean();
|
||||
totalFrame = reader.ReadInt32();
|
||||
reader.ReadUInt16();
|
||||
int length = reader.ReadUInt16();
|
||||
runtimeClipNodes = new RuntimeClipNode[length];
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
ushort typeId = reader.ReadUInt16();
|
||||
RuntimeClipNode node = TimelineNodeFactory.CreateNode(typeId);
|
||||
node.Deserialize(reader);
|
||||
runtimeClipNodes[i] = node;
|
||||
}
|
||||
}
|
||||
|
||||
public void SerializeForEditor(Serializer writer)
|
||||
{
|
||||
writer.Write(endManual);
|
||||
writer.Write(totalFrame);
|
||||
Serializer timelineWriter = new Serializer();
|
||||
ushort length = (ushort)(runtimeClipNodes == null ? 0 : runtimeClipNodes.Length);
|
||||
timelineWriter.Write(length);
|
||||
for (int i = 0; i < length; ++i)
|
||||
{
|
||||
timelineWriter.Write(runtimeClipNodes[i].TypeId);
|
||||
runtimeClipNodes[i].Serialize(timelineWriter);
|
||||
}
|
||||
writer.Write(timelineWriter.CopyData());
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: df29214abf293a947871997803a995e8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public partial class TimelineNodeFactory
|
||||
{
|
||||
public delegate RuntimeClipNode NodeCreateFunc();
|
||||
|
||||
private static readonly Dictionary<ushort, NodeCreateFunc> s_TypeId2FactoryFunc;
|
||||
|
||||
static TimelineNodeFactory()
|
||||
{
|
||||
s_TypeId2FactoryFunc = new Dictionary<ushort, NodeCreateFunc>();
|
||||
Register((ushort)EnumRuntimeClipNode.InstantGE, () => new InstantGEClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.DurationalGE, () => new DurationalGEClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.InstantTask, () => new InstantTaskClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.DurationalTask, () => new DurationalTaskClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.PassiveGE, () => new PassiveGEClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.PassiveTask, () => new PassiveTaskClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.InstantCue, () => new InstantCueClipNode());
|
||||
Register((ushort)EnumRuntimeClipNode.DurationalCue, () => new DurationalCueClipNode());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册Timeline节点类型构造方法
|
||||
/// </summary>
|
||||
/// <param name="typeId"></param>
|
||||
/// <param name="func"></param>
|
||||
public static void Register(ushort typeId, NodeCreateFunc func)
|
||||
{
|
||||
s_TypeId2FactoryFunc[typeId] = func;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建Timeline节点
|
||||
/// </summary>
|
||||
/// <param name="typeId">Timeline节点类型</param>
|
||||
/// <returns></returns>
|
||||
public static RuntimeClipNode CreateNode(ushort typeId)
|
||||
{
|
||||
if (!s_TypeId2FactoryFunc.ContainsKey(typeId))
|
||||
{
|
||||
Debug.LogError($"Can Not Find TimelineClipNode Factory Function Id={typeId}");
|
||||
return null;
|
||||
}
|
||||
return s_TypeId2FactoryFunc[typeId]();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f011728d4f11f524ebcba27077e6f402
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using GAS.General;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 运行时的Timeline实例
|
||||
/// <para>包含反序列化的Timeline结构描述TimelineAsset(运行时不变),不同Timeline实例可指向同一TimelineAsset</para>
|
||||
/// </summary>
|
||||
public sealed class TimelinePlayer
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline结构描述配置信息,二进制文件读入
|
||||
/// </summary>
|
||||
private TimelineInfo m_TimelineInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Timeline运行时数据,可在Timeline各节点间互相范围的公共数据
|
||||
/// </summary>
|
||||
private TimelineWorkingContext m_WorkingContext;
|
||||
public TimelineWorkingContext WorkingContext => m_WorkingContext;
|
||||
|
||||
public bool IsInitialized { get; private set; }
|
||||
public bool IsPlaying { get; private set; }
|
||||
|
||||
private RuntimeClipNode[] m_RuntimeClipNode;
|
||||
|
||||
private List<PassiveTaskClipNode> m_PassiveTaskNodes;
|
||||
|
||||
public void DoAwake(TimelineAbilitySpec abilitySpec)
|
||||
{
|
||||
IsInitialized = false;
|
||||
ITimelineAbilityAsset abilityAsset = abilitySpec.Ability.DataReference as ITimelineAbilityAsset;
|
||||
if (abilityAsset == null)
|
||||
{
|
||||
Debug.LogError($"AbilityTimeline异常,没有找到对应的Timeline技能配置,{abilitySpec.Ability.Name}");
|
||||
return;
|
||||
}
|
||||
m_WorkingContext = new TimelineWorkingContext(abilitySpec);
|
||||
m_TimelineInfo = abilityAsset.TimelineAbilityInfo;
|
||||
m_RuntimeClipNode = m_TimelineInfo.CreateRealTimeClipNode();
|
||||
// Passive缓存,需要在PlayEnd后继续Tick
|
||||
m_PassiveTaskNodes = new List<PassiveTaskClipNode>();
|
||||
for (int i = 0; i < m_RuntimeClipNode.Length; ++i)
|
||||
{
|
||||
if (m_RuntimeClipNode[i] is PassiveTaskClipNode passiveTaskClipNode)
|
||||
{
|
||||
m_PassiveTaskNodes.Add(passiveTaskClipNode);
|
||||
}
|
||||
}
|
||||
IsInitialized = true;
|
||||
IsPlaying = false;
|
||||
}
|
||||
|
||||
public void DoUpdate(int deltaTime)
|
||||
{
|
||||
if (!IsInitialized) { return; }
|
||||
|
||||
if (!IsPlaying)
|
||||
{ // 播放结束,仍然执行PassiveTask的Tick
|
||||
if (WorkingContext.CurrentFrame >= m_TimelineInfo.totalFrame)
|
||||
{
|
||||
TickPassiveTask(deltaTime);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
WorkingContext.TotalRunTime += deltaTime;
|
||||
var targetFrame = WorkingContext.TotalRunTime / GASTimer.TimeLineAbilityTickTime;
|
||||
while (WorkingContext.CurrentFrame < targetFrame)
|
||||
{
|
||||
++WorkingContext.CurrentFrame;
|
||||
TickNormal(WorkingContext.CurrentFrame, deltaTime);
|
||||
}
|
||||
if (WorkingContext.CurrentFrame >= m_TimelineInfo.totalFrame)
|
||||
{ // 执行到最后一帧
|
||||
OnPlayEnd();
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
if (!IsInitialized) { return; }
|
||||
m_WorkingContext.CurrentFrame = -1; // 为了播放第0帧
|
||||
m_WorkingContext.TotalRunTime = 0;
|
||||
foreach (var timelineNode in m_RuntimeClipNode)
|
||||
{
|
||||
timelineNode.Awake(m_WorkingContext);
|
||||
}
|
||||
IsPlaying = true;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!IsInitialized) { return; }
|
||||
|
||||
foreach (RuntimeClipNode clipNode in m_RuntimeClipNode)
|
||||
{
|
||||
clipNode.Destroy(m_WorkingContext);
|
||||
}
|
||||
IsPlaying = false;
|
||||
// m_WorkingContext = null;
|
||||
// m_TimelineInfo = null;
|
||||
// m_PassiveTaskNodes = null;
|
||||
// IsInitialized = false;
|
||||
}
|
||||
|
||||
private void TickNormal(int currentFrame, int deltaTime)
|
||||
{
|
||||
foreach (RuntimeClipNode clipNode in m_RuntimeClipNode)
|
||||
{
|
||||
if (currentFrame == clipNode.startFrame)
|
||||
{
|
||||
clipNode.Start(m_WorkingContext);
|
||||
}
|
||||
if (currentFrame >= clipNode.startFrame && currentFrame <= clipNode.EndFrame)
|
||||
{
|
||||
clipNode.Update(m_WorkingContext, deltaTime);
|
||||
}
|
||||
if (currentFrame == clipNode.EndFrame)
|
||||
{
|
||||
clipNode.End(m_WorkingContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TickPassiveTask(int deltaTime)
|
||||
{
|
||||
int currentFrame = WorkingContext.CurrentFrame;
|
||||
for (int i = 0; i < m_PassiveTaskNodes.Count; ++i)
|
||||
{
|
||||
if (currentFrame == m_PassiveTaskNodes[i].startFrame)
|
||||
{
|
||||
m_PassiveTaskNodes[i].Start(m_WorkingContext);
|
||||
}
|
||||
m_PassiveTaskNodes[i].Update(m_WorkingContext, deltaTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Timeline播放到最后一帧
|
||||
/// </summary>
|
||||
private void OnPlayEnd()
|
||||
{
|
||||
IsPlaying = false;
|
||||
if (!m_TimelineInfo.endManual)
|
||||
{
|
||||
m_WorkingContext.OwnerAbility.TryEndAbility();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 633643c43c44bec4a90cc06ab78d76a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using JNGame.GAS;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// Timeline运行时上下文数据,可在Timeline各节点间共享数据
|
||||
/// </summary>
|
||||
public class TimelineWorkingContext
|
||||
{
|
||||
/// <summary>
|
||||
/// 关联的Ability运行时实例
|
||||
/// </summary>
|
||||
public TimelineAbilitySpec OwnerAbility { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// GE作用目标集
|
||||
/// </summary>
|
||||
private readonly List<AbilitySystemComponent> m_Targets = new List<AbilitySystemComponent>();
|
||||
public List<AbilitySystemComponent> Targets => m_Targets;
|
||||
|
||||
/// <summary>
|
||||
/// Timeline当前推进到的帧
|
||||
/// </summary>
|
||||
public int CurrentFrame { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timeline当前累计执行时长
|
||||
/// </summary>
|
||||
public int TotalRunTime { get; internal set; }
|
||||
|
||||
public TimelineWorkingContext(TimelineAbilitySpec abilitySpec)
|
||||
{
|
||||
OwnerAbility = abilitySpec;
|
||||
}
|
||||
|
||||
public IGASResourceService GetGASResourceService()
|
||||
{
|
||||
return OwnerAbility.Owner.GASResService;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad0bfdbb87f21d54d9fd1c118fddaec2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,12 +1,31 @@
|
||||
namespace GAS.Runtime
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public abstract class AbilityTaskBase
|
||||
/// <summary>
|
||||
/// AbilityTask的基类
|
||||
/// </summary>
|
||||
public abstract class AbilityTaskBase : ISerializable
|
||||
{
|
||||
protected AbilitySpec _spec;
|
||||
public AbilitySpec Spec => _spec;
|
||||
/// <summary>
|
||||
/// AbilityTask的类型ID,用于重建各派生AbilityTask实例
|
||||
/// </summary>
|
||||
public virtual ushort TypeId => 0;
|
||||
|
||||
protected AbilitySpec m_Spec;
|
||||
|
||||
public virtual void Init(AbilitySpec spec)
|
||||
{
|
||||
_spec = spec;
|
||||
m_Spec = spec;
|
||||
}
|
||||
|
||||
public virtual void Clear()
|
||||
{
|
||||
// m_Spec = null;
|
||||
}
|
||||
|
||||
public abstract void Serialize(Serializer writer);
|
||||
|
||||
public abstract void Deserialize(Deserializer reader);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// AbilityTask构造工厂
|
||||
/// </summary>
|
||||
public static class AbilityTaskFactory
|
||||
{
|
||||
public delegate AbilityTaskBase NodeCreateFunc();
|
||||
|
||||
/// <summary>
|
||||
/// TypeID映射构造方法
|
||||
/// </summary>
|
||||
private static readonly Dictionary<ushort, NodeCreateFunc> s_TypeId2FactoryFunc;
|
||||
|
||||
static AbilityTaskFactory()
|
||||
{
|
||||
s_TypeId2FactoryFunc = new Dictionary<ushort, NodeCreateFunc>();
|
||||
Register((ushort)EnumAbilityTaskType.InstantTaskStart, () => new DefaultInstantAbilityTask());
|
||||
Register((ushort)EnumAbilityTaskType.OngoingTaskStart, () => new DefaultOngoingAbilityTask());
|
||||
Register((ushort)EnumAbilityTaskType.PassiveTaskStart, () => new DefaultPassiveAbilityTask());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册Timeline节点类型构造方法
|
||||
/// </summary>
|
||||
/// <param name="typeId"></param>
|
||||
/// <param name="func"></param>
|
||||
public static void Register(ushort typeId, NodeCreateFunc func)
|
||||
{
|
||||
s_TypeId2FactoryFunc[typeId] = func;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建InstantAbilityTask实例
|
||||
/// </summary>
|
||||
/// <param name="typeId">AbilityTask类型</param>
|
||||
/// <returns></returns>
|
||||
public static InstantAbilityTask CreateInstantTask(ushort typeId)
|
||||
{
|
||||
if (typeId < (ushort)EnumAbilityTaskType.InstantTaskStart && typeId >= (ushort)EnumAbilityTaskType.OngoingTaskStart)
|
||||
{
|
||||
Debug.LogError($"TypeId不是InstantTask派生的,TypeId={typeId}");
|
||||
return null;
|
||||
}
|
||||
return CreateTask(typeId) as InstantAbilityTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建OngoingAbilityTask实例
|
||||
/// </summary>
|
||||
/// <param name="typeId">AbilityTask类型</param>
|
||||
/// <returns></returns>
|
||||
public static OngoingAbilityTask CreateOngoingTask(ushort typeId)
|
||||
{
|
||||
if (typeId < (ushort)EnumAbilityTaskType.OngoingTaskStart && typeId >= (ushort)EnumAbilityTaskType.PassiveTaskStart)
|
||||
{
|
||||
Debug.LogError($"TypeId不是OngoingTask派生的,TypeId={typeId}");
|
||||
return null;
|
||||
}
|
||||
return CreateTask(typeId) as OngoingAbilityTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建PassiveAbilityTask实例
|
||||
/// </summary>
|
||||
/// <param name="typeId">AbilityTask类型</param>
|
||||
/// <returns></returns>
|
||||
public static PassiveAbilityTask CreatePassiveTask(ushort typeId)
|
||||
{
|
||||
if (typeId < (ushort)EnumAbilityTaskType.PassiveTaskStart)
|
||||
{
|
||||
Debug.LogError($"TypeId不是PassiveTask派生的,TypeId={typeId}");
|
||||
return null;
|
||||
}
|
||||
return CreateTask(typeId) as PassiveAbilityTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 通过节点类型创建AbilityTask实例
|
||||
/// </summary>
|
||||
/// <param name="typeId">AbilityTask类型</param>
|
||||
/// <returns></returns>
|
||||
public static AbilityTaskBase CreateTask(ushort typeId)
|
||||
{
|
||||
if (!s_TypeId2FactoryFunc.ContainsKey(typeId))
|
||||
{
|
||||
Debug.LogError($"没有注册的AbilityTask, TypeId={typeId}");
|
||||
return null;
|
||||
}
|
||||
return s_TypeId2FactoryFunc[typeId]();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dd3b4da37ee90634ba4dfabea86fddb3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// AbilityTask的类型枚举
|
||||
/// </summary>
|
||||
public enum EnumAbilityTaskType : ushort
|
||||
{
|
||||
InstantTaskStart = 1000, // InstantTask派生的起始Type索引,上层派生由此递增
|
||||
|
||||
OngoingTaskStart = 2000, // OngoingTask派生的起始Type索引,上层派生由此递增
|
||||
|
||||
PassiveTaskStart = 3000, // PassiveTask派生的起始Type索引,上层派生由此递增
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d25566798f1d04e41915811072188f07
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,7 +1,14 @@
|
||||
namespace GAS.Runtime
|
||||
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行一帧的Task,错过不候
|
||||
/// </summary>
|
||||
public abstract class InstantAbilityTask : AbilityTaskBase
|
||||
{
|
||||
|
||||
#if UNITY_EDITOR
|
||||
/// <summary>
|
||||
/// 编辑器预览用
|
||||
@@ -11,11 +18,18 @@
|
||||
{
|
||||
}
|
||||
#endif
|
||||
public abstract void OnExecute();
|
||||
}
|
||||
|
||||
public abstract class InstantAbilityTaskT<T> : InstantAbilityTask where T : AbilitySpec
|
||||
{
|
||||
public new T Spec => (T)_spec;
|
||||
/// <summary>
|
||||
/// 抵达触发时机,执行的自定义Task内容
|
||||
/// </summary>
|
||||
public abstract void OnExecute();
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
namespace GAS.Runtime
|
||||
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行一段时间的AbilityTask
|
||||
/// </summary>
|
||||
public abstract class OngoingAbilityTask : AbilityTaskBase
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
@@ -14,15 +20,33 @@
|
||||
{
|
||||
}
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// 开始执行
|
||||
/// </summary>
|
||||
/// <param name="startFrame"></param>
|
||||
public abstract void OnStart(int startFrame);
|
||||
|
||||
/// <summary>
|
||||
/// 结束执行
|
||||
/// </summary>
|
||||
/// <param name="endFrame"></param>
|
||||
public abstract void OnEnd(int endFrame);
|
||||
|
||||
/// <summary>
|
||||
/// Tick更新
|
||||
/// </summary>
|
||||
/// <param name="frameIndex"></param>
|
||||
/// <param name="startFrame"></param>
|
||||
/// <param name="endFrame"></param>
|
||||
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;
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
/// <summary>
|
||||
@@ -33,5 +34,13 @@ namespace GAS.Runtime
|
||||
/// </summary>
|
||||
/// <param name="deltaTime"></param>
|
||||
public abstract void OnTick(int deltaTime);
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5a9d88b958e546c7aaef67ab354c71a4
|
||||
timeCreated: 1729189790
|
||||
guid: 39de6ad84e9d43f1bce81ac2b76b0d1a
|
||||
timeCreated: 1717572967
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dce896cd86840d68e56efd1948114f5
|
||||
timeCreated: 1729189740
|
||||
@@ -1,13 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class ApplyCostAndCoolDown : InstantAbilityTask
|
||||
{
|
||||
public override void OnExecute()
|
||||
{
|
||||
_spec.DoCost();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c8689e959d44db393a7821d0923f750
|
||||
timeCreated: 1709277143
|
||||
@@ -1,9 +1,21 @@
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultInstantAbilityTask : InstantAbilityTask
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumAbilityTaskType.InstantTaskStart;
|
||||
|
||||
public override void OnExecute()
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
namespace GAS.Runtime
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultOngoingAbilityTask : OngoingAbilityTask
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumAbilityTaskType.OngoingTaskStart;
|
||||
|
||||
public override void OnStart(int startFrame)
|
||||
{
|
||||
}
|
||||
@@ -13,5 +17,13 @@
|
||||
public override void OnTick(int frameIndex, int startFrame, int endFrame)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
|
||||
using JNGame.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
public class DefaultPassiveAbilityTask : PassiveAbilityTask
|
||||
{
|
||||
public override ushort TypeId => (ushort)EnumAbilityTaskType.PassiveTaskStart;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
}
|
||||
@@ -14,5 +17,13 @@ namespace GAS.Runtime
|
||||
public override void OnTick(int deltaTime)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Serialize(Serializer writer)
|
||||
{
|
||||
}
|
||||
|
||||
public override void Deserialize(Deserializer reader)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3826985009604c178d6623947f501916
|
||||
timeCreated: 1729189761
|
||||
guid: 64c722b82d12457091806f6de5fb1d90
|
||||
timeCreated: 1717572995
|
||||
@@ -1,76 +1,12 @@
|
||||
using JNGame.Math;
|
||||
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 LFloat 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 LFloat GetPlaySpeed()
|
||||
{
|
||||
return Data.AbilityAsset.Speed;
|
||||
}
|
||||
|
||||
public override void CancelAbility()
|
||||
{
|
||||
_player.Stop();
|
||||
}
|
||||
|
||||
public override void EndAbility()
|
||||
{
|
||||
_player.Stop();
|
||||
}
|
||||
|
||||
protected override void AbilityTick(int dt)
|
||||
{
|
||||
Profiler.BeginSample("TimelineAbilitySpecT<T>::AbilityTick()");
|
||||
_player.Tick(dt);
|
||||
Profiler.EndSample();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这是一个最朴素的TimelineAbility实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT和TimelineAbilitySpecT为基类
|
||||
/// 这是一个最朴素的TimelineAbility实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT<T>和TimelineAbilitySpecT<T>为基类
|
||||
/// </summary>
|
||||
public sealed class TimelineAbility : TimelineAbilityT<TimelineAbilityAsset>
|
||||
public sealed class TimelineAbility : AbstractAbility
|
||||
{
|
||||
public TimelineAbility(TimelineAbilityAsset abilityAsset) : base(abilityAsset)
|
||||
public TimelineAbility(ITimelineAbilityAsset abilityAsset) : base(abilityAsset)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -81,12 +17,49 @@ namespace GAS.Runtime
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 这是一个最朴素的TimelineAbilitySpec实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT和TimelineAbilitySpecT为基类
|
||||
/// 这是一个最朴素的TimelineAbilitySpec实现, 如果要实现更复杂的TimelineAbility, 请用TimelineAbilityT<T>和TimelineAbilitySpecT<T>为基类
|
||||
/// </summary>
|
||||
public sealed class TimelineAbilitySpec : TimelineAbilitySpecT<TimelineAbilityT<TimelineAbilityAsset>, TimelineAbilityAsset>
|
||||
public sealed class TimelineAbilitySpec : AbilitySpec
|
||||
{
|
||||
public TimelineAbilitySpec(TimelineAbilityT<TimelineAbilityAsset> ability, AbilitySystemComponent owner) : base(ability, owner)
|
||||
/// <summary>
|
||||
/// 每个基于Timeline调度的技能实例,都有一个TimelinePlayer
|
||||
/// </summary>
|
||||
private readonly TimelinePlayer m_Player;
|
||||
|
||||
/// <summary>
|
||||
/// 指向性技能的作用目标
|
||||
/// </summary>
|
||||
public AbilitySystemComponent Target { get; private set; }
|
||||
|
||||
public TimelineAbilitySpec(TimelineAbility ability, AbilitySystemComponent owner) : base(ability, owner)
|
||||
{
|
||||
m_Player = new TimelinePlayer();
|
||||
m_Player.DoAwake(this);
|
||||
}
|
||||
|
||||
public void SetAbilityTarget(AbilitySystemComponent mainTarget)
|
||||
{
|
||||
Target = mainTarget;
|
||||
}
|
||||
|
||||
public override void ActivateAbility(params object[] args)
|
||||
{
|
||||
m_Player.Play();
|
||||
}
|
||||
|
||||
public override void CancelAbility()
|
||||
{
|
||||
m_Player.Stop();
|
||||
}
|
||||
|
||||
public override void EndAbility()
|
||||
{
|
||||
m_Player.Stop();
|
||||
}
|
||||
|
||||
protected override void AbilityTick(int deltaTime)
|
||||
{
|
||||
m_Player.DoUpdate(deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using GAS.General;
|
||||
using JNGame.Math;
|
||||
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 LFloat Speed = 1;
|
||||
|
||||
[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>
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
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>();
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ece82d688852440699c4a50ee47a671e
|
||||
timeCreated: 1729189527
|
||||
@@ -1,3 +0,0 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a6a98a9a0278441cada7de695771be21
|
||||
timeCreated: 1729189725
|
||||
@@ -1,36 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class TaskClipEventTrackData:TrackDataBase
|
||||
{
|
||||
public List<TaskClipEvent> clipEvents = new List<TaskClipEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.OngoingTasks.Add(this);
|
||||
}
|
||||
|
||||
public override void DefaultInit()
|
||||
{
|
||||
base.DefaultInit();
|
||||
trackName = "Task Clips";
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TaskClipEvent : ClipEventBase
|
||||
{
|
||||
public OngoingTaskData ongoingTask;
|
||||
|
||||
public OngoingAbilityTask Load()
|
||||
{
|
||||
return ongoingTask.Load() as OngoingAbilityTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GAS.Runtime;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class TaskMarkEventTrackData : TrackDataBase
|
||||
{
|
||||
public List<TaskMarkEvent> markEvents = new List<TaskMarkEvent>();
|
||||
|
||||
public override void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
base.AddToAbilityAsset(abilityAsset);
|
||||
abilityAsset.InstantTasks.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class TaskMarkEvent:MarkEventBase
|
||||
{
|
||||
public List<InstantTaskData> InstantTasks = new List<InstantTaskData>();
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public class TrackDataBase
|
||||
{
|
||||
public string trackName;
|
||||
|
||||
public virtual void AddToAbilityAsset(TimelineAbilityAssetBase abilityAsset)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void DefaultInit()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
using System;
|
||||
using UnityEngine.Serialization;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
[Serializable]
|
||||
public abstract class TrackEventBase
|
||||
{
|
||||
public int startFrame;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public abstract class MarkEventBase:TrackEventBase
|
||||
{
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public abstract class ClipEventBase:TrackEventBase
|
||||
{
|
||||
public int durationFrame;
|
||||
public int EndFrame => startFrame + durationFrame;
|
||||
}
|
||||
}
|
||||
@@ -8,22 +8,28 @@ namespace GAS.Runtime
|
||||
{
|
||||
public class AttributeAggregator
|
||||
{
|
||||
private record ModifierSpec
|
||||
private record ModifierSpec : IPool
|
||||
{
|
||||
public EntityRef<GameplayEffectSpec> SpecRef { get; private set; }
|
||||
public GameplayEffectModifier Modifier { get; private set; }
|
||||
public GameplayEffectSpec Spec { get; private set; }
|
||||
public GEModifierCfg Modifier { get; private set; }
|
||||
|
||||
public void Init(EntityRef<GameplayEffectSpec> spec, GameplayEffectModifier modifier)
|
||||
public int ModifierIndex{ get; private set; }
|
||||
|
||||
public void Init(GameplayEffectSpec spec, in GEModifierCfg modifier, int modifierIndex)
|
||||
{
|
||||
SpecRef = spec;
|
||||
Spec = spec;
|
||||
Modifier = modifier;
|
||||
ModifierIndex = modifierIndex;
|
||||
}
|
||||
|
||||
public void Release()
|
||||
{
|
||||
SpecRef = default;
|
||||
Spec = default;
|
||||
Modifier = default;
|
||||
ModifierIndex = -1;
|
||||
}
|
||||
|
||||
public bool IsFromPool { get; set; }
|
||||
}
|
||||
|
||||
AttributeBase _processedAttribute;
|
||||
@@ -33,82 +39,91 @@ namespace GAS.Runtime
|
||||
/// modifiers的顺序很重要,因为modifiers的执行是按照顺序来的。
|
||||
/// </summary>
|
||||
private readonly List<ModifierSpec> _modifierCache = new();
|
||||
private readonly List<ModifierSpec> _cached = new();
|
||||
|
||||
public AttributeAggregator(AttributeBase attribute, AbilitySystemComponent owner)
|
||||
public void Init(AttributeBase attribute, AbilitySystemComponent owner)
|
||||
{
|
||||
_processedAttribute = attribute;
|
||||
_owner = owner;
|
||||
|
||||
// OnEnable();
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
_processedAttribute.RegisterPostBaseValueChange(UpdateCurrentValueWhenBaseValueIsDirty);
|
||||
_owner.GameplayEffectContainer.RegisterOnGameplayEffectContainerIsDirty(RefreshModifierCache);
|
||||
}
|
||||
|
||||
public void OnDisable()
|
||||
{
|
||||
_processedAttribute.UnregisterPostBaseValueChange(UpdateCurrentValueWhenBaseValueIsDirty);
|
||||
_owner.GameplayEffectContainer.UnregisterOnGameplayEffectContainerIsDirty(RefreshModifierCache);
|
||||
}
|
||||
|
||||
public void OnDestroy()
|
||||
public void Release()
|
||||
{
|
||||
ReleaseModifiersCache();
|
||||
_processedAttribute = default;
|
||||
_owner = default;
|
||||
}
|
||||
|
||||
// public void OnDestroy()
|
||||
// {
|
||||
// ReleaseModifiersCache();
|
||||
// }
|
||||
|
||||
/// <summary>
|
||||
/// it's triggered only when the owner's gameplay effect is added or removed.
|
||||
/// </summary>
|
||||
void RefreshModifierCache()
|
||||
public void RefreshModifierCache()
|
||||
{
|
||||
// UnityEngine.Profiling.Profiler.BeginSample("AttributeAggregator.RefreshModifierCache");
|
||||
|
||||
var isDirty = _modifierCache.Count > 0;
|
||||
|
||||
// 注销属性变化监听回调
|
||||
UnregisterAttributeChangedListen();
|
||||
ReleaseModifiersCache();
|
||||
|
||||
var gameplayEffects = _owner.GameplayEffectContainer.GameplayEffects();
|
||||
var gameplayEffects = _owner.GameplayEffectContainer.FetchGameplayEffects();
|
||||
|
||||
foreach (var geSpec in gameplayEffects)
|
||||
{
|
||||
if (geSpec.IsActive)
|
||||
{
|
||||
foreach (var modifier in geSpec.Modifiers)
|
||||
var modifiersLength = geSpec.Modifiers.Length;
|
||||
for (int i = 0; i < modifiersLength; i++)
|
||||
{
|
||||
if (modifier.AttributeName == _processedAttribute.Name)
|
||||
var modifier = geSpec.Modifiers[i];
|
||||
if (modifier.attributeName == _processedAttribute.Name)
|
||||
{
|
||||
var modifierSpec = JexGasObjectPool.Instance.Fetch<ModifierSpec>();
|
||||
modifierSpec.Init(geSpec, modifier);
|
||||
var modifierSpec = ObjectPool.Instance.Fetch<ModifierSpec>();
|
||||
modifierSpec.Init(geSpec, modifier, i);
|
||||
_modifierCache.Add(modifierSpec);
|
||||
TryRegisterAttributeChangedListen(geSpec, modifier);
|
||||
}
|
||||
}
|
||||
|
||||
// foreach (var modifier in geSpec.Modifiers)
|
||||
// {
|
||||
// if (modifier.attributeName == _processedAttribute.Name)
|
||||
// {
|
||||
// var modifierSpec = ObjectPool.Instance.Fetch<ModifierSpec>();
|
||||
// modifierSpec.Init(geSpec, modifier);
|
||||
// _modifierCache.Add(modifierSpec);
|
||||
// TryRegisterAttributeChangedListen(geSpec, modifier);
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
isDirty = isDirty || _modifierCache.Count > 0;
|
||||
|
||||
_owner.GameplayEffectContainer.ReturnGameplayEffects(gameplayEffects);
|
||||
|
||||
if (isDirty)
|
||||
{
|
||||
UpdateCurrentValueWhenModifierIsDirty();
|
||||
}
|
||||
|
||||
// UnityEngine.Profiling.Profiler.EndSample();
|
||||
}
|
||||
|
||||
private void ReleaseModifiersCache()
|
||||
{
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
_cached.AddRange(_modifierCache);
|
||||
_modifierCache.Clear();
|
||||
|
||||
foreach (var modifierSpec in _cached)
|
||||
{
|
||||
modifierSpec.Release();
|
||||
JexGasObjectPool.Instance.Recycle(modifierSpec);
|
||||
ObjectPool.Instance.Recycle(modifierSpec);
|
||||
}
|
||||
|
||||
_modifierCache.Clear();
|
||||
_cached.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -119,185 +134,112 @@ namespace GAS.Runtime
|
||||
/// 3._modifierCache的AttributeBased类的MMC,Track类属性变化时
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
LFloat CalculateNewValue()
|
||||
int CalculateNewValue()
|
||||
{
|
||||
switch (_processedAttribute.CalculateMode)
|
||||
LFloat newValue = _processedAttribute.BaseValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
case CalculateMode.Stacking:
|
||||
var spec = modifierSpec.Spec;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
if (!spec.Source.enabled || !spec.Owner.enabled)
|
||||
{
|
||||
LFloat newValue = _processedAttribute.BaseValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
switch (modifier.Operation)
|
||||
{
|
||||
case GEOperation.Add:
|
||||
newValue += magnitude;
|
||||
break;
|
||||
case GEOperation.Minus:
|
||||
newValue -= magnitude;
|
||||
break;
|
||||
case GEOperation.Multiply:
|
||||
newValue *= magnitude;
|
||||
break;
|
||||
case GEOperation.Divide:
|
||||
newValue /= magnitude;
|
||||
break;
|
||||
case GEOperation.Override:
|
||||
newValue = magnitude;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
}
|
||||
|
||||
return newValue;
|
||||
Debug.LogWarning($"modifier 相关 asc 出现已回收 spec: {spec.GameplayEffect.GameplayEffectName}");
|
||||
// continue;
|
||||
}
|
||||
case CalculateMode.MinValueOnly:
|
||||
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.modifierMagnitude);
|
||||
|
||||
switch (modifier.operation)
|
||||
{
|
||||
var hasOverride = false;
|
||||
var min = LFloat.MaxValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
if (modifier.Operation != GEOperation.Override)
|
||||
{
|
||||
throw new InvalidOperationException("MinValueOnly mode only support override operation.");
|
||||
}
|
||||
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
min = LMath.Min(min, magnitude);
|
||||
hasOverride = true;
|
||||
}
|
||||
|
||||
return hasOverride ? min : _processedAttribute.BaseValue;
|
||||
case EnumGEOperation.Add:
|
||||
newValue += magnitude;
|
||||
break;
|
||||
case EnumGEOperation.Multiply:
|
||||
newValue *= magnitude;
|
||||
break;
|
||||
case EnumGEOperation.Override:
|
||||
newValue = magnitude;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
case CalculateMode.MaxValueOnly:
|
||||
{
|
||||
var hasOverride = false;
|
||||
var max = LFloat.MinValue;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
var spec = modifierSpec.SpecRef;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
|
||||
if (_processedAttribute.IsSupportOperation(modifier.Operation) == false)
|
||||
{
|
||||
throw new InvalidOperationException("Unsupported operation.");
|
||||
}
|
||||
|
||||
if (modifier.Operation != GEOperation.Override)
|
||||
{
|
||||
throw new InvalidOperationException("MaxValueOnly mode only support override operation.");
|
||||
}
|
||||
|
||||
var magnitude = modifier.CalculateMagnitude(spec, modifier.ModiferMagnitude);
|
||||
max = LMath.Max(max, magnitude);
|
||||
hasOverride = true;
|
||||
}
|
||||
|
||||
return hasOverride ? max : _processedAttribute.BaseValue;
|
||||
}
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
return newValue.ToInt();
|
||||
}
|
||||
|
||||
void UpdateCurrentValueWhenBaseValueIsDirty(AttributeBase attribute, LFloat oldBaseValue, LFloat newBaseValue)
|
||||
{
|
||||
if (LMath.Approximately(oldBaseValue, newBaseValue)) return;
|
||||
|
||||
LFloat newValue = CalculateNewValue();
|
||||
public void UpdateCurrentValueWhenBaseValueIsDirty(AttributeBase attribute, int sourceId, int oldBaseValue, int newBaseValue)
|
||||
{
|
||||
if (oldBaseValue == newBaseValue) return;
|
||||
|
||||
int newValue = CalculateNewValue();
|
||||
_processedAttribute.SetCurrentValue(newValue);
|
||||
}
|
||||
|
||||
void UpdateCurrentValueWhenModifierIsDirty()
|
||||
{
|
||||
LFloat newValue = CalculateNewValue();
|
||||
int newValue = CalculateNewValue();
|
||||
_processedAttribute.SetCurrentValue(newValue);
|
||||
}
|
||||
|
||||
private void UnregisterAttributeChangedListen()
|
||||
{
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
TryUnregisterAttributeChangedListen(modifierSpec.SpecRef, modifierSpec.Modifier);
|
||||
{
|
||||
TryUnregisterAttributeChangedListen(modifierSpec.Spec, modifierSpec.Modifier);
|
||||
}
|
||||
}
|
||||
|
||||
private void TryUnregisterAttributeChangedListen(GameplayEffectSpec ge, GameplayEffectModifier modifier)
|
||||
private void TryUnregisterAttributeChangedListen(GameplayEffectSpec ge, GEModifierCfg modifier)
|
||||
{
|
||||
if (modifier.MMC is AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc)
|
||||
if (modifier.mmc is PureAttributeBasedModCalculation mmc &&
|
||||
mmc.captureType == EnumGEAttributeCaptureType.Track)
|
||||
{
|
||||
if (mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target)
|
||||
if (mmc.attributeFromType == EnumAttributeFrom.Target)
|
||||
{
|
||||
if (ge.Owner != null)
|
||||
ge.Owner.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
ge.Owner?.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName].UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ge.Source != null)
|
||||
ge.Source.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
ge.Source?.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName].UnregisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TryRegisterAttributeChangedListen(GameplayEffectSpec ge, GameplayEffectModifier modifier)
|
||||
private void TryRegisterAttributeChangedListen(GameplayEffectSpec ge, GEModifierCfg modifier)
|
||||
{
|
||||
if (modifier.MMC is AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc)
|
||||
if (modifier.mmc is PureAttributeBasedModCalculation mmc && mmc.captureType == EnumGEAttributeCaptureType.Track)
|
||||
{
|
||||
if (mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target)
|
||||
if (mmc.attributeFromType == EnumAttributeFrom.Target)
|
||||
{
|
||||
if (ge.Owner != null)
|
||||
ge.Owner.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
ge.Owner?.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName].RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ge.Source != null)
|
||||
ge.Source.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName]
|
||||
.RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
ge.Source?.AttributeSetContainer.Sets[mmc.attributeSetName][mmc.attributeShortName].RegisterPostCurrentValueChange(OnAttributeChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttributeChanged(AttributeBase attribute, LFloat oldValue, LFloat newValue)
|
||||
{
|
||||
if (IsTrackingAttribute(attribute))
|
||||
UpdateCurrentValueWhenModifierIsDirty();
|
||||
}
|
||||
|
||||
private bool IsTrackingAttribute(AttributeBase attribute)
|
||||
private void OnAttributeChanged(AttributeBase attribute, int oldValue, int newValue)
|
||||
{
|
||||
if (_modifierCache.Count == 0) return;
|
||||
foreach (var modifierSpec in _modifierCache)
|
||||
{
|
||||
if (modifierSpec.Modifier.MMC is not AttributeBasedModCalculation { captureType: AttributeBasedModCalculation.GEAttributeCaptureType.Track } mmc) continue;
|
||||
if (attribute.Name != mmc.attributeName) continue;
|
||||
var geSpec = modifierSpec.SpecRef.Value;
|
||||
if (geSpec == null) continue;
|
||||
if ((mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Target && attribute.Owner == geSpec.Owner) ||
|
||||
(mmc.attributeFromType == AttributeBasedModCalculation.AttributeFrom.Source && attribute.Owner == geSpec.Source))
|
||||
var ge = modifierSpec.Spec;
|
||||
var modifier = modifierSpec.Modifier;
|
||||
if (modifier.mmc is PureAttributeBasedModCalculation mmc && mmc.captureType == EnumGEAttributeCaptureType.Track
|
||||
&& attribute.Name == mmc.attributeName)
|
||||
{
|
||||
return true;
|
||||
if ((mmc.attributeFromType == EnumAttributeFrom.Target && attribute.Owner == ge.Owner)
|
||||
|| (mmc.attributeFromType == EnumAttributeFrom.Source && attribute.Owner == ge.Source))
|
||||
{
|
||||
UpdateCurrentValueWhenModifierIsDirty();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JNGame.Math;
|
||||
using UnityEngine;
|
||||
|
||||
namespace GAS.Runtime
|
||||
{
|
||||
@@ -11,86 +8,67 @@ namespace GAS.Runtime
|
||||
public readonly string Name;
|
||||
public readonly string SetName;
|
||||
public readonly string ShortName;
|
||||
protected event Action<AttributeBase, LFloat, LFloat> _onPostCurrentValueChange;
|
||||
protected event Action<AttributeBase, LFloat, LFloat> _onPostBaseValueChange;
|
||||
protected event Action<AttributeBase, LFloat> _onPreCurrentValueChange;
|
||||
protected event Func<AttributeBase, LFloat, LFloat> _onPreBaseValueChange;
|
||||
protected IEnumerable<Func<AttributeBase, LFloat, LFloat>> _preBaseValueChangeListeners;
|
||||
|
||||
// 以下回调
|
||||
// pre 可在属性变化前修改为想要的最终结果
|
||||
// post 是去计算新的属性
|
||||
// Changed 是提供属性变化后的回调
|
||||
|
||||
protected event Action<AttributeBase, int, int> _onCurrentValueChanged;
|
||||
protected event Action<AttributeBase, int, int, int> _onBaseValueChanged;
|
||||
protected event Action<AttributeBase, int, int> _onPostCurrentValueChange;
|
||||
protected event Action<AttributeBase, int, int, int> _onPostBaseValueChange;
|
||||
|
||||
protected event Action<AttributeBase, int> _onPreCurrentValueChange;
|
||||
|
||||
// protected event Func<AttributeBase, int, int> _onPreBaseValueChange;
|
||||
// protected IEnumerable<Func<AttributeBase, int, int>> _preBaseValueChangeListeners;
|
||||
protected List<Func<AttributeBase, int, int>> _onPreBaseValueChangeList = new();
|
||||
|
||||
private AttributeValue _value;
|
||||
private AbilitySystemComponent _owner;
|
||||
public AbilitySystemComponent Owner => _owner;
|
||||
|
||||
public AttributeBase(string attrSetName, string attrName,
|
||||
LFloat value,LFloat minValue, LFloat maxValue,
|
||||
CalculateMode calculateMode = CalculateMode.Stacking,
|
||||
SupportedOperation supportedOperation = SupportedOperation.All)
|
||||
public AttributeBase(string name, string attrSetName, string attrName, int value = 0)
|
||||
{
|
||||
Name = name;
|
||||
SetName = attrSetName;
|
||||
Name = $"{attrSetName}.{attrName}";
|
||||
ShortName = attrName;
|
||||
_value = new AttributeValue(value, minValue,maxValue,calculateMode, supportedOperation);
|
||||
_value = new AttributeValue(value);
|
||||
}
|
||||
|
||||
|
||||
public AttributeValue Value => _value;
|
||||
public LFloat BaseValue => _value.BaseValue;
|
||||
public LFloat CurrentValue => _value.CurrentValue;
|
||||
|
||||
public LFloat MinValue => _value.MinValue;
|
||||
public LFloat MaxValue => _value.MaxValue;
|
||||
|
||||
public CalculateMode CalculateMode => _value.CalculateMode;
|
||||
public SupportedOperation SupportedOperation => _value.SupportedOperation;
|
||||
public int BaseValue => _value.BaseValue;
|
||||
public int CurrentValue => _value.CurrentValue;
|
||||
|
||||
public void Init(int baseValue)
|
||||
{
|
||||
SetBaseValue(baseValue);
|
||||
SetCurrentValue(baseValue);
|
||||
}
|
||||
|
||||
public void SetOwner(AbilitySystemComponent owner)
|
||||
{
|
||||
_owner = owner;
|
||||
}
|
||||
|
||||
public void SetMinValue(LFloat min)
|
||||
public void SetCurrentValue(int value)
|
||||
{
|
||||
_value.SetMinValue(min);
|
||||
}
|
||||
|
||||
public void SetMaxValue(LFloat max)
|
||||
{
|
||||
_value.SetMaxValue(max);
|
||||
}
|
||||
|
||||
public void SetMinMaxValue(LFloat min, LFloat max)
|
||||
{
|
||||
_value.SetMinValue(min);
|
||||
_value.SetMaxValue(max);
|
||||
}
|
||||
|
||||
public bool IsSupportOperation(GEOperation operation)
|
||||
{
|
||||
return _value.IsSupportOperation(operation);
|
||||
}
|
||||
|
||||
public void Init(LFloat baseValue)
|
||||
{
|
||||
SetBaseValue(baseValue);
|
||||
SetCurrentValue(baseValue);
|
||||
}
|
||||
|
||||
public void SetCurrentValue(LFloat value)
|
||||
{
|
||||
value = LMath.Clamp(value, _value.MinValue, _value.MaxValue);
|
||||
|
||||
_onPreCurrentValueChange?.Invoke(this, value);
|
||||
|
||||
var oldValue = CurrentValue;
|
||||
_value.SetCurrentValue(value);
|
||||
|
||||
if (!LMath.Approximately(oldValue, value))
|
||||
if (oldValue != value)
|
||||
{
|
||||
_onPostCurrentValueChange?.Invoke(this, oldValue, value);
|
||||
_onCurrentValueChanged?.Invoke(this, oldValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetBaseValue(LFloat value)
|
||||
public void SetBaseValue(int value)
|
||||
{
|
||||
if (_onPreBaseValueChange != null)
|
||||
if (_onPreBaseValueChangeList.Count > 0)
|
||||
{
|
||||
value = InvokePreBaseValueChangeListeners(value);
|
||||
}
|
||||
@@ -98,79 +76,136 @@ namespace GAS.Runtime
|
||||
var oldValue = _value.BaseValue;
|
||||
_value.SetBaseValue(value);
|
||||
|
||||
if (!LMath.Approximately(oldValue, value))
|
||||
_onPostBaseValueChange?.Invoke(this, oldValue, value);
|
||||
if (oldValue != value)
|
||||
{
|
||||
InvokeOnPostBaseValueChange(this, 0, oldValue, value);
|
||||
_onBaseValueChanged?.Invoke(this, 0, oldValue, value);
|
||||
}
|
||||
|
||||
string strValue = "";
|
||||
int lenght = strValue.Length;
|
||||
}
|
||||
|
||||
public void SetCurrentValueWithoutEvent(LFloat value)
|
||||
public void SetBaseValue(int sourceId, int value)
|
||||
{
|
||||
if (_onPreBaseValueChangeList.Count > 0)
|
||||
{
|
||||
value = InvokePreBaseValueChangeListeners(value);
|
||||
}
|
||||
|
||||
var oldValue = _value.BaseValue;
|
||||
_value.SetBaseValue(value);
|
||||
|
||||
if (oldValue != value)
|
||||
{
|
||||
InvokeOnPostBaseValueChange(this, sourceId, oldValue, value);
|
||||
_onBaseValueChanged?.Invoke(this, sourceId, oldValue, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCurrentValueWithoutEvent(int value)
|
||||
{
|
||||
_value.SetCurrentValue(value);
|
||||
}
|
||||
|
||||
public void SetBaseValueWithoutEvent(LFloat value)
|
||||
public void SetBaseValueWithoutEvent(int value)
|
||||
{
|
||||
_value.SetBaseValue(value);
|
||||
}
|
||||
|
||||
public void RegisterPreBaseValueChange(Func<AttributeBase, LFloat, LFloat> func)
|
||||
public void RegisterPreBaseValueChange(Func<AttributeBase, int, int> func)
|
||||
{
|
||||
_onPreBaseValueChange += func;
|
||||
_preBaseValueChangeListeners =
|
||||
_onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, LFloat, LFloat>>();
|
||||
_onPreBaseValueChangeList.Add(func);
|
||||
// _onPreBaseValueChange += func;
|
||||
// _preBaseValueChangeListeners =
|
||||
// _onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, int, int>>();
|
||||
}
|
||||
|
||||
public void RegisterPostBaseValueChange(Action<AttributeBase, LFloat, LFloat> action)
|
||||
public void RegisterPostBaseValueChange(Action<AttributeBase, int, int, int> action)
|
||||
{
|
||||
_onPostBaseValueChange += action;
|
||||
}
|
||||
|
||||
public void RegisterPreCurrentValueChange(Action<AttributeBase, LFloat> action)
|
||||
public void RegisterBaseValueChanged(Action<AttributeBase, int, int, int> action)
|
||||
{
|
||||
_onBaseValueChanged += action;
|
||||
}
|
||||
|
||||
public void RegisterPreCurrentValueChange(Action<AttributeBase, int> action)
|
||||
{
|
||||
_onPreCurrentValueChange += action;
|
||||
}
|
||||
|
||||
public void RegisterPostCurrentValueChange(Action<AttributeBase, LFloat, LFloat> action)
|
||||
public void RegisterPostCurrentValueChange(Action<AttributeBase, int, int> action)
|
||||
{
|
||||
_onPostCurrentValueChange += action;
|
||||
}
|
||||
|
||||
public void UnregisterPreBaseValueChange(Func<AttributeBase, LFloat, LFloat> func)
|
||||
public void RegisterCurrentValueChanged(Action<AttributeBase, int, int> action)
|
||||
{
|
||||
_onPreBaseValueChange -= func;
|
||||
_preBaseValueChangeListeners =
|
||||
_onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, LFloat, LFloat>>();
|
||||
_onCurrentValueChanged += action;
|
||||
}
|
||||
|
||||
public void UnregisterPostBaseValueChange(Action<AttributeBase, LFloat, LFloat> action)
|
||||
public void UnregisterPreBaseValueChange(Func<AttributeBase, int, int> func)
|
||||
{
|
||||
_onPreBaseValueChangeList.Remove(func);
|
||||
// _onPreBaseValueChange -= func;
|
||||
// _preBaseValueChangeListeners =
|
||||
// _onPreBaseValueChange?.GetInvocationList().Cast<Func<AttributeBase, int, int>>();
|
||||
}
|
||||
|
||||
public void UnregisterPostBaseValueChange(Action<AttributeBase, int, int, int> action)
|
||||
{
|
||||
_onPostBaseValueChange -= action;
|
||||
}
|
||||
|
||||
public void UnregisterPreCurrentValueChange(Action<AttributeBase, LFloat> action)
|
||||
public void UnregisterBaseValueChanged(Action<AttributeBase, int, int, int> action)
|
||||
{
|
||||
_onBaseValueChanged -= action;
|
||||
}
|
||||
|
||||
public void UnregisterPreCurrentValueChange(Action<AttributeBase, int> action)
|
||||
{
|
||||
_onPreCurrentValueChange -= action;
|
||||
}
|
||||
|
||||
public void UnregisterPostCurrentValueChange(Action<AttributeBase, LFloat, LFloat> action)
|
||||
public void UnregisterPostCurrentValueChange(Action<AttributeBase, int, int> action)
|
||||
{
|
||||
_onPostCurrentValueChange -= action;
|
||||
}
|
||||
|
||||
public virtual void Dispose()
|
||||
public void UnregisterCurrentValueChanged(Action<AttributeBase, int, int> action)
|
||||
{
|
||||
_onPreBaseValueChange = null;
|
||||
_onPostBaseValueChange = null;
|
||||
_onPreCurrentValueChange = null;
|
||||
_onPostCurrentValueChange = null;
|
||||
_onCurrentValueChanged -= action;
|
||||
}
|
||||
|
||||
private LFloat InvokePreBaseValueChangeListeners(LFloat value)
|
||||
public virtual void Dispose()
|
||||
{
|
||||
if (_preBaseValueChangeListeners == null) return value;
|
||||
_onCurrentValueChanged = default;
|
||||
_onBaseValueChanged = default;
|
||||
_onPostCurrentValueChange = default;
|
||||
_onPostBaseValueChange = default;
|
||||
_onPreCurrentValueChange = default;
|
||||
// _onPreBaseValueChange = default;
|
||||
// _preBaseValueChangeListeners = default;
|
||||
_onPreBaseValueChangeList.Clear();
|
||||
_value = default;
|
||||
_owner = default;
|
||||
}
|
||||
|
||||
foreach (var t in _preBaseValueChangeListeners)
|
||||
private int InvokePreBaseValueChangeListeners(int value)
|
||||
{
|
||||
if (_onPreBaseValueChangeList.Count <= 0) return value;
|
||||
|
||||
foreach (var t in _onPreBaseValueChangeList)
|
||||
value = t.Invoke(this, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
private void InvokeOnPostBaseValueChange(AttributeBase attribute, int sourceId, int oldBaseValue, int newBaseValue)
|
||||
{
|
||||
_onPostBaseValueChange?.Invoke(attribute, sourceId, oldBaseValue, newBaseValue);
|
||||
_owner.AttributeSetContainer.UpdateCurrentValueWhenBaseValueIsDirty(attribute, sourceId, oldBaseValue, newBaseValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
public class MemoryPool<T> where T : class, new()
|
||||
{
|
||||
private readonly Stack<T> _pool;
|
||||
private readonly int _maxSize;
|
||||
|
||||
public MemoryPool(int initialCapacity = 10, int maxSize = 100)
|
||||
{
|
||||
_pool = new Stack<T>(initialCapacity);
|
||||
_maxSize = maxSize;
|
||||
|
||||
// 预先分配一些对象
|
||||
for (int i = 0; i < initialCapacity; i++)
|
||||
{
|
||||
_pool.Push(new T());
|
||||
}
|
||||
}
|
||||
|
||||
// 从池中获取一个对象
|
||||
public T Get()
|
||||
{
|
||||
if (_pool.Count > 0)
|
||||
{
|
||||
return _pool.Pop();
|
||||
}
|
||||
|
||||
// 如果池中没有对象,创建一个新的对象
|
||||
return new T();
|
||||
}
|
||||
|
||||
// 将对象归还到池中
|
||||
public void Release(T obj)
|
||||
{
|
||||
if (_pool.Count < _maxSize)
|
||||
{
|
||||
_pool.Push(obj);
|
||||
}
|
||||
// 如果池已满,丢弃对象(也可以选择对对象进行特殊处理,如销毁或重置)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7309444019131ee41ab4723e5528b9a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user