├── .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 | ![](https://r2.youngmoe.com/ym-r2-bucket/2023/11/fb552984c57c7f0d554303d97d4387c6.gif) 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 | ![](https://r2.youngmoe.com/ym-r2-bucket/2023/11/fb552984c57c7f0d554303d97d4387c6.gif) 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 | } --------------------------------------------------------------------------------