简单提交

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

File diff suppressed because it is too large Load Diff

View File

@ -43,13 +43,13 @@
<ItemGroup>
<None Include="Assets\Plugins\Sirenix\Odin Inspector\Assets\Editor\Bootstrap License.txt" />
<Reference Include="UnityEngine">
<HintPath>C:\APP\UnityEdit\2022.3.16f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.dll</HintPath>
<HintPath>D:\Unity\2022.3.1f1\Editor\Data\Managed\UnityEngine\UnityEngine.dll</HintPath>
</Reference>
<Reference Include="UnityEngine.CoreModule">
<HintPath>C:\APP\UnityEdit\2022.3.16f1c1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll</HintPath>
<HintPath>D:\Unity\2022.3.1f1\Editor\Data\Managed\UnityEngine\UnityEngine.CoreModule.dll</HintPath>
</Reference>
<Reference Include="UnityEditor">
<HintPath>C:\APP\UnityEdit\2022.3.16f1c1\Editor\Data\Managed\UnityEngine\UnityEditor.dll</HintPath>
<HintPath>D:\Unity\2022.3.1f1\Editor\Data\Managed\UnityEngine\UnityEditor.dll</HintPath>
</Reference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -1,6 +1,7 @@
fileFormatVersion: 2
guid: 34ae97a0d741c5844a4fac12ff8e1c45
TextScriptImporter:
guid: 52f44ade52e60e247a5ef830c3152bc0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fda7b329dfe891948b714c3f8966e36f
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ed22e0034a143d443ad93ed0c1f0fe47
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,14 @@
// auto generate by tools, DO NOT Modify it!!!
using GAS.Runtime;
namespace JNGame.GAS
{
#region MMC枚举ID自动生成
#endregion
}

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: a829f1acecbd5d348800bcbf18fb138e
guid: 13b4d6ef882241949b349a1492f466c4
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -0,0 +1,6 @@
// auto generate by tools, DO NOT Modify it!!!
using GAS.Runtime;
using JNGame.Serialization;

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 79aaab94f725e0347b32b6383dc6361e
guid: 2b2c4acd8004e3144ae1fddc5c8eee6d
MonoImporter:
externalObjects: {}
serializedVersion: 2

View File

@ -1,12 +1,16 @@
using System;
using System.IO;
using System.Linq;
using GAS.Runtime;
using UnityEditor;
using UnityEngine;
using JNGame.Serialization;
#if UNITY_EDITOR
namespace GAS.Editor
{
using Runtime;
using UnityEditor;
using System;
using System.IO;
using GAS;
using Editor;
using UnityEngine;
public class AbilityCollectionGenerator
{
public static void Gen()
@ -62,12 +66,8 @@ namespace GAS.Editor
writer.WriteLine("");
var abilityAssets = EditorUtil
.FindAssetsByType<AbilityAsset>(GASSettingAsset.GameplayAbilityLibPath)
.OrderBy(x => x.UniqueName)
.ThenBy(x => x.name)
.ToArray();
var abilityAssets =
EditorUtil.FindAssetsByType<AbilityAsset>(GASSettingAsset.GameplayAbilityLibPath);
foreach (var ability in abilityAssets)
{
var path = AssetDatabase.GetAssetPath(ability);
@ -130,6 +130,9 @@ namespace GAS.Editor
writer.Write("}");
Console.WriteLine($"Generated GTagLib at path: {filePath}");
Debug.Log("GenerateAbilityCollection Finish");
}
}
}
}
#endif

View File

@ -1,6 +1,5 @@
using System.Collections.Generic;
using System.Linq;
using GAS.General.Validation;
using GAS.Runtime;
using Sirenix.OdinInspector;
using UnityEditor;
@ -54,22 +53,12 @@ namespace GAS.Editor
AssetDatabase.Refresh();
}
private bool _orderByUniqueName = true;
[HorizontalGroup("Buttons", width: 180)]
[Button(SdfIconType.SortAlphaDown, "@_orderByUniqueName?\"Sort By AssetName\":\"Sort By UniqueName\"",
ButtonHeight = 30)]
public void ToggleOrderByUniqueName()
{
_orderByUniqueName = !_orderByUniqueName;
Refresh();
}
private bool _showDetail = false;
[HorizontalGroup("Buttons", width: 120)]
[Button(SdfIconType.TicketDetailed, "@_showDetail?\"Hide Detail\":\"Show Detail\"", ButtonHeight = 30)]
public void ToggleShowDetail()
public void Toggle()
{
_showDetail = !_showDetail;
Refresh();
@ -83,25 +72,16 @@ namespace GAS.Editor
{
Abilities.Clear();
var abilityAssets = EditorUtil.FindAssetsByType<AbilityAsset>(GASSettingAsset.GameplayAbilityLibPath);
var orderedAbilityAssets = _orderByUniqueName
? abilityAssets
.OrderBy(x => x.UniqueName)
.ThenBy(x => x.name)
: abilityAssets.OrderBy(x => x.name);
Abilities = orderedAbilityAssets.Select(ability =>
abilityAssets.ForEach(ability =>
{
var text = Validations.ValidateVariableName(ability.UniqueName).IsValid
? ability.UniqueName
: $"{ability.UniqueName}(非法UniqueName)";
var text = $"{ability.UniqueName}";
if (_showDetail)
{
text += $" - asset: {ability.name}, type: {ability.GetType().FullName}";
}
return text;
}).ToList();
Abilities.Add(text);
});
}
bool ExistAbilityWithEmptyUniqueName()

View File

@ -1,5 +1,3 @@
using GAS.Runtime;
#if UNITY_EDITOR
namespace GAS.Editor
{
@ -9,9 +7,10 @@ namespace GAS.Editor
{
public int FrameUnitWidth = 10;
public const int StandardFrameUnitWidth = 1;
public const int MaxFrameUnitLevel= 20;
public const int MaxFrameUnitLevel = 20;
public const float MinTimerShaftFrameDrawStep = 5;
public int DefaultFrameRate => JexGasManager.FrameRate;
public int TickTime => GASTimer.TimeLineAbilityTickTime;
public int FrameRate => GASTimer.FrameRateValue;
}
}
#endif

View File

