C#/Unity - Unable to save scriptable object to disk - c#

Here is the code of my scriptable object:
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEngine;
namespace ModelManager
{
[CreateAssetMenu(fileName = "New Texture Set", menuName = "Model Manager/New Texture Set")]
public class TextureSetSO : ScriptableObject
{
[SerializeField]
public RaceModelSO race;
public List<Textures> textures;
private RaceModelSO _race;
private void OnEnable()
{
_race = race;
}
private void OnValidate()
{
if (race != _race)
{
_race = race;
textures.Clear();
if (race)
{
if (race.GetRaceBodySlots().Length != 0)
{
for (int i = 0; i < race.GetRaceBodySlots().Length; i++)
{
Textures textures = new Textures();
textures.raceSlot = race.GetRaceBodySlots()[i];
this.textures.Add(textures);
}
}
}
}
EditorUtility.SetDirty(this);
AssetDatabase.SaveAssets();
}
private void OnDestroy()
{
AssetDatabase.SaveAssets();
}
private void OnDisable()
{
AssetDatabase.SaveAssets();
}
}
[Serializable]
public class Textures
{
public RaceSlotSO raceSlot;
public Texture normalMap;
public Texture albedoMap;
public Texture metalicMap;
public Texture ambientOcullsionMap;
public Texture emissionMap;
}
}
Whenever I use the editor to set anything inside the List<Textures> textures and then if I close Unity everything is getting lost.
Why is that? How can I keep the changes when close and open back Unity?
Any way to store that info to the disk ?

Problem
The reason why this happens is that the
private RaceModelSO _race;
is not serialized => Not saved.
Thus, everytime you reopen the project _race = null and therefore the check
if (race != _race)
is true and you erase all textures.
Solution
If you need the other field _race to be serialized as well you will need to have [SerializeField] (btw it makes no sense on a public field)
[SerializeField] private RaceModelSO _race;
if you want to serialize it but not exposed in the Inspector you can use
[HideInInspector] private RaceModelSO _race;
you don't need [SerializeField] then, as it is already included in the [HideInInspector].
Note
You should not call AssetDatabase.SaveAssets() here at all. You do this by pressing CTRL + S.
Makes little sense to do this in OnDestroy or OnDisabled at all for a ScriptableObject and is also extremely expensive doing it for every OnValidate call!

You need to call EditorUtility.SetDirty(scriptableObject). Although you seem to call it inside OnValidate, OnValidate isn't called when modifying variables from code. It is called only when modifying the variable via Inspector or undoing/redoing a prior change.
An alternative to EditorUtility.SetDirty is to call Undo.RecordObject(scriptableObject) prior to modifying the ScriptableObject; this method also allows undoing/redoing the change.
P.S. You don't need to call EditorUtility.SetDirty or AssetDatabase.SaveAssets inside OnValidate. You also don't need [SerializeField] attribute on a public field.

Related

Why do my variables keep going back to 0 and why do my variables look different on the screen then in reality?

