简单提交

This commit is contained in:
PC-20230316NUNE\Administrator
2024-10-22 16:10:12 +08:00
parent 930911e7df
commit 0e94e376fb
562 changed files with 26182 additions and 29365 deletions

View File

@@ -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
- 修复全局配置保存失败问题TagAttributeAttributeSetSetting的配置文件保存不该使用AssetDataBase。
- 修正无法打包编译异常 #11 From: BCC @kenkinky
## [1.0.6] - 2024-04-16
优化type查找优化GAS的项目级配置文件管理。
### Changed
- 修改了TagAttributeAttributeSetSetting的配置文件路径调整至ProjectSettings并且为单例配置文件。
- 优化了TypeUtilEditor环境下类型查找范围改为全程序集。
### 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

View File

@@ -1,7 +0,0 @@
fileFormatVersion: 2
guid: 34ae97a0d741c5844a4fac12ff8e1c45
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 2f87f52c980e4d1ba211174b7e0ce100
timeCreated: 1702438660
guid: bb1c30c31ad34ddbb692b946a977c4be
timeCreated: 1715843648

View File

@@ -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 = 同步GEGE激活时激活";
"SyncWithEffect = 同步GEGE激活时激活";
public const string TIP_GRANT_ABILITY_DEACTIVATION_POLICY = "None = 无相关取消激活逻辑, 需要用户调用ASC取消激活; " +
"SyncWithEffect = 同步GEGE失活时取消激活";
"SyncWithEffect = 同步GEGE失活时取消激活";
public const string TIP_GRANT_ABILITY_REMOVE_POLICY = "None = 不移除能力;" +
"SyncWithEffect = 同步GEGE移除时移除" +
"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 = "特效旋转";

View File

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

View File

@@ -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;
// }
// }
// }
// }

View File

@@ -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;
// }
// }
// }

View File

@@ -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
{
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ac5d19bb315743de9f460aae665ccd42
timeCreated: 1723619186

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: d6f50d4142b94bc3b37233ff4806abf6
timeCreated: 1723102748
fileFormatVersion: 2
guid: 4786837dcf7f48c8abd8fd5d6d92fda7
timeCreated: 1723551728

View File

@@ -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;

View File

@@ -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;

View File

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

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5fa92b69503043a58c6b9b79b15b8f2c
timeCreated: 1719295275

View File

@@ -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)
{

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 51efeef0278341a9ab883d49a20bd485
timeCreated: 1711514345

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 207a4ba83f8f4b799e477e19a8d8b9d9
timeCreated: 1729569447

View File

@@ -1,3 +1,8 @@
fileFormatVersion: 2
guid: cddbd5a2fd644ea68b4cf1d44faea8bf
timeCreated: 1701928931
guid: d02f2c4a6ac8451cbce922e1c3d29c9c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -1,10 +0,0 @@
using System;
namespace GAS.Runtime
{
public struct AbilityInstanceInfo
{
public AbilityAsset abilityAsset;
public Type abilityType;
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 99ad85c3caba4604b9950ae1105e85b4
timeCreated: 1706090943

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 58b4935f7a5a43d69506e28666032462
timeCreated: 1709451834

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -0,0 +1,14 @@
namespace GAS.Runtime
{
/// <summary>
/// 目标捕获器类型枚举
/// </summary>
public enum EnumTargetCatcherType
{
CatchSelf = 1, // 捕获Ability的施法者自身
CatchTarget, // 捕获Ability的AI目标
EnumBuiltinCount = 10, // 内建捕获器类型枚举的上限控制,上层业务派生由此开始
}
}

View File

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

View File

@@ -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
}
}

View File

@@ -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]();
}
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: af0af80f518a09a43bc4895a97f6d06d
guid: a56af01f043a99d44b6b019b8b161c67
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -0,0 +1,23 @@
namespace GAS.Runtime
{
/// <summary>
/// Timeline运行时节点枚举
/// </summary>
public enum EnumRuntimeClipNode : ushort
{
InstantGE = 1, // 直接添加一个GE效果GE的时限效果由GE自己决定
DurationalGE, // 持续性GE效果Timeline控制其生命周期
InstantTask, // 瞬时的TaskTask可以理解为Condition+GE判断条件决定是否触发GE
DurationalTask, // 持续一段时间的TaskTimeline控制其生命周期
PassiveGE, // 被动GETimeline只负责添加不负责销毁外部手动控制
PassiveTask, // 被动TaskTimeline只负责添加不负责销毁外部手动控制
InstantCue, // 直接触发一个GameplayCue
DurationalCue, // 持续型GameplayCueTimeline控制其生命周期
EnumBuiltinCount = 100, // 内建行为树节点的类型上限,上层业务自行扩展的节点由此递增
}
}

View File

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

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fa26d750811c9a44f88f4e478131110e
guid: 00a222d3e7696384197e8aede030e7ba
folderAsset: yes
DefaultImporter:
externalObjects: {}

View File

@@ -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);
}
}
}

View File

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

View File

@@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: cfcc47cc435a1514c9cf242b88b4c813
TextScriptImporter:
guid: 81083f075918f2a48bdeba6f799e36a6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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);
}
}
}

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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();
}
}
}

View File

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

View File

@@ -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) { }
}
}

View File

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

View File

@@ -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
}
}

View File

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

View File

@@ -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]();
}
}
}

View File

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

View File

@@ -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();
}
}
}
}

View File

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

View File

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

View File

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

View File

@@ -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);
}
}

View File

@@ -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]();
}
}
}

View File

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

View File

@@ -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索引上层派生由此递增
}
}

View File

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

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 5a9d88b958e546c7aaef67ab354c71a4
timeCreated: 1729189790
guid: 39de6ad84e9d43f1bce81ac2b76b0d1a
timeCreated: 1717572967

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 5dce896cd86840d68e56efd1948114f5
timeCreated: 1729189740

View File

@@ -1,13 +0,0 @@
using System;
namespace GAS.Runtime
{
[Serializable]
public class ApplyCostAndCoolDown : InstantAbilityTask
{
public override void OnExecute()
{
_spec.DoCost();
}
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: 3c8689e959d44db393a7821d0923f750
timeCreated: 1709277143

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 3826985009604c178d6623947f501916
timeCreated: 1729189761
guid: 64c722b82d12457091806f6de5fb1d90
timeCreated: 1717572995

View File

@@ -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);
}
}
}

View File

@@ -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>
{
}
}

View File

@@ -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,
}
}

View File

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

View File

@@ -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>();
}
}

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: ece82d688852440699c4a50ee47a671e
timeCreated: 1729189527

View File

@@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: a6a98a9a0278441cada7de695771be21
timeCreated: 1729189725

View File

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

View File

@@ -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>();
}
}

View File

@@ -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()
{
}
}
}

View File

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

View File

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

View File

@@ -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);
}
}
}

View File

@@ -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);
}
// 如果池已满,丢弃对象(也可以选择对对象进行特殊处理,如销毁或重置)
}
}

View File

@@ -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