@ -1,22 +1,20 @@
using System;
using GAS.Runtime;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
namespace GAS.Editor
{
/// <summary>
/// 这个类被反射引用到, 重构请小心!!
/// </summary>
using System;
using Editor;
using GAS.Runtime;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEditor.UIElements;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
public class AbilityTimelineEditorWindow : EditorWindow
{
[SerializeField]
private VisualTreeAsset m_VisualTreeAsset;
[SerializeField] private VisualTreeAsset m_VisualTreeAsset;
private VisualElement _root;
@ -27,7 +25,6 @@ namespace GAS.Editor
public TimelineInspector TimelineInspector { get; private set; }
private static EditorWindow _childInspector;
public void CreateGUI()
{
Instance = this;
@ -44,16 +41,13 @@ namespace GAS.Editor
TrackView = new TimelineTrackView(_root);
TimelineInspector = new TimelineInspector(_root);
}
/// <summary>
/// 这个方法被反射引用到, 重构请小心!!
/// </summary>
public static void ShowWindow(TimelineAbilityAssetBase asset)
public static void ShowWindow(TimelineAbilityAsset asset)
{
var wnd = GetWindow<AbilityTimelineEditorWindow>();
wnd.titleContent = new GUIContent("AbilityTimelineEditorWindow");
wnd.InitAbility(asset);
// 打开子Inspector
EditorApplication.delayCall += () => wnd.ShowChildInspector();
}
@ -63,7 +57,7 @@ namespace GAS.Editor
AbilityAsset.Save();
}
private void InitAbility(TimelineAbilityAssetBase asset)
private void InitAbility(TimelineAbilityAsset asset)
{
_abilityAsset.value = asset;
MaxFrame.value = AbilityAsset.FrameCount;
@ -84,11 +78,11 @@ namespace GAS.Editor
private ObjectField _abilityAsset;
private Button _btnShowAbilityAssetDetail;
public TimelineAbilityAssetBase AbilityAsset => _abilityAsset.value as TimelineAbilityAssetBase;
public TimelineAbilityAsset AbilityAsset => _abilityAsset.value as TimelineAbilityAsset;
// private TimelineAbilityEditorWindow AbilityAssetEditor => AbilityAsset != null
// ? UnityEditor.Editor.CreateEditor(AbilityAsset) as TimelineAbilityEditorWindow
// : null;
private TimelineAbilityEditorWindow AbilityAssetEditor => AbilityAsset != null
? UnityEditor.Editor.CreateEditor(AbilityAsset) as TimelineAbilityEditorWindow
: null;
private void InitAbilityAssetBar()
{
@ -101,6 +95,7 @@ namespace GAS.Editor
private void OnSequentialAbilityAssetChanged(ChangeEvent<Object> evt)
{
var asset = evt.newValue as TimelineAbilityAsset;
if (AbilityAsset != null)
{
MaxFrame.value = AbilityAsset.FrameCount;
@ -138,17 +133,17 @@ namespace GAS.Editor
BtnLoadPreviewScene.clickable.clicked += LoadPreviewScene;
BtnBackToScene = _root.Q<Button>(nameof(BtnBackToScene));
BtnBackToScene.clickable.clicked += BackToScene;
BtnChildInspector = _root.Q<Button>(nameof(BtnChildInspector));
BtnChildInspector.clickable.clicked += ShowChildInspector;
_previewObjectField = _root.Q<ObjectField>("PreviewInstance");
_previewObjectField.RegisterValueChangedCallback(OnPreviewObjectChanged);
}
private void ShowChildInspector()
{
if (_childInspector == null)
if(_childInspector==null)
{
_childInspector = GetInspectTarget();
_childInspector.Show();
@ -157,7 +152,7 @@ namespace GAS.Editor
EditorApplication.delayCall += () =>
DockUtilities.DockWindow(this, _childInspector, DockUtilities.DockPosition.Right);
}
private void OnPreviewObjectChanged(ChangeEvent<Object> evt)
{
// TODO : 在这里处理预览对象的变化
@ -205,10 +200,10 @@ namespace GAS.Editor
_currentMaxFrame = 0;
return;
}
if (_currentMaxFrame == value) return;
_currentMaxFrame = value;
AbilityAsset.FrameCount = _currentMaxFrame;
AbilityAsset.frameCount = _currentMaxFrame;
SaveAsset();
MaxFrame.value = _currentMaxFrame;
TrackView.UpdateContentSize();
@ -240,7 +235,7 @@ namespace GAS.Editor
{
return TimerShaftView.GetFrameIndexByPosition(x);
}
public int GetFrameIndexByMouse(float x)
{
return TimerShaftView.GetFrameIndexByMouse(x);
@ -266,23 +261,30 @@ namespace GAS.Editor
BtnRightFrame = _root.Q<Button>(nameof(BtnRightFrame));
BtnRightFrame.clickable.clicked += OnRightFrame;
InitInfo();
CurrentFrame = _root.Q<IntegerField>(nameof(CurrentFrame));
CurrentFrame.RegisterValueChangedCallback(OnCurrentFrameChanged);
MaxFrame = _root.Q<IntegerField>(nameof(MaxFrame));
MaxFrame.RegisterValueChangedCallback(OnMaxFrameChanged);
}
private void OnMaxFrameChanged(ChangeEvent<int> evt)
{
CurrentMaxFrame = evt.newValue;
MaxFrame.value = CurrentMaxFrame;
TotalTime.text = $"total:{CurrentMaxFrame * Config.TickTime}ms";
}
private void OnCurrentFrameChanged(ChangeEvent<int> evt)
{
CurrentSelectFrameIndex = evt.newValue;
CurrentFrame.value = CurrentSelectFrameIndex;
CurrentTime.text = $"time:{CurrentSelectFrameIndex * Config.TickTime}ms";
}
private void RefreshPlayButton()
@ -294,24 +296,44 @@ namespace GAS.Editor
private void OnPlay()
{
if (AbilityAsset == null) return;
if(AbilityAsset==null) return;
IsPlaying = !IsPlaying;
}
private void OnLeftFrame()
{
if (AbilityAsset == null) return;
if(AbilityAsset==null) return;
IsPlaying = false;
CurrentSelectFrameIndex -= 1;
}
private void OnRightFrame()
{
if (AbilityAsset == null) return;
if(AbilityAsset==null) return;
IsPlaying = false;
CurrentSelectFrameIndex += 1;
}
#endregion
#region Info
private Label FrameRate;
private Label CurrentTime;
private Label TotalTime;
private int FrameRateValue;
private void InitInfo()
{
FrameRate = _root.Q<Label>(nameof(FrameRate));
FrameRateValue = Config.FrameRate;
FrameRate.text = $"FPS:{FrameRateValue}";
CurrentTime = _root.Q<Label>(nameof(CurrentTime));
TotalTime = _root.Q<Label>(nameof(TotalTime));
}
#endregion
#region Clip Inspector
@ -320,7 +342,7 @@ namespace GAS.Editor
public void SetInspector(object target = null)
{
if (AbilityAsset == null) return;
if(AbilityAsset==null) return;
TimelineInspector.SetInspector(target);
}
@ -354,7 +376,7 @@ namespace GAS.Editor
if (IsPlaying)
{
var deltaTime = (DateTime.Now - _startTime).TotalSeconds;
var frameIndex = (int)(deltaTime * Config.DefaultFrameRate) + _startPlayFrameIndex;
var frameIndex = (int)(deltaTime * FrameRateValue) + _startPlayFrameIndex;
if (frameIndex >= CurrentMaxFrame)
{
frameIndex = CurrentMaxFrame;
@ -368,9 +390,9 @@ namespace GAS.Editor
private void EvaluateFrame(int frameIndex)
{
if (AbilityAsset == null || _previewObjectField.value == null) return;
foreach (var track in TrackView.TrackList)
track.TickView(frameIndex);
track.TickView(frameIndex);
}
private bool CanPlay()
@ -384,14 +406,16 @@ namespace GAS.Editor
#region Another Inspector
private static EditorWindow GetInspectTarget(Object targetGO = null)
private static EditorWindow GetInspectTarget(Object targetGO=null)
{
Type inspectorType = typeof(UnityEditor.Editor).Assembly.GetType("UnityEditor.InspectorWindow");
Type inspectorType = typeof(Editor).Assembly.GetType("UnityEditor.InspectorWindow");
EditorWindow inspectorInstance = CreateInstance(inspectorType) as EditorWindow;
if (targetGO) Selection.activeObject = targetGO;
if(targetGO) Selection.activeObject = targetGO;
return inspectorInstance;
}
#endregion
}
}
}
#endif

View File

@ -1,5 +1,5 @@
<ui:UXML xmlns:ui="UnityEngine.UIElements" xmlns:uie="UnityEditor.UIElements" xsi="http://www.w3.org/2001/XMLSchema-instance" engine="UnityEngine.UIElements" editor="UnityEditor.UIElements" noNamespaceSchemaLocation="../../../../../UIElementsSchema/UIElements.xsd" editor-extension-mode="True">
<Style src="project://database/Assets/GAS/Editor/Ability/AbilityTimelineEditor/EditorWindow/AbilityTimelineEditorWindow.uss?fileID=7433441132597879392&amp;guid=28fcc296ac2d3554db81c84901f63835&amp;type=3#AbilityTimelineEditorWindow" />
<Style src="project://database/Assets/GameScript/GAS/Editor/Ability/AbilityTimelineEditor/EditorWindow/AbilityTimelineEditorWindow.uss?fileID=7433441132597879392&amp;guid=28fcc296ac2d3554db81c84901f63835&amp;type=3#AbilityTimelineEditorWindow" />
<ui:VisualElement name="Root" style="flex-grow: 1; flex-direction: column; height: 100%; min-height: 100%; max-height: 100%; flex-shrink: 0;">
<ui:VisualElement name="AbilityAsset" style="flex-grow: 1; min-width: auto; min-height: auto; max-height: 40px; flex-direction: row; border-left-color: rgba(34, 34, 34, 0.84); border-right-color: rgba(34, 34, 34, 0.84); border-top-color: rgba(34, 34, 34, 0.84); border-bottom-color: rgba(34, 34, 34, 0.84); border-top-width: 0; border-right-width: 0; border-bottom-width: 2px; border-left-width: 0; height: 40px; align-self: auto; align-items: center;">
<uie:ObjectField label="Ability配置 " name="SequentialAbilityAsset" type="GAS.Runtime.TimelineAbilityAsset, JNGame.Runtime" style="-unity-font-style: bold; font-size: 13px; -unity-text-align: middle-left; align-items: center; width: auto; align-self: stretch; flex-direction: row; min-width: auto; max-height: none; max-width: 300px;" />
@ -14,24 +14,29 @@
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="Content" style="flex-grow: 1; flex-direction: row; flex-shrink: 1; height: auto;">
<ui:VisualElement name="LeftConsole" style="flex-grow: 1; width: 200px; min-width: 200px; border-left-color: rgb(41, 41, 41); border-right-color: rgb(41, 41, 41); border-top-color: rgb(41, 41, 41); border-bottom-color: rgb(41, 41, 41); border-left-width: 0; border-right-width: 2px; margin-right: 5px; max-width: 200px; height: 100%; max-height: 100%; min-height: auto; flex-shrink: 0;">
<ui:VisualElement name="Controller" style="flex-grow: 1; flex-direction: row; height: 30px; max-height: 30px; min-width: 200px; width: 200px; max-width: 200px; border-left-color: rgba(24, 24, 24, 0.5); border-right-color: rgba(24, 24, 24, 0.5); border-top-color: rgba(24, 24, 24, 0.5); border-bottom-color: rgba(24, 24, 24, 0.5); border-right-width: 0; align-items: auto; align-self: flex-start; justify-content: space-around; position: relative; left: auto; border-bottom-width: 2px;">
<ui:VisualElement name="LeftConsole" style="flex-grow: 1; width: 220px; min-width: 220px; border-left-color: rgb(41, 41, 41); border-right-color: rgb(41, 41, 41); border-top-color: rgb(41, 41, 41); border-bottom-color: rgb(41, 41, 41); border-left-width: 0; border-right-width: 2px; margin-right: 5px; max-width: 220px; height: 100%; max-height: 100%; min-height: auto; flex-shrink: 0;">
<ui:VisualElement name="Controller" style="flex-grow: 1; flex-direction: row; height: 30px; max-height: 30px; min-width: 220px; width: 220px; max-width: 220px; border-left-color: rgba(24, 24, 24, 0.5); border-right-color: rgba(24, 24, 24, 0.5); border-top-color: rgba(24, 24, 24, 0.5); border-bottom-color: rgba(24, 24, 24, 0.5); border-right-width: 0; align-items: auto; align-self: flex-start; justify-content: space-around; position: relative; left: auto; border-bottom-width: 2px;">
<ui:VisualElement name="ButtonGroup" style="flex-grow: 1; flex-direction: row; justify-content: flex-start; align-self: center;">
<ui:Button text="&lt;" parse-escape-sequences="true" display-tooltip-when-elided="true" name="BtnLeftFrame" style="font-size: 13px; -unity-font-style: bold; align-self: center;" />
<ui:Button text="▶" parse-escape-sequences="true" display-tooltip-when-elided="true" name="BtnPlay" style="-unity-font-style: bold; font-size: 13px; align-self: center;" />
<ui:Button text="&gt;" parse-escape-sequences="true" display-tooltip-when-elided="true" name="BtnRightFrame" style="-unity-font-style: bold; font-size: 13px; align-self: center;" />
</ui:VisualElement>
<ui:VisualElement name="FrameCount" style="flex-grow: 1; flex-direction: row-reverse; justify-content: flex-end; align-items: center; align-self: center; flex-wrap: nowrap; flex-shrink: 1;">
<ui:IntegerField value="0" name="MaxFrame" is-delayed="true" style="width: 40px;" />
<uie:IntegerField value="0" name="MaxFrame" is-delayed="true" style="width: 40px;" />
<ui:Label tabindex="-1" text="/" parse-escape-sequences="true" display-tooltip-when-elided="true" name="Label" />
<ui:IntegerField value="0" name="CurrentFrame" readonly="false" style="width: 40px;" />
<uie:IntegerField value="0" name="CurrentFrame" readonly="false" style="width: 40px;" />
</ui:VisualElement>
</ui:VisualElement>
<ui:VisualElement name="Info" style="flex-grow: 1; flex-direction: row; height: 30px; max-height: 30px; min-width: 220px; width: 220px; max-width: 220px; border-left-color: rgba(24, 24, 24, 0.5); border-right-color: rgba(24, 24, 24, 0.5); border-top-color: rgba(24, 24, 24, 0.5); border-bottom-color: rgba(24, 24, 24, 0.5); border-right-width: 0; align-items: auto; align-self: flex-start; justify-content: space-around; position: relative; left: auto; border-bottom-width: 2px;">
<ui:Label tabindex="-1" text="FPS:0" parse-escape-sequences="true" display-tooltip-when-elided="true" name="FrameRate" style="-unity-text-align: middle-left; height: 25px; width: auto; white-space: nowrap; position: relative;" />
<ui:Label tabindex="-1" text="time:0ms" parse-escape-sequences="true" display-tooltip-when-elided="true" name="CurrentTime" style="-unity-text-align: middle-left; height: 25px; width: auto; white-space: nowrap; position: relative;" />
<ui:Label tabindex="-1" text="total:0ms" parse-escape-sequences="true" display-tooltip-when-elided="true" name="TotalTime" style="-unity-text-align: middle-left; height: 25px; width: auto; white-space: nowrap; position: relative;" />
</ui:VisualElement>
<ui:VisualElement name="TrackMenu" style="flex-grow: 1; height: 100%; max-height: 100%; min-height: auto; padding-left: 3px; padding-right: 3px;" />
</ui:VisualElement>
<ui:VisualElement name="RightContent" style="flex-grow: 1; height: 100%; max-height: 100%; min-height: 100%; flex-shrink: 0;">
<ui:VisualElement name="RightTimeline" style="flex-grow: 1; padding-right: 0; width: auto; flex-shrink: 0; height: 100%; max-height: 100%; min-height: 100%; min-width: auto; max-width: none;">
<ui:IMGUIContainer name="TimerShaft" style="height: 30px; min-height: 30px; border-left-color: rgb(43, 43, 43); border-right-color: rgb(43, 43, 43); border-top-color: rgb(43, 43, 43); border-bottom-color: rgb(43, 43, 43); border-bottom-width: 1px; margin-right: 13px; max-height: 30px;" />
<ui:IMGUIContainer name="TimerShaft" style="height: 58px; min-height: 58px; border-left-color: rgb(43, 43, 43); border-right-color: rgb(43, 43, 43); border-top-color: rgb(43, 43, 43); border-bottom-color: rgb(43, 43, 43); border-bottom-width: 1px; margin-right: 13px; max-height: 58px;" />
<ui:ScrollView name="MainContent" mode="VerticalAndHorizontal" vertical-scroller-visibility="AlwaysVisible" style="flex-grow: 1;">
<ui:VisualElement name="ContentTrackList" style="flex-grow: 1; height: 1000px; flex-shrink: 1; width: auto;" />
</ui:ScrollView>

View File

@ -15,14 +15,14 @@ namespace GAS.Editor
private readonly VisualElement _root;
private Button _btnAddTrack;
private VisualElement _contentTrackListParent;
private MenuTrack _menuPassiveGameplayEffect;
private MenuTrack _menuBuffGameplayEffect;
private MenuTrack _menuDurationalCue;
private MenuTrack _menuInstantCue;
private MenuTrack _menuInstantTask;
private MenuTrack _menuOngoingTask;
private MenuTrack _menuReleaseGameplayEffect;
private MenuTrack _menuPassiveGameplayEffect;
private MenuTrack _menuPassiveTask;
private MenuTrack _menuReleaseGameplayEffect;
private VisualElement _trackMenuParent;
public TimelineTrackView(VisualElement root)
@ -34,7 +34,7 @@ namespace GAS.Editor
public List<TrackBase> TrackList { get; } = new();
private static AbilityTimelineEditorConfig Config => AbilityTimelineEditorWindow.Instance.Config;
private static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private void InitTracks()
{
@ -118,7 +118,7 @@ namespace GAS.Editor
buffGameplayEffectTrackData);
TrackList.Add(buffTrack);
}
// Ongoing Task
_menuOngoingTask = new MenuTrack();
_menuOngoingTask.Init(_contentTrackListParent, _trackMenuParent, Config.FrameUnitWidth,
@ -157,7 +157,7 @@ namespace GAS.Editor
customClipEventTrackData);
TrackList.Add(customClipTrack);
}
UpdateContentSize();
}

View File

@ -1,31 +1,31 @@
//
// #if UNITY_EDITOR
// namespace GAS.Editor
// {
// using Runtime;
// using Sirenix.OdinInspector.Editor;
// using UnityEditor;
// using UnityEngine;
// using GAS.General;
//
// [CustomEditor(typeof(TimelineAbilityAsset))]
// public class TimelineAbilityEditorWindow : OdinEditor
// {
// private TimelineAbilityAsset _asset => target as TimelineAbilityAsset;
//
// public override void OnInspectorGUI()
// {
// base.OnInspectorGUI();
//
// EditorGUILayout.BeginVertical(GUI.skin.box);
// if (GUILayout.Button(GASTextDefine.BUTTON_CHECK_TIMELINE_ABILITY, GUILayout.Height(30), GUILayout.Width(300))) EditAbilityTimeline();
// EditorGUILayout.EndVertical();
// }
//
// private void EditAbilityTimeline()
// {
// AbilityTimelineEditorWindow.ShowWindow(_asset);
// }
// }
// }
// #endif

#if UNITY_EDITOR
namespace GAS.Editor
{
using Runtime;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
using GAS.General;
[CustomEditor(typeof(TimelineAbilityAsset))]
public class TimelineAbilityEditorWindow : OdinEditor
{
private TimelineAbilityAsset _asset => target as TimelineAbilityAsset;
public override void OnInspectorGUI()
{
base.OnInspectorGUI();
EditorGUILayout.BeginVertical(GUI.skin.box);
if (GUILayout.Button(GASTextDefine.BUTTON_CHECK_TIMELINE_ABILITY, GUILayout.Height(30), GUILayout.Width(300))) EditAbilityTimeline();
EditorGUILayout.EndVertical();
}
private void EditAbilityTimeline()
{
AbilityTimelineEditorWindow.ShowWindow(_asset);
}
}
}
#endif

View File

@ -8,7 +8,7 @@ namespace GAS.Editor
public class TaskClip : TrackClip<TaskClipEventTrack>
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public TaskClipEvent TaskClipData => clipData as TaskClipEvent;
public TaskClipEvent ClipDataForSave
@ -36,7 +36,7 @@ namespace GAS.Editor
public override void RefreshShow(float newFrameUnitWidth)
{
base.RefreshShow(newFrameUnitWidth);
var taskType = TaskClipData.ongoingTask.TaskData.Type;
var taskType = TaskClipData.ongoingTask.taskData.Type;
var shortName = taskType.Split('.').Last();
ItemLabel.text = !string.IsNullOrEmpty(shortName) ? shortName : "Null!";
}

View File

@ -42,7 +42,7 @@ namespace GAS.Editor
private const string GRP_BOX = "GRP_BOX";
private const string GRP_BOX_TASK = "GRP_BOX/Task";
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TaskClip _clip;
public static TaskClipEditor Create(TaskClip clip)
@ -90,7 +90,7 @@ namespace GAS.Editor
{
RunInfo = $"<b>Run(f):{_clip.TaskClipData.startFrame} -> {_clip.TaskClipData.EndFrame}</b>";
Duration = _clip.TaskClipData.durationFrame;
OngoingTaskType = _clip.TaskClipData.ongoingTask.TaskData.Type;
OngoingTaskType = _clip.TaskClipData.ongoingTask.taskData.Type;
RefreshTaskInspector();
}
@ -123,8 +123,8 @@ namespace GAS.Editor
private void OnTaskTypeChanged()
{
_clip.ClipDataForSave.ongoingTask.TaskData.Type = OngoingTaskType;
_clip.ClipDataForSave.ongoingTask.TaskData.Data = null;
_clip.ClipDataForSave.ongoingTask.taskData.Type = OngoingTaskType;
_clip.ClipDataForSave.ongoingTask.taskData.Data = null;
AbilityTimelineEditorWindow.Instance.Save();
AbilityTimelineEditorWindow.Instance.TimelineInspector.RefreshInspector();

View File

@ -13,7 +13,7 @@ namespace GAS.Editor
protected override Color TrackColor => new Color(0.7f, 0.3f, 0.7f, 0.2f);
protected override Color MenuColor => new Color(0.5f, 0.3f, 0.5f, 1);
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public TaskClipEventTrackData TaskClipTrackDataForSave
{
get

View File

@ -36,7 +36,7 @@ namespace GAS.Editor
string info = "";
foreach (var clip in _track.TaskClipTrackDataForSave.clipEvents)
{
var taskName = clip.ongoingTask.TaskData.Type;
var taskName = clip.ongoingTask.taskData.Type;
var shortName = taskName.Substring(taskName.LastIndexOf('.') + 1);
info += $"[{shortName}] Run(f):{clip.startFrame} -> {clip.EndFrame} \n";
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: ad3afdc4d0854b7e87d840e52dbedd44
timeCreated: 1729189674
guid: b4a27decc519488fb2d4065ccafb75d9
timeCreated: 1717572791

View File

@ -5,9 +5,10 @@ namespace GAS.Editor
using System.Linq;
using GAS.Runtime;
public class PassiveTaskClip : TrackClip<PassiveTaskClipEventTrack>
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public PassiveTaskClipEvent TaskClipData => clipData as PassiveTaskClipEvent;
public PassiveTaskClipEvent ClipDataForSave
@ -22,6 +23,7 @@ namespace GAS.Editor
}
}
public override void Delete()
{
var success = track.TaskClipTrackDataForSave.clipEvents.Remove(TaskClipData);
@ -34,7 +36,7 @@ namespace GAS.Editor
public override void RefreshShow(float newFrameUnitWidth)
{
base.RefreshShow(newFrameUnitWidth);
var taskType = TaskClipData.passiveTask.TaskData.Type;
var taskType = TaskClipData.passiveTask.taskData.Type;
var shortName = taskType.Split('.').Last();
ItemLabel.text = !string.IsNullOrEmpty(shortName) ? shortName : "Null!";
}

View File

@ -42,7 +42,7 @@ namespace GAS.Editor
private const string GRP_BOX = "GRP_BOX";
private const string GRP_BOX_TASK = "GRP_BOX/Task";
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private PassiveTaskClip _clip;
public static PassiveTaskClipEditor Create(PassiveTaskClip clip)
@ -96,7 +96,7 @@ namespace GAS.Editor
RunInfo = $"<b>Start(f):{_clip.TaskClipData.startFrame}</b>";
// Duration = _clip.TaskClipData.durationFrame;
StartFrame = _clip.TaskClipData.startFrame;
PassiveTaskType = _clip.TaskClipData.passiveTask.TaskData.Type;
PassiveTaskType = _clip.TaskClipData.passiveTask.taskData.Type;
RefreshTaskInspector();
}
@ -138,8 +138,8 @@ namespace GAS.Editor
private void OnTaskTypeChanged()
{
_clip.ClipDataForSave.passiveTask.TaskData.Type = PassiveTaskType;
_clip.ClipDataForSave.passiveTask.TaskData.Data = null;
_clip.ClipDataForSave.passiveTask.taskData.Type = PassiveTaskType;
_clip.ClipDataForSave.passiveTask.taskData.Data = null;
AbilityTimelineEditorWindow.Instance.Save();
AbilityTimelineEditorWindow.Instance.TimelineInspector.RefreshInspector();

View File

@ -13,7 +13,7 @@ namespace GAS.Editor
protected override Color TrackColor => new Color(0.7f, 0.3f, 0.7f, 0.2f);
protected override Color MenuColor => new Color(0.5f, 0.3f, 0.5f, 1);
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public PassiveTaskClipEventTrackData TaskClipTrackDataForSave
{
get

View File

@ -36,7 +36,7 @@ namespace GAS.Editor
string info = "";
foreach (var clip in _track.TaskClipTrackDataForSave.clipEvents)
{
var taskName = clip.passiveTask.TaskData.Type;
var taskName = clip.passiveTask.taskData.Type;
var shortName = taskName.Substring(taskName.LastIndexOf('.') + 1);
info += $"[{shortName}] Run(f):{clip.startFrame} -> {clip.EndFrame} \n";
}

View File

@ -1,11 +1,18 @@
using GAS.Runtime;
using UnityEngine;

#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.Linq;
using GAS.General;
using Runtime;
using UnityEngine;
using UnityEngine.UIElements;
public class TaskMark : TrackMark<TaskMarkEventTrack>
{
public new TaskMarkEvent MarkData => markData as TaskMarkEvent;
public TaskMarkEvent MarkData => markData as TaskMarkEvent;
public TaskMarkEvent MarkDataForSave
{
@ -18,8 +25,8 @@ namespace GAS.Editor
return null;
}
}
public override Object DataInspector => TaskMarkEditor.Create(this);
public override UnityEngine.Object DataInspector => TaskMarkEditor.Create(this);
public override void Duplicate()
{
@ -31,10 +38,10 @@ namespace GAS.Editor
var markEvent = new TaskMarkEvent
{
startFrame = startFrame,
InstantTasks = (markData as TaskMarkEvent)?.InstantTasks
instantTasks = (markData as TaskMarkEvent)?.instantTasks
};
track.InstantTaskEventTrackData.markEvents.Add(markEvent);
// 刷新显示
var mark = new TaskMark();
mark.InitTrackMark(track, track.Track, FrameUnitWidth, markEvent);
@ -159,7 +166,7 @@ namespace GAS.Editor
//
// #endregion
public override void Delete()
{
var success = track.InstantTaskEventTrackData.markEvents.Remove(MarkData);
@ -168,7 +175,7 @@ namespace GAS.Editor
track.RemoveTrackItem(this);
AbilityTimelineEditorWindow.Instance.SetInspector();
}
public override void UpdateMarkDataFrame(int newStartFrame)
{
var updatedClip = MarkDataForSave;
@ -181,7 +188,7 @@ namespace GAS.Editor
{
if (frameIndex == StartFrameIndex)
{
foreach (var t in MarkData.InstantTasks)
foreach (var t in MarkData.instantTasks)
{
var task = t.Load() as InstantAbilityTask;
task?.OnEditorPreview();
@ -194,4 +201,5 @@ namespace GAS.Editor
//MarkDataForSave.InstantTasks[taskList.selectedIndex].Save(task);
}
}
}
}
#endif

View File

@ -1,20 +1,20 @@
using System;
using System.Collections;
using System.Collections.Generic;
using GAS.General;
using GAS.Runtime;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections;
using System.Collections.Generic;
using Editor;
using GAS.General;
using Runtime;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
public class TaskMarkEditor : OdinEditorWindow
{
[BoxGroup]
[HideLabel]
[DisplayAsString(TextAlignment.Left, true)]
[BoxGroup] [HideLabel] [DisplayAsString(TextAlignment.Left, true)]
public string RunInfo;
private TaskMark _mark;
@ -22,13 +22,13 @@ namespace GAS.Editor
[Delayed]
[BoxGroup]
[HideReferenceObjectPicker]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = true)]
[ListDrawerSettings(Expanded = true, DraggableItems = true)]
[OnValueChanged("OnTaskListChanged", true)]
public List<InstantTaskCellInspector> Tasks;
public static TaskMarkEditor Create(TaskMark mark)
{
var window = CreateInstance<TaskMarkEditor>();
var window = new TaskMarkEditor();
window._mark = mark;
window.UpdateMarkInfo();
@ -47,7 +47,7 @@ namespace GAS.Editor
{
RunInfo = $"<b>Trigger(f):{_mark.MarkData.startFrame}</b>";
Tasks = new List<InstantTaskCellInspector>();
foreach (var taskData in _mark.MarkData.InstantTasks) Tasks.Add(new InstantTaskCellInspector(taskData));
foreach (var taskData in _mark.MarkData.instantTasks) Tasks.Add(new InstantTaskCellInspector(taskData));
}
private void OnTaskListChanged()
@ -56,14 +56,14 @@ namespace GAS.Editor
foreach (var t in Tasks)
tasks.Add(new InstantTaskData
{
TaskData = new JsonData
taskData = new JsonData
{
Type = t.InstantTaskType,
Data = t.Data()
}
});
_mark.MarkDataForSave.InstantTasks = tasks;
_mark.MarkDataForSave.instantTasks = tasks;
AbilityTimelineEditorWindow.Instance.Save();
}
}
@ -92,18 +92,15 @@ namespace GAS.Editor
[InfoBox("This Task has no inspector!", InfoMessageType.Warning, "InstantTaskIsNull")]
[OnValueChanged("OnTaskTypeChanged")]
public string InstantTaskType;
[BoxGroup]
[HideReferenceObjectPicker]
[HideIf("InstantTaskIsNull")]
[LabelText("Detail")]
[BoxGroup] [HideReferenceObjectPicker] [HideIf("InstantTaskIsNull")] [LabelText("Detail")]
public InstantTaskInspector InstantTask;
public InstantTaskCellInspector(InstantTaskData data)
{
_data = data;
_instantAbilityTask = data.Load() as InstantAbilityTask;
InstantTaskType = data.TaskData.Type;
InstantTaskType = data.taskData.Type;
RefreshDetailInspector();
}
@ -111,7 +108,7 @@ namespace GAS.Editor
{
_data = new InstantTaskData();
_instantAbilityTask = _data.Load() as InstantAbilityTask;
InstantTaskType = _data.TaskData.Type;
InstantTaskType = _data.taskData.Type;
RefreshDetailInspector();
}
@ -126,11 +123,8 @@ namespace GAS.Editor
_instantTaskInspectorMap = new Dictionary<Type, Type>();
foreach (var inspectorType in InstantTaskInspectorTypes)
{
if (inspectorType.BaseType != null)
{
var taskType = inspectorType.BaseType.GetGenericArguments()[0];
_instantTaskInspectorMap.Add(taskType, inspectorType);
}
var taskType = inspectorType.BaseType.GetGenericArguments()[0];
_instantTaskInspectorMap.Add(taskType, inspectorType);
}
return _instantTaskInspectorMap;
@ -142,13 +136,13 @@ namespace GAS.Editor
if (_instantAbilityTask == null) return null;
_data.Save(_instantAbilityTask);
return _data.TaskData.Data;
return _data.taskData.Data;
}
private void OnTaskTypeChanged()
{
_data.TaskData.Type = InstantTaskType;
_data.TaskData.Data = null;
_data.taskData.Type = InstantTaskType;
_data.taskData.Data = null;
RefreshDetailInspector();
}
@ -160,7 +154,7 @@ namespace GAS.Editor
public void RefreshDetailInspector()
{
_instantAbilityTask = _data.Load() as InstantAbilityTask;
if (_instantAbilityTask != null && InstantTaskInspectorMap.TryGetValue(_instantAbilityTask.GetType(), out var inspectorType))
if (InstantTaskInspectorMap.TryGetValue(_instantAbilityTask.GetType(), out var inspectorType))
{
var taskInspector = (InstantTaskInspector)Activator.CreateInstance(inspectorType);
taskInspector.Init(_instantAbilityTask);
@ -172,4 +166,5 @@ namespace GAS.Editor
}
}
}
}
}
#endif

View File