So for some reason when I try to access variables from another script the text is showing what I put but when I change things in the editor the accrual values don't change. even if in the code I put playerPoints to 9 when I run it its 0 during a collision. I have all the scripts and objects connected up, and when I try to flip where the variables are, make them in the destroy script, for some reason, it doesn't work. it wont let me use public BallsText bt; it will return an error even though it works fine for the other. I'm sorry if all of these are really basic questions but I've looked as far as I can on the internet and I cant find anything, so any help would be appreciated.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class BallsText : MonoBehaviour
{
public Destroy destroy;
// public int playerPoints = 0;
//public int enemyPoints = 0;
//int playerPoints = 0;
//int enemyPoints = 0;
public Text playerPointsText;
public Text enemyPointsText;
void Update()
{
playerPointsText.text = destroy.playerPoints.ToString();
enemyPointsText.text = destroy.enemyPoints.ToString();
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Destroy : MonoBehaviour
{
public int playerPoints = 9;
public int enemyPoints = 0;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
Destroy(this.gameObject);
}
}
}
You can hide them from the inspector's view with HideInInspector while still being public.
[HideInInspector] public int playerPoints = 9;
You can also use the reset script to return the setting of public numbers to the first state.
Like Everts said, you had the value at zero and created an instance of the script. Now when you change the script Unity will serialize (save) all the public/serialized fields, then reload the script, then deserialize (load) all the public/serialized fields it previously saved. This means your values are locked into the instance that you previously used. If you make a new instance, you get the current values, and if you reset the script you'll get the current values.
This has also bitten me enough times that I don't set default values when variables are declared anymore. If you have a value that you want to use, set those values in Start() or Awake().
The advantage here is that whatever is in the script will get overwritten when play mode starts. The disadvantage is that you can't customize those values on a per-instance basis anymore, because all instances of the script will all load the same default values when play mode starts. If this matters to you, and you want to be able to customize those values, then unfortunately you'll need to go to each script and change those values manually.
If you use a property with an automatic backing field then you won't be able to see it in the editor
public class Destroy : MonoBehaviour
{
public int playerPoints{get; set;} = 9; // Can't see this in the editor
public int enemyPoints{get; set;} = 0; // Can't see this in the editor
If you use a property with an explicit backing field then you can expose the backing field to the editor with the [SerializeField] tag, but then you've got the same problem you've got now - the editor will serialize that field and subsequent changes to the script won't affect instances:
public class Destroy : MonoBehaviour
{
[SerializeField]
private int playerPoints_ = 9; // Instances will "lock in" values and later changes to the script here won't take effect
[SerializeField]
private int enemyPoints_ = 0; // Instances will "lock in" values and later changes to the script here won't take effect
public int playerPoints
{
get=>playerPoints_;
set{playerPoints_ = value;}
}
public int enemyPoints
{
get=>enemyPoints_;
set{enemyPoints_ = value;}
}
If you keep the fields public (and thus exposed to the editor) but set the values at runtime in Awake() or Start() then you can see the values in the editor but the editor values for all instances will be overridden when play mode starts:
public class Destroy : MonoBehaviour
{
public int playerPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public int enemyPoints; // Doesn't matter what the value is here on instantiation becuase you'll override it on Awake()
public void Awake()
{
playerPoints = 9; // Will override *every* instance's values with this.
enemyPoints = 0; // Will override *every* instance's values with this.
}
:EDIT:
I'll add too that repeatedly polling is wasteful. If you use events then you can subscribe and get notifications when there's something to see. Consider instead:
public class Destroy : MonoBehaviour
{
private int playerPoints = 9;
private int enemyPoints = 0;
public System.EventHandler<int> OnPlayerPointsChanged;
public System.EventHandler<int> OnEnemyPointsChanged;
void OnCollisionEnter2D(Collision2D col)
{
if (this.name == "Destroy.cs")
{
Debug.Log("i have a doughnut");
}
if (col.gameObject.name == "Player")
{
Debug.Log(playerPoints);
playerPoints++;
OnPlayerPointsChanged?.Invoke(this, playerPoints);
Debug.Log(playerPoints);
Debug.Log("at least im a dounut");
Destroy(this.gameObject);
}
else if (col.gameObject.name == "Enemy Zone")
{
enemyPoints++;
OnEnemyPointsChanged?.Invoke(this, enemyPoints);
Destroy(this.gameObject);
}
}
}
Now Destroy has two public events that fire when the public or enemy points change. Anyone subscribing to those events will get notified when the points change, and part of the event notification is the current point value.
Then your other script subscribes to the events and does whatever they need to when they receive that event. Here they'll convert the points .ToString() and update the Text values:
public class BallsText : MonoBehaviour
{
public Destroy destroy;
public Text playerPointsText;
public Text enemyPointsText;
private void Start()
{
destroy.OnPlayerPointsChanged += PlayerPointsChanged;
destroy.OnEnemyPointsChanged += EnemyPointsChanged;
}
public void PlayerPointsChanged(object sender, int points)
{
playerPointsText.text = points.ToString;
}
public void EnemyPointsChanged(object sender, int points)
{
enemyPointsText.text = points.ToString();
}
}
Last note here is that your Destroy script increments enemyPoints but then also immediately destroys the gameObject, so I don't see the point in incrementing enemyPoints unless there's something else accumulating enemy points. With the subscription model that's totally doable - you could have something else subscribing to the Destroy script models and they'll get notifications before the script self-destructs.

How to change the value of field simultaneously in two classes without using "static"? C#. Unity

I have two classes Player and PlayerUI in Player class I have its 'health' value and in PlayerUI class I have a Slider which value updates each frame according to 'health' value in Player class. I also have a Damage method that changes 'health' value. How can I change the value of field 'health' simultaneously in PlayerUI class and in Player class without using static?
Player class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
}
}
PlayerUI class:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.health;
}
private void Update()
{
hpBar.value = Player.health;
}
}
In general you don't want to poll and set values in Update every frame but rather make your code more event driven => Only change the UI slider in the moment the value is actually changed.
I would use a property to make it event driven.
The main questions with this kind of constructs are: "Who shall be responsible for what?" and "Who shall know who?"
Option A
For example: Shall the player actively control the UI, without the UI knowing that a Player even exists?
The player "knows" the PlayerUI => it has its reference
The player actively informs the UI and updated the slider value
like e.g.
public class Player : MonoBehaviour
{
// In this case the Player needs to know the UI
public PlayerUI ui;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed actively update the UI
ui.hpBar.value = value;
}
}
private void Start ()
{
// initially inform the UI
Health = health;
}
...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}
Advantage
Player gains more power and responsibility and possibly becomes the core component that ensures a clean interoperability between multiple subcomponents like the UI and others. It would also be the central API point for accessing these sub components from the outside
Disadvantage
As the player needs to know all the subcomponents, everytime you add one you have to hard code it into the class and configure the references and connections between the subcomponents
Option 2
Or should it rather be the other way round and the Player doesn't even know that a UI exists?
-> Instead of knowing the UI you could add a UnityEvent (just like the button onClick) in befores code example where you can attach callbacks via the Inspector or on runtime to react to every change of the health property.
The Player simply invokes his event, not knowing/caring who is listening to it
The UI hooks up to the event -> The UI knows the player
like e.g.
public class Player : MonoBehaviour
{
// In this case the Player doesn't know anyone
// attach listeners via the Inspector or on runtime via code
public UnityEvent<float> OnHealthChanged;
private float health = 150;
public float Health
{
get => health;
set
{
health = value;
// Whenever the value of Health is changed just raise the event
// you don't care who is listening or not, but whoever is will get informed
OnHealthChanged.Invoke(value);
}
}
private void Start ()
{
// initially inform all listeners
// Note that for timing reasons it is essential that the listeners are attached in "Awake"
// so before this "Start" is called
// Alternatively the listeners can of course also ONCE poll the value directly -> Up to you
Health = health;
}
...
void Damage()
{
// Important: don't change the field anymore but rather the property!
Health -= 10;
}
}
The UI can hook up to the player and listen to the event and react to it like e.g.
public class PlayerUI : MonoBehaviour
{
...
// In this case the UI needs to know the player
public Player player;
private void Awake ()
{
player.OnHealthChanged.AddListener(UpdateSlider);
}
private void OnDestroy ()
{
if(player) player.OnHealthChanged.RemoveListener(UpdateSlider);
}
private void UpdateSlider (float value)
{
hpBar.value = value;
}
}
Advantage
This solves exactly the Disadvantage of Option A and allows to extremely flexible add and remove listeners without the Player having to care at all.
Disadvantage
With a lot of subcomponents and events and listeners it can quick get out of hand and become hard to debug and maintain. Also as noted this is more open for race conditions and timing and order issues.
Imo the best approach would be to create an event like "OnPlayerDamaged" in Player script that is raised when player gets any damage. Then create a method in PlayerUI that is subscribed to OnPlayerDamaged event and changes your healthbar.
Player script:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Player : MonoBehaviour
{
public static event Action<float> OnPlayerDamaged;
private float health = 150;
private void Update()
{
if (Input.GetKeyDown(KeyCode.Q))
{
Damage();
}
}
void Damage()
{
health -= 10;
OnPlayerDamaged.Invoke(health)
}
public static float GetHealth()
{
return health;
}
}
PlayerUI
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PlayerUI : MonoBehaviour
{
public Slider hpBar;
private void Awake()
{
hpBar.maxValue = Player.GetHealth();
}
private void OnEnable()
{
Player.OnPlayerDamaged += ChangeHealth;
}
private void OnDisable()
{
Player.OnPlayerDamaged -= ChangeHealth;
}
private void Update()
{
}
public void ChangeHealth(float currentHealth)
{
hpBar.value = currentHealth;
}
}
With this approach you can later add anything you want that uses the information about player getting damaged like screen effects or audio change. Just add the method to the event just like the ChangeHealth() method in our case. Remember to remove the method from the event on script disable to avoid multiple subscription of the same method.
GetHealth() method is a quick hack there but you should use ScriptableObject for player statistics like health and reference it where you want to use it. Then you don't need to pass currentHealth in the event and when it is raised just get value from scriptable object.

