Move/Transfer GameObject to another scene - c#

We tryed different ways to move UI-object to another scene, but we failed. Over object is in Canvas.
Method 1: we used LoadLevelAdditive, but there moved all objects from first scene without over Canvas with it's elements.
Method 2: we used DontDestroyOnLoad. We need to change our element on Canvas. DDOL save last position on the scene, but we can't change object at all.
Can you get some advice, please?
Thanks.

Don't use Application.LoadLevelXXX. These are deprecated functions. If you are using old version of Unity, please update it otherwise, you may not be able to use the solution below.
First, load scene with SceneManager.LoadSceneAsync. Set allowSceneActivation to false so that the scene won't activate automatically after loading.
The main solution to your problem is the SceneManager.MoveGameObjectToScene function which is used to transfer GameObject from one scene to another. Call that after loading the scene then call SceneManager.SetActiveScene to activate the scene. Below is an example of that.
public GameObject UIRootObject;
private AsyncOperation sceneAsync;
void Start()
{
StartCoroutine(loadScene(2));
}
IEnumerator loadScene(int index)
{
AsyncOperation scene = SceneManager.LoadSceneAsync(index, LoadSceneMode.Additive);
scene.allowSceneActivation = false;
sceneAsync = scene;
//Wait until we are done loading the scene
while (scene.progress < 0.9f)
{
Debug.Log("Loading scene " + " [][] Progress: " + scene.progress);
yield return null;
}
OnFinishedLoadingAllScene();
}
void enableScene(int index)
{
//Activate the Scene
sceneAsync.allowSceneActivation = true;
Scene sceneToLoad = SceneManager.GetSceneByBuildIndex(index);
if (sceneToLoad.IsValid())
{
Debug.Log("Scene is Valid");
SceneManager.MoveGameObjectToScene(UIRootObject, sceneToLoad);
SceneManager.SetActiveScene(sceneToLoad);
}
}
void OnFinishedLoadingAllScene()
{
Debug.Log("Done Loading Scene");
enableScene(2);
Debug.Log("Scene Activated!");
}

Related

Unity coroutine stopping for no reason

I'm making my first 2D topdown shooter game. I'm working on the loading part of the game. I have this functioning loading screen function (a method of my GameManager class) I made. I tried swapping between the main title scene and the first level a few times to test it. After loading the first level, then the title screen, when I try loading the first level back again the load screen is stuck.
The whole thing is working on a coroutine, and after some debugging I figured out the problem was that this coroutine stopped executing in a certain part of the code for some reason.
public static bool isLoadingScene { get; private set; } = false;
public void LoadScene(int sceneIndex)
{
// If another scene is already loading, abort
if (isLoadingScene)
{
Debug.LogWarning($"Attempting to load scene {sceneIndex} while another scene is already loading, aborting");
return;
}
isLoadingScene = true;
var sceneLoader = FindObjectOfType<SceneLoader>().gameObject;
if (sceneLoader != null)
{
// Make the loading screen appear
var animator = sceneLoader.GetComponent<Animator>();
animator.SetTrigger("appear");
// Make sure the SceneLoader object is maintained until the next scene
// in order to not make the animation stop
DontDestroyOnLoad(sceneLoader);
}
else // If a SceneLoader is not found in the current scene, throw an error
{
Debug.LogError($"SceneLoader could not be found in {SceneManager.GetActiveScene().name}");
}
// Unload active scene and load new one
StartCoroutine(LoadScene_(sceneIndex, sceneLoader));
}
IEnumerator LoadScene_(int sceneIndex, GameObject sceneLoader)
{
float t = Time.time;
// Start loading the new scene, but don't activate it yet
AsyncOperation load = SceneManager.LoadSceneAsync(sceneIndex, LoadSceneMode.Additive);
load.allowSceneActivation = false;
// Wait until the loading is finished, but also give the loading screen enough
// time to appear
while (load.progress < 0.9f || Time.time <= (t + LoadingScreen_appearTime))
{
yield return null;
}
// Activate loaded scene
load.allowSceneActivation = true;
// Unload old scene
AsyncOperation unload = SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
// Wait until it's finished unloading
while (!unload.isDone) yield return null;
// --- THE COROUTINE STOPS HERE DURING THE 3RD LOADING --- //
// Make the loading screen disappear
sceneLoader.GetComponent<Animator>().SetTrigger("disappear");
// Wait until the disappearing animation is over, then destroy the SceneLoader object
// which I set to DontDestroyOnLoad before
yield return new WaitForSeconds(LoadingScreen_disappearTime);
Destroy(sceneLoader);
isLoadingScene = false;
}
Only during the 3rd loading, right after the while (!unload.isDone) yield return null; line the coroutine just stops executing (I know it because I inserted a Debug.Log inside the while loop and it got called, but I inserted one right after and it did NOT get called).
Any suggestions?
I figured out the problem by myself. The GameManager itself was programmed as a singleton, and since there was a GameManager in both scenes that was messing things up. Had to move the GameManager to a separate scene loaded additively.