@ -11,7 +11,7 @@ namespace GAS.Editor
public class TaskMarkEventTrack : TrackBase
{
private TaskMarkEventTrackData _instantTasksTrackData;
private static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public TaskMarkEventTrackData InstantTaskEventTrackData
{

View File

@ -37,9 +37,9 @@ namespace GAS.Editor
foreach (var mark in _track.InstantTaskEventTrackData.markEvents)
{
info += $"Trigger(f):{mark.startFrame} \n";
foreach (var task in mark.InstantTasks)
foreach (var task in mark.instantTasks)
{
var taskName = task.TaskData.Type;
var taskName = task.taskData.Type;
var shortName = taskName.Substring(taskName.LastIndexOf('.') + 1);
info += $" |-> {shortName}\n";
}

View File

@ -59,7 +59,15 @@ namespace GAS.Editor
AbilityTimelineEditorWindow.Instance.Save();
clipData = updatedClip;
}
public void UpdateGameplayEffectValueId(int id)
{
var updatedClip = ClipDataForSave;
ClipDataForSave.gameplayEffectValueId = id;
AbilityTimelineEditorWindow.Instance.Save();
clipData = updatedClip;
}
public void UpdateClipDataBuff(GameplayEffectAsset newBuff)
{
var updatedClip = ClipDataForSave;

View File

@ -10,7 +10,7 @@ namespace GAS.Editor
public class BuffGameplayEffectClipEditor : OdinEditorWindow
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private BuffGameplayEffectClip _clip;
@ -33,6 +33,12 @@ namespace GAS.Editor
[OnValueChanged("OnDurationFrameChanged")]
public int Duration;
[Delayed]
[BoxGroup]
[LabelText("BuffValueId")]
[OnValueChanged("OnGameplayEffectValueIdChanged")]
public int GameplayEffectValueId;
[Delayed]
[BoxGroup]
[AssetSelector]
@ -52,6 +58,7 @@ namespace GAS.Editor
Buff = _clip.BuffGameplayEffectClipData.gameplayEffect;
RunInfo = $"<b>Run(f):{_clip.BuffGameplayEffectClipData.startFrame} -> {_clip.BuffGameplayEffectClipData.EndFrame}</b>";
Duration = _clip.BuffGameplayEffectClipData.durationFrame;
GameplayEffectValueId = _clip.BuffGameplayEffectClipData.gameplayEffectValueId;
}
private void OnDurationFrameChanged()
@ -65,6 +72,13 @@ namespace GAS.Editor
_clip.RefreshShow(_clip.FrameUnitWidth);
Refresh();
}
private void OnGameplayEffectValueIdChanged()
{
// 保存数据
_clip.UpdateGameplayEffectValueId(GameplayEffectValueId);
Refresh();
}
private void OnBuffChanged()
{

View File

@ -13,7 +13,7 @@ namespace GAS.Editor
protected override Color TrackColor => new(0.9f, 0.6f, 0.6f, 0.2f);
protected override Color MenuColor => new(0.9f, 0.6f, 0.6f, 1);
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public BuffGameplayEffectTrackData BuffTrackDataForSave
{

View File

@ -8,7 +8,7 @@ namespace GAS.Editor
public class DurationalCueClip : TrackClip<DurationalCueTrack>
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public DurationalCueClipEvent DurationalCueClipData => clipData as DurationalCueClipEvent;
private DurationalCueClipEvent ClipDataForSave

View File

@ -10,7 +10,7 @@ namespace GAS.Editor
public class DurationalCueClipEditor : OdinEditorWindow
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private DurationalCueClip _clip;

View File

@ -13,7 +13,7 @@ namespace GAS.Editor
protected override Color TrackColor => new(0.1f, 0.6f, 0.1f, 0.2f);
protected override Color MenuColor => new(0.1f, 0.6f, 0.1f, 1);
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public DurationalCueTrackData CueTrackDataForSave
{

View File

@ -12,7 +12,7 @@ namespace GAS.Editor
public class InstantCueTrack : TrackBase
{
private InstantCueTrackData _instantCuesTrackData;
private static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public InstantCueTrackData InstantCueTrackData
{

View File

@ -13,7 +13,7 @@ namespace GAS.Editor
private Color _trackColor;
private Type _trackDataType;
private Type _trackType;
private static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static AbilityTimelineEditorConfig Config => AbilityTimelineEditorWindow.Instance.Config;
private static TimelineTrackView TrackView => AbilityTimelineEditorWindow.Instance.TrackView;
public override Type TrackDataType { get; }
@ -61,7 +61,7 @@ namespace GAS.Editor
// 创建Data
var data = (TrackDataBase)Activator.CreateInstance(_trackDataType);
data.DefaultInit();
data.AddToAbilityAsset(AbilityAsset);
data.AddToAbilityAsset(AbilityAsset, _trackDataType);
// 初始化View
track.Init(TrackParent, MenuParent, Config.FrameUnitWidth, data);
@ -105,18 +105,18 @@ namespace GAS.Editor
if (_trackType == typeof(BuffGameplayEffectTrack))
return baseIndex + (AbilityAsset.BuffGameplayEffects?.Count ?? 0);
if (_trackType == typeof(PassiveGameplayEffectTrack))
return baseIndex + (AbilityAsset.PassiveGameplayEffects?.Count ?? 0);
if (_trackType == typeof(TaskClipEventTrack))
return baseIndex + (AbilityAsset.OngoingTasks?.Count ?? 0);
if (_trackType == typeof(DurationalCueTrack))
return baseIndex + (AbilityAsset.DurationalCues?.Count ?? 0);
if (_trackType == typeof(PassiveGameplayEffectTrack))
return baseIndex + (AbilityAsset.PassiveGameplayEffects?.Count ?? 0);
if (_trackType == typeof(PassiveTaskClipEventTrack))
return baseIndex + (AbilityAsset.PassiveTasks?.Count ?? 0);
return -1;
}
}

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: e2da34a43d154bf7af1859c786b7e438
timeCreated: 1729189458
fileFormatVersion: 2
guid: 3a6f1fc54df943d3a4143196c049703d
timeCreated: 1717123975

View File

@ -10,7 +10,7 @@ namespace GAS.Editor
public class PassiveGameplayEffectClipEditor : OdinEditorWindow
{
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private PassiveGameplayEffectClip _clip;

View File

@ -9,12 +9,11 @@ namespace GAS.Editor
public class PassiveGameplayEffectTrack : TrackBase
{
private PassiveGameplayEffectTrackData _PassiveGameplayEffectTrackData;
private TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public override Type TrackDataType => typeof(PassiveGameplayEffectTrackData);
protected override Color TrackColor => new(0.9f, 0.6f, 0.6f, 0.2f);
protected override Color MenuColor => new(0.9f, 0.6f, 0.6f, 1);
private TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public PassiveGameplayEffectTrackData BuffTrackDataForSave
{

View File

@ -1,11 +1,18 @@
using GAS.Runtime;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.Linq;
using GAS.General;
using Runtime;
using UnityEngine;
using UnityEngine.UIElements;
using Object = UnityEngine.Object;
public class ReleaseGameplayEffectMark : TrackMark<ReleaseGameplayEffectTrack>
{
private new ReleaseGameplayEffectMarkEvent MarkData => markData as ReleaseGameplayEffectMarkEvent;
private ReleaseGameplayEffectMarkEvent MarkData => markData as ReleaseGameplayEffectMarkEvent;
public ReleaseGameplayEffectMarkEvent MarkDataForSave
{
@ -127,4 +134,5 @@ namespace GAS.Editor
}
}
}
}
}
#endif

View File

@ -1,25 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GAS.General;
using GAS.Runtime;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
public class ReleaseGameplayEffectMarkEditor : OdinEditorWindow
using System.Collections;
using System.Linq;
using UnityEngine.Serialization;
using System;
using System.Collections.Generic;
using UnityEditor;
using Editor;
using UnityEngine;
using Sirenix.OdinInspector.Editor;
using Sirenix.OdinInspector;
using GAS.General;
using GAS.Runtime;
public class ReleaseGameplayEffectMarkEditor:OdinEditorWindow
{
private const string GRP_BOX = "GRP_BOX";
private const string GRP_BOX_CATCHER = "GRP_BOX/Catcher";
private ReleaseGameplayEffectMark _mark;
public static ReleaseGameplayEffectMarkEditor Create(ReleaseGameplayEffectMark mark)
{
var window = CreateInstance<ReleaseGameplayEffectMarkEditor>();
var window = ScriptableObject.CreateInstance<ReleaseGameplayEffectMarkEditor>();
window._mark = mark;
window.UpdateMarkInfo();
return window;
@ -27,54 +29,72 @@ namespace GAS.Editor
[BoxGroup(GRP_BOX)]
[HideLabel]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
public string RunInfo;
// TODO TargetCatcher
[Delayed]
[BoxGroup(GRP_BOX_CATCHER)]
[LabelText("Target Catcher")]
[ValueDropdown("TargetCatcherSonTypeChoices")]
[InfoBox("This Catcher has no inspector!", InfoMessageType.Warning, "CatcherIsNull")]
[InfoBox("This Catcher has no inspector!",InfoMessageType.Warning, "CatcherIsNull")]
[OnValueChanged("OnCatcherTypeChanged")]
public string CatcherType;
[Delayed]
[BoxGroup(GRP_BOX_CATCHER)]
[HideReferenceObjectPicker]
[HideIf("CatcherIsNull")]
[LabelText("Detail")]
[OnValueChanged("OnCatcherChanged", true)]
[OnValueChanged("OnCatcherChanged",true)]
public TargetCatcherInspector Catcher;
// [Delayed]
// [BoxGroup(GRP_BOX)]
// [AssetSelector]
// [ListDrawerSettings(Expanded = true, DraggableItems = true)]
// [OnValueChanged("OnGameplayEffectListChanged")]
// public List<GameplayEffectAsset> gameplayEffects;
[Delayed]
[BoxGroup(GRP_BOX)]
[AssetSelector]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = true)]
[OnValueChanged("OnGameplayEffectListChanged")]
public List<GameplayEffectAsset> gameplayEffects;
[BoxGroup(GRP_BOX)]
[Button]
[GUIColor(0.9f, 0.2f, 0.2f)]
void Delete()
{
_mark.Delete();
}
[ListDrawerSettings(Expanded = true, DraggableItems = true)]
[OnValueChanged("OnGameplayEffectAssetDataListChanged",true)]
public List<GameplayEffectAssetData> gameplayEffects;
// [BoxGroup(GRP_BOX)]
// [Button]
// void Save()
// {
// // OnGameplayEffectListChanged();
// OnGameplayEffectAssetDataListChanged();
//
// AbilityTimelineEditorWindow.Instance.Save();
// AbilityTimelineEditorWindow.Instance.TimelineInspector.RefreshInspector();
// }
// [BoxGroup(GRP_BOX)]
// [Button]
// [GUIColor(0.9f,0.2f,0.2f)]
// void Delete()
// {
// _mark.Delete();
// }
void UpdateMarkInfo()
{
RunInfo = $"<b>Trigger(f):{_mark.MarkData.startFrame}</b>";
gameplayEffects = _mark.MarkDataForSave.gameplayEffectAssets;
// gameplayEffects = _mark.MarkDataForSave.gameplayEffectAssets;
gameplayEffects= _mark.MarkDataForSave.gameplayEffectAssets;
CatcherType = _mark.MarkDataForSave.jsonTargetCatcher.Type;
RefreshCatcherInspector();
}
void RefreshCatcherInspector()
{
// 根据选择的OngoingAbilityTask子类显示对应的属性
var catcher = _mark.MarkDataForSave.LoadTargetCatcher();
var catcher = _mark.MarkDataForSave.TargetCatcher;
if (TargetCatcherInspectorMap.TryGetValue(catcher.GetType(), out var inspectorType))
{
var targetCatcherInspector =
@ -87,10 +107,16 @@ namespace GAS.Editor
Debug.LogWarning($"[EX] TargetCatcherInspector not found: {catcher.GetType()}");
}
}
// void OnGameplayEffectListChanged()
// {
// _mark.MarkDataForSave.gameplayEffectAssets = gameplayEffects ;
// AbilityTimelineEditorWindow.Instance.Save();
// }
void OnGameplayEffectListChanged()
void OnGameplayEffectAssetDataListChanged()
{
_mark.MarkDataForSave.gameplayEffectAssets = gameplayEffects;
_mark.MarkDataForSave.gameplayEffectAssets = gameplayEffects ;
AbilityTimelineEditorWindow.Instance.Save();
}
@ -98,19 +124,20 @@ namespace GAS.Editor
{
_mark.MarkDataForSave.jsonTargetCatcher.Type = CatcherType;
_mark.MarkDataForSave.jsonTargetCatcher.Data = null;
AbilityTimelineEditorWindow.Instance.Save();
AbilityTimelineEditorWindow.Instance.TimelineInspector.RefreshInspector();
}
void OnCatcherChanged()
{
// _mark.MarkDataForSave.jsonTargetCatcher.Data = Catcher.ToJson();
// _mark.MarkDataForSave.jsonTargetCatcher.Data = JsonUtility.ToJson(Catcher);
// AbilityTimelineEditorWindow.Instance.Save();
}
private bool CatcherIsNull => Catcher == null;
private static Type[] _targetCatcherInspectorTypes;
private static List<string> TargetCatcherSonTypeChoices;
private static Dictionary<Type, Type> _targetCatcherInspectorMap;
@ -134,20 +161,18 @@ namespace GAS.Editor
_targetCatcherInspectorMap = new Dictionary<Type, Type>();
foreach (var catcherInspectorType in TargetCatcherInspectorTypes)
{
if (catcherInspectorType.BaseType != null)
{
var targetCatcherType = catcherInspectorType.BaseType.GetGenericArguments()[0];
_targetCatcherInspectorMap.Add(targetCatcherType, catcherInspectorType);
}
var targetCatcherType = catcherInspectorType.BaseType.GetGenericArguments()[0];
_targetCatcherInspectorMap.Add(targetCatcherType, catcherInspectorType);
}
return _targetCatcherInspectorMap;
}
}
}
[CustomEditor(typeof(ReleaseGameplayEffectMarkEditor))]
public class ReleaseGameplayEffectMarkInspector : OdinEditorWithoutHeader
public class ReleaseGameplayEffectMarkInspector:OdinEditorWithoutHeader
{
}
}
}
#endif

View File

