How to pass data between scenes in Unity

后端 未结 5 1317
误落风尘
误落风尘 2020-11-22 08:30

How can I pass score value from one scene to another?

I\'ve tried the following:

Scene one:

void Start () {
    score = 0;
          


        
相关标签:
5条回答
  • 2020-11-22 08:47

    I use a functional approach I call Stateless Scenes.

    using UnityEngine;
    public class MySceneBehaviour: MonoBehaviour {
        private static MySceneParams loadSceneRegister = null;
    
        public MySceneParams sceneParams;
    
        public static void loadMyScene(MySceneParams sceneParams, System.Action<MySceneOutcome> callback) {
            MySceneBehaviour.loadSceneRegister = sceneParams;
            sceneParams.callback = callback;
            UnityEngine.SceneManagement.SceneManager.LoadScene("MyScene");
        }
    
        public void Awake() {
            if (loadSceneRegister != null) sceneParams = loadSceneRegister;
            loadSceneRegister = null; // the register has served its purpose, clear the state
        }
    
        public void endScene (MySceneOutcome outcome) {
            if (sceneParams.callback != null) sceneParams.callback(outcome);
            sceneParams.callback = null; // Protect against double calling;
        }
    }
    
    [System.Serializable]
    public class MySceneParams {
        public System.Action<MySceneOutcome> callback;
        // + inputs of the scene 
    }
    
    public class MySceneOutcome {
        // + outputs of the scene 
    }
    

    You can keep global state in the caller's scope, so scene inputs and outputs states can be minimized (makes testing easy). To use it you can use anonymous functions:-

    MyBigGameServices services ...
    MyBigGameState bigState ...
    
    Splash.loadScene(bigState.player.name, () => {
       FirstLevel.loadScene(bigState.player, (firstLevelResult) => {
           // do something else
           services.savePlayer(firstLevelResult);
       })
    )}
    

    More info at https://corepox.net/devlog/unity-pattern:-stateless-scenes

    0 讨论(0)
  • 2020-11-22 08:49

    There are various way, but assuming that you have to pass just some basic data, you can create a singelton instance of a GameController and use that class to store the data.

    and, of course DontDestroyOnLoad is mandatory!

    public class GameControl : MonoBehaviour
    {
        //Static reference
    public static GameControl control;
    
    //Data to persist
    public float health;
    public float experience;
    
    void Awake()
    {
        //Let the gameobject persist over the scenes
        DontDestroyOnLoad(gameObject);
        //Check if the control instance is null
        if (control == null)
        {
            //This instance becomes the single instance available
            control = this;
        }
        //Otherwise check if the control instance is not this one
        else if (control != this)
        {
            //In case there is a different instance destroy this one.
            Destroy(gameObject);
        }
    }
    

    Here is the full tutorial with some other example.

    0 讨论(0)
  • 2020-11-22 08:57

    There are 3 ways to do this but the solution to this depends on the type of data you want to pass between scenes. Components/Scripts and GameObjects are destroyed when new scene is loaded and even when marked as static.

    1. Use the static keyword.

    Use this method if the variable to pass to the next scene is not a component, does not inherit from MonoBehaviour and is not a GameObject then make the variable to be static.

    Built-in primitive data types such as int, bool, string, float, double. All those variables can be made a static variable.

    Example of built-in primitive data types that can be marked as static:

    static int counter = 0;
    static bool enableAudio = 0;
    static float timer = 100;
    

    These should work without problems.


    Example of Objects that can be marked as static:

    public class MyTestScriptNoMonoBehaviour
    {
    
    }
    

    then

    static MyTestScriptNoMonoBehaviour testScriptNoMono;
    
    void Start()
    {
        testScriptNoMono = new MyTestScriptNoMonoBehaviour();
    }
    

    Notice that the class does not inherit from MonoBehaviour. This should work.


    Example of Objects that cannot be marked as static:

    Anything that inherits from Object, Component or GameObject will not work.

    1A.Anything that inherits from MonoBehaviour

    public class MyTestScript : MonoBehaviour 
    {
    
    }
    

    then

    static MyTestScript testScript;
    
    void Start()
    {
        testScript = gameObject.AddComponent<MyTestScript>();
    } 
    

    This will not work because it inherits from MonoBehaviour.

    1B.All GameObject:

    static GameObject obj;
    
    void Start()
    {
        obj = new GameObject("My Object");
    }  
    

    This will not work either because it is a GameObject and GameObject inherit from an Object.

    Unity will always destroy its Object even if they are declared with the static keyword.

    See #2 for a workaround.


    2.Use the DontDestroyOnLoad function.

    You only need to use this if the data to keep or pass to the next scene inherits from Object, Component or is a GameObject. This solves the problem described in 1A and 1B.

    You can use it to make this GameObject not to destroy when scene unloads:

    void Awake() 
    {
        DontDestroyOnLoad(transform.gameObject);
    }
    

    You can even use it with the static keyword solve problem from 1A and 1B:

    public class MyTestScript : MonoBehaviour 
    {
    
    }
    

    then

    static MyTestScript testScript;
    
    void Awake() 
    {
        DontDestroyOnLoad(transform.gameObject);
    }
    
    void Start()
    {
        testScript = gameObject.AddComponent<MyTestScript>();
    } 
    

    The testScript variable will now be preserved when new scene loads.

    3.Save to local storage then load during next scene.

    This method should be used when this is a game data that must be preserved when the game is closed and reopened. Example of this is the player high-score, the game settings such as music volume, objects locations, joystick profile data and so on.

    Thare are two ways to save this:

    3A.Use the PlayerPrefs API.

    Use if you have just few variables to save. Let's say player score:

    int playerScore = 80;
    

    And we want to save playerScore:

    Save the score in the OnDisable function

    void OnDisable()
    {
        PlayerPrefs.SetInt("score", playerScore);
    }
    

    Load it in the OnEnable function

    void OnEnable()
    {
        playerScore  =  PlayerPrefs.GetInt("score");
    }
    

    3B.Serialize the data to json, xml or binaray form then save using one of the C# file API such as File.WriteAllBytes and File.ReadAllBytes to save and load files.

    Use this method if there are many variables to save.

    General, you need to create a class that does not inherit from MonoBehaviour. This class you should use to hold your game data so that in can be easily serialized or de-serialized.

    Example of data to save:

    [Serializable]
    public class PlayerInfo
    {
        public List<int> ID = new List<int>();
        public List<int> Amounts = new List<int>();
        public int life = 0;
        public float highScore = 0;
    }
    

    Grab the DataSaver class which is a wrapper over File.WriteAllBytes and File.ReadAllBytes that makes saving data easier from this post.

    Create new instance:

    PlayerInfo saveData = new PlayerInfo();
    saveData.life = 99;
    saveData.highScore = 40;
    

    Save data from PlayerInfo to a file named "players":

    DataSaver.saveData(saveData, "players");
    

    Load data from a file named "players":

    PlayerInfo loadedData = DataSaver.loadData<PlayerInfo>("players");
    
    0 讨论(0)
  • 2020-11-22 08:57

    There is another way:

    ScriptableObject

    ScriptableObjects are basically data containers but may also implement own logic. They "live" only in the Assets like prefabs. They can not be used to store data permanently, but they store the data during one session so they can be used to share data between Scenes ... and - something I also often needed - between Scenes and an AnimatorController!

    Script

    First you need a script similar to MonoBehaviours. A simple example of a ScriptableObject might look like

    // fileName is the default name when creating a new Instance
    // menuName is where to find it in the context menu of Create
    [CreateAssetMenu(fileName = "Data", menuName = "Examples/ExamoleScriptableObject")]
    public class ExampleScriptableObject : ScriptableObject
    {
        public string someStringValue = "";
        public CustomDataClass someCustomData = null;
    
        // Could also implement some methods to set/read data,
        // do stuff with the data like parsing between types, fileIO etc
    
        // Especially ScriptableObjects also implement OnEnable and Awake
        // so you could still fill them with permanent data via FileIO at the beginning of your app and store the data via FileIO in OnDestroy !!
    }
    
    // If you want the data to be stored permanently in the editor
    // and e.g. set it via the Inspector
    // your types need to be Serializable!
    //
    // I intentionally used a non-serializable class here to show that also 
    // non Serializable types can be passed between scenes 
    public class CustomDataClass
    {
        public int example;
        public Vector3 custom;
        public Dictionary<int, byte[]> data;
    }
    

    Create Instances

    You can create instances of ScriptableObject either via script

    var scriptableObject = ScriptableObject.CreateInstance<ExampleScriptableObject>();
    

    or to make things easier use the [CreateAssetMenu] as shown in the example above.

    As this created ScriptabeObject instance lives in the Assets it is not bound to a scene and can therefore be referenced everywhere!

    This when you want to share the data between two Scenes or also e.g. the Scene and an AnimatorController all you need to do is reference this ScriptableObject instance in both.

    Fill Data

    I often use e.g. one component to fill the data like

    public class ExampleWriter : MonoBehaviour
    {
        // Here you drag in the ScriptableObject instance via the Inspector in Unity
        [SerializeField] private ExampleScriptableObject example;
    
        public void StoreData(string someString, int someInt, Vector3 someVector, List<byte[]> someDatas)
        {
            example.someStringValue = someString;
            example.someCustomData = new CustomDataClass
                                     {
                                         example = someInt;
                                         custom = someVector;
                                         data = new Dictionary<int, byte[]>();
                                     };
            for(var i = 0; i < someDatas.Count; i++)
            {
                example.someCustomData.data.Add(i, someDatas[i]);
            }
        }
    }
    

    Consume Data

    So after you have written and stored your required data into this ExampleScriptableObject instance every other class in any Scene or AnimatorController or also other ScriptableObjects can read this data on just the same way:

    public class ExmpleConsumer : MonoBehaviour
    {
        // Here you drag in the same ScriptableObject instance via the Inspector in Unity
        [SerializeField] private ExampleScriptableObject example;
    
        public void ExampleLog()
        {
            Debug.Log($"string: {example.someString}", this);
            Debug.Log($"int: {example.someCustomData.example}", this);
            Debug.Log($"vector: {example.someCustomData.custom}", this);
            Debug.Log($"data: There are {example.someCustomData.data.Count} entries in data.", this);
        }
    }
    

    Persistence

    As said the changes in a ScriptableObject itself are only in the Unity Editor really persistent.

    In a build they are only persistent during the same session.

    Therefore of needed I often combine the session persistence with some FileIO for loading and deserializing the values once at session begin (or whenever needed) from the hard drive and serialize and store them to a file once on session end (OnApplicationQuit) or whenever needed.

    0 讨论(0)
  • 2020-11-22 08:59

    Besides playerPrefs another dirty way is to preserve an object during level loading by calling DontDestroyOnLoad on it.

    DontDestroyOnLoad (transform.gameObject);
    

    Any script attached to the game object will survive and so will the variables in the script. The DontDestroyOnLoad function is generally used to preserve an entire GameObject, including the components attached to it, and any child objects it has in the hierarchy.

    You could create an empty GameObject, and place only the script containing the variables you want preserved on it.

    0 讨论(0)
提交回复
热议问题