Search Unity

Accessing an instantiated script has strange behaviour.

Discussion in 'Scripting' started by Deridealized, Feb 27, 2020.

  1. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Hi All,

    Thanks for reading.

    I instantiate the player at Run-time. Everything works like a charm, except my UI script on a separate game object does not update correctly.

    I have public references to the prefab and a GetComponent for the playerController script.
    ^^ - I have tried both assigning in the inspector and assigning through code for both the player GameObject and the PlayerController component.

    The variables I need access to are:
    currentDrift and currentWeapon.

    The player drifts and fires fine, but the UI remains unchanged.

    Through Debug.Log I have ascertained that the currentDrift is always = 1 (initialised as such) and currentWeapon is always " " again, initialised as such.

    The game throws absolutely no errors. In the attached image you can see that the currentDrift is working (also knowable as the functionality is fine), but the UI is accessing it as just 1.

    Image shows selected clone's variable and the playerController.GetDrift() with Debug.Log(currentDrift) in the method.

    The UI worked fine before I decided to instantiate the player at run-time.

    2 days I've been working on this! It's driving me up the wall as I cannot for the life of me work this out.

    Any help would be highly appreciated.
    Thanks
     

    Attached Files:

    Last edited: Feb 27, 2020
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    It would be more useful to see some actual relevant code (use code formatting please! See the first post in the forum), as well as a small snippet about what each part does, how you modified it when you started instantiating the player, etc.
     
  3. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Hi Kurt, thanks for replying. Ok, I'll give this a shot:

    How the player is instantiated inside the RaceManager script.

    Code (CSharp):
    1. public GameObject player1;
    2. public PlayerController playerController;
    3.  
    4. //InitialiseRace() - Inside Awake, also called when race is reset.
    5. if(player1 != null && firstLoad)
    6.         {
    7.             player1 = Instantiate(player1, gridPos0.position, Quaternion.Euler(0, 180, 0));
    8.             playerController = player1.GetComponent<PlayerController>();
    9.        
    10.             firstLoad = false;
    11.         } else if(player1 != null && !firstLoad)
    12.         {
    13.             player1.transform.position = gridPos0.position;
    14.             player1.transform.rotation = Quaternion.Euler(0, 180, 0);
    15.             playerController.ResetForRaceStart();            
    16.         }
    17.  
    18. public GameObject GetPlayerInfo()
    19.     {
    20.         return player1;
    21.     }
    22.  
    23.    
    How the UIManager is trying to access it: (I have also tried assigning these manually in the inspector with the same results).

    Code (CSharp):
    1. public GameObject player1;
    2. public PlayerController playerController;
    3.  
    4. //Called in Awake
    5. void GetInitialReferences()
    6.     {  
    7.         raceManagerScript = raceManager.GetComponent<RaceManager>();
    8.         player1 = raceManagerScript.GetPlayerInfo();
    9.         playerController = player1.GetComponent<PlayerController>();
    10.         raceOverText = raceOverUI.GetComponentInChildren<TextMeshProUGUI>(true);
    11.     }
    Testing method that uses it inside UIManager:

    Code (CSharp):
    1.  
    2. //In Update
    3. if (Input.GetKey(KeyCode.Space))
    4.         {
    5.             playerController.Test();
    6.         }
    Inside the PlayerController:
    Code (CSharp):
    1. public void Test()
    2.     {
    3.         Debug.Log("TESTING");
    4.         Debug.Log(currentDrift);
    5.     }
    All values done through the above Test method return the initialised, non-current value.

    I'm hoping that's enough, anything else please ask.
    Thanks
     
    Last edited: Feb 28, 2020
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    First of all, try and separate out the dual usage you have for
    player1
    . It would appear you are using it to both drag-in the Prefab reference, as well as to store the reference to the cloned object.

    This would help isolate if your GetInitialReference() call is actually snapshotting the Prefab object before instantiation happens, then a fresh instantiated one is made, but the UI continues to call .Test() on the Prefab reference.
     
  5. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Could you please confirm what you mean by separating out the dual usage? I've removed the public access in the UIManager script so it should only be collecting from the RaceManager instantiation rather than assignment in the inspector, is this what you meant? - it doesn't work if so.
    Thanks
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    UIManager calls GetPlayerInfo(). If that is happening BEFORE instantiation of the player, UIManager is getting the prefab, not your Instantiated player, because you are recycling that variable.

    Instead, if you have two GameObjects, one for the prefab and one for the instance, if this happens you will get a null reference in the UIManager, which then you can simply elect to wait until the next frame to get the real player.

    You can also put Debug.Log() calls inside the GetPlayerInfo() and beside the Instantiate and see which hits first.
     
    Deridealized likes this.
  7. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Ok, so the Debug.Log showed that the script is being searched for AFTER instantiation is called.

    I modified the RaceManager script so there is now player1Inst and changed GetPlayerInfo to return player1Inst.

    Code (CSharp):
    1.  
    2. public GameObject player1;
    3. GameObject player1Inst;
    4.  
    5. if(player1 != null && firstLoad)
    6.         {
    7.             Debug.Log("Instantiated");
    8.             player1Inst = Instantiate(player1, gridPos0.position, Quaternion.Euler(0, 180, 0));
    9.             playerController = player1Inst.GetComponent<PlayerController>();
    10.          
    11.             firstLoad = false;
    12.         }
    However, this now pulls up a Null Exception on the UIManager script at the below playerController = player1.etc , Line 5.

    Code (CSharp):
    1. void GetInitialReferences()
    2.     {    
    3.         raceManagerScript = raceManager.GetComponent<RaceManager>();
    4.         player1 = raceManagerScript.GetPlayerInfo();
    5.         playerController = player1.GetComponent<PlayerController>();
    6.         raceOverText = raceOverUI.GetComponentInChildren<TextMeshProUGUI>(true);
    7.     }
    At least there is now a showing as to what's not getting found, although with the debug's before changing to 2 GameObjects the search was conducted after instantiation, so, what's changed that?
     
  8. WallaceT_MFM

    WallaceT_MFM

    Joined:
    Sep 25, 2017
    Posts:
    394
    Are you sure this part of the logic is correct? You check if player1 isn't null, and if it isn't, you instantiate a new one.
    EDIT: Oh wait I see what you're doing, instancing a player prefab?
    Code (csharp):
    1. //Called in Awake
    2. void GetInitialReferences()
    3.     {
    4.         raceManagerScript = raceManager.GetComponent<RaceManager>();
    5.         player1 = raceManagerScript.GetPlayerInfo();
    6.         playerController = player1.GetComponent<PlayerController>();
    7.         raceOverText = raceOverUI.GetComponentInChildren<TextMeshProUGUI>(true);
    8.     }
    You could try calling this in Start() instead. Other objects aren't guaranteed to exist in awake, IIRC.
     
    Deridealized likes this.
  9. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Yea I'm sure, Debug.Log shows "Instantiated" (as per my last comment's updated code).

    - player1 should not be null as it is assigned in the inspector.

    edit
    sorry, hadn't refreshed to see your edit before posting this
     
    Last edited: Feb 28, 2020
  10. Deridealized

    Deridealized

    Joined:
    Jan 17, 2017
    Posts:
    97
    Oh my, that's it!

    Moving initial references to Start combined with separating out the 2 GameObjects has worked.

    I can't thank the 2 of you enough!
     
    Kurt-Dekker likes this.
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,745
    You're very welcome of course. Timing can be tricky with multiple interoperating scripts... Debug.Log() is often your best friend, and another one of my favorite pieces of reference for timing and order of operations is this:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
    Deridealized likes this.
  12. WallaceT_MFM

    WallaceT_MFM

    Joined:
    Sep 25, 2017
    Posts:
    394
    As a rule of thumb, I make my scripts do any self-initialization in Awake() and only gather references to other scripts in Start(). I find that helps avoid order of execution errors in most cases.
     
    Deridealized and Kurt-Dekker like this.