@ -9,7 +9,7 @@ namespace GAS.Editor
public class ReleaseGameplayEffectTrack : TrackBase
{
private static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
public ReleaseGameplayEffectTrackData ReleaseGameplayEffectTrackData {
get
{
@ -65,7 +65,7 @@ namespace GAS.Editor
var markEvent = new ReleaseGameplayEffectMarkEvent
{
startFrame = GetTrackIndexByMouse(action.eventInfo.localMousePosition.x),
gameplayEffectAssets = new List<GameplayEffectAsset>()
gameplayEffectAssets = new List<GameplayEffectAssetData>()
};
ReleaseGameplayEffectTrackData.markEvents.Add(markEvent);

View File

@ -37,9 +37,19 @@ namespace GAS.Editor
foreach (var mark in _track.ReleaseGameplayEffectTrackData.markEvents)
{
info += $"Trigger(f):{mark.startFrame} \n";
foreach (var ge in mark.gameplayEffectAssets)
// foreach (var ge in mark.gameplayEffectAssets)
// {
// var geName = ge != null ? ge.name : "NULL";
// info += $" |-> {geName}\n";
// }
foreach (var data in mark.gameplayEffectAssets)
{
var geName = ge != null ? ge.name : "NULL";
var geName = "NULL";
if (data != null && data.asset != null)
{
geName = $"{data.asset.name} {data.id}";
}
info += $" |-> {geName}\n";
}

View File

@ -8,7 +8,7 @@ namespace GAS.Editor
using UnityEngine.UIElements;
public abstract class TrackMarkBase:TrackItemBase
{
protected static TimelineAbilityAssetBase AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
protected static TimelineAbilityAsset AbilityAsset => AbilityTimelineEditorWindow.Instance.AbilityAsset;
private static string MarkAssetGuid => "5a3b3360bcba29b4cac2875f518af19d";
public float FrameUnitWidth { get;protected set; }
public int StartFrameIndex=>markData.startFrame;

View File

@ -364,7 +364,7 @@ namespace GAS.Editor
listView.showFoldoutHeader = true;
listView.itemsSource = list;
listView.selectionType = SelectionType.Single;
listView.selectionChanged += onSelectionChanged;
listView.onSelectionChange += onSelectionChanged;
listView.itemsAdded += onItemsAdded;
listView.itemsRemoved += onItemsRemoved;

View File

@ -0,0 +1,50 @@

// #if UNITY_EDITOR
// namespace GAS.Editor
// {
// using Editor;
// using GAS.General;
// using Runtime;
// using UnityEngine;
// using UnityEngine.UIElements;
// using Sirenix.OdinInspector;
// public class CatchAreaBox2DInspector:TargetCatcherInspector<CatchAreaBox2D>
// {
// [BoxGroup] [Delayed] [OnValueChanged("OnCatcherChanged")]
// public Vector2 Offset;
// [BoxGroup] [Delayed] [OnValueChanged("OnCatcherChanged")]
// public Vector2 Size;
// [BoxGroup] [Delayed] [LabelText("Rotation")] [OnValueChanged("OnCatcherChanged")]
// public float Rotation;
// [BoxGroup] [Delayed] [LabelText("Detect Layer")] [OnValueChanged("OnCatcherChanged")]
// public LayerMask Layer;
// [BoxGroup] [Delayed] [LabelText("Center Type")] [OnValueChanged("OnCatcherChanged")]
// public EffectCenterType CenterType;
// public CatchAreaBox2DInspector(CatchAreaBox2D targetCatcherBase) : base(targetCatcherBase)
// {
// Offset = targetCatcherBase.offset;
// Size = targetCatcherBase.size;
// Rotation = targetCatcherBase.rotation;
// Layer = targetCatcherBase.checkLayer;
// CenterType = targetCatcherBase.centerType;
// }
// public void OnCatcherChanged()
// {
// _targetCatcher.offset = Offset;
// _targetCatcher.size = Size;
// _targetCatcher.rotation = Rotation;
// _targetCatcher.checkLayer = Layer;
// _targetCatcher.centerType = CenterType;
// Save();
// }
// }
// }
// #endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 16da9d76e7c940b5a560bb31dab5d588
timeCreated: 1709458583

View File

@ -24,8 +24,8 @@ namespace GAS.Editor
// AbilityTimelineEditorWindow.Instance.Save();
// }
//public abstract VisualElement Inspector();
//public abstract void OnTargetCatcherPreview(GameObject obj);
// public abstract VisualElement Inspector();
// public abstract void OnTargetCatcherPreview(GameObject obj);
}
public abstract class TargetCatcherInspector<T>:TargetCatcherInspector where T : TargetCatcherBase

View File

@ -1,3 +1,3 @@
fileFormatVersion: 2
guid: 8378e98772454500b5c36d8f4c14862d
timeCreated: 1729190435
guid: 45bb062aed8542afab83194d24ee85c8
timeCreated: 1717573129

View File

@ -0,0 +1,101 @@
using System;
using UnityEngine;
namespace GAS.Runtime
{
/// <summary>
/// Timeline轨道数据的扩展操作
/// </summary>
public static class TrackDataExtension
{
/// <summary>
/// 编辑器下使用,添加
/// </summary>
/// <param name="trackData"></param>
/// <param name="abilityAsset"></param>
/// <param name="subType"></param>
public static void AddToAbilityAsset(this TrackDataBase trackData, TimelineAbilityAsset abilityAsset, Type subType)
{
CommonAddToAbilityAsset(trackData, abilityAsset);
switch (trackData)
{
case BuffGameplayEffectTrackData buffTrackData:
abilityAsset.BuffGameplayEffects.Add(buffTrackData);
break;
case DurationalCueTrackData durationalCueTrackData:
abilityAsset.DurationalCues.Add(durationalCueTrackData);
break;
case InstantCueTrackData instCueTrackData:
abilityAsset.InstantCues.Add(instCueTrackData);
break;
case PassiveGameplayEffectTrackData passiveGETrackData:
abilityAsset.PassiveGameplayEffects.Add(passiveGETrackData);
break;
case PassiveTaskClipEventTrackData passiveTaskTrackData:
abilityAsset.PassiveTasks.Add(passiveTaskTrackData);
break;
case ReleaseGameplayEffectTrackData releaseGETrackData:
abilityAsset.ReleaseGameplayEffect.Add(releaseGETrackData);
break;
case TaskMarkEventTrackData eventTrackData:
abilityAsset.InstantTasks.Add(eventTrackData);
break;
case TaskClipEventTrackData clipTrackData:
abilityAsset.OngoingTasks.Add(clipTrackData);
break;
default:
Debug.LogError($"AddToAbilityAsset, 未知的轨道数据类型type = {trackData.GetType()}");
break;
}
}
/// <summary>
/// Timeline添加轨道数据的通用操作
/// </summary>
/// <param name="trackData"></param>
/// <param name="abilityAsset"></param>
public static void CommonAddToAbilityAsset(this TrackDataBase trackData, TimelineAbilityAsset abilityAsset)
{
// Do nothing有后续的通用操作在此添加
}
/// <summary>
/// 默认初始化TrackData编辑器下使用
/// </summary>
/// <param name="trackData"></param>
public static void DefaultInit(this TrackDataBase trackData)
{
switch (trackData)
{
case BuffGameplayEffectTrackData buffTrackData:
trackData.trackName = "BuffGE";
break;
case DurationalCueTrackData durationalCueTrackData:
trackData.trackName = "DurationalCue";
break;
case InstantCueTrackData instCueTrackData:
trackData.trackName = "InstantCue";
break;
case PassiveGameplayEffectTrackData passiveGETrackData:
trackData.trackName = "PassiveGE";
break;
case PassiveTaskClipEventTrackData passiveTaskTrackData:
trackData.trackName = "PassiveTask";
break;
case ReleaseGameplayEffectTrackData releaseGETrackData:
trackData.trackName = "ReleaseGE";
break;
case TaskMarkEventTrackData eventTrackData:
trackData.trackName = "TaskMark";
break;
case TaskClipEventTrackData clipTrackData:
trackData.trackName = "TaskClips";
break;
default:
Debug.LogError($"DefaultInit, 未知的轨道数据类型type = {trackData.GetType()}");
break;
}
}
}
}

View File

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

View File

@ -0,0 +1,59 @@
#if UNITY_EDITOR
using GAS.Runtime;
using JNGame.Serialization;
namespace GAS.Editor
{
public static class AbilitySystemComponentPresetConverter
{
public static byte[] ToBytes(this AbilitySystemComponentPreset preset)
{
Serializer serializer = new Serializer();
serializer.Write(preset.name);
ushort length = (ushort)(preset.attributeSets == null ? 0 : preset.attributeSets.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
serializer.Write(preset.attributeSets[i]);
}
serializer.WriteArray(preset.baseTags);
length = (ushort)(preset.baseAbilities == null ? 0 : preset.baseAbilities.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
serializer.Write(preset.baseAbilities[i] == null ? "" : preset.baseAbilities[i].name);
}
return serializer.CopyData();
}
/// <summary>
/// PureASCPresetAsset数据序列化为字节数据的工具函数
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public static byte[] Serialize(PureASCPresetAsset geAsset)
{
var writer = new Serializer();
geAsset.Serialize(writer);
return writer.CopyData();
}
/// <summary>
/// 字节数据反序列化为PureASCPresetAsset的工具函数
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static PureASCPresetAsset Deserialize(byte[] bytes)
{
var reader = new Deserializer(bytes);
var geAsset = new PureASCPresetAsset();
geAsset.Deserialize(reader);
return geAsset;
}
}
}
#endif

View File

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

View File

@ -3,8 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using GAS.Editor.General;
using GAS.Editor.General.Validation;
using GAS.General;
using GAS.General.Validation;
using Sirenix.OdinInspector;
using Sirenix.Utilities.Editor;
using UnityEditor;
@ -23,21 +23,28 @@ namespace GAS.Editor
[HorizontalGroup("A/R", order: 1)]
[DisplayAsString(TextAlignment.Left, FontSize = 18)]
[HideLabel]
[ValidateInput("@ExistDuplicatedAttribute() == false", GASTextDefine.ERROR_DuplicatedAttribute)]
[InfoBox(GASTextDefine.ERROR_DuplicatedAttribute, InfoMessageType.Error,
VisibleIf = "ExistDuplicatedAttribute")]
[InfoBox(GASTextDefine.ERROR_Empty, InfoMessageType.Error, VisibleIf = "EmptyAttribute")]
[InfoBox(GASTextDefine.ERROR_EmptyName, InfoMessageType.Error, VisibleIf = "EmptyAttributeSetName")]
public string Name = "Unnamed";
[Space]
[ListDrawerSettings(ShowFoldout = true, ShowIndexLabels = false, ShowItemCount = false, ShowPaging = false)]
[ListDrawerSettings(ShowFoldout = true, ShowIndexLabels = false, ShowItemCount = false, ShowPaging = false,
OnTitleBarGUI = "DrawAttributeNamesButtons")]
[ValueDropdown("AttributeChoices", IsUniqueList = true)]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@SortAttributeNames()")]
[LabelText("Attributes")]
[Searchable]
public List<string> AttributeNames = new();
public List<string> AttributeNames = new List<string>();
private void SortAttributeNames() => AttributeNames = AttributeNames.OrderBy(x => x).ToList();
private void DrawAttributeNamesButtons()
{
if (SirenixEditorGUI.ToolbarButton(SdfIconType.SortAlphaDown))
{
AttributeNames = AttributeNames.OrderBy(x => x).ToList();
ParentAsset.SaveAsset();
}
}
[HorizontalGroup("A", Width = 50)]
[HorizontalGroup("A/L", order: 0, Width = 50)]
@ -100,20 +107,24 @@ namespace GAS.Editor
[ListDrawerSettings(ShowFoldout = true,
CustomAddFunction = "OnAddAttributeSet",
CustomRemoveElementFunction = "OnRemoveElement",
CustomRemoveIndexFunction = "OnRemoveIndex")]
[DisableContextMenu(disableForMember: false, disableCollectionElements: true)]
[CustomContextMenu("排序", "@SortAttributeSetConfigs()")]
CustomRemoveIndexFunction = "OnRemoveIndex", OnTitleBarGUI = "DrawAttributeSetConfigsButtons")]
[Searchable]
public List<AttributeSetConfig> AttributeSetConfigs = new List<AttributeSetConfig>();
private void SortAttributeSetConfigs() => AttributeSetConfigs = AttributeSetConfigs.OrderBy(x => x.Name).ToList();
private void DrawAttributeSetConfigsButtons()
{
if (SirenixEditorGUI.ToolbarButton(SdfIconType.SortAlphaDown))
{
AttributeSetConfigs = AttributeSetConfigs.OrderBy(x => x.Name).ToList();
SaveAsset();
}
}
private void OnEnable()
{
AttributeSetConfig.ParentAsset = this;
var asset = AttributeAsset.LoadOrCreate();
var attributeChoices = asset != null ? (from attr in asset.attributes where !string.IsNullOrWhiteSpace(attr.Name) select attr.Name).ToList() : new();
AttributeSetConfig.SetAttributeChoices(attributeChoices);
AttributeSetConfig.SetAttributeChoices(asset?.AttributeNames);
}
public void SaveAsset()

View File

@ -1,14 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using GAS.Runtime;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
public static class AttributeSetClassGen
{
public static void Gen()
@ -16,8 +15,7 @@ namespace GAS.Editor
var attributeSetAsset = AttributeSetAsset.LoadOrCreate();
var attributeAsset = AttributeAsset.LoadOrCreate();
var attributeNames =
(from t in attributeAsset.attributes where !string.IsNullOrWhiteSpace(t.Name) select t.Name)
var attributeNames = (from t in attributeAsset.attributes where !string.IsNullOrWhiteSpace(t.Name) select t.Name)
.ToList();
// Check if AttributeSet contains attribute that is not defined in AttributeAsset
@ -27,7 +25,8 @@ namespace GAS.Editor
{
if (!attributeNames.Contains(attributeName))
{
var msg = $"Invalid Attribute(\"{attributeName}\") in AttributeSet(\"{attributeSetConfig.Name}\"), \"{attributeName}\" is not defined in AttributeAsset!";
var msg =
$"Invalid Attribute(\"{attributeName}\") in AttributeSet(\"{attributeSetConfig.Name}\"), \"{attributeName}\" is not defined in AttributeAsset!";
Debug.LogError(msg);
EditorUtility.DisplayDialog("Error", msg, "OK");
return;
@ -35,29 +34,13 @@ namespace GAS.Editor
}
}
var invalidAttributes = attributeAsset.attributes.Where(x =>
x.CalculateMode is CalculateMode.MinValueOnly or CalculateMode.MaxValueOnly &&
x.SupportedOperation != SupportedOperation.Override).ToArray();
if (invalidAttributes.Length > 0)
{
var msg =
$"计算模式为\"\"或\"取最大值\"的属性只能支持\"替换\"操作:\n{string.Join("\n", invalidAttributes.Select(x => $"\"{x.Name}\""))}";
Debug.LogError(msg.Replace("\n", ", "));
EditorUtility.DisplayDialog("Error", msg, "OK");
return;
}
string pathWithoutAssets = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
var filePath =
$"{pathWithoutAssets}/{GASSettingAsset.CodeGenPath}/{GasDefine.GAS_ATTRIBUTESET_LIB_CSHARP_SCRIPT_NAME}";
GenerateAttributeCollection(attributeSetAsset.AttributeSetConfigs, attributeAsset, filePath);
Console.WriteLine($"Generated Code Script at path: {filePath}");
GenerateAttributeCollection(attributeSetAsset.AttributeSetConfigs, filePath);
}
private static void GenerateAttributeCollection(List<AttributeSetConfig> attributeSetConfigs,
AttributeAsset attributeAsset, string filePath)
private static void GenerateAttributeCollection(List<AttributeSetConfig> attributeSetConfigs, string filePath)
{
using var writer = new IndentedWriter(new StreamWriter(filePath));
@ -70,26 +53,24 @@ namespace GAS.Editor
writer.WriteLine("using System;");
writer.WriteLine("using System.Collections.Generic;");
writer.WriteLine("using JNGame.Math;");
writer.WriteLine("using GAS.General;");
writer.WriteLine("");
writer.WriteLine("namespace GAS.Runtime");
writer.WriteLine("{");
writer.Indent++;
{
foreach (var attributeSetConfig in attributeSetConfigs.OrderBy(x => x.Name))
foreach (var attributeSet in attributeSetConfigs)
{
var validName = EditorUtil.MakeValidIdentifier(attributeSetConfig.Name);
var validName = EditorUtil.MakeValidIdentifier(attributeSet.Name);
writer.WriteLine($"public class AS_{validName} : AttributeSet");
writer.WriteLine("{");
writer.Indent++;
{
bool skippedFirst = false;
foreach (var attributeName in attributeSetConfig.AttributeNames.OrderBy(x => x))
foreach (var attributeName in attributeSet.AttributeNames)
{
var attributeAccessor = attributeAsset.attributes.Find(x => x.Name == attributeName);
if (!skippedFirst) skippedFirst = true;
else writer.WriteLine("");
@ -97,15 +78,46 @@ namespace GAS.Editor
writer.WriteLine($"#region {attributeName}");
writer.WriteLine("");
{
writer.WriteLine($"/// <summary>{attributeAccessor.Comment}</summary>");
writer.WriteLine($"public AttributeBase {validAttrName} {{ get; }} = new(\"AS_{validName}\", \"{attributeName}\", new LFloat(true,{attributeAccessor.DefaultValue.rawValue}), {(attributeAccessor.LimitMinValue ? $"new LFloat(true,{attributeAccessor.MinValue.rawValue})" : "LFloat.MinValue")}, {(attributeAccessor.LimitMaxValue ? $"new LFloat(true,{attributeAccessor.MaxValue.rawValue})" : "LFloat.MaxValue")}, CalculateMode.{attributeAccessor.CalculateMode}, (SupportedOperation){(byte)attributeAccessor.SupportedOperation});");
writer.WriteLine(
$"private AttributeBase _{validAttrName} = new AttributeBase(\"AS_{validName}.{attributeName}\",\"AS_{validName}\", \"{attributeName}\");");
writer.WriteLine("");
writer.WriteLine($"public void Init{validAttrName}(LFloat value) => {validAttrName}.Init(value);");
writer.WriteLine($"public void SetCurrent{validAttrName}(LFloat value) => {validAttrName}.SetCurrentValue(value);");
writer.WriteLine($"public void SetBase{validAttrName}(LFloat value) => {validAttrName}.SetBaseValue(value);");
writer.WriteLine($"public void SetMin{validAttrName}(LFloat value) => {validAttrName}.SetMinValue(value);");
writer.WriteLine($"public void SetMax{validAttrName}(LFloat value) => {validAttrName}.SetMaxValue(value);");
writer.WriteLine($"public void SetMinMax{validAttrName}(LFloat min, LFloat max) => {validAttrName}.SetMinMaxValue(min, max);");
writer.WriteLine($"public AttributeBase {validAttrName} => _{validAttrName};");
writer.WriteLine("");
writer.WriteLine($"public void Init{validAttrName}(int value)");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine($"_{validAttrName}.SetBaseValue(value);");
writer.WriteLine($"_{validAttrName}.SetCurrentValue(value);");
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("");
writer.WriteLine($"public void SetCurrent{validAttrName}(int value)");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine($"_{validAttrName}.SetCurrentValue(value);");
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("");
writer.WriteLine($"public void SetBase{validAttrName}(int value)");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine($"_{validAttrName}.SetBaseValue(value);");
}
writer.Indent--;
writer.WriteLine("}");
}
writer.WriteLine("");
writer.WriteLine($"#endregion {attributeName}");
@ -125,13 +137,13 @@ namespace GAS.Editor
writer.WriteLine("{");
writer.Indent++;
{
foreach (var attributeName in attributeSetConfig.AttributeNames)
foreach (var attributeName in attributeSet.AttributeNames)
{
string validAttrName = EditorUtil.MakeValidIdentifier(attributeName);
writer.WriteLine($"case \"{validAttrName}\":");
writer.Indent++;
{
writer.WriteLine($"return {validAttrName};");
writer.WriteLine($"return _{validAttrName};");
}
writer.Indent--;
}
@ -153,7 +165,7 @@ namespace GAS.Editor
writer.WriteLine("{");
writer.Indent++;
{
foreach (var attributeName in attributeSetConfig.AttributeNames)
foreach (var attributeName in attributeSet.AttributeNames)
{
string validAttrName = EditorUtil.MakeValidIdentifier(attributeName);
writer.WriteLine($"\"{validAttrName}\",");
@ -168,10 +180,10 @@ namespace GAS.Editor
writer.Indent++;
{
writer.WriteLine("_owner = owner;");
foreach (var attributeName in attributeSetConfig.AttributeNames)
foreach (var attributeName in attributeSet.AttributeNames)
{
string validAttrName = EditorUtil.MakeValidIdentifier(attributeName);
writer.WriteLine($"{validAttrName}.SetOwner(owner);");
writer.WriteLine($"_{validAttrName}.SetOwner(owner);");
}
}
writer.Indent--;
@ -182,15 +194,16 @@ namespace GAS.Editor
writer.WriteLine("{");
writer.Indent++;
{
foreach (var attributeName in attributeSetConfig.AttributeNames)
foreach (var attributeName in attributeSet.AttributeNames)
{
string validAttrName = EditorUtil.MakeValidIdentifier(attributeName);
writer.WriteLine(
$"public const string {attributeName} = \"AS_{validName}.{validAttrName}\";");
writer.WriteLine($"public const string {attributeName} = \"AS_{validName}.{validAttrName}\";");
}
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine($"public static readonly string SetName = \"AS_{validName}\";");
}
writer.Indent--;
writer.WriteLine("}");
@ -201,7 +214,8 @@ namespace GAS.Editor
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public static readonly IReadOnlyDictionary<string, Type> AttrSetTypeDict = new Dictionary<string, Type>");
writer.WriteLine(
"public static readonly Dictionary<string, Type> AttrSetTypeDict = new Dictionary<string, Type>()");
writer.WriteLine("{");
writer.Indent++;
{
@ -216,7 +230,8 @@ namespace GAS.Editor
writer.WriteLine("");
writer.WriteLine("public static readonly IReadOnlyDictionary<Type, string> TypeToName = new Dictionary<Type, string>");
writer.WriteLine(
"public static readonly Dictionary<Type, string> TypeToName = new Dictionary<Type, string>");
writer.WriteLine("{");
writer.Indent++;
{
@ -229,9 +244,32 @@ namespace GAS.Editor
writer.Indent--;
writer.WriteLine("};");
// writer.WriteLine("");
// writer.WriteLine(
// "public static AttributeSet GetSetInstance(string attrSetType)");
// writer.WriteLine("{");
// writer.WriteLine(" if (attrSetType.Contains(\"AS_Fight\"))");
// writer.WriteLine("{");
// writer.WriteLine("return ObjectPool.Instance.Fetch<AS_Fight>();");
// writer.WriteLine("}");
// writer.WriteLine("else if(attrSetType.Contains(\"AS_BaseAttr\"))");
// writer.WriteLine("{");
// writer.WriteLine("return ObjectPool.Instance.Fetch<AS_BaseAttr>();");
// writer.WriteLine("}");
// writer.WriteLine("return null;");
// writer.WriteLine("}");
writer.WriteLine("");
writer.WriteLine(
"public static void RecycleSetInstance(object obj)");
writer.WriteLine("{");
writer.WriteLine(" ObjectPool.Instance.Recycle(obj);");
writer.WriteLine("}");
writer.WriteLine("");
writer.WriteLine("public static readonly IReadOnlyList<string> AttributeFullNames = new List<string>");
writer.WriteLine("public static List<string> AttributeFullNames = new List<string>()");
writer.WriteLine("{");
writer.Indent++;
{
@ -245,14 +283,27 @@ namespace GAS.Editor
}
}
}
writer.Indent--;
writer.WriteLine("};");
writer.WriteLine("");
// writer.WriteLine("public static string GetName(string setName, string shortName)");
// writer.WriteLine("{");
// writer.Indent++;
// {
// }
// writer.Indent--;
// writer.WriteLine("};");
}
writer.Indent--;
writer.WriteLine("}");
}
writer.Indent--;
writer.Write("}");
Console.WriteLine($"Generated Code Script at path: {filePath}");
}
}
}
}
#endif

View File

@ -1,11 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using GAS;
using Editor;
using Runtime;
using UnityEditor;
using UnityEngine;
public class AttributeSetConfigEditorWindow : EditorWindow
{
private static List<string> _attributeOptions;
@ -17,7 +20,7 @@ namespace GAS.Editor
private Action<string, List<string>> _callback;
private Func<AttributeSetConfig, bool> _checkAttributeSetValid;
private List<int> _selectedAttributeIndexes;
private List<int> _selectedAttributeIndexs;
private GUIStyle BigFontLabelStyle;
@ -28,7 +31,7 @@ namespace GAS.Editor
if (_attributeOptions == null)
{
var asset = AttributeAsset.LoadOrCreate();
_attributeOptions = asset != null ? (from attr in asset.attributes where !string.IsNullOrWhiteSpace(attr.Name) select attr.Name).OrderBy(x => x).ToList() : new();
_attributeOptions = asset?.AttributeNames;
}
return _attributeOptions;
@ -57,7 +60,7 @@ namespace GAS.Editor
if (GUILayout.Button("Add Attribute", GUILayout.Width(100)))
{
attributeNames.Add("");
_selectedAttributeIndexes.Add(-1);
_selectedAttributeIndexs.Add(-1);
}
EditorGUILayout.Space();
@ -69,13 +72,13 @@ namespace GAS.Editor
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Attribute:", GUILayout.Width(80));
_selectedAttributeIndexes[i] = EditorGUILayout.Popup("", _selectedAttributeIndexes[i],
_selectedAttributeIndexs[i] = EditorGUILayout.Popup("", _selectedAttributeIndexs[i],
AttributeOptions.ToArray());
// 更新选中的字符串
attributeNames[i] = _selectedAttributeIndexes[i] < 0
attributeNames[i] = _selectedAttributeIndexs[i] < 0
? ""
: AttributeOptions[_selectedAttributeIndexes[i]];
: AttributeOptions[_selectedAttributeIndexs[i]];
if (GUILayout.Button("Remove", GUILayout.Width(100)))
{
@ -101,9 +104,9 @@ namespace GAS.Editor
_callback = callback;
_checkAttributeSetValid = checkAttributeSetValid;
this.attributeNames = attributeNames;
_selectedAttributeIndexes = new List<int>();
_selectedAttributeIndexs = new List<int>();
foreach (var attributeName in attributeNames)
_selectedAttributeIndexes.Add(AttributeOptions.IndexOf(attributeName));
_selectedAttributeIndexs.Add(AttributeOptions.IndexOf(attributeName));
BigFontLabelStyle = new GUIStyle(EditorStyles.label);
@ -112,7 +115,7 @@ namespace GAS.Editor
private void Save()
{
var attributeSetConfig = new AttributeSetConfig()
AttributeSetConfig attributeSetConfig = new AttributeSetConfig()
{
Name = editedName,
AttributeNames = attributeNames
@ -130,4 +133,5 @@ namespace GAS.Editor
Save();
}
}
}
}
#endif

View File

@ -1,48 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using GAS.Editor.General;
using GAS.General;
using GAS.General.Validation;
using GAS.Runtime;
using JNGame.Math;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;

#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
using GAS.General;
using Sirenix.Utilities.Editor;
[FilePath(GasDefine.GAS_ATTRIBUTE_ASSET_PATH)]
internal class AttributeAsset : ScriptableSingleton<AttributeAsset>
public class AttributeAsset : ScriptableSingleton<AttributeAsset>
{
[BoxGroup("Warning", order: -1)]
[BoxGroup("Warning", order: -1)]
[HideLabel]
[ShowIf("ExistDuplicatedAttribute")]
[DisplayAsString(TextAlignment.Left, true)]
[NonSerialized]
[ShowInInspector]
private string Warning_DuplicatedAttribute = "";
public string Warning_DuplicatedAttribute = "";
[VerticalGroup("Attributes", order: 1)]
[ListDrawerSettings(
ShowFoldout = true,
Expanded = true,
CustomRemoveElementFunction = "OnRemoveElement",
CustomRemoveIndexFunction = "OnRemoveIndex",
CustomAddFunction = "OnAddAttribute",
ShowPaging = false)]
ShowPaging = false, OnTitleBarGUI = "DrawAttributeButtons")]
[Searchable]
[OnValueChanged("@SaveAsset()", true)]
[OnCollectionChanged(after: "@SaveAsset()")]
[CustomContextMenu("排序", "@SortAttributes()")]
[SerializeField]
public List<AttributeAccessor> attributes = new();
[OnValueChanged("Save")]
public List<AttributeAccessor> attributes = new List<AttributeAccessor>();
private void DrawAttributeButtons()
{
if (SirenixEditorGUI.ToolbarButton(SdfIconType.SortAlphaDown))
{
attributes = attributes.OrderBy(x => x.Name).ToList();
}
}
private void SortAttributes() => attributes = attributes.OrderBy(x => x.Name).ToList();
public List<string> AttributeNames =>
(from attr in attributes where !string.IsNullOrEmpty(attr.Name) select attr.Name).ToList();
private void OnEnable()
{
AttributeAccessor.ParentAsset = this;
}
[VerticalGroup("Gen Code", order: 0)]
[GUIColor(0, 0.9f, 0)]
[Button(SdfIconType.Upload, GASTextDefine.BUTTON_GenerateAttributeCollection, ButtonHeight = 30, Expanded = true)]
[ValidateInput("@ExistEmptyAttribute() == false", GASTextDefine.TIP_Warning_EmptyAttribute)]
[Button(SdfIconType.Upload, GASTextDefine.BUTTON_GenerateAttributeCollection, ButtonHeight = 30,
Expanded = true)]
[InfoBox(GASTextDefine.TIP_Warning_EmptyAttribute, InfoMessageType.Error, VisibleIf = "ExistEmptyAttribute")]
void GenCode()
{
if (ExistEmptyAttribute() || ExistDuplicatedAttribute())
@ -95,27 +104,12 @@ namespace GAS.Editor
private void OnAddAttribute()
{
StringEditWindow.OpenWindow("创建新属性", null, newName =>
AttributeEditorWindow.OpenWindow(new AttributeEditorWindow.Data(), AttributeNames, (d =>
{
var validateVariableName = Validations.ValidateVariableName(newName);
if (validateVariableName.IsValid == false)
{
return validateVariableName;
}
if (attributes.Exists(x => x.Name == newName))
{
return ValidationResult.Invalid($"属性名已存在: \"{newName}\"!");
}
return ValidationResult.Valid;
}, x =>
{
attributes.Add(new AttributeAccessor { Name = x });
attributes.Add(new AttributeAccessor(d.Name, d.Comment));
SaveAsset();
Debug.Log("[EX] Attribute Asset add element!");
});
}), "Add new Attribute");
GUIUtility.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
}
@ -145,85 +139,42 @@ namespace GAS.Editor
}
[Serializable]
internal sealed class AttributeAccessor
public class AttributeAccessor
{
private const int LabelWidth = 100;
public static AttributeAsset ParentAsset;
private string DisplayName => $"{Name} - {Comment}";
[HorizontalGroup("A")] [HorizontalGroup("A/R", order: 1)] [DisplayAsString] [HideLabel]
public string Name;
[FoldoutGroup("$DisplayName", false)]
[LabelText("属性名"), LabelWidth(LabelWidth)]
[DelayedProperty]
[ValidateInput("@Validations.IsValidVariableName($value)", "Attribute name is invalid!")]
[PropertyOrder(1)]
public string Name = "Unnamed";
[HorizontalGroup("A")] [HorizontalGroup("A/R", order: 3)] [DisplayAsString] [HideLabel]
public string Comment;
[FoldoutGroup("$DisplayName")]
[DelayedProperty]
[LabelText("备注"), LabelWidth(LabelWidth)]
[PropertyOrder(2)]
public string Comment = "";
[FoldoutGroup("$DisplayName")]
[LabelText("计算模式"), LabelWidth(LabelWidth)]
[PropertyOrder(3)]
[OnValueChanged("@OnCalculateModeChanged()")]
[HorizontalGroup("$DisplayName/d")]
public CalculateMode CalculateMode = CalculateMode.Stacking;
private void OnCalculateModeChanged()
public AttributeAccessor(string attributeName, string attributeComment = "")
{
if (CalculateMode is CalculateMode.MinValueOnly or CalculateMode.MaxValueOnly)
{
SupportedOperation = SupportedOperation.Override;
}
Name = string.IsNullOrWhiteSpace(attributeName) ? "Unnamed" : attributeName;
Comment = string.IsNullOrWhiteSpace(attributeComment) ? "" : attributeComment;
}
[FoldoutGroup("$DisplayName")]
[LabelText("支持运算"), LabelWidth(LabelWidth)]
[PropertyOrder(4)]
[DisableIf(
"@CalculateMode == GAS.Runtime.CalculateMode.MinValueOnly || CalculateMode == GAS.Runtime.CalculateMode.MaxValueOnly")]
[HorizontalGroup("$DisplayName/d")]
public SupportedOperation SupportedOperation = SupportedOperation.All;
[HorizontalGroup("A", Width = 50)]
[HorizontalGroup("A/L", order: 0, Width = 50)]
[Button(SdfIconType.Brush, "", ButtonHeight = 25)]
public void Edit()
{
if (ParentAsset == null) return;
[FoldoutGroup("$DisplayName")]
[LabelText("默认值"), LabelWidth(LabelWidth)]
[DelayedProperty]
[PropertyOrder(5)]
[HorizontalGroup("$DisplayName/Values")]
public LFloat DefaultValue = LFloat.L0;
[FoldoutGroup("$DisplayName")]
[LabelText("最小值"), LabelWidth(40)]
[DelayedProperty]
[PropertyOrder(6)]
[HorizontalGroup("$DisplayName/Values")]
[ToggleLeft]
public bool LimitMinValue = false;
[FoldoutGroup("$DisplayName")]
[HideLabel]
[DelayedProperty]
[PropertyOrder(6)]
[EnableIf("LimitMinValue")]
[HorizontalGroup("$DisplayName/Values")]
public LFloat MinValue = LFloat.MinValue;
[FoldoutGroup("$DisplayName")]
[LabelText("最大值"), LabelWidth(50)]
[DelayedProperty]
[PropertyOrder(6)]
[HorizontalGroup("$DisplayName/Values")]
public bool LimitMaxValue = false;
[FoldoutGroup("$DisplayName")]
[HideLabel]
[DelayedProperty]
[PropertyOrder(7)]
[EnableIf("LimitMaxValue")]
[HorizontalGroup("$DisplayName/Values")]
public LFloat MaxValue = LFloat.MaxValue;
var nameBlackList = ParentAsset.AttributeNames.Where(x => x != Name);
AttributeEditorWindow.OpenWindow(new AttributeEditorWindow.Data(Name, Comment), nameBlackList,
x =>
{
if (ParentAsset != null)
{
Name = x.Name;
Comment = x.Comment;
ParentAsset.SaveAsset();
}
}, "Edit Attribute Asset");
}
}
}
}
}
#endif

