Object loses Inspector objects When Scene Changes

I’m pretty sure this is something that is solved by script but for the longest time now I have been unable to get an explanation of how to solve this issue.
Basically I have a game object that has items assigned to it in the inspector like a UI slide, and Audio source reference, and some UI Text references, now when I change scenes these items gets lost which for the scene change is is OK because in the 2nd scene they are not needed and would only be in the way. BUT when I return to my main scene where they are needed they are missing. So there must be a way to re attache them? I have searched all over the place for some explanation of this but no one seems to explain it properly.

Here is a video demo of what I’m talking about.

I didn’t encounter that yet, but you can check what scene was loaded in http://docs.unity3d.com/ScriptReference/MonoBehaviour.OnLevelWasLoaded.html and if it was original one, you set those variables to what you need them ‘back’ to. (back isn’t appropriate because old objects were destroyed and new ones were instantiated when scenes change).
To get variables either Find for each one or create another MonoBehaviour which only stores them within original scene(and gets destroyed when another scene loaded and loaded when this scene is loaded back) and get values from it. With this you can give that MonoBehaviour’s GameObject unique name and Find or GetComponent that one monobehaviour anytime. (Just be sure to keep that object always active).

Optimally, you shouldn’t even try to save links for objects that get destroyed between scenes. That’s a small design oversight you suffer from.

1 Like

Thanks Teravisor,
Sounds like the kind of thing I’m after but can you give a little more information on how I would go about doing that?
This is what the inspector looks like when I start of in scene one.
2469032--169984--Screen Shot 2016-01-17 at 2.41.08 PM.png
And this is what it looks like when I return to scene one.
2469194--169995--Screen Shot 2016-01-17 at 2.41.30 PM.png

I’d create small MonoBehaviour like

using UnityEngine;

public class Storage : MonoBehaviour
{
    public AudioSource source;
    public Text temperatureText;
    public GameObject lightSlider;
    public Slider slider;
}

I don’t know: Slider and Text are UnityEngine.UI elements? Then add in beginning using UnityEngine.UI;
Attach it to always-active GameObject for example called “DelayedSending” and fill it with same values as Sending. That object must be in scene, but must not persist between scenes like Sending.
Then in your Sending script I’d do

void OnLevelWasLoaded (int level)
{
    if(level == 0) //I don't know what number your base scene is, replace 0 with that. Or check something like Application.loadedLevelName
    {
        Storage stor = GameObject.Find("DelayedSending").GetComponent<Storage>();
//Variable names here are a guess. Replace them with how they are written in script.
        source = stor.source;
        temperatureText = stor.temperatureText;
        lightSlider = stor.lightSlider;
        slider = stor.slider;
    }
}

However the best of the best ways is to separate Sending script into two: one is persistant and contains all logic that is scene-independent and another is scene-dependant and gets destroyed when scene is unloaded. That requires re-writing Sending(and possibly scripts that use Sending or init), and I can’t help you with that. What I suggested above is more like just patch-on-the-knee(whoever reads code after you will be quite unhappy with those patches).

1 Like

I covered this in a tutorial once. I had two gui’s one for the game and one for the intro screen. I utilized DontDestroyOnLoad() to keep both of them. Since the game I was making didn’t have different interfaces than that it was easy. I did however, have to disable the Canvas for each. Since disabling the object had bad effects when I went to change scenes.

Thanks Teravisor,
I did make an empty game object and call it “DataHolder” which just has this in it.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class DataHolder : MonoBehaviour {

    //UI Text Reference
    public Text TemperatureText;
    public GameObject lightSlider;
    public Slider slider;
    //Slider lightSlider;
    public AudioSource source;


}

now the thing is, is that my “Sending” script on that “Init” game object sends and receives data from an Arduino, all of that is working fine, even when I return to the main scene the button inputs and outputs all work fine, and in the inspector I can see the data from the LDR and Temperature sensor are still being displayed in the inspector.
2469194--169995--Screen Shot 2016-01-17 at 2.41.30 PM.png
Like you can see here,
But now would I use the “DataHolder” script to somehow repopulate my “Init” game object with the Slider and UI text and AudioSource objects?
I’m fuzzy on how to do that.

Ahhh I think we are cooking with some real gas now my friend.

I placed this into my Sending script:

void OnLevelWasLoaded (int level)
    {
        if (level == 2) {
            DataHolder stor = GameObject.Find ("DataHolder").GetComponent<DataHolder> ();
            //Variable names here
            source = stor.source;
            TemperatureText = stor.TemperatureText;
            //lightSlider = stor.lightSlider;
            //Slider = stor.slider;
        }
    }

Just to test I had to comment out the two references to the slider because in the console it gave me these errors:

