Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

How to change the players start location to where they left the scene.

Discussion in 'Scripting' started by RobbieNI, Mar 8, 2019.

  1. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Hi all,

    Any help would be massive.

    Basically, I want the player to be able to traverse back though levels they have already visited, so this means I want to change the player's start location to where they exited this level if they have already been there before, for example if you left your house you would want to start back at the front door, not in the bathroom. I tried using if statements where a bool would be set on exit of the level however I couldn't get it to call when I loaded the scene again.

    Does anyone have any advice?

    Thanks,

    Robbie.
     
  2. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,151
    You're going to have to save out a value. If you need it between game loads, save it to some sort of file. If you need it just for the session, a static variable might be enough or a singleton file.
     
    Joe-Censored likes this.
  3. Joe-Censored

    Joe-Censored

    Joined:
    Mar 26, 2013
    Posts:
    11,847
    Use a static class or a singleton set to DontDestroyOnLoad for saving values between scene loads. If you need to save between entire game sessions, write out to a file or write to PlayerPrefs.
     
  4. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    How would I call those values each time that scene is loaded? I'm currently using a persistent (don't destroy on load) level manager which is setting the values correctly however it never seems to call them. I tried the OnLevelLoad method but I had no luck.
     
  5. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Use Start. Add a script to the player GameObject in each scene that checks if there's a previously-saved position. Example:

    SavedPositionManager.cs
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections.Generic;
    3.  
    4. public static class SavedPositionManager // Static class to remember player positions per scene.
    5. {
    6.     public static Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();
    7. }
    SaveAndRestorePosition.cs
    Code (csharp):
    1. using UnityEngine;
    2. using UnityEngine.SceneManagement;
    3.  
    4. public class SaveAndRestorePosition : MonoBehaviour
    5. {
    6.     void Start() // Check if we've saved a position for this scene; if so, go there.
    7.     {
    8.         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
    9.         if (SavedPositionManager.savedPositions.ContainsKey(sceneIndex))
    10.         {
    11.             transform.position = SavedPositionManager.savedPositions[sceneIndex];
    12.         }
    13.     }
    14.  
    15.     void OnDestroy() // Unloading scene, so save position.
    16.     {
    17.         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
    18.         SavedPositionManager.savedPositions[sceneIndex] = transform.position;
    19.     }
    20. }
    You might also want to save the player's rotation.

    This doesn't save the info to a file or PlayerPrefs, but you could add that.
     
  6. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Hi Tony,

    Thanks for the reply - I tried to implement those scripts however I am getting the following error:
    "'SavedPositionManager' is missing the class attribute 'ExtensionOfNativeClass'"
    Any ideas on this?
     
  7. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Sorry, I didn't specify which script to add to the scene. Add SaveAndRestorePosition to your player GameObject. Don't add SavedPositionManager to anything; it's not a MonoBehaviour. If that doesn't fix it, something else in your project may be conflicting.
     
  8. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Should've realised that haha - been a long day. That fixed the error however I'm still not able to get the player to change position. I noticed that it saves the position OnDestroy, however my player GO is a child of a GameManager (which is don't destroy on load) I had to do it like this in for my saving and loading data system to work. I was wondering if this is why the positions aren't being saved?

    Thanks again.
     
  9. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    In that case you'll need to add some code that gets run just before changing levels. Maybe put it in your GameManager. For example, instead of the scripts in my previous reply, you might add something like this:

    Code (csharp):
    1. public class GameManager : MonoBehaviour
    2. {
    3.     Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();
    4.  
    5.     void Awake()
    6.     {
    7.         SceneManager.sceneLoaded += OnSceneLoaded;
    8.     }
    9.  
    10.     public void LoadScene(string sceneName)
    11.     {
    12.         // Save player position before loading scene:
    13.         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
    14.         savedPositions[sceneIndex] = FindObjectWithTag("Player").position;
    15.         SceneManager.LoadScene(sceneName);
    16.     }
    17.  
    18.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    19.     {
    20.         // After loading scene, check if we need to move player to previously-saved position:
    21.         if (savedPositions.ContainsKey(scene.buildIndex))
    22.         {
    23.             FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    24.         }
    25.     }
    26.     ...
     
    Last edited: Mar 10, 2019
  10. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Sorry again, but each time I load a level, as my player is persistent, it's just keeping the same position each time, I implemented that code into my GameManager however it doesn't seem to ever be loading.
     
  11. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    I should've mentioned: Use GameManager.LoadScene instead of SceneManager.LoadScene. GameManager.LoadScene is like a wrapper that first saves the player's position in the old scene before loading the new scene. I also just fixed a copy-paste error in GameManager.LoadScene.
     
  12. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Where am I calling GameManager.LoadScene?
     
  13. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Wherever you change scenes.

    If you can't do that for some reason, you'll need to add a script to a non-DontDestroyOnLoad GameObject that saves the player's position in OnDestroy.
     
  14. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Ahhhhhhhh I think that fixed it!

    It seems to work as expected but I was just wondering if you could verify that this is how the scripts should look. I'm always trying to improve my code so your feedback would be great!

    GameManager (Calling it LevelManager.cs at the moment):

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using UnityEngine.AI;
    6.  
    7. [System.Serializable]
    8. public class LevelManager : MonoBehaviour {
    9.  
    10.     public string activeSceneName;
    11.     public static LevelManager instance = null;
    12.     public GameObject player;
    13.  
    14.     Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();
    15.  
    16.     void Awake()
    17.     {
    18.         if (instance == null)
    19.             instance = this;
    20.         else if (instance != this)
    21.             Destroy(gameObject);
    22.  
    23.         DontDestroyOnLoad(gameObject);
    24.         player = GameObject.FindWithTag("Player");
    25.  
    26.         SceneManager.sceneLoaded += OnSceneLoaded;
    27.     }
    28.  
    29.     public void LoadScene(string sceneName)
    30.     {
    31.         // Save player position before loading scene:
    32.         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
    33.         savedPositions[sceneIndex] = GameObject.FindWithTag("Player").transform.position;
    34.         SceneManager.LoadScene(sceneName);
    35.     }
    36.  
    37.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    38.     {
    39.         // After loading scene, check if we need to move player to previously-saved position:
    40.         if (savedPositions.ContainsKey(scene.buildIndex))
    41.         {
    42.             GameObject.FindWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    43.         }
    44.     }
    45.  
    46.     public void GetSceneName()
    47.     {
    48.         activeSceneName = SceneManager.GetActiveScene().name;
    49.         Debug.Log(activeSceneName);
    50.     }
    51.  
    52.     public void LoadLevel(string name)
    53.     {
    54.         Debug.Log("New Level load: " + name);
    55.         SceneManager.LoadScene(name);
    56.     }
    57.  
    58.     public void QuitRequest()
    59.     {
    60.         Debug.Log("Quit requested");
    61.         Application.Quit();
    62.     }
    63. }

    Then LevelEntryTrigger.cs (what im using to move between scenes)

    Code (CSharp):
    1. public class LevelEntryTrigger : MonoBehaviour
    2. {
    3.     public GameObject triggerText;
    4.     public string levelName;
    5.     public string sceneName;
    6.     public LevelManager levelMan;
    7.  
    8.     void Start()
    9.     {
    10.         levelMan = GameObject.FindWithTag("GameManager").GetComponent<LevelManager>();
    11.     }
    12.  
    13.     void OnTriggerStay(Collider _collider)
    14.     {
    15.         if (_collider.gameObject.tag == "Player")
    16.         {
    17.             triggerText.SetActive(true);
    18.             if (Input.GetKeyDown(KeyCode.F))
    19.             {
    20.  
    21.                 Debug.Log("f key pressed");
    22.                 SceneManager.LoadScene(levelName, LoadSceneMode.Single);
    23.                 levelMan.LoadScene(sceneName);
    24.             }
    25.         }
    26.     }
    27.  
    28.     void OnTriggerExit(Collider _collider)
    29.     {
    30.         if (_collider.gameObject.tag == "Player")
    31.         {
    32.             triggerText.SetActive(false);
    33.         }
    34.     }
    35. }
    Thanks again for your patience.
     
  15. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Code can always be improved, but the first and most important step is to get it working and correct. It looks pretty good, but I have two suggestions:
    • In LevelManager, remove the LoadLevel method. Always use the LoadScene method instead.
    • In LevelEntryTrigger's OnTriggerStay method, remove SceneManager.LoadScene. Only call levelMan.LoadScene.
     
  16. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Okay will do! Thanks again for all your help, you are an absolute saint!
     
  17. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Glad to help!
     
    RobbieNI likes this.
  18. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Hi Tony,

    Sorry to be a burden again but I was wondering if you could offer any further assistance on this. I have got the loading previously saved position working however I was curious if you knew a way I could assign the player a position on a certain level if they haven't been there before.

    So far i've tried the following,

    Code (CSharp):
    1.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    2.     {
    3.         Scene currentSceneTag = SceneManager.GetActiveScene();
    4.         string sceneTag = currentSceneTag.name;
    5.  
    6.         // After loading scene, check if we need to move player to previously-saved position:
    7.         if (savedPositions.ContainsKey(scene.buildIndex))
    8.         {
    9.             hasVisited = true;
    10.             GameObject.FindWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    11.             Debug.Log("Loaded Saved Position");
    12.         }
    13.         else if (sceneTag == "Caves" && hasVisited == false)
    14.         {
    15.             Vector3 playerPos;
    16.             playerPos = new Vector3(92, 11, 88);
    17.             player.transform.position = playerPos;
    18.             Debug.Log("Loaded Caves");
    19.         }
    20.         else if (sceneTag == "Forest2" && hasVisited == false)
    21.         {
    22.             Vector3 playerPos;
    23.             playerPos = new Vector3(356, 22, 395);
    24.             player.transform.position = playerPos;
    25.             Debug.Log("Loaded Forest2");
    26.         }
    27.     }
    It seems to work okay for that caves level (as it puts the player to that position) however it then seems to set the players position to that each time from then on (as opposed to setting it to the saved positions)

    Thanks again.
     
  19. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    I suggest a little change to make it more general. This way you won't need to hard-code info for Caves, Forest2, etc.

    The original OnSceneLoaded method looked like this:
    Code (csharp):
    1. void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    2.  
    3.     // After loading scene, check if we need to move player to previously-saved position:
    4.     if (savedPositions.ContainsKey(scene.buildIndex))
    5.     {
    6.         FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    7.     }
    8. }
    Change it to this:
    Code (csharp):
    1. void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    2.  
    3.     // After loading scene, check if we need to move player to previously-saved position:
    4.     if (savedPositions.ContainsKey(scene.buildIndex))
    5.     {
    6.         FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    7.     }
    8.     else
    9.     {
    10.         // Otherwise look for a PlayerStart component and move the player there:
    11.         var playerStart = FindObjectOfType<PlayerStart>();
    12.         if (playerStart != null)
    13.         {
    14.             FindObjectWithTag("Player").transform.position = playerStart.transform.position;
    15.         }
    16.     }
    17. }
    Then create a script named PlayerStart.cs:
    Code (csharp):
    1. using UnityEngine;
    2. public class PlayerStart : MonoBehaviour {}
    Add it to a new, empty GameObject. Position the GameObject where you want the player to start. This script marks the GameObject as the place where the player should start. Alternatively, you could create a new tag (e.g., "PlayerStart") instead of using a script, and set the GameObject to that tag.
     
  20. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Hi again,

    This is really weird - that works completely as expected for the first time you enter and exit the scene, however (and I know this as I put debug statements in each if and else) if you go back and fourth again it seems to run both the if and else statement.

    Basically, It works perfectly fine if I go from level 1 to 2 then back to 1, or 2 to 3 the back to 2, however If I go from level 1 to 2 to 3 then back to 2, it sets my position on level 2 back to player start, instead of the end position.

    I'm also getting the "Error adding Enlighten system data ..... RadiosityData is missing" - im wondering if this is the reason it is not working?
     
  21. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Do you have two LevelManager components in your scene perhaps?
     
  22. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    No I don't, I'm also using this in the LevelManager's awake which should check for this anyway:
     
  23. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Code (CSharp):
    1.             if (instance == null)
    2.                 instance = this;
    3.             else if (instance != this)
    4.                 Destroy(gameObject);
    5.  
    6.             DontDestroyOnLoad(gameObject);
    Sorry, hit the wrong button there. This code here in the awake. I have no clue how the player position code isn't working, as I said it seems to work every now and then.
     
  24. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Carefully trace through your LevelManager script by hand. There might be an issue with your if..else statement, or something similarly tricky. For example, hypothetically it could have a hard-to-notice error like this:
    Code (csharp):
    1. // THIS CODE HAS AN ERROR. IT'S MISSING 'else'.
    2.     if (savedPositions.ContainsKey(scene.buildIndex))
    3.     {
    4.         // If there's a saved position, move player to saved position:
    5.         FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    6.     }
    7.     {
    8.         // Otherwise look for a PlayerStart component and move the player there:
    9.         var playerStart = FindObjectOfType<PlayerStart>();
    10.         if (playerStart != null)
    11.         {
    12.             FindObjectWithTag("Player").transform.position = playerStart.transform.position;
    13.         }
    14.     }
    It looks almost correct, but it's missing an 'else' so both code blocks might run. That's just an example of one type of error.

    It's a better exercise to go through it by hand rather than using the interactive debugger. But you could also use the debugger as described in this tutorial.
     
  25. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Hi Tony,

    I'm pretty sure everything is in the script, nothing seems to be missing. The whole script is below so you can see.


    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using UnityEngine.AI;
    6.  
    7. [System.Serializable]
    8. public class LevelManager : MonoBehaviour
    9. {
    10.     public string activeSceneName;
    11.     public static LevelManager instance = null;
    12.     public GameObject player;
    13.     public bool hasVisited;
    14.  
    15.     Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();
    16.  
    17.     void Awake()
    18.     {
    19.         hasVisited = false;
    20.  
    21.         Scene currentSceneTag = SceneManager.GetActiveScene();
    22.         string sceneTag = currentSceneTag.name;
    23.  
    24.         if (sceneTag == "MainMenu")
    25.         {
    26.             return;
    27.         }
    28.         else
    29.         {
    30.             if (instance == null)
    31.                 instance = this;
    32.             else if (instance != this)
    33.                 Destroy(gameObject);
    34.  
    35.             DontDestroyOnLoad(gameObject);
    36.         }
    37.  
    38.         DontDestroyOnLoad(gameObject);
    39.         player = GameObject.FindWithTag("Player");
    40.  
    41.         SceneManager.sceneLoaded += OnSceneLoaded;
    42.     }
    43.    
    44.     public void LoadScene(string sceneName)
    45.     {
    46.         // Save player position before loading scene:
    47.         int sceneIndex = SceneManager.GetActiveScene().buildIndex;
    48.         savedPositions[sceneIndex] = GameObject.FindWithTag("Player").transform.position;
    49.         SceneManager.LoadScene(sceneName);
    50.         Debug.Log("Positions " + savedPositions);
    51.     }
    52.  
    53.     void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    54.     {
    55.         Scene currentSceneTag = SceneManager.GetActiveScene();
    56.         string sceneTag = currentSceneTag.name;
    57.  
    58.         // After loading scene, check if we need to move player to previously-saved position:
    59.         if (savedPositions.ContainsKey(scene.buildIndex))
    60.         {
    61.             GameObject.FindWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    62.             Debug.Log("Spawned Player to Exit location");
    63.         }
    64.         else
    65.         {
    66.             // Otherwise look for a PlayerStart component and move the player there:
    67.             GameObject playerStart = GameObject.FindWithTag("PlayerStart");
    68.             if (playerStart != null)
    69.             {
    70.                 GameObject.FindWithTag("Player").transform.position = playerStart.transform.position;
    71.                 Debug.Log("Spawned Player at PlayerStart");
    72.             }
    73.         }
    74.     }
    75.  
    76.     public void GetSceneName()
    77.     {
    78.         activeSceneName = SceneManager.GetActiveScene().name;
    79.         Debug.Log(activeSceneName);
    80.     }
    81.  
    82.     // It is suggested that using LoadScene is better, may update to this further down the line
    83.     public void LoadLevel(string name)
    84.     {
    85.         Debug.Log("New Level load: " + name);
    86.         SceneManager.LoadScene(name);
    87.     }
    88.  
    89.     public void QuitRequest()
    90.     {
    91.         Debug.Log("Quit requested");
    92.         Application.Quit();
    93.     }
    94. }
     
  26. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    There are a few things you could omit (for example, [System.Serializable] isn't necessary at the top of the class definition), but I suspect the problem is that you're still using LevelManager.LoadLevel instead of LevelManager.LoadScene. I recommend completely deleting LevelManager.LoadLevel to make sure you always use LoadScene.
     
  27. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    This is the code I'm using to load the level, as you can see i'm using LoadScene. I had to pass in that parameter otherwise I get an error, could this be the issue?

    Code (CSharp):
    1. public class LevelEntryTrigger : MonoBehaviour
    2. {
    3.     public GameObject triggerText;
    4.     public string levelName;
    5.     public string sceneName;
    6.     public LevelManager levelMan;
    7.  
    8.     void Start()
    9.     {
    10.         levelMan = GameObject.FindWithTag("GameManager").GetComponent<LevelManager>();
    11.     }
    12.  
    13.     void OnTriggerStay(Collider _collider)
    14.     {
    15.         if (_collider.gameObject.tag == "Player")
    16.         {
    17.             triggerText.SetActive(true);
    18.             if (Input.GetKeyDown(KeyCode.F))
    19.             {
    20.                 Debug.Log("f key pressed");
    21.                 levelMan.LoadScene(sceneName);
    22.                 // SceneManager.LoadScene(levelName, LoadSceneMode.Single);
    23.             }
    24.         }
    25.     }
    26.  
    27.     void OnTriggerExit(Collider _collider)
    28.     {
    29.         if (_collider.gameObject.tag == "Player")
    30.         {
    31.             triggerText.SetActive(false);
    32.         }
    33.     }
    34. }
     
  28. TonyLi

    TonyLi

    Joined:
    Apr 10, 2012
    Posts:
    12,545
    Quick side note: You're already storing a reference to the LevelManager instance, so you don't need to search for it:
    Code (csharp):
    1. // UNNECESSARY:
    2. levelMan = GameObject.FindWithTag("GameManager").GetComponent<LevelManager>();
    Instead, change this:
    Code (csharp):
    1. levelMan.LoadScene(sceneName);
    to this:
    Code (csharp):
    1. LevelManager.instance.LoadScene(sceneName);
    I don't see the reason why your script isn't working. So I either totally missed it, or the error is somewhere else. Try setting breakpoints at the beginning of LevelManager's LoadScene and OnSceneLoaded methods. Then play the scene and trace through to make sure it's running the code you expect it to run. If you're not familiar with the debugger, I included a link to Unity's tutorial in a previous reply.
     
  29. RobbieNI

    RobbieNI

    Joined:
    Nov 8, 2017
    Posts:
    19
    Okay, thanks for your help!
     
  30. HaMrDev

    HaMrDev

    Joined:
    Nov 4, 2019
    Posts:
    7
    Do you guys know how to change the save data to playerprefs or binary because i am trying to make a mobile game,and i am trying to save the build index
     
  31. rdsp007

    rdsp007

    Joined:
    Dec 19, 2019
    Posts:
    4


    tysm man i was looking for this!!
     
    TonyLi likes this.