View File

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
public static class AttributeCollectionGen
{
private sealed class AttributeInfo
@ -28,8 +30,6 @@ namespace GAS.Editor
GenerateAttributeCollection(attributeInfos, filePath);
Console.WriteLine($"Generated GTagLib at path: {filePath}");
}
private static void GenerateAttributeCollection(IEnumerable<AttributeInfo> attributes, string filePath)
@ -63,7 +63,9 @@ namespace GAS.Editor
else writer.WriteLine();
var validName = EditorUtil.MakeValidIdentifier(attr.Name);
writer.WriteLine($"/// <summary>{attr.Comment}</summary>");
writer.WriteLine("/// <summary>");
writer.WriteLine($"/// {attr.Comment}");
writer.WriteLine("/// </summary>");
writer.WriteLine($"public const string {validName} = \"{attr.Name}\";");
names.Add(validName);
}
@ -71,13 +73,13 @@ namespace GAS.Editor
writer.WriteLine("");
writer.WriteLine("// For facilitating the creation of a Value Dropdown in the editor.");
writer.WriteLine("public static readonly IReadOnlyList<string> AttributeNames = new List<string>");
writer.WriteLine("public static List<string> AttributeNames = new List<string>()");
writer.WriteLine("{");
writer.Indent++;
{
foreach (var name in names)
{
writer.WriteLine($"{name},");
writer.WriteLine($"\"{name}\",");
}
}
writer.Indent--;
@ -88,6 +90,9 @@ namespace GAS.Editor
}
writer.Indent--;
writer.Write("}");
Console.WriteLine($"Generated GTagLib at path: {filePath}");
}
}
}
}
#endif

View File

@ -0,0 +1,107 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GAS.Editor.General.Validation;
using UnityEditor;
using UnityEngine;
namespace GAS.Editor
{
public sealed class AttributeEditorWindow : EditorWindow
{
public sealed class Data
{
public string Name;
public string Comment;
public Data(string name = null, string comment = null)
{
Name = name ?? "";
Comment = comment ?? "He is very lazy and left nothing behind.";
}
public override string ToString()
{
return $"Name: {Name}, Comment: {Comment}";
}
}
Data _oldData;
Data _newData;
private IEnumerable<string> _nameBlackList;
private Action<Data> _callback;
public static void OpenWindow(Data data, IEnumerable<string> nameBlackList,
Action<Data> callback, string title = "AttributeEditorWindow")
{
var window = GetWindow<AttributeEditorWindow>();
window.Init(data, nameBlackList, callback);
window.titleContent = new GUIContent(title);
window.ShowModalUtility();
}
private void Init(Data data, IEnumerable<string> nameBlackList,
Action<Data> callback)
{
_oldData = data;
_newData = new Data(data.Name, data.Comment);
_nameBlackList = nameBlackList;
_callback = callback;
}
private void OnGUI()
{
_newData.Name = EditorGUILayout.TextField($"Attribute Name:", _newData.Name);
_newData.Comment = EditorGUILayout.TextField($"Comment:", _newData.Comment);
EditorGUILayout.Space();
if (GUILayout.Button("Ok"))
{
Save();
}
}
private void Save()
{
// name is empty means create a new attribute
// if name is not empty, show the warning dialog
if (_oldData.Name != "" && (_oldData.Name != _newData.Name || _oldData.Comment != _newData.Comment))
{
var sb = new StringBuilder();
if (_oldData.Name != _newData.Name)
{
sb.AppendLine($"- name: \"{_oldData.Name}\" => \"{_newData.Name}\"");
}
if (_oldData.Comment != _newData.Comment)
{
sb.AppendLine($"- comment: \"{_oldData.Comment}\" => \"{_newData.Comment}\"");
}
if (!EditorUtility.DisplayDialog("The following changes will be made:", sb.ToString(), "Yes", "No"))
{
return;
}
}
var validationResult = Validations.ValidateVariableName(_newData.Name);
if (!validationResult.IsValid)
{
EditorUtility.DisplayDialog("Name error", validationResult.Message, "OK");
return;
}
if (_nameBlackList.Contains(_newData.Name))
{
EditorUtility.DisplayDialog("Name error", "The name already exists!", "OK");
return;
}
_callback?.Invoke(_newData);
Close();
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 1bf0629a3e1f4cba9b8040ae65a6ea2b
timeCreated: 1711509167

View File

@ -23,42 +23,42 @@ namespace GAS.Editor
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Attribute Name:", GUILayout.Width(100));
var indexOfTag = _attributeOptions.IndexOf(_sourceModifier.AttributeName);
var indexOfTag = _attributeOptions.IndexOf(_sourceModifier.attributeName);
var idx = EditorGUILayout.Popup("", indexOfTag, _attributeOptions.ToArray());
idx = Mathf.Clamp(idx, 0, _attributeOptions.Count - 1);
_sourceModifier.AttributeName = _attributeOptions[idx];
var nameSplit = _sourceModifier.AttributeName.Split('.');
_sourceModifier.AttributeSetName = nameSplit[0];
_sourceModifier.AttributeShortName = nameSplit[1];
_sourceModifier.attributeName = _attributeOptions[idx];
var nameSplit = _sourceModifier.attributeName.Split('.');
_sourceModifier.attributeSetName = nameSplit[0];
_sourceModifier.attributeShortName = nameSplit[1];
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Operation:", GUILayout.Width(100));
_sourceModifier.Operation = (GEOperation)EditorGUILayout.EnumPopup("", _sourceModifier.Operation);
_sourceModifier.operation = (EnumGEOperation)EditorGUILayout.EnumPopup("", _sourceModifier.operation);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField("Value:", GUILayout.Width(100));
_sourceModifier.ModiferMagnitude = EditorGUILayout.FloatField("", _sourceModifier.ModiferMagnitude).ToLFloat();
_sourceModifier.modiferMagnitude = (LFloat)EditorGUILayout.FloatField("", _sourceModifier.modiferMagnitude);
EditorGUILayout.EndHorizontal();
EditorGUILayout.Space();
EditorGUILayout.BeginVertical();
EditorGUILayout.LabelField("Modifier Magnitude Calculation:", GUILayout.Width(200));
_sourceModifier.MMC = (ModifierMagnitudeCalculation)EditorGUILayout.ObjectField("", _sourceModifier.MMC,
_sourceModifier.mmc = (ModifierMagnitudeCalculation)EditorGUILayout.ObjectField("", _sourceModifier.mmc,
typeof(ModifierMagnitudeCalculation), false);
EditorGUILayout.EndVertical();
if (_sourceModifier.MMC != null)
if (_sourceModifier.mmc != null)
{
EditorGUILayout.BeginVertical(GUI.skin.box);
EditorGUILayout.LabelField("MMC Info:");
var editor = UnityEditor.Editor.CreateEditor(_sourceModifier.MMC);
var editor = UnityEditor.Editor.CreateEditor(_sourceModifier.mmc);
editor.OnInspectorGUI();
EditorGUILayout.EndVertical();
}

View File

@ -0,0 +1,100 @@
using GAS.Runtime;
using UnityEditor;
namespace GAS.Editor
{
public static class ForEeachScriptableObject
{
// [MenuItem("EX-GAS/Refresh Ability Asset")]
// public static void ForEachAbilityAsset()
// {
// string[] guids = AssetDatabase.FindAssets("t:AbilityAsset");
// if (guids == null || guids.Length == 0) { return; }
// foreach (string guid in guids)
// {
// string path = AssetDatabase.GUIDToAssetPath(guid);
// AbilityAsset abilityAsset = AssetDatabase.LoadAssetAtPath<AbilityAsset>(path);
// EditorUtility.SetDirty(abilityAsset);
// }
// AssetDatabase.Refresh();
// AssetDatabase.SaveAssets();
// }
// [MenuItem("EX-GAS/Refresh GameplayCue Asset")]
// public static void ForEachGameplayCue()
// {
// string[] guids = AssetDatabase.FindAssets("t:GameplayCue");
// if (guids == null || guids.Length == 0) { return; }
// foreach (string guid in guids)
// {
// string path = AssetDatabase.GUIDToAssetPath(guid);
// GameplayCue cueAsset = AssetDatabase.LoadAssetAtPath<GameplayCue>(path);
// EditorUtility.SetDirty(cueAsset);
// }
// AssetDatabase.Refresh();
// AssetDatabase.SaveAssets();
// }
// [MenuItem("EX-GAS/Refresh GameplayEffect Asset")]
// public static void ForEachGameplayEffectAsset()
// {
// string[] guids = AssetDatabase.FindAssets("t:GameplayEffectAsset");
// if (guids == null || guids.Length == 0) { return; }
// foreach (string guid in guids)
// {
// string path = AssetDatabase.GUIDToAssetPath(guid);
// GameplayEffectAsset gameplayEffectAsset = AssetDatabase.LoadAssetAtPath<GameplayEffectAsset>(path);
// EditorUtility.SetDirty(gameplayEffectAsset);
// }
// AssetDatabase.Refresh();
// AssetDatabase.SaveAssets();
// }
// [MenuItem("EX-GAS/Refresh MMC Asset")]
// public static void ForEachMMCAsset()
// {
// string[] guids = AssetDatabase.FindAssets("t:ModifierMagnitudeCalculation");
// if (guids == null || guids.Length == 0) { return; }
// foreach (string guid in guids)
// {
// string path = AssetDatabase.GUIDToAssetPath(guid);
// ModifierMagnitudeCalculation mmcAsset = AssetDatabase.LoadAssetAtPath<ModifierMagnitudeCalculation>(path);
// EditorUtility.SetDirty(mmcAsset);
// }
// AssetDatabase.Refresh();
// AssetDatabase.SaveAssets();
// }
// [MenuItem("EX-GAS/Refresh AscPreset Asset")]
// public static void ForEachAscPreset()
// {
// string[] guids = AssetDatabase.FindAssets("t:AbilitySystemComponentPreset");
// if (guids == null || guids.Length == 0) { return; }
// foreach (string guid in guids)
// {
// string path = AssetDatabase.GUIDToAssetPath(guid);
// AbilitySystemComponentPreset mmcAsset = AssetDatabase.LoadAssetAtPath<AbilitySystemComponentPreset>(path);
// EditorUtility.SetDirty(mmcAsset);
// }
// AssetDatabase.Refresh();
// AssetDatabase.SaveAssets();
// }
// [MenuItem("EX-GAS/Refresh All GAS Asset")]
// public static void ForEachGASAll()
// {
// ForEachAbilityAsset();
// ForEachGameplayCue();
// ForEachGameplayEffectAsset();
// ForEachMMCAsset();
// ForEachAscPreset();
// }
}
}

View File

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

View File

@ -1,16 +1,16 @@
using System;
using System.IO;
using System.Linq;
using UnityEditorInternal;
using UnityEngine;
using Object = UnityEngine.Object;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditorInternal;
using UnityEngine;
public class ScriptableSingleton<T> : ScriptableObject where T : ScriptableObject
{
private static T s_Instance;
public static T Instance
{
get
@ -19,24 +19,21 @@ namespace GAS.Editor
{
LoadOrCreate();
}
return s_Instance;
}
}
public static T LoadOrCreate()
{
string filePath = GetFilePath();
if (!string.IsNullOrEmpty(filePath))
{
var arr = InternalEditorUtility.LoadSerializedFileAndForget(filePath);
s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance ? s_Instance : CreateInstance<T>();
s_Instance = arr.Length > 0 ? arr[0] as T : s_Instance??CreateInstance<T>();
}
else
{
Debug.LogError($"save location of {nameof(ScriptableSingleton<T>)} is invalid");
}
return s_Instance;
}
@ -52,18 +49,11 @@ namespace GAS.Editor
if (!string.IsNullOrEmpty(filePath))
{
string directoryName = Path.GetDirectoryName(filePath);
if (directoryName is null)
{
Debug.LogError($"save location of {nameof(ScriptableSingleton<T>)} is invalid");
return;
}
if (!Directory.Exists(directoryName))
{
Directory.CreateDirectory(directoryName);
}
Object[] obj = { s_Instance };
UnityEngine.Object[] obj = new T[1] { s_Instance };
InternalEditorUtility.SaveToSerializedFileAndForget(obj, filePath, saveAsText);
//Debug.Log($"Saved ScriptableSingleton to {filePath}");
}
@ -74,7 +64,7 @@ namespace GAS.Editor
if (asset == null) return;
s_Instance = asset;
}
protected static string GetFilePath()
{
return typeof(T).GetCustomAttributes(inherit: true)
@ -84,12 +74,10 @@ namespace GAS.Editor
?.filepath;
}
}
[AttributeUsage(AttributeTargets.Class)]
public class FilePathAttribute : Attribute
{
internal string filepath;
/// <summary>
/// 单例存放路径
/// </summary>
@ -100,13 +88,12 @@ namespace GAS.Editor
{
throw new ArgumentException("Invalid relative path (it is empty)");
}
if (path[0] == '/')
{
path = path[1..];
path = path.Substring(1);
}
filepath = path;
}
}
}
}
#endif

View File

@ -0,0 +1,278 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using GAS.Runtime;
using JNGame.Serialization;
namespace GAS.Editor
{
public static class GameplayAbilityAssetConverter
{
internal static RuntimeClipNode ConvertRuntimeClipNode(this DurationalCueClipEvent clipNode)
{
return new DurationalCueClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = clipNode.durationFrame,
cueAssetLocation = clipNode.cue == null ? "" : clipNode.cue.name
};
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this InstantCueMarkEvent clipNode)
{
InstantCueClipNode runtimeClipNode = new InstantCueClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = 0,
cueAssetLocations = new string[clipNode.cues.Count],
};
for (int i = 0; i < clipNode.cues.Count; ++i)
{
runtimeClipNode.cueAssetLocations[i] = clipNode.cues[i].name;
}
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this ReleaseGameplayEffectMarkEvent clipNode)
{
InstantGEClipNode runtimeClipNode = new InstantGEClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = 0,
targetCatcher = clipNode.TargetCatcher,
linkGEAssets = new LinkGEAsset[clipNode.gameplayEffectAssets.Count],
};
for (int i = 0; i < clipNode.gameplayEffectAssets.Count; ++i)
{
runtimeClipNode.linkGEAssets[i] = new LinkGEAsset()
{
valueId = clipNode.gameplayEffectAssets[i].id,
linkAssetLocation = clipNode.gameplayEffectAssets[i].asset == null ? "" : clipNode.gameplayEffectAssets[i].asset.name,
};
}
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this BuffGameplayEffectClipEvent clipNode)
{
DurationalGEClipNode runtimeClipNode = new DurationalGEClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = clipNode.durationFrame,
linkGEAsset = new LinkGEAsset
{
valueId = clipNode.gameplayEffectValueId,
linkAssetLocation = clipNode.gameplayEffect == null ? "" : clipNode.gameplayEffect.name,
}
};
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this TaskMarkEvent clipNode, int index)
{
InstantTaskClipNode runtimeClipNode = new InstantTaskClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = 0,
instantTask = clipNode.instantTasks[index].CreateTask(null), // 仅做转换并序列化使用
};
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this TaskClipEvent clipNode)
{
DurationalTaskClipNode runtimeClipNode = new DurationalTaskClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = clipNode.durationFrame,
ongoingTask = clipNode.ongoingTask.CreateTask(null),
};
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this PassiveGameplayEffectClipEvent clipNode)
{
PassiveGEClipNode runtimeClipNode = new PassiveGEClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = clipNode.durationFrame,
linkGEAsset = new LinkGEAsset()
{
valueId = clipNode.gameplayEffectValueId,
linkAssetLocation = clipNode.gameplayEffect == null ? "" : clipNode.gameplayEffect.name,
}
};
return runtimeClipNode;
}
internal static RuntimeClipNode ConvertRuntimeClipNode(this PassiveTaskClipEvent clipNode)
{
PassiveTaskClipNode runtimeClipNode = new PassiveTaskClipNode
{
startFrame = clipNode.startFrame,
durationalFrame = clipNode.durationFrame,
passiveTask = clipNode.passiveTask.CreateTask(null),
};
return runtimeClipNode;
}
/// <summary>
/// 序列化TimelineAbilityAsset
/// </summary>
/// <param name="timelineAbilityAsset"></param>
/// <returns></returns>
public static byte[] ToBytes(this TimelineAbilityAsset timelineAbilityAsset)
{
Serializer serializer = new Serializer();
// 序列化TimlineAbilityAsset的Ability属性配置
serializer.Write(timelineAbilityAsset.Name);
serializer.Write(timelineAbilityAsset.UniqueName);
serializer.Write(timelineAbilityAsset.SkillId);
serializer.Write(timelineAbilityAsset.Cost == null ? "" : timelineAbilityAsset.Cost.Name);
serializer.Write(timelineAbilityAsset.CooldownTime);
serializer.WriteArray(timelineAbilityAsset.AssetTags);
serializer.WriteArray(timelineAbilityAsset.CancelAbilityTags);
serializer.WriteArray(timelineAbilityAsset.BlockAbilityTags);
serializer.WriteArray(timelineAbilityAsset.ActivationOwnedTags);
serializer.WriteArray(timelineAbilityAsset.ActivationRequiredTags);
serializer.WriteArray(timelineAbilityAsset.ActivationBlockedTags);
// Ability的Timeline执行规则部分转化为TimelineInfo
TimelineInfo timelineInfo = Convert2TimelineInfo(timelineAbilityAsset);
timelineInfo.SerializeForEditor(serializer);
return serializer.CopyData();
}
/// <summary>
/// TimelineAbilityAsset的Timeline部分数据转换为TimelineInfo格式
/// </summary>
/// <param name="timelineAbilityAsset"></param>
/// <returns></returns>
private static TimelineInfo Convert2TimelineInfo(TimelineAbilityAsset timelineAbilityAsset)
{
TimelineInfo timelineInfo = new TimelineInfo
{
endManual = timelineAbilityAsset.ManualEndAbility,
totalFrame = timelineAbilityAsset.FrameCount
};
List<RuntimeClipNode> tempNodes = new List<RuntimeClipNode>();
// InstantGE
foreach (var trackData in timelineAbilityAsset.ReleaseGameplayEffect)
{
foreach (var instantGEClipEvent in trackData.markEvents)
{
tempNodes.Add(instantGEClipEvent.ConvertRuntimeClipNode());
}
}
// DurationalGE
foreach (var trackData in timelineAbilityAsset.BuffGameplayEffects)
{
foreach (var durationalGEClipEvent in trackData.clipEvents)
{
tempNodes.Add(durationalGEClipEvent.ConvertRuntimeClipNode());
}
}
// InstantTask
foreach (var trackData in timelineAbilityAsset.InstantTasks)
{
foreach (var instantTaskClipEvent in trackData.markEvents)
{
int length = instantTaskClipEvent.instantTasks == null ? 0 : instantTaskClipEvent.instantTasks.Count;
for (int i = 0; i < length; ++i)
{
tempNodes.Add(instantTaskClipEvent.ConvertRuntimeClipNode(i));
}
}
}
// DurationalTask
foreach (var trackData in timelineAbilityAsset.OngoingTasks)
{
foreach (var durationalTaskClipEvent in trackData.clipEvents)
{
tempNodes.Add(durationalTaskClipEvent.ConvertRuntimeClipNode());
}
}
// PassiveGE
foreach (var trackData in timelineAbilityAsset.PassiveGameplayEffects)
{
foreach (var passiveGEClipEvent in trackData.clipEvents)
{
tempNodes.Add(passiveGEClipEvent.ConvertRuntimeClipNode());
}
}
// PassvieTask
foreach (var trackData in timelineAbilityAsset.PassiveTasks)
{
foreach (var passiveTaskClipEvent in trackData.clipEvents)
{
tempNodes.Add(passiveTaskClipEvent.ConvertRuntimeClipNode());
}
}
// InstantCue
foreach (var trackData in timelineAbilityAsset.InstantCues)
{
foreach (var instantCueClipEvent in trackData.markEvents)
{
tempNodes.Add(instantCueClipEvent.ConvertRuntimeClipNode());
}
}
// DurationalCue
foreach (var trackData in timelineAbilityAsset.DurationalCues)
{
foreach (var durationalCueClipEvent in trackData.clipEvents)
{
tempNodes.Add(durationalCueClipEvent.ConvertRuntimeClipNode());
}
}
// 按起始触发时间排序
tempNodes.Sort((lhs, rhs) => lhs.startFrame.CompareTo(rhs.startFrame));
timelineInfo.runtimeClipNodes = new RuntimeClipNode[tempNodes.Count];
for (int i = 0; i < tempNodes.Count; ++i)
{
timelineInfo.runtimeClipNodes[i] = tempNodes[i];
}
return timelineInfo;
}
}
public static class AbilityAssetTool
{
/// <summary>
/// PureTimelineAbilityAsset数据序列化为字节数据的工具函数
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public static byte[] Serialize(PureTimelineAbilityAsset abilityAsset)
{
var writer = new Serializer();
abilityAsset.SerializeForEditor(writer);
return writer.CopyData();
}
/// <summary>
/// 字节数据反序列化为PureTimelineAbilityAsset的工具函数
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static PureTimelineAbilityAsset Deserialize(byte[] bytes)
{
var reader = new Deserializer(bytes);
var abilityAsset = new PureTimelineAbilityAsset();
abilityAsset.DeserializeForEditor(reader);
return abilityAsset;
}
}
}
#endif

