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

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.

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.

1 Like

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.

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.

Use Start. Add a script to the player GameObject in each scene that checks if there’s a previously-saved position. Example:

SavedPositionManager.cs

using UnityEngine;
using System.Collections.Generic;

public static class SavedPositionManager // Static class to remember player positions per scene.
{
    public static Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();
}

SaveAndRestorePosition.cs

using UnityEngine;
using UnityEngine.SceneManagement;

public class SaveAndRestorePosition : MonoBehaviour
{
    void Start() // Check if we've saved a position for this scene; if so, go there.
    {
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        if (SavedPositionManager.savedPositions.ContainsKey(sceneIndex))
        {
            transform.position = SavedPositionManager.savedPositions[sceneIndex];
        }
    }

    void OnDestroy() // Unloading scene, so save position.
    {
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        SavedPositionManager.savedPositions[sceneIndex] = transform.position;
    }
}

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.

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?

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.

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.

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:

public class GameManager : MonoBehaviour
{
    Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();

    void Awake()
    {
        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    public void LoadScene(string sceneName)
    {
        // Save player position before loading scene:
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        savedPositions[sceneIndex] = FindObjectWithTag("Player").position;
        SceneManager.LoadScene(sceneName);
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // After loading scene, check if we need to move player to previously-saved position:
        if (savedPositions.ContainsKey(scene.buildIndex))
        {
            FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
        }
    }
    ...

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.

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.

Where am I calling GameManager.LoadScene?

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.

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):

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.AI;

[System.Serializable]
public class LevelManager : MonoBehaviour {

    public string activeSceneName;
    public static LevelManager instance = null;
    public GameObject player;

    Dictionary<int, Vector3> savedPositions = new Dictionary<int, Vector3>();

    void Awake()
    {
        if (instance == null)
            instance = this;
        else if (instance != this)
            Destroy(gameObject);

        DontDestroyOnLoad(gameObject);
        player = GameObject.FindWithTag("Player");

        SceneManager.sceneLoaded += OnSceneLoaded;
    }

    public void LoadScene(string sceneName)
    {
        // Save player position before loading scene:
        int sceneIndex = SceneManager.GetActiveScene().buildIndex;
        savedPositions[sceneIndex] = GameObject.FindWithTag("Player").transform.position;
        SceneManager.LoadScene(sceneName);
    }

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        // After loading scene, check if we need to move player to previously-saved position:
        if (savedPositions.ContainsKey(scene.buildIndex))
        {
            GameObject.FindWithTag("Player").transform.position = savedPositions[scene.buildIndex];
        }
    }

    public void GetSceneName()
    {
        activeSceneName = SceneManager.GetActiveScene().name;
        Debug.Log(activeSceneName);
    }

    public void LoadLevel(string name)
    {
        Debug.Log("New Level load: " + name);
        SceneManager.LoadScene(name);
    }

    public void QuitRequest()
    {
        Debug.Log("Quit requested");
        Application.Quit();
    }
}

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

public class LevelEntryTrigger : MonoBehaviour
{
    public GameObject triggerText;
    public string levelName;
    public string sceneName;
    public LevelManager levelMan;

    void Start()
    {
        levelMan = GameObject.FindWithTag("GameManager").GetComponent<LevelManager>();
    }

    void OnTriggerStay(Collider _collider)
    {
        if (_collider.gameObject.tag == "Player")
        {
            triggerText.SetActive(true);
            if (Input.GetKeyDown(KeyCode.F))
            {

                Debug.Log("f key pressed");
                SceneManager.LoadScene(levelName, LoadSceneMode.Single);
                levelMan.LoadScene(sceneName);
            }
        }
    }

    void OnTriggerExit(Collider _collider)
    {
        if (_collider.gameObject.tag == "Player")
        {
            triggerText.SetActive(false);
        }
    }
}

Thanks again for your patience.

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.

Okay will do! Thanks again for all your help, you are an absolute saint!

Glad to help!

1 Like

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,

    void OnSceneLoaded(Scene scene, LoadSceneMode mode)
    {
        Scene currentSceneTag = SceneManager.GetActiveScene();
        string sceneTag = currentSceneTag.name;

        // After loading scene, check if we need to move player to previously-saved position:
        if (savedPositions.ContainsKey(scene.buildIndex))
        {
            hasVisited = true;
            GameObject.FindWithTag("Player").transform.position = savedPositions[scene.buildIndex];
            Debug.Log("Loaded Saved Position");
        }
        else if (sceneTag == "Caves" && hasVisited == false)
        {
            Vector3 playerPos;
            playerPos = new Vector3(92, 11, 88);
            player.transform.position = playerPos;
            Debug.Log("Loaded Caves");
        }
        else if (sceneTag == "Forest2" && hasVisited == false)
        {
            Vector3 playerPos;
            playerPos = new Vector3(356, 22, 395);
            player.transform.position = playerPos;
            Debug.Log("Loaded Forest2");
        }
    }

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.

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:

void OnSceneLoaded(Scene scene, LoadSceneMode mode)
 
    // After loading scene, check if we need to move player to previously-saved position:
    if (savedPositions.ContainsKey(scene.buildIndex))
    {
        FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    }
}

Change it to this:

void OnSceneLoaded(Scene scene, LoadSceneMode mode)

    // After loading scene, check if we need to move player to previously-saved position:
    if (savedPositions.ContainsKey(scene.buildIndex))
    {
        FindObjectWithTag("Player").transform.position = savedPositions[scene.buildIndex];
    }
    else
    {
        // Otherwise look for a PlayerStart component and move the player there:
        var playerStart = FindObjectOfType<PlayerStart>();
        if (playerStart != null)
        {
            FindObjectWithTag("Player").transform.position = playerStart.transform.position;
        }
    }
}

Then create a script named PlayerStart.cs:

using UnityEngine;
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.

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?