Coroutine doesn't work in Unity

I need to make a screenshot in Unity. I did it the next way:
public void Capture(){
StartCoroutine(CaptureScreenshot());
}
private IEnumerator CaptureScreenshot(){
GameObject canvas = GameObject.Find("Canvas");
canvas.SetActive(false); // hide all buttons
yield return new WaitForEndOfFrame();
string timestamp = DateTime.Now.ToString("dd_MMMM_hh_mm_ss_tt");
Application.CaptureScreenshot("screenshot" + timestamp + ".png");
Debug.Log("Screenshot was captured.");
yield return new WaitForEndOfFrame();
canvas.SetActive(true); // restore all buttons
yield return null;
}
When I invoke Capture(), canvas hides but neither screenshot takes nor text logs.
What is wrong with this code? Thanks for response.
Because your script is running on a object in the canvas when you do canvas.SetActive(false); that also disables the script, preventing the coroutine from continuing from yield return new WaitForEndOfFrame();.
Move the script to a game object that does not live on the canvas and it should solve the problem.
You could also do this.
foreach(Transform t in GameObject.Find("Canvas").transform)
{
if(transform.gameObject.name != t.gameObject.name)
t.gameObject.SetActive(false);
}
Be sure to re-enable them with
foreach(Transform t in GameObject.Find("Canvas").transform)
{
t.gameObject.SetActive(true);
}

Changing text programmatically to show score on game over screen in unity

I've been working on a simple 2D game in unity and it just has three scenes, the start scene, the game scene, and the game over scene. I want to display the score from the game in the game over screen. I created a score manager game object in the game scene that uses the DontDestroyOnLoad() function to carry it over into the game over screen and I gave it access to the score which is managed by the game manager. I've been debugging my code and the score is translated over into the score manager and is maintained when the game over screen loads, but for some reason it won't let me update the score text object. Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ScoreManager : MonoBehaviour {
public static ScoreManager Instance;
private GameController gameController;
private int scoreInstance;
private Text scoreText;
// When scene is first loaded
void Awake() {
this.InstantiateController();
}
// Use this for initialization
void Start () {
GameObject gameControllerObject = GameObject.FindWithTag("GameController");
if (gameControllerObject != null)
{
gameController = gameControllerObject.GetComponent<GameController>();
}
GameObject scoreTextObject = GameObject.FindWithTag("ScoreText");
if (scoreTextObject != null)
{
scoreText = scoreTextObject.GetComponent<Text>();
}
scoreInstance = 0;
scoreText.text = "";
}
// Update is called once per frame
void Update () {
scoreInstance = gameController.score;
Debug.Log("Score: " + scoreInstance.ToString());
scoreText.text = scoreInstance.ToString();
}
private void InstantiateController ()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this);
}
else if (this != Instance)
{
Destroy(this.gameObject);
}
}
}
So I tried to programmatically gather the "score text" ui component in the start function because I figured I can't just make it public and drag in the text component because the score manager is actually in a different scene than the score text object. I also tried adding this whole bit of code to gather the text component into the update function so that it can do that when the score manager is actually a part of game over screen. Nothing seems to work and I have no idea why. Can anybody please help me with this? Also I keep getting a "NullReferenceException: Object reference not set to an instance of an object" error. Thanks in advance for any help.
Unity Start function is only called the first time the script is enabled, i.e. not every time the scene changes for a DontDestroyOnLoad object.
So if you need to wire up some changes after a scene change, you need to either detect the scene change, or have an object that starts in that scene trigger the code you want to run.
Having another object on the new scene trigger things is easy and pretty fool-proof, but there's a builtin function you can add to your other objects:
void OnLevelWasLoaded(int currentLevel)
{
}
This will be called on level changes, and give you the level's number (not name sadly). However, the above is deprecated and they want you to use Unity's SceneManager, so the proper way to set this up is now:
Unity 5 OnLevelWasLoaded?
Start()
{
SceneManager.sceneLoaded += this.OnLoadCallback;
}
void OnLoadCallback(Scene scene, LoadSceneMode sceneMode)
{
// you can query the name of the loaded scene here
}

Timer keeps continuously loading a scene instead of just once