View File

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

View File

@ -29,8 +29,6 @@ namespace GAS.Editor
writer.WriteLine("");
writer.WriteLine("using System;");
writer.WriteLine("using System.Linq;");
writer.WriteLine("using UnityEngine;");
writer.WriteLine("");
@ -72,35 +70,18 @@ namespace GAS.Editor
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("");
writer.WriteLine(
"public static void InitWithPreset(this AbilitySystemComponent asc, int level, AbilitySystemComponentPreset preset = null)");
"public static void InitWithPreset(this AbilitySystemComponent asc, int entityId, int level, AbilitySystemComponentPreset preset = null)");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("if (preset != null) asc.SetPreset(preset);");
writer.WriteLine("if (asc.Preset == null) return;");
writer.WriteLine("");
writer.WriteLine("#if UNITY_EDITOR", true);
writer.WriteLine("if (asc.Preset.BaseAbilities != null && asc.Preset.BaseAbilities.Any(x => x == null))");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine(
"Debug.LogWarning($\"BaseAbilities contains null in preset: {asc.Preset.name}\");");
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("#endif", true);
writer.WriteLine("");
writer.WriteLine(
"asc.Init(asc.PresetBaseTags(), asc.PresetAttributeSetTypes(), asc.Preset.BaseAbilities, level);");
"asc.Init(asc.PresetBaseTags(), asc.PresetAttributeSetTypes(), asc.Preset.BaseAbilities, entityId, level);");
}
writer.Indent--;
writer.WriteLine("}");

View File

@ -3,9 +3,8 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using GAS.Editor.General;
using GAS.General.Validation;
using GAS.Editor.General.Validation;
using GAS.Runtime;
using Sirenix.OdinInspector;
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
@ -50,27 +49,13 @@ namespace GAS.Editor
"D- Ability System Component"
};
private const string OpenWindow_MenuItemName = "EX-GAS/Asset Aggregator";
#if EX_GAS_ENABLE_HOT_KEYS
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName + " %F9";
#else
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName;
#endif
[MenuItem(OpenWindow_MenuItemNameEnh, priority = 1)]
[MenuItem("EX-GAS/Asset Aggregator", priority = 1)]
private static void OpenWindow()
{
CheckLibPaths();
var window = GetWindow<GASAssetAggregator>();
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(1600, 900);
window.MenuWidth = 240;
}
private void ShowButton(Rect rect)
{
if (SirenixEditorGUI.SDFIconButton(rect, "GitHub", SdfIconType.Github))
{
Application.OpenURL("https://github.com/No78Vino/gameplay-ability-system-for-unity");
}
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(1250, 625);
window.MenuWidth = 220;
}
private static void CheckLibPaths()
@ -141,10 +126,6 @@ namespace GAS.Editor
var directoryInfo = selected.Value is AbilityOverview
? _directoryInfos[3]
: selected.Value as DirectoryInfo;
if (directoryInfo == null)
{
return;
}
if (SirenixEditorGUI.ToolbarButton(new GUIContent("浏览")))
{
@ -154,13 +135,15 @@ namespace GAS.Editor
if (SirenixEditorGUI.ToolbarButton(new GUIContent("新建子文件夹")))
{
CreateNewSubDirectory(directoryInfo);
GUIUtility.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
GUIUtility
.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
}
if (SirenixEditorGUI.ToolbarButton(new GUIContent("新建")))
{
CreateNewAsset(directoryInfo);
GUIUtility.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
GUIUtility
.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
}
if (!directoryInfo.Root)
@ -168,7 +151,8 @@ namespace GAS.Editor
if (SirenixEditorGUI.ToolbarButton(new GUIContent("删除")))
{
RemoveSubDirectory(directoryInfo);
GUIUtility.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
GUIUtility
.ExitGUI(); // In order to solve: "EndLayoutGroup: BeginLayoutGroup must be called first."
}
}
}
@ -247,8 +231,12 @@ namespace GAS.Editor
s =>
{
var newPath = directoryInfo.Directory + "/" + s;
var isExist = AssetDatabase.IsValidFolder(newPath);
return isExist ? ValidationResult.Invalid("Folder already exists!") : ValidationResult.Valid;
if (AssetDatabase.IsValidFolder(newPath))
{
return ValidationResult.Invalid("Folder already exists!");
}
return ValidationResult.Valid;
},
s =>
{
@ -294,22 +282,13 @@ namespace GAS.Editor
private void RemoveAsset(ScriptableObject asset)
{
if (asset == null)
{
EditorUtility.DisplayDialog("Warning", "The asset you want to delete is null", "Ok");
return;
}
if (!EditorUtility.DisplayDialog("Warning", "Are you sure you want to delete this asset?", "Yes",
"No")) return;
var assetName = asset.name; // Get the name before deleting
var assetPath = AssetDatabase.GetAssetPath(asset);
if (EditorUtility.DisplayDialog("Warning",
$"Are you sure you want to delete this asset?\n\nName=\"{assetName}\"\nPath=\"{assetPath}\""
, "Yes", "No"))
{
AssetDatabase.DeleteAsset(assetPath);
Refresh();
Debug.Log($"[EX] delete asset: Name=\"{assetName}\", Path=\"{assetPath}\"");
}
var name = asset.name; // Get the name before deleting
AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(asset));
Refresh();
Debug.Log($"[EX] {name} asset deleted!");
}
void OnMenuSelectionChange(SelectionChangedType selectionChangedType)

View File

@ -0,0 +1,178 @@
using JNGame.Serialization;
#if UNITY_EDITOR
namespace GAS.Editor
{
using Runtime;
using UnityEditor;
using System;
using System.IO;
using GAS;
using Editor;
using UnityEngine;
public class GASGen
{
[MenuItem("EX-GAS/GenAllLib", priority = 4)]
public static void GenAllLib()
{
AbilityCollectionGenerator.Gen();
string pathWithoutAssets = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
// TODO 拆分到独立面板 暂时先放这里使用
var ascFilePath = $"{pathWithoutAssets}/{GASSettingAsset.CodeGenPath}/{GasDefine.GAS_ASCPRESET_LIB_CSHARP_SCRIPT_NAME}";
GenerateASCPrsetCollection(ascFilePath);
// TODO 拆分到独立面板 暂时先放这里使用
var geFilePath = $"{pathWithoutAssets}/{GASSettingAsset.CodeGenPath}/{GasDefine.GAS_GAMEPLAYEFFECT_LIB_CSHARP_SCRIPT_NAME}";
GenerateGECollection(geFilePath);
}
private static void GenerateASCPrsetCollection(string filePath)
{
using var writer = new IndentedWriter(new StreamWriter(filePath));
writer.WriteLine("///////////////////////////////////");
writer.WriteLine("//// This is a generated file. ////");
writer.WriteLine("//// Do not modify it. ////");
writer.WriteLine("///////////////////////////////////");
writer.WriteLine("");
writer.WriteLine("using System;");
writer.WriteLine("using System.Collections.Generic;");
writer.WriteLine("");
writer.WriteLine("namespace GAS.Runtime");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public static class GASCPresetLib");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public struct ASCPresetInfo");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public string Name;");
writer.WriteLine("public string AssetPath;");
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("");
var abilityAssets = EditorUtil.FindAssetsByType<AbilitySystemComponentPreset>(GASSettingAsset.ASCLibPath);
foreach (var ability in abilityAssets)
{
var path = AssetDatabase.GetAssetPath(ability);
writer.WriteLine(
$"public static ASCPresetInfo {ability.name} = " +
$"new ASCPresetInfo {{ " +
$"Name = \"{ability.name}\", " +
$"AssetPath = \"{path}\"}};");
writer.WriteLine("");
}
writer.WriteLine("");
writer.WriteLine(
"public static Dictionary<string, ASCPresetInfo> ASCPresetMap = new Dictionary<string, ASCPresetInfo>");
writer.WriteLine("{");
writer.Indent++;
{
foreach (var ability in abilityAssets)
{
writer.WriteLine($"[\"{ability.name}\"] = {ability.name},");
}
}
writer.Indent--;
writer.WriteLine("};");
}
writer.Indent--;
writer.WriteLine("}");
}
writer.Indent--;
writer.Write("}");
Console.WriteLine($"Generated GASCPresetLib at path: {filePath}");
Debug.Log("GenerateASCPrsetCollection Finish");
}
private static void GenerateGECollection(string filePath)
{
using var writer = new IndentedWriter(new StreamWriter(filePath));
writer.WriteLine("///////////////////////////////////");
writer.WriteLine("//// This is a generated file. ////");
writer.WriteLine("//// Do not modify it. ////");
writer.WriteLine("///////////////////////////////////");
writer.WriteLine("");
writer.WriteLine("using System;");
writer.WriteLine("using System.Collections.Generic;");
writer.WriteLine("");
writer.WriteLine("namespace GAS.Runtime");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public static class GGameplayEffectLib");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public struct GameplayEffectAssetInfo");
writer.WriteLine("{");
writer.Indent++;
{
writer.WriteLine("public string Name;");
writer.WriteLine("public string AssetPath;");
}
writer.Indent--;
writer.WriteLine("}");
writer.WriteLine("");
var abilityAssets = EditorUtil.FindAssetsByType<GameplayEffectAsset>(GASSettingAsset.GameplayEffectLibPath);
foreach (var ability in abilityAssets)
{
var path = AssetDatabase.GetAssetPath(ability);
writer.WriteLine(
$"public static GameplayEffectAssetInfo {ability.name} = " +
$"new GameplayEffectAssetInfo {{ " +
$"Name = \"{ability.name}\", " +
$"AssetPath = \"{path}\"}};");
writer.WriteLine("");
}
writer.WriteLine("");
writer.WriteLine(
"public static Dictionary<string, GameplayEffectAssetInfo> GameplayEffectMap = new Dictionary<string, GameplayEffectAssetInfo>");
writer.WriteLine("{");
writer.Indent++;
{
foreach (var ability in abilityAssets)
{
writer.WriteLine($"[\"{ability.name}\"] = {ability.name},");
}
}
writer.Indent--;
writer.WriteLine("};");
}
writer.Indent--;
writer.WriteLine("}");
}
writer.Indent--;
writer.Write("}");
Console.WriteLine($"Generated GameplayEffectLib at path: {filePath}");
Debug.Log("GenerateGECollection Finish");
}
}
}
#endif

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 6d416e8b67924353b9ca08561ab6923e
timeCreated: 1719801078

View File

@ -1,10 +1,11 @@
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
#if UNITY_EDITOR
namespace GAS.Editor
{
using Sirenix.OdinInspector.Editor;
using Sirenix.Utilities;
using Sirenix.Utilities.Editor;
using UnityEditor;
public class GASSettingAggregator : OdinMenuEditorWindow
{
private static GASSettingAsset _settingAsset;
@ -51,17 +52,11 @@ namespace GAS.Editor
}
}
private const string OpenWindow_MenuItemName = "EX-GAS/Settings";
#if EX_GAS_ENABLE_HOT_KEYS
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName + " %F12";
#else
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName;
#endif
[MenuItem(OpenWindow_MenuItemNameEnh, priority = 0)]
[MenuItem("EX-GAS/Settings", priority = 0)]
public static void OpenWindow()
{
var window = GetWindow<GASSettingAggregator>();
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(1200, 600);
window.position = GUIHelper.GetEditorWindowRect().AlignCenter(900, 600);
}
protected override OdinMenuTree BuildMenuTree()
@ -86,4 +81,5 @@ namespace GAS.Editor
return tree;
}
}
}
}
#endif

View File

@ -1,94 +1,106 @@
using System;
using System.IO;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System.Collections.Generic;
using System.IO;
using GAS;
using GAS.General;
using Sirenix.OdinInspector;
using UnityEditor;
using UnityEngine;
[FilePath(GasDefine.GAS_BASE_SETTING_PATH)]
public class GASSettingAsset : ScriptableSingleton<GASSettingAsset>
{
private const int LABEL_WIDTH = 200;
private const int SHORT_LABEL_WIDTH = 200;
private static GASSettingAsset _setting;
[Title(GASTextDefine.TITLE_SETTING, Bold = true)]
[BoxGroup("A", false, order: 1)]
[Title(GASTextDefine.TITLE_SETTING,Bold = true)]
[BoxGroup("A", false,order:1)]
[LabelText(GASTextDefine.LABEL_OF_CodeGeneratePath)]
[LabelWidth(LABEL_WIDTH)]
[FolderPath]
[OnValueChanged("SaveAsset")]
public string CodeGeneratePath = "Assets/Scripts/Gen";
[BoxGroup("A")]
[LabelText(GASTextDefine.LABEL_OF_GASConfigAssetPath)]
[BoxGroup("A")]
[LabelText(GASTextDefine.LABEL_OF_GASConfigAssetPath)]
[LabelWidth(LABEL_WIDTH)]
[FolderPath]
[OnValueChanged("SaveAsset")]
public string GASConfigAssetPath = "Assets/GAS/Config";
public static GASSettingAsset Setting
{
get
{
if (_setting == null) _setting = LoadOrCreate();
return _setting;
}
}
[BoxGroup("A")]
[LabelText("二进制配置文件生成路径")]
[LabelWidth(LABEL_WIDTH)]
[FolderPath]
[OnValueChanged("SaveAsset")]
public string GASBinaryAssetPath = "Assets/GAS/Binary";
[BoxGroup("A")]
[LabelText("业务脚本生成路径")]
[LabelWidth(LABEL_WIDTH)]
[FolderPath]
[OnValueChanged("SaveAsset")]
public string LogicCodeGeneratePath = "Assets/Scripts/Gen";
[BoxGroup("A")]
[LabelText("业务脚本Assembly")]
[LabelWidth(LABEL_WIDTH)]
public List<string> LogicCodeGenerateAssemblies = new List<string>();
public static GASSettingAsset Setting => Instance;
[ShowInInspector]
[BoxGroup("V", false, order: 0)]
[HideLabel]
[DisplayAsString(TextAlignment.Left, true)]
private static string Version =>
$"<size=15><b><color=white>EX-GAS Version: {GasDefine.GAS_VERSION}</color></b></size>";
[BoxGroup("V",false,order:0)]
[HideLabel][DisplayAsString(TextAlignment.Left,true)]
private static string Version => $"<size=15><b><color=white>EX-GAS Version: {GasDefine.GAS_VERSION}</color></b></size>";
public static string CodeGenPath => Setting.CodeGeneratePath;
public static string LogicCodeGenPath => Setting.LogicCodeGeneratePath;
[Title(GASTextDefine.TITLE_PATHS, Bold = true)]
[Title(GASTextDefine.TITLE_PATHS,Bold = true)]
[PropertySpace(10)]
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string ASCLibPath => $"{Setting.GASConfigAssetPath}/{GasDefine.GAS_ASC_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string GameplayEffectLibPath =>
$"{Setting.GASConfigAssetPath}/{GasDefine.GAS_EFFECT_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string GameplayAbilityLibPath =>
$"{Setting.GASConfigAssetPath}/{GasDefine.GAS_ABILITY_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string GameplayCueLibPath => $"{Setting.GASConfigAssetPath}/{GasDefine.GAS_CUE_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string MMCLibPath => $"{Setting.GASConfigAssetPath}/{GasDefine.GAS_MMC_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[DisplayAsString(TextAlignment.Left,true)]
[LabelWidth(SHORT_LABEL_WIDTH)]
public static string AbilityTaskLib =>
$"{Setting.GASConfigAssetPath}/{GasDefine.GAS_ABILITY_TASK_LIBRARY_FOLDER}";
public static string AbilityTaskLib => $"{Setting.GASConfigAssetPath}/{GasDefine.GAS_ABILITY_TASK_LIBRARY_FOLDER}";
[ShowInInspector]
[BoxGroup("A")]
@ -137,13 +149,13 @@ namespace GAS.Editor
parentFolderPath += "/" + newFolderName;
}
}
[BoxGroup("A")]
[DisplayAsString(TextAlignment.Left, true)]
[GUIColor(0, 0.8f, 0)]
[DisplayAsString(TextAlignment.Left,true)]
[GUIColor(0,0.8f,0)]
[PropertySpace(10)]
[InfoBox(GASTextDefine.TIP_CREATE_FOLDERS)]
[Button(SdfIconType.FolderCheck, GASTextDefine.BUTTON_CheckAllPathFolderExist, ButtonHeight = 38)]
[Button(SdfIconType.FolderCheck,GASTextDefine.BUTTON_CheckAllPathFolderExist,ButtonHeight = 38)]
void CheckAllPathFolderExist()
{
CheckPathFolderExist(GASConfigAssetPath);
@ -154,6 +166,7 @@ namespace GAS.Editor
CheckPathFolderExist(GameplayCueLibPath);
CheckPathFolderExist(MMCLibPath);
CheckPathFolderExist(AbilityTaskLib);
CheckPathFolderExist(LogicCodeGeneratePath);
AssetDatabase.Refresh();
}
@ -167,14 +180,14 @@ namespace GAS.Editor
{
string pathWithoutAssets = Application.dataPath.Substring(0, Application.dataPath.Length - 6);
var filePath =
$"{pathWithoutAssets}/{CodeGenPath}/{GasDefine.GAS_ATTRIBUTESET_LIB_CSHARP_SCRIPT_NAME}";
$"{pathWithoutAssets}/{GASSettingAsset.CodeGenPath}/{GasDefine.GAS_ATTRIBUTESET_LIB_CSHARP_SCRIPT_NAME}";
if (!File.Exists(filePath))
{
EditorUtility.DisplayDialog("Error!", "Please generate AttributeSetAsset first!", "OK");
return;
}
AbilitySystemComponentUtilGenerator.Gen();
AssetDatabase.Refresh();
}
@ -185,36 +198,6 @@ namespace GAS.Editor
UpdateAsset(this);
Save();
}
private const string EX_GAS_ENABLE_HOT_KEYS = "EX_GAS_ENABLE_HOT_KEYS";
#if EX_GAS_ENABLE_HOT_KEYS
public const bool EnableHotKeys = true;
#else
public const bool EnableHotKeys = false;
#endif
[TabGroup("Advance", "Advance", SdfIconType.Gear, TextColor = "#FF7F00"), PropertyOrder(1)]
[InfoBox(
"@\"当前快捷键状态: \" + (EnableHotKeys ? \"启用\":\"禁用\") + \", 冲突时可禁用快捷键\"")]
#if EX_GAS_ENABLE_HOT_KEYS
[Button(SdfIconType.ToggleOn, "禁用快捷键")]
#else
[Button(SdfIconType.ToggleOff, "开启快捷键")]
#endif
private void ToggleScriptDefineSymbol_EX_GAS_ENABLE_HOT_KEYS()
{
if (EditorUtility.DisplayDialog("Ex-GAS",
"切换快捷键状态\n将在你的项目中切换\"EX_GAS_ENABLE_HOT_KEYS\"宏定义\n\n这会重新编译你的代码, 之后你可能需要手动保存你的项目(请留意ProjectSettings.asset的变化).",
"确定", "取消"))
{
#pragma warning disable 162
if (EnableHotKeys)
ScriptingDefineSymbolsHelper.Remove(EX_GAS_ENABLE_HOT_KEYS);
else
ScriptingDefineSymbolsHelper.Add(EX_GAS_ENABLE_HOT_KEYS);
#pragma warning restore 162
}
}
}
}
}
#endif