Unity calling singleton which is from another scene without using dontDestroyOnLoad

I have singleton( which is monobehavior also same question for non-monobehavior) which is created at scene 1 , and it created without dontDestroyOnLoad. im calling this singleton from scene 2 , and getting/using the info inside without any problem.I have read something about ghost GameObjects in this case but couldnt find detailed info.
In Scene 1
using UnityEngine;
public class RefreshAccount : MonoBehaviour
{
public static RefreshAccount refreshAccount;
public string aString = "aaaaaaaa";
void Awake()
{
if (!refreshAccount) refreshAccount = this;
else Destroy(this.gameObject);
// it is not labeled as DontDestroyOnLoad
}
(...)
}
Scene 2
using UnityEngine;
using UnityEngine.SceneManagement;
public class testnewscen : MonoBehaviour
{
private void Start()
{
Debug.Log(RefreshAccount.refreshAccount.aString );
}
}
So will it cause any problem/error in the future of this app ?
Will there be any memory problem or performance problem?
If you use this solution, you cannot run scene 2 without run scene 1 before
If you dont need to set serialize variable / game object / prefabs to your singleton (RefreshAccount) I prefer to use non-monobehaviour singleton instead like
public class RefreshAccount {
private static RefreshAccount instance
public static RefreshAccount Instance {
get {
if(instance == null) {
instance = createInstance();
}
return instance;
}
}
}
If you need to use for read some serialize value (variable, config, gameobject, etc.) without behaviour ( awake, update, fix update )
You can use SerializableObject
SerialzpizeObject is similar static class or prefab but you need to use RESOURCE.LOAD to read it

