├── .gitignore
├── .gitattributes
├── Samples.unitypackage
├── Resources
├── MAOTimelineExtensionsConfigSO.asset.meta
└── MAOTimelineExtensionsConfigSO.asset
├── Runtime
├── MAOTimelineExtensionsConfigSO.cs
├── Extensions.cs
└── MAOTimelineExtensionVolumeSettings.cs
├── LICENSE
├── Editor
├── MAOTimelineExtensionVolumeSettingsEditor.cs
├── TimelinePlayableWizardCreateScript.cs
└── MAOTimelinePlayableWizard.cs
├── README_CN.md
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | *.meta
2 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * -text
2 |
3 | *.cs text eol=lf diff=csharp
--------------------------------------------------------------------------------
/Samples.unitypackage:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShiinaRinne/EasyTimeline/HEAD/Samples.unitypackage
--------------------------------------------------------------------------------
/Resources/MAOTimelineExtensionsConfigSO.asset.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ed6fe8c0a6b699541855119898f8587e
3 | NativeFormatImporter:
4 | externalObjects: {}
5 | mainObjectFileID: 11400000
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Resources/MAOTimelineExtensionsConfigSO.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &11400000
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 11500000, guid: f7bd84cfcc3744247b7b235f4cf3b61a, type: 3}
13 | m_Name: MAOTimelineExtensionsConfigSO
14 | m_EditorClassIdentifier:
15 | rootFolderPath: Assets/TimelineExtensions
16 | defaultNameSpace: SR.Timeline.VolumeExtensions
17 |
--------------------------------------------------------------------------------
/Runtime/MAOTimelineExtensionsConfigSO.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.Serialization;
3 |
4 | namespace MAOTimelineExtension.Runtime
5 | {
6 | [CreateAssetMenu(fileName = "MAOTimelineExtensionsConfigSO", menuName = "MAOTimelineExtensions/ConfigSO", order = 0)]
7 | public class MAOTimelineExtensionsConfigSO : ScriptableObject
8 | {
9 | public string rootFolderPath = "Assets/TimelineExtensions";
10 | public string volumeDefaultNameSpace = "MAOTimelineExtension.VolumeExtensions";
11 | public string componentDefaultNameSpace = "MAOTimelineExtension.ComponentExtensions";
12 | }
13 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 ShiinaRinne
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Editor/MAOTimelineExtensionVolumeSettingsEditor.cs:
--------------------------------------------------------------------------------
1 | using UnityEditor;
2 | using UnityEngine;
3 |
4 | using MAOTimelineExtension.Runtime;
5 |
6 | namespace MAOTimelineExtension.Editor
7 | {
8 | [CustomEditor(typeof(MAOTimelineExtensionVolumeSettings))]
9 | public class MAOTimelineExtensionVolumeSettingsEditor : UnityEditor.Editor
10 | {
11 | SerializedProperty autoSwitchVolumeAccessType;
12 | SerializedProperty manualVolumeAccessType;
13 |
14 | private void OnEnable()
15 | {
16 | autoSwitchVolumeAccessType = serializedObject.FindProperty("autoSwitchVolumeAccessType");
17 | manualVolumeAccessType = serializedObject.FindProperty("manualVolumeAccessType");
18 | }
19 |
20 | public override void OnInspectorGUI()
21 | {
22 | serializedObject.Update();
23 |
24 | EditorGUILayout.PropertyField(autoSwitchVolumeAccessType, new GUIContent("Auto Switch Access Type"));
25 |
26 | using (new EditorGUI.DisabledGroupScope(autoSwitchVolumeAccessType.boolValue))
27 | {
28 | EditorGUILayout.PropertyField(manualVolumeAccessType, new GUIContent("Volume Access Type"));
29 | }
30 |
31 | serializedObject.ApplyModifiedProperties();
32 | }
33 | }
34 | }
--------------------------------------------------------------------------------
/Runtime/Extensions.cs:
--------------------------------------------------------------------------------
1 |
2 | using System.Linq;
3 | using UnityEngine.Rendering;
4 |
5 | namespace MAOTimelineExtension.Runtime
6 | {
7 | public static class MAOExtensions
8 | {
9 | /// Return a titlecased version of the string
10 | /// Examples: "elysia".Title() => "Elysia"
11 | public static string Title(this string str)
12 | {
13 | return str.First().ToString().ToUpper() + str.Substring(1);
14 | }
15 |
16 |
17 | ///
18 | /// Repeats the given string a specified number of times, with the option to insert a separator between repetitions.
19 | ///
20 | /// The string to be repeated.
21 | /// The number of times to repeat. If less than or equal to 0, an empty string is returned.
22 | /// The separator to insert between repetitions of the string. Defaults to an empty string, meaning no separator is added.
23 | /// The concatenated string resulting from repeating the input string the specified number of times. Returns an empty string if the input string is null or empty, or if the repeat count is less than or equal to 0.
24 | public static string Repeat(this string str, int times, string separator = "")
25 | {
26 | if (string.IsNullOrEmpty(str) || times <= 0)
27 | {
28 | return string.Empty;
29 | }
30 |
31 | return string.Join(separator , Enumerable.Repeat(str, times));
32 | }
33 |
34 |
35 | ///
36 | ///
37 | ///
38 | ///
39 | public static T GetValue(this VolumeParameter vp)
40 | {
41 | return vp.GetValue();
42 | }
43 |
44 | }
45 | }
--------------------------------------------------------------------------------
/README_CN.md:
--------------------------------------------------------------------------------
1 | [English](README.md) | 简体中文
2 |
3 | ## Introduction
4 |
5 | 主要是代码生成器,在不编写代码的情况下直接生成 Unity Timeline 对 Volume 或 Component 的扩展代码,便于快速开发原型以便自定义更多逻辑。
6 | 不复杂的逻辑也可以直接导入项目使用。
7 |
8 | 目前这个 repo 里有一些 Unity URP 原有的后处理 Volume 的扩展,用来在 Timeline 中动态调节 Volume
9 | 可以直接导入项目使用, 也可以通过”**MAO Timeline Playable Wizard**”这个工具自行扩展。
10 |
11 | 
12 |
13 | ## Features
14 |
15 | ### 目前在 Component/Volume 模式中支持的、可用的参数类型:
16 | - 常见的基础字段(`int`, `float`, `bool`, `Vector`, `Color`, `Texture` 等)
17 | - `FloatParameter`
18 | - `IntParameter`
19 | - `BoolParameter`
20 | - `Vector2Parameter`
21 | - `Vector3Parameter`
22 | - `Vector4Parameter`
23 | - `ColorParameter`
24 | - `TextureParameter`
25 | - `Enum`(例如景深的 Gaussian 或 Bokeh 模式。目前可以生成代码,但 Clip 的 Inspector 面板中会一次性全部列出来,你可能需要重写指定 Clip 的面板)
26 |
27 | ### 目前不支持或没有经过完全测试的:
28 | - `LayerMaskParameter`
29 | - `FloatRangeParameter`
30 | - `RenderTextureParameter`
31 | - `CubemapParameter`
32 | - `ObjectParameter`
33 | - `AnimationCurveParameter`
34 | - `TextureCurveParameter`
35 |
36 | ## Usage
37 |
38 | ### Typical usecase
39 |
40 | 1. 打开 Timeline 窗口,创建一个新的 Timeline
41 | 2. 在 Scene 中创建一个 Volume,添加 `TimelineExtensionVolumeSettings` 组件
42 | 3. 在 Timeline 中添加一个新的 Track。如果是直接使用的这个 repo,它的名字应该以`MAO`开头,例如`MAOBloom`
43 | 4. 将创建的 `TimelineExtensionVolumeSettings` 组件绑定到这个Track上
44 | 5. 在 Track 中添加新的 Clip,编辑属性,或者与其他的 Clip 进行混合即可
45 |
46 | #### `TimelineExtensionVolumeSettings` 组件设置:
47 | - `VolumeAccessType`:
48 | - `Profile`: 访问 `profile` 的副本,不会影响原本的 `volume profile`文件(但编辑模式下通过 Timeline 控制之后,手动调节的 Volume 参数无法保存)
49 | - `Shared Profile`:访问 `profile` 的引用,做的修改会直接影响到原本的 `volume profile` 文件,类似于 Editor 模式下修改 Volume 属性。当退出运行模式后无法重置设置
50 |
51 | 推荐在 Editor 模式下使用 `Shared Profile`,在 `Play` 模式下使用 `Profile`
52 | 如果需要使用这种方式,可以勾选 `AutoSwitchType` 自动切换
53 | 更多信息可以参考 [Unity官方文档](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@16.0/manual/Volumes-API.html#access-a-shared-volume-profile)
54 |
55 | ### Wizard Usage
56 |
57 | 这是一个可以快速生成 Timeline 扩展代码的工具
58 | 它可以直接获取当前 AppDomain 下的所有类,并通过 C# 反射来获取需要的字段,这样就不再需要自己写扩展代码了~
59 |
60 |
61 | **Volume Component:**
62 |
63 | 1. 在 `Window/MAO Timeline Playable Wizard`打开
64 | 2. 切换 `WorkType`为 `VolumeComponent`,选择需要的 `Track Binding Type`
65 |
66 |
67 |
68 | 3. 将 `Default Values` 设置为 `Volume`
69 |
70 |
71 |
72 | 4. 添加属性
73 |
74 |
75 |
76 | 5. 最后点 `Create` 就可以了,等编译完之后就可以使用,你可以在 `Assets/TimelineExtensions` 找到生成的脚本
77 |
78 | > [!IMPORTANT]
79 | > 对 Enum 类型(例如景深的 Gaussian 或 Bokeh 模式),不支持根据原本的规则生成自定义的 Editor,会一次性将全部字段列出来,可能会不方便查看。
80 | > 可以参考以下方法分成多个 Track 制作,或自己写代码完成 Clip 的 Editor 扩展
81 | >
82 | >
83 |
84 | ## 高级设置
85 | 你可以通过 `TimelineExtensions/Resources/MAOTimelineExtensionsConfigSO` 自定义生成的代码路径,以及默认的命名空间。
86 |
87 | 命名空间会影响在 `Timeline` 中添加 `Track` 时右键菜单的显示。当存在命名空间时,对应 Track 会生成在子菜单中,否则会生成在最外部。
88 | >
89 |
90 |
91 | ## License
92 |
93 | [MIT License](https://github.com/ShiinaRinne/TimelineExtensions/blob/master/LICENSE)
94 |
95 | ## Credits
96 |
97 | • [Default Playables - Unity Technologies](https://assetstore.unity.com/packages/essentials/default-playables-95266)
98 |
99 |
100 | [//]: # (## 彩蛋)
101 | [//]: # (我不是在给爱莉生日做视频吗!为什么最后做了这个东西出来!我的爱莉呢!!!)
102 |
103 | [//]: # (## 彩蛋2)
104 | [//]: # (一年过去了,爱莉还是没有来到我身边QAQ)
105 |
106 | [//]: # (## 彩蛋2)
107 | [//]: # (两年了)
--------------------------------------------------------------------------------
/Runtime/MAOTimelineExtensionVolumeSettings.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 | using UnityEngine.Rendering;
3 |
4 | namespace MAOTimelineExtension.Runtime
5 | {
6 | [ExecuteAlways]
7 | [DisallowMultipleComponent]
8 | public class MAOTimelineExtensionVolumeSettings : MonoBehaviour
9 | {
10 | // https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@16.0/manual/Volumes-API.html#access-a-shared-volume-profile
11 | [Tooltip("" +
12 | @"- Access a shared Volume Profile
13 | One method is to access the Volume's shared Profile. You do this via the Volume's sharedProfile property. This gives you a reference to the instance of the Volume Profile asset.
14 | If you modify this Volume Profile:
15 | - HDRP applies any changes you make to every Volume that uses this Volume Profile asset.
16 | - The modifications you make affect the actual Volume Profile asset which means they do not reset when you exit Play mode
17 | Note the sharedProfile property can return null if the Volume does not reference a Volume Profile asset.
18 |
19 |
20 | - Access an owned Volume Profile
21 | The other method is to clone the Volume Profile asset. The advantage of this is that your modifications only affect the Volume component you clone the Volume Profile from and don't affect any other Volumes that use the same Volume Profile asset.
22 | To do this, use the Volume's profile property. This returns a reference to a new instance of a Volume Profile (if not already created).
23 | If you were already modifying the Volume's sharedProfile, any changes you made are copied over to the new instance.
24 | If you modify this Volume Profile:
25 | - HDRP only applies changes to the particular Volume.
26 | - The modification you make reset when you exit Play mode.
27 | - It is your responsibility to destroy the duplicate Volume Profile when you no longer need it.
28 | Note that you can use this property to assign a different Volume Profile to the Volume.")]
29 |
30 | [SerializeField] private VolumeAccessType manualVolumeAccessType = VolumeAccessType.Profile;
31 |
32 | private VolumeAccessType volumeAccessType
33 | {
34 | get
35 | {
36 | if (!autoSwitchVolumeAccessType)
37 | return manualVolumeAccessType;
38 |
39 | return Application.isPlaying ? VolumeAccessType.Profile : VolumeAccessType.SharedProfile;
40 | }
41 | }
42 |
43 |
44 | [SerializeField] private bool autoSwitchVolumeAccessType = true;
45 |
46 | private Volume _volume;
47 | private Volume VolumeComponent
48 | {
49 | get
50 | {
51 | if (_volume == null)
52 | {
53 | _volume = GetComponent();
54 | }
55 | return _volume;
56 | }
57 | }
58 |
59 | private VolumeProfile _volumeProfile;
60 | public VolumeProfile VolumeProfile
61 | {
62 | get
63 | {
64 | if (_volumeProfile == null)
65 | {
66 | _volumeProfile = volumeAccessType == VolumeAccessType.Profile
67 | ? VolumeComponent.profile
68 | : VolumeComponent.sharedProfile;
69 | }
70 | return _volumeProfile;
71 | }
72 | }
73 |
74 | private VolumeAccessType _volumeAccessTypeCache;
75 |
76 | private void OnEnable()
77 | {
78 | _volumeAccessTypeCache = volumeAccessType;
79 | UpdateVolumeProfile();
80 | }
81 |
82 | private void Update()
83 | {
84 | if (VolumeProfile == null || _volumeAccessTypeCache != volumeAccessType)
85 | {
86 | UpdateVolumeProfile();
87 | }
88 | }
89 |
90 | public enum VolumeAccessType
91 | {
92 | Profile,
93 | SharedProfile
94 | }
95 |
96 | private void UpdateVolumeProfile()
97 | {
98 | _volumeAccessTypeCache = volumeAccessType;
99 | _volumeProfile = null; // 强制更新 VolumeProfile
100 | }
101 | }
102 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TimelineExtensions
2 | English | [中文](README_CN.md)
3 |
4 | ## Introduction
5 |
6 | A code generator tool for `Unity Timeline` that creates extension code for `Volume` or `Component` without writing code manually.
7 | It helps rapid prototyping and allows for easy customization of additional logic.
8 | Non-complex generated code can be directly imported into projects.
9 |
10 | [//]: # (This project was originally developed mainly to expand the post-processing volume,
11 | and will gradually improve other types in the future.)
12 |
13 | At present, there are some extensions to the original Post-Processing Volume of Unity URP, which are used to dynamically adjust the volume in the Timeline
14 | It can be directly imported into the project for use, or quickly expand through the "**MAO Timeline playable Wizard**" tool.
15 |
16 | 
17 |
18 | ## Features
19 | ### Currently supported parameter types in Component/Volume mode:
20 | - Common basic fields (`int`, `float`, `bool`, `Vector`, `Color`, `Texture`, etc.)
21 | - `FloatParameter`
22 | - `IntParameter`
23 | - `BoolParameter`
24 | - `Vector2Parameter`
25 | - `Vector3Parameter`
26 | - `Vector4Parameter`
27 | - `ColorParameter`
28 | - `TextureParameter`
29 | - `Enum` (e.g., `Depth of Field's` **Gaussian** or **Bokeh** modes. Code generation is supported,
30 | but the Clip's Inspector panel will list all options at once - you may need to customize the Clip's panel)
31 |
32 | ### Currently unsupported or not fully tested:
33 | - `LayerMaskParameter`
34 | - `FloatRangeParameter`
35 | - `RenderTextureParameter`
36 | - `CubemapParameter`
37 | - `ObjectParameter`
38 | - `AnimationCurveParameter`
39 | - `TextureCurveParameter`
40 |
41 |
42 | ## Usage
43 |
44 | ### Typical usecase
45 |
46 | 1. Open the Timeline window and create a new Timeline.
47 | 2. Create a new Global Volume, add `TimelineExtensionVolumeSettings` component.
48 | 3. Add a new Track which starts with "MAO", such as `MAOBloom`.
49 | 4. Set TrackBinding to the `TimelineExtensionVolumeSettings` component.
50 | 5. Add a new Clip to the Track, edit properties in the Clip or mix with other Clips.
51 |
52 | #### `TimelineExtensionVolumeSettings` component settings:
53 | - VolumeAccessType:
54 | - `Profile`: Access a copy of the profile, which will not affect the original volume profile file (but if you adjust the Volume property through Timeline in Editor mode and then manually adjust it, this modification cannot be saved)
55 | - `Shared Profile`: Access a reference to the profile, which will directly affect the original `volume profile`. The settings cannot be reset after exiting play mode
56 |
57 | > [!TIP]
58 | > It is recommended to use `Shared Profile` in Editor mode and `Profile` in Play mode.
59 | > If you need to use this switching method, you can check `AutoSwitchType` in `TimelineExtensionVolumeSettings`
60 | > For more information, please refer to [Unity Documentation](https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@16.0/manual/Volumes-API.html)
61 |
62 |
63 | #### VolumeComponent:
64 | 1. You can find it in the menu bar `Window/MAO Timeline Playable Wizard`
65 |
66 | 2. Switch `WorkType` to VolumeComponent, select the `Track Binding Type`
67 |
68 |
69 |
70 | 3. Set `Default Values` to the Volume
71 |
72 |
73 |
74 | 4. Add the properties
75 |
76 |
77 |
78 | 5. Finally, click `Create`, wait for the compilation to complete and start enjoying~
79 | You can find it in `Assets/TimelineExtensions`
80 |
81 | > [!IMPORTANT]
82 | > For Enum types (such as Gaussian or Bokeh mode in Depth of Field), the default rule-based custom Editor generation is not supported.
83 | > So It will list all fields at once, which might be inconvenient to view.
84 | > You can either split them into multiple Tracks as shown below, or write your own code for custom Clip Editor extensions
85 | >
86 | >
87 |
88 | ## Advanced Settings
89 | You can customize the generated code path and default namespace through `TimelineExtensions/Resources/MAOTimelineExtensionsConfigSO`.
90 |
91 | The namespace affects how the Track appears in the context menu when adding a Track in Timeline. When a namespace is present, the corresponding Track will be generated in a submenu; otherwise, it will appear in the root menu.
92 |
93 | >
94 |
95 |
96 | ## License
97 | [MIT License](https://github.com/ShiinaRinne/TimelineExtensions/blob/master/LICENSE)
98 |
99 | ## Credits
100 | - [Default Playables - Unity Technologies](https://assetstore.unity.com/packages/essentials/default-playables-95266)
--------------------------------------------------------------------------------
/Editor/TimelinePlayableWizardCreateScript.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Linq;
4 | using UnityEditor;
5 | using MAOTimelineExtension.Runtime;
6 |
7 | namespace MAOTimelineExtension.Editor
8 | {
9 | public partial class MaoTimelinePlayableWizard : EditorWindow
10 | {
11 | const string Note =
12 | @"// This code is automatically generated by MAO Timeline Playable Wizard.
13 | // For more information, please visit
14 | // https://github.com/ShiinaRinne/EasyTimeline";
15 |
16 | CreationError CreateScripts()
17 | {
18 | if (ScriptAlreadyExists(playableName + k_TimelineClipAssetSuffix))
19 | return CreationError.PlayableAssetAlreadyExists;
20 |
21 | if (ScriptAlreadyExists(playableName + k_TimelineClipBehaviourSuffix))
22 | return CreationError.PlayableBehaviourAlreadyExists;
23 |
24 | if (ScriptAlreadyExists(playableName + k_PlayableBehaviourMixerSuffix))
25 | return CreationError.PlayableBehaviourMixerAlreadyExists;
26 |
27 | if (ScriptAlreadyExists(playableName + k_TrackAssetSuffix))
28 | return CreationError.TrackAssetAlreadyExists;
29 |
30 | if (m_CreateDrawer && ScriptAlreadyExists(playableName + k_PropertyDrawerSuffix))
31 | return CreationError.PlayableDrawerAlreadyExists;
32 |
33 | if (!Directory.Exists(Config.rootFolderPath))
34 | {
35 | Directory.CreateDirectory(Config.rootFolderPath);
36 | }
37 |
38 | AssetDatabase.CreateFolder(Config.rootFolderPath, playableName);
39 |
40 | if (workType == WorkType.Component)
41 | {
42 | CreateScript(playableName + k_TimelineClipAssetSuffix, StandardBlendPlayableAsset());
43 | CreateScript(playableName + k_TimelineClipBehaviourSuffix, StandardBlendPlayableBehaviour());
44 | CreateScript(playableName + k_PlayableBehaviourMixerSuffix, StandardBlendPlayableBehaviourMixer());
45 | CreateScript(playableName + k_TrackAssetSuffix, StandardBlendTrackAssetScript());
46 | }
47 | else if (workType == WorkType.VolumeComponent)
48 | {
49 | CreateScript(playableName + k_TimelineClipAssetSuffix, VolumeBlendPlayableAsset());
50 | CreateScript(playableName + k_TimelineClipBehaviourSuffix, VolumeBlendPlayableBehaviour());
51 | CreateScript(playableName + k_PlayableBehaviourMixerSuffix, VolumeBlendPlayableBehaviourMixer());
52 | CreateScript(playableName + k_TrackAssetSuffix, VolumeTrackAssetScript());
53 | }
54 |
55 | AssetDatabase.SaveAssets();
56 | AssetDatabase.Refresh();
57 |
58 | return CreationError.NoError;
59 | }
60 |
61 | static bool ScriptAlreadyExists(string scriptName)
62 | {
63 | string[] guids = AssetDatabase.FindAssets(scriptName);
64 |
65 | if (guids.Length == 0)
66 | return false;
67 |
68 | foreach (var guid in guids)
69 | {
70 | string path = AssetDatabase.GUIDToAssetPath(guid);
71 | Type assetType = AssetDatabase.GetMainAssetTypeAtPath(path);
72 | if (assetType == typeof(MonoScript))
73 | return true;
74 | }
75 |
76 | return false;
77 | }
78 |
79 | void CreateScript(string fileName, string content)
80 | {
81 | string path = $"{Config.rootFolderPath}/{playableName}/{fileName}.cs";
82 | using var writer = File.CreateText(path);
83 | writer.Write(content);
84 | }
85 |
86 | string VolumeNameSpaceStart()
87 | {
88 | return string.IsNullOrEmpty(Config.volumeDefaultNameSpace) ? "" : $@"namespace {Config.volumeDefaultNameSpace}
89 | {{";
90 | }
91 |
92 | string VolumeNameSpaceEnd()
93 | {
94 | return string.IsNullOrEmpty(Config.volumeDefaultNameSpace) ? "" : "}";
95 | }
96 |
97 | string ComponentNameSpaceStart()
98 | {
99 | return string.IsNullOrEmpty(Config.componentDefaultNameSpace)
100 | ? ""
101 | : $@"namespace {Config.componentDefaultNameSpace}
102 | {{";
103 | }
104 |
105 | string ComponentNameSpaceEnd()
106 | {
107 | return string.IsNullOrEmpty(Config.componentDefaultNameSpace) ? "" : "}";
108 | }
109 |
110 | string VolumeBlendScriptPlayablePropertiesInitialize()
111 | {
112 | string returnVal = "";
113 | foreach (var prop in postProcessVolumeProperties)
114 | {
115 | returnVal += $"{k_Tab.Repeat(3)}behaviour.{prop.name} = {prop.name};\r\n";
116 | }
117 |
118 | return returnVal;
119 | }
120 |
121 | string VolumeBlendPlayableAsset()
122 | {
123 | return "" +
124 | @$"{Note}
125 |
126 | {AllNeededNameSpace()}
127 |
128 | {VolumeNameSpaceStart()}
129 | [Serializable]
130 | public class {playableName}{k_TimelineClipAssetSuffix} : PlayableAsset, ITimelineClipAsset
131 | {{
132 | {VolumeBlendScriptPlayablePropertiesToStringWithDefaultValue()}
133 |
134 | public ClipCaps clipCaps
135 | {{
136 | get {{ return ClipCaps.Blending; }}
137 | }}
138 |
139 | public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)
140 | {{
141 | var playable = ScriptPlayable<{playableName}{k_TimelineClipBehaviourSuffix}>.Create(graph);
142 | var behaviour = playable.GetBehaviour();
143 |
144 | {VolumeBlendScriptPlayablePropertiesInitialize()}
145 |
146 | return playable;
147 | }}
148 | }}
149 | {VolumeNameSpaceEnd()}
150 | ";
151 | }
152 |
153 | string VolumeBlendPlayableBehaviour()
154 | {
155 | return "" +
156 | @$"{Note}
157 |
158 | {AllNeededNameSpace()}
159 |
160 | {VolumeNameSpaceStart()}
161 | [Serializable]
162 | public class {playableName}{k_TimelineClipBehaviourSuffix} : PlayableBehaviour
163 | {{
164 | {VolumeBlendScriptPlayablePropertiesToString()}
165 | }}
166 | {VolumeNameSpaceEnd()}
167 | ";
168 | }
169 |
170 | string VolumeBlendPlayableBehaviourMixer()
171 | {
172 | return "" +
173 | @$"{Note}
174 |
175 | {AllNeededNameSpace()}
176 |
177 | {VolumeNameSpaceStart()}
178 | public class {playableName}{k_PlayableBehaviourMixerSuffix} : PlayableBehaviour
179 | {{
180 | {VolumeBlendTrackBindingPropertiesDefaultsDeclarationToString()}
181 |
182 | {VolumeBlendTrackBindingPropertiesBlendedDeclarationToString()}
183 |
184 | {TrackBinding.name} m_TrackBinding;
185 | bool m_FirstFrameHappened;
186 |
187 | public override void ProcessFrame(Playable playable, FrameData info, object playerData)
188 | {{
189 | if (playerData is null)
190 | {{
191 | Debug.LogError(""{playableName} Track Binding is null, you need to assign a `VolumeSettings` Component"");
192 | return;
193 | }}
194 | {MixerTrackBindingLocalVariableToString()}
195 |
196 | if(!m_FirstFrameHappened)
197 | {{
198 | {VolumeSaveOriginalValue()}
199 | m_FirstFrameHappened = true;
200 | }}
201 |
202 | int inputCount = playable.GetInputCount();
203 |
204 | {VolumeBlendedVariablesCreationToString()}
205 | float totalWeight = 0f;
206 | float greatestWeight = 0f;
207 | int currentInputs = 0;
208 |
209 | for(int i = 0; i < inputCount; i++)
210 | {{
211 | float inputWeight = playable.GetInputWeight(i);
212 | ScriptPlayable<{playableName}{k_TimelineClipBehaviourSuffix}> inputPlayable =(ScriptPlayable<{playableName}{k_TimelineClipBehaviourSuffix}>)playable.GetInput(i);
213 | {playableName}{k_TimelineClipBehaviourSuffix} input = inputPlayable.GetBehaviour();
214 |
215 | {VolumeBlendedVariablesWeightedIncrementationToString()}
216 | totalWeight += inputWeight;
217 |
218 | if (inputWeight > greatestWeight)
219 | {{
220 | {VolumeBlendAssignableVariablesAssignedBasedOnGreatestWeightToString()}
221 | greatestWeight = inputWeight;
222 | }}
223 |
224 | if (!Mathf.Approximately (inputWeight, 0f))
225 | currentInputs++;
226 | }}
227 | {VolumeTrackBindingPropertiesBlendedAssignmentToString()}
228 |
229 | {VolumeBlendTrackBindingPropertiesAssignableAssignmentToString()}
230 | }}
231 |
232 |
233 |
234 | public override void OnPlayableDestroy(Playable playable)
235 | {{
236 | m_FirstFrameHappened = false;
237 |
238 | if(m_TrackBinding == null)
239 | return;
240 |
241 | {VolumeRecoveryOriginalValue()}
242 | }}
243 | }}
244 | {VolumeNameSpaceEnd()}
245 |
246 | ";
247 | }
248 |
249 | string VolumeTrackAssetScript()
250 | {
251 | return "" +
252 | @$"{Note}
253 |
254 | {AllNeededNameSpace()}
255 |
256 | {VolumeNameSpaceStart()}
257 | [TrackColor({trackColor.r}f, {trackColor.g}f, {trackColor.b}f)]
258 | [TrackClipType(typeof({playableName}{k_TimelineClipAssetSuffix}))]
259 | {TrackBindingToString()}
260 | public class {playableName}{k_TrackAssetSuffix} : TrackAsset
261 | {{
262 | public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
263 | {{
264 | return ScriptPlayable<{playableName}{k_PlayableBehaviourMixerSuffix}>.Create(graph, inputCount);
265 | }}
266 | }}
267 | {VolumeNameSpaceEnd()}
268 |
269 | ";
270 | }
271 |
272 | string BlendScriptPlayablePropertiesGetPropertyAttributes(UsableProperty prop)
273 | {
274 | return prop.propertyAttributesType switch
275 | {
276 | UsableProperty.PropertyAttributesType.Range => $"[Range({prop.range.min}f, {prop.range.max}f)] ",
277 | UsableProperty.PropertyAttributesType.Min => $"[Min({prop.min.min}f)] ",
278 | UsableProperty.PropertyAttributesType.Max => $"[Max({prop.range.max}f)] ",
279 | UsableProperty.PropertyAttributesType.ColorVolumeParameter => $"[ColorUsage({(prop.colorParameter.hdr ? "true," : "false,")} {(prop.colorParameter.showAlpha ? "true" : "false")})] ",
280 | UsableProperty.PropertyAttributesType.ColorUsage => $"[ColorUsage({(prop.colorUsage.showAlpha ? "true," : "false,")} {(prop.colorUsage.hdr ? "true" : "false")})] ",
281 | UsableProperty.PropertyAttributesType.GradientUsage => $"[GradientUsage({(prop.gradientUsage.hdr ? "true" : "false")})] ",
282 | _ => string.Empty
283 | };
284 | }
285 |
286 | string VolumeBlendScriptPlayablePropertiesToStringWithDefaultValue()
287 | {
288 | string returnVal = "";
289 | foreach (var prop in postProcessVolumeProperties)
290 | {
291 | string attributes = BlendScriptPlayablePropertiesGetPropertyAttributes(prop);
292 | if (prop.type.EndsWith("Parameter"))
293 | returnVal += $"{k_Tab.Repeat(2)}{attributes} public {prop.type}"; // TODO: 看看这里是啥东西,忘了. 原本没加也没事,但旧的加了这个
294 | else if (prop.defaultValue == "")
295 | {
296 | returnVal += $"{k_Tab.Repeat(2)}{attributes}public {prop.type} {prop.name};\r\n";
297 | }
298 | else
299 | {
300 | returnVal += $"{k_Tab.Repeat(2)}{attributes}public {prop.type} {prop.name} = {prop.defaultValue};\r\n";
301 | }
302 | }
303 |
304 | return returnVal;
305 | }
306 |
307 | string VolumeBlendScriptPlayablePropertiesToString()
308 | {
309 | string returnVal = "";
310 | foreach (var prop in postProcessVolumeProperties)
311 | {
312 | returnVal += $"{k_Tab.Repeat(2)}public {prop.type} {prop.name};\r\n";
313 | }
314 |
315 | return returnVal;
316 | }
317 |
318 | string VolumeBlendTrackBindingPropertiesDefaultsDeclarationToString()
319 | {
320 | string returnVal = "";
321 | foreach (var prop in postProcessVolumeProperties)
322 | {
323 | returnVal += $"{k_Tab.Repeat(2)}{prop.type} {prop.NameAsPrivateDefault};\n";
324 | }
325 |
326 | return returnVal;
327 | }
328 |
329 | string VolumeBlendTrackBindingPropertiesBlendedDeclarationToString()
330 | {
331 | string returnVal = "";
332 | foreach (var prop in postProcessVolumeProperties)
333 | {
334 | returnVal += $"{k_Tab.Repeat(2)}{prop.type} {prop.NameAsPrivateAssigned};\n";
335 | }
336 |
337 | return returnVal;
338 | }
339 |
340 | string VolumeSaveOriginalValue()
341 | {
342 | string returnVal = "";
343 | foreach (var prop in postProcessVolumeProperties)
344 | {
345 | returnVal += $"{k_Tab.Repeat(4)}{prop.NameAsPrivateDefault} = m_TrackBinding.{prop.name}.value;\n";
346 | }
347 |
348 | return returnVal;
349 | }
350 |
351 | string VolumeRecoveryOriginalValue()
352 | {
353 | string returnVal = "";
354 | foreach (var prop in postProcessVolumeProperties)
355 | {
356 | returnVal += $"{k_Tab.Repeat(3)}m_TrackBinding.{prop.name}.value = {prop.NameAsPrivateDefault};\n";
357 | }
358 |
359 | return returnVal;
360 | }
361 |
362 | string VolumeBlendedVariablesCreationToString()
363 | {
364 | string returnVal = "";
365 | foreach (var prop in postProcessVolumeProperties)
366 | {
367 | if(!prop.blendable)
368 | continue;
369 |
370 | string type = prop.type == "int" ? "float" : prop.type;
371 | string zeroVal = type == "int" ? "0f" : prop.ZeroValueAsString();
372 | returnVal += $"{k_Tab.Repeat(3)}{type} {prop.NameAsLocalBlended} = {zeroVal};\n";
373 | }
374 |
375 | return returnVal;
376 | }
377 |
378 | string VolumeBlendedVariablesWeightedIncrementationToString()
379 | {
380 | string returnVal = "";
381 | foreach (var prop in postProcessVolumeProperties)
382 | {
383 | if(!prop.blendable)
384 | continue;
385 |
386 | returnVal += $"{k_Tab.Repeat(4)}{prop.NameAsLocalBlended} += input.{prop.name} * inputWeight;\n";
387 | }
388 |
389 | return returnVal;
390 | }
391 |
392 | string VolumeTrackBindingPropertiesBlendedAssignmentToString()
393 | {
394 | string returnVal = "";
395 | foreach (var prop in postProcessVolumeProperties)
396 | {
397 | if(!prop.blendable)
398 | continue;
399 |
400 | if (prop.type == "int")
401 | returnVal += $"{k_Tab.Repeat(3)}m_TrackBinding.{prop.name}.value = Mathf.RoundToInt({prop.NameAsLocalBlended} + {prop.NameAsPrivateDefault} * (1f-totalWeight));\n";
402 | else
403 | returnVal += $"{k_Tab.Repeat(3)}m_TrackBinding.{prop.name}.value = {prop.NameAsLocalBlended} + {prop.NameAsPrivateDefault} * (1f-totalWeight);\n";
404 | }
405 | return returnVal;
406 | }
407 |
408 |
409 | string AllNeededNameSpace()
410 | {
411 | return @"using System;
412 | using UnityEngine;
413 | using UnityEngine.Timeline;
414 | using UnityEngine.Playables;
415 | using UnityEngine.Rendering;
416 | using MAOTimelineExtension.Runtime;
417 | " + AdditionalNamespacesToString();
418 | }
419 |
420 | string TrackBindingToString()
421 | {
422 | return workType switch
423 | {
424 | WorkType.Component => $"[TrackBindingType(typeof({TrackBinding.name}))]",
425 | WorkType.VolumeComponent => $"[TrackBindingType(typeof(MAOTimelineExtensionVolumeSettings))]",
426 | _ => ""
427 | };
428 | }
429 |
430 | string AdditionalNamespacesToString()
431 | {
432 | UsableType[] exposedReferenceTypes = Variable.GetUsableTypesFromVariableArray(exposedReferences.ToArray());
433 | UsableType[] behaviourVariableTypes = Variable.GetUsableTypesFromVariableArray(playableBehaviourVariables.ToArray());
434 | UsableType[] allUsedTypes = new UsableType[exposedReferenceTypes.Length + behaviourVariableTypes.Length + 1];
435 |
436 | exposedReferenceTypes.CopyTo(allUsedTypes, 0);
437 | behaviourVariableTypes.CopyTo(allUsedTypes, exposedReferenceTypes.Length);
438 | allUsedTypes[^1] = TrackBinding;
439 |
440 | return string.Join("\n",
441 | UsableType.GetDistinctAdditionalNamespaces(allUsedTypes)
442 | .Where(x => !string.IsNullOrEmpty(x))
443 | .Select(x => $"using {x};"));
444 | }
445 |
446 | #region Original Unused Code
447 |
448 | string ExposedReferencesToString()
449 | {
450 | string expRefText = "";
451 | foreach (var expRef in exposedReferences)
452 | expRefText += k_Tab + "public ExposedReference<" + expRef.usableType.name + "> " + expRef.name + ";\n";
453 | return expRefText;
454 | }
455 |
456 | string ExposedReferencesResolvingToString()
457 | {
458 | string returnVal = "";
459 | returnVal += k_Tab + k_Tab + playableName + k_TimelineClipBehaviourSuffix +
460 | " clone = playable.GetBehaviour();\n";
461 |
462 | foreach (var reference in exposedReferences)
463 | {
464 | returnVal += k_Tab + k_Tab + "clone." + reference.name + " = " + reference.name + ".Resolve(graph.GetResolver());\n";
465 | }
466 |
467 | return returnVal;
468 | }
469 |
470 | /*string OnCreateFunctionToString()
471 | {
472 | if(!setClipDefaults)
473 | return "";
474 |
475 | string returnVal = "\n";
476 | returnVal += k_Tab + "public override void OnCreate()\n";
477 | returnVal += k_Tab + "{\n";
478 | returnVal += k_Tab + k_Tab + "owner.duration = " + clipDefaultDurationSeconds + ";\n";
479 | returnVal += k_Tab + k_Tab + "owner.easeInDuration = " + clipDefaultEaseInSeconds + ";\n";
480 | returnVal += k_Tab + k_Tab + "owner.easeOutDuration = " + clipDefaultEaseOutSeconds + ";\n";
481 | returnVal += k_Tab + k_Tab + "owner.clipIn = " + clipDefaultClipInSeconds + ";\n";
482 | returnVal += k_Tab + k_Tab + "owner.timeScale = " + clipDefaultSpeedMultiplier + ";\n";
483 | returnVal += k_Tab + "}\n";
484 | return returnVal;
485 | }*/
486 |
487 | string ClipCapsToString()
488 | {
489 | string message = clipCaps.ToString();
490 | string[] splits = message.Split(' ');
491 |
492 | for (int i = 0; i < splits.Length; i++)
493 | {
494 | if (splits[i][splits[i].Length - 1] == ',')
495 | splits[i] = splits[i].Substring(0, splits[i].Length - 1);
496 | }
497 |
498 | string returnVal = "";
499 | for (int i = 0; i < splits.Length; i++)
500 | {
501 | returnVal += "ClipCaps." + splits[i];
502 |
503 | if (i < splits.Length - 1)
504 | returnVal += " | ";
505 | }
506 |
507 | return returnVal;
508 | }
509 |
510 | string ExposedReferencesAsScriptVariablesToString()
511 | {
512 | string returnVal = "";
513 | foreach (var reference in exposedReferences)
514 | {
515 | returnVal += k_Tab + "clone." + reference.name + " = " + reference.name + ";\n";
516 | }
517 |
518 | return returnVal;
519 | }
520 |
521 | #endregion
522 |
523 | string MixerTrackBindingLocalVariableToString() =>
524 | @$"{k_Tab.Repeat(3)}((MAOTimelineExtensionVolumeSettings) playerData).VolumeProfile.TryGet(out m_TrackBinding);
525 | if (m_TrackBinding == null)
526 | return;
527 | ";
528 |
529 |
530 | string StandardBlendPlayableAsset()
531 | {
532 | return AllNeededNameSpace() +
533 | "\n" +
534 | ComponentNameSpaceStart() +
535 | "\n" +
536 | "[Serializable]\n" +
537 | "public class " + playableName + k_TimelineClipAssetSuffix + " : PlayableAsset, ITimelineClipAsset\n" +
538 | "{\n" +
539 | k_Tab + "public " + playableName + k_TimelineClipBehaviourSuffix + " template = new " + playableName +
540 | k_TimelineClipBehaviourSuffix + "();\n" +
541 | "\n" +
542 | k_Tab + "public ClipCaps clipCaps\n" +
543 | k_Tab + "{\n" +
544 | k_Tab + k_Tab + "get { return ClipCaps.Blending; }\n" +
545 | k_Tab + "}\n" +
546 | "\n" +
547 | k_Tab + "public override Playable CreatePlayable(PlayableGraph graph, GameObject owner)\n" +
548 | k_Tab + "{\n" +
549 | k_Tab + k_Tab + "var playable = ScriptPlayable<" + playableName + k_TimelineClipBehaviourSuffix +
550 | ">.Create(graph, template);\n" +
551 | k_Tab + k_Tab + "return playable;\n" +
552 | k_Tab + "}\n" +
553 | "}\n" +
554 | ComponentNameSpaceEnd();
555 | }
556 |
557 | string StandardBlendPlayableBehaviour()
558 | {
559 | return AllNeededNameSpace() +
560 | "\n" +
561 | ComponentNameSpaceStart() +
562 | "\n" +
563 | "[Serializable]\n" +
564 | "public class " + playableName + k_TimelineClipBehaviourSuffix + " : PlayableBehaviour\n" +
565 | "{\n" +
566 | StandardBlendScriptPlayablePropertiesToString() +
567 | "}\n" +
568 | ComponentNameSpaceEnd();
569 | }
570 |
571 | string StandardBlendPlayableBehaviourMixer()
572 | {
573 | return AllNeededNameSpace() +
574 | "\n" +
575 | ComponentNameSpaceStart() +
576 | "\n" +
577 | "public class " + playableName + k_PlayableBehaviourMixerSuffix + " : PlayableBehaviour\n" +
578 | "{\n" +
579 | StandardBlendTrackBindingPropertiesDefaultsDeclarationToString() +
580 | "\n" +
581 | StandardBlendTrackBindingPropertiesBlendedDeclarationToString() +
582 | "\n" +
583 | k_Tab + TrackBinding.name + " m_TrackBinding;\n" +
584 | "\n" +
585 | k_Tab + "public override void ProcessFrame(Playable playable, FrameData info, object playerData)\n" +
586 | k_Tab + "{\n" +
587 | k_Tab + k_Tab + "m_TrackBinding = playerData as " + TrackBinding.name + ";\n" +
588 | "\n" +
589 | k_Tab + k_Tab + "if(m_TrackBinding == null)\n" +
590 | k_Tab + k_Tab + k_Tab + "return;\n" +
591 | "\n" +
592 | StandardBlendTrackBindingPropertiesDefaultsAssignmentToString() +
593 | "\n" +
594 | k_Tab + k_Tab + "int inputCount = playable.GetInputCount();\n" +
595 | "\n" +
596 | StandardBlendBlendedVariablesCreationToString() +
597 | k_Tab + k_Tab + "float totalWeight = 0f;\n" +
598 | k_Tab + k_Tab + "float greatestWeight = 0f;\n" +
599 | StandardBlendPlayableCurrentInputsDeclarationToString() +
600 | "\n" +
601 | k_Tab + k_Tab + "for(int i = 0; i < inputCount; i++)\n" +
602 | k_Tab + k_Tab + "{\n" +
603 | k_Tab + k_Tab + k_Tab + "float inputWeight = playable.GetInputWeight(i);\n" +
604 | k_Tab + k_Tab + k_Tab + "ScriptPlayable<" + playableName + k_TimelineClipBehaviourSuffix +
605 | "> inputPlayable =(ScriptPlayable<" + playableName + k_TimelineClipBehaviourSuffix +
606 | ">)playable.GetInput(i);\n" +
607 | k_Tab + k_Tab + k_Tab + playableName + k_TimelineClipBehaviourSuffix +
608 | " input = inputPlayable.GetBehaviour();\n" +
609 | k_Tab + k_Tab + k_Tab + "\n" +
610 | StandardBlendBlendedVariablesWeightedIncrementationToString() +
611 | k_Tab + k_Tab + k_Tab + "totalWeight += inputWeight;\n" +
612 | "\n" +
613 | StandardBlendAssignableVariablesAssignedBasedOnGreatestWeightToString() +
614 | StandardBlendPlayableCurrentInputIterationToString() +
615 | k_Tab + k_Tab + "}\n" +
616 | StandardBlendTrackBindingPropertiesBlendedAssignmentToString() +
617 | StandardBlendTrackBindingPropertiesAssignableAssignmentToString() +
618 | k_Tab + "}\n" +
619 | "}\n" +
620 | ComponentNameSpaceEnd();
621 | }
622 |
623 | string StandardBlendTrackAssetScript()
624 | {
625 | return AllNeededNameSpace() +
626 | "\n" +
627 | ComponentNameSpaceStart() +
628 | "\n" +
629 | "[TrackColor(" + trackColor.r + "f, " + trackColor.g + "f, " + trackColor.b + "f)]\n" +
630 | "[TrackClipType(typeof(" + playableName + k_TimelineClipAssetSuffix + "))]\n" +
631 | StandardBlendComponentBindingToString() +
632 | "public class " + playableName + k_TrackAssetSuffix + " : TrackAsset\n" +
633 | "{\n" +
634 | k_Tab +
635 | "public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)\n" +
636 | k_Tab + "{\n" +
637 | k_Tab + k_Tab + "return ScriptPlayable<" + playableName + k_PlayableBehaviourMixerSuffix +
638 | ">.Create(graph, inputCount);\n" +
639 | k_Tab + "}\n" +
640 | "\n" +
641 | k_Tab + "// Please note this assumes only one component of type " + TrackBinding.name +
642 | " on the same gameobject.\n" +
643 | k_Tab +
644 | "public override void GatherProperties(PlayableDirector director, IPropertyCollector driver)\n" +
645 | k_Tab + "{\n" +
646 | "#if UNITY_EDITOR\n" +
647 | k_Tab + k_Tab + TrackBinding.name + " trackBinding = director.GetGenericBinding(this) as " +
648 | TrackBinding.name + ";\n" +
649 | k_Tab + k_Tab + "if(trackBinding == null)\n" +
650 | k_Tab + k_Tab + k_Tab + "return;\n" +
651 | "\n" +
652 | StandardBlendPropertiesAssignedToPropertyDriverToString() +
653 | "#endif\n" +
654 | k_Tab + k_Tab + "base.GatherProperties(director, driver);\n" +
655 | k_Tab + "}\n" +
656 | "}\n" +
657 | ComponentNameSpaceEnd();
658 | }
659 |
660 | string StandardBlendScriptPlayablePropertiesToString()
661 | {
662 | string returnVal = "";
663 | foreach (var prop in standardBlendPlayableProperties)
664 | {
665 | string attributes = BlendScriptPlayablePropertiesGetPropertyAttributes(prop);
666 | if (prop.defaultValue == "")
667 | returnVal += k_Tab + $"{attributes}public " + prop.type + " " + prop.name + ";\n";
668 | else
669 | returnVal += k_Tab + $"{attributes}public " + prop.type + " " + prop.name + " = " + prop.defaultValue + ";\n";
670 | }
671 |
672 | return returnVal;
673 | }
674 |
675 | string StandardBlendTrackBindingPropertiesDefaultsDeclarationToString()
676 | {
677 | string returnVal = "";
678 | foreach (var prop in standardBlendPlayableProperties)
679 | {
680 | returnVal += k_Tab + prop.type + " " + prop.NameAsPrivateDefault + ";\n";
681 | }
682 |
683 | return returnVal;
684 | }
685 |
686 | string StandardBlendTrackBindingPropertiesBlendedDeclarationToString()
687 | {
688 | string returnVal = "";
689 | foreach (var prop in standardBlendPlayableProperties)
690 | {
691 | returnVal += k_Tab + prop.type + " " + prop.NameAsPrivateAssigned + ";\n";
692 | }
693 |
694 | return returnVal;
695 | }
696 |
697 | string StandardBlendTrackBindingPropertiesDefaultsAssignmentToString()
698 | {
699 | string returnVal = "";
700 | foreach (var prop in standardBlendPlayableProperties)
701 | {
702 | switch (prop.type)
703 | {
704 | case "float":
705 | returnVal += k_Tab + k_Tab + "if(!Mathf.Approximately(m_TrackBinding." + prop.name + ", " +
706 | prop.NameAsPrivateAssigned + "))\n";
707 | returnVal += k_Tab + k_Tab + k_Tab + prop.NameAsPrivateDefault + " = m_TrackBinding." +
708 | prop.name + ";\n";
709 | break;
710 | case "double":
711 | returnVal += k_Tab + k_Tab + "if(!Mathf.Approximately((float)m_TrackBinding." + prop.name +
712 | ",(float)" + prop.NameAsPrivateAssigned + "))\n";
713 | returnVal += k_Tab + k_Tab + k_Tab + prop.NameAsPrivateDefault + " = m_TrackBinding." +
714 | prop.name + ";\n";
715 | break;
716 | case "Gradient":
717 | returnVal += k_Tab + k_Tab + "if(!m_TrackBinding." + prop.name + ".Equals(" +
718 | prop.NameAsPrivateAssigned + "))\n";
719 | returnVal += k_Tab + k_Tab + k_Tab + prop.NameAsPrivateDefault + " = m_TrackBinding." +
720 | prop.name + ";\n";
721 | break;
722 | default:
723 | returnVal += k_Tab + k_Tab + "if(m_TrackBinding." + prop.name + " != " +
724 | prop.NameAsPrivateAssigned + ")\n";
725 | returnVal += k_Tab + k_Tab + k_Tab + prop.NameAsPrivateDefault + " = m_TrackBinding." +
726 | prop.name + ";\n";
727 | break;
728 | }
729 | }
730 |
731 | return returnVal;
732 | }
733 |
734 | string StandardBlendBlendedVariablesCreationToString()
735 | {
736 | string returnVal = "";
737 | foreach (var prop in standardBlendPlayableProperties)
738 | {
739 | if (!prop.blendable)
740 | continue;
741 |
742 | string type = prop.type == "int" ? "float" : prop.type;
743 | string zeroVal = prop.type == "int" ? "0f" : prop.ZeroValueAsString();
744 | returnVal += k_Tab + k_Tab + type + " " + prop.NameAsLocalBlended + " = " + zeroVal + ";\n";
745 | }
746 |
747 | return returnVal;
748 | }
749 |
750 | string StandardBlendPlayableCurrentInputsDeclarationToString()
751 | {
752 | if (standardBlendPlayableProperties.Any(x => !x.blendable))
753 | {
754 | return k_Tab + k_Tab + "int currentInputs = 0;\n";
755 | }
756 |
757 | return "";
758 | }
759 |
760 | string StandardBlendBlendedVariablesWeightedIncrementationToString()
761 | {
762 | string returnVal = "";
763 | foreach (var prop in standardBlendPlayableProperties)
764 | {
765 | if (!prop.blendable)
766 | continue;
767 |
768 | returnVal += k_Tab + k_Tab + k_Tab + prop.NameAsLocalBlended + " += input." + prop.name + " * inputWeight;\n";
769 | }
770 |
771 | return returnVal;
772 | }
773 |
774 | string VolumeBlendAssignableVariablesAssignedBasedOnGreatestWeightToString()
775 | {
776 | string returnVal = string.Empty;
777 | foreach (var prop in postProcessVolumeProperties)
778 | {
779 | if (prop.blendable)
780 | continue;
781 |
782 | returnVal += $"{k_Tab.Repeat(5)}m_TrackBinding.{prop.name}.value = input.{prop.name};\r\n";
783 | }
784 |
785 | return returnVal;
786 | }
787 |
788 | string VolumeBlendTrackBindingPropertiesAssignableAssignmentToString()
789 | {
790 | string returnVal = string.Empty;
791 | returnVal += $"{k_Tab.Repeat(3)}if(currentInputs != 1 && 1f - totalWeight > greatestWeight)\r\n";
792 | returnVal += $"{k_Tab.Repeat(3)}{{\r\n";
793 | foreach (var prop in postProcessVolumeProperties)
794 | {
795 | if (prop.blendable)
796 | continue;
797 |
798 | returnVal += $"{k_Tab.Repeat(4)}m_TrackBinding.{prop.name}.value = {prop.NameAsPrivateDefault};\r\n";
799 | }
800 | returnVal += $"{k_Tab.Repeat(3)}}}";
801 |
802 | return returnVal;
803 | }
804 |
805 | string StandardBlendAssignableVariablesAssignedBasedOnGreatestWeightToString()
806 | {
807 | if (standardBlendPlayableProperties.Count == 0)
808 | return "";
809 |
810 | string returnVal = k_Tab + k_Tab + k_Tab + "if(inputWeight > greatestWeight)\n";
811 | returnVal += k_Tab + k_Tab + k_Tab + "{\n";
812 |
813 | foreach (var prop in standardBlendPlayableProperties)
814 | {
815 | if (prop.blendable)
816 | continue;
817 |
818 | returnVal += k_Tab + k_Tab + k_Tab + k_Tab + prop.NameAsPrivateAssigned + " = input." + prop.name + ";\n";
819 | returnVal += k_Tab + k_Tab + k_Tab + k_Tab + "m_TrackBinding." + prop.name + " = " + prop.NameAsPrivateAssigned + ";\n";
820 | }
821 |
822 | returnVal += k_Tab + k_Tab + k_Tab + k_Tab + "greatestWeight = inputWeight;\n";
823 | returnVal += k_Tab + k_Tab + k_Tab + "}\n";
824 | return returnVal;
825 | }
826 |
827 | string StandardBlendPlayableCurrentInputIterationToString()
828 | {
829 | if (standardBlendPlayableProperties.Any(x => !x.blendable))
830 | {
831 | string returnVal = "\n";
832 | returnVal += k_Tab + k_Tab + k_Tab + "if(!Mathf.Approximately(inputWeight, 0f))\n";
833 | returnVal += k_Tab + k_Tab + k_Tab + k_Tab + "currentInputs++;\n";
834 | return returnVal;
835 | }
836 |
837 | return "";
838 | }
839 |
840 | string StandardBlendTrackBindingPropertiesBlendedAssignmentToString()
841 | {
842 | string returnVal = "";
843 | bool firstNewLine = false;
844 |
845 | foreach (var prop in standardBlendPlayableProperties)
846 | {
847 | if (!prop.blendable)
848 | continue;
849 |
850 | if (!firstNewLine)
851 | {
852 | firstNewLine = true;
853 | returnVal += "\n";
854 | }
855 |
856 | if (prop.type == "int")
857 | returnVal += k_Tab + k_Tab + prop.NameAsPrivateAssigned + " = Mathf.RoundToInt(" +
858 | prop.NameAsLocalBlended + " + " + prop.NameAsPrivateDefault +
859 | " * (1f - totalWeight));\n";
860 | else
861 | returnVal += k_Tab + k_Tab + prop.NameAsPrivateAssigned + " = " + prop.NameAsLocalBlended + " + " +
862 | prop.NameAsPrivateDefault + " * (1f - totalWeight);\n";
863 |
864 | returnVal += k_Tab + k_Tab + "m_TrackBinding." + prop.name + " = " + prop.NameAsPrivateAssigned + ";\n";
865 | }
866 |
867 | return returnVal;
868 | }
869 |
870 | string StandardBlendTrackBindingPropertiesAssignableAssignmentToString()
871 | {
872 | if (standardBlendPlayableProperties.Count == 0)
873 | return "";
874 |
875 | if (standardBlendPlayableProperties.Any(x => !x.blendable))
876 | {
877 | string returnVal = "\n" + k_Tab + k_Tab +
878 | "if(currentInputs != 1 && 1f - totalWeight > greatestWeight)\n";
879 | returnVal += k_Tab + k_Tab + "{\n";
880 |
881 | foreach (var prop in standardBlendPlayableProperties)
882 | {
883 | if (prop.blendable)
884 | continue;
885 |
886 | returnVal += k_Tab + k_Tab + k_Tab + "m_TrackBinding." + prop.name + " = " +
887 | prop.NameAsPrivateDefault + ";\n";
888 | }
889 |
890 | returnVal += k_Tab + k_Tab + "}\n";
891 | return returnVal;
892 | }
893 |
894 | return "";
895 | }
896 |
897 | string StandardBlendComponentBindingToString()
898 | {
899 | return "[TrackBindingType(typeof(" + TrackBinding.name + "))]\n";
900 | }
901 |
902 | string StandardBlendPropertiesAssignedToPropertyDriverToString()
903 | {
904 | if (standardBlendPlayableProperties.Count == 0)
905 | return "";
906 |
907 | string
908 | returnVal = k_Tab + k_Tab + "// These field names are procedurally generated estimations based on the associated property names.\n";
909 | returnVal += k_Tab + k_Tab + "// If any of the names are incorrect you will get a DrivenPropertyManager error saying it has failed to register the name.\n";
910 | returnVal += k_Tab + k_Tab + "// In this case you will need to find the correct backing field name.\n";
911 | returnVal += k_Tab + k_Tab + "// The suggested way of finding the field name is to:\n";
912 | returnVal += k_Tab + k_Tab + "// 1. Make sure your scene is serialized to text.\n";
913 | returnVal += k_Tab + k_Tab + "// 2. Search the text for the track binding component type.\n";
914 | returnVal += k_Tab + k_Tab + "// 3. Look through the field names until you see one that looks correct.\n";
915 |
916 | foreach (var prop in standardBlendPlayableProperties)
917 | {
918 | if (prop.usablePropertyType == UsableProperty.UsablePropertyType.Field)
919 | {
920 | returnVal += k_Tab + k_Tab + "driver.AddFromName<" + TrackBinding.name +
921 | ">(trackBinding.gameObject, \"" + prop.name + "\");\n";
922 | }
923 | else
924 | {
925 | returnVal += k_Tab + k_Tab + "driver.AddFromName<" + TrackBinding.name +
926 | ">(trackBinding.gameObject, \"" + prop.NameAsPrivate + "\");\n";
927 | }
928 | }
929 |
930 | return returnVal;
931 | }
932 | }
933 | }
--------------------------------------------------------------------------------
/Editor/MAOTimelinePlayableWizard.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Globalization;
5 | using System.Reflection;
6 | using System.Text.RegularExpressions;
7 | using UnityEditor;
8 | using UnityEngine;
9 | using UnityEngine.Timeline;
10 | using UnityEngine.Rendering;
11 | using MAOTimelineExtension.Runtime;
12 |
13 | namespace MAOTimelineExtension.Editor
14 | {
15 | public partial class MaoTimelinePlayableWizard : EditorWindow
16 | {
17 | public class Variable : IComparable
18 | {
19 | public string name;
20 | public UsableType usableType;
21 |
22 | int m_TypeIndex;
23 |
24 | public Variable(string name, UsableType usableType)
25 | {
26 | this.name = name;
27 | this.usableType = usableType;
28 | }
29 |
30 | public bool GUI(UsableType[] usableTypes)
31 | {
32 | bool removeThis = false;
33 | EditorGUILayout.BeginHorizontal();
34 | name = EditorGUILayout.TextField(name);
35 | m_TypeIndex = EditorGUILayout.Popup(m_TypeIndex, UsableType.GetNameWithSortingArray(usableTypes));
36 | usableType = usableTypes[m_TypeIndex];
37 | if (GUILayout.Button("Remove", GUILayout.Width(60f)))
38 | {
39 | removeThis = true;
40 | }
41 |
42 | EditorGUILayout.EndHorizontal();
43 |
44 | return removeThis;
45 | }
46 |
47 | public int CompareTo(object obj)
48 | {
49 | if (obj is null)
50 | return 1;
51 |
52 | UsableType other = (UsableType) obj;
53 |
54 | if (other is null)
55 | throw new ArgumentException("This object is not a Variable.");
56 |
57 | return string.Compare(name, other.name, StringComparison.CurrentCulture);
58 | }
59 |
60 | public static UsableType[] GetUsableTypesFromVariableArray(Variable[] variables)
61 | => variables.Select(variable => variable.usableType).ToArray();
62 | }
63 |
64 | public class UsableType : IComparable
65 | {
66 | public readonly string name;
67 | public readonly string nameWithSorting;
68 | public readonly string additionalNamespace;
69 | public readonly GUIContent guiContentWithSorting;
70 | public readonly Type type;
71 |
72 | public readonly string[] unrequiredNamespaces =
73 | {
74 | "UnityEngine",
75 | "UnityEngine.Timeline",
76 | "UnityEngine.Playables"
77 | };
78 |
79 | public const string blankAdditionalNamespace = "";
80 |
81 | const string k_NameForNullType = "None";
82 |
83 | public UsableType(Type usableType)
84 | {
85 | type = usableType;
86 | if (usableType != null)
87 | {
88 | name = usableType.Name;
89 | nameWithSorting = name.ToUpper()[0] + "/" + name;
90 | additionalNamespace = unrequiredNamespaces.All(t => usableType.Namespace != t)
91 | ? usableType.Namespace
92 | : blankAdditionalNamespace;
93 | }
94 | else
95 | {
96 | name = k_NameForNullType;
97 | nameWithSorting = k_NameForNullType;
98 | additionalNamespace = blankAdditionalNamespace;
99 | }
100 |
101 | guiContentWithSorting = new GUIContent(nameWithSorting);
102 | }
103 |
104 | public UsableType(string name)
105 | {
106 | this.name = name;
107 | nameWithSorting = name.ToUpper()[0] + "/" + name;
108 | additionalNamespace = blankAdditionalNamespace;
109 | guiContentWithSorting = new GUIContent(nameWithSorting);
110 | }
111 |
112 | public int CompareTo(object obj)
113 | {
114 | if (obj is null)
115 | return 1;
116 |
117 | UsableType other = (UsableType) obj;
118 |
119 | if (other is null)
120 | throw new ArgumentException("This object is not a UsableType.");
121 |
122 | return string.Compare(name, other.name, StringComparison.CurrentCulture);
123 | }
124 |
125 | public static UsableType[] GetUsableTypeArray(Type[] types, params UsableType[] additionalUsableTypes) =>
126 | types.Select(type => new UsableType(type)).Concat(additionalUsableTypes).ToArray();
127 |
128 | public static UsableType[] AmalgamateUsableTypes(UsableType[] usableTypeArray, params UsableType[] usableTypes) =>
129 | usableTypes.Concat(usableTypeArray).ToArray();
130 |
131 | public static string[] GetNameWithSortingArray(UsableType[] usableTypes) =>
132 | usableTypes?.Select(type => type.nameWithSorting).ToArray() ?? Array.Empty();
133 |
134 | public static GUIContent[] GetGUIContentWithSortingArray(UsableType[] usableTypes)=>
135 | usableTypes?.Select(type => type.guiContentWithSorting).ToArray() ?? Array.Empty();
136 |
137 | public static string[] GetDistinctAdditionalNamespaces(UsableType[] usableTypes)=>
138 | usableTypes?.Select(type => type.additionalNamespace).Distinct().ToArray() ?? Array.Empty();
139 | }
140 |
141 | public class UsableProperty : IComparable
142 | {
143 | public enum Usability
144 | {
145 | Blendable,
146 | Assignable,
147 | Not
148 | }
149 |
150 | public enum UsablePropertyType
151 | {
152 | Property,
153 | Field
154 | }
155 |
156 | /// ================================
157 | /// for PropertyAttribute
158 | public enum PropertyAttributesType
159 | {
160 | Range,
161 | Min,
162 | Max,
163 | ColorVolumeParameter,
164 | ColorUsage,
165 | GradientUsage,
166 | Null
167 | }
168 |
169 | public PropertyAttributesType propertyAttributesType = PropertyAttributesType.Null;
170 | public ColorParameter colorParameter;
171 | // volume
172 | // min max
173 | public MinFloatParameter minFloatParameter;
174 | public MinIntParameter minIntParameter;
175 | public MaxFloatParameter maxFloatParameter;
176 | public MaxIntParameter maxIntParameter;
177 | // range
178 | public FloatRangeParameter floatRangeParameter;
179 | public NoInterpFloatRangeParameter noInterpFloatRangeParameter;
180 | // range
181 | public ClampedFloatParameter clampedFloatParameter;
182 | public ClampedIntParameter clampedIntParameter;
183 |
184 | // component
185 | public ColorUsageAttribute colorUsage;
186 | public GradientUsageAttribute gradientUsage;
187 | public RangeAttribute range;
188 | public MinAttribute min;
189 | // public MaxAttribute max;
190 |
191 | // =================================
192 |
193 |
194 | public string type;
195 | public string name;
196 | public string defaultValue;
197 | public Usability usability;
198 | public UsablePropertyType usablePropertyType;
199 | public bool blendable;
200 | public PropertyInfo propertyInfo;
201 | public FieldInfo fieldInfo;
202 |
203 | public int typeIndex;
204 |
205 | public string NameWithCapital => name.Title();
206 |
207 | public string NameAsPrivate => "m_" + NameWithCapital;
208 |
209 | public string NameAsPrivateDefault => "m_Default" + NameWithCapital;
210 |
211 | public string NameAsPrivateAssigned => "m_Assigned" + NameWithCapital;
212 |
213 | public string NameAsLocalBlended => "blended" + NameWithCapital;
214 |
215 | public string NameAsLocalSerializedProperty => name + "Prop";
216 |
217 | public UsableProperty(PropertyInfo propertyInfo)
218 | {
219 | usablePropertyType = UsablePropertyType.Property;
220 | this.propertyInfo = propertyInfo;
221 |
222 | type = propertyInfo.PropertyType.Name switch
223 | {
224 | "Single" => "float",
225 | "Int32" => "int",
226 | "Double" => "double",
227 | "Boolean"=> "bool",
228 | "String" => "string",
229 | _ => propertyInfo.PropertyType.Name
230 | };
231 |
232 | name = propertyInfo.Name;
233 |
234 | if (IsTypeBlendable(propertyInfo.PropertyType))
235 | usability = Usability.Blendable;
236 | else if (IsTypeAssignable(propertyInfo.PropertyType))
237 | usability = Usability.Assignable;
238 | else
239 | usability = Usability.Not;
240 | }
241 |
242 | public UsableProperty(FieldInfo fieldInfo)
243 | {
244 | usablePropertyType = UsablePropertyType.Field;
245 | this.fieldInfo = fieldInfo;
246 |
247 | if (fieldInfo.FieldType.Name == "Single" || fieldInfo.FieldType.Name.Contains("FloatParameter"))
248 | type = "float";
249 | else if (fieldInfo.FieldType.Name == "Int32" || fieldInfo.FieldType.Name.Contains("IntParameter"))
250 | type = "int";
251 | else if (fieldInfo.FieldType.Name == "Double")
252 | type = "double";
253 | else if (fieldInfo.FieldType.Name == "Boolean" || fieldInfo.FieldType.Name.Contains("BoolParameter"))
254 | type = "bool";
255 | else if (fieldInfo.FieldType.Name == "String")
256 | type = "string";
257 | else if (fieldInfo.FieldType.Name == "Color" || fieldInfo.FieldType.Name.Contains("ColorParameter"))
258 | type = "Color";
259 | else if (fieldInfo.FieldType.Name == "Vector2" || fieldInfo.FieldType.Name.Contains("Vector2Parameter"))
260 | type = "Vector2";
261 | else if (fieldInfo.FieldType.Name == "Vector3" || fieldInfo.FieldType.Name.Contains("Vector3Parameter"))
262 | type = "Vector3";
263 | else if (fieldInfo.FieldType.Name == "Vector4" || fieldInfo.FieldType.Name.Contains("Vector4Parameter"))
264 | type = "Vector4";
265 | // TODO: Check Texture、Texture2D、Texture3D
266 | else if (fieldInfo.FieldType.Name == "Texture" || fieldInfo.FieldType.Name.Contains("TextureParameter"))
267 | type = "Texture";
268 | else if (fieldInfo.FieldType.Name == "Texture2D" || fieldInfo.FieldType.Name.Contains("Texture2DParameter"))
269 | type = "Texture2D";
270 | else if (fieldInfo.FieldType.Name == "Texture3D" || fieldInfo.FieldType.Name.Contains("Texture3DParameter"))
271 | type = "Texture3D";
272 | else if (IsVolumeParameterEnum(fieldInfo.FieldType))
273 | type = fieldInfo.FieldType.BaseType?.GenericTypeArguments[0].Name;
274 | else
275 | type = fieldInfo.FieldType.Name;
276 |
277 | name = fieldInfo.Name;
278 |
279 | if (IsTypeBlendable(fieldInfo.FieldType))
280 | {
281 | usability = Usability.Blendable;
282 | blendable = true;
283 | }
284 | else if (IsTypeAssignable(fieldInfo.FieldType))
285 | usability = Usability.Assignable;
286 | else if (IsVolumeParameterEnum(fieldInfo.FieldType))
287 | usability = Usability.Assignable;
288 | else
289 | usability = Usability.Not;
290 | }
291 |
292 | public string ZeroValueAsString() => type switch
293 | {
294 | "float" => "0.0f",
295 | "int" => "0",
296 | "double" => "0.0d",
297 | "Vector2" => "Vector2.zero",
298 | "Vector3" => "Vector3.zero",
299 | "Vector4" => "Vector4.zero",
300 | "Color" => "Color.clear",
301 | "bool" => "false",
302 | "Texture" => "new Texture2D(1,1)",
303 | "Texture2D" => "new Texture2D(1,1)",
304 | "Texture3D" => "new Texture3D(1, 1, 1, TextureFormat.ARGB32, false)",
305 | _ => ""
306 | };
307 |
308 | public void CreateSettingDefaultValueString(Component defaultValuesComponent)
309 | {
310 | if (defaultValuesComponent is null)
311 | {
312 | defaultValue = "";
313 | return;
314 | }
315 |
316 | object defaultValueObj = usablePropertyType == UsablePropertyType.Property
317 | ? propertyInfo.GetValue(defaultValuesComponent, null)
318 | : fieldInfo.GetValue(defaultValuesComponent);
319 |
320 | switch (type)
321 | {
322 | case "float":
323 | float defaultFloatValue = (float) defaultValueObj;
324 | defaultValue = defaultFloatValue + "f";
325 | break;
326 | case "int":
327 | int defaultIntValue = (int) defaultValueObj;
328 | defaultValue = defaultIntValue.ToString();
329 | break;
330 | case "double":
331 | double defaultDoubleValue = (double) defaultValueObj;
332 | defaultValue = defaultDoubleValue.ToString(CultureInfo.CurrentCulture);
333 | break;
334 | case "Vector2":
335 | Vector2 defaultVector2Value = (Vector2) defaultValueObj;
336 | defaultValue = $"new Vector2({defaultVector2Value.x}f, {defaultVector2Value.y}f)";
337 | break;
338 | case "Vector3":
339 | Vector3 defaultVector3Value = (Vector3) defaultValueObj;
340 | defaultValue = $"new Vector3({defaultVector3Value.x}f, {defaultVector3Value.y}f, {defaultVector3Value.z}f)";
341 | break;
342 | case "Vector4":
343 | Vector4 defaultVector4Value = (Vector4) defaultValueObj;
344 | defaultValue = $"new Vector4({defaultVector4Value.x}f, {defaultVector4Value.y}f, {defaultVector4Value.z}f)";
345 | break;
346 | case "Color":
347 | Color defaultColorValue = (Color) defaultValueObj;
348 | defaultValue = $"new Color({defaultColorValue.r}f, {defaultColorValue.g}f, {defaultColorValue.b}f, {defaultColorValue.a}f)";
349 | break;
350 | case "string":
351 | defaultValue = "\"" + defaultValueObj + "\"";
352 | break;
353 | case "bool":
354 | bool defaultBoolValue = (bool) defaultValueObj;
355 | defaultValue = defaultBoolValue.ToString().ToLower();
356 | break;
357 | case "Texture":
358 | defaultValue = "";
359 | break;
360 | case "Gradient":
361 | if (defaultValueObj is Gradient gradient)
362 | {
363 | var alphaKeysStr = string.Join(", ", gradient.alphaKeys.Select(k =>
364 | $"new GradientAlphaKey({k.alpha}f, {k.time}f)"));
365 | var colorKeysStr = string.Join(", ", gradient.colorKeys.Select(k =>
366 | $"new GradientColorKey(new Color({k.color.r}f, {k.color.g}f, {k.color.b}f, {k.color.a}f), {k.time}f)"));
367 |
368 | defaultValue = $"new Gradient() {{ " +
369 | $"alphaKeys = new GradientAlphaKey[] {{ {alphaKeysStr} }}, " +
370 | $"colorKeys = new GradientColorKey[] {{ {colorKeysStr} }} }}";
371 | }
372 | break;
373 | default: // enum only
374 | try
375 | {
376 | Enum defaultEnumValue = (Enum)defaultValueObj;
377 | Type enumSystemType = defaultEnumValue.GetType();
378 | string[] splits = enumSystemType.ToString().Split('+');
379 | string enumType = splits[^1];
380 | string enumConstantName = Enum.GetName(enumSystemType, defaultEnumValue);
381 | defaultValue = enumType + "." + enumConstantName;
382 | }
383 | catch (InvalidCastException e)
384 | {
385 | Debug.LogError($"{e}\r\nDon't know how to handle {type} for {name} in {defaultValuesComponent.GetType()}");
386 | }
387 | break;
388 | }
389 |
390 | var attrs = fieldInfo.GetCustomAttributes();
391 | foreach (var attr in attrs)
392 | {
393 | switch (attr)
394 | {
395 | case ColorUsageAttribute colorUsageAttr:
396 | propertyAttributesType = PropertyAttributesType.ColorUsage;
397 | colorUsage = colorUsageAttr;
398 | break;
399 | case GradientUsageAttribute gradientUsageAttr:
400 | propertyAttributesType = PropertyAttributesType.GradientUsage;
401 | gradientUsage = gradientUsageAttr;
402 | break;
403 | case MinAttribute minAttr:
404 | propertyAttributesType = PropertyAttributesType.Min;
405 | min = minAttr;
406 | break;
407 | case RangeAttribute rangeAttr:
408 | propertyAttributesType = PropertyAttributesType.Range;
409 | range = rangeAttr;
410 | break;
411 | }
412 | }
413 | }
414 |
415 | #region Volume Property Attributes
416 |
417 | ///
418 | /// 获取字段的 Range,Min,Max 属性。仅对 VolumeParameter 类型有效
419 | ///
420 | /// T: ClampedFloat, RangeInt, MinFloat, etc
421 | public void GetPropertyAttributes(object parameter) where T : VolumeParameter
422 | {
423 | var typeName = typeof(T).Name;
424 | if(typeName.Contains("Range") || typeName.Contains("Clamped"))
425 | {
426 | propertyAttributesType = PropertyAttributesType.Range;
427 | range = new RangeAttribute(GetPropertyAttributesMin(parameter), GetPropertyAttributesMax(parameter));
428 | }
429 | else if (typeName.Contains("Min"))
430 | {
431 | propertyAttributesType = PropertyAttributesType.Min;
432 | min = new MinAttribute(GetPropertyAttributesMin(parameter));
433 | }
434 | else if (typeName.Contains("ColorParameter"))
435 | {
436 | propertyAttributesType = PropertyAttributesType.ColorVolumeParameter;
437 | colorParameter = (ColorParameter)parameter;
438 | }
439 | }
440 |
441 | public float GetPropertyAttributesMax(object parameter)
442 | => (float)Convert.ToDouble(parameter.GetType().GetField("max").GetValue(parameter));
443 |
444 |
445 | public float GetPropertyAttributesMin(object parameter)
446 | => (float)Convert.ToDouble(parameter.GetType().GetField("min").GetValue(parameter));
447 |
448 | public void CreateSettingDefaultValueStringVolume(Volume defaultValuesComponent, UsableProperty prop)
449 | where T : VolumeComponent
450 | {
451 | // var component = (T)FormatterServices.GetUninitializedObject(typeof(T));
452 | defaultValuesComponent.profile.TryGet(out var component);
453 | if (component is null)
454 | {
455 | defaultValue = "";
456 | return;
457 | }
458 | var parameter = usablePropertyType == UsablePropertyType.Property
459 | ? propertyInfo.GetValue(component, null)
460 | : fieldInfo.GetValue(component);
461 |
462 |
463 | var method = typeof(VolumeParameter).GetMethod("GetValue");
464 | if(method is null) return;
465 |
466 | switch (prop.type)
467 | {
468 | case "float":
469 | var defaultFloatValue = (float) method.MakeGenericMethod(typeof(float)).Invoke(parameter, null);
470 | defaultValue = defaultFloatValue + "f";
471 | break;
472 | case "int":
473 | var defaultIntValue = (int) method.MakeGenericMethod(typeof(int)).Invoke(parameter, null);
474 | defaultValue = defaultIntValue.ToString();
475 | break;
476 | case "double":
477 | var defaultDoubleValue = (double) method.MakeGenericMethod(typeof(double)).Invoke(parameter, null);
478 | defaultValue = defaultDoubleValue.ToString(CultureInfo.CurrentCulture);
479 | break;
480 | case "Vector2":
481 | var defaultVector2Value = (Vector2) method.MakeGenericMethod(typeof(Vector2)).Invoke(parameter, null);
482 | defaultValue = $"new Vector2({defaultVector2Value.x}f, {defaultVector2Value.y}f)";
483 | break;
484 | case "Vector3":
485 | var defaultVector3Value = (Vector3) method.MakeGenericMethod(typeof(Vector3)).Invoke(parameter, null);
486 | defaultValue = $"new Vector3({defaultVector3Value.x}f, {defaultVector3Value.y}f, {defaultVector3Value.z}f)";
487 | break;
488 | case "Vector4":
489 | var defaultVector4Value = (Vector4) method.MakeGenericMethod(typeof(Vector4)).Invoke(parameter, null);
490 | defaultValue = $"new Vector4({defaultVector4Value.x}f, {defaultVector4Value.y}f, {defaultVector4Value.z}f)";
491 | break;
492 | case "Color":
493 | var defaultColorValue = (Color) method.MakeGenericMethod(typeof(Color)).Invoke(parameter, null);
494 | defaultValue = $"new Color({defaultColorValue.r}f, {defaultColorValue.g}f, {defaultColorValue.b}f, {defaultColorValue.a}f)";
495 | break;
496 | case "string":
497 | defaultValue = "\"" + (string) method.MakeGenericMethod(typeof(string)).Invoke(parameter, null) + "\"";
498 | break;
499 | case "bool":
500 | var defaultBoolValue = (bool) method.MakeGenericMethod(typeof(bool)).Invoke(parameter, null);
501 | defaultValue = defaultBoolValue.ToString().ToLower();
502 | break;
503 | case "Texture":
504 | defaultValue = "";
505 | break;
506 | default: // Enum
507 | Type enumType;
508 | string defaultVal;
509 | if (IsVolumeParameterEnum(prop.fieldInfo.FieldType))
510 | {
511 | enumType = prop.fieldInfo.FieldType.BaseType?.GenericTypeArguments[0];
512 | defaultVal = Enum.GetValues(enumType).GetValue(0).ToString();
513 | }
514 | else if (parameter is Enum defaultEnumValue)
515 | {
516 | enumType = defaultEnumValue.GetType();
517 | defaultVal = Enum.GetName(enumType, defaultEnumValue);
518 | }
519 | else
520 | {
521 | Debug.LogError($"Can't handle {prop.type} for {prop.name} in {defaultValuesComponent.GetType()}");
522 | break;
523 | }
524 | defaultValue = $"{enumType.Name}.{defaultVal}";
525 | break;
526 | }
527 |
528 | try
529 | {
530 | typeof(UsableProperty).GetMethod("GetPropertyAttributes")?
531 | .MakeGenericMethod(parameter.GetType()).Invoke(prop, new object[] {parameter});
532 | }
533 | catch (Exception e)
534 | {
535 | Debug.Log(e + " " + parameter.GetType() + " " + prop.type + " "+ prop.name);
536 | }
537 | }
538 |
539 | #endregion
540 |
541 | public bool ShowGUI(List allUsableProperties)
542 | {
543 | bool removeThis = false;
544 | EditorGUILayout.BeginHorizontal();
545 |
546 | typeIndex = EditorGUILayout.Popup(typeIndex, GetNameWithSortingArray(allUsableProperties), GUILayout.Width(200f));
547 | type = allUsableProperties[typeIndex].type;
548 | name = allUsableProperties[typeIndex].name;
549 | usablePropertyType = allUsableProperties[typeIndex].usablePropertyType;
550 | propertyInfo = allUsableProperties[typeIndex].propertyInfo;
551 | fieldInfo = allUsableProperties[typeIndex].fieldInfo;
552 | usability = allUsableProperties[typeIndex].usability;
553 |
554 | bool isTypeSupported = usability != Usability.Not;
555 | Color originalColor = GUI.color;
556 | if (!isTypeSupported) GUI.color = Color.red;
557 | GUILayout.Label(type, GUILayout.Width(150f)); // unsupported types are red
558 | GUI.color = originalColor;
559 |
560 | EditorGUI.BeginDisabledGroup(usability != Usability.Blendable);
561 | var content = new GUIContent("", "Toggle to enable/disable property blending");
562 | blendable = EditorGUILayout.Toggle(content, blendable, GUILayout.Width(60f));
563 | EditorGUI.EndDisabledGroup();
564 |
565 | if (GUILayout.Button("Remove", GUILayout.Width(60f)))
566 | removeThis = true;
567 |
568 | EditorGUILayout.EndHorizontal();
569 | return removeThis;
570 | }
571 |
572 | public int CompareTo(object obj)
573 | {
574 | if (obj is null)
575 | return 1;
576 |
577 | UsableType other = (UsableType) obj;
578 |
579 | if (other is null)
580 | throw new ArgumentException("This object is not a UsableProperty.");
581 |
582 | return string.Compare(name, other.name, StringComparison.CurrentCulture);
583 | }
584 |
585 | static string[] GetNameWithSortingArray(List usableProperties)
586 | => usableProperties.Select(property => property.name).ToArray();
587 |
588 | public UsableProperty GetDuplicate()
589 | {
590 | UsableProperty duplicate = usablePropertyType == UsablePropertyType.Property
591 | ? new UsableProperty(propertyInfo)
592 | : new UsableProperty(fieldInfo);
593 | duplicate.defaultValue = defaultValue;
594 | duplicate.typeIndex = typeIndex;
595 | duplicate.blendable = blendable;
596 | return duplicate;
597 | }
598 | }
599 |
600 | public enum CreationError
601 | {
602 | NoError,
603 | PlayableAssetAlreadyExists,
604 | PlayableBehaviourAlreadyExists,
605 | PlayableBehaviourMixerAlreadyExists,
606 | TrackAssetAlreadyExists,
607 | PlayableDrawerAlreadyExists,
608 | }
609 |
610 | public enum WorkType
611 | {
612 | Component,
613 | VolumeComponent
614 | }
615 |
616 | private static MAOTimelineExtensionsConfigSO _config;
617 | private static MAOTimelineExtensionsConfigSO Config
618 | {
619 | get
620 | {
621 | if (_config is null)
622 | {
623 | _config = Resources.Load("MAOTimelineExtensionsConfigSO");
624 | if (_config is null)
625 | {
626 | Debug.LogError("Cannot find MAOTimelineExtensionsConfigSO in Resources folder!");
627 | }
628 | }
629 | return _config;
630 | }
631 | }
632 |
633 | public bool showHelpBoxes = true;
634 | public string playableName = "";
635 |
636 | public WorkType workType = WorkType.Component;
637 |
638 | public static UsableType TrackBinding;
639 | public Component defaultValuesComponent;
640 | // public VolumeComponent defaultValuesVolumeComponent;
641 | public Volume defaultValuesVolume;
642 | public List exposedReferences = new List();
643 | public List playableBehaviourVariables = new List();
644 |
645 | public List standardBlendPlayableProperties = new List();
646 |
647 | public List postProcessVolumeProperties = new List();
648 |
649 | public ClipCaps clipCaps;
650 |
651 | public Color trackColor = new Color(240 / 255f, 248 / 255f, 255 / 255f);
652 |
653 | // int m_TrackBindingTypeIndex;
654 | int m_ComponentBindingTypeIndex;
655 | PropertyInfo[] m_TrackBindingProperties;
656 | FieldInfo[] m_TrackBindingFields;
657 |
658 | List m_TrackBindingUsableProperties = new List();
659 |
660 | // List m_TrackBindingUsableBlendProperties = new List();
661 | bool m_CreateDrawer;
662 | bool m_CreateButtonPressed;
663 | Vector2 m_ScrollViewPos;
664 | CreationError m_CreationError;
665 |
666 | #region GUIContent
667 |
668 | readonly GUIContent m_ShowHelpBoxesContent =
669 | new GUIContent("Show Help", "Do you want to see the help boxes as part of this wizard?");
670 |
671 | readonly GUIContent m_PlayableNameContent = new GUIContent("Playable Name",
672 | "This is the name that will represent the playable. E.G. TransformTween. It will be the basis for the class names so it is best not to use the postfixes: 'Clip', 'Behaviour', 'MixerBehaviour' or 'Drawer'.");
673 |
674 | readonly GUIContent m_StandardBlendPlayableContent = new GUIContent("Standard Blend Playable",
675 | "Often when creating a playable it's intended purpose is just to briefly override the properties of a component for the playable's duration and then blend back to the defaults. For example a playable that changes the color of a Light but changes it back. To make a playable with this functionality, check this box.");
676 |
677 | readonly GUIContent m_WorkType =
678 | new GUIContent("WorkType", "WorkType, now it's only support Component and VolumeComponent");
679 |
680 | readonly GUIContent m_TrackBindingTypeContent =
681 | new GUIContent("Track Binding Type",
682 | "This is the type of object the Playable will affect. E.G. To affect the position choose Transform.");
683 |
684 | readonly GUIContent m_DefaultValuesComponentContent = new GUIContent("Default Values",
685 | "When the scripts are created, each of the selected properties are assigned a default from the selected Component. If this is left blank no defaults will be used.");
686 |
687 | readonly GUIContent m_TrackColorContent = new GUIContent("Track Color",
688 | "Timeline tracks have a colored outline, use this to select that color for your track.");
689 |
690 | readonly GUIContent m_StandardBlendPlayablePropertiesContent = new GUIContent(
691 | "Standard Blend Playable Properties",
692 | "Having already selected a Track Binding type, you can select the properties of the bound component you want the playable to affect. For example, if your playable is bound to a Transform, you can affect the position property. Note that changing the component binding will clear the list of properties.");
693 |
694 | readonly GUIContent m_PostProcessVolumePropertiesContent =
695 | new GUIContent("PostProcess Playable Properties",
696 | "Having already selected a Track Binding type, you can select the properties of the bound component you want the playable to affect. For example, if your playable is bound to a VolumeComponent(Bloom), you can affect the threshold 、intensity property. Note that changing the component binding will clear the list of properties.");
697 |
698 | readonly GUIContent m_ClipCapsContent = new GUIContent("Clip Caps",
699 | "Clip Caps are used to change the way Timelines work with your playables. For example, enabling Blending will mean that your playables can blend when they overlap and have ease in and out durations. To find out a little about each hover the cursor over the options. For details, please see the documentation.");
700 |
701 | readonly GUIContent m_CCNoneContent =
702 | new GUIContent("None", "Your playable supports none of the features below.");
703 |
704 | readonly GUIContent m_CCLoopingContent = new GUIContent("Looping",
705 | "Your playable has a specified time that it takes and will start again after it finishes until the clip's duration has played.");
706 |
707 | readonly GUIContent m_CCExtrapolationContent = new GUIContent("Extrapolation",
708 | "Your playable will persist beyond its end time and its results will continue until the next clip is encountered.");
709 |
710 | readonly GUIContent m_CCClipInContent =
711 | new GUIContent("Clip In", "Your playable need not be at the start of the Timeline.");
712 |
713 | readonly GUIContent m_CCSpeedMultiplierContent =
714 | new GUIContent("Speed Multiplier", "Your playable supports changes to the time scale.");
715 |
716 | readonly GUIContent m_CCBlendingContent = new GUIContent("Blending",
717 | "Your playable supports overlapping of clips to blend between them.");
718 |
719 | readonly GUIContent m_CCAllContent = new GUIContent("All", "Your playable supports all of the above features.");
720 |
721 | #endregion
722 |
723 | const string k_Tab = " ";
724 | const string k_ShowHelpBoxesKey = "TimelinePlayableWizard_ShowHelpBoxes";
725 | const string k_TimelineClipAssetSuffix = "Clip";
726 | const string k_TimelineClipBehaviourSuffix = "Behaviour";
727 | const string k_PlayableBehaviourMixerSuffix = "MixerBehaviour";
728 | const string k_TrackAssetSuffix = "Track";
729 | const string k_PropertyDrawerSuffix = "Drawer";
730 | const int k_PlayableNameCharLimit = 64;
731 | const float k_WindowWidth = 550f;
732 | const float k_MaxWindowHeight = 800f;
733 | const float k_ScreenSizeWindowBuffer = 100f;
734 |
735 | static UsableType[] s_ComponentTypes;
736 |
737 | static UsableType[] s_VolumeComponentTypes;
738 |
739 | static UsableType[] s_TrackBindingTypes;
740 | static UsableType[] s_ExposedReferenceTypes;
741 | static UsableType[] s_BehaviourVariableTypes;
742 |
743 | static Type[] s_BlendableTypes =
744 | {
745 | typeof(float), typeof(int), typeof(double), typeof(Vector2), typeof(Vector3), typeof(Color),
746 | typeof(FloatParameter), typeof(IntParameter), typeof(Vector2Parameter), typeof(Vector3Parameter),
747 | typeof(ColorParameter)
748 | };
749 |
750 | static Type[] s_AssignableTypes =
751 | {
752 | typeof(string), typeof(bool),
753 | typeof(BoolParameter), typeof(TextureParameter),
754 | typeof(Gradient)
755 | };
756 |
757 | static string[] s_DisallowedPropertyNames = { "name" };
758 |
759 | [MenuItem("Window/MAO Timeline Playable Wizard")]
760 | static void CreateWindow()
761 | {
762 | MaoTimelinePlayableWizard wizard = GetWindow(true, "MAO Timeline Playable Wizard", true);
763 |
764 | float screenWidth = Screen.currentResolution.width;
765 | float screenHeight = Screen.currentResolution.height;
766 |
767 | float windowX = (screenWidth - k_WindowWidth) * 0.5f;
768 | float windowY = (screenHeight - Mathf.Min(screenHeight * 0.8f, k_MaxWindowHeight)) * 0.5f;
769 |
770 | float scale = EditorGUIUtility.pixelsPerPoint;
771 |
772 | windowX = Mathf.Clamp(windowX, 0, screenWidth - k_WindowWidth) / scale;
773 | windowY = Mathf.Clamp(windowY, 0, screenHeight - k_MaxWindowHeight) / scale;
774 |
775 | wizard.position = new Rect(
776 | windowX,
777 | windowY,
778 | k_WindowWidth,
779 | Mathf.Min(Screen.currentResolution.height - k_ScreenSizeWindowBuffer, k_MaxWindowHeight));
780 |
781 | wizard.showHelpBoxes = EditorPrefs.GetBool(k_ShowHelpBoxesKey);
782 | wizard.Show();
783 |
784 | Init();
785 | }
786 |
787 | static void Init()
788 | {
789 | #region Component
790 |
791 | Type[] componentTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
792 | .Where(t => typeof(Component).IsAssignableFrom(t)).Where(t => t.IsPublic).ToArray();
793 |
794 | List componentUsableTypesList = UsableType.GetUsableTypeArray(componentTypes).ToList();
795 | componentUsableTypesList.Sort();
796 | s_ComponentTypes = componentUsableTypesList.ToArray();
797 |
798 | UsableType gameObjectUsableType = new UsableType(typeof(GameObject));
799 | UsableType[] defaultUsableTypes = UsableType.GetUsableTypeArray(componentTypes, gameObjectUsableType);
800 |
801 | List exposedRefTypeList = defaultUsableTypes.ToList();
802 | exposedRefTypeList.Sort();
803 | s_ExposedReferenceTypes = exposedRefTypeList.ToArray();
804 |
805 | UsableType noneType = new UsableType((Type) null);
806 | s_TrackBindingTypes = UsableType.AmalgamateUsableTypes(s_ExposedReferenceTypes, noneType);
807 |
808 | s_BehaviourVariableTypes = UsableType.AmalgamateUsableTypes
809 | (
810 | s_ExposedReferenceTypes,
811 | new UsableType("int"),
812 | new UsableType("bool"),
813 | new UsableType("float"),
814 | new UsableType("Color"),
815 | new UsableType("double"),
816 | new UsableType("string"),
817 | new UsableType("Vector2"),
818 | new UsableType("Vector3"),
819 | new UsableType("AudioClip"),
820 | new UsableType("AnimationCurve")
821 | );
822 | List scriptVariableTypeList = s_BehaviourVariableTypes.ToList();
823 | scriptVariableTypeList.Sort();
824 | s_BehaviourVariableTypes = scriptVariableTypeList.ToArray();
825 |
826 | #endregion
827 |
828 | #region Volume
829 |
830 | Type[] volumeComponentTypes = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
831 | .Where(t => typeof(VolumeComponent).IsAssignableFrom(t)).Where(t => t.IsPublic).ToArray();
832 |
833 | List volumeComponentUsableTypesList = UsableType.GetUsableTypeArray(volumeComponentTypes).ToList();
834 | volumeComponentUsableTypesList.Sort();
835 | s_VolumeComponentTypes = volumeComponentUsableTypesList.ToArray();
836 |
837 | #endregion
838 | }
839 |
840 | void OnGUI()
841 | {
842 | if (s_ComponentTypes is null || s_TrackBindingTypes is null || s_ExposedReferenceTypes is null ||
843 | s_BehaviourVariableTypes is null || s_VolumeComponentTypes is null)
844 | Init();
845 |
846 | if (s_ComponentTypes is null || s_TrackBindingTypes is null || s_ExposedReferenceTypes is null ||
847 | s_BehaviourVariableTypes is null || s_VolumeComponentTypes is null)
848 | {
849 | EditorGUILayout.HelpBox("Failed to initialise.", MessageType.Error);
850 | return;
851 | }
852 |
853 | m_ScrollViewPos = EditorGUILayout.BeginScrollView(m_ScrollViewPos);
854 |
855 | // Show help
856 | GUIShowHelpPart();
857 |
858 |
859 | // Playable name
860 | GUIPlayableNamePart(out bool playableNameNotEmpty, out bool playableNameFormatted, out bool playableNameTooLong);
861 |
862 |
863 | // Work type
864 | WorkType oldWorkType = workType;
865 | GUIWorkTypePart();
866 |
867 | // Track binding type
868 | GUITrackBindingTypePart(out int oldIndex, out bool defaultValuesNotExist);
869 |
870 |
871 | // Property
872 | GUIPropertyPart(oldIndex, oldWorkType);
873 |
874 |
875 | // Track Color
876 | GUITrackColorPart();
877 |
878 |
879 | // Create
880 | // defaultValuesNotExist = workType == WorkType.VolumeComponent ^ defaultValuesVolume is not null;
881 | GUICreatePart(playableNameNotEmpty, playableNameFormatted, playableNameTooLong, defaultValuesNotExist);
882 |
883 |
884 | // Reset
885 | GUIResetPart();
886 |
887 | EditorGUILayout.EndScrollView();
888 | }
889 |
890 | void GUIShowHelpPart()
891 | {
892 | bool oldShowHelpBoxes = showHelpBoxes;
893 | showHelpBoxes = EditorGUILayout.Toggle(m_ShowHelpBoxesContent, showHelpBoxes);
894 | if (oldShowHelpBoxes != showHelpBoxes)
895 | {
896 | EditorPrefs.SetBool(k_ShowHelpBoxesKey, showHelpBoxes);
897 | EditorGUILayout.Space();
898 | }
899 |
900 | if (showHelpBoxes)
901 | {
902 | EditorGUILayout.HelpBox(
903 | "This wizard is used to create the basics of a custom playable for the Timeline. "
904 | + "It will create 4 scripts that you can then edit to complete their functionality. "
905 | + "The purpose is to setup the boilerplate code for you. If you are already familiar "
906 | + "with playables and the Timeline, you may wish to create your own scripts instead.",
907 | MessageType.None);
908 | EditorGUILayout.Space();
909 | }
910 |
911 | EditorGUILayout.Space();
912 | EditorGUILayout.Space();
913 | }
914 |
915 | void GUIPlayableNamePart(out bool playableNameNotEmpty, out bool playableNameFormatted,
916 | out bool playableNameTooLong)
917 | {
918 | EditorGUILayout.BeginVertical(GUI.skin.box);
919 | ShowHelpBoxInfo(showHelpBoxes, m_PlayableNameContent);
920 |
921 | playableName = EditorGUILayout.TextField(m_PlayableNameContent, playableName);
922 |
923 | playableNameNotEmpty = !string.IsNullOrEmpty(playableName);
924 | playableNameFormatted = IsValidIdentifier(playableName);
925 | if (!playableNameNotEmpty || !playableNameFormatted)
926 | {
927 | EditorGUILayout.HelpBox(
928 | "The Playable needs a name which starts with a capital letter and contains no spaces or special characters.",
929 | MessageType.Error);
930 | }
931 |
932 | playableNameTooLong = playableName.Length > k_PlayableNameCharLimit;
933 | if (playableNameTooLong)
934 | {
935 | EditorGUILayout.HelpBox(
936 | "The Playable needs a name which is fewer than " + k_PlayableNameCharLimit + " characters long.",
937 | MessageType.Error);
938 | }
939 |
940 | EditorGUILayout.EndVertical();
941 |
942 | EditorGUILayout.Space();
943 | EditorGUILayout.Space();
944 | }
945 |
946 | void GUIWorkTypePart()
947 | {
948 | EditorGUILayout.BeginVertical(GUI.skin.box);
949 | // TODO: Edit the help tooltip
950 | ShowHelpBoxInfo(showHelpBoxes, m_WorkType);
951 |
952 | var newWorkType = (WorkType)EditorGUILayout.EnumPopup("Work type", workType);
953 | if (newWorkType != workType)
954 | {
955 | // WorkType changed, reset related fields
956 | workType = newWorkType;
957 | m_ComponentBindingTypeIndex = 0;
958 | }
959 |
960 | EditorGUILayout.EndVertical();
961 |
962 | EditorGUILayout.Space();
963 | EditorGUILayout.Space();
964 | }
965 |
966 | void GUITrackBindingTypePart(out int oldIndex, out bool defaultValuesNotExist)
967 | {
968 | EditorGUILayout.BeginVertical(GUI.skin.box);
969 | ShowHelpBoxInfo(showHelpBoxes, m_TrackBindingTypeContent);
970 |
971 | oldIndex = m_ComponentBindingTypeIndex;
972 | defaultValuesNotExist = false;
973 | if (workType == WorkType.Component)
974 | {
975 | m_ComponentBindingTypeIndex = EditorGUILayout.Popup(m_TrackBindingTypeContent,
976 | m_ComponentBindingTypeIndex, UsableType.GetGUIContentWithSortingArray(s_ComponentTypes));
977 | TrackBinding = s_ComponentTypes[m_ComponentBindingTypeIndex];
978 |
979 | EditorGUILayout.Space();
980 |
981 | defaultValuesComponent = EditorGUILayout.ObjectField(m_DefaultValuesComponentContent,
982 | defaultValuesComponent, TrackBinding.type, true)
983 | as Component;
984 | }
985 | else if (workType == WorkType.VolumeComponent)
986 | {
987 | m_ComponentBindingTypeIndex = EditorGUILayout.Popup(m_TrackBindingTypeContent,
988 | m_ComponentBindingTypeIndex, UsableType.GetGUIContentWithSortingArray(s_VolumeComponentTypes));
989 | TrackBinding = s_VolumeComponentTypes[m_ComponentBindingTypeIndex];
990 |
991 | EditorGUILayout.Space();
992 |
993 | defaultValuesVolume = EditorGUILayout.ObjectField(m_DefaultValuesComponentContent, defaultValuesVolume,
994 | typeof(Volume), true) as Volume;
995 |
996 | defaultValuesNotExist = defaultValuesVolume is null;
997 | if (defaultValuesNotExist)
998 | {
999 | EditorGUILayout.HelpBox("A Volume component is required as the default values source", MessageType.Error);
1000 | }
1001 | else
1002 | {
1003 | if (defaultValuesVolume?.gameObject.GetComponent() is null)
1004 | {
1005 | defaultValuesVolume?.gameObject.AddComponent();
1006 | }
1007 | }
1008 | }
1009 |
1010 | EditorGUILayout.EndVertical();
1011 | }
1012 |
1013 | void GUIPropertyPart(int oldIndex, WorkType oldWorkType)
1014 | {
1015 | if (workType == WorkType.Component)
1016 | {
1017 | StandardBlendPlayablePropertyGUI(oldIndex != m_ComponentBindingTypeIndex || oldWorkType != WorkType.Component);
1018 | }
1019 | else
1020 | {
1021 | VolumeComponentPropertyGUI(oldIndex != m_ComponentBindingTypeIndex || oldWorkType != WorkType.VolumeComponent);
1022 | }
1023 |
1024 | EditorGUILayout.Space();
1025 | EditorGUILayout.Space();
1026 | }
1027 |
1028 | void GUITrackColorPart()
1029 | {
1030 | EditorGUILayout.BeginVertical(GUI.skin.box);
1031 |
1032 | ShowHelpBoxInfo(showHelpBoxes, m_TrackColorContent);
1033 |
1034 | trackColor = EditorGUILayout.ColorField(m_TrackColorContent, trackColor);
1035 | EditorGUILayout.EndVertical();
1036 | }
1037 |
1038 | void GUICreatePart(bool playableNameNotEmpty, bool playableNameFormatted, bool playableNameTooLong, bool defaultValuesNotExist)
1039 | {
1040 | if (playableNameNotEmpty && playableNameFormatted
1041 | // && allUniqueVariableNames && exposedVariablesNamesValid && scriptVariablesNamesValid
1042 | && !playableNameTooLong
1043 | && !defaultValuesNotExist)
1044 | {
1045 | if (GUILayout.Button("Create", GUILayout.Width(60f)))
1046 | {
1047 | m_CreateButtonPressed = true;
1048 |
1049 | if (workType == WorkType.Component)
1050 | {
1051 | foreach (var prop in standardBlendPlayableProperties)
1052 | {
1053 | prop.CreateSettingDefaultValueString(defaultValuesComponent);
1054 | }
1055 | }
1056 | else if (workType == WorkType.VolumeComponent)
1057 | {
1058 | var genericMethod = typeof(UsableProperty).GetMethod("CreateSettingDefaultValueStringVolume")
1059 | ?.MakeGenericMethod(TrackBinding.type);
1060 |
1061 | foreach (var prop in postProcessVolumeProperties)
1062 | {
1063 | genericMethod?.Invoke(prop, new object[] {defaultValuesVolume, prop});
1064 | }
1065 | }
1066 |
1067 | m_CreationError = CreateScripts();
1068 |
1069 | if (m_CreationError == CreationError.NoError)
1070 | {
1071 | Close();
1072 | }
1073 | }
1074 | }
1075 |
1076 | EditorGUILayout.Space();
1077 | EditorGUILayout.Space();
1078 |
1079 | CheckCreateButtonPressed();
1080 | }
1081 |
1082 | void CheckCreateButtonPressed()
1083 | {
1084 | if (m_CreateButtonPressed)
1085 | {
1086 | switch (m_CreationError)
1087 | {
1088 | case CreationError.NoError:
1089 | EditorGUILayout.HelpBox("Playable was successfully created.", MessageType.Info);
1090 | break;
1091 | case CreationError.PlayableAssetAlreadyExists:
1092 | EditorGUILayout.HelpBox(
1093 | "The type " + playableName + k_TimelineClipAssetSuffix +
1094 | " already exists, no files were created.", MessageType.Error);
1095 | break;
1096 | case CreationError.PlayableBehaviourAlreadyExists:
1097 | EditorGUILayout.HelpBox(
1098 | "The type " + playableName + k_TimelineClipBehaviourSuffix +
1099 | " already exists, no files were created.", MessageType.Error);
1100 | break;
1101 | case CreationError.PlayableBehaviourMixerAlreadyExists:
1102 | EditorGUILayout.HelpBox(
1103 | "The type " + playableName + k_PlayableBehaviourMixerSuffix +
1104 | " already exists, no files were created.", MessageType.Error);
1105 | break;
1106 | case CreationError.TrackAssetAlreadyExists:
1107 | EditorGUILayout.HelpBox(
1108 | "The type " + playableName + k_TrackAssetSuffix + " already exists, no files were created.",
1109 | MessageType.Error);
1110 | break;
1111 | case CreationError.PlayableDrawerAlreadyExists:
1112 | EditorGUILayout.HelpBox(
1113 | "The type " + playableName + k_PropertyDrawerSuffix +
1114 | " already exists, no files were created.", MessageType.Error);
1115 | break;
1116 | }
1117 | }
1118 | }
1119 |
1120 | void GUIResetPart()
1121 | {
1122 | if (GUILayout.Button("Reset", GUILayout.Width(60f)))
1123 | {
1124 | ResetWindow();
1125 | }
1126 | }
1127 |
1128 | void StandardBlendPlayablePropertyGUI(bool findNewProperties)
1129 | {
1130 | if (findNewProperties || m_TrackBindingProperties is null && m_TrackBindingFields is null)
1131 | {
1132 | m_TrackBindingUsableProperties.Clear();
1133 |
1134 | IEnumerable propertyInfos = TrackBinding.type.GetProperties(
1135 | BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty | BindingFlags.GetProperty);
1136 | propertyInfos = propertyInfos.Where(x => IsTypeBlendable(x.PropertyType) || IsTypeAssignable(x.PropertyType))
1137 | .Where(x => x.CanWrite && x.CanRead)
1138 | .Where(x => HasAllowedName(x));
1139 | // Uncomment the below to stop Obsolete properties being selectable.
1140 | propertyInfos = propertyInfos.Where (x => !Attribute.IsDefined (x, typeof(ObsoleteAttribute)));
1141 | m_TrackBindingProperties = propertyInfos.ToArray();
1142 | foreach (PropertyInfo trackBindingProperty in m_TrackBindingProperties)
1143 | {
1144 | // Uncomment the below to add properties
1145 | // m_TrackBindingUsableProperties.Add(new UsableProperty(trackBindingProperty));
1146 | }
1147 |
1148 | IEnumerable fieldInfos = TrackBinding.type.GetFields(BindingFlags.Instance | BindingFlags.Public);
1149 | fieldInfos = fieldInfos.Where(x => IsTypeBlendable(x.FieldType) || IsTypeAssignable(x.FieldType));
1150 | m_TrackBindingFields = fieldInfos.ToArray();
1151 | foreach (FieldInfo trackBindingField in m_TrackBindingFields)
1152 | {
1153 | m_TrackBindingUsableProperties.Add(new UsableProperty(trackBindingField));
1154 | }
1155 |
1156 | m_TrackBindingUsableProperties = m_TrackBindingUsableProperties.OrderBy(x => x.name).ToList();
1157 | standardBlendPlayableProperties.Clear();
1158 | }
1159 |
1160 | EditorGUILayout.BeginVertical(GUI.skin.box);
1161 |
1162 | ShowHelpBoxInfo(showHelpBoxes, m_StandardBlendPlayablePropertiesContent);
1163 |
1164 | EditorGUILayout.LabelField(m_StandardBlendPlayablePropertiesContent);
1165 |
1166 | var availableProperties = m_TrackBindingUsableProperties
1167 | .Where(property => !standardBlendPlayableProperties.Any(p => p.name == property.name)).ToList();
1168 | if (availableProperties.Count > 0)
1169 | {
1170 | EditorGUILayout.BeginHorizontal();
1171 | if (GUILayout.Button("Add", GUILayout.Width(60f)))
1172 | {
1173 | var property = availableProperties[0];
1174 | standardBlendPlayableProperties.Add(CreateDuplicateProperty(property));
1175 | }
1176 |
1177 | if (GUILayout.Button("Add All", GUILayout.Width(60f)))
1178 | {
1179 | standardBlendPlayableProperties.AddRange(availableProperties.Select(CreateDuplicateProperty));
1180 | }
1181 |
1182 | if(GUILayout.Button("Clear", GUILayout.Width(60f)))
1183 | {
1184 | standardBlendPlayableProperties.Clear();
1185 | }
1186 | EditorGUILayout.EndHorizontal();
1187 | }
1188 | else
1189 | {
1190 | EditorGUILayout.HelpBox("You have added all properties.", MessageType.Info);
1191 |
1192 | if(GUILayout.Button("Clear", GUILayout.Width(60f)))
1193 | {
1194 | standardBlendPlayableProperties.Clear();
1195 | }
1196 | }
1197 |
1198 | EditorGUILayout.BeginHorizontal();
1199 | GUILayout.Label("Field/Properties", GUILayout.Width(200f));
1200 | GUILayout.Label("Type", GUILayout.Width(150f));
1201 | GUILayout.Label(new GUIContent("Blendable", "Toggle to enable/disable property blending"), GUILayout.Width(60f));
1202 | EditorGUILayout.EndHorizontal();
1203 |
1204 | int indexToRemove = -1;
1205 | for (int i = 0; i < standardBlendPlayableProperties.Count; i++)
1206 | {
1207 | if (standardBlendPlayableProperties[i].ShowGUI(m_TrackBindingUsableProperties))
1208 | indexToRemove = i;
1209 | }
1210 |
1211 | if (indexToRemove != -1)
1212 | standardBlendPlayableProperties.RemoveAt(indexToRemove);
1213 |
1214 | if (standardBlendPlayableProperties.Any(IsObsolete))
1215 | EditorGUILayout.HelpBox(
1216 | "One or more of your chosen properties are marked 'Obsolete'. Consider changing them to avoid deprecation with future versions of Unity.",
1217 | MessageType.Warning);
1218 |
1219 | EditorGUILayout.EndVertical();
1220 | }
1221 |
1222 | void VolumeComponentPropertyGUI(bool findNewProperties)
1223 | {
1224 | if (findNewProperties || m_TrackBindingProperties is null && m_TrackBindingFields is null)
1225 | {
1226 | m_TrackBindingUsableProperties.Clear();
1227 |
1228 | m_TrackBindingProperties = TrackBinding.type.GetProperties().Where(x => x.Name == "parameters").ToArray();
1229 | m_TrackBindingFields = TrackBinding.type.GetFields().ToArray();
1230 | foreach (FieldInfo trackBindingField in m_TrackBindingFields)
1231 | {
1232 | if (trackBindingField.Name == "active")
1233 | {
1234 | continue;
1235 | }
1236 |
1237 | m_TrackBindingUsableProperties.Add(new UsableProperty(trackBindingField));
1238 | }
1239 |
1240 | m_TrackBindingUsableProperties = m_TrackBindingUsableProperties.Where(x=>IsObsolete(x)==false).ToList();
1241 | postProcessVolumeProperties.Clear();
1242 | }
1243 |
1244 | // Standard Blend Playable Properties
1245 | EditorGUILayout.BeginVertical(GUI.skin.box);
1246 |
1247 | // normal properties
1248 | ShowHelpBoxInfo(showHelpBoxes, m_PostProcessVolumePropertiesContent);
1249 |
1250 | EditorGUILayout.LabelField(m_PostProcessVolumePropertiesContent);
1251 |
1252 | var availableProperties = m_TrackBindingUsableProperties
1253 | .Where(property => !postProcessVolumeProperties.Any(p => p.name == property.name))
1254 | .ToList();
1255 |
1256 | if (availableProperties.Count > 0)
1257 | {
1258 | EditorGUILayout.BeginHorizontal();
1259 | if (GUILayout.Button("Add", GUILayout.Width(60f)))
1260 | {
1261 | var property = availableProperties[0];
1262 | postProcessVolumeProperties.Add(CreateDuplicateProperty(property));
1263 | }
1264 |
1265 | if (GUILayout.Button("Add All", GUILayout.Width(60f)))
1266 | {
1267 | postProcessVolumeProperties.AddRange(availableProperties.Select(CreateDuplicateProperty));
1268 | }
1269 |
1270 | if(GUILayout.Button("Clear", GUILayout.Width(60f)))
1271 | {
1272 | postProcessVolumeProperties.Clear();
1273 | }
1274 | EditorGUILayout.EndHorizontal();
1275 | }
1276 | else
1277 | {
1278 | EditorGUILayout.HelpBox("You have added all properties.", MessageType.Info);
1279 |
1280 | if(GUILayout.Button("Clear", GUILayout.Width(60f)))
1281 | {
1282 | postProcessVolumeProperties.Clear();
1283 | }
1284 | }
1285 | if(postProcessVolumeProperties.Any(prop => prop.usability == UsableProperty.Usability.Not))
1286 | EditorGUILayout.HelpBox("One or more of your chosen properties are not supported, which may cause errors, " +
1287 | "or you should modify the generated code to make it work.", MessageType.Warning);
1288 |
1289 | EditorGUILayout.BeginHorizontal();
1290 | GUILayout.Label("Field/Properties", GUILayout.Width(200f));
1291 | GUILayout.Label("Type", GUILayout.Width(150f));
1292 | GUILayout.Label("Blendable", GUILayout.Width(60f));
1293 | EditorGUILayout.EndHorizontal();
1294 |
1295 | int indexToRemove = -1;
1296 | for (int i = 0; i < postProcessVolumeProperties.Count; i++)
1297 | {
1298 | if (postProcessVolumeProperties[i].ShowGUI(m_TrackBindingUsableProperties))
1299 | indexToRemove = i;
1300 | }
1301 |
1302 | if (indexToRemove != -1)
1303 | postProcessVolumeProperties.RemoveAt(indexToRemove);
1304 |
1305 | if (postProcessVolumeProperties.Any(IsObsolete))
1306 | EditorGUILayout.HelpBox(
1307 | "One or more of your chosen properties are marked 'Obsolete'. Consider changing them to avoid deprecation with future versions of Unity.",
1308 | MessageType.Warning);
1309 |
1310 | var q = postProcessVolumeProperties.GroupBy(x => x.name)
1311 | .Where(g => g.Count() > 1).Select(y => y.Key).ToList();
1312 | if (q.Count > 0)
1313 | EditorGUILayout.HelpBox("Cannot have the same attribute", MessageType.Error);
1314 |
1315 | EditorGUILayout.EndVertical();
1316 | }
1317 |
1318 | private UsableProperty CreateDuplicateProperty(UsableProperty property)
1319 | {
1320 | var dup = property.GetDuplicate();
1321 | dup.typeIndex = m_TrackBindingUsableProperties.FindIndex(p => p.name == dup.name);
1322 | return dup;
1323 | }
1324 |
1325 | static bool IsTypeBlendable(Type type)
1326 | => s_BlendableTypes.Any(blendableType => type == blendableType || type.BaseType == blendableType);
1327 |
1328 | static bool IsTypeAssignable(Type type)
1329 | => type.IsEnum || s_AssignableTypes.Contains(type);
1330 |
1331 | static bool IsVolumeParameterEnum(Type type)
1332 | {
1333 | return type.BaseType?.GenericTypeArguments is Type[] { Length: > 0 } args && args[0].IsEnum;
1334 | }
1335 |
1336 | static bool HasAllowedName(PropertyInfo propertyInfo)
1337 | => !s_DisallowedPropertyNames.Contains(propertyInfo.Name);
1338 |
1339 | static bool IsObsolete(UsableProperty usableProperty)
1340 | {
1341 | if (usableProperty.usablePropertyType == UsableProperty.UsablePropertyType.Field)
1342 | return Attribute.IsDefined(usableProperty.fieldInfo, typeof(ObsoleteAttribute));
1343 | return Attribute.IsDefined(usableProperty.propertyInfo, typeof(ObsoleteAttribute));
1344 | }
1345 |
1346 | bool VariableListGUI(List variables, UsableType[] usableTypes, GUIContent guiContent, string newName)
1347 | {
1348 | EditorGUILayout.BeginVertical(GUI.skin.box);
1349 |
1350 | ShowHelpBoxInfo(showHelpBoxes, guiContent);
1351 |
1352 | EditorGUILayout.LabelField(guiContent);
1353 |
1354 | int indexToRemove = -1;
1355 | bool allNamesValid = true;
1356 | for (int i = 0; i < variables.Count; i++)
1357 | {
1358 | if (variables[i].GUI(usableTypes))
1359 | indexToRemove = i;
1360 |
1361 | if (!IsValidIdentifier(variables[i].name))
1362 | {
1363 | allNamesValid = false;
1364 | }
1365 | }
1366 |
1367 | if (indexToRemove != -1)
1368 | variables.RemoveAt(indexToRemove);
1369 |
1370 | if (GUILayout.Button("Add", GUILayout.Width(40f)))
1371 | variables.Add(new Variable(newName, usableTypes[0]));
1372 |
1373 | if (!allNamesValid)
1374 | EditorGUILayout.HelpBox(
1375 | "One of the variables has an invalid character, make sure they don't contain any spaces or special characters.",
1376 | MessageType.Error);
1377 |
1378 | EditorGUILayout.EndVertical();
1379 |
1380 | return allNamesValid;
1381 | }
1382 |
1383 | bool AllVariablesUniquelyNamed()
1384 | {
1385 | for (int i = 0; i < exposedReferences.Count; i++)
1386 | {
1387 | string exposedRefName = exposedReferences[i].name;
1388 |
1389 | for (int j = 0; j < exposedReferences.Count; j++)
1390 | {
1391 | if (i != j && exposedRefName == exposedReferences[j].name)
1392 | return false;
1393 | }
1394 |
1395 | for (int j = 0; j < playableBehaviourVariables.Count; j++)
1396 | {
1397 | if (exposedRefName == playableBehaviourVariables[j].name)
1398 | return false;
1399 | }
1400 | }
1401 |
1402 | for (int i = 0; i < playableBehaviourVariables.Count; i++)
1403 | {
1404 | string scriptPlayableVariableName = playableBehaviourVariables[i].name;
1405 |
1406 | for (int j = 0; j < exposedReferences.Count; j++)
1407 | {
1408 | if (scriptPlayableVariableName == exposedReferences[j].name)
1409 | return false;
1410 | }
1411 |
1412 | for (int j = 0; j < playableBehaviourVariables.Count; j++)
1413 | {
1414 | if (i != j && scriptPlayableVariableName == playableBehaviourVariables[j].name)
1415 | return false;
1416 | }
1417 | }
1418 |
1419 | return true;
1420 | }
1421 |
1422 | void ClipCapsGUI()
1423 | {
1424 | EditorGUILayout.BeginVertical(GUI.skin.box);
1425 |
1426 | ShowHelpBoxInfo(showHelpBoxes, m_ClipCapsContent);
1427 |
1428 | EditorGUILayout.LabelField(m_ClipCapsContent);
1429 |
1430 | bool isLooping = (clipCaps & ClipCaps.Looping) == ClipCaps.Looping;
1431 | bool isExtrapolation = (clipCaps & ClipCaps.Extrapolation) == ClipCaps.Extrapolation;
1432 | bool isClipIn = (clipCaps & ClipCaps.ClipIn) == ClipCaps.ClipIn;
1433 | bool isSpeedMultiplier = (clipCaps & ClipCaps.SpeedMultiplier) == ClipCaps.SpeedMultiplier;
1434 | bool isBlending = (clipCaps & ClipCaps.Blending) == ClipCaps.Blending;
1435 |
1436 | bool isNone = !isLooping && !isExtrapolation && !isClipIn && !isSpeedMultiplier && !isBlending;
1437 | bool isAll = isLooping && isExtrapolation && isClipIn && isSpeedMultiplier && isBlending;
1438 |
1439 | EditorGUI.BeginChangeCheck();
1440 | isNone = EditorGUILayout.ToggleLeft(m_CCNoneContent, isNone);
1441 | if (EditorGUI.EndChangeCheck())
1442 | {
1443 | if (isNone)
1444 | {
1445 | isLooping = false;
1446 | isExtrapolation = false;
1447 | isClipIn = false;
1448 | isSpeedMultiplier = false;
1449 | isBlending = false;
1450 | isAll = false;
1451 | }
1452 | }
1453 |
1454 | EditorGUI.BeginChangeCheck();
1455 | isLooping = EditorGUILayout.ToggleLeft(m_CCLoopingContent, isLooping);
1456 | isExtrapolation = EditorGUILayout.ToggleLeft(m_CCExtrapolationContent, isExtrapolation);
1457 | isClipIn = EditorGUILayout.ToggleLeft(m_CCClipInContent, isClipIn);
1458 | isSpeedMultiplier = EditorGUILayout.ToggleLeft(m_CCSpeedMultiplierContent, isSpeedMultiplier);
1459 | isBlending = EditorGUILayout.ToggleLeft(m_CCBlendingContent, isBlending);
1460 | if (EditorGUI.EndChangeCheck())
1461 | {
1462 | isNone = !isLooping && !isExtrapolation && !isClipIn && !isSpeedMultiplier && !isBlending;
1463 | isAll = isLooping && isExtrapolation && isClipIn && isSpeedMultiplier && isBlending;
1464 | }
1465 |
1466 | EditorGUI.BeginChangeCheck();
1467 | isAll = EditorGUILayout.ToggleLeft(m_CCAllContent, isAll);
1468 | if (EditorGUI.EndChangeCheck())
1469 | {
1470 | if (isAll)
1471 | {
1472 | isNone = false;
1473 | isLooping = true;
1474 | isExtrapolation = true;
1475 | isClipIn = true;
1476 | isSpeedMultiplier = true;
1477 | isBlending = true;
1478 | }
1479 | }
1480 |
1481 | EditorGUILayout.EndVertical();
1482 |
1483 | clipCaps = ClipCaps.None;
1484 |
1485 | if (isNone)
1486 | return;
1487 |
1488 | if (isAll)
1489 | {
1490 | clipCaps = ClipCaps.All;
1491 | return;
1492 | }
1493 |
1494 | if (isLooping)
1495 | clipCaps |= ClipCaps.Looping;
1496 |
1497 | if (isExtrapolation)
1498 | clipCaps |= ClipCaps.Extrapolation;
1499 |
1500 | if (isClipIn)
1501 | clipCaps |= ClipCaps.ClipIn;
1502 |
1503 | if (isSpeedMultiplier)
1504 | clipCaps |= ClipCaps.SpeedMultiplier;
1505 |
1506 | if (isBlending)
1507 | clipCaps |= ClipCaps.Blending;
1508 | }
1509 |
1510 | static void ShowHelpBoxInfo(bool showHelpBoxes, GUIContent content)
1511 | {
1512 | if (showHelpBoxes)
1513 | {
1514 | EditorGUILayout.HelpBox(content.tooltip, MessageType.Info);
1515 | EditorGUILayout.Space();
1516 | }
1517 | }
1518 |
1519 | static bool IsValidIdentifier(string identifier)
1520 | {
1521 | if (string.IsNullOrEmpty(identifier)) return false;
1522 |
1523 | string pattern = @"^[a-zA-Z_][a-zA-Z0-9_]*$";
1524 | return Regex.IsMatch(identifier, pattern);
1525 | }
1526 |
1527 | void ResetWindow()
1528 | {
1529 | playableName = "";
1530 | workType = WorkType.Component;
1531 | TrackBinding = s_TrackBindingTypes[0];
1532 | defaultValuesComponent = null;
1533 | defaultValuesVolume = null;
1534 | // defaultValuesVolumeComponent = null;
1535 | exposedReferences = new List();
1536 | playableBehaviourVariables = new List();
1537 | standardBlendPlayableProperties = new List();
1538 | postProcessVolumeProperties = new List();
1539 | clipCaps = ClipCaps.None;
1540 | trackColor = new Color(240 / 255f, 248 / 255f, 255 / 255f); // Alice-Blue ~
1541 |
1542 | m_ComponentBindingTypeIndex = 0;
1543 | m_TrackBindingProperties = null;
1544 | m_TrackBindingFields = null;
1545 | m_TrackBindingUsableProperties = new List();
1546 | m_CreateDrawer = false;
1547 | }
1548 | }
1549 | }
--------------------------------------------------------------------------------