├── Editor ├── SceneListSettingsProvider.cs.meta ├── Icons │ ├── PlayGameButton.png │ ├── PlaySceneButton.png │ ├── PlayGameButton.png.meta │ └── PlaySceneButton.png.meta ├── Icons.meta ├── com.antonysze.custom-play-button.Editor.asmdef.meta ├── SceneBookmark.cs.meta ├── CustomPlayButton.cs.meta ├── EditorSelectScenePopup.cs.meta ├── com.antonysze.custom-play-button.Editor.asmdef ├── SceneBookmark.cs ├── SceneListSettingsProvider.cs ├── EditorSelectScenePopup.cs └── CustomPlayButton.cs ├── CHANGELOG.md.meta ├── LICENSE.md.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── CHANGELOG.md ├── package.json ├── LICENSE.md ├── .gitignore └── README.md /Editor/SceneListSettingsProvider.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5c84b486363173b4da2923867fbb00a1 -------------------------------------------------------------------------------- /Editor/Icons/PlayGameButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonysze/unity-custom-play-button/HEAD/Editor/Icons/PlayGameButton.png -------------------------------------------------------------------------------- /Editor/Icons/PlaySceneButton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antonysze/unity-custom-play-button/HEAD/Editor/Icons/PlaySceneButton.png -------------------------------------------------------------------------------- /CHANGELOG.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6fdc9af68b4a03a43848f51f4a4124ed 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /LICENSE.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 986b864ecfcc0114887a3d99b46fbcfc 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4436d9c818d267240a722e829b37182c 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 525ca1f29762cdf429d71fdbde827769 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1dc4bf650a5826648a1ae31c223444ca 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/Icons.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7730c0298a408b48aaf290a66198d98 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Editor/com.antonysze.custom-play-button.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fd2585ab72598fb429e0ddfc3dec4a06 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/SceneBookmark.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c4c2b9217a418d84a87be2b372b7048f 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CustomPlayButton.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 23952c69964e2784091016c4d5e6a6fa 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/EditorSelectScenePopup.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 1e3c2b27ad45d8c45a184d78abe38f06 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/com.antonysze.custom-play-button.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CustomPlayButton.Editor", 3 | "rootNamespace": "ASze.CustomPlayButton", 4 | "references": [ 5 | "GUID:0a0471484b079be408cb3a0d7fe94736" 6 | ], 7 | "includePlatforms": [ 8 | "Editor" 9 | ], 10 | "excludePlatforms": [], 11 | "allowUnsafeCode": false, 12 | "overrideReferences": false, 13 | "precompiledReferences": [], 14 | "autoReferenced": true, 15 | "defineConstraints": [], 16 | "versionDefines": [ 17 | { 18 | "name": "com.marijnzwemmer.unity-toolbar-extender", 19 | "expression": "1.4.1", 20 | "define": "UNITY_TOOLBAR_EXTENDER" 21 | } 22 | ], 23 | "noEngineReferences": false 24 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.2.0] - 2025-06-22 8 | ### Changed 9 | - Support older version instead of only Unity 6 10 | - Add popup width to settings 11 | 12 | ## [1.1.0] - 2025-06-22 13 | ### Changed 14 | - Use editor preference setting to store bookmarks instead of ScriptableObject 15 | - Swap bookmark and scene button to make it more intuitive 16 | - Now can only support Unity 6 since `SettingsProvider` starts being used 17 | 18 | ## [1.0.0] - 2022-01-05 19 | ### Added 20 | - First version of the package 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.antonysze.custom-play-button", 3 | "version": "1.2.0", 4 | "displayName": "Custom Play Button", 5 | "description": "Extend the Unity Toolbar with custom play buttons.\n\nPrerequisite package:\n unity-toolbar-extender\n - https://github.com/marijnz/unity-toolbar-extender.git", 6 | "unity": "2019.1", 7 | "keywords": [ 8 | "toolbar", 9 | "scene", 10 | "custom", 11 | "UI", 12 | "button" 13 | ], 14 | "author": { 15 | "name": "Antony Sze", 16 | "email": "antonyszex@gmail.com", 17 | "url": "https://github.com/antonysze/" 18 | }, 19 | "documentationUrl": "https://github.com/antonysze/Unity-Custom-Play-Button/blob/main/README.md", 20 | "changelogUrl": "https://github.com/antonysze/Unity-Custom-Play-Button/blob/main/CHANGELOG.md", 21 | "licensesUrl": "https://github.com/antonysze/Unity-Custom-Play-Button/blob/main/LICENSE.md" 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Antony Sze 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 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # IDE cache directory 23 | .vs/ 24 | .idea/ 25 | 26 | # Gradle cache directory 27 | .gradle/ 28 | 29 | # Autogenerated VS/MD/Consulo solution and project files 30 | ExportedObj/ 31 | .consulo/ 32 | *.csproj 33 | *.unityproj 34 | *.sln 35 | *.suo 36 | *.tmp 37 | *.user 38 | *.userprefs 39 | *.pidb 40 | *.booproj 41 | *.svd 42 | *.pdb 43 | *.mdb 44 | *.opendb 45 | *.VC.db 46 | 47 | # Unity3D generated meta files 48 | *.pidb.meta 49 | *.pdb.meta 50 | *.mdb.meta 51 | 52 | # Unity3D generated file on crash reports 53 | sysinfo.txt 54 | 55 | # Builds 56 | *.apk 57 | *.unitypackage 58 | 59 | # Crashlytics generated file 60 | crashlytics-build.properties 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity Custom Play Button 2 | Add 2 custom play buttons to the Unity Toolbar to avoid keep switching scenes when testing the game. Directly play the game (the first scene in build setting) or specific scene (chose from dropdown list) by clicking those buttons without changing scene. When stop playing, scene will change back to the scene before play. Additional dropdown list for quick build scene or bookmarked scene switching without searching it. 3 | 4 | ![image](https://user-images.githubusercontent.com/3353695/148316557-47d93af2-fb9d-46b8-97da-9ce409e02317.png) 5 | 6 | ## Installation 7 | Import this from Unity Package Manager. You can [download and import it from your hard drive](https://docs.unity3d.com/Manual/upm-ui-local.html), or [link to it from github directly](https://docs.unity3d.com/Manual/upm-ui-giturl.html). 8 | 9 | ## Prerequisites 10 | **Tested Unity version:** 6000.0.43 11 | 12 | Please make sure following package is installed to make this package works: 13 | - [unity-toolbar-extender](https://github.com/marijnz/unity-toolbar-extender) - 1.4.1 or above 14 | 15 | You can also install the prerequisite package via popup window after you installed this package: 16 | ![image](https://user-images.githubusercontent.com/3353695/148312273-2188311b-fe3e-4a4b-87ea-00ccaead8aef.png) 17 | 18 | ## How to use 19 | ![image](https://user-images.githubusercontent.com/3353695/148320339-2efc85a4-fc4b-44d7-bd84-662ff9e34c52.gif) 20 | 21 | ### Play Buttons on Unity Toobar 22 | ![image](https://user-images.githubusercontent.com/3353695/148315309-e6369f75-5a44-4684-8848-f59341058443.png) 23 | 1. **Dropdown button of custom scene** - open scene selection window 24 | 2. **Play custom scene button** - play target scene from dropdown button 25 | 3. **Play game button** - play the game (the first scene in build setting) 26 | 27 | ### Scene selection window 28 | ![image](https://user-images.githubusercontent.com/3353695/148323734-6bc75ebb-a3f7-4791-a865-5da002d43d49.png) 29 | 30 | 4. Select custom scene (for **play custom scene button**) 31 | 5. Bookmark/Unbookmark 32 | 6. Open scene in scene view 33 | 7. Select bookmark scriptable object 34 | 8. Unbookmark 35 | 36 | ### Scene Bookmark 37 | Please note that bookmark is stored in a bookmark scripable object in your project. Please add to .gitignore if you do not want to share it. This scriptable object will be automatucally created from `Assets/Editor/CustomPlayButton/BookmarkSetting.asset`. You can also edit or reorder the list of bookmarks by modifying the scripable object directly. 38 | -------------------------------------------------------------------------------- /Editor/Icons/PlayGameButton.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b5ad3ce0a326344fad1f391a681e97d 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 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 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 1 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 0 66 | platformSettings: 67 | - serializedVersion: 3 68 | buildTarget: DefaultTexturePlatform 69 | maxTextureSize: 2048 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | androidETC2FallbackOverride: 0 78 | forceMaximumCompressionQuality_BC6H_BC7: 0 79 | - serializedVersion: 3 80 | buildTarget: Standalone 81 | maxTextureSize: 2048 82 | resizeAlgorithm: 0 83 | textureFormat: -1 84 | textureCompression: 1 85 | compressionQuality: 50 86 | crunchedCompression: 0 87 | allowsAlphaSplitting: 0 88 | overridden: 0 89 | androidETC2FallbackOverride: 0 90 | forceMaximumCompressionQuality_BC6H_BC7: 0 91 | - serializedVersion: 3 92 | buildTarget: Server 93 | maxTextureSize: 2048 94 | resizeAlgorithm: 0 95 | textureFormat: -1 96 | textureCompression: 1 97 | compressionQuality: 50 98 | crunchedCompression: 0 99 | allowsAlphaSplitting: 0 100 | overridden: 0 101 | androidETC2FallbackOverride: 0 102 | forceMaximumCompressionQuality_BC6H_BC7: 0 103 | spriteSheet: 104 | serializedVersion: 2 105 | sprites: [] 106 | outline: [] 107 | physicsShape: [] 108 | bones: [] 109 | spriteID: 5e97eb03825dee720800000000000000 110 | internalID: 0 111 | vertices: [] 112 | indices: 113 | edges: [] 114 | weights: [] 115 | secondaryTextures: [] 116 | nameFileIdTable: {} 117 | spritePackingTag: 118 | pSDRemoveMatte: 0 119 | pSDShowRemoveMatteOption: 0 120 | userData: 121 | assetBundleName: 122 | assetBundleVariant: 123 | -------------------------------------------------------------------------------- /Editor/Icons/PlaySceneButton.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a8df29c9d1016ca48a80389a6b232e67 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 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 | vTOnly: 0 27 | ignoreMasterTextureLimit: 0 28 | grayScaleToAlpha: 0 29 | generateCubemap: 6 30 | cubemapConvolution: 0 31 | seamlessCubemap: 0 32 | textureFormat: 1 33 | maxTextureSize: 2048 34 | textureSettings: 35 | serializedVersion: 2 36 | filterMode: 1 37 | aniso: 1 38 | mipBias: 0 39 | wrapU: 1 40 | wrapV: 1 41 | wrapW: 1 42 | nPOTScale: 0 43 | lightmap: 0 44 | compressionQuality: 50 45 | spriteMode: 1 46 | spriteExtrude: 1 47 | spriteMeshType: 1 48 | alignment: 0 49 | spritePivot: {x: 0.5, y: 0.5} 50 | spritePixelsToUnits: 100 51 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 52 | spriteGenerateFallbackPhysicsShape: 1 53 | alphaUsage: 1 54 | alphaIsTransparency: 1 55 | spriteTessellationDetail: -1 56 | textureType: 8 57 | textureShape: 1 58 | singleChannelComponent: 0 59 | flipbookRows: 1 60 | flipbookColumns: 1 61 | maxTextureSizeSet: 0 62 | compressionQualitySet: 0 63 | textureFormatSet: 0 64 | ignorePngGamma: 0 65 | applyGammaDecoding: 0 66 | platformSettings: 67 | - serializedVersion: 3 68 | buildTarget: DefaultTexturePlatform 69 | maxTextureSize: 2048 70 | resizeAlgorithm: 0 71 | textureFormat: -1 72 | textureCompression: 1 73 | compressionQuality: 50 74 | crunchedCompression: 0 75 | allowsAlphaSplitting: 0 76 | overridden: 0 77 | androidETC2FallbackOverride: 0 78 | forceMaximumCompressionQuality_BC6H_BC7: 0 79 | - serializedVersion: 3 80 | buildTarget: Standalone 81 | maxTextureSize: 2048 82 | resizeAlgorithm: 0 83 | textureFormat: -1 84 | textureCompression: 1 85 | compressionQuality: 50 86 | crunchedCompression: 0 87 | allowsAlphaSplitting: 0 88 | overridden: 0 89 | androidETC2FallbackOverride: 0 90 | forceMaximumCompressionQuality_BC6H_BC7: 0 91 | - serializedVersion: 3 92 | buildTarget: Server 93 | maxTextureSize: 2048 94 | resizeAlgorithm: 0 95 | textureFormat: -1 96 | textureCompression: 1 97 | compressionQuality: 50 98 | crunchedCompression: 0 99 | allowsAlphaSplitting: 0 100 | overridden: 0 101 | androidETC2FallbackOverride: 0 102 | forceMaximumCompressionQuality_BC6H_BC7: 0 103 | spriteSheet: 104 | serializedVersion: 2 105 | sprites: [] 106 | outline: [] 107 | physicsShape: [] 108 | bones: [] 109 | spriteID: 5e97eb03825dee720800000000000000 110 | internalID: 0 111 | vertices: [] 112 | indices: 113 | edges: [] 114 | weights: [] 115 | secondaryTextures: [] 116 | nameFileIdTable: {} 117 | spritePackingTag: 118 | pSDRemoveMatte: 0 119 | pSDShowRemoveMatteOption: 0 120 | userData: 121 | assetBundleName: 122 | assetBundleVariant: 123 | -------------------------------------------------------------------------------- /Editor/SceneBookmark.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_TOOLBAR_EXTENDER 2 | using UnityEngine; 3 | using UnityEditor; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Unity.Collections; 7 | using System; 8 | 9 | namespace ASze.CustomPlayButton 10 | { 11 | public static class SceneBookmark 12 | { 13 | [Serializable] 14 | private class BookmarksData 15 | { 16 | public string[] bookmarkGUIDs; 17 | 18 | public BookmarksData(GUID[] gUIDs) 19 | { 20 | bookmarkGUIDs = gUIDs.Select(g => g.ToString()).ToArray(); ; 21 | } 22 | } 23 | 24 | private const string EDITOR_PREF_KEY = "ASze.CustomPlayButton.SceneBookmark"; 25 | 26 | private static List _bookmarks; 27 | 28 | public static List Bookmarks 29 | { 30 | get 31 | { 32 | if (_bookmarks == null) 33 | { 34 | LoadBookmarks(); 35 | } 36 | return _bookmarks; 37 | } 38 | } 39 | 40 | public static void AddBookmark(SceneAsset sceneAsset) 41 | { 42 | if (_bookmarks == null) 43 | { 44 | LoadBookmarks(); 45 | } 46 | 47 | if (sceneAsset == null || !_bookmarks.Contains(sceneAsset)) 48 | { 49 | _bookmarks.Add(sceneAsset); 50 | SaveBookMarks(); 51 | } 52 | } 53 | 54 | public static void RemoveBookmark(SceneAsset sceneAsset) 55 | { 56 | if (_bookmarks == null) 57 | { 58 | LoadBookmarks(); 59 | } 60 | 61 | if (sceneAsset != null && _bookmarks.Contains(sceneAsset)) 62 | { 63 | _bookmarks.Remove(sceneAsset); 64 | SaveBookMarks(); 65 | } 66 | } 67 | 68 | public static void RemoveBookmarkAt(int index) 69 | { 70 | if (_bookmarks == null) 71 | { 72 | LoadBookmarks(); 73 | } 74 | 75 | if (index >= 0 && index < _bookmarks.Count) 76 | { 77 | _bookmarks.RemoveAt(index); 78 | SaveBookMarks(); 79 | } 80 | } 81 | 82 | public static void RemoveLastBookmark() 83 | { 84 | if (_bookmarks == null) 85 | { 86 | LoadBookmarks(); 87 | } 88 | 89 | RemoveBookmarkAt(_bookmarks.Count - 1); 90 | } 91 | 92 | public static void RemoveAll() 93 | { 94 | _bookmarks?.Clear(); 95 | SaveBookMarks(); 96 | } 97 | 98 | public static bool HasBookmark() 99 | { 100 | return _bookmarks != null && _bookmarks.Count > 0; 101 | } 102 | 103 | public static void SaveBookMarks() 104 | { 105 | if (_bookmarks != null) 106 | { 107 | NativeArray instanceIDs = new NativeArray(_bookmarks.Select(b => b?.GetInstanceID() ?? 0).ToArray(), Allocator.TempJob); 108 | NativeArray guids = new NativeArray(instanceIDs.Length, Allocator.TempJob); 109 | 110 | AssetDatabase.InstanceIDsToGUIDs(instanceIDs, guids); 111 | 112 | if (guids != null && guids.Length > 0) 113 | { 114 | string json = JsonUtility.ToJson(new BookmarksData(guids.ToArray())); 115 | EditorPrefs.SetString(EDITOR_PREF_KEY, json); 116 | } 117 | else 118 | { 119 | EditorPrefs.DeleteKey(EDITOR_PREF_KEY); 120 | } 121 | 122 | instanceIDs.Dispose(); 123 | guids.Dispose(); 124 | } 125 | } 126 | 127 | private static void LoadBookmarks() 128 | { 129 | _bookmarks = new List(); 130 | 131 | string json = EditorPrefs.GetString(EDITOR_PREF_KEY, null); 132 | if (!string.IsNullOrEmpty(json)) 133 | { 134 | BookmarksData data = JsonUtility.FromJson(json); 135 | GUID[] guids = data?.bookmarkGUIDs.Select(g => new GUID(g)).ToArray(); 136 | if (guids != null && guids.Length > 0) 137 | { 138 | foreach (var guid in guids) 139 | { 140 | string path = AssetDatabase.GUIDToAssetPath(guid); 141 | SceneAsset sceneAsset = AssetDatabase.LoadAssetAtPath(path); 142 | _bookmarks.Add(sceneAsset); 143 | } 144 | } 145 | } 146 | } 147 | } 148 | } 149 | #endif 150 | -------------------------------------------------------------------------------- /Editor/SceneListSettingsProvider.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_TOOLBAR_EXTENDER && UNITY_6000_0_OR_NEWER 2 | using UnityEditor; 3 | using UnityEngine; 4 | using UnityEditorInternal; // Required for ReorderableList 5 | using System.Collections.Generic; 6 | using ASze.CustomPlayButton; 7 | using System.Linq; 8 | 9 | public class SceneListSettingsProvider : SettingsProvider 10 | { 11 | private const string BOOKMARKS_SETTING_PATH = "Custom Play Button/Bookmarks"; 12 | private ReorderableList reorderableList; 13 | private float columnWidth; 14 | 15 | // Constructor required by SettingsProvider 16 | public SceneListSettingsProvider(string path, SettingsScope scopes = SettingsScope.Project) 17 | : base(path, scopes) 18 | { 19 | } 20 | 21 | // This method is called when the provider is activated. 22 | // Use it to set up your ReorderableList. 23 | public override void OnActivate(string searchContext, UnityEngine.UIElements.VisualElement rootElement) 24 | { 25 | // Initialize the ReorderableList 26 | reorderableList = new ReorderableList(SceneBookmark.Bookmarks, typeof(string), true, true, true, true); 27 | 28 | // Set up the header drawing 29 | reorderableList.drawHeaderCallback = (Rect rect) => 30 | { 31 | EditorGUI.LabelField(rect, "Scenes in List"); 32 | }; 33 | 34 | // Set up how each element is drawn 35 | reorderableList.drawElementCallback = (Rect rect, int index, bool isActive, bool isFocused) => 36 | { 37 | // Ensure the list has enough elements before trying to access 38 | if (index >= SceneBookmark.Bookmarks.Count) 39 | { 40 | // This can happen if elements are removed while drawing 41 | return; 42 | } 43 | 44 | SceneAsset sceneAsset = SceneBookmark.Bookmarks[index]; 45 | 46 | EditorGUI.BeginChangeCheck(); 47 | sceneAsset = (SceneAsset)EditorGUI.ObjectField(rect, sceneAsset, typeof(SceneAsset), false); 48 | if (EditorGUI.EndChangeCheck()) 49 | { 50 | if (SceneBookmark.Bookmarks.Contains(sceneAsset)) 51 | { 52 | // If the scene is already in the list, do not add it again 53 | return; 54 | } 55 | SceneBookmark.Bookmarks[index] = sceneAsset; 56 | SceneBookmark.SaveBookMarks(); 57 | } 58 | }; 59 | 60 | // Set up adding new elements 61 | reorderableList.onAddCallback = (ReorderableList list) => 62 | { 63 | SceneBookmark.AddBookmark(null); // Add a new empty bookmark 64 | // Save done in the function above 65 | }; 66 | 67 | // Set up removing elements 68 | reorderableList.onRemoveCallback = (ReorderableList list) => 69 | { 70 | SceneBookmark.RemoveLastBookmark(); 71 | // Save done in the function above 72 | }; 73 | 74 | // Set up reordering callback 75 | reorderableList.onReorderCallback = (ReorderableList list) => 76 | { 77 | SceneBookmark.SaveBookMarks(); 78 | }; 79 | 80 | // Set up height for each element 81 | reorderableList.elementHeight = EditorGUIUtility.singleLineHeight + EditorGUIUtility.standardVerticalSpacing; 82 | 83 | columnWidth = EditorSelectScenePopup.GetColumnWidth(); 84 | } 85 | 86 | // This method is called to draw the UI for your settings. 87 | public override void OnGUI(string searchContext) 88 | { 89 | // Draw the ReorderableList 90 | reorderableList.DoLayoutList(); 91 | 92 | // Add a horizontal line for separation 93 | EditorGUILayout.Space(); 94 | EditorGUILayout.Separator(); 95 | EditorGUILayout.Space(); 96 | 97 | if (GUILayout.Button("Clear All Scenes")) 98 | { 99 | if (EditorUtility.DisplayDialog("Warning!", "Are you sure you want to clear the entire scene list?", "Clear", "Cancel")) 100 | { 101 | SceneBookmark.RemoveAll(); 102 | // If you clear the list, the ReorderableList needs to know to update 103 | // its internal state. Re-initializing it effectively refreshes it. 104 | // Or you might need reorderableList.displayAdd, displayRemove etc. to be managed. 105 | // For a full clear, re-creating is simple. 106 | reorderableList = new ReorderableList(SceneBookmark.Bookmarks.ToList(), typeof(string), true, true, true, true); 107 | } 108 | } 109 | 110 | EditorGUI.BeginChangeCheck(); 111 | columnWidth = EditorGUILayout.FloatField("Popup Column Width", columnWidth); 112 | if (EditorGUI.EndChangeCheck()) 113 | { 114 | EditorSelectScenePopup.SaveColumnWidth(columnWidth); 115 | } 116 | 117 | // Inform the user about persistence 118 | EditorGUILayout.HelpBox("Scene list changes are automatically saved to EditorPrefs.", MessageType.Info); 119 | } 120 | 121 | 122 | // This static method registers the SettingsProvider with Unity. 123 | // The path here should match the path in the constructor. 124 | [SettingsProvider] 125 | public static SettingsProvider CreateSceneListSettingsProvider() 126 | { 127 | var provider = new SceneListSettingsProvider("Project/My Company/Scene List", SettingsScope.Project); 128 | 129 | // You can add keywords to make your settings searchable 130 | provider.keywords = new HashSet(new[] { "Scene", "List", "Build", "Order", "Paths" }); 131 | return provider; 132 | } 133 | 134 | #region Editor Menu 135 | [SettingsProvider] 136 | public static SettingsProvider CustomSettings_Bookmarks() 137 | { 138 | var provider = new SceneListSettingsProvider(BOOKMARKS_SETTING_PATH, SettingsScope.Project); 139 | return provider; 140 | } 141 | 142 | public static void OpenBookmarkSettings() 143 | { 144 | SettingsService.OpenProjectSettings(BOOKMARKS_SETTING_PATH); 145 | } 146 | #endregion 147 | } 148 | #endif 149 | -------------------------------------------------------------------------------- /Editor/EditorSelectScenePopup.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_TOOLBAR_EXTENDER 2 | using UnityEngine; 3 | using UnityEditor; 4 | using UnityEditor.SceneManagement; 5 | using System.Collections.Generic; 6 | 7 | namespace ASze.CustomPlayButton 8 | { 9 | public class EditorSelectScenePopup : PopupWindowContent 10 | { 11 | public const string EDITOR_COLUMN_WIDTH_PREF_KEY = "ASze.CustomPlayButton.columnWidth"; 12 | private const float DEFAULT_COLUMN_WIDTH = 200.0f; 13 | const float ICON_SIZE = 20.0f; 14 | readonly GUILayoutOption[] ICON_LAYOUT = new GUILayoutOption[] { 15 | GUILayout.Width(ICON_SIZE), GUILayout.Height(ICON_SIZE) 16 | }; 17 | 18 | 19 | GUIStyle titleButtonStyle; 20 | GUIStyle buttonStyle; 21 | GUIStyle selectedButtonStyle; 22 | GUIContent bookmarkContent; 23 | SceneAsset[] buildScenes; 24 | SceneAsset currentScene; 25 | 26 | Vector2 scrollPosBuild; 27 | Vector2 scrollPosBookmark; 28 | 29 | private float collumnWidth; 30 | 31 | public EditorSelectScenePopup() : base() 32 | { 33 | InitStyles(); 34 | 35 | bookmarkContent = EditorGUIUtility.IconContent("blendKeySelected", "Bookmark ScriptableObject"); 36 | 37 | GetBuildScenes(); 38 | currentScene = AssetDatabase.LoadAssetAtPath(EditorSceneManager.GetActiveScene().path); 39 | 40 | 41 | } 42 | 43 | void InitStyles() 44 | { 45 | var blankTex = MakeTex(new Color(0f, 0f, 0f, 0f)); 46 | var selectedTex = MakeTex(new Color(0f, 0f, 0f, 0.3f)); 47 | 48 | var hoverState = new GUIStyleState() 49 | { 50 | background = selectedTex, 51 | textColor = GUI.skin.button.onHover.textColor, 52 | }; 53 | buttonStyle = new GUIStyle(GUI.skin.label) 54 | { 55 | onHover = hoverState, 56 | hover = hoverState, 57 | }; 58 | buttonStyle.normal.background = blankTex; 59 | 60 | selectedButtonStyle = new GUIStyle(buttonStyle); 61 | selectedButtonStyle.normal.background = selectedTex; 62 | 63 | titleButtonStyle = new GUIStyle(EditorStyles.boldLabel); 64 | titleButtonStyle.onHover = buttonStyle.onHover; 65 | titleButtonStyle.hover = buttonStyle.hover; 66 | titleButtonStyle.normal.background = blankTex; 67 | } 68 | 69 | public static Texture2D MakeTex(Color col) 70 | { 71 | var texture = new Texture2D(1, 1, TextureFormat.ARGB32, false); 72 | texture.SetPixel(0, 0, col); 73 | texture.Apply(); 74 | return texture; 75 | } 76 | 77 | public override Vector2 GetWindowSize() 78 | { 79 | var width = GetColumnWidth() * (SceneBookmark.HasBookmark() ? 2 : 1); 80 | var maxRow = Mathf.Max(buildScenes.Length, SceneBookmark.Bookmarks.Count, 1); 81 | var height = Mathf.Min(22 * maxRow + 26, Screen.currentResolution.height * 0.5f); 82 | return new Vector2(width, height); 83 | } 84 | 85 | public override void OnGUI(Rect rect) 86 | { 87 | EditorGUILayout.BeginHorizontal(); 88 | DrawBuildScenes(); 89 | DrawBookmarkScenes(); 90 | EditorGUILayout.EndHorizontal(); 91 | 92 | if (Event.current.type == EventType.MouseMove && EditorWindow.mouseOverWindow == editorWindow) 93 | editorWindow?.Repaint(); 94 | } 95 | 96 | void DrawBuildScenes() 97 | { 98 | EditorGUILayout.BeginVertical(); 99 | 100 | EditorGUILayout.BeginHorizontal(); 101 | GUILayout.Label("Scenes in Build", EditorStyles.boldLabel, GUILayout.Height(20.0f)); 102 | #if UNITY_6000_0_OR_NEWER 103 | if (!SceneBookmark.HasBookmark()) 104 | { 105 | if (GUILayout.Button(EditorGUIUtility.IconContent("blendKeySelected"), titleButtonStyle, ICON_LAYOUT)) 106 | { 107 | SceneListSettingsProvider.OpenBookmarkSettings(); 108 | } 109 | } 110 | #endif 111 | EditorGUILayout.EndHorizontal(); 112 | 113 | if (buildScenes.Length > 0) 114 | { 115 | scrollPosBuild = EditorGUILayout.BeginScrollView(scrollPosBuild); 116 | for (int i = 0; i < buildScenes.Length; i++) 117 | { 118 | DrawSelection(buildScenes[i], i, true); 119 | } 120 | EditorGUILayout.EndScrollView(); 121 | } 122 | else 123 | { 124 | GUILayout.Label("No scene in build setting"); 125 | } 126 | EditorGUILayout.EndVertical(); 127 | } 128 | 129 | void DrawBookmarkScenes() 130 | { 131 | if (!SceneBookmark.HasBookmark()) return; 132 | 133 | EditorGUILayout.BeginVertical(GUILayout.MinWidth(GetColumnWidth())); 134 | 135 | #if UNITY_6000_0_OR_NEWER 136 | var content = new GUIContent(bookmarkContent); 137 | content.text = " Bookmarks"; 138 | if (GUILayout.Button(content, titleButtonStyle, GUILayout.Height(20.0f))) 139 | { 140 | SceneListSettingsProvider.OpenBookmarkSettings(); 141 | } 142 | #endif 143 | 144 | scrollPosBookmark = EditorGUILayout.BeginScrollView(scrollPosBookmark); 145 | var bookmarks = new List(SceneBookmark.Bookmarks); 146 | for (int i = 0; i < bookmarks.Count; i++) 147 | { 148 | DrawSelection(bookmarks[i], i); 149 | } 150 | 151 | EditorGUILayout.EndScrollView(); 152 | EditorGUILayout.EndVertical(); 153 | } 154 | 155 | void DrawSelection(SceneAsset scene, int index = -1, bool bookmarkButton = false) 156 | { 157 | GUILayout.BeginHorizontal(); 158 | var style = CustomPlayButton.SelectedScene == scene ? selectedButtonStyle : buttonStyle; 159 | string sceneName = scene != null ? scene.name : ""; 160 | if (GUILayout.Button(index >= 0 ? $"{index}\t{sceneName}" : sceneName, style)) 161 | { 162 | SelectScene(scene); 163 | } 164 | 165 | if (scene != null) 166 | { 167 | style = currentScene == scene ? selectedButtonStyle : buttonStyle; 168 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_BuildSettings.SelectedIcon", "Open Scene"), style, ICON_LAYOUT)) 169 | { 170 | OpenScene(scene); 171 | } 172 | } 173 | else 174 | { 175 | GUILayout.Space(ICON_SIZE); 176 | } 177 | 178 | 179 | if (bookmarkButton) 180 | { 181 | if (scene != null) 182 | { 183 | bool inBookmark = SceneBookmark.Bookmarks.Contains(scene); 184 | GUIContent content; 185 | if (inBookmark) 186 | content = EditorGUIUtility.IconContent("blendKeySelected", "Unbookmark"); 187 | else 188 | content = EditorGUIUtility.IconContent("blendKeyOverlay", "Bookmark"); 189 | if (GUILayout.Button(content, buttonStyle, ICON_LAYOUT)) 190 | { 191 | if (inBookmark) 192 | { 193 | SceneBookmark.RemoveBookmark(scene); 194 | } 195 | else 196 | { 197 | SceneBookmark.AddBookmark(scene); 198 | } 199 | } 200 | } 201 | else 202 | { 203 | GUILayout.Space(ICON_SIZE); 204 | } 205 | } 206 | else 207 | { 208 | if (GUILayout.Button(EditorGUIUtility.IconContent("d_P4_DeletedLocal", "Unbookmark"), buttonStyle, ICON_LAYOUT)) 209 | { 210 | SceneBookmark.RemoveBookmarkAt(index); 211 | } 212 | } 213 | 214 | GUILayout.EndHorizontal(); 215 | } 216 | 217 | void SelectScene(SceneAsset scene) 218 | { 219 | if (scene == null) return; 220 | CustomPlayButton.SelectedScene = scene; 221 | editorWindow.Close(); 222 | } 223 | 224 | void OpenScene(SceneAsset scene) 225 | { 226 | if (scene == null) return; 227 | if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) 228 | { 229 | var scenePath = AssetDatabase.GetAssetPath(scene); 230 | EditorSceneManager.OpenScene(scenePath); 231 | currentScene = scene; 232 | // Recreate textures which are destoryed by OpenScene 233 | InitStyles(); 234 | } 235 | } 236 | 237 | void GetBuildScenes() 238 | { 239 | List buildSceneList = new List(); 240 | var settingScenes = EditorBuildSettings.scenes; 241 | foreach (var settingScene in settingScenes) 242 | { 243 | string scenePath = AssetDatabase.GUIDToAssetPath(settingScene.guid.ToString()); 244 | var scene = AssetDatabase.LoadAssetAtPath(scenePath); 245 | if (scene != null) buildSceneList.Add(scene); 246 | } 247 | buildScenes = buildSceneList.ToArray(); 248 | } 249 | 250 | public static float GetColumnWidth() 251 | { 252 | return EditorPrefs.GetFloat(EDITOR_COLUMN_WIDTH_PREF_KEY, DEFAULT_COLUMN_WIDTH); 253 | } 254 | 255 | public static void SaveColumnWidth(float columnWidth) 256 | { 257 | EditorPrefs.SetFloat(EDITOR_COLUMN_WIDTH_PREF_KEY, columnWidth); 258 | } 259 | } 260 | } 261 | #endif 262 | -------------------------------------------------------------------------------- /Editor/CustomPlayButton.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEditor; 3 | using UnityEditor.SceneManagement; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | #if UNITY_TOOLBAR_EXTENDER 8 | using UnityToolbarExtender; 9 | #else 10 | using UnityEditor.PackageManager; 11 | using UnityEditor.PackageManager.Requests; 12 | #endif 13 | 14 | #if UNITY_2019_1_OR_NEWER 15 | using VisualElement = UnityEngine.UIElements.VisualElement; 16 | #else 17 | using VisualElement = UnityEngine.Experimental.UIElements.VisualElement; 18 | #endif 19 | 20 | namespace ASze.CustomPlayButton 21 | { 22 | [InitializeOnLoad] 23 | public static class CustomPlayButton 24 | { 25 | #if UNITY_TOOLBAR_EXTENDER 26 | const string FOLDER_PATH = "Assets/Editor/CustomPlayButton/"; 27 | const string ICONS_PATH = "Packages/com.antonysze.custom-play-button/Editor/Icons/"; 28 | 29 | private static SceneAsset selectedScene = null; 30 | 31 | 32 | static GUIContent customSceneContent; 33 | static GUIContent gameSceneContent; 34 | 35 | static Rect buttonRect; 36 | static VisualElement toolbarElement; 37 | static SceneAsset lastScene = null; 38 | 39 | public static SceneAsset SelectedScene 40 | { 41 | get { return selectedScene; } 42 | set 43 | { 44 | selectedScene = value; 45 | toolbarElement?.MarkDirtyRepaint(); 46 | 47 | if (value != null) 48 | { 49 | var path = AssetDatabase.GetAssetPath(value); 50 | EditorPrefs.SetString(GetEditorPrefKey(), path); 51 | } 52 | else 53 | { 54 | EditorPrefs.DeleteKey(GetEditorPrefKey()); 55 | } 56 | } 57 | } 58 | 59 | static class ToolbarStyles 60 | { 61 | public static readonly GUIStyle commandButtonStyle; 62 | 63 | static ToolbarStyles() 64 | { 65 | EditorApplication.playModeStateChanged += HandleOnPlayModeChanged; 66 | commandButtonStyle = new GUIStyle("Command") 67 | { 68 | fontSize = 16, 69 | alignment = TextAnchor.MiddleCenter, 70 | imagePosition = ImagePosition.ImageAbove, 71 | fontStyle = FontStyle.Bold 72 | }; 73 | } 74 | } 75 | 76 | static CustomPlayButton() 77 | { 78 | ToolbarExtender.LeftToolbarGUI.Add(OnToolbarLeftGUI); 79 | EditorApplication.update += OnUpdate; 80 | 81 | var savedScenePath = EditorPrefs.GetString(GetEditorPrefKey(), ""); 82 | selectedScene = AssetDatabase.LoadAssetAtPath(savedScenePath); 83 | if (selectedScene == null && EditorBuildSettings.scenes.Length > 0) 84 | { 85 | var scenePath = EditorBuildSettings.scenes[0].path; 86 | SelectedScene = AssetDatabase.LoadAssetAtPath(scenePath); 87 | } 88 | 89 | customSceneContent = CreateIconContent("PlaySceneButton.png", "d_UnityEditor.Timeline.TimelineWindow@2x", "Play Custom Scene"); 90 | gameSceneContent = CreateIconContent("PlayGameButton.png", "d_UnityEditor.GameView@2x", "Play Game Scene"); 91 | } 92 | 93 | static void OnToolbarLeftGUI() 94 | { 95 | GUILayout.FlexibleSpace(); 96 | 97 | var sceneName = selectedScene != null ? selectedScene.name : "Select Scene..."; 98 | var selected = EditorGUILayout.DropdownButton(new GUIContent(sceneName), FocusType.Passive, GUILayout.Width(128.0f)); 99 | 100 | if (Event.current.type == EventType.Repaint) 101 | { 102 | buttonRect = GUILayoutUtility.GetLastRect(); 103 | } 104 | 105 | if (selected) 106 | { 107 | PopupWindow.Show(buttonRect, new EditorSelectScenePopup()); 108 | } 109 | 110 | if (GUILayout.Button(customSceneContent, ToolbarStyles.commandButtonStyle)) 111 | { 112 | if (selectedScene != null) 113 | { 114 | StartScene(selectedScene); 115 | } 116 | else 117 | { 118 | EditorUtility.DisplayDialog( 119 | "Cannot play custom scene", 120 | "No scene is selected to play. Please select a scene from the dropdown list.", 121 | "Ok"); 122 | } 123 | } 124 | 125 | if (GUILayout.Button(gameSceneContent, ToolbarStyles.commandButtonStyle)) 126 | { 127 | if (EditorBuildSettings.scenes.Length > 0) 128 | { 129 | var scenePath = EditorBuildSettings.scenes[0].path; 130 | var scene = AssetDatabase.LoadAssetAtPath(scenePath); 131 | StartScene(scene); 132 | } 133 | else 134 | { 135 | if (!EditorUtility.DisplayDialog( 136 | "Cannot play the game", 137 | "Please add the first scene in build setting in order to play the game.", 138 | "Ok", "Open build setting")) 139 | { 140 | EditorWindow.GetWindow(System.Type.GetType("UnityEditor.BuildPlayerWindow,UnityEditor")); 141 | } 142 | } 143 | } 144 | } 145 | 146 | static void StartScene(SceneAsset scene) 147 | { 148 | if (EditorApplication.isPlaying) 149 | { 150 | lastScene = scene; 151 | EditorApplication.isPlaying = false; 152 | } 153 | else 154 | { 155 | ChangeScene(scene); 156 | } 157 | } 158 | 159 | static void OnUpdate() 160 | { 161 | // Get toolbar element for repainting 162 | if (toolbarElement == null) 163 | { 164 | var toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar"); 165 | var toolbars = Resources.FindObjectsOfTypeAll(toolbarType); 166 | var currentToolbar = toolbars.Length > 0 ? (ScriptableObject)toolbars[0] : null; 167 | if (currentToolbar != null) 168 | { 169 | var guiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView"); 170 | #if UNITY_2020_1_OR_NEWER 171 | var iWindowBackendType = typeof(Editor).Assembly.GetType("UnityEditor.IWindowBackend"); 172 | var guiBackend = guiViewType.GetProperty("windowBackend", 173 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 174 | var viewVisualTree = iWindowBackendType.GetProperty("visualTree", 175 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 176 | var windowBackend = guiBackend.GetValue(currentToolbar); 177 | toolbarElement = (VisualElement)viewVisualTree.GetValue(windowBackend, null); 178 | #else 179 | var viewVisualTree = guiViewType.GetProperty("visualTree", 180 | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); 181 | toolbarElement = (VisualElement)viewVisualTree.GetValue(currentToolbar, null); 182 | #endif 183 | } 184 | } 185 | 186 | if (lastScene == null || 187 | EditorApplication.isPlaying || EditorApplication.isPaused || 188 | EditorApplication.isCompiling || EditorApplication.isPlayingOrWillChangePlaymode) 189 | { 190 | return; 191 | } 192 | 193 | ChangeScene(lastScene); 194 | lastScene = null; 195 | } 196 | 197 | static void ChangeScene(SceneAsset scene) 198 | { 199 | if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) 200 | { 201 | EditorSceneManager.playModeStartScene = scene; 202 | EditorApplication.isPlaying = true; 203 | } 204 | } 205 | 206 | static void HandleOnPlayModeChanged(PlayModeStateChange playMode) 207 | { 208 | if (playMode == PlayModeStateChange.ExitingPlayMode) 209 | { 210 | EditorSceneManager.playModeStartScene = null; 211 | } 212 | } 213 | 214 | public static string GetEditorPrefKey() 215 | { 216 | var projectPrefix = PlayerSettings.companyName + "." + PlayerSettings.productName; 217 | return projectPrefix + "_CustomPlayButton_SelectedScenePath"; 218 | } 219 | 220 | public static GUIContent CreateIconContent(string localTex, string builtInTex, string tooltip) 221 | { 222 | var tex = LoadTexture(localTex); 223 | if (tex != null) return new GUIContent(tex, tooltip); 224 | else return EditorGUIUtility.IconContent(builtInTex, tooltip); 225 | } 226 | 227 | public static Texture2D LoadTexture(string path) 228 | { 229 | return (Texture2D)EditorGUIUtility.Load(ICONS_PATH + path); 230 | } 231 | #else 232 | static AddRequest request; 233 | 234 | static CustomPlayButton() 235 | { 236 | if (!EditorUtility.DisplayDialog( 237 | "Cannot activate Custom Play Button", 238 | "Prerequisite package is needed for \"unity-custom-play-button\".\nPlease install package \"unity-toolbar-extender\"(https://github.com/marijnz/unity-toolbar-extender.git).", 239 | "Ok", "Install package")) 240 | { 241 | request = Client.Add("https://github.com/marijnz/unity-toolbar-extender.git"); 242 | EditorApplication.update += Progress; 243 | } 244 | } 245 | 246 | static void Progress() 247 | { 248 | if (request.IsCompleted) 249 | { 250 | if (request.Status == StatusCode.Success) 251 | Debug.Log("Installed: " + request.Result.packageId); 252 | else if (request.Status >= StatusCode.Failure) 253 | Debug.Log(request.Error.message); 254 | 255 | EditorApplication.update -= Progress; 256 | } 257 | } 258 | #endif 259 | } 260 | } 261 | --------------------------------------------------------------------------------