Keeping changes when switching scene in unity [duplicate]

How can I pass score value from one scene to another?
I've tried the following:
Scene one:
void Start () {
score = 0;
updateScoreView ();
StartCoroutine (DelayLoadlevel(20));
}
public void updateScoreView(){
score_text.text = "The Score: "+ score;
}
public void AddNewScore(int NewscoreValue){
score = score + NewscoreValue;
updateScoreView ();
}
IEnumerator DelayLoadlevel(float seconds){
yield return new WaitForSeconds(10);
secondsLeft = seconds;
loadingStart = true;
do {
yield return new WaitForSeconds(1);
} while(--secondsLeft >0);
// here I should store my last score before move to level two
PlayerPrefs.SetInt ("player_score", score);
Application.LoadLevel (2);
}
Scene two:
public Text score_text;
private int old_score;
// Use this for initialization
void Start () {
old_score = PlayerPrefs.GetInt ("player_score");
score_text.text = "new score" + old_score.ToString ();
}
but nothing displayed on screen, and there's no error.
Is this the correct way to pass data ?
I am using Unity 5 free edition, develop game for Gear VR (meaning the game will run in android devices).
Any suggestion?
There are many 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.
In this answer you can find
Use the static keyword
Use DontDestroyOnLoad
Store the data local
3a PlayerPrefs
3b serialize to XML/JSON/Binary and use FileIO
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");
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 and references 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;
public Transform someTransformReference = 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]);
}
example.someTransformReference = transform;
}
}
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);
Debug.Log($"The data writer {example.someTransformReference.name} is at position {example.someTransformReference.position}", 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 if needed I often combine the session persistence with some FileIO (as described in this answer's section 3b) 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.
(This won't work with references of course.)
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.
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
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.
you have several options.
The first one I see is to use static variables, which you will not lose their information or value passing from scenes to scenes (since they are not bound to the object). [you lose the information when closing the game, but not when passing between scenes]
the second option is that the player or the object of which you do not want to lose the information, you pass it through the DontDestroyOnLoad function
Here I give you the documentation and the sample code. [You lose the information when you close the game, but not when you go between scenes]
https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
Third is to use the playerPrefab [https://docs.unity3d.com/ScriptReference/PlayerPrefs.html]
that allow you to save information and retrieve it at any time without hanging it even after closing the game [you must be very careful with the latter if you plan to use it to save data even after closing the game since you can lose the data if you close the game suddenly , since player prefab creates a file and retrieves the information from there, but it saves the file at the end or closes the app correctly]

Check the previous loaded scene

I'm making a game in Unity3D with C# for mobile devices and can't figure out how to check which scene was loaded before the current scene. I need to check this to change the spawn point from the player gameobject. First I added a simple script to my buttons (loadnextscene and loadprevscene)
public class SwitchScene : MonoBehaviour {
public int sceneNumber;
public void LoadScene(int sceneNumber) {
Application.LoadLevel(sceneNumber);
}
}
A second scripts handles the touch input from the user and changes the movement of the player object.
So, for example: If the player clicks on the "load previous scene" button in the second Level to switch to the first level again, I want to set the spawn point of the player object on the right half on the screen and not on the left side like when the game was started the first time.
I tried it with Singleton and PlayerPrefs, but it did not work out.
You need to save the scene number to some variable before LoadScene, then check it after the scene loaded.
The only problem is that this variable will be destroyed after the new scene is loaded. So, to prevent it, you can use DontDestroyOnLoad. Here is what you do:
First, create a new empty game object, and attach the following script to it:
using UnityEngine;
using System.Collections;
public class Indestructable : MonoBehaviour {
public static Indestructable instance = null;
// For sake of example, assume -1 indicates first scene
public int prevScene = -1;
void Awake() {
// If we don't have an instance set - set it now
if(!instance )
instance = this;
// Otherwise, its a double, we dont need it - destroy
else {
Destroy(this.gameObject) ;
return;
}
DontDestroyOnLoad(this.gameObject) ;
}
}
And now, before you load, save the scene number in the Indestructable object:
public class SwitchScene : MonoBehaviour {
public int sceneNumber;
public void LoadScene(int sceneNumber) {
Indestructable.instance.prevScene = Application.loadedLevel;
Application.LoadLevel(sceneNumber);
}
}
And last, in your scene Start() check Indestructable.instance.prevScene and do your magic accordingly.
More info here:
http://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html
*I did not compile the code, so there may be some errors, but this is the general idea.
Why did the PlayerPrefs approach did not work?
I think its the easiest way to solve your problem.
public class FirstLevel : MonoBehaviour {
public void Start() {
PlayerPrefs.SetString("SceneNumber", SceneManager.GetActiveScene().name);
}
}
And then in the second scene simply read the saved PlayerPrefs
public class SecondLevel : MonoBehaviour {
string PrevScene;
public void Start() {
PrevScene = PlayerPrefs.GetString("SceneNumber");
// if there will be a third scene, etc.
PlayerPrefs.SetString("SceneNumber", SceneManager.GetActiveScene().name);
}
public void GoToPrevScene() {
SceneManager.LoadScene(PrevScene);
}
}
You can solve this problem with a single static member variable in the SwitchScene class. No need for the singleton pattern or DontDestroyOnLoad.
public class SwitchScene : MonoBehaviour
{
public int sceneNumber;
private static int previousScene;
private int oldPreviousScene;
void Start()
{
oldPreviousScene = previousScene;
previousScene = sceneNumber;
}
public void HandleLoadPrevButtonClick()
{
SceneManager.LoadScene(oldPreviousScene);
}
}

Categories