So you have say 10 scenes in a project. In any real project it “has to” begin on a particular scene, your scene 0, “GamePrep” which loads stuff to make the game work.
When you’re working on the game in the editor. You might be working on some other scene, say scene 4.
Every single time you hit play in the editor, in reality you FIRST have to click to scene zero, and then hit play.
Would it be possible, using Editor Script magic, to fix this Common Problem ?
So, when you click or keystroke “Play”, it changes you to scene 0, and only then "Play"s.
BTW Here’s a full simple working solution based on everyone’s amazing work below!
// IN YOUR EDITOR FOLDER, have SimpleEditorUtils.cs.
// paste in this text.
// to play, HIT COMMAND-ZERO rather than command-P
// (the zero key, is near the P key, so it's easy to remember)
// simply insert the actual name of your opening scene
// "__preEverythingScene" on the second last line of code below.
using UnityEditor;
using UnityEngine;
using System.Collections;
[InitializeOnLoad]
public static class SimpleEditorUtils
{
// click command-0 to go to the prelaunch scene and then play
[MenuItem("Edit/Play-Unplay, But From Prelaunch Scene %0")]
public static void PlayFromPrelaunchScene()
{
if ( EditorApplication.isPlaying == true )
{
EditorApplication.isPlaying = false;
return;
}
EditorApplication.SaveCurrentSceneIfUserWantsTo();
EditorApplication.OpenScene(
"Assets/stuff/Scenes/__preEverythingScene.unity");
EditorApplication.isPlaying = true;
}
}
using UnityEditor;
using UnityEngine;
using System.IO;
using System.Linq;
using System.Collections;
[ExecuteInEditMode]
public class PlayFromScene : EditorWindow {
[SerializeField] string lastScene="";
[SerializeField] int targetScene = 0;
[SerializeField] string waitScene = null;
[SerializeField] bool hasPlayed = false;
[MenuItem("Edit/Play From Scene %0")]
public static void Run() {
EditorWindow.GetWindow<PlayFromScene>();
}
static string[] sceneNames;
static EditorBuildSettingsScene[] scenes;
void OnEnable() {
scenes = EditorBuildSettings.scenes;
sceneNames = scenes.Select(x=>AsSpacedCamelCase(Path.GetFileNameWithoutExtension(x.path))).ToArray();
}
void Update() {
if ( !EditorApplication.isPlaying ) {
if ( null==waitScene && !string.IsNullOrEmpty(lastScene) ) {
EditorApplication.OpenScene(lastScene);
lastScene = null;
}
}
}
void OnGUI() {
if ( EditorApplication.isPlaying ) {
if ( EditorApplication.currentScene == waitScene ) {
waitScene = null;
}
return;
}
if ( EditorApplication.currentScene == waitScene ) {
EditorApplication.isPlaying = true;
}
if ( null == sceneNames ) return;
targetScene = EditorGUILayout.Popup(targetScene,sceneNames);
if(GUILayout.Button("Play")) {
lastScene = EditorApplication.currentScene;
waitScene = scenes[targetScene].path;
EditorApplication.SaveCurrentSceneIfUserWantsTo();
EditorApplication.OpenScene(waitScene);
}
}
public string AsSpacedCamelCase(string text) {
System.Text.StringBuilder sb = new System.Text.StringBuilder(text.Length*2);
sb.Append(char.ToUpper(text[0]));
for(int i=1; i<text.Length;i++) {
if ( char.IsUpper(text*) && text[i-1] != ' ' )*
sb.Append(' ');*
_ sb.Append (text*);_
_ }_
_ return sb.ToString();_
_ }_
_}_ give Tonyli a bump up too if this works for ya, i totally stole his words
_-----_
_[Editor’s note… and here’s the original “super-elegant” solution. I have found it very useful:]_
_// class doesn’t matter, add to anything in the Editor folder*_ // for any beginners reading, this is c#
[MenuItem(“Edit/Play-Stop, But From Prelaunch Scene %0”)] public static void PlayFromPrelaunchScene() { if ( EditorApplication.isPlaying == true ) { EditorApplication.isPlaying = false; return; }
Hook into the play button. So you don’t need use the menu item.
Automatically use the currently scene changes in runtime, you don’t even need to save them.
After exit play mode, scene go back to previously editing scene. And the all the unsaved changes still there.
So here’s the code,
public static class PlayFromTheFirstScene
{
const string playFromFirstMenuStr = "Edit/Always Start From Scene 0 &p";
static bool playFromFirstScene
{
get{return EditorPrefs.HasKey(playFromFirstMenuStr) && EditorPrefs.GetBool(playFromFirstMenuStr);}
set{EditorPrefs.SetBool(playFromFirstMenuStr, value);}
}
[MenuItem(playFromFirstMenuStr, false, 150)]
static void PlayFromFirstSceneCheckMenu()
{
playFromFirstScene = !playFromFirstScene;
Menu.SetChecked(playFromFirstMenuStr, playFromFirstScene);
ShowNotifyOrLog(playFromFirstScene ? "Play from scene 0" : "Play from current scene");
}
// The menu won't be gray out, we use this validate method for update check state
[MenuItem(playFromFirstMenuStr, true)]
static bool PlayFromFirstSceneCheckMenuValidate()
{
Menu.SetChecked(playFromFirstMenuStr, playFromFirstScene);
return true;
}
// This method is called before any Awake. It's the perfect callback for this feature
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void LoadFirstSceneAtGameBegins()
{
if(!playFromFirstScene)
return;
if(EditorBuildSettings.scenes.Length == 0)
{
Debug.LogWarning("The scene build list is empty. Can't play from first scene.");
return;
}
foreach(GameObject go in Object.FindObjectsOfType<GameObject>())
go.SetActive(false);
SceneManager.LoadScene(0);
}
static void ShowNotifyOrLog(string msg)
{
if(Resources.FindObjectsOfTypeAll<SceneView>().Length > 0)
EditorWindow.GetWindow<SceneView>().ShowNotification(new GUIContent(msg));
else
Debug.Log(msg); // When there's no scene view opened, we just print a log
}
}
Enjoy!
Oh, also, there’s some other useful tricks like how to make a checkable menu item correctly, or how to show a compile error like message in the scene view. Check them out in the code if you interested.
In scene 0, create a game object called “Scene 0 Object” and attach this script:
public class DontDestroyOnLoad : MonoBehaviour {
void Start() {
DontDestroyOnLoad(this.GameObject);
}
}
In your other scenes, create a game object called “Play Scene 0” and attach this script:
public class PlayScene0 : MonoBehaviour {
void Start() {
if (Application.isEditor && (GameObject.Find("Scene 0 Object") == null)) {
Application.LoadLevel(0);
}
}
}
You can make this a prefab and just add the prefab to every scene.
This way you can continue using your regular play button. If you really want to run from scene 4 instead of auto-loading scene 0 on play, just disable the “Play Scene 0” object.
My approach would be to directly hook into the user pressing the play button with the following code:
using UnityEngine;
using UnityEditor;
using System.Collections;
[InitializeOnLoad]
public static class LoadSceneOnPressingPlay{
[SerializeField]
public static string oldScene;
static void LoadSceneOnPressingPlay(){
EditorApplication.playmodeStateChanged += StateChange;
}
static void StateChange(){
if(EditorApplication.isPlaying){
EditorApplication.playmodeStateChanged -= StateChange;
if(!EditorApplication.isPlayingOrWillChangePlaymode){
//We're in playmode, just about to change playmode to Editor
Debug.Log("Loading original level");
EditorApplication.OpenScene(oldScene);
}else{
//We're in playmode, right after having pressed Play
oldScene = EditorApplication.currentScene;
Debug.Log("Loading first level");
Application.LoadLevel(0);
}
}
}
}
For the record, the alternative to LoadLevel in the Editor is
I just went through setting up Fattie’s Grid system with the Scene Auto Loader by User_Yoyo in a little video I put together.
Note: This movie shows how to rig it so when you press play, the pre-game scene gets loaded and then the scene you are working on gets automatically loaded after that.
I also put a download link to the package in the movie details.
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
/// <summary>
/// Scene auto loader.
/// </summary>
/// <description>
/// This class adds a File > Scene Autoload menu containing options to select
/// a "master scene" enable it to be auto-loaded when the user presses play
/// in the editor. When enabled, the selected scene will be loaded on play,
/// then the original scene will be reloaded on stop.
///
/// Based on an idea on this thread:
/// http://forum.unity3d.com/threads/157502-Executing-first-scene-in-build-settings-when-pressing-play-button-in-editor
/// </description>
[InitializeOnLoad]
static class SceneAutoLoader {
// Static constructor binds a playmode-changed callback.
// [InitializeOnLoad] above makes sure this gets executed.
static SceneAutoLoader() {
EditorApplication.playmodeStateChanged += OnPlayModeChanged;
}
// Menu items to select the "master" scene and control whether or not to load it.
[MenuItem("File/Scene Autoload/Select Master Scene...")]
private static void SelectMasterScene() {
string masterScene = EditorUtility.OpenFilePanel("Select Master Scene", Application.dataPath, "unity");
if (!string.IsNullOrEmpty(masterScene)) {
MasterScene = masterScene;
LoadMasterOnPlay = true;
}
}
[MenuItem("File/Scene Autoload/Load Master On Play", true)]
private static bool ShowLoadMasterOnPlay() {
return !LoadMasterOnPlay;
}
[MenuItem("File/Scene Autoload/Load Master On Play")]
private static void EnableLoadMasterOnPlay() {
LoadMasterOnPlay = true;
}
[MenuItem("File/Scene Autoload/Don't Load Master On Play", true)]
private static bool ShowDontLoadMasterOnPlay() {
return LoadMasterOnPlay;
}
[MenuItem("File/Scene Autoload/Don't Load Master On Play")]
private static void DisableLoadMasterOnPlay() {
LoadMasterOnPlay = false;
}
// Play mode change callback handles the scene load/reload.
private static void OnPlayModeChanged() {
if (!LoadMasterOnPlay) {
return;
}
if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode) {
// User pressed play -- autoload master scene.
PreviousScene = EditorSceneManager.GetActiveScene().path;
if (EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) {
EditorSceneManager.OpenScene(MasterScene);
}
else {
// User cancelled the save operation -- cancel play as well.
EditorApplication.isPlaying = false;
}
}
if (!EditorApplication.isPlaying && !EditorApplication.isPlayingOrWillChangePlaymode) {
// User pressed stop -- reload previous scene.
EditorSceneManager.OpenScene(PreviousScene);
}
}
// Properties are remembered as editor preferences.
private const string cEditorPrefLoadMasterOnPlay = "SceneAutoLoader.LoadMasterOnPlay";
private const string cEditorPrefMasterScene = "SceneAutoLoader.MasterScene";
private const string cEditorPrefPreviousScene = "SceneAutoLoader.PreviousScene";
private static bool LoadMasterOnPlay {
get { return EditorPrefs.GetBool(cEditorPrefLoadMasterOnPlay, false); }
set { EditorPrefs.SetBool(cEditorPrefLoadMasterOnPlay, value); }
}
private static string MasterScene {
get { return EditorPrefs.GetString(cEditorPrefMasterScene, "Master.unity"); }
set { EditorPrefs.SetString(cEditorPrefMasterScene, value); }
}
private static string PreviousScene {
get { return EditorPrefs.GetString(cEditorPrefPreviousScene, EditorSceneManager.GetActiveScene().path); }
set { EditorPrefs.SetString(cEditorPrefPreviousScene, value); }
}
}
I made this simple script that loads the scene at index 0 in the build settings when you push Play. I hope someone find it useful.
It detects when the play button is push and load the scene. Then, everything comes back to normal.
Oh! And it automatically executes itself after opening Unity and after compile scripts, so never mind about execute it. Simply put it on a Editor folder and it works.
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.SceneManagement;
[InitializeOnLoadAttribute]
public static class DefaultSceneLoader
{
static DefaultSceneLoader(){
EditorApplication.playModeStateChanged += LoadDefaultScene;
}
static void LoadDefaultScene(PlayModeStateChange state){
if (state == PlayModeStateChange.ExitingEditMode) {
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo ();
}
if (state == PlayModeStateChange.EnteredPlayMode) {
EditorSceneManager.LoadScene (0);
}
}
}
#endif
I wanted to reload the Scene I had open when I hit the stop button. This is my take on it.
using UnityEditor;
using UnityEngine.SceneManagement;
[InitializeOnLoadAttribute]
public static class LoadDefalutScene
{
// register an event handler when the class is initialized
static LoadDefalutScene()
{
EditorApplication.playModeStateChanged += LogPlayModeState;
}
private static void LogPlayModeState(PlayModeStateChange state)
{
if(state == PlayModeStateChange.EnteredPlayMode)
{
SceneManager.LoadScene("EditorMenu");
}
}
}
Way late for the answer, but I found a much simpler way to manage this. Its a little inflexible, but gets the job done quickly and effectively.
Provided you have a persistent GameManager game object (using the singleton pattern) in your preload scene, you can accomplish this with a static constructor:
I’ve stolen parts of code from other guys here and made my version:) It uses menu shortcut (Edit > PlayFromStartupScene) to start the game from the startup scene. After the playmode is stopped, it loads the scene the user was editing before that.
Tested with Unity 2017.4.18 lts
using UnityEditor;
using UnityEditor.SceneManagement;
[InitializeOnLoad]
public static class PlayFromTheStartupScene
{
static PlayFromTheStartupScene()
{
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
}
private static void OnPlayModeStateChanged(PlayModeStateChange state)
{
if(state == PlayModeStateChange.EnteredEditMode)
{
if(EditorPrefs.HasKey("lastLoadedScenePath"))
{
EditorSceneManager.OpenScene(EditorPrefs.GetString("lastLoadedScenePath"));
EditorPrefs.DeleteKey("lastLoadedScenePath");
}
}
}
[MenuItem("Edit/PlayFromStartupScene %0")]
public static void PlayFromStartupScene()
{
if (EditorApplication.isPlaying == true)
{
EditorApplication.isPlaying = false;
return;
}
EditorPrefs.SetString("lastLoadedScenePath", EditorSceneManager.GetActiveScene().path);
EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo();
EditorSceneManager.OpenScene("Assets/Scenes/StartupScene.unity");
EditorApplication.isPlaying = true;
}
}
I took a really simplistic approach to this, just create a script that checks if a persistent object type exists that you would only have on your startup scene, add this script to non-startup scenes e.g.
public class CheckStartedOnInitialScreen : MonoBehaviour
{
void Start()
{
var clientApiSystem = FindObjectOfType<ClientApiSystem>();
if (clientApiSystem == null)
{
if (SceneManager.GetActiveScene().name != "Startup")
{
SceneManager.LoadScene(sceneName: "Startup");
}
}
}
}
And voila, if you forget to switch to the startup scene it will do it for you and leave you where you left off
#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Util
{
public static class PlayFromStartingSceneUtil
{
private const string PLAY_FROM_FIRST_MENU_STR = "Edit/Always Start From Scene 0 &p";
private static bool PlayFromFirstScene
{
get => EditorPrefs.HasKey(PLAY_FROM_FIRST_MENU_STR) && EditorPrefs.GetBool(PLAY_FROM_FIRST_MENU_STR);
set => EditorPrefs.SetBool(PLAY_FROM_FIRST_MENU_STR, value);
}
[MenuItem(PLAY_FROM_FIRST_MENU_STR, false, 150)]
private static void PlayFromFirstSceneCheckMenu()
{
PlayFromFirstScene = !PlayFromFirstScene;
Menu.SetChecked(PLAY_FROM_FIRST_MENU_STR, PlayFromFirstScene);
ShowNotifyOrLog(PlayFromFirstScene ? "Play from scene 0" : "Play from current scene");
}
// The menu won't be gray out, we use this validate method for update check state
[MenuItem(PLAY_FROM_FIRST_MENU_STR, true)]
private static bool PlayFromFirstSceneCheckMenuValidate()
{
Menu.SetChecked(PLAY_FROM_FIRST_MENU_STR, PlayFromFirstScene);
return true;
}
// This method is called before any Awake. It's the perfect callback for this feature
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
private static void LoadFirstSceneAtGameBegins()
{
if(!PlayFromFirstScene)
return;
if(EditorBuildSettings.scenes.Length == 0)
{
Debug.LogWarning("The scene build list is empty. Can't play from first scene.");
return;
}
foreach(GameObject go in Object.FindObjectsOfType<GameObject>())
go.SetActive(false);
SceneManager.LoadScene(0);
}
private static void ShowNotifyOrLog(string msg)
{
if(Resources.FindObjectsOfTypeAll<SceneView>().Length > 0)
EditorWindow.GetWindow<SceneView>().ShowNotification(new GUIContent(msg));
else
Debug.Log(msg); // When there's no scene view opened, we just print a log
}
}
}
#endif