├── SimpleAnimator ├── doc │ ├── 1.png │ ├── 2.png │ ├── 3.png │ ├── 4.png │ ├── 5.png │ ├── 1.png.meta │ ├── 2.png.meta │ ├── 3.png.meta │ ├── 4.png.meta │ └── 5.png.meta ├── doc.meta ├── SimpleAnimation.cs ├── README.md ├── Editor │ ├── ScriptableObjectUtility.cs │ └── SimpleAnimationEditor.cs └── SimpleAnimator.cs ├── Git ├── Git.cs.meta └── Git.cs ├── Builder ├── Sequences.meta ├── Settings.meta ├── BuilderEditor.cs.meta ├── BuilderUtils.cs.meta ├── Settings │ ├── BuilderSettings.cs.meta │ ├── AndroidBuilderSettings.cs.meta │ ├── iOSBuilderSettings.cs.meta │ ├── NintendoSwitchBuilderSettings.cs.meta │ ├── iOSBuilderSettings.cs │ ├── NintendoSwitchBuilderSettings.cs │ ├── BuilderSettings.cs │ └── AndroidBuilderSettings.cs ├── Sequences │ ├── SteamSequenceSettings.cs.meta │ ├── BuilderSequenceSettings.cs.meta │ ├── BuilderSequenceSettings.cs │ └── SteamSequenceSettings.cs ├── BuildAssetBundles.cs.meta ├── BuildAssetBundles.cs ├── BuilderUtils.cs └── BuilderEditor.cs ├── AssetsManager ├── README.md └── AssetsManager.cs ├── Screenshoter ├── ScreenshotWindow.cs.meta └── ScreenshotWindow.cs ├── README.md ├── .gitignore ├── LICENSE ├── Misc └── AnimationWindowHelper.cs └── Thumbnail └── ThumbnailsEditorWindow.cs /SimpleAnimator/doc/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valryon/tools-unity/HEAD/SimpleAnimator/doc/1.png -------------------------------------------------------------------------------- /SimpleAnimator/doc/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valryon/tools-unity/HEAD/SimpleAnimator/doc/2.png -------------------------------------------------------------------------------- /SimpleAnimator/doc/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valryon/tools-unity/HEAD/SimpleAnimator/doc/3.png -------------------------------------------------------------------------------- /SimpleAnimator/doc/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valryon/tools-unity/HEAD/SimpleAnimator/doc/4.png -------------------------------------------------------------------------------- /SimpleAnimator/doc/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/valryon/tools-unity/HEAD/SimpleAnimator/doc/5.png -------------------------------------------------------------------------------- /Git/Git.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 8ee3fc23fbec401b9212f7a9b310550b 3 | timeCreated: 1634818979 -------------------------------------------------------------------------------- /Builder/Sequences.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 715cc608f594429d8cac7bcab5bf73dc 3 | timeCreated: 1612887453 -------------------------------------------------------------------------------- /Builder/Settings.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c69d298647904495abf8129afd4cbd26 3 | timeCreated: 1612887440 -------------------------------------------------------------------------------- /Builder/BuilderEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 38e8b4058bf64ff39ba48650f6bca0cc 3 | timeCreated: 1612799262 -------------------------------------------------------------------------------- /Builder/BuilderUtils.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a8854b8e0d3e4ab6b73f35e9049a0976 3 | timeCreated: 1612884339 -------------------------------------------------------------------------------- /Builder/Settings/BuilderSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 754ef588bef148209c6618198ae2cb9a 3 | timeCreated: 1612799768 -------------------------------------------------------------------------------- /Builder/Sequences/SteamSequenceSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0e755fd7a2194b588dfaceede5f38fe6 3 | timeCreated: 1612866253 -------------------------------------------------------------------------------- /Builder/Settings/AndroidBuilderSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 005a71e44fbb4b80a1dfc82c8a2e4a23 3 | timeCreated: 1612887379 -------------------------------------------------------------------------------- /Builder/Settings/iOSBuilderSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 79cd8162c036443badddb62e168b2f60 3 | timeCreated: 1612887412 -------------------------------------------------------------------------------- /AssetsManager/README.md: -------------------------------------------------------------------------------- 1 | # Asset Bundle easy manager 2 | 3 | WIP 4 | 5 | Combine with . -------------------------------------------------------------------------------- /Builder/Sequences/BuilderSequenceSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cb275a0953d64070a2498a354d256f9e 3 | timeCreated: 1612866749 -------------------------------------------------------------------------------- /Builder/Settings/NintendoSwitchBuilderSettings.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 88188d50e44a4311aed58e22d90d6ee9 3 | timeCreated: 1612889586 -------------------------------------------------------------------------------- /SimpleAnimator/doc.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4067394115bd8e4ca7e2393f1b59464 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Builder/BuildAssetBundles.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 32a90c06a194bcd49a3e86a42aa7fc92 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Screenshoter/ScreenshotWindow.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 10daf779197cb4a4fb653316f93865a6 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tools collection 2 | 3 | A repository with several tools I made and that could be useful to someone else. 4 | 5 | - [SimpleAnimator](/SimpleAnimator) : A 2D frame by frame animator used in [Steredenn](https://steredenn.pixelnest.io) 6 | - [Builder](/Builder) : A build system for Unity with a nice editor to up version number and change platform targets 7 | - [Thumbnail maker](/Thumbnail) : An editor window to quickly create a screenshot of a GameObject with a renderer 8 | - [Misc](/Misc) : Collections of scripts I like -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /[Ll]ibrary/ 2 | /[Tt]emp/ 3 | /[Oo]bj/ 4 | /[Bb]uild/ 5 | /[Bb]uilds/ 6 | /Assets/AssetStoreTools* 7 | 8 | # Visual Studio 2015 cache directory 9 | /.vs/ 10 | 11 | # Autogenerated VS/MD/Consulo solution and project files 12 | ExportedObj/ 13 | .consulo/ 14 | *.csproj 15 | *.unityproj 16 | *.sln 17 | *.suo 18 | *.tmp 19 | *.user 20 | *.userprefs 21 | *.pidb 22 | *.booproj 23 | *.svd 24 | *.pdb 25 | 26 | # Unity3D generated meta files 27 | *.pidb.meta 28 | 29 | # Unity3D Generated File On Crash Reports 30 | sysinfo.txt 31 | 32 | # Builds 33 | *.apk 34 | -------------------------------------------------------------------------------- /SimpleAnimator/SimpleAnimation.cs: -------------------------------------------------------------------------------- 1 | // This file is subject to the terms and conditions defined in 2 | // file 'LICENSE.md', which is part of this source code package. 3 | using UnityEngine; 4 | 5 | /// 6 | /// Simple animation clip 7 | /// 8 | public class SimpleAnimation : ScriptableObject 9 | { 10 | public new string name = "newAnimation"; 11 | public float imagesPerSeconds = 24f; 12 | public bool loop = false; 13 | public bool randomFirstFrame = false; 14 | public Sprite[] frames; 15 | 16 | /// 17 | /// Allow global update via menu 18 | /// 19 | public bool allowAutoUpdate = true; 20 | 21 | public float FrameDuration => 1f / imagesPerSeconds; 22 | 23 | /// 24 | /// Duration, in seconds 25 | /// 26 | public float Duration => FrameDuration * frames.Length; 27 | } -------------------------------------------------------------------------------- /Builder/Settings/iOSBuilderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Builder 6 | { 7 | [CreateAssetMenu(fileName = "iOSBuilderSettings", menuName = "Flat Eye/Builder/iOS Settings", 8 | order = 10000)] 9 | public class IOSBuilderSettings : BuilderSettings 10 | { 11 | public int buildNumber; 12 | 13 | public override Task OnPreBuild() 14 | { 15 | PlayerSettings.iOS.buildNumber = buildNumber.ToString(); 16 | return Task.CompletedTask; 17 | } 18 | 19 | public override void DrawCustomSettings() 20 | { 21 | EditorGUILayout.BeginVertical("Box"); 22 | { 23 | EditorGUILayout.LabelField("iOS", EditorStyles.boldLabel); 24 | 25 | buildNumber = EditorGUILayout.IntField("Build number", buildNumber); 26 | } 27 | EditorGUILayout.EndVertical(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Builder/Settings/NintendoSwitchBuilderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Builder 6 | { 7 | [CreateAssetMenu(fileName = "NintendoSwitchBuilderSettings", menuName = "Flat Eye/Builder/Nintendo Switch Settings", 8 | order = 10000)] 9 | public class NintendoSwitchBuilderSettings : BuilderSettings 10 | { 11 | public int releaseVersion; 12 | 13 | public override Task OnPreBuild() 14 | { 15 | PlayerSettings.iOS.buildNumber = releaseVersion.ToString(); 16 | return Task.CompletedTask; 17 | } 18 | 19 | public override void DrawCustomSettings() 20 | { 21 | EditorGUILayout.BeginVertical("Box"); 22 | { 23 | EditorGUILayout.LabelField("Nintendo Switch", EditorStyles.boldLabel); 24 | 25 | releaseVersion = EditorGUILayout.IntField("Release version", releaseVersion); 26 | } 27 | EditorGUILayout.EndVertical(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Damien Mayance 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 | -------------------------------------------------------------------------------- /Builder/Sequences/BuilderSequenceSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace Builder 7 | { 8 | [CreateAssetMenu(fileName = "BuilderSequenceSettings", menuName = "Flat Eye/Builder/Sequence", order = 0)] 9 | public class BuilderSequenceSettings : ScriptableObject 10 | { 11 | public List sequence = new List(); 12 | public bool devBuild = true; 13 | public bool continueIfError = true; 14 | public string version; 15 | public string flags; 16 | public string openURL; 17 | 18 | public virtual void DrawCustomSettings() 19 | { 20 | foreach (var s in sequence) 21 | { 22 | EditorGUILayout.BeginHorizontal(); 23 | { 24 | EditorGUI.BeginDisabledGroup(true); 25 | EditorGUILayout.ObjectField(s, typeof(BuilderSettings), false); 26 | EditorGUI.EndDisabledGroup(); 27 | } 28 | EditorGUILayout.EndHorizontal(); 29 | } 30 | } 31 | 32 | public virtual Task OnPreSequence() 33 | { 34 | return Task.CompletedTask; 35 | } 36 | 37 | public virtual Task OnPostSequence() 38 | { 39 | return Task.CompletedTask; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /SimpleAnimator/README.md: -------------------------------------------------------------------------------- 1 | # SimpleAnimator-unity 2 | 3 | A simple 2D animator for Unity, lightweight and easier to use than Mecanim for large volumes of sprites or files that often change. 4 | 5 | Compatible with Unity 4.2+. 6 | 7 | Used in the game [Steredenn](http://steredenn.pixelnest.io) to handle 24000 sprites and thousands of animations. 8 | 9 | **Disclaimer: this is a very rough release and it is provided ith no support!** 10 | 11 | ## Installation 12 | 13 | - Download the sources and copy them to your project. 14 | 15 | OR 16 | 17 | - Download the latest release and install the `.unitypackage` in your project. 18 | 19 | ## Usage 20 | 21 | 1/ Right-clic on a folder with all your animation frames 22 | 23 | ![Screen](./doc/1.png) 24 | 25 | 2/ Create a new 2D/Simple Animation 26 | 27 | ![Screen](./doc/2.png) 28 | 29 | 3/ A ScriptableObject should have been created. You can preview the animation and tweak the settings. 30 | 31 | ![Screen](./doc/3.png) 32 | 33 | 4/ Create a GameObject with a SpriteRenderer and a SimpleAnimator. 34 | 35 | 5/ Assign the created animation object to the SimpleAnimator. If you have several animations, you should list them all in `clips`. 36 | 37 | ![Screen](./doc/4.png) 38 | 39 | 6/ Your animation should now play. If you want the GameObject to be automatically destroyed at the end of the animation, check the following setting: 40 | 41 | ![Screen](./doc/5.png) 42 | 43 | ## Code usage 44 | 45 | ```csharp 46 | var anim = GetComponent(); 47 | anim.Play("attack"); 48 | ``` 49 | -------------------------------------------------------------------------------- /Builder/Settings/BuilderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Threading.Tasks; 3 | using UnityEditor; 4 | using UnityEngine; 5 | 6 | namespace Builder 7 | { 8 | [CreateAssetMenu(fileName = "BuilderSettings", menuName = "Flat Eye/Builder/Settings", order = 10000)] 9 | public class BuilderSettings : ScriptableObject 10 | { 11 | public BuildTarget target; 12 | public BuildOptions buildOptions; 13 | public string outputFolder; 14 | public string executableName; 15 | public bool buildAB = true; 16 | 17 | public virtual void DrawCustomSettings() { } 18 | 19 | public string GetExecutablePath() 20 | { 21 | if (string.IsNullOrEmpty(outputFolder) || string.IsNullOrEmpty(executableName)) return string.Empty; 22 | if (string.IsNullOrEmpty(outputFolder) == false && Path.IsPathRooted(outputFolder) == false) 23 | { 24 | return Path.GetFullPath(Path.Combine(Application.dataPath, 25 | outputFolder, executableName)); 26 | } 27 | 28 | return Path.Combine(outputFolder, executableName); 29 | } 30 | 31 | public string GetExecutableFolder() 32 | { 33 | return Path.GetDirectoryName(GetExecutablePath()); 34 | } 35 | 36 | public virtual Task OnPreBuild() 37 | { 38 | return Task.CompletedTask; 39 | } 40 | 41 | public virtual Task OnPostBuild() 42 | { 43 | return Task.CompletedTask; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /Screenshoter/ScreenshotWindow.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using UnityEngine; 4 | using UnityEditor; 5 | using System.IO; 6 | 7 | /// 8 | /// The super useful screenshoter window 9 | /// 10 | public class ScreenshotWindow : EditorWindow 11 | { 12 | #region Menus 13 | 14 | [MenuItem("Tools/Screenshoter")] 15 | public static void ShowWindow() 16 | { 17 | GetWindow(typeof(ScreenshotWindow)); 18 | } 19 | 20 | #endregion 21 | 22 | private string screenshotLocation = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Desktop); 23 | 24 | void OnGUI() 25 | { 26 | titleContent = new GUIContent("Screenshoter"); 27 | 28 | EditorGUILayout.BeginVertical("Box"); 29 | { 30 | EditorGUILayout.LabelField("Screenshoter", EditorStyles.boldLabel); 31 | 32 | EditorGUILayout.LabelField("Path"); 33 | screenshotLocation = EditorGUILayout.TextArea(screenshotLocation); 34 | 35 | EditorGUILayout.BeginHorizontal(); 36 | 37 | EditorGUILayout.Space(); 38 | 39 | if (GUILayout.Button("Screenshot")) 40 | { 41 | TakeScreenshot(); 42 | } 43 | 44 | EditorGUILayout.EndHorizontal(); 45 | } 46 | EditorGUILayout.EndVertical(); 47 | } 48 | 49 | 50 | private void TakeScreenshot() 51 | { 52 | string filename = ScreenShotName("unity"); 53 | ScreenCapture.CaptureScreenshot(filename, 1); 54 | Debug.Log($"Screenshot saved to {filename}"); 55 | } 56 | 57 | private string ScreenShotName(string source) 58 | { 59 | return $"{screenshotLocation}/screen_{source}_{System.DateTime.Now:yyyy-MM-dd_HH-mm-ss}.png"; 60 | } 61 | } 62 | 63 | #endif 64 | -------------------------------------------------------------------------------- /SimpleAnimator/Editor/ScriptableObjectUtility.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | using UnityEngine; 5 | using UnityEditor; 6 | using System.IO; 7 | 8 | /// 9 | /// ScriptableObject helper 10 | /// 11 | /// Source: http://www.jacobpennock.com/Blog/?page_id=715 12 | public static class ScriptableObjectUtility 13 | { 14 | public static T CreateAsset() where T : ScriptableObject 15 | { 16 | return CreateAsset("New " + typeof(T).ToString() + ".asset"); 17 | } 18 | 19 | public static T CreateAsset(string name) where T : ScriptableObject 20 | { 21 | if (name.EndsWith(".asset") == false) 22 | { 23 | name = name + ".asset"; 24 | } 25 | 26 | string path = AssetDatabase.GetAssetPath(Selection.activeObject); 27 | if (path == "") 28 | { 29 | path = "Assets"; 30 | } 31 | else if (Path.GetExtension(path) != "") 32 | { 33 | path = path.Replace(Path.GetFileName(AssetDatabase.GetAssetPath(Selection.activeObject)), ""); 34 | } 35 | 36 | return CreateAsset(path, name); 37 | } 38 | 39 | public static T CreateAsset(string path, string name) where T : ScriptableObject 40 | { 41 | if (name.EndsWith(".asset") == false) 42 | { 43 | name = name + ".asset"; 44 | } 45 | 46 | string assetPathAndName = AssetDatabase.GenerateUniqueAssetPath(path + "/" + name); 47 | 48 | T asset = ScriptableObject.CreateInstance(); 49 | 50 | AssetDatabase.CreateAsset(asset, assetPathAndName); 51 | 52 | AssetDatabase.SaveAssets(); 53 | EditorUtility.FocusProjectWindow(); 54 | Selection.activeObject = asset; 55 | 56 | return asset; 57 | } 58 | } 59 | #endif -------------------------------------------------------------------------------- /Builder/BuildAssetBundles.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using System.IO; 3 | using UnityEngine; 4 | using UnityEditor; 5 | 6 | public static class BuildAssetBundles 7 | { 8 | private const string BASE_PATH = "Builds/📦 "; 9 | private const string BUILD_PATH = BASE_PATH + "Asset Bundles/Build"; 10 | 11 | 12 | #if ENABLE_AB 13 | [MenuItem(BUILD_PATH, priority = 10)] 14 | public static void BuildAB() 15 | { 16 | Build(); 17 | } 18 | #endif 19 | 20 | public static void Build(BuildTarget target = BuildTarget.NoTarget) 21 | { 22 | #if ENABLE_AB 23 | target = target == BuildTarget.NoTarget ? EditorUserBuildSettings.activeBuildTarget : target; 24 | string path = Application.streamingAssetsPath + "/" + AssetBundles.Utility.GetPlatformForAssetBundles(target); 25 | Debug.Log("Build AssetBundles platform=[" + target + "] path=[" + path + "]"); 26 | 27 | if (Directory.Exists(path) == false) 28 | { 29 | Directory.CreateDirectory(path); 30 | } 31 | 32 | var manifest = 33 | BuildPipeline.BuildAssetBundles(path, BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression, target); 34 | if (manifest == null) 35 | { 36 | Debug.LogError("Error in AB build"); 37 | return; 38 | } 39 | 40 | // Rename appropriately 41 | string validName = AssetBundles.Utility.GetPlatformForAssetBundles(target); 42 | foreach (var f in Directory.GetFiles(path)) 43 | { 44 | if (Path.GetFileName(f).Contains("StreamingAssets")) 45 | { 46 | var ext = Path.GetExtension(f); 47 | if (ext != ".meta") 48 | { 49 | var newFilename = Path.Combine(Path.GetDirectoryName(f), validName + ext); 50 | File.Copy(f, newFilename, true); 51 | File.Delete(f); 52 | } 53 | } 54 | } 55 | #else 56 | Debug.LogError("Asset bundles disabled in this project."); 57 | #endif 58 | } 59 | } 60 | #endif -------------------------------------------------------------------------------- /SimpleAnimator/doc/1.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9b39a7ef25de05642a740e4d03a7ecf8 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | externalObjects: {} 6 | serializedVersion: 9 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 2 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | spriteSheet: 73 | serializedVersion: 2 74 | sprites: [] 75 | outline: [] 76 | physicsShape: [] 77 | bones: [] 78 | spriteID: 987742b6292dc9f45889879bbf189780 79 | vertices: [] 80 | indices: 81 | edges: [] 82 | weights: [] 83 | spritePackingTag: 84 | pSDRemoveMatte: 0 85 | pSDShowRemoveMatteOption: 0 86 | userData: 87 | assetBundleName: 88 | assetBundleVariant: 89 | -------------------------------------------------------------------------------- /SimpleAnimator/doc/2.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 338cc098869533e4994b481a86f85488 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | externalObjects: {} 6 | serializedVersion: 9 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 2 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | spriteSheet: 73 | serializedVersion: 2 74 | sprites: [] 75 | outline: [] 76 | physicsShape: [] 77 | bones: [] 78 | spriteID: 426ae5f4c4ddf104bb03d9db6fa3e62a 79 | vertices: [] 80 | indices: 81 | edges: [] 82 | weights: [] 83 | spritePackingTag: 84 | pSDRemoveMatte: 0 85 | pSDShowRemoveMatteOption: 0 86 | userData: 87 | assetBundleName: 88 | assetBundleVariant: 89 | -------------------------------------------------------------------------------- /SimpleAnimator/doc/3.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0f44e98020280444b8cbf96aa606939f 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | externalObjects: {} 6 | serializedVersion: 9 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 2 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | spriteSheet: 73 | serializedVersion: 2 74 | sprites: [] 75 | outline: [] 76 | physicsShape: [] 77 | bones: [] 78 | spriteID: aa5bb94793705d34f98f03a69bcec70b 79 | vertices: [] 80 | indices: 81 | edges: [] 82 | weights: [] 83 | spritePackingTag: 84 | pSDRemoveMatte: 0 85 | pSDShowRemoveMatteOption: 0 86 | userData: 87 | assetBundleName: 88 | assetBundleVariant: 89 | -------------------------------------------------------------------------------- /SimpleAnimator/doc/4.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc32a741f1960d345b4f85ace8660a13 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | externalObjects: {} 6 | serializedVersion: 9 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 2 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | spriteSheet: 73 | serializedVersion: 2 74 | sprites: [] 75 | outline: [] 76 | physicsShape: [] 77 | bones: [] 78 | spriteID: efd937ce6a43d7d42ac39d09de3cb512 79 | vertices: [] 80 | indices: 81 | edges: [] 82 | weights: [] 83 | spritePackingTag: 84 | pSDRemoveMatte: 0 85 | pSDShowRemoveMatteOption: 0 86 | userData: 87 | assetBundleName: 88 | assetBundleVariant: 89 | -------------------------------------------------------------------------------- /SimpleAnimator/doc/5.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d515d5161b8db4f4398652353d1c9ba9 3 | TextureImporter: 4 | fileIDToRecycleName: {} 5 | externalObjects: {} 6 | serializedVersion: 9 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | grayScaleToAlpha: 0 27 | generateCubemap: 6 28 | cubemapConvolution: 0 29 | seamlessCubemap: 0 30 | textureFormat: 1 31 | maxTextureSize: 2048 32 | textureSettings: 33 | serializedVersion: 2 34 | filterMode: -1 35 | aniso: -1 36 | mipBias: -100 37 | wrapU: 1 38 | wrapV: 1 39 | wrapW: 1 40 | nPOTScale: 0 41 | lightmap: 0 42 | compressionQuality: 50 43 | spriteMode: 1 44 | spriteExtrude: 1 45 | spriteMeshType: 1 46 | alignment: 0 47 | spritePivot: {x: 0.5, y: 0.5} 48 | spritePixelsToUnits: 100 49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 50 | spriteGenerateFallbackPhysicsShape: 1 51 | alphaUsage: 1 52 | alphaIsTransparency: 1 53 | spriteTessellationDetail: -1 54 | textureType: 8 55 | textureShape: 1 56 | singleChannelComponent: 0 57 | maxTextureSizeSet: 0 58 | compressionQualitySet: 0 59 | textureFormatSet: 0 60 | platformSettings: 61 | - serializedVersion: 2 62 | buildTarget: DefaultTexturePlatform 63 | maxTextureSize: 2048 64 | resizeAlgorithm: 0 65 | textureFormat: -1 66 | textureCompression: 1 67 | compressionQuality: 50 68 | crunchedCompression: 0 69 | allowsAlphaSplitting: 0 70 | overridden: 0 71 | androidETC2FallbackOverride: 0 72 | spriteSheet: 73 | serializedVersion: 2 74 | sprites: [] 75 | outline: [] 76 | physicsShape: [] 77 | bones: [] 78 | spriteID: c31a105f5b2bbc247b24bdcf0b715728 79 | vertices: [] 80 | indices: 81 | edges: [] 82 | weights: [] 83 | spritePackingTag: 84 | pSDRemoveMatte: 0 85 | pSDShowRemoveMatteOption: 0 86 | userData: 87 | assetBundleName: 88 | assetBundleVariant: 89 | -------------------------------------------------------------------------------- /Builder/BuilderUtils.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Builder 6 | { 7 | public static class BuilderUtils 8 | { 9 | public static string BrowseField(string text, string value, string extension) 10 | { 11 | EditorGUILayout.BeginHorizontal(); 12 | 13 | value = EditorGUILayout.TextField(text, value); 14 | if (GUILayout.Button("...", GUILayout.Width(50))) 15 | { 16 | string newPath = null; 17 | 18 | if (string.IsNullOrEmpty(extension)) 19 | { 20 | newPath = EditorUtility.OpenFolderPanel("Select folder", "", ""); 21 | } 22 | else 23 | { 24 | newPath = EditorUtility.OpenFilePanel("Select save file", "", extension); 25 | } 26 | 27 | if (string.IsNullOrEmpty(newPath) == false) 28 | { 29 | value = newPath; 30 | } 31 | } 32 | 33 | EditorGUILayout.EndHorizontal(); 34 | 35 | return value; 36 | } 37 | 38 | public static T[] FindAllAssets() where T : Object 39 | { 40 | string searchTerm = "t:" + typeof(T).Name; 41 | return AssetDatabase.FindAssets(searchTerm) 42 | .Select(guid => AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid))) 43 | .ToArray(); 44 | } 45 | 46 | public static string GetPlatformForAssetBundles(BuildTarget target) 47 | { 48 | switch (target) 49 | { 50 | case BuildTarget.Android: 51 | return "Android"; 52 | case BuildTarget.iOS: 53 | return "iOS"; 54 | case BuildTarget.tvOS: 55 | return "tvOS"; 56 | case BuildTarget.WebGL: 57 | return "WebGL"; 58 | case BuildTarget.StandaloneWindows: 59 | case BuildTarget.StandaloneWindows64: 60 | return "StandaloneWindows"; 61 | #if UNITY_2017_4_OR_NEWER 62 | case BuildTarget.StandaloneOSX: 63 | return "StandaloneOSX"; 64 | #else 65 | case BuildTarget.StandaloneOSXIntel: 66 | case BuildTarget.StandaloneOSXIntel64: 67 | return "StandaloneOSXIntel"; 68 | #endif 69 | // Add more build targets for your own. 70 | // If you add more targets, don't forget to add the same platforms to the function below. 71 | case BuildTarget.StandaloneLinux64: 72 | return "StandaloneLinux"; 73 | #if UNITY_SWITCH 74 | case BuildTarget.Switch: 75 | return "Switch"; 76 | #endif 77 | default: 78 | Debug.Log("Unknown BuildTarget: Using Default Enum Name: " + target); 79 | return target.ToString(); 80 | } 81 | } 82 | } 83 | } -------------------------------------------------------------------------------- /Builder/Settings/AndroidBuilderSettings.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | namespace Builder 6 | { 7 | [CreateAssetMenu(fileName = "AndroidBuilderSettings", menuName = "Flat Eye/Builder/Android Settings", 8 | order = 10000)] 9 | public class AndroidBuilderSettings : BuilderSettings 10 | { 11 | public bool aab; 12 | public int buildVersionCode; 13 | 14 | public override void DrawCustomSettings() 15 | { 16 | EditorGUILayout.BeginVertical("Box"); 17 | { 18 | EditorGUILayout.LabelField("Android", EditorStyles.boldLabel); 19 | 20 | aab = EditorGUILayout.Toggle("AAB", aab); 21 | buildVersionCode = EditorGUILayout.IntField("Build version code", buildVersionCode); 22 | 23 | EditorGUILayout.LabelField("Signing", EditorStyles.boldLabel); 24 | KeyStoreLocation = EditorGUILayout.TextField("KeyStore name", KeyStoreLocation); 25 | KeyStorePassword = EditorGUILayout.PasswordField("KeyStore pass", KeyStorePassword); 26 | KeyAliasName = EditorGUILayout.TextField("KeyAlias name", KeyAliasName); 27 | KeyAliasPassword = EditorGUILayout.PasswordField("KeyAlias pass", KeyAliasPassword); 28 | } 29 | EditorGUILayout.EndVertical(); 30 | } 31 | 32 | public override Task OnPreBuild() 33 | { 34 | PlayerSettings.Android.bundleVersionCode = buildVersionCode; 35 | PlayerSettings.Android.useCustomKeystore = true; 36 | PlayerSettings.Android.keystoreName = KeyStoreLocation; 37 | PlayerSettings.Android.keystorePass = KeyStorePassword; 38 | PlayerSettings.Android.keyaliasName = KeyAliasName; 39 | PlayerSettings.Android.keyaliasPass = KeyAliasPassword; 40 | EditorUserBuildSettings.buildAppBundle = aab; 41 | return Task.CompletedTask; 42 | } 43 | 44 | public string KeyStoreLocation 45 | { 46 | get => EditorPrefs.GetString("Builder.Android.Keystore"); 47 | set => EditorPrefs.SetString("Builder.Android.Keystore", value); 48 | } 49 | 50 | public string KeyStorePassword 51 | { 52 | get => EditorPrefs.GetString("Builder.Android.Keystore.Password"); 53 | set => EditorPrefs.SetString("Builder.Android.Keystore.Password", value); 54 | } 55 | 56 | public string KeyAliasName 57 | { 58 | get => EditorPrefs.GetString("Builder.Android.KeyAlias"); 59 | set => EditorPrefs.SetString("Builder.Android.KeyAlias", value); 60 | } 61 | 62 | public string KeyAliasPassword 63 | { 64 | get => EditorPrefs.GetString("Builder.Android.KeyAlias.Password"); 65 | set => EditorPrefs.SetString("Builder.Android.KeyAlias.Password", value); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Git/Git.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | Copyright (c) 2016 RedBlueGames 3 | Code written by Doug Cox 4 | */ 5 | 6 | using System; 7 | using System.Diagnostics; 8 | using System.Text; 9 | using System.Text.RegularExpressions; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | using Debug = UnityEngine.Debug; 13 | 14 | /// 15 | /// GitException includes the error output from a Git.Run() command as well as the 16 | /// ExitCode it returned. 17 | /// 18 | /// https://gist.github.com/edwardrowe/fdec706fe53bfff0671e063f263ada63 19 | public class GitException : InvalidOperationException 20 | { 21 | public GitException(int exitCode, string errors) : base(errors) => 22 | ExitCode = exitCode; 23 | 24 | /// 25 | /// The exit code returned when running the Git command. 26 | /// 27 | public readonly int ExitCode; 28 | } 29 | 30 | public static class Git 31 | { 32 | /// 33 | /// The currently active branch. 34 | /// 35 | public static string Branch => Run(@"rev-parse --abbrev-ref HEAD"); 36 | 37 | /// 38 | /// Returns a listing of all uncommitted or untracked (added) files. 39 | /// 40 | public static string Status => Run(@"status --porcelain"); 41 | 42 | 43 | public static string Log => Run(@"log --oneline"); 44 | 45 | // https://stackoverflow.com/questions/1441010/the-shortest-possible-output-from-git-log-containing-author-and-date 46 | // %h = abbreviated commit hash 47 | // %x09 = tab (character for code 9) 48 | // %an = author name 49 | // %ad = author date (format respects --date= option) 50 | // %s = subject 51 | /// 52 | /// Log 150 commits with specific format hash\tauthor\tmessage 53 | /// 54 | public static string LogWithFormat => Run("log HEAD~150.. --pretty=format:\"%h%x09%an%x09%s\""); 55 | 56 | /// 57 | /// Runs git exe with the specified arguments and returns the output. 58 | /// 59 | public static string Run(string arguments, int timeout = 10000) 60 | { 61 | using (var process = new Process 62 | { 63 | StartInfo = 64 | { 65 | FileName = "git", 66 | Arguments = arguments, 67 | CreateNoWindow = true, 68 | UseShellExecute = false, 69 | RedirectStandardOutput = true, 70 | RedirectStandardError = true 71 | } 72 | }) 73 | { 74 | var output = new StringBuilder(); 75 | var error = new StringBuilder(); 76 | 77 | using (var outputWaitHandle = new AutoResetEvent(false)) 78 | using (var errorWaitHandle = new AutoResetEvent(false)) 79 | { 80 | process.OutputDataReceived += (sender, e) => 81 | { 82 | if (e.Data == null) 83 | { 84 | outputWaitHandle.Set(); 85 | } 86 | else 87 | { 88 | output.AppendLine(e.Data); 89 | } 90 | }; 91 | process.ErrorDataReceived += (sender, e) => 92 | { 93 | if (e.Data == null) 94 | { 95 | errorWaitHandle.Set(); 96 | } 97 | else 98 | { 99 | error.AppendLine(e.Data); 100 | } 101 | }; 102 | 103 | process.Start(); 104 | 105 | process.BeginOutputReadLine(); 106 | process.BeginErrorReadLine(); 107 | 108 | if (process.WaitForExit(timeout) && 109 | outputWaitHandle.WaitOne(timeout) && 110 | errorWaitHandle.WaitOne(timeout)) 111 | { 112 | if (process.ExitCode == 0) 113 | { 114 | return output.ToString(); 115 | } 116 | else 117 | { 118 | throw new GitException(process.ExitCode, error.ToString()); 119 | } 120 | 121 | // Process completed. Check process.ExitCode here. 122 | } 123 | else 124 | { 125 | process.CancelOutputRead(); 126 | throw new GitException(1, "Timeout"); 127 | } 128 | } 129 | } 130 | } 131 | 132 | /// 133 | /// Runs git exe asynchronously with the specified arguments and returns the output. 134 | /// 135 | public static async Task RunAsync(string arguments, int timeout = 10000) 136 | { 137 | return await Task.Run(() => Run(arguments, timeout)); 138 | } 139 | 140 | public static string GetShortCommitHash() 141 | { 142 | var commit = Run("rev-parse HEAD"); 143 | return Regex.Replace(commit.Substring(0, 9), @"\t|\n|\r", ""); 144 | } 145 | } -------------------------------------------------------------------------------- /Misc/AnimationWindowHelper.cs: -------------------------------------------------------------------------------- 1 | // This file is subject to the terms and conditions defined in 2 | // file 'LICENSE.md', which is part of this source code package. 3 | using System; 4 | using System.Linq; 5 | using System.Reflection; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | /// 10 | /// Get access to some properties and controls of the AnimationWindow. 11 | /// 12 | /// It may breaks over time... 13 | public static class AnimationWindowHelper 14 | { 15 | private static EditorWindow _window; 16 | 17 | private static BindingFlags _flags; 18 | private static FieldInfo _animEditor; 19 | 20 | private static Type _animEditorType; 21 | private static System.Object _animEditorObject; 22 | private static FieldInfo _animWindowState; 23 | 24 | /// 25 | /// Force open the Animation window 26 | /// 27 | public static void OpenWindow() 28 | { 29 | EditorApplication.ExecuteMenuItem("Window/Animation/Animation"); 30 | 31 | GetOpenAnimationWindow(); 32 | } 33 | 34 | #region Internal 35 | 36 | private static Type _animationWindowType; 37 | 38 | private static Type GetAnimationWindowType() 39 | { 40 | if (_animationWindowType == null) 41 | { 42 | _animationWindowType = Type.GetType("UnityEditor.AnimationWindow,UnityEditor"); 43 | } 44 | 45 | return _animationWindowType; 46 | } 47 | 48 | public static EditorWindow GetOpenAnimationWindow() 49 | { 50 | if (_window == null) 51 | { 52 | var openAnimationWindows = Resources.FindObjectsOfTypeAll(GetAnimationWindowType()); 53 | if (openAnimationWindows.Length > 0) 54 | { 55 | _window = openAnimationWindows[0] as EditorWindow; 56 | 57 | // Store all reflection info so we don't request them every time 58 | _flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; 59 | _animEditor = GetAnimationWindowType().GetField("m_AnimEditor", _flags); 60 | 61 | if (_animEditor != null) 62 | { 63 | _animEditorType = _animEditor.FieldType; 64 | _animEditorObject = _animEditor.GetValue(_window); 65 | } 66 | 67 | _animWindowState = _animEditorType.GetField("m_State", _flags); 68 | } 69 | } 70 | 71 | return _window; 72 | } 73 | 74 | #endregion 75 | 76 | #region Public methods 77 | 78 | public static void StartRecord() 79 | { 80 | InvokeMethod("set_recording", true); 81 | Repaint(); 82 | } 83 | 84 | public static void StopRecord() 85 | { 86 | InvokeMethod("set_recording", false); 87 | Repaint(); 88 | } 89 | 90 | public static bool IsRecording() 91 | { 92 | return AnimationMode.InAnimationMode(); 93 | } 94 | 95 | public static bool GetPlaying() 96 | { 97 | return InvokeMethod("get_playing"); 98 | } 99 | 100 | public static void Repaint() 101 | { 102 | InvokeMethod("Repaint"); 103 | } 104 | 105 | public static void StartPlayback() 106 | { 107 | InvokeMethod("StartPlayback"); 108 | Repaint(); 109 | } 110 | 111 | public static void StopPlayback() 112 | { 113 | InvokeMethod("StopPlayback"); 114 | Repaint(); 115 | } 116 | 117 | // public Void set_currentFrame(Int32) 118 | public static void SetCurrentFrame(int frame) 119 | { 120 | InvokeMethod("set_currentFrame", frame); 121 | Repaint(); 122 | } 123 | 124 | // public Int32 get_currentTime() 125 | public static int GetCurrentFrame() 126 | { 127 | return InvokeMethod("get_currentFrame", null); 128 | } 129 | 130 | // public Single get_currentTime() 131 | public static float GetCurrentTime() 132 | { 133 | return InvokeMethod("get_currentTime"); 134 | } 135 | 136 | // public Single TimeToFrame(Single) 137 | public static float TimeToFrame(float time) 138 | { 139 | return InvokeMethod("TimeToFrame", time); 140 | } 141 | 142 | // public Single FrameToTime(Single) 143 | public static float FrameToTime(float frame) 144 | { 145 | return InvokeMethod("FrameToTime", frame); 146 | } 147 | 148 | // public UnityEngine.Vector2 get_timeRange() 149 | public static Vector2 GetTimeRange() 150 | { 151 | return InvokeMethod("get_timeRange"); 152 | } 153 | 154 | 155 | private static object InvokeMethod(string methodName, params object[] methodParams) 156 | { 157 | if (_window != null) 158 | { 159 | // Get the State of the window. The object (with values) AND the type. 160 | var type = _animWindowState.FieldType; 161 | var target = _animWindowState.GetValue(_animEditorObject); 162 | 163 | // Now find the method in the state type 164 | var methodParamTypes = methodParams?.Select(p => p.GetType()).ToArray() ?? new Type[] { }; 165 | var bindingFlags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static; 166 | MethodInfo method = null; 167 | while (method == null && type != null) 168 | { 169 | method = type.GetMethod(methodName, bindingFlags, Type.DefaultBinder, methodParamTypes, null); 170 | type = type.BaseType; 171 | } 172 | 173 | // Invoke the method on the state object 174 | return method?.Invoke(target, methodParams); 175 | } 176 | 177 | return null; 178 | } 179 | 180 | private static T InvokeMethod(string methodName, params object[] methodParams) 181 | { 182 | T ret = default(T); 183 | if (_window != null) 184 | { 185 | System.Object v = InvokeMethod(methodName, methodParams); 186 | if (v != null && v is T) 187 | { 188 | ret = (T) v; 189 | } 190 | else 191 | { 192 | Debug.LogError("Return value was null or from the wrong type: " + v); 193 | } 194 | } 195 | 196 | return ret; 197 | } 198 | 199 | #endregion 200 | 201 | #region Debug 202 | 203 | /// 204 | /// Debug to get new methods from Unity assembly 205 | /// 206 | public static void PrintMethods() 207 | { 208 | var w = GetOpenAnimationWindow(); 209 | if (w != null) 210 | { 211 | var flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance; 212 | var animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags); 213 | if (animEditor != null) 214 | { 215 | var animEditorType = animEditor.FieldType; 216 | var animWindowState = animEditorType.GetField("m_State", flags); 217 | if (animWindowState != null) 218 | { 219 | var windowStateType = animWindowState.FieldType; 220 | var methods = windowStateType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance); 221 | Debug.Log("Methods : " + methods.Length); 222 | for (int i = 0; i < methods.Length; i++) 223 | { 224 | var currentInfo = methods[i]; 225 | var visibility = ""; 226 | if (currentInfo.IsPublic) visibility = "public "; 227 | if (currentInfo.IsPrivate) visibility = "private "; 228 | var isStatic = currentInfo.IsStatic ? "static " : ""; 229 | Debug.Log(visibility + isStatic + currentInfo); 230 | } 231 | } 232 | } 233 | } 234 | } 235 | 236 | #endregion 237 | } -------------------------------------------------------------------------------- /Thumbnail/ThumbnailsEditorWindow.cs: -------------------------------------------------------------------------------- 1 | // This file is subject to the terms and conditions defined in 2 | // file 'LICENSE.md', which is part of this source code package. 3 | using System.IO; 4 | using Sirenix.OdinInspector.Editor; 5 | using Sirenix.Utilities; 6 | using UnityEditor; 7 | using UnityEditor.SceneManagement; 8 | using UnityEngine; 9 | 10 | public class ThumbnailsEditorWindow : OdinEditorWindow { 11 | private const string TITLE = "Item Screenshoter"; 12 | 13 | private const int THUMBNAIL_WIDTH = 512; 14 | private const int THUMBNAIL_HEIGHT = 256; 15 | 16 | private GameObject _item, _createdItem; 17 | private RenderTexture _rt; 18 | private UnityEngine.SceneManagement.Scene _tempScene; 19 | private string _path; 20 | private Camera _cam; 21 | 22 | [MenuItem("Tools/" + TITLE, false, 20)] 23 | public static void OpenWindow() { 24 | Show(null); 25 | } 26 | 27 | /// 28 | /// Open the tool on a given GameObject from an external source 29 | /// 30 | /// 31 | public static void Show(GameObject item) { 32 | var w = (ThumbnailsEditorWindow) GetWindow(typeof(ThumbnailsEditorWindow), false, TITLE); 33 | w.SetItem(item); 34 | w.Show(); 35 | } 36 | 37 | private void SetItem(GameObject item) { 38 | _item = item; 39 | ResetScene(); 40 | } 41 | 42 | protected override void OnEnable() { 43 | base.OnEnable(); 44 | maxSize = new Vector2(300, 350); 45 | minSize = maxSize; 46 | } 47 | 48 | protected override void OnGUI() { 49 | var i = (GameObject) EditorGUILayout.ObjectField("Selected item", _item, typeof(GameObject), false); 50 | if (i != _item) { 51 | SetItem(i); 52 | return; 53 | } 54 | 55 | if (_item == null) { 56 | EditorGUILayout.LabelField("No item selected"); 57 | return; 58 | } 59 | 60 | _path = EditorGUILayout.TextField(_path); 61 | 62 | EditorGUILayout.Space(); 63 | EditorGUILayout.LabelField("Adjust the item and the camera in the scene."); 64 | EditorGUILayout.LabelField("Then create the thumbnail."); 65 | 66 | if (_rt != null) { 67 | EditorGUILayout.LabelField(new GUIContent(_rt), GUILayout.Width(256), GUILayout.Height(128)); 68 | } 69 | 70 | EditorGUILayout.Space(); 71 | EditorGUILayout.BeginHorizontal(); 72 | { 73 | EditorGUILayout.Space(); 74 | if (GUILayout.Button("Create thumbnail", GUILayout.Width(200), GUILayout.Height(50))) { 75 | CreateThumbnail(); 76 | return; 77 | } 78 | 79 | EditorGUILayout.Space(); 80 | } 81 | EditorGUILayout.EndHorizontal(); 82 | EditorGUILayout.Space(); 83 | EditorGUILayout.BeginHorizontal(); 84 | { 85 | EditorGUILayout.Space(); 86 | if (GUILayout.Button("Magic Crop", GUILayout.Width(100), GUILayout.Height(20))) { 87 | MagicCrop(); 88 | return; 89 | } 90 | 91 | if (GUILayout.Button("Reset scene", GUILayout.Width(100), GUILayout.Height(20))) { 92 | ResetScene(); 93 | return; 94 | } 95 | 96 | EditorGUILayout.Space(); 97 | } 98 | EditorGUILayout.EndHorizontal(); 99 | } 100 | 101 | private void ResetScene() { 102 | if (_item == null) return; 103 | 104 | // Create or clean new temp scene 105 | var s = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene); 106 | 107 | if (_tempScene.IsValid()) { 108 | EditorSceneManager.CloseScene(_tempScene, true); 109 | } 110 | 111 | _tempScene = s; 112 | 113 | // Add the item 114 | _createdItem = Instantiate(_item); 115 | _createdItem.transform.position = Vector3.zero; 116 | _createdItem.transform.rotation = Quaternion.identity; 117 | 118 | if (_rt != null) { 119 | DestroyImmediate(_rt); 120 | } 121 | 122 | _rt = new RenderTexture(THUMBNAIL_WIDTH, THUMBNAIL_HEIGHT, 24); 123 | 124 | // Add a camera 125 | var camGo = new GameObject("Camera"); 126 | _cam = camGo.AddComponent(); 127 | _cam.orthographic = true; 128 | _cam.tag = "MainCamera"; 129 | _cam.backgroundColor = new Color(0, 0, 0, 0); 130 | _cam.clearFlags = CameraClearFlags.SolidColor; 131 | _cam.targetTexture = _rt; 132 | 133 | MagicCrop(); 134 | 135 | // Light 136 | var lightGo = new GameObject("Light"); 137 | var light = lightGo.AddComponent(); 138 | light.type = LightType.Directional; 139 | 140 | Repaint(); 141 | } 142 | 143 | private void CreateThumbnail() { 144 | if (_rt == null) return; 145 | 146 | Debug.Log("Generating thumbnail for [" + _item.name + "] at " + _path); 147 | 148 | if (File.Exists(_path)) { 149 | AssetDatabase.DeleteAsset(_path); 150 | File.Delete(_path); 151 | } 152 | 153 | // RenderTexture to PNG 154 | RenderTexture currentActiveRt = RenderTexture.active; 155 | RenderTexture.active = _rt; 156 | Texture2D tex = new Texture2D(_rt.width, _rt.height); 157 | tex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0); 158 | 159 | var bytes = tex.EncodeToPNG(); 160 | File.WriteAllBytes(_path, bytes); 161 | DestroyImmediate(tex); 162 | 163 | RenderTexture.active = currentActiveRt; 164 | 165 | AssetDatabase.ImportAsset(_path, ImportAssetOptions.ForceUpdate); 166 | var t = AssetDatabase.LoadAssetAtPath(_path); 167 | Selection.activeObject = t; 168 | } 169 | 170 | private void MagicCrop() { 171 | var renderer = _createdItem.GetComponentInChildren(); 172 | if (renderer == null) return; 173 | var rect = new Rect(renderer.bounds.min.x, renderer.bounds.min.y, renderer.bounds.size.x, renderer.bounds.size.y); 174 | 175 | // Margins 176 | rect = rect.Expand(0.25f); 177 | 178 | _cam.transform.position = CalculateCameraPosition(rect); 179 | _cam.orthographicSize = CalculateOrthographicSize(_cam, rect); 180 | } 181 | 182 | // Source: https://answers.unity.com/questions/1231701/fitting-bounds-into-orthographic-2d-camera.html 183 | // Slightly modified :) 184 | 185 | /// 186 | /// Calculates a camera position given the a bounding box containing all the targets. 187 | /// 188 | /// A Rect bounding box containg all targets. 189 | /// A Vector3 in the center of the bounding box. 190 | private Vector3 CalculateCameraPosition(Rect boundingBox) { 191 | return new Vector3(boundingBox.center.x, boundingBox.center.y, -10f); 192 | } 193 | 194 | /// 195 | /// Calculates a new orthographic size for the camera based on the target bounding box. 196 | /// 197 | /// 198 | /// A Rect bounding box containg all targets. 199 | /// A float for the orthographic size. 200 | private float CalculateOrthographicSize(Camera camera, Rect boundingBox) { 201 | float orthographicSize; 202 | if (boundingBox.width > boundingBox.height) { 203 | orthographicSize = Mathf.Abs(boundingBox.width) / camera.aspect / 2f; 204 | } else { 205 | orthographicSize = Mathf.Abs(boundingBox.height) / 2f; 206 | } 207 | 208 | return orthographicSize; 209 | } 210 | } -------------------------------------------------------------------------------- /AssetsManager/AssetsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using AssetBundles; 5 | using UniRx; 6 | using UnityEngine; 7 | using UnityEngine.SceneManagement; 8 | #if UNITY_EDITOR 9 | using UnityEditor; 10 | 11 | #endif 12 | 13 | public class AssetsManager { 14 | 15 | public static bool SimulateAssetBundle = false; 16 | public static string AssetBundleURL; 17 | public static bool LogAssetAccess = true; 18 | 19 | private static AssetsManager _instance; 20 | 21 | private Dictionary _assetBundles; 22 | private Dictionary> _loadingAssetBundles; 23 | private bool _init; 24 | 25 | private AssetBundleManager _assetBundleManager; 26 | 27 | public static AssetsManager Instance => _instance ?? (_instance = new AssetsManager()); 28 | 29 | private async Task Init() { 30 | _assetBundleManager = new AssetBundleManager(); 31 | 32 | if (Application.isEditor && SimulateAssetBundle) { 33 | _assetBundleManager.UseSimulatedUri(); 34 | } else { 35 | _assetBundleManager.SetBaseUri(AssetBundleURL); 36 | } 37 | 38 | await _assetBundleManager.Initialize(); 39 | _assetBundles = new Dictionary(); 40 | _loadingAssetBundles = new Dictionary>(); 41 | _init = true; 42 | } 43 | 44 | public async Task GetBundle(FixedAssetBundles assetBundleName) { 45 | return await GetBundle(assetBundleName.ToString()); 46 | } 47 | 48 | /// 49 | /// Return an AssetBundle using its name 50 | /// 51 | /// The name of the asset bundle to load 52 | /// 53 | /// Will throw an exception if the bundle does not exist 54 | public async Task GetBundle(string assetBundleName) { 55 | if (!_init) await Init(); 56 | 57 | var lowerName = assetBundleName.ToLower(); 58 | 59 | if (_loadingAssetBundles.ContainsKey(lowerName)) { 60 | return await _loadingAssetBundles[lowerName]; 61 | } 62 | 63 | if (!_assetBundles.ContainsKey(lowerName)) { 64 | var loadingTask = _assetBundleManager.GetBundle(lowerName); 65 | _loadingAssetBundles.Add(lowerName, loadingTask); 66 | 67 | var bundle = await loadingTask; 68 | _loadingAssetBundles.Remove(lowerName); 69 | 70 | if (bundle == null) { 71 | throw new Exception($"Failed to load AssetBundle {lowerName} !"); 72 | } 73 | 74 | // We check again for concurrency reasons 75 | _assetBundles.Add(lowerName, bundle); 76 | } 77 | 78 | return _assetBundles[lowerName]; 79 | } 80 | 81 | /// 82 | /// Load an asset bundle but don't use the result 83 | /// 84 | /// 85 | /// 86 | public async Task PreloadBundle(FixedAssetBundles assetBundleName) { 87 | if (SimulateAssetBundle) return; 88 | await GetBundle(assetBundleName); 89 | } 90 | 91 | public static async Task> LoadLevelAsync(string assetBundleName, string levelName, LoadSceneMode loadSceneMode) { 92 | #if UNITY_EDITOR 93 | if (SimulateAssetBundle) { 94 | if (LogAssetAccess) { 95 | Debug.Log("=> Scene (Simu) " + levelName, LogColor, true); 96 | } 97 | 98 | LoadLevelEditor(assetBundleName, levelName, loadSceneMode); 99 | 100 | // Create fake observable 101 | return Observable.TimerFrame(1).Select(x => (float) x); 102 | } 103 | #endif 104 | if (LogAssetAccess) { 105 | Debug.Log("=> Scene (AB) " + levelName, LogColor, true); 106 | } 107 | 108 | return await Instance.LoadLevelAsyncInternal(assetBundleName, levelName, loadSceneMode); 109 | } 110 | 111 | #if UNITY_EDITOR 112 | public static void LoadLevelEditor(string assetBundleName, string levelName, LoadSceneMode loadSceneMode) { 113 | 114 | var path = "Assets/Scenes/" + levelName + ".unity"; 115 | UnityEditor.SceneManagement.EditorSceneManager.LoadSceneInPlayMode(path, new LoadSceneParameters(loadSceneMode)); 116 | 117 | if (LogAssetAccess) { 118 | Debug.Log("OK Scene (Simu) " + path, LogColor, true); 119 | } 120 | } 121 | #endif 122 | 123 | private async Task> LoadLevelAsyncInternal(string assetBundleName, string levelName, LoadSceneMode loadSceneMode) { 124 | if (!_init) await Init(); 125 | 126 | var observable = (await Instance._assetBundleManager.LoadLevelAsync(assetBundleName, levelName, loadSceneMode)) 127 | .AsObservable() 128 | .Select(operation => operation.progress) 129 | .Last(); 130 | return observable; 131 | } 132 | 133 | public static async Task LoadAsset(FixedAssetBundles assetBundleName, string assetName) where T : UnityEngine.Object { 134 | var debugName = GetDebugName(assetBundleName.ToString(), assetName); 135 | if (LogAssetAccess) { 136 | Debug.Log("=> " + debugName, LogColor, true); 137 | } 138 | 139 | #if UNITY_EDITOR 140 | if (SimulateAssetBundle) { 141 | return LoadAssetEditorAsync(assetBundleName.ToString(), assetName); 142 | } 143 | #endif 144 | var result = await Instance.LoadAssetInternal(assetBundleName.ToString(), assetName); 145 | 146 | return result; 147 | } 148 | 149 | #if UNITY_EDITOR 150 | public static T LoadAssetEditorAsync(string assetBundleName, string assetName, bool log = true) where T : UnityEngine.Object { 151 | assetBundleName = assetBundleName.ToLower(); 152 | var debugName = GetDebugName(assetBundleName, assetName); 153 | string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, assetName); 154 | if (assetPaths.Length == 0) { 155 | if (log) { 156 | Debug.LogError("! Asset not found " + debugName, LogColor); 157 | } 158 | 159 | return null; 160 | } 161 | 162 | if (assetPaths.Length > 1) { 163 | Debug.LogWarning("Multiple assets found name=[" + assetName + "] bundle=[" + assetBundleName + "]. Only the first one will be used."); 164 | } 165 | 166 | if (LogAssetAccess) { 167 | if (log) { 168 | Debug.Log("OK (Simu)" + debugName, LogColor, true); 169 | } 170 | } 171 | 172 | var target = AssetDatabase.LoadAssetAtPath(assetPaths[0]); 173 | return target; 174 | } 175 | #endif 176 | private async Task LoadAssetInternal(string assetBundleName, string assetName) where T : UnityEngine.Object { 177 | var debugName = GetDebugName(assetBundleName, assetName); 178 | if (!_init) await Init(); 179 | 180 | try { 181 | var bundle = await GetBundle(assetBundleName).TimeoutAfter(120); 182 | var asset = bundle.LoadAsset(assetName); 183 | if (asset == null) Debug.LogError("Failed to load Asset {0} from {1} ({2})", logTag: Logger.LogTag.Assets, args: new object[] {assetName, assetBundleName, typeof(T)}); 184 | 185 | if (LogAssetAccess) { 186 | Debug.Log("OK (AB) " + debugName, LogColor, true); 187 | } 188 | 189 | return asset; 190 | } catch (Exception e) { 191 | Debug.LogError("Couldn't load asset [" + assetName + "] in bundle [" + assetBundleName + "]"); 192 | Debug.LogException(e); 193 | throw; 194 | } 195 | } 196 | 197 | private static string GetDebugName(string assetBundleName, string assetName) { 198 | return "[" + assetName + "]<" + typeof(T) + "> (" + assetBundleName + ")"; 199 | } 200 | } -------------------------------------------------------------------------------- /SimpleAnimator/Editor/SimpleAnimationEditor.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | // This file is subject to the terms and conditions defined in 3 | // file 'LICENSE.md', which is part of this source code package. 4 | using System.Collections.Generic; 5 | using System.IO; 6 | using System.Linq; 7 | using UnityEditor; 8 | using UnityEngine; 9 | 10 | namespace Pixelnest 11 | { 12 | 13 | [CanEditMultipleObjects] 14 | [CustomEditor(typeof(SimpleAnimation))] 15 | public class SimpleAnimationEditor : Editor 16 | { 17 | #region Menus 18 | 19 | [MenuItem("Assets/Create/2D/Simple Animation")] 20 | public static void CreateSpriteFrameAnimation() 21 | { 22 | var anim = ScriptableObjectUtility.CreateAsset(Selection.activeObject.name); 23 | 24 | AutoFillAnimation(anim); 25 | } 26 | 27 | public static void AutoFillAnimation(SimpleAnimation anim, bool reverse = false, bool clean = true) 28 | { 29 | if (anim.allowAutoUpdate) 30 | { 31 | string path = AssetDatabase.GetAssetPath(anim.GetInstanceID()); 32 | 33 | // Get the directory 34 | string dir = Path.GetDirectoryName(path); 35 | 36 | 37 | if (Directory.Exists(dir)) 38 | { 39 | // List all sprites from the dir 40 | List sprites = new List(); 41 | 42 | foreach (string file in Directory.GetFiles(dir)) 43 | { 44 | foreach (var asset in AssetDatabase.LoadAllAssetsAtPath(file)) 45 | { 46 | if (asset is Sprite) 47 | { 48 | // Add them to the anim 49 | sprites.Add(asset as Sprite); 50 | } 51 | } 52 | } 53 | 54 | if (reverse) 55 | { 56 | sprites.Reverse(); 57 | } 58 | 59 | if (clean == false) 60 | { 61 | sprites.InsertRange(0, anim.frames); 62 | } 63 | 64 | anim.frames = sprites.ToArray(); 65 | 66 | EditorUtility.SetDirty(anim); 67 | 68 | AssetDatabase.SaveAssets(); 69 | sprites = null; 70 | } 71 | } 72 | } 73 | 74 | #endregion 75 | 76 | #region Inspector 77 | 78 | private SimpleAnimation anim; 79 | private bool play; 80 | private double elapsedTime; 81 | private double cooldown; 82 | private Sprite currentSprite; 83 | private int currentIndex; 84 | private float speedFactor; 85 | 86 | void OnEnable() 87 | { 88 | anim = target as SimpleAnimation; 89 | play = false; 90 | currentIndex = 0; 91 | speedFactor = 1f; 92 | 93 | elapsedTime = EditorApplication.timeSinceStartup; 94 | EditorApplication.update += Update; 95 | 96 | take = anim.frames.Length; 97 | } 98 | void OnDisable() { EditorApplication.update -= Update; } 99 | 100 | void Update() 101 | { 102 | if (currentSprite == null && anim.frames.Length > 0) 103 | { 104 | currentIndex = 0; 105 | currentSprite = anim.frames[currentIndex]; 106 | } 107 | if (play) 108 | { 109 | double t = EditorApplication.timeSinceStartup - elapsedTime; 110 | elapsedTime = EditorApplication.timeSinceStartup; 111 | cooldown -= t; 112 | if (cooldown < 0) 113 | { 114 | cooldown = (1f / anim.imagesPerSeconds) * speedFactor; 115 | 116 | if (currentIndex < anim.frames.Length - 1 || anim.loop) 117 | { 118 | currentIndex++; 119 | if (currentIndex >= anim.frames.Length && anim.loop) currentIndex = 0; 120 | Repaint(); 121 | } 122 | } 123 | } 124 | 125 | if (currentIndex < anim.frames.Length) 126 | { 127 | currentSprite = anim.frames[currentIndex]; 128 | } 129 | } 130 | 131 | private int skip, take; 132 | 133 | public override void OnInspectorGUI() 134 | { 135 | if (anim != null) 136 | { 137 | GUILayout.Label("Settings", EditorStyles.boldLabel); 138 | 139 | DrawDefaultInspector(); 140 | 141 | if (Selection.objects.Length == 1) 142 | { 143 | EditorGUILayout.Space(); 144 | 145 | EditorGUILayout.LabelField("Cut", EditorStyles.boldLabel); 146 | GUILayout.BeginVertical(); 147 | skip = EditorGUILayout.IntField("skip", skip, GUILayout.Width(240), GUILayout.ExpandWidth(false)); 148 | take = EditorGUILayout.IntField("take", take, GUILayout.Width(240), GUILayout.ExpandWidth(false)); 149 | if (GUILayout.Button("Cut", GUILayout.Width(35))) 150 | { 151 | anim.frames = anim.frames.Skip(skip).Take(take).ToArray(); 152 | } 153 | GUILayout.EndVertical(); 154 | EditorGUILayout.Space(); 155 | 156 | // Magic button 157 | GUILayout.Label("Magic frames fill", EditorStyles.boldLabel); 158 | 159 | if (anim.allowAutoUpdate && GUILayout.Button("Auto-fill")) 160 | { 161 | AutoFillAnimation(anim); 162 | take = anim.frames.Length; 163 | } 164 | if (anim.allowAutoUpdate && GUILayout.Button("Reverse auto-fill")) 165 | { 166 | AutoFillAnimation(anim, true); 167 | take = anim.frames.Length; 168 | } 169 | if (anim.allowAutoUpdate && GUILayout.Button("Cumulative auto-fill")) 170 | { 171 | AutoFillAnimation(anim, false, false); 172 | take = anim.frames.Length; 173 | } 174 | if (anim.allowAutoUpdate && GUILayout.Button("Cumulative reverse auto-fill")) 175 | { 176 | AutoFillAnimation(anim, true, false); 177 | take = anim.frames.Length; 178 | } 179 | 180 | EditorGUILayout.Space(); 181 | GUILayout.Label("Quick name", EditorStyles.boldLabel); 182 | GUILayout.BeginVertical(); 183 | 184 | string[] options = { "default", "attack", "attackstart", "attackend", "load", "loaded", "unload", "destroy" }; 185 | int count = 0; 186 | const int max = 4; 187 | 188 | GUILayout.BeginHorizontal(); 189 | 190 | foreach (var option in options) 191 | { 192 | if (GUILayout.Button(option)) 193 | { 194 | anim.name = option; 195 | EditorUtility.SetDirty(anim); 196 | } 197 | 198 | count++; 199 | if (count == max) 200 | { 201 | count = 0; 202 | GUILayout.EndHorizontal(); 203 | GUILayout.BeginHorizontal(); 204 | } 205 | } 206 | 207 | // Fill with empty spaces 208 | for (int i = 0; i < max - count; i++) 209 | { 210 | GUILayout.Label(""); 211 | } 212 | 213 | GUILayout.EndHorizontal(); 214 | 215 | GUILayout.EndVertical(); 216 | 217 | EditorGUILayout.Space(); 218 | EditorGUILayout.LabelField("Preview", EditorStyles.boldLabel); 219 | 220 | GUILayout.BeginHorizontal(); 221 | if (GUILayout.Button("▶️▶️ Play", GUILayout.Width(55), GUILayout.Height(30))) 222 | { 223 | play = true; 224 | currentIndex = 0; 225 | speedFactor = 1f; 226 | } 227 | if (GUILayout.Button("▶️ Slow", GUILayout.Width(55), GUILayout.Height(30))) 228 | { 229 | play = true; 230 | currentIndex = 0; 231 | speedFactor = 4f; 232 | } 233 | if (GUILayout.Button("▪️ Stop", GUILayout.Width(55), GUILayout.Height(30))) 234 | { 235 | play = false; 236 | currentIndex = 0; 237 | currentSprite = null; 238 | } 239 | 240 | GUILayout.BeginVertical(); 241 | EditorGUILayout.LabelField("Preview: " + (play ? "on" : "off")); 242 | EditorGUILayout.LabelField("Frame " + (currentIndex + 1) + "/" + anim.frames.Length); 243 | GUILayout.EndVertical(); 244 | 245 | GUILayout.EndHorizontal(); 246 | if (currentSprite != null) 247 | { 248 | Rect spriteRect = new Rect(currentSprite.rect.x / currentSprite.texture.width, currentSprite.rect.y / currentSprite.texture.height, 249 | currentSprite.rect.width / currentSprite.texture.width,currentSprite.rect.height / currentSprite.texture.height); 250 | GUI.DrawTextureWithTexCoords(EditorGUILayout.GetControlRect(GUILayout.Width(250), GUILayout.Height(250)), currentSprite.texture, spriteRect, true); 251 | //EditorGUI.DrawTextureTransparent(EditorGUILayout.GetControlRect(GUILayout.Width(250), GUILayout.Height(250)), currentSprite.texture, ScaleMode.ScaleToFit); 252 | } 253 | } 254 | } 255 | } 256 | 257 | #endregion 258 | } 259 | } 260 | #endif 261 | -------------------------------------------------------------------------------- /SimpleAnimator/SimpleAnimator.cs: -------------------------------------------------------------------------------- 1 | // This file is subject to the terms and conditions defined in 2 | // file 'LICENSE.md', which is part of this source code package. 3 | using UnityEngine; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | 7 | 8 | /// 9 | /// Simple animator 10 | /// 11 | [RequireComponent(typeof(SpriteRenderer))] 12 | public sealed class SimpleAnimator : MonoBehaviour 13 | { 14 | public bool debugMode = false; 15 | public SimpleAnimation defaultAnimation; 16 | public SimpleAnimation[] clips; 17 | public bool destroyGameObjectWhenFinished = false; 18 | public float speed = 1f; 19 | 20 | /// 21 | /// Animation "name" is completed 22 | /// 23 | public event System.Action AnimationCompleted; 24 | 25 | /// 26 | /// Callback that replace the destroy feature 27 | /// 28 | public event System.Action OnDestruction; 29 | 30 | private SimpleAnimation defaultAnimationSaved; 31 | private SpriteRenderer spriteRenderer; 32 | private SimpleAnimation currentAnimation; 33 | private bool firstInit; 34 | private int currentFrame; 35 | private float timer; 36 | private float previousRealtime; 37 | 38 | void Awake() 39 | { 40 | spriteRenderer = GetComponent(); 41 | 42 | defaultAnimationSaved = defaultAnimation; 43 | } 44 | 45 | void Start() 46 | { 47 | firstInit = true; 48 | 49 | if (defaultAnimation != null) 50 | { 51 | // We allow clips to be empty if we just want to use the default anim 52 | if (clips.Length == 0) 53 | { 54 | clips = new SimpleAnimation[] { defaultAnimation }; 55 | } 56 | else if (Find(defaultAnimation.name) == null) 57 | { 58 | Debug.LogError("Playing a default animation that is not registered as a clip! Not that I care..."); 59 | } 60 | 61 | if (currentAnimation == null) Play(defaultAnimation, true); 62 | } 63 | 64 | firstInit = false; 65 | 66 | // Maybe there is nothing to animate? 67 | // Better disable the script 68 | if (clips.Length == 1 && clips[0] == defaultAnimation && defaultAnimation.frames.Length == 1) 69 | { 70 | enabled = false; 71 | } 72 | } 73 | 74 | void Update() 75 | { 76 | // Animator needs... an animation! 77 | if (currentAnimation == null) return; 78 | 79 | // Sometimes we are not animating anything 80 | if (currentAnimation.frames.Length == 1 && currentAnimation.loop) return; 81 | 82 | // Default animation with only one frame... do nothing 83 | if (currentAnimation == defaultAnimation && currentAnimation.frames.Length == 1) return; 84 | 85 | // Update time 86 | if (DisableAutoNextFrame == false) 87 | { 88 | float timeDelta = 0f; 89 | if (Time.timeScale > 0) 90 | { 91 | timeDelta = Time.deltaTime; 92 | } 93 | else 94 | { 95 | // Animation may continue! 96 | if (currentAnimation.loop) 97 | { 98 | timeDelta = Time.realtimeSinceStartup - previousRealtime; 99 | } 100 | } 101 | 102 | timer -= timeDelta; 103 | if (timer <= 0) 104 | { 105 | currentFrame++; 106 | timer = currentAnimation.FrameDuration / speed; 107 | } 108 | } 109 | 110 | // End of the animation 111 | if (currentFrame >= currentAnimation.frames.Length) 112 | { 113 | if (currentAnimation.loop == false) 114 | { 115 | if (destroyGameObjectWhenFinished) 116 | { 117 | if (OnDestruction != null) 118 | { 119 | OnDestruction(this.gameObject); 120 | } 121 | else 122 | { 123 | Destroy(this.gameObject); 124 | return; 125 | } 126 | } 127 | 128 | // Back to default (can be nothing) 129 | Play(defaultAnimation, true); 130 | 131 | if (AnimationCompleted != null) AnimationCompleted(currentAnimation.name); 132 | AnimationCompleted = null; 133 | } 134 | 135 | // Reset anyway 136 | Reset(); 137 | } 138 | 139 | // Change sprite 140 | ChangeSprite(); 141 | 142 | previousRealtime = Time.realtimeSinceStartup; 143 | } 144 | 145 | void ChangeSprite() 146 | { 147 | if (currentAnimation != null && spriteRenderer != null) 148 | { 149 | if (currentAnimation.frames.Length > currentFrame) 150 | { 151 | spriteRenderer.sprite = currentAnimation.frames[currentFrame]; 152 | } 153 | else 154 | { 155 | Debug.LogError("Animation Error: " + this + "#ChangeSprite currentFrame=" + currentFrame + " total frames=" + currentAnimation.frames.Length); 156 | } 157 | } 158 | } 159 | 160 | /// 161 | /// Play animation from name 162 | /// 163 | /// 164 | public SimpleAnimation Play(string name) 165 | { 166 | return Play(name, false, true); 167 | } 168 | 169 | /// 170 | /// Play animation from name 171 | /// 172 | /// 173 | public SimpleAnimation Play(string name, bool reset, bool required) 174 | { 175 | return Play(name, reset, required, null); 176 | } 177 | 178 | /// 179 | /// Play animation from name 180 | /// 181 | /// 182 | public SimpleAnimation Play(string name, bool reset, bool required, System.Action animCompleted) 183 | { 184 | if (this.enabled == false) return null; 185 | 186 | // Find the clip in the collection 187 | SimpleAnimation[] anims = Find(name); 188 | 189 | if (anims == null || anims.Length == 0) 190 | { 191 | if (required) 192 | { 193 | Debug.LogError("Missing animation \"" + name + "\" " + this); 194 | } 195 | } 196 | else 197 | { 198 | this.AnimationCompleted += animCompleted; 199 | 200 | // Select one anim 201 | var anim = anims[Random.Range(0, anims.Length)]; 202 | 203 | Play(anim, reset); 204 | return anim; 205 | } 206 | 207 | return null; 208 | } 209 | 210 | /// 211 | /// Play animation 212 | /// 213 | /// 214 | public void Play(SimpleAnimation anim, bool reset) 215 | { 216 | if (this.enabled == false) return; 217 | 218 | if (currentAnimation != anim) 219 | { 220 | currentAnimation = anim; 221 | Reset(); 222 | } 223 | else if (currentAnimation == anim && reset) 224 | { 225 | Reset(); 226 | } 227 | else 228 | { 229 | // Already playing the right animation 230 | return; 231 | } 232 | 233 | if (debugMode) 234 | { 235 | Debug.Log("ANIMATION - Playing animation \"" + currentAnimation + "\" (" + currentAnimation.Duration + " sec)"); 236 | } 237 | 238 | if (currentAnimation.frames.Length == 0) 239 | { 240 | Debug.LogWarning("Empty animation (no frames): " + anim.name + " " + anim); 241 | currentAnimation = null; 242 | } 243 | } 244 | 245 | /// 246 | /// Reset animation parameters 247 | /// 248 | public void Reset() 249 | { 250 | currentFrame = 0; 251 | if (firstInit && currentAnimation.randomFirstFrame) 252 | { 253 | currentFrame = Random.Range(0, currentAnimation.frames.Length); 254 | } 255 | 256 | if (currentAnimation != null) 257 | { 258 | timer = currentAnimation.FrameDuration; 259 | } 260 | 261 | ChangeSprite(); 262 | } 263 | 264 | /// 265 | /// Check if the animation exists for this instance 266 | /// 267 | /// 268 | /// 269 | public bool Contains(string name) 270 | { 271 | return Find(name).Length > 0; 272 | } 273 | 274 | public void SetAndPlayDefault(string name) 275 | { 276 | SetDefault(name); 277 | Play(name); 278 | } 279 | 280 | /// 281 | /// Change the default animation 282 | /// 283 | public void SetDefault(string name) 284 | { 285 | defaultAnimationSaved = defaultAnimation; 286 | 287 | var anims = Find(name); 288 | if (anims.Length > 0) 289 | { 290 | defaultAnimation = anims[Random.Range(0, anims.Length)]; ; 291 | 292 | if (debugMode) 293 | { 294 | Debug.Log("ANIMATION - New default: " + name + " " + defaultAnimation); 295 | } 296 | } 297 | else 298 | { 299 | Debug.LogError("Missing animation \"" + name + "\", can't set as default! " + this); 300 | } 301 | } 302 | 303 | /// 304 | /// Set the default animation back 305 | /// 306 | public void RestoreDefault() 307 | { 308 | defaultAnimation = defaultAnimationSaved; 309 | 310 | if (debugMode) 311 | { 312 | Debug.Log("ANIMATION - Restore default: " + defaultAnimation.name + " " + defaultAnimation); 313 | } 314 | } 315 | 316 | private SimpleAnimation[] Find(string name) 317 | { 318 | List anims = new List(); 319 | 320 | foreach (var clip in clips) 321 | { 322 | if (name.ToLower() == clip.name.ToLower()) 323 | { 324 | anims.Add(clip); 325 | } 326 | } 327 | return anims.ToArray(); 328 | } 329 | 330 | public float TimeLeft 331 | { 332 | get 333 | { 334 | if (currentAnimation != null) 335 | { 336 | return (currentAnimation.frames.Length - currentFrame) * currentAnimation.FrameDuration; 337 | } 338 | 339 | return 0f; 340 | } 341 | } 342 | 343 | public SimpleAnimation Current 344 | { 345 | get 346 | { 347 | return currentAnimation; 348 | } 349 | } 350 | 351 | /// 352 | /// Get current animation frame 353 | /// 354 | public int Frame 355 | { 356 | get 357 | { 358 | return currentFrame; 359 | } 360 | set 361 | { 362 | currentFrame = value; 363 | } 364 | } 365 | 366 | public bool DisableAutoNextFrame 367 | { 368 | get; set; 369 | } 370 | 371 | public SpriteRenderer SpriteRenderer 372 | { 373 | get 374 | { 375 | return spriteRenderer; 376 | } 377 | } 378 | } -------------------------------------------------------------------------------- /Builder/Sequences/SteamSequenceSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | #if UNITY_EDITOR 5 | using UnityEditor; 6 | #endif 7 | using UnityEngine; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace Builder 11 | { 12 | [CreateAssetMenu(fileName = "SteamSequenceSettings", menuName = "Flat Eye/Builder/Steam Sequence", order = 0)] 13 | public class SteamSequenceSettings : BuilderSequenceSettings 14 | { 15 | public long appID = 1358840; 16 | public string vdfFolder; 17 | public string branch; 18 | public SteamDepotSettings[] depots; 19 | 20 | #if UNITY_EDITOR 21 | public string SteamSDK 22 | { 23 | get => EditorPrefs.GetString("FlatEye.Steam.SDK"); 24 | set => EditorPrefs.SetString("FlatEye.Steam.SDK", value); 25 | } 26 | 27 | public string SteamUser 28 | { 29 | get => EditorPrefs.GetString("FlatEye.Steam.User"); 30 | set => EditorPrefs.SetString("FlatEye.Steam.User", value); 31 | } 32 | 33 | public string SteamPassword 34 | { 35 | get => EditorPrefs.GetString("FlatEye.Steam.Password"); 36 | set => EditorPrefs.SetString("FlatEye.Steam.Password", value); 37 | } 38 | 39 | public string SteamBranch 40 | { 41 | // get => EditorPrefs.GetString("FlatEye.Steam.Branch"); 42 | // set => EditorPrefs.SetString("FlatEye.Steam.Branch", value); 43 | get => branch; 44 | set => branch = value; 45 | } 46 | #endif 47 | 48 | public override void DrawCustomSettings() 49 | { 50 | EditorGUILayout.BeginVertical("Box"); 51 | { 52 | EditorGUILayout.LabelField("Steam", EditorStyles.boldLabel); 53 | 54 | var newID = EditorGUILayout.LongField("App ID", appID); 55 | if (newID != appID) 56 | { 57 | appID = newID; 58 | depots = null; 59 | } 60 | 61 | string steamSdkPath = SteamSDK; 62 | string newSdkPath = BuilderUtils.BrowseField("ContentBuilder path", steamSdkPath, null); 63 | if (newSdkPath != steamSdkPath) 64 | { 65 | SteamSDK = newSdkPath; 66 | } 67 | 68 | EditorGUILayout.BeginHorizontal(); 69 | { 70 | string steamUser = SteamUser; 71 | string newLogin = EditorGUILayout.TextField("Steam login", steamUser); 72 | if (newLogin != steamUser) 73 | { 74 | SteamUser = newLogin; 75 | } 76 | 77 | string steamPassword = SteamPassword; 78 | string newPassword = EditorGUILayout.PasswordField(steamPassword); 79 | if (newPassword != steamPassword) 80 | { 81 | SteamPassword = newPassword; 82 | } 83 | } 84 | EditorGUILayout.EndHorizontal(); 85 | 86 | string branch = SteamBranch; 87 | string newBranch = EditorGUILayout.TextField("Set on branch", branch); 88 | if (newBranch != branch) 89 | { 90 | SteamBranch = newBranch; 91 | } 92 | 93 | EditorGUILayout.Space(); 94 | EditorGUILayout.LabelField("Depots", EditorStyles.miniBoldLabel); 95 | if (depots == null || depots.Length != sequence.Count) 96 | { 97 | depots = new SteamDepotSettings[sequence.Count]; 98 | for (int i = 0; i < sequence.Count; i++) 99 | { 100 | depots[i].ID = appID + i + 1; 101 | } 102 | } 103 | 104 | for (int i = 0; i < sequence.Count; i++) 105 | { 106 | EditorGUILayout.BeginHorizontal(); 107 | { 108 | sequence[i] = 109 | (BuilderSettings) EditorGUILayout.ObjectField(sequence[i], typeof(BuilderSettings), 110 | false); 111 | depots[i].ID 112 | = EditorGUILayout.LongField("Depot ID", depots[i].ID); 113 | } 114 | EditorGUILayout.EndHorizontal(); 115 | } 116 | 117 | vdfFolder = BuilderUtils.BrowseField("VDF folder", vdfFolder, null); 118 | 119 | EditorGUILayout.BeginHorizontal(); 120 | { 121 | EditorGUILayout.Space(); 122 | 123 | var bgColor = GUI.backgroundColor; 124 | 125 | GUI.backgroundColor = Color.gray; 126 | if (GUILayout.Button("Generate VDFs", GUILayout.Width(125), 127 | GUILayout.Height(25))) 128 | { 129 | try 130 | { 131 | string path = GenerateVDFs(); 132 | Debug.Log(path); 133 | } 134 | catch (Exception e) 135 | { 136 | Debug.LogError(e); 137 | } 138 | } 139 | 140 | GUI.backgroundColor = Color.gray; 141 | if (GUILayout.Button("Upload", GUILayout.Width(125), 142 | GUILayout.Height(25))) 143 | { 144 | try 145 | { 146 | OnPostSequence(); 147 | } 148 | catch (Exception e) 149 | { 150 | Debug.LogError(e); 151 | } 152 | } 153 | 154 | GUI.backgroundColor = bgColor; 155 | } 156 | EditorGUILayout.EndHorizontal(); 157 | } 158 | EditorGUILayout.EndVertical(); 159 | 160 | if (GUI.changed) 161 | { 162 | EditorUtility.SetDirty(this); 163 | } 164 | } 165 | 166 | public override Task OnPostSequence() 167 | { 168 | string appVdf = GenerateVDFs(); 169 | 170 | // Upload with steamcmd 171 | string cmd = Path.Combine(SteamSDK, "builder", "steamcmd.exe"); 172 | System.Diagnostics.Process process = new System.Diagnostics.Process(); 173 | System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(); 174 | startInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Normal; 175 | startInfo.FileName = cmd; 176 | // startInfo.Arguments = $"+login {SteamUser} \"{SteamPassword}\" +run_app_build \"{appVdf}\""; // DEBUG 177 | startInfo.Arguments = $"+login {SteamUser} \"{SteamPassword}\" +run_app_build \"{appVdf}\" +quit"; 178 | process.StartInfo = startInfo; 179 | 180 | Debug.Log($"Starting process {process.StartInfo.FileName} {process.StartInfo.Arguments}"); 181 | 182 | process.Start(); 183 | 184 | return base.OnPostSequence(); 185 | } 186 | 187 | public string GenerateVDFs() 188 | { 189 | // One for each depot 190 | List depotsVdfPaths = new List(); 191 | for (int i = 0; i < depots.Length; i++) 192 | { 193 | var depot = depots[i]; 194 | var settings = sequence[i]; 195 | var path = GenerateDepotVDF(depot, settings); 196 | depotsVdfPaths.Add(path); 197 | } 198 | 199 | // One for the app 200 | var pathFinal = GenerateAppVDF(depotsVdfPaths); 201 | return pathFinal; 202 | } 203 | 204 | private string GetVDFPath(string filename) 205 | { 206 | filename += Path.GetExtension(filename) != ".vdf" ? ".vdf" : ""; 207 | string basePath = Path.IsPathRooted(vdfFolder) ? vdfFolder : Path.Combine(Application.dataPath, vdfFolder); 208 | return Path.GetFullPath(Path.Combine(basePath, filename)); 209 | } 210 | 211 | private string GenerateDepotVDF(SteamDepotSettings depot, BuilderSettings settings) 212 | { 213 | // depot_ID.vdf 214 | // "DepotBuildConfig" 215 | // { 216 | // "DepotID" "1358842" 217 | // "contentroot" "F:\projects\flat-eye\builds\osx" 218 | // "FileMapping" 219 | // { 220 | // "LocalPath" "*" 221 | // "DepotPath" "." 222 | // "recursive" "1" 223 | // } 224 | // "FileExclusion" "*.pdb" 225 | // } 226 | string path = GetVDFPath("depot_" + depot.ID); 227 | 228 | if (File.Exists(path)) File.Delete(path); 229 | 230 | string content = "\"DepotBuildConfig\"\n" + 231 | "{\n" + 232 | "\t\"DepotID\" \"" + depot.ID + "\"\n" + 233 | "\t\"contentroot\" \"" + settings.GetExecutableFolder() + "\"\n" + 234 | "\t\"FileMapping\"\n" + 235 | "\t{\n" + 236 | "\t\t\"LocalPath\" \"*\"\n" + 237 | "\t\t\"DepotPath\" \".\"\n" + 238 | "\t\t\"recursive\" \"1\"\n" + 239 | "\t}\n" + 240 | "\t\"FileExclusion\" \"*.pdb\"\n" + 241 | "}"; 242 | 243 | File.WriteAllText(path, content); 244 | 245 | return path; 246 | } 247 | 248 | private string GenerateAppVDF(List depotsPath) 249 | { 250 | //app_ID.cdf 251 | // "appbuild" 252 | // { 253 | // "appid" "1358840" 254 | // "desc" "" 255 | // "buildoutput" "F:\dev\Steamworks\tools\ContentBuilder\output" 256 | // "contentroot" "" 257 | // "setlive" "" 258 | // "preview" "1" 259 | // "local" "" 260 | // "depots" 261 | // { 262 | // "1358841" "F:\dev\Steamworks\tools\ContentBuilder\scripts\depot_1358841.vdf" 263 | // "1358842" "F:\dev\Steamworks\tools\ContentBuilder\scripts\depot_1358842.vdf" 264 | // } 265 | // } 266 | string path = GetVDFPath("app_" + appID); 267 | if (File.Exists(path)) File.Delete(path); 268 | 269 | string content = "\"appbuild\"\n" + 270 | "{\n" + 271 | "\t\"appid\" \"" + appID + "\"\n" + 272 | "\t\"desc\" \"\"\n" + 273 | "\t\"buildoutput\" \"" + Path.GetFullPath(Path.Combine(SteamSDK, "output")) + "\"\n" + 274 | "\t\"contentroot\" \"\"\n" + 275 | "\t\"setlive\" \"" + SteamBranch + "\"\n" + 276 | "\t\"preview\" \"0\"\n" + 277 | "\t\"local\" \"\"\n" + 278 | "\t\"depots\"\n" + 279 | "\t{\n"; 280 | 281 | for (int i = 0; i < depots.Length; i++) 282 | { 283 | var depot = depots[i]; 284 | string depotPath = depotsPath[i]; 285 | content += "\t\t\"" + depot.ID + "\"\t\"" + depotPath + "\"\n"; 286 | } 287 | 288 | content += "\t}\n"; 289 | content += "}"; 290 | 291 | File.WriteAllText(path, content); 292 | 293 | return path; 294 | } 295 | } 296 | 297 | [Serializable] 298 | public struct SteamDepotSettings 299 | { 300 | public long ID; 301 | } 302 | } -------------------------------------------------------------------------------- /Builder/BuilderEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using UnityEditor; 8 | using UnityEditor.Build.Reporting; 9 | using UnityEditorInternal; 10 | using UnityEngine; 11 | using Debug = UnityEngine.Debug; 12 | 13 | namespace Builder 14 | { 15 | public class BuilderEditor : EditorWindow 16 | { 17 | #region Builder Editor 18 | 19 | [MenuItem("Builds/💻 Builder", priority = 50)] 20 | private static void ShowWindow() 21 | { 22 | var window = GetWindow(); 23 | window.titleContent = new GUIContent("Builder"); 24 | window.Show(); 25 | } 26 | 27 | private Vector2 scroll; 28 | private Color bgColor, color; 29 | private Dictionary foldSettings = new Dictionary(); 30 | 31 | private List allSequences; 32 | private BuilderSequenceSettings selectedSequence; 33 | private ReorderableList settingsList; 34 | 35 | private void OnEnable() 36 | { 37 | Refresh(); 38 | } 39 | 40 | private void OnGUI() 41 | { 42 | scroll = EditorGUILayout.BeginScrollView(scroll); 43 | bgColor = GUI.backgroundColor; 44 | color = GUI.color; 45 | 46 | EditorGUILayout.BeginVertical("Box", GUILayout.ExpandHeight(true)); 47 | { 48 | DrawSequence(); 49 | } 50 | EditorGUILayout.EndVertical(); 51 | EditorGUILayout.Space(); 52 | 53 | EditorGUILayout.BeginVertical("Box"); 54 | { 55 | EditorGUI.BeginDisabledGroup(selectedSequence == null); 56 | EditorGUILayout.BeginHorizontal(); 57 | { 58 | EditorGUILayout.Space(); 59 | 60 | GUI.backgroundColor = Color.green; 61 | if (GUILayout.Button("BUILD\n" + selectedSequence.name.Replace("BuilderSequence_", ""), 62 | GUILayout.Width(250), GUILayout.Height(50))) 63 | { 64 | BuildSequence(selectedSequence); 65 | } 66 | 67 | EditorGUILayout.Space(); 68 | 69 | GUI.backgroundColor = bgColor; 70 | } 71 | EditorGUILayout.EndHorizontal(); 72 | EditorGUI.EndDisabledGroup(); 73 | } 74 | EditorGUILayout.EndVertical(); 75 | 76 | 77 | EditorGUILayout.EndScrollView(); 78 | } 79 | 80 | private void DrawSequence() 81 | { 82 | EditorGUILayout.BeginHorizontal(); 83 | { 84 | int selectedIndex = allSequences.IndexOf(selectedSequence); 85 | var i = EditorGUILayout.Popup("Build Sequence", selectedIndex, 86 | allSequences.Select(s => s.name).ToArray()); 87 | if (i != selectedIndex) 88 | { 89 | selectedSequence = allSequences[i]; 90 | settingsList = null; 91 | } 92 | 93 | if (GUILayout.Button(EditorGUIUtility.FindTexture("TreeEditor.Refresh"), GUILayout.Width(30))) 94 | { 95 | Refresh(); 96 | } 97 | 98 | if (GUILayout.Button(EditorGUIUtility.FindTexture("ScriptableObject Icon"), 99 | GUILayout.Width(30))) 100 | { 101 | if (selectedSequence != null) Selection.activeObject = selectedSequence; 102 | } 103 | } 104 | EditorGUILayout.EndHorizontal(); 105 | 106 | EditorGUILayout.Separator(); 107 | 108 | if (selectedSequence != null) 109 | { 110 | EditorGUILayout.BeginVertical("Box"); 111 | { 112 | EditorGUILayout.LabelField("Build order", EditorStyles.boldLabel); 113 | 114 | // Draw settings 115 | if (settingsList == null) 116 | { 117 | settingsList = new ReorderableList(selectedSequence.sequence, typeof(BuilderSettings), true, 118 | false, 119 | true, true); 120 | settingsList.onAddCallback = list => { selectedSequence.sequence.Add(null); }; 121 | settingsList.onRemoveCallback = list => 122 | { 123 | if (selectedSequence.sequence.Count > settingsList.index && settingsList.index >= 0) 124 | { 125 | var item = selectedSequence.sequence[settingsList.index]; 126 | selectedSequence.sequence.Remove(item); 127 | } 128 | }; 129 | settingsList.drawElementCallback = (rect, index, active, focused) => 130 | { 131 | BuilderSettings item = null; 132 | if (selectedSequence.sequence.Count > index && index >= 0) 133 | { 134 | item = selectedSequence.sequence[index]; 135 | } 136 | 137 | const int LABEL_WIDTH = 125; 138 | EditorGUI.LabelField(new Rect(rect.x, rect.y, LABEL_WIDTH, rect.height), 139 | item != null ? item.target.ToString() : string.Empty); 140 | BuilderSettings newItem = 141 | (BuilderSettings)EditorGUI.ObjectField( 142 | new Rect(rect.x + LABEL_WIDTH, rect.y, rect.width - LABEL_WIDTH, rect.height), 143 | item, typeof(BuilderSettings), false); 144 | if (item != newItem) 145 | { 146 | selectedSequence.sequence[index] = newItem; 147 | } 148 | }; 149 | } 150 | 151 | settingsList.DoLayoutList(); 152 | 153 | // Display all sequence with foldable 154 | foreach (var s in selectedSequence.sequence) 155 | { 156 | if (s == null) continue; 157 | bool display = false; 158 | if (foldSettings.ContainsKey(s)) display = foldSettings[s]; 159 | 160 | display = EditorGUILayout.Foldout(display, s.name); 161 | foldSettings[s] = display; 162 | 163 | if (display) 164 | { 165 | EditorGUILayout.BeginVertical("Box"); 166 | { 167 | DrawSettings(s); 168 | } 169 | EditorGUILayout.EndVertical(); 170 | } 171 | } 172 | } 173 | EditorGUILayout.EndVertical(); 174 | 175 | EditorGUILayout.BeginVertical("Box"); 176 | { 177 | EditorGUILayout.LabelField("Sequence settings", EditorStyles.boldLabel); 178 | selectedSequence.version = 179 | EditorGUILayout.TextField("Game version", selectedSequence.version); 180 | 181 | selectedSequence.devBuild = EditorGUILayout.Toggle("Development build", selectedSequence.devBuild); 182 | GUI.color = selectedSequence.devBuild ? Color.green : Color.magenta; 183 | EditorGUILayout.LabelField(selectedSequence.devBuild 184 | ? "DEBUG - includes Bug report & console" 185 | : "RELEASE /!\\ - removes bug reporter & console"); 186 | 187 | GUI.color = Color.Lerp(Color.yellow, Color.white, 0.5f); 188 | 189 | GUI.color = color; 190 | 191 | selectedSequence.openURL = 192 | EditorGUILayout.TextField("Post build URL", selectedSequence.openURL); 193 | 194 | selectedSequence.flags = 195 | EditorGUILayout.TextField("Flags", selectedSequence.flags); 196 | } 197 | EditorGUILayout.EndVertical(); 198 | 199 | selectedSequence.DrawCustomSettings(); 200 | 201 | EditorGUILayout.Space(); 202 | } 203 | } 204 | 205 | public void Refresh() 206 | { 207 | allSequences = BuilderUtils.FindAllAssets().ToList(); 208 | if (selectedSequence == null) selectedSequence = allSequences.FirstOrDefault(); 209 | } 210 | 211 | private async void DrawSettings(BuilderSettings s) 212 | { 213 | if (s != null) 214 | { 215 | s.target = 216 | (BuildTarget)EditorGUILayout.EnumPopup("Target", s.target); 217 | s.buildOptions = 218 | (BuildOptions)EditorGUILayout.EnumFlagsField("Build options", 219 | s.buildOptions); 220 | s.buildAB = 221 | EditorGUILayout.Toggle("Build AssetBundles", s.buildAB); 222 | 223 | EditorGUILayout.Space(); 224 | 225 | EditorGUILayout.LabelField("Destination", EditorStyles.miniBoldLabel); 226 | s.outputFolder = 227 | BuilderUtils.BrowseField("Output path", s.outputFolder, null); 228 | s.executableName = 229 | EditorGUILayout.TextField("Executable name", s.executableName); 230 | 231 | EditorGUILayout.LabelField(s.GetExecutablePath(), EditorStyles.miniLabel); 232 | 233 | EditorGUILayout.Space(); 234 | 235 | s.DrawCustomSettings(); 236 | 237 | EditorGUILayout.Space(); 238 | EditorGUILayout.BeginHorizontal(); 239 | { 240 | EditorGUILayout.Space(); 241 | 242 | GUI.backgroundColor = Color.gray; 243 | if (GUILayout.Button("Build " + s.target, GUILayout.Width(250), 244 | GUILayout.Height(25))) 245 | { 246 | await Build(selectedSequence, s); 247 | } 248 | 249 | GUI.backgroundColor = bgColor; 250 | } 251 | EditorGUILayout.EndHorizontal(); 252 | } 253 | else 254 | { 255 | EditorGUILayout.LabelField("Select or create a build configuration to continue."); 256 | } 257 | } 258 | 259 | #endregion 260 | 261 | #region Builds 262 | 263 | public static async void BuildSequence(BuilderSequenceSettings sequence) 264 | { 265 | var currentTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; 266 | var currentTarget = EditorUserBuildSettings.selectedStandaloneTarget; 267 | 268 | // Instantiate! 269 | var builderSequence = Instantiate(sequence); 270 | 271 | Debug.Log("🚀 Starting build sequence " + (builderSequence.sequence.Count) + " builds."); 272 | 273 | Debug.Log("Pre builds..."); 274 | await builderSequence.OnPreSequence(); 275 | bool abort = false; 276 | foreach (var settings in builderSequence.sequence) 277 | { 278 | if (settings == null) 279 | { 280 | Debug.LogError("Null BuilderSettings found in sequence. Skipping."); 281 | continue; 282 | } 283 | 284 | bool success = await Build(builderSequence, settings); 285 | 286 | if (success == false && builderSequence.continueIfError == false) 287 | { 288 | Debug.LogError("Stopping build sequence."); 289 | EditorUtility.DisplayDialog("Build failed.", "Sorry, build failed. Check the console for details.", 290 | "OK"); 291 | abort = true; 292 | break; 293 | } 294 | } 295 | 296 | if (abort == false) 297 | { 298 | Debug.Log("Post builds..."); 299 | await builderSequence.OnPostSequence(); 300 | 301 | 302 | if (string.IsNullOrEmpty(sequence.openURL) == false) 303 | { 304 | Application.OpenURL(sequence.openURL); 305 | } 306 | 307 | Debug.Log("✨ Build sequence completed"); 308 | } 309 | 310 | EditorUserBuildSettings.selectedBuildTargetGroup = currentTargetGroup; 311 | EditorUserBuildSettings.selectedStandaloneTarget = currentTarget; 312 | } 313 | 314 | public static async Task Build(BuilderSequenceSettings sequence, BuilderSettings builderSettings) 315 | { 316 | if (builderSettings == null) return false; 317 | 318 | Stopwatch watch = new Stopwatch(); 319 | watch.Start(); 320 | Debug.Log("BUILD STARTED " + builderSettings.target); 321 | 322 | if (builderSettings.buildAB) 323 | { 324 | Debug.Log("-> Cleaning Streaming Assets"); 325 | CleanStreamingAssets(); 326 | 327 | Debug.Log("-> Building AB "); 328 | BuildAssetBundles.Build(builderSettings.target); 329 | } 330 | 331 | await builderSettings.OnPreBuild(); 332 | 333 | var path = builderSettings.GetExecutablePath(); 334 | Debug.Log("-> Building executable. devmode= " + sequence.devBuild + " path=" + path + " flags=" + sequence.flags); 335 | 336 | BuildPlayerOptions buildPlayerOptions = new BuildPlayerOptions(); 337 | buildPlayerOptions.locationPathName = path; 338 | buildPlayerOptions.target = builderSettings.target; 339 | buildPlayerOptions.options = builderSettings.buildOptions; 340 | if (sequence.devBuild) 341 | { 342 | buildPlayerOptions.options |= BuildOptions.Development; 343 | } 344 | 345 | List flags = new(sequence.flags.Split(";").Where(s => string.IsNullOrWhiteSpace(s) == false)); 346 | buildPlayerOptions.extraScriptingDefines = flags.ToArray(); 347 | 348 | buildPlayerOptions.scenes = EditorBuildSettings.scenes.Where(s => s.enabled).Select(s => s.path).ToArray(); 349 | PlayerSettings.bundleVersion = sequence.version; 350 | 351 | BuildReport report = BuildPipeline.BuildPlayer(buildPlayerOptions); 352 | BuildSummary summary = report.summary; 353 | 354 | watch.Stop(); 355 | if (summary.result == BuildResult.Succeeded) 356 | { 357 | Debug.Log("BUILD succeeded! " + watch.Elapsed.TotalSeconds.ToString("N2") + "s " + 358 | (summary.totalSize / (1024 * 1024) / 8) + " Mo"); 359 | 360 | DeleteBurstDebugInformationFolder(report); 361 | 362 | return true; 363 | } 364 | 365 | if (summary.result == BuildResult.Failed) 366 | { 367 | Debug.LogError("BUILD " + summary.result + "... :("); 368 | } 369 | 370 | return false; 371 | } 372 | 373 | private static void DeleteBurstDebugInformationFolder(BuildReport buildReport) 374 | { 375 | if (buildReport == null) return; 376 | 377 | string outputPath = buildReport.summary.outputPath; 378 | 379 | try 380 | { 381 | string applicationName = Path.GetFileNameWithoutExtension(outputPath); 382 | string outputFolder = Path.GetDirectoryName(outputPath); 383 | if (string.IsNullOrEmpty(outputFolder)) return; 384 | 385 | outputFolder = Path.GetFullPath(outputFolder); 386 | 387 | string burstDebugInformationDirectoryPath = Path.Combine(outputFolder, $"{applicationName}_BurstDebugInformation_DoNotShip"); 388 | 389 | if (Directory.Exists(burstDebugInformationDirectoryPath)) 390 | { 391 | Debug.Log($" > Deleting Burst debug information folder at path '{burstDebugInformationDirectoryPath}'..."); 392 | 393 | Directory.Delete(burstDebugInformationDirectoryPath, true); 394 | } 395 | } 396 | catch (Exception e) 397 | { 398 | Debug.LogWarning($"An unexpected exception occurred while performing build cleanup: {e}"); 399 | } 400 | } 401 | 402 | 403 | #if ENABLE_AB 404 | [MenuItem("Builds/Clean StreamingAssets", priority = 50)] 405 | #endif 406 | public static void CleanStreamingAssets() 407 | { 408 | string path = Application.streamingAssetsPath; 409 | DirectoryInfo di = new DirectoryInfo(path); 410 | 411 | foreach (FileInfo file in di.GetFiles()) 412 | { 413 | file.Delete(); 414 | } 415 | 416 | foreach (DirectoryInfo dir in di.GetDirectories()) 417 | { 418 | dir.Delete(true); 419 | } 420 | } 421 | 422 | #endregion 423 | 424 | 425 | } 426 | } --------------------------------------------------------------------------------