I wrote a simple timer script, after the timer reaches 0 it loads a new scene. But it keeps continuously loading the scene instead of once not allowing the scene it loads to be played. I just need it to load the scene once.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using UnityEngine.SceneManagement;
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
public void ChangeScene(int changeTheScene)
{
SceneManager.LoadScene(changeTheScene);
}
void Update()
{
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
SceneManager.LoadScene(1);
}
}
}
NOTE: your code as described won't cause the issue you describe. I see two possibilities:
your newly loaded scene has the same component in it, which repeatedly loads the scene
you are actually loading the scene additively so this component continues to run after the load.
Assuming the latter, if you follow your code through, you'll see that your if statement has a condition that will always be true once the timelimit is reached:
Every frame you subtract a number from timelimit. Then, if timelimit is now less than zero, set timelimit to zero and load the scene.
If you've set timelimit to zero in the previous frame, and then subtract a number, it will always be less than zero: you'll always load the scene again on each subsequent frame.
Try instead using a boolean variable to track whether you've loaded the scene or not. Or alternatively, destroy the component as soon as you load the scene, so that your code stops running.
If the problem is actually that you have this component in your new scene, too... consider removing it! :-)
EDIT:
Try instead using a boolean variable to track whether you've loaded
the scene or not.
public class Timer : MonoBehaviour
{
public float timelimit;
public Text text;
static bool loadedScene = false;
public void ChangeScene(int changeTheScene)
{
//SceneManager.LoadScene(changeTheScene);
}
void Update()
{
//Exit if we have already loaded scene
if (loadedScene)
{
//Destroy Timer Text
Destroy(text.gameObject);
//Destroy this Timer GameObject and Script
Destroy(gameObject);
return;
}
timelimit -= Time.deltaTime;
text.text = "TimerText:" + Mathf.Round(timelimit);
if (timelimit < 0)
{
timelimit = 0;
loadedScene = true; //We have loaded Scene so mark it true
SceneManager.LoadScene(1);
}
}
}
You're timelimit might not be initialized. Did you make sure it isn't set to 0 in the Unity Editor?

can't use WaitForSeconds with OnMouseDown

I used OnMouseDown() to deactivate an object but i want the object to activate again in a few seconds. I have used WaitForSeconds() for other things but this time it just doesn't work
this is what i could gather by researching (the deactivating part works fine):
void Start()
{
StartCoroutine(wait());
}
void Update(){}
void OnMouseDown()
{
gameObject.SetActive(false);
}
IEnumarator wait()
{
yield return new WaitForSeconds(3);
gameObject.SetActive(true);
}
There are too many reasons your code isn't work right. You are doing it backwards. Your coroutine starts immediately when your program starts because wait() is called from the Start() function. When it starts, it pauses for 3 seconds and set your GameObject to SetActive(true);
If your GameObject is already visible to the screen, your code wont do anything because SetActive(true) will be called even when it is visible. If you fail to press/click on the screen before that 3 seconds, you wont be able to see SetActive(true); because your coroutine code would have finished running by that time.
Also, if you disable a GameObject, the coroutine attached to it will stop. The solution is to create a reference of the GameObject you want to disable then use that reference to disable and enable it from another script without problems.
Since provided a code, I fixed/re-wrote it for you. I replaced the OnMouseDown fucntion with something more robust.
All you have to do is create an empty GameObject. Attach this script to that empty GameObject. Then drag and drop that GameObject you want to disable and enable to the this "Game Object To Disable" slot in this script, from the Editor.
Do NOT attach this script to that GameObject you want to disable and enable.
Tested with cube and it worked.
using UnityEngine;
using System.Collections;
public class ALITEST: MonoBehaviour
{
//Reference to the GameObject you want to Disable/Enable
//Drag the Object you want to disable here(From the Editor)
public GameObject gameObjectToDisable;
void Start()
{
}
void Update()
{
//Keep checking if mouse is pressed
checkMouseClick();
}
//Code that checks when the mouse is pressed down(Replaces OnMouseDown function)
void checkMouseClick()
{
//Check if mouse button is pressed
if (Input.GetMouseButtonDown(0))
{
RaycastHit hitInfo = new RaycastHit();
if (Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hitInfo))
{
//Check if the object clicked is that object
if (hitInfo.collider.gameObject == gameObjectToDisable)
{
Debug.Log("Cube hit");
StartCoroutine(wait()); //Call the function to Enable/Disable stuff
}
}
}
}
//This value is used to make sure that the coroutine is not called again while is it already running(fixes many bugs too)
private bool isRunning = false;
IEnumerator wait(float secondsToWait = 3)
{
//Exit coroutine while it is already running
if (isRunning)
{
yield break; //Exit
}
isRunning = true;
//Exit coroutine if gameObjectToDisable is not assigned/null
if (gameObjectToDisable == null)
{
Debug.Log("GAME OBJECT NOT ATTACHED");
isRunning = false;
yield break; //Exit
}
gameObjectToDisable.SetActive(false);
//Wait for x amount of Seconds
yield return new WaitForSeconds(secondsToWait);
//Exit coroutine if gameObjectToDisable is not assigned/null
if (gameObjectToDisable == null)
{
Debug.Log("GAME OBJECT NOT ATTACHED");
isRunning = false;
yield break; //Exit
}
gameObjectToDisable.SetActive(true);
isRunning = false;
}
}
Because you're calling StartCoroutine() in Start(), your coroutine will resume 3 seconds after the component is started. You want to call StartCoroutine(wait()) in OnMouseDown() so the GameObject will become active after that.
You cannot deactivate GameObject and continue Coroutine on it. If you deactivate GameObject that has runing Coroutine it will be stoped.
So if you want to do it right, you need Coroutine runing on other GameObject and from there actvating this GameObject.
If you need more help, ask.

Categories

Resources