View File

@ -1,4 +1,8 @@
#if UNITY_EDITOR
using System;
using JNGame.Game;
using JNGame.GAS;
#if UNITY_EDITOR
namespace GAS.Editor
{
using System.Collections.Generic;
@ -24,13 +28,10 @@ namespace GAS.Editor
private AbilitySystemComponent _selected;
[HideLabel]
[DisplayAsString(TextAlignment.Center, true)]
[HideLabel] [DisplayAsString(TextAlignment.Center, true)]
public string windowTitle = "<size=18><b>EX Gameplay Ability System Watcher</b></size>";
[BoxGroup(BOXGROUP_TIPS)]
[HideLabel]
[DisplayAsString(TextAlignment.Left, true)]
[BoxGroup(BOXGROUP_TIPS)] [HideLabel] [DisplayAsString(TextAlignment.Left, true)]
public string tips = GASTextDefine.TIP_WATCHER;
[BoxGroup(BOXGROUP_TIPS_RUNNINGTIP, false)]
@ -49,6 +50,16 @@ namespace GAS.Editor
[ShowIf("IsPlaying")]
public string Navis = "NAVI";
[HorizontalGroup(BOXGROUP_ASC_H)]
[BoxGroup(BOXGROUP_ASC_H_R, false)]
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)]
[Title("Object Mark", bold: true)]
[DisplayAsString]
[LabelWidth(75)]
[ShowIf("IsPlaying")]
public int ASC_Number;
[HorizontalGroup(BOXGROUP_ASC_H)]
[BoxGroup(BOXGROUP_ASC_H_R, false)]
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
@ -59,17 +70,21 @@ namespace GAS.Editor
[ShowIf("IsPlaying")]
public int IID;
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)]
[ReadOnly]
[LabelWidth(75)]
[ShowIf("IsPlaying")]
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)] [ReadOnly] [LabelWidth(75)] [ShowIf("IsPlaying")]
public GameObject instance;
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)] [DisplayAsString] [LabelWidth(75)] [ShowIf("IsPlaying")]
public int Level;
[HorizontalGroup(BOXGROUP_ASC_H)]
[BoxGroup(BOXGROUP_ASC_H_R, false)]
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)]
[Title("TargetID Mark", bold: true)]
[DisplayAsString]
[LabelWidth(75)]
[ShowIf("IsPlaying")]
public int Level;
public int TargetIID;
[Space]
[Title("Abilities", bold: true)]
@ -80,7 +95,18 @@ namespace GAS.Editor
[LabelText(" ")]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> Abilities = new List<string>();
public List<string> Abilities = new();
[Space]
[Title("局外属性", bold: true)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_V)]
[ListDrawerSettings(ShowFoldout = true, ShowIndexLabels = false, ShowItemCount = true, IsReadOnly = true,
ShowPaging = false)]
[DisplayAsString]
[LabelText(" ")]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> OutsideAttributes = new();
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_VB)]
@ -91,8 +117,17 @@ namespace GAS.Editor
[LabelText(" ")]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> Attributes = new List<string>();
public List<string> Attributes = new();
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
[Title("最终属性", bold: true)]
[ListDrawerSettings(ShowFoldout = true, ShowIndexLabels = false, ShowItemCount = true, IsReadOnly = true,
ShowPaging = false)]
[DisplayAsString]
[LabelText(" ")]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> FinalAttributes = new();
[HorizontalGroup(BOXGROUP_ASC_H_R_A, PaddingRight = 0.01f)]
[Title("GameplayEffects", bold: true)]
@ -103,7 +138,7 @@ namespace GAS.Editor
[InfoBox("format: [ActiveState][DurationInfo]GeName", InfoMessageType.None, "IsPlaying")]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> Effects = new List<string>();
public List<string> Effects = new();
[HorizontalGroup(BOXGROUP_ASC_H_R_A)]
@ -114,7 +149,7 @@ namespace GAS.Editor
[DisplayAsString]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> FixedTag = new List<string>();
public List<string> FixedTag = new();
[Title(" ", bold: true)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_VC)]
@ -123,7 +158,16 @@ namespace GAS.Editor
[DisplayAsString]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> DynamicTag = new List<string>();
public List<string> DynamicTag = new();
[Title("最近造成伤害记录", bold: true)]
[VerticalGroup(BOXGROUP_ASC_H_R_A_VC)]
[ListDrawerSettings(ShowFoldout = true, ShowIndexLabels = false, ShowItemCount = true, IsReadOnly = true,
ShowPaging = false)]
[DisplayAsString]
[ShowIf("IsPlaying")]
[Searchable]
public List<string> DamageRecorder = new();
private Vector2 menuScrollPos;
@ -134,25 +178,17 @@ namespace GAS.Editor
{
if (IsPlaying)
{
if (_selected == null || _selected.Id == 0)
{
_selected = JexGasManager.Editor.AbilitySystemComponents.Count > 0
? JexGasManager.Editor.AbilitySystemComponents[0] as AbilitySystemComponent
if (_selected == null || _selected.EntityId == 0)
_selected = GameplayAbilitySystem.GAS.AbilitySystemComponents.Count > 0
? GameplayAbilitySystem.GAS.AbilitySystemComponents[0] as AbilitySystemComponent
: null;
}
RefreshAscInfo();
Repaint();
}
}
private const string OpenWindow_MenuItemName = "EX-GAS/Runtime Watcher";
#if EX_GAS_ENABLE_HOT_KEYS
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName + " %F11";
#else
private const string OpenWindow_MenuItemNameEnh = OpenWindow_MenuItemName;
#endif
[MenuItem(OpenWindow_MenuItemNameEnh, priority = 3)]
[MenuItem("EX-GAS/Runtime Watcher", priority = 3)]
private static void OpenWindow()
{
var window = GetWindow<GASWatcher>();
@ -160,16 +196,16 @@ namespace GAS.Editor
window.Show();
}
void OnDrawNavi()
private void OnDrawNavi()
{
if (!IsPlaying) return;
menuScrollPos = EditorGUILayout.BeginScrollView(menuScrollPos, GUI.skin.box);
foreach (var iasc in JexGasManager.Editor.AbilitySystemComponents)
foreach (var iasc in GameplayAbilitySystem.GAS.AbilitySystemComponents)
{
var asc = (AbilitySystemComponent)iasc;
var presetName = asc.Preset != null ? asc.Preset.name : "NoPreset";
if (GUILayout.Button($"{presetName}#{asc.Id}"))
var presetName = asc.Preset != null ? asc.Preset.Name : "NoPreset";
if (GUILayout.Button($"{presetName}#{asc.EntityId}"))
{
_selected = asc;
RefreshAscInfo();
@ -183,44 +219,57 @@ namespace GAS.Editor
{
if (_selected == null)
{
ASC_Number = -1;
IID = 0;
instance = null;
Level = 0;
TargetIID = 0;
Abilities.Clear();
OutsideAttributes.Clear();
Attributes.Clear();
FinalAttributes.Clear();
Effects.Clear();
FixedTag.Clear();
DynamicTag.Clear();
DamageRecorder.Clear();
return;
}
IID = _selected.Id;
// instance = _selected.GetView().gameObject;
ASC_Number = _selected.SelfNumber;
IID = _selected.EntityId;
instance = GameObject.Find(_selected.EntityName);
Level = _selected.Level;
RefreshAbilityInfo();
RefreshAttributesInfo();
RefreshGameplayEffectsInfo();
RefreshTagsInfo();
RefreshDamageInfo();
}
private void RefreshGameplayEffectsInfo()
{
Effects.Clear();
foreach (var ge in _selected.GameplayEffectContainer.GameplayEffects())
var list = _selected.GameplayEffectContainer.FetchGameplayEffects();
foreach (var ge in list)
{
string isActive = ge.IsActive ? "√" : "×";
string durationStr = ge.DurationPolicy switch
var isActive = ge.IsActive ? "√" : "×";
var durationStr = ge.DurationPolicy switch
{
EffectsDurationPolicy.Duration => $"{ge.DurationRemaining():N2}/{ge.Duration:N2}(s)",
EffectsDurationPolicy.Infinite => "∞",
EffectsDurationPolicy.Instant => "N/A",
_ => "Unknown"
};
var stackCountText = ge.Stacking.stackingType != StackingType.None ? $"[S:{ge.StackCount}]" : "";
var stackCountText = ge.Stacking.stackingType != EnumStackingType.None ? $"[S:{ge.StackCount}]" : "";
Effects.Add($"[{isActive}][{durationStr}]{stackCountText}{ge.GameplayEffect.GameplayEffectName}");
}
_selected.GameplayEffectContainer.ReturnGameplayEffects(list);
}
private void RefreshAbilityInfo()
@ -228,7 +277,7 @@ namespace GAS.Editor
Abilities.Clear();
foreach (var ability in _selected.AbilityContainer.AbilitySpecs())
{
string isActive = ability.Value.IsActive ? "(Active)" : "";
var isActive = ability.Value.IsActive ? "(Active)" : "";
Abilities.Add($"{ability.Key} | Lv.{ability.Value.Level} {isActive}");
}
}
@ -243,7 +292,7 @@ namespace GAS.Editor
{
var attr = attributeSet[attributeName];
Attributes.Add(
$" - {attributeName} = {attr.CurrentValue:N2}({attr.BaseValue:N2} + {attr.CurrentValue - attr.BaseValue:N2})");
$" - {attributeName} = {attr.CurrentValue}({attr.BaseValue} + {attr.CurrentValue - attr.BaseValue})");
}
}
}
@ -254,16 +303,13 @@ namespace GAS.Editor
RefreshDynamicTagsInfo();
}
void RefreshFixedTagsInfo()
private void RefreshFixedTagsInfo()
{
FixedTag.Clear();
foreach (var tag in _selected.GameplayTagAggregator.FixedTags)
{
FixedTag.Add(tag.Name);
}
foreach (var tag in _selected.GameplayTagAggregator.FixedTags) FixedTag.Add(tag.Name);
}
void RefreshDynamicTagsInfo()
private void RefreshDynamicTagsInfo()
{
DynamicTag.Clear();
foreach (var kv in _selected.GameplayTagAggregator.DynamicAddedTags)
@ -272,23 +318,35 @@ namespace GAS.Editor
DynamicTag.Add($"{tagName} ↓ ");
foreach (var obj in kv.Value)
{
switch (obj)
{
case GameplayEffectSpec spec:
{
DynamicTag.Add(
$" - From: {spec.Owner.Id}'s GE: {spec.GameplayEffect.GameplayEffectName}");
$" - From: {spec.Owner.EntityId}'s GE: {spec.GameplayEffect.GameplayEffectName}");
break;
}
case AbilitySpec ability:
{
DynamicTag.Add(
$" - From: {ability.Owner.Id}'s Ability: {ability.Ability.Name}");
$" - From: {ability.Owner.EntityId}'s Ability: {ability.Ability.Name}");
break;
}
}
}
}
}
private void RefreshDamageInfo()
{
DamageRecorder.Clear();
foreach (var kv in _selected.DamageList)
{
DamageRecorder.Add($"--对 {kv.asc.EntityId}@造成了{kv.atkValue}伤害");
DamageRecorder.Add($" 伤害来源 {kv.skillId}");
DamageRecorder.Add($" @{kv.damageType.Name}@{kv.other.ToString()}");
DamageRecorder.Add($" @闪避率:{kv.missRate}@暴击率{kv.criRate}");
DamageRecorder.Add($" @是否双倍{kv.isDouble}@双倍概率{kv.doubleRate}");
DamageRecorder.Add($" 当前时间戳{kv.time}");
}
}
}

View File

@ -0,0 +1,201 @@
#if UNITY_EDITOR
using GAS.Runtime;
using JNGame.Serialization;
namespace GAS.Editor
{
public static class GameplayEffectConverter
{
/// <summary>
/// 序列化GEAsset配置
/// </summary>
/// <param name="effectAsset"></param>
/// <returns></returns>
public static byte[] ToBytes(this GameplayEffectAsset effectAsset)
{
Serializer serializer = new Serializer();
// 策略相关字段
serializer.Write(effectAsset.Name);
serializer.Write((byte)effectAsset.DurationPolicy);
serializer.Write(effectAsset.Duration);
serializer.Write(effectAsset.Period);
serializer.Write(effectAsset.PeriodExecutionId);
serializer.Write(effectAsset.PeriodExcutionAsset == null ? "" : effectAsset.PeriodExcutionAsset.Name);
// 堆叠规则相关字段
serializer.Write((byte)effectAsset.stacking.stackingType);
serializer.Write(effectAsset.stacking.stackingCodeName);
serializer.Write(effectAsset.stacking.limitCount);
serializer.Write((byte)effectAsset.stacking.durationRefreshPolicy);
serializer.Write((byte)effectAsset.stacking.periodResetPolicy);
serializer.Write((byte)effectAsset.stacking.expirationPolicy);
serializer.Write(effectAsset.stacking.denyOverflowApplication);
serializer.Write(effectAsset.stacking.clearStackOnOverflow);
ushort length = (ushort)(effectAsset.stacking.overflowEffects == null ? 0 : effectAsset.stacking.overflowEffects.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
serializer.Write(effectAsset.stacking.overflowEffects[i].id);
serializer.Write(effectAsset.stacking.overflowEffects[i].asset == null ? "" : effectAsset.stacking.overflowEffects[i].asset.Name);
}
// 授予能力相关字段
length = (ushort)(effectAsset.grantedAbilities == null ? 0 : effectAsset.grantedAbilities.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
serializer.Write(effectAsset.grantedAbilities[i].abilityAsset == null ? "" : effectAsset.grantedAbilities[i].abilityAsset.Name);
serializer.Write(effectAsset.grantedAbilities[i].abilityLevel);
serializer.Write((byte)effectAsset.grantedAbilities[i].activationPolicy);
serializer.Write((byte)effectAsset.grantedAbilities[i].deactivationPolicy);
serializer.Write((byte)effectAsset.grantedAbilities[i].removePolicy);
}
// 属性修改器相关字段
length = (ushort)(effectAsset.modifiers == null ? 0 : effectAsset.modifiers.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
serializer.Write(effectAsset.modifiers[i].attributeName);
serializer.Write(effectAsset.modifiers[i].attributeShortName);
serializer.Write(effectAsset.modifiers[i].attributeSetName);
serializer.Write(effectAsset.modifiers[i].modiferMagnitude);
serializer.Write((byte)effectAsset.modifiers[i].operation);
serializer.Write(effectAsset.modifiers[i].mmc.TypeId);
effectAsset.modifiers[i].mmc.Serialize(serializer);
}
// Tags相关字段
serializer.WriteArray(effectAsset.assetTags);
serializer.WriteArray(effectAsset.grantedTags);
serializer.WriteArray(effectAsset.applicationRequiredTags);
serializer.WriteArray(effectAsset.ongoingRequiredTags);
serializer.WriteArray(effectAsset.removeGameplayEffectsWithTags);
serializer.WriteArray(effectAsset.applicationImmunityTags);
// Cue相关字段
length = (ushort)(effectAsset.cueOnExecute == null ? 0 : effectAsset.cueOnExecute.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueOnExecute[i] == null ? "" : effectAsset.cueOnExecute[i].name;
serializer.Write(cueAssetName);
}
length = (ushort)(effectAsset.cueDurational == null ? 0 : effectAsset.cueDurational.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueDurational[i] == null ? "" : effectAsset.cueDurational[i].name;
serializer.Write(cueAssetName);
}
length = (ushort)(effectAsset.cueOnAdd == null ? 0 : effectAsset.cueOnAdd.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueOnAdd[i] == null ? "" : effectAsset.cueOnAdd[i].name;
serializer.Write(cueAssetName);
}
length = (ushort)(effectAsset.cueOnRemove == null ? 0 : effectAsset.cueOnRemove.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueOnRemove[i] == null ? "" : effectAsset.cueOnRemove[i].name;
serializer.Write(cueAssetName);
}
length = (ushort)(effectAsset.cueOnActivate == null ? 0 : effectAsset.cueOnActivate.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueOnActivate[i] == null ? "" : effectAsset.cueOnActivate[i].name;
serializer.Write(cueAssetName);
}
length = (ushort)(effectAsset.cueOnDeactivate == null ? 0 : effectAsset.cueOnDeactivate.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
string cueAssetName = effectAsset.cueOnDeactivate[i] == null ? "" : effectAsset.cueOnDeactivate[i].name;
serializer.Write(cueAssetName);
}
// Expiration相关字段
length = (ushort)(effectAsset.prematureExpirationEffect == null ? 0 : effectAsset.prematureExpirationEffect.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
if (effectAsset.prematureExpirationEffect[i] == null)
{
serializer.Write(0);
serializer.Write("");
}
else
{
serializer.Write(effectAsset.prematureExpirationEffect[i].id);
serializer.Write(effectAsset.prematureExpirationEffect[i].asset == null ? "" : effectAsset.prematureExpirationEffect[i].asset.name);
}
}
length = (ushort)(effectAsset.routineExpirationEffectClasses == null ? 0 : effectAsset.routineExpirationEffectClasses.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
if (effectAsset.routineExpirationEffectClasses[i] == null)
{
serializer.Write(0);
serializer.Write("");
}
else
{
serializer.Write(effectAsset.routineExpirationEffectClasses[i].id);
serializer.Write(effectAsset.routineExpirationEffectClasses[i].asset == null ? "" : effectAsset.routineExpirationEffectClasses[i].asset.name);
}
}
// TickEvent相关字段
serializer.Write(effectAsset.IsUseTickEventGE);
if (effectAsset.IsUseTickEventGE)
{
effectAsset.tickEventTag.Serialize(serializer);
length = (ushort)(effectAsset.tickEventEffect == null ? 0 : effectAsset.tickEventEffect.Length);
serializer.Write(length);
for (int i = 0; i < length; ++i)
{
if (effectAsset.tickEventEffect[i] == null)
{
serializer.Write(0);
serializer.Write("");
}
else
{
serializer.Write(effectAsset.tickEventEffect[i].id);
serializer.Write(effectAsset.tickEventEffect[i].asset == null ? "" : effectAsset.tickEventEffect[i].asset.name);
}
}
}
return serializer.CopyData();
}
}
public static class GameEffectAssetSerializationTool
{
/// <summary>
/// PureGameplayEffectAsset数据序列化为字节数据的工具函数
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public static byte[] Serialize(PureGameplayEffectAsset geAsset)
{
var writer = new Serializer();
geAsset.Serialize(writer);
return writer.CopyData();
}
/// <summary>
/// 字节数据反序列化为PureGameplayEffectAsset的工具函数
/// </summary>
/// <param name="bytes"></param>
/// <returns></returns>
public static PureGameplayEffectAsset Deserialize(byte[] bytes)
{
var reader = new Deserializer(bytes);
var geAsset = new PureGameplayEffectAsset();
geAsset.Deserialize(reader);
return geAsset;
}
}
}
#endif

View File

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

View File

@ -32,23 +32,12 @@ namespace GAS.Editor
_streamWriter.Write(GetIndentation() + text);
}
public void WriteLine(string line = null, bool ignoreIndent = false)
public void WriteLine(string line = null)
{
if (string.IsNullOrWhiteSpace(line))
{
_streamWriter.WriteLine();
}
else
{
if (ignoreIndent)
{
_streamWriter.WriteLine(line);
}
else
{
_streamWriter.WriteLine(GetIndentation() + line);
}
}
_streamWriter.WriteLine(GetIndentation() + line);
}
private bool _isDisposed;

View File

@ -1,52 +0,0 @@
using System.Linq;
using GAS.General.Validation;
using UnityEditor;
using UnityEngine;
namespace GAS.Editor
{
public static class ScriptingDefineSymbolsHelper
{
public static void Add(string define)
{
if (!Validations.IsValidVariableName(define))
{
Debug.LogError($@"Add scripting define symbol error: ""{define}"" is not a valid variable name!");
return;
}
var group = EditorUserBuildSettings.selectedBuildTargetGroup;
var definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
var defines = definesString.Split(';');
if (defines.Contains(define))
{
Debug.Log($@"Add scripting define symbol failed: ""{define}"" already exists!");
return;
}
var newDefinesString = string.Join(";", defines.Append(define));
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, newDefinesString);
}
public static void Remove(string define)
{
if (string.IsNullOrWhiteSpace(define))
{
Debug.LogError($@"Remove scripting define symbol error: ""{define}"" is null or empty!");
return;
}
var group = EditorUserBuildSettings.selectedBuildTargetGroup;
var definesString = PlayerSettings.GetScriptingDefineSymbolsForGroup(group);
var defines = definesString.Split(';');
if (!defines.Contains(define))
{
Debug.Log($@"Remove scripting define symbol failed: ""{define}"" does not exist!");
return;
}
var newDefinesString = string.Join(";", defines.Where(d => d != define));
PlayerSettings.SetScriptingDefineSymbolsForGroup(group, newDefinesString);
}
}
}

