I was wondering if there was a way in Unity that when I start my program on a scene it fires a function first, I should add that I want this one function to work regardless of what scene I'm in. So a simple Start function wont cut it. Not sure if this is possible in Unity?
public void ProgramBegins()
{
//FIRES FIRST ON ANY SCENE
//DO STUFF
}
RuntimeInitializeOnLoadMethodAttribute can help you :)
using UnityEngine;
class MyClass
{
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
static void OnBeforeSceneLoadRuntimeMethod()
{
Debug.Log("Before scene loaded");
}
}
Here you can find the execution order of all functions in unity: http://docs.unity3d.com/Manual/ExecutionOrder.html
Awake is the first function to be executed in your standalone application. For it to run you need to have a GameObject with a attached script containing the Awake function. This should be added to every scene if you want it to run regardless of the scene.
You still have to decide what is the startup scene of your game. So it is enough to add the GameObject there, if you actually want it to run just in the start of the program.
I use a prefab _AppStartup which is just an empty game object having a script AppStartup. Drag this in every scene and configure AppStartup to be executed as first like #maZZZu has stated.
AppStartup performs the following jobs:
Global initialisation tasks when the app is started
Switch to boot scene if have start an arbitrary scene in editor mode
Scene specific initialisation tasks
public class AppStartup : MonoBehaviour
{
const int bootSceneNo = 0;
public static bool veryFirstCallInApp = true;
void Awake ()
{
if (veryFirstCallInApp) {
ProgramBegins ();
if (Application.loadedLevel != bootSceneNo) {
// not the right scene, load boot scene and CU later
Application.LoadLevel (bootSceneNo);
// return as this scene will be destroyed now
return;
} else {
// boot scene stuff goes here
}
} else {
// stuff that must not be done in very first initialisation but afterwards
}
InitialiseScene ();
veryFirstCallInApp = false;
DestroyObject (gameObject);
}
void ProgramBegins()
{
// code executed only once when the app is started
}
void InitialiseScene ()
{
// code to initialise scene
}
}
So all you have to do is drag this prefab in every scene manually and give it -100 or whatever in the script execution order. Especially when the project grows and relies on a predefined scene flow it will save you al lot time and hassle.
Yes...
Decorate your method with [RuntimeInitializeOnLoadMethod].
It will be invoked as soon as the scene has finished loading (after Awake events).
[RuntimeInitializeOnLoadMethod]
static void OnRuntimeMethodLoad() {
Debug.Log("After Scene is loaded and game is running");
}
Documentation: https://docs.unity3d.com/ScriptReference/RuntimeInitializeOnLoadMethodAttribute.html
//try this it work in my case
public static bool isFirstLoad;
void Awake()
{
//your work that run only once even restart this scene
mainPanel.SetActive(!isFirstLoad);
if(!isFirstLoad)
{
isFirstLoad=true;
}
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this.gameObject);
}
}
Related
I am working on this 3d game were a player has to press E to interact with objects.
the player has a collider that when touching a type of trigger that has this type of code, makes an object pop up showing that the player's is "selecting" something.
when the player is in the trigger I made it were when they press E, the objects animation plays. When adding the objecting selecting thing it made it now that I cant make the animation play when pressing E
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fixedpress : MonoBehaviour
{
public Animator Tributton;
public GameObject GM;
private Coroutine routine;
private void Start()
{
GM.SetActive(false);
}
private void OnTriggerStay(Collider other)
{
// in general rather use CompareTag instead of ==
// it is slightly faster and also shows an error if the tag doesn't exist instead of failing silent
if (!other.CompareTag("LookTrig")) {
GM.SetActive(true);
return;
}
// just in case to prevent concurrent routines
if (routine != null) StopCoroutine(routine);
// start a new Coroutine
routine = StartCoroutine(WaitForKeyPress());
}
private IEnumerator WaitForKeyPress()
{
// check each FRAME if the key goes down
// This is way more reliable as OnTriggerStay which is called
// in the physics loop and might skip some frames
// This also prevents from holding E while entering the trigger, it needs to go newly down
yield return new WaitUntil(() => Input.GetKeyDown(KeyCode.E));
// set the trigger once and finish the routine
// There is no way to trigger twice except exit the trigger and enter again now
Tributton.SetTrigger("Fiveyon");
Debug.Log("Fiveyon!");
// If you even want to prevent this from getting triggered ever again simply add
enabled = false;
// Now this can only be triggered ONCE for the entire lifecycle of this component
// (except you enable it from the outside again of course)
}
void OnTriggerExit(Collider other)
{
if (!other.CompareTag("LookTrig")) {
GM.SetActive(false);
return;
}
// when exiting the trigger stop the routine so later button press is not handled
if (routine != null)
{
StopCoroutine(routine);
GM.SetActive(false);
}
}
}
OnTrigger methods are called when another object with a Collider enters in your object Collider.
First, make sure everything was well set up well, and maybe you want to use OnCollision instead of OnTrigger methods, if you want to detect a collision whether than a "penetration" if i may say
I got this script. The idea is to trigger LoadNextScene() after finishing the game. This will load a scene called "Well done". This scene shall be open for about three seconds and after that load the scene called "Start menu".
This works half the way. When the game is finished "Well done" scene is loaded but the "Start menu" isnt loaded and the debug message in MyCoroutine() isnt printed.
What can be wrong?
Im also thinking about if I shall stop my coroutine as good practise the way I do in the code?
Here is my code:
public void LoadNextScene()
{
Debug.Log("NEXT SCENE WAIT 3 sec");
SceneManager.LoadScene("Well done");
Debug.Log("Start waiting");
StartCoroutine(MyCoroutine());
StopCoroutine(MyCoroutine());
}
private IEnumerator MyCoroutine()
{
yield return new WaitForSeconds(3);
Debug.Log("Finish waiting and load start menu");
SceneManager.LoadScene("Start Menu");
}
Well after your very first SceneManager.Load the current Scene this script instance belongs to is unloaded → this GameObject destroyed → Nobody is running your Coroutine anymore.
For some reason you additional used StopCoroutine right after starting it so it wouldn't run anyway → No this makes no sense ;)
Alternatively you could simply have a separate dedicated script in your Well Done scene which automatically goes back to the main menu after 3 seconds.
Actually to make it more flexible and reusable you could have a component like
public class SwitchSceneDelayed : MonoBehaviour
{
// Configure these in the Inspector
[SerializeField] bool autoSwitch = true;
[SerializeField] float _delay = 3f;
[SerializeField] string targetScene = "Start menu";
private void Start()
{
if(autoSwitch) StartCoroutine(SwitchDelayedRoutine(_delay));
}
public void SwitchDelayed()
{
StartCoroutine(SwitchDelayedRoutine(targetScene, _delay));
}
public void SwitchDelayed(float delay)
{
StartCoroutine(SwitchDelayedRoutine(targetScene, delay));
}
public void SwitchDelayed(string scene)
{
StartCoroutine(SwitchDelayedRoutine(scene, _delay));
}
public void SwitchDelayed(string scene, float delay)
{
StartCoroutine(SwitchDelayedRoutine(scene, delay));
}
private IEnumerator SwitchDelayedRoutine(string scene, float delay)
{
Debug.Log($"Started waiting for {delay} seconds ...");
yield return new WaitForSeconds (delay);
SceneManager.LoadScene(scene);
}
}
So this script allows you to switch delayed to another scene configured in targetScene after delay seconds. If you enable autoSwitch the routine will start right away, otherwise you can trigger it at any time via calling SwitchDelayed.
Put this in your Well Done scene and then simply only do
public void LoadNextScene()
{
SceneManager.LoadScene("Well done");
}
in your original script.
Im trying to make a basic platformer game and my level controller script isnt working, it needs to load loading scene first and then nextlevel but it constantly loads the nextlevel
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
}
IEnumerator Wait()
{
yield return new WaitForSeconds(3);
}
void Update()
{
if (ReadyToNextLevel())
{
SceneManager.LoadScene("Loading");
StartCoroutine(Wait());
SceneManager.LoadScene(nextLevel);
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
There are two big issues in your code.
Starting a Coroutine does not delay the method that started the routine!
If you want something happening after the Coroutine is finished you need to either move it into the routine itself or use a callback.
However, the thing is: You are loading a new scene, namely the "Loading" scene -> The current scene is unloaded -> the object this script is on gets destroyed -> the routine would no further executed.
Except your object is DontDestroyOnLoad which seems not the case from your code.
So in order to solve both you will need to make sure this object is not destroyed when another scene is loaded, at least not until it finished the loading process.
You could do this like e.g.
public class LevelController : MonoBehaviour
{
[SerializeField] string nextLevel;
private Gem[] gems;
void OnEnable()
{
gems = FindObjectsOfType<Gem>();
// Makes sure this is not destroyed when another scene is load
DontDestroyOnLoad(gameObject);
}
bool alreadyLoading;
IEnumerator Wait(Action whenDone)
{
yield return new WaitForSeconds(3);
// invoke the callback action
whenDone?.Invoke();
}
void Update()
{
if (ReadyToNextLevel() && ! alreadyLoading)
{
alreadyLoading = true;
SceneManager.LoadScene("Loading");
StartCoroutine(Wait(() =>
{
// This will be done after the routine finished
SceneManager.LoadScene(nextLevel);
// Now we don't need this object anymore
Destroy(gameObject);
}));
}
}
bool ReadyToNextLevel()
{
foreach (var gem in gems)
{
if (gem.gameObject.activeSelf)
{
return false;
}
}
return true;
}
}
EDIT: see derHugo's answer. They are more familiar with Unity than I am and point out that your script will be unloaded on scene change.
Your Wait() coroutine yields immediately, so StartCoroutine(Wait()) will return immediately and load the next scene.
If you want to wait three seconds before loading, then put the load inside the coroutine.
Like this:
private bool isLoading;
void Update()
{
if (!isLoading && ReadyToNextLevel())
StartCoroutine(LoadNextLevel());
}
IEnumerator LoadNextLevel()
{
isLoading = true;
SceneManager.LoadScene("Loading");
yield return new WaitForSeconds(3);
SceneManager.LoadScene(nextLevel);
isLoading = false;
}
See Coroutine help for more details
there are two reasons why this doesn't work, firstly when you load into the loading scene the gameObject is destroyed because you haven't told it to DontDestroyOnLoad, so the script will be sort of 'deleted' from the current scene. Second of all, when you call the coroutine, it calls the coroutine and then immediately continuous on to the next line of code, it doesn't wait for the coroutine to finish before moving on. I think derHugo covered how to fix these.
When loading a new Scene I run into the trouble of having my mouse drag not carry over to the next scene and having to re-click when the new scene is loaded.
I would like the mouse click to carry over seamlessly to the next scene without the player noticing and more generally I would like to know how best to preserve certain game objects and make them carry over to the next scene.
In essence, what I'm trying to do is have the entire game act like one big scene that the player can play trough but still be broken down into smaller scenes that could be accessed or transformed into levels at a later stage.
Thanks in advance.
This is the code I'm currently using
using UnityEngine;
using System.Collections;
using UnityEngine.Profiling;
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
private void OnMouseDrag()
{
mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
transform.position = mousePos;
DontDestroyOnLoad(this.gameObject);
}
}
Bellow is the script that is responsible for the loading of the scene:
public class StarCollision : MonoBehaviour
{
private bool alreadyScored = false;
private void OnEnable()
{
alreadyScored = false;
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("White Ball"))
{
if (!alreadyScored)
{
ScoreScript.scoreValue += 1;
StartCoroutine(ChangeColor());
alreadyScored = true;
}
}
if (ScoreScript.scoreValue > 4)
{
SceneManager.LoadScene(1);
}
}
private IEnumerator ChangeColor()
{
ScoreScript.score.color = Color.yellow;
yield return new WaitForSeconds(0.1f);
ScoreScript.score.color = Color.white;
gameObject.SetActive(false);
}
}
I think the main reason why it doesn't work is that you probably also have another Camera in the new Scene.
The OnMouseDrag rely on the Physics system internally using the objects Collider and raycasts from the Camera. Now if you switch Scene I'ld guess the one Camera gets disabled so your drag gets interrupted.
Also using LoadScene instead of LoadSceneAsync causes a visible lag and might also be related to the issue.
I have a maybe a bit more complex solution but that is what I usually do:
1. Have one Global Scene "MainScene"
This Scene contains stuff like e.g. the MainCamera, global ligthning, global manager components that should never be destroyed anyway.
2. Use additive async Scene loading
You said you do not want your user to not note when the scene switches so I would recommend using SceneManager.LoadSceneAsync anyway.
Then in order to not unload the before mentioned MainScene you pass the optional parameter LoadSceneMode.Additive. This makes the new Scene be loaded additional to the already present one. Then later you only have to exchange those by unloading the previously additive loaded scene.
I created a very simple static manager for this:
public static class MySceneManager
{
// store build index of last loaded scene
// in order to unload it later
private static int lastLoadedScene = -1;
public static void LoadScene(int index, MonoBehaviour caller)
{
caller.StartCoroutine(loadNextScene(index));
}
// we need this to be a Coroutine (see link below)
// in order to correctly set the SceneManager.SetActiveScene(newScene);
// after the scene has finished loading. So the Coroutine is required
// in order to wait with it until the reight moment
private static IEnumerator loadNextScene(int index)
{
// start loading the new scene async and additive
var _async = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
// optionally prevent the scene from being loaded instantly but e.g.
// display a loading progress
// (in your case not but for general purpose I added it)
_async.allowSceneActivation = false;
while (_async.progress < 0.9f)
{
// e.g. show progress of loading
// yield in a Coroutine means
// "pause" the execution here, render this frame
// and continue from here in the next frame
yield return null;
}
_async.allowSceneActivation = true;
// loads the remaining 10%
// (meaning it runs all the Awake and OnEnable etc methods)
while (!_async.isDone)
{
yield return null;
}
// at this moment the new Scene is supposed to be fully loaded
// Get the new scene
var newScene = SceneManager.GetSceneByBuildIndex(index);
// would return false if something went wrong during loading the scene
if (!newScene.IsValid()) yield break;
// Set the new scene active
// we need this later in order to place objects back into the correct scene
// if we do not want them to be DontDestroyOnLoad anymore
// (see explanation in SetDontDestroyOnLoad)
SceneManager.SetActiveScene(newScene);
// Unload the last loaded scene
if (lastLoadedScene >= 0) SceneManager.UnloadSceneAsync(lastLoadedScene);
// update the stored index
lastLoadedScene = index;
}
}
This MySceneManager is a static class so it is not attached to any GameObject or Scene but simply "lives" in the Assets. You can now call it from anywhere using
MySceneManager.LoadScene(someIndex, theMonoBehaviourCallingIt);
The second parameter of type MonoBehaviour (so basically your scripts) is required because someone has to be responsible for running the IEnumerator Coroutine which can't be done by the static class itself.
3. DontDestroyOnLoad
Currently you are adding any GameObject you dragged at any time to DontDestroyOnLoad. But you never undo this so anything you touched meanwhile will be carried on from that moment ... forever.
I would rather use e.g. something like
public static class GameObjectExtensions
{
public static void SetDontDestroyOnLoad(this GameObject gameObject, bool value)
{
if (value)
{
// Note in general if DontDestroyOnLoad is called on a child object
// the call basically bubbles up until the root object in the Scene
// and makes this entire root tree DontDestroyOnLoad
// so you might consider if you call this on a child object to first do
//gameObject.transform.SetParent(null);
UnityEngine.Object.DontDestroyOnLoad(gameObject);
}
else
{
// add a new temporal GameObject to the active scene
// therefore we needed to make sure before to set the
// SceneManager.activeScene correctly
var newGO = new GameObject();
// This moves the gameObject out of the DontdestroyOnLoad Scene
// back into the currently active scene
gameObject.transform.SetParent(newGO.transform, true);
// remove its parent and set it back to the root in the
// scene hierachy
gameObject.transform.SetParent(null, true);
// remove the temporal newGO GameObject
UnityEngine.Object.Destroy(newGO);
}
}
}
This is an Extension Method which allows you to simply call
someGameObject.SetDontDestroyOnLoad(boolvalue);
on any GameObject reference.
Then I changed your script to
public class MoveBall : MonoBehaviour
{
public static Vector2 mousePos = new Vector2();
// On mouse down enable DontDestroyOnLoad
private void OnMouseDown()
{
gameObject.SetDontDestroyOnLoad(true);
}
// Do your dragging part here
private void OnMouseDrag()
{
// NOTE: Your script didn't work for me
// in ScreenToWorldPoint you have to pass in a Vector3
// where the Z value equals the distance to the
// camera/display plane
mousePos = Camera.main.ScreenToWorldPoint(new Vector3(
Input.mousePosition.x,
Input.mousePosition.y,
transform.position.z)));
transform.position = mousePos;
}
// On mouse up disable DontDestroyOnLoad
private void OnMouseUp()
{
gameObject.SetDontDestroyOnLoad(false);
}
}
And in your StarCollision script you only have to exchange
SceneManager.LoadScene(1);
with
MySceneManager.LoadScene(2, this);
Demo
For a little demonstration I "faked" it using two simple scripts
This one in the Main scene
public class LoadFirstscene : MonoBehaviour
{
// Start is called before the first frame update
private void Start()
{
MySceneManager.LoadScene(1, this);
}
}
And this one in the other scenes
public class LoadNextScene : MonoBehaviour
{
[SerializeField] private int nexSceneIndex;
private void Update()
{
if (!Input.GetKeyDown(KeyCode.Space)) return;
MySceneManager.LoadScene(nexSceneIndex, this);
}
}
And have 3 Scenes:
Main: As mentioned contains
the MainCamera
a DirectionalLight
the LoadFirstScene
test: contains
a MoveBall "Sphere"
the LoadNextScene
test2: contains
a MoveBall "Cube"
the LoadNextScene
With the indexes matching the build settings (make sure Main is always at 0 ;) )
I can now switch between test and test2 using the Space key.
If I drag one of the objects meanwhile I can carry it on into the next scene (but only 1 at a time). I can even take it on again back to the first scene in order to have e.g. two sphere objects I can play with ;)
Hey I am trying to make a pause menu in my game. When escape is pressed pause game go to menu, but now I want to be able to press back in menu and resume my game. So far I can only pause game and cant press back. Also if i press Play in menu it starts at my tutorial scene and not the current scene. Is there a smart way to do this? Without resetting my game.
`using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
public class MenuScript : MonoBehaviour
{
// Use this for initialization
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
Time.timeScale = 1;
}
else
{
Time.timeScale = 0;
}
SceneManager.LoadScene("Menu");
}
}
}`
I get that this isn't straightforward problem since you seem new to Unity.
You got the idea correctly, changing the time scale will freeze all agents on the scene. HOWEVER, if you load a new scene you'll need to reload the game scene - losing any data you had (and that is not what you want). My advice is creating an overlay element (UI) on the game scene and just show/hide it. There are multiple tutorials online use this as an starting point. Let me know if you require more help.
code sample
// Update is called once per frame
void Update()
{
if (Input.GetKey(KeyCode.Escape))
{
if (Time.timeScale == 0)
{
Time.timeScale = 1;
pauseMenu.gameObject.setActive(true);
}
else
{
Time.timeScale = 0;
pauseMenu.gameObject.setActive(false);
}
}
}
You will need a reference to the pauseMenu game object attached on this script using the Unity editor.
Depending on what you need, this would work as well.
private bool _isPaused;
private void Update(){
if (Input.GetKeyUp(KeyCode.Escape))
{
_isPaused = !_isPaused;
if (_isPaused)
{
//Do Pause Logic here
}
else
{
//Do Unpause Logic Here
}
}
}