Assets/Scripts/Sending.cs(116,25): error CS0029: Cannot implicitly convert type UnityEngine.GameObject' to UnityEngine.UI.Slider’

and

Assets/Scripts/Sending.cs(117,25): error CS0131: The left-hand side of an assignment must be a variable, a property or an indexer

Now my UI text for the Temperature sensor is repopulated back into the inspector when I return to scene 1 or I should say 2 technically :wink: and is working as it should so we are almost there.

Got it working, just had to pull out the one that was referencing it as a game object, apparently in my “Sending” script I did not even need that defined as it was already referencing the UI canvas slider :wink:
Thanks Teravisor FINALY it took someone like you to explain properly what it was I needed to do. Now it’s working perfectly,
Now you referred to this as a patch but it seems to work fine.
Is there a more appropriate way one should approach this sort of problem?

If you replace all links and usages from init/Sender variables to use DataHolder variables instead and remove those variables from Sender, it will be easier to understand what you wanted to do here from code. It’s not mandatory, but if you will ever hire another programmer, he might encounter that ‘patch’ and get stuck or create a bug. That’s why I reference it as ‘patch’ - it’s a bit of code that works, but hard to understand for someone other than original coder.

1 Like

Ah so I would have to explain what I did, good tip to know thanks again Teravisor :smile:

Oh hey Teravisor,
I wonder if I can pick your brain on another slight issue, in my main scene 2 I have a couple of timer panels one is basically a trip timer so it counts up.
and the other is a count down timer that I use to activate devices or set off a clock alarm when it reaches 0, would I be able to use this method to enable my timer to resume from where it left off when I return to scene 2?
I just realized that is another data persistence issue I had been having.

Of course you can. For same way you can add float field to Sender or a new MonoBehaviour with just that float and drag it between scenes. On Update() you can make timer write to that value and OnLevelWasLoaded() you read that value.

1 Like