View File

@ -1,3 +0,0 @@
fileFormatVersion: 2
guid: b9055976068442439bd3318d8b42a345
timeCreated: 1718347417

View File

@ -1,5 +1,5 @@
using System;
using GAS.General.Validation;
using GAS.Editor.General.Validation;
using UnityEditor;
using UnityEngine;

View File

@ -1,8 +1,3 @@
fileFormatVersion: 2
guid: b84e44e848a2cc54a9f86e88d1219512
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
guid: 8207bd0f7b514c9a9a29ef37d242fd70
timeCreated: 1729568975

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 69be32fe4d27dde489209c5885c1e5dc
guid: 1fd36c55067263b4e8a57d66895368a6
timeCreated: 1472024155
licenseType: Pro
MonoImporter:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: fd65e8f324e17a344a97ddcf5a8d89d2
guid: 57988073b2c6d944dbba8856bcae1b6d
timeCreated: 1471616285
licenseType: Pro
MonoImporter:

View File

@ -1,5 +1,5 @@
fileFormatVersion: 2
guid: 6f9fab1cf2636a6439c644bf08108abb
guid: ddf437cbf1378614fb3fb17f4ba85426
timeCreated: 1472122507
licenseType: Pro
MonoImporter:

View File

@ -0,0 +1,65 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
using UnityEngine;
[Serializable]
public class TreeElement
{
[SerializeField] private int _id;
[SerializeField] private string _name;
[SerializeField] private int _depth;
[NonSerialized] private List<TreeElement> _children;
[NonSerialized] private TreeElement _parent;
public TreeElement()
{
}
public TreeElement(string name, int depth, int id)
{
_name = name;
_id = id;
_depth = depth;
}
public int Depth
{
get => _depth;
set => _depth = value;
}
public TreeElement Parent
{
get => _parent;
set => _parent = value;
}
public List<TreeElement> Children
{
get => _children;
set => _children = value;
}
public bool HasChildren => Children != null && Children.Count > 0;
public string Name
{
get => _name;
set => _name = value;
}
public int ID
{
get => _id;
set => _id = value;
}
}
}
#endif

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 69be32fe4d27dde489209c5885c1e5dc
timeCreated: 1472024155
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,171 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
// TreeElementUtility and TreeElement are useful helper classes for backend tree data structures.
// See tests at the bottom for examples of how to use.
public static class TreeElementUtility
{
public static void TreeToList<T>(T root, IList<T> result) where T : TreeElement
{
if (result == null)
throw new NullReferenceException("The input 'IList<T> result' list is null");
result.Clear();
Stack<T> stack = new Stack<T>();
stack.Push(root);
while (stack.Count > 0)
{
T current = stack.Pop();
result.Add(current);
if (current.Children != null && current.Children.Count > 0)
{
for (int i = current.Children.Count - 1; i >= 0; i--)
{
stack.Push((T)current.Children[i]);
}
}
}
}
// Returns the root of the tree parsed from the list (always the first element).
// Important: the first item and is required to have a depth value of -1.
// The rest of the items should have depth >= 0.
public static T ListToTree<T>(IList<T> list) where T : TreeElement
{
// Validate input
ValidateDepthValues (list);
// Clear old states
foreach (var element in list)
{
element.Parent = null;
element.Children = null;
}
// Set child and parent references using depth info
for (int parentIndex = 0; parentIndex < list.Count; parentIndex++)
{
var parent = list[parentIndex];
bool alreadyHasValidChildren = parent.Children != null;
if (alreadyHasValidChildren)
continue;
int parentDepth = parent.Depth;
int childCount = 0;
// Count children based depth value, we are looking at children until it's the same depth as this object
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].Depth == parentDepth + 1)
childCount++;
if (list[i].Depth <= parentDepth)
break;
}
// Fill child array
List<TreeElement> childList = null;
if (childCount != 0)
{
childList = new List<TreeElement>(childCount); // Allocate once
childCount = 0;
for (int i = parentIndex + 1; i < list.Count; i++)
{
if (list[i].Depth == parentDepth + 1)
{
list[i].Parent = parent;
childList.Add(list[i]);
childCount++;
}
if (list[i].Depth <= parentDepth)
break;
}
}
parent.Children = childList;
}
return list[0];
}
// Check state of input list
public static void ValidateDepthValues<T>(IList<T> list) where T : TreeElement
{
if (list.Count == 0)
throw new ArgumentException("list should have items, count is 0, check before calling ValidateDepthValues", "list");
if (list[0].Depth != -1)
throw new ArgumentException("list item at index 0 should have a depth of -1 (since this should be the hidden root of the tree). Depth is: " + list[0].Depth, "list");
for (int i = 0; i < list.Count - 1; i++)
{
int depth = list[i].Depth;
int nextDepth = list[i + 1].Depth;
if (nextDepth > depth && nextDepth - depth > 1)
throw new ArgumentException(string.Format("Invalid depth info in input list. Depth cannot increase more than 1 per row. Index {0} has depth {1} while index {2} has depth {3}", i, depth, i + 1, nextDepth));
}
for (int i = 1; i < list.Count; ++i)
if (list[i].Depth < 0)
throw new ArgumentException("Invalid depth value for item at index " + i + ". Only the first item (the root) should have depth below 0.");
if (list.Count > 1 && list[1].Depth != 0)
throw new ArgumentException("Input list item at index 1 is assumed to have a depth of 0", "list");
}
// For updating depth values below any given element e.g after reparenting elements
public static void UpdateDepthValues<T>(T root) where T : TreeElement
{
if (root == null)
throw new ArgumentNullException("root", "The root is null");
if (!root.HasChildren)
return;
Stack<TreeElement> stack = new Stack<TreeElement>();
stack.Push(root);
while (stack.Count > 0)
{
TreeElement current = stack.Pop();
if (current.Children != null)
{
foreach (var child in current.Children)
{
child.Depth = current.Depth + 1;
stack.Push(child);
}
}
}
}
// Returns true if there is an ancestor of child in the elements list
static bool IsChildOf<T>(T child, IList<T> elements) where T : TreeElement
{
while (child != null)
{
child = (T)child.Parent;
if (elements.Contains(child))
return true;
}
return false;
}
public static IList<T> FindCommonAncestorsWithinList<T>(IList<T> elements) where T : TreeElement
{
if (elements.Count == 1)
return new List<T>(elements);
List<T> result = new List<T>(elements);
result.RemoveAll(g => IsChildOf(g, elements));
return result;
}
}
}
#endif

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: fd65e8f324e17a344a97ddcf5a8d89d2
timeCreated: 1471616285
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,238 @@
#if UNITY_EDITOR
namespace UnityEditor.TreeDataModel
{
using System;
using System.Collections.Generic;
using System.Linq;
// The TreeModel is a utility class working on a list of serializable TreeElements where the order and the depth of each TreeElement define
// the tree structure. Note that the TreeModel itself is not serializable (in Unity we are currently limited to serializing lists/arrays) but the
// input list is.
// The tree representation (parent and children references) are then build internally using TreeElementUtility.ListToTree (using depth
// values of the elements).
// The first element of the input list is required to have depth == -1 (the hiddenroot) and the rest to have
// depth >= 0 (otherwise an exception will be thrown)
public class TreeModel<T> where T : TreeElement
{
IList<T> m_Data;
int m_MaxID;
public T Root { get; private set; }
public event Action modelChanged;
public int NumberOfDataElements => m_Data.Count;
public TreeModel (IList<T> data)
{
SetData (data);
}
public T Find (int id)
{
return m_Data.FirstOrDefault (element => element.ID == id);
}
public void SetData (IList<T> data)
{
Init (data);
}
void Init (IList<T> data)
{
m_Data = data ?? throw new ArgumentNullException("data", "Input data is null. Ensure input is a non-null list.");
if (m_Data.Count > 0)
{
Root = TreeElementUtility.ListToTree(data);
}
else
{
T root = Activator.CreateInstance(typeof(T), "Root", -1, 0) as T;
AddRoot(root);
Root = root;
}
m_MaxID = m_Data.Max(e => e.ID);
}
public int GenerateUniqueID ()
{
return ++m_MaxID;
}
public IList<int> GetAncestors (int id)
{
var parents = new List<int>();
TreeElement T = Find(id);
if (T != null)
{
while (T.Parent != null)
{
parents.Add(T.Parent.ID);
T = T.Parent;
}
}
return parents;
}
public IList<int> GetDescendantsThatHaveChildren (int id)
{
T searchFromThis = Find(id);
if (searchFromThis != null)
{
return GetParentsBelowStackBased(searchFromThis);
}
return new List<int>();
}
IList<int> GetParentsBelowStackBased(TreeElement searchFromThis)
{
Stack<TreeElement> stack = new Stack<TreeElement>();
stack.Push(searchFromThis);
var parentsBelow = new List<int>();
while (stack.Count > 0)
{
TreeElement current = stack.Pop();
if (current.HasChildren)
{
parentsBelow.Add(current.ID);
foreach (var T in current.Children)
{
stack.Push(T);
}
}
}
return parentsBelow;
}
public void RemoveElements (IList<int> elementIDs)
{
IList<T> elements = m_Data.Where (element => elementIDs.Contains (element.ID)).ToArray ();
RemoveElements (elements);
}
public void RemoveElements (IList<T> elements)
{
foreach (var element in elements)
if (element == Root)
throw new ArgumentException("It is not allowed to remove the root element");
var commonAncestors = TreeElementUtility.FindCommonAncestorsWithinList (elements);
foreach (var element in commonAncestors)
{
element.Parent.Children.Remove (element);
element.Parent = null;
}
TreeElementUtility.TreeToList(Root, m_Data);
Changed();
}
public void AddElements (IList<T> elements, TreeElement parent, int insertPosition)
{
if (elements == null)
throw new ArgumentNullException("elements", "elements is null");
if (elements.Count == 0)
throw new ArgumentNullException("elements", "elements Count is 0: nothing to add");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.Children == null)
parent.Children = new List<TreeElement>();
parent.Children.InsertRange(insertPosition, elements.Cast<TreeElement> ());
foreach (var element in elements)
{
element.Parent = parent;
element.Depth = parent.Depth + 1;
TreeElementUtility.UpdateDepthValues(element);
}
TreeElementUtility.TreeToList(Root, m_Data);
Changed();
}
public void AddRoot (T root)
{
if (root == null)
throw new ArgumentNullException("root", "root is null");
if (m_Data == null)
throw new InvalidOperationException("Internal Error: data list is null");
if (m_Data.Count != 0)
throw new InvalidOperationException("AddRoot is only allowed on empty data list");
root.Name = "Root";
root.ID = GenerateUniqueID ();
root.Depth = -1;
m_Data.Add (root);
}
public void AddElement (T element, TreeElement parent, int insertPosition)
{
if (element == null)
throw new ArgumentNullException("element", "element is null");
if (parent == null)
throw new ArgumentNullException("parent", "parent is null");
if (parent.Children == null)
parent.Children = new List<TreeElement> ();
parent.Children.Insert (insertPosition, element);
element.Parent = parent;
TreeElementUtility.UpdateDepthValues(parent);
TreeElementUtility.TreeToList(Root, m_Data);
Changed ();
}
public void MoveElements(TreeElement parentElement, int insertionIndex, List<TreeElement> elements)
{
if (insertionIndex < 0)
throw new ArgumentException("Invalid input: insertionIndex is -1, client needs to decide what index elements should be reparented at");
// Invalid reparenting input
if (parentElement == null)
return;
// We are moving items so we adjust the insertion index to accomodate that any items above the insertion index is removed before inserting
if (insertionIndex > 0)
insertionIndex -= parentElement.Children.GetRange(0, insertionIndex).Count(elements.Contains);
// Remove draggedItems from their parents
foreach (var draggedItem in elements)
{
draggedItem.Parent.Children.Remove(draggedItem); // remove from old parent
draggedItem.Parent = parentElement; // set new parent
}
if (parentElement.Children == null)
parentElement.Children = new List<TreeElement>();
// Insert dragged items under new parent
parentElement.Children.InsertRange(insertionIndex, elements);
TreeElementUtility.UpdateDepthValues (Root);
TreeElementUtility.TreeToList (Root, m_Data);
Changed ();
}
void Changed ()
{
if (modelChanged != null)
modelChanged ();
}
}
}
#endif

View File

@ -0,0 +1,12 @@
fileFormatVersion: 2
guid: 6f9fab1cf2636a6439c644bf08108abb
timeCreated: 1472122507
licenseType: Pro
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,45 @@
using System.Text.RegularExpressions;
namespace GAS.Editor.General.Validation
{
public readonly struct ValidationResult
{
public readonly bool IsValid;
public readonly string Message;
public ValidationResult(bool isValid, string message)
{
IsValid = isValid;
Message = message;
}
public static readonly ValidationResult Valid = new ValidationResult(true, null);
public static ValidationResult Invalid(string message) => new ValidationResult(false, message);
}
public delegate ValidationResult ValidationDelegate(string input);
public static class Validations
{
// https://learn.microsoft.com/zh-cn/dotnet/csharp/fundamentals/coding-style/identifier-names
// 可以在标识符上使用 @ 前缀来声明与 C# 关键字匹配的标识符。 @ 不是标识符名称的一部分。 例如,@if 声明名为 if 的标识符。
// 因此类似 @123abc 这样的标识符是不合法的。因为抛开@之后, 它实际上是以数字开头。
private const string VariableNamePattern = @"^@?[a-zA-Z_][a-zA-Z0-9_]*$";
public static readonly Regex VariableNameRegex = new Regex(VariableNamePattern);
public static ValidationResult ValidateVariableName(string name)
{
if (string.IsNullOrEmpty(name))
return ValidationResult.Invalid("The name is empty!");
return VariableNameRegex.IsMatch(name)
? ValidationResult.Valid
: ValidationResult.Invalid($"The name(\"{name}\") is invalid!");
}
public static bool IsValidVariableName(string name)
{
return ValidateVariableName(name).IsValid;
}
}
}

View File

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

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 688920bc1afba114290b8e45101a8054
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GAS.Runtime;
using JNGame.Tools.CodeGen;
using UnityEngine;
namespace GAS.Editor
{
/// <summary>
/// AbilityTask枚举和构造工厂方法注册代码生成
/// </summary>
public class CodeGenAbilityTaskInjecter
{
private static readonly string s_CodeTemplate = @"
// auto generate by tools, DO NOT Modify it!!!
using GAS.Runtime;
namespace ##NAMESPACE
{
/// <summary>
/// 游戏业务逻辑扩展的AbilityTask枚举
/// </summary>
public enum EnumGameAbilityTaskType
{
##CODEREPLACE_0
}
#region AbilityTask构造工厂方法注册
public partial class GASInjector
{
private partial void InternalAbilityTaskInject()
{
##CODEREPLACE_1
}
}
#endregion
#region AbilityTask枚举ID自动生成
##CODEREPLACE_2
#endregion
}";
public static void GenCode(CodeGenInfo info)
{
string prefix = "\t";
var contextTemplates = new List<string>()
{
prefix + prefix + "##TYPE_NAME = ##INDEX,",
prefix + prefix + prefix + "AbilityTaskFactory.Register((ushort)EnumGameAbilityTaskType.##TYPE_NAME, () => new ##FULL_TYPE_NAME());",
prefix + "public partial class ##TYPE_NAME { public override ushort TypeId => (ushort)EnumGameAbilityTaskType.##TYPE_NAME; }"
};
var types = info.allTypes.ToArray().ToList();
types.Sort((a, b) => a.Name.CompareTo(b.Name));
var finalStr = s_CodeTemplate.Replace("##NAMESPACE", info.codeGenNameSpace);
for (int i = 0; i < contextTemplates.Count; i++)
{
finalStr = finalStr.Replace("##CODEREPLACE_" + i, GenCodeByTemplate(types, contextTemplates[i]));
}
var path = info.outputPath;
FileUtil.SaveFile(path, finalStr);
}
private static string GenCodeByTemplate(List<Type> types, string template)
{
StringBuilder sb = new StringBuilder();
int counterInstantTask = 0;
int counterOngoingTask = 0;
int counterPassiveTask = 0;
for (int i = 0; i < types.Count; i++)
{
var type = types[i];
string index;
if (type.IsSubclassOf(typeof(InstantAbilityTask)))
{
index = ((int)EnumAbilityTaskType.InstantTaskStart + (++counterInstantTask)).ToString();
}
else if (type.IsSubclassOf(typeof(OngoingAbilityTask)))
{
index = ((int)EnumAbilityTaskType.OngoingTaskStart + (++counterOngoingTask)).ToString();
}
else if(type.IsSubclassOf(typeof(PassiveAbilityTask)))
{
index = ((int)EnumAbilityTaskType.PassiveTaskStart + (++counterPassiveTask)).ToString();
}
else
{
Debug.LogError($"AbilityTask的类型不是 InstantAbilityTask /OngoingAbilityTask / PassiveAbilityTask 任意一种,{type.FullName}");
continue;
}
var typeName = type.Name.ToString();
var fullTypeName = type.FullName.ToString();
var str = template.Replace("##INDEX", index).Replace("##TYPE_NAME", typeName).Replace("##FULL_TYPE_NAME", fullTypeName);
sb.AppendLine(str);
}
return sb.ToString();
}
}
}

View File

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

View File

@ -0,0 +1,127 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using JNGame.Tools.CodeGen;
namespace GAS.Editor
{
/// <summary>
/// 行为树节点序列化相关代码生成
/// </summary>
public class CodeGenAbilityTaskSerialization
{
/// <summary>
/// 代码文件头部模板代码
/// </summary>
private static readonly string s_CodeHeaderTemplate = @"
// auto generate by tools, DO NOT Modify it!!!
using GAS.Runtime;
using JNGame.Serialization;
";
/// <summary>
/// 类模板代码
/// </summary>
private static readonly string s_ClsTemplate = @"
namespace ##NAMESPACE
{
public partial class ##TYPE_NAME
{
public override void Serialize(Serializer writer)
{
base.Serialize(writer);
##CODEREPLACE_0
}
public override void Deserialize(Deserializer reader)
{
base.Deserialize(reader);
##CODEREPLACE_1
}
}
}
";
private static readonly BindingFlags s_BindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
public static void GenCode(CodeGenInfo info)
{
string prefix = "\t\t\t";
var types = info.allTypes.ToArray().ToList();
types.Sort((a, b) => a.Name.CompareTo(b.Name));
StringBuilder sb = new StringBuilder();
foreach (var type in types)
{
var clsStr = GenCodeByTemplate(type, s_ClsTemplate);
var contextTemplates = new List<CodeGenTemplateInfo>()
{
new CodeGenTemplateInfo()
{
Default = prefix + "writer.Write(##NAME);",
Enum = prefix + "writer.Write((int)##NAME);", // TODO deal with array list &dict
Array = prefix + "writer.WriteArray(##NAME);",
},
new CodeGenTemplateInfo()
{
Default = prefix + "##NAME = reader.Read##TYPE_NAME();",
Enum = prefix + "##NAME = (##TYPE_NAME)reader.ReadInt32();",
Array = prefix + "##NAME = reader.ReadArray(new ##ELEMENT_TYPE_NAME());",
},
};
var fields = type.GetFields(s_BindingFlags)
.Select(a => new CodeGenFieldInfo() { name = a.Name, type = a.FieldType }).ToList();
// var properties = type.GetProperties(s_BindingFlags).Where(a => a.CanRead && a.CanWrite)
// .Select(a => new CodeGenFieldInfo() { name = a.Name, type = a.PropertyType }).ToList();
// fields.AddRange(properties);
for (int i = 0; i < contextTemplates.Count; i++)
{
clsStr = clsStr.Replace("##CODEREPLACE_" + i, GetFieldsCode(fields, contextTemplates[i]));
}
if (fields.Count > 0)
{
sb.AppendLine(clsStr);
}
}
var finalStr = s_CodeHeaderTemplate;
finalStr += sb.ToString();
var path = info.outputPath;
FileUtil.SaveFile(path, finalStr);
}
private static string GetFieldsCode(List<CodeGenFieldInfo> fields, CodeGenTemplateInfo template)
{
StringBuilder sbField = new StringBuilder();
foreach (var field in fields)
{
var templateStr = template.GetTemplateStr(field);
string elementTypeName = "";
if(field.IsArray || field.IsList)
{
elementTypeName = field.type.GetElementType().Name;
}
var str = templateStr.Replace("##NAME", field.name).Replace("##TYPE_NAME", field.TypeName).Replace("##FULL_TYPE_NAME", field.FullTypeName)
.Replace("##ELEMENT_TYPE_NAME", elementTypeName);
sbField.AppendLine(str);
}
return sbField.ToString();
}
private static string GenCodeByTemplate(Type type, string template)
{
StringBuilder sb = new StringBuilder();
var nameSpace = type.Namespace.ToString();
var typeName = type.Name.ToString();
var fullTypeName = type.FullName.ToString();
var str = template.Replace("##NAMESPACE", nameSpace).Replace("##TYPE_NAME", typeName).Replace("##FULL_TYPE_NAME", fullTypeName);
sb.AppendLine(str);
return sb.ToString();
}
}
}

Some files were not shown because too many files have changed in this diff Show More