Sounds like a treat to try and figure out :wink:
So if this is my timer script how would I work with this?

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class CountdownTimerManager : MonoBehaviour {
   
    public AudioClip Alarm;
    float timer  = 3600;
    bool isFinishedTimer   = true;
    public UnityEngine.UI.Text displayText;
    public UnityEngine.UI.Text timeText;
   
    string minsDisplay;
    string secsDisplay;
   
    int mySeconds  = 0;
   
    private float oldTimer;

    //Device Toggle States
    bool dev01State = false;
    bool dev02State = false;
    bool dev03State = false;
    bool dev04State = false;
    bool dev05State = false;
    bool dev06State = false;
    bool dev07State = false;
    bool dev08State = false;
    bool dev09State = false;



    //Random Clips
    public AudioClip[] voices;

    public static CountdownTimerManager countDownManager;
   
    void Start(){
       
        oldTimer = timer;
    }
   
    void Update(){
       
        if (!isFinishedTimer) {
            timer -= Time.deltaTime;
        }
       
        CurrentTime();
    }


   
    void CurrentTime() {
        System.DateTime dt  = System.DateTime.Now;
        int h  = dt.Hour;
        int m  = dt.Minute;
        int s  = dt.Second;
       
        timeText.text = h + ":" + m + ":" + s;
       
        if(mySeconds != s)
        {
            mySeconds = s;
            //Timing();
            StartCoroutine(Timing());
        }
       
    }
   
    IEnumerator  Timing()
    {
        if (timer > 0) {
            //var minsDisplay : String = parseInt( timer / 60 ).ToString();
            minsDisplay = System.Convert.ToInt32( timer / 60 ).ToString();
           
            //var secsDisplay : String = parseInt( timer ).ToString();
            secsDisplay = System.Convert.ToInt32( timer ).ToString();
           
            if ( (timer - ( System.Convert.ToInt32(minsDisplay) * 60)) > 10 ) {
                secsDisplay = System.Convert.ToInt32( timer - ( System.Convert.ToInt32(minsDisplay) * 60) ).ToString();
            }
            else {
                secsDisplay = "0" + System.Convert.ToInt32( timer - ( System.Convert.ToInt32(minsDisplay) * 60) ).ToString();
            }
           
            //displayText.text = minsDisplay + " : " + secsDisplay;
        }
        //Timer Reaches End We Can Do Something Here
        else {
            timer += oldTimer;
            GetComponent<AudioSource>().PlayOneShot(Alarm);//Plays Alarm Sound
            isFinishedTimer = true;//Sets Inspector Value to true or false based on what is set here
            yield return new WaitForSeconds(5.8f);//Wait Time Setting
            //Do Something if Desired
           
            //if (GetComponent.<AudioSource>().isPlaying) return; // don't play a new sound while the last hasn't finished
            GetComponent<AudioSource>().clip = voices[Random.Range(0,voices.Length)];
            GetComponent<AudioSource>().Play();

           
            yield return new WaitForSeconds(0.8f);//Wait Time Setting
            //Do Something if Desired
            if (dev01State == true){
                Sending.sendRed2 (); //Scanner
                MessageCentreManager mcm = FindObjectOfType<MessageCentreManager>();
                mcm.GoScannerMessage();
            }
            if (dev02State == true){
                Sending.sendRed (); //Oil Slick
                MessageCentreManager mcm = FindObjectOfType<MessageCentreManager>();
                mcm.GoOilSlickMessage();
            }
            if (dev03State == true){
                Sending.sendGreen2 (); //Fog Lights
            }
            if (dev04State == true){
                Sending.sendGreen3 (); //Head Lights
            }
            if (dev05State == true){
                Sending.sendBlue (); //Surveillance Mode
                MessageCentreManager mcm = FindObjectOfType<MessageCentreManager>();
                mcm.GoSurveillanceModeMessage();
                SurveillanceModeManager smm = FindObjectOfType<SurveillanceModeManager>();
                //smm.TogglePlay();
                smm.PlayLEDLightsAnimation();
                SurveillanceModeSounder sms = FindObjectOfType<SurveillanceModeSounder>();
                sms.TogglePlay();

            }
            if (dev06State == true){
                Sending.sendPulse2 (); //Grappling Hook
            }
            if (dev07State == true){
                Sending.sendPulse1 (); //Rear Hatch
            }
            if (dev08State == true){
                Sending.sendYellow (); //Movie Player
                MessageCentreManager mcm = FindObjectOfType<MessageCentreManager>();
                mcm.GoMoviePlayerMessage();
            }
            if (dev09State == true){
                Sending.sendGreen (); //Auto Phone
                MessageCentreManager mcm = FindObjectOfType<MessageCentreManager>();
                mcm.GoAutoPhoneMessage();
            }

            Debug.Log ("Timer Ended");
        }
        displayText.text = minsDisplay + " : " + secsDisplay;
    }
   
   
    #region Timer Buttons Settings
    //Timer Stop Button
    public void GoTimerStop()
    {
        isFinishedTimer = true;
    }
   
    //Timer Start Button
    public void GoTimerStart()
    {
        isFinishedTimer = false;
    }
   
    //Timer Settings
    public void GoTimerSetting60Sec()
    {
        timer = 60;
    }
   
    public void GoTimerSetting5Min()
    {
        timer = 300;
    }
   
    public void GoTimerSetting10Min()
    {
        timer = 600;
    }
   
    public void GoTimerSetting20Min()
    {
        timer = 1200;
    }
   
    public void GoTimerSetting30Min()
    {
        timer = 1800;
    }
   
    public void GoTimerSetting40Min()
    {
        timer = 2400;
    }
   
    public void GoTimerSetting50Min()
    {
        timer = 3000;
    }
   
    public void GoTimerSetting1Hr()
    {
        timer = 3600;
    }
   
    public void GoTimerSetting1Point5Hr()
    {
        timer = 5400;
    }
   
    public void GoTimerSetting2Hr()
    {
        timer = 7200;
    }
   
    public void GoTimerSetting2Point5Hr()
    {
        timer = 9000;
    }
   
    public void GoTimerSetting3Hr()
    {
        timer = 10800;
    }
    #endregion

    #region Device Toggle Settings
    //Device One Toggle
    public void ToogleDeviceOne() {
        //Device Set to go off with timer Here
        dev01State = !dev01State;
    }

    //Device Two Toggle
    public void ToogleDeviceTwo() {
        //Device Set to go off with timer Here
        dev02State = !dev02State;
    }

    //Device Three Toggle
    public void ToogleDeviceThree() {
        //Device Set to go off with timer Here
        dev03State = !dev03State;
    }

    //Device Four Toggle
    public void ToogleDeviceFour() {
        //Device Set to go off with timer Here
        dev04State = !dev04State;
    }

    //Device Five Toggle
    public void ToogleDeviceFive() {
        //Device Set to go off with timer Here
        dev05State = !dev05State;
    }

    //Device Six Toggle
    public void ToogleDeviceSix() {
        //Device Set to go off with timer Here
        dev06State = !dev06State;
    }

    //Device Seven Toggle
    public void ToogleDeviceSeven() {
        //Device Set to go off with timer Here
        dev07State = !dev07State;
    }

    //Device Eight Toggle
    public void ToogleDeviceEight() {
        //Device Set to go off with timer Here
        dev08State = !dev08State;
    }

    //Device Nine Toggle
    public void ToogleDeviceNine() {
        //Device Set to go off with timer Here
        dev09State = !dev09State;
    }
    #endregion
}

add

public float timer;
public bool isFinishedTimer;

to Sender.
replace

if(!isFinishedTimer){
    timer -= Time.deltaTime;
}

to

if(!isFinishedTimer){
    timer -= Time.deltaTime;
    GameObject.Find("init").GetComponent<Sender>().timer = timer;
}
GameObject.Find("init").GetComponent<Sender>().isFinishedTimer = isFinishedTimer;

Then add

void OnLevelWasLoaded()
{
    timer = GameObject.Find("init").GetComponent<Sender>().timer;
    isFinishedTimer = GameObject.Find("init").GetComponent<Sender>().isFinishedTimer;
}

OR
add

timer = GameObject.Find("init").GetComponent<Sender>().timer;
isFinishedTimer = GameObject.Find("init").GetComponent<Sender>().isFinishedTimer;

to Start(). In your case this should behave same way (timer doesn’t persist between scenes so it gets Start() call when scene is loaded).

That should remember timer’s time and has it finished or not. If you need to remember all those variables inside timer… You can just make it persistant between scenes. Or write something like I wrote for each variable. Or stop switching between scenes at all. Just disable one canvas and enable another. No scene load, no problems. I’m not going to write all those variables manually, I’m not maso.

Oh, and GameObject.Find(“init”.GetComponent() is quite slow if you decide to use it a lot (more than 10000 times per frame I guess) so then you should save its value in variable somewhere.

1 Like

Awesome Teravisor
I think that worked but I think when the scene changes the timer stops counting but resumes when I go back to my original scene?

Yes, it stops counting.

1 Like

lol was it supposed to do that?
Is there a way to make it continue across scene changes?
I just have the one scene change which is for a music player that I figure probably needs it’s own scene if people load it up with a boat load of songs lol

Well, you could try making that timer itself persistant, but I see you have AudioClip and UI.Text that are not persistant. You’ll have to save those links and repeat all problems above…

Um… I have to ask one thing: Music player doesn’t load all songs at once. It just lists them then streams from disc.
Do you really think you can have enough songs on your discs to kill application from amount of buttons they create? And then those maximum of several megabytes of RAM you free up from another scene will save you? I see preemptive optimizations here. And attempts to cut uncuttable application logic :wink:

Basically what I want to say… Stop doing stupid things and just merge those scenes. I don’t know who you must be to have more than 10000000 songs (everything above will require you to start streaming song names… What you created will crash anyway unless it’s 64 bit PC)

Or if you don’t list all songs as buttons, there’s no problem at all.

1 Like

Not sure what you mean by the songs being buttons. As far as I know the music player is just streaming songs from a folder. The music play could really use a loading bar or some UI text that displays the percentage of loading though.

So you would suggest making the music player part on my main scene too?

About the timer, should I handle it the same as I do the “init” object with the “Sending” script and return a reference to it’s references?

On my timer script I also have toggles that set certain devices. I set up a script called “ToggleSettings” and made it like this with the don’t destroy on it, It does carry the toggle settings back and forth between scene changes, but I’m not sure how to reference the information back to my main toggle setting on that timer script.
So much of this is still pretty new to me. But I’m getting the hang of a lot of it.

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

public class ToggleSettings : MonoBehaviour {

    public static ToggleSettings toggleSettings;


    //Device Toggle States
    public bool dev01State = false; //ScannerOn
    public bool dev02State = false; //OilSlickOn
    public bool dev03State = false; //FogLightsOn
    public bool dev04State = false; //HeadLightsOn
    public bool dev05State = false; //SurvModeOn
    public bool dev06State = false; //GrapplingHookOn
    public bool dev07State = false; //RearHatchOn
    public bool dev08State = false; //MoviePlayerOn
    public bool dev09State = false; //AutoPhoneOn


    void Awake () {
        if (toggleSettings == null) {
            DontDestroyOnLoad (gameObject);
            toggleSettings = this;
        } else if (toggleSettings != this) {
            Destroy (gameObject);
        }
    }

    //Device One Toggle
    public void ToogleDeviceOne() {
        //Device Set to go off with timer Here
        dev01State = !dev01State;
    }
}

I mean… Do you really sure that

Can really cause anything bad to your program if you have only one scene? What exactly can go wrong? I just don’t see what can go wrong. So why several scenes?

You can do it like that. Or you can stop for a bit and think: why does timer need to report to UI? Why don’t you make it reverse: UI must take from timer? Like script on UI gets values from timer and changes UI accordingly while timer not knowing anything about UI at all. That’s the exact design problem you’ve been experiencing from the beginning.