Please consider the following code. It creates a material and shader from a certain path. It's a utility to use in an editor extensions.
public static class GpuImageProcessing
{
private static readonly string matPath = Application.dataPath + "/Uplus/Zcommon/Material/ImageProcessing/";
private static Shader Gaussian2D5Shader;
private static Material Gaussian2D5Mat;
static GpuImageProcessing()
{
Gaussian2D5Shader = (Shader) AssetDatabase.LoadAssetAtPath(matPath
+ "Gaussian2D5.shader", typeof(Shader));
Gaussian2D5Mat = new Material(Gaussian2D5Shader);
}
}
Now the problem is how can I destroy this material before editor recompiles each time some script is changed? I mean after some code is changed the editor needs to recompile scripts and create a new execution context and a new version of this GpuImageProcessing will be created. I want to destroy the materials created in previous runtime.
PS: This is included in a DLL file so I can't make it a ScriptableObject and listen to the event callbacks and also because it's a utility class I really like it being static.
Thanks to #Programmer Figured out a workaround to ensure the destruction of materials. In order for this to work, the class should become a singleton and inherit from ScriptableObject. Then we can implement OnDisable() and it will be called when editor is recompiling. Here is the working example:
public class GpuImageProcessing:ScriptableObject
{
private static readonly string matPath = "Assets/Uplus/Zcommon/Material/ImageProcessing/";
private Material Gaussian2D5Mat;
private Material Gaussian1DVariableMat;
private static GpuImageProcessing _instance;
private bool destroyedAlready;
public static GpuImageProcessing Instance
{
get
{
if (_instance != null) return _instance;
_instance= CreateInstance<GpuImageProcessing>();
_instance.Init();
return _instance;
}
}
private void Init()
{
var Gaussian2D5Shader = (Shader)AssetDatabase.LoadAssetAtPath(matPath + "Gaussian2D5.shader", typeof(Shader));
Gaussian2D5Mat = new Material(Gaussian2D5Shader);
var gaus1dVarShader = (Shader)AssetDatabase.LoadAssetAtPath(matPath + "Gaussian1DVariable.shader", typeof(Shader));
Gaussian1DVariableMat = new Material(gaus1dVarShader);
}
private void OnDisable()
{
Debug.Log("On disable GpuImgProc");
if(destroyedAlready) return;
DestroyImmediate(Gaussian2D5Mat);
DestroyImmediate(Gaussian1DVariableMat);
destroyedAlready = true;
DestroyImmediate(this);
}
}
Important: Please notice the destroyedAlready field. It ensures materials are destroyed only once since the OnDisable is called two times (one when unity editor invokes it and one when we DestroyImmediate(this)). Please set destroyedAlready before calling DestroyImmediate(this).
While not the exact solution for original question and not a nice solution, it does work and prevents the memory leak while preserving the static nature of the class.
Related
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]
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]
I need multiple materials at the same time and only difference between them should be color, even texture is the same and i have to create them one by one and i wonder which way is better, to create them in editor and store them in assets or create them when game starts as temporary materials from script
It depends mainly of when you need them.
I'm assuming you will need all of them at startup. In this case:
Instantiating them at runtime will use more CPU for the instantiation, so the application will need more time to load.
Having them created in the editor will need to store the data statically, so the application will occupy more memory
If you do not need all of them at startup you could start spawning them in background with a Coroutine.
public class MaterialSpawn : MonoBehaviour
{
private Material[] materials;
public int amount = 100;
public static bool IsSpawning = false;
private void Awake() { StartCoroutine(SpawnYourMaterial()); }
private IEnumerator SpawnYourMaterial()
{
IsSpawning = true;
int spawned = 0;
while (spawned < amount)
{
//INSTANTIATE YOUR MATERIAL
spawned++;
yield return null;
}
IsSpawning = false;
}
}
I am making a probject in Unity, and would like to have one play to access all my SceneNaming;
Right now in the UI, I have to set the scene name manually.
I would like to store all my scene name in an object, so that I can just use a drag drop to choose all my scenes names.
I tried to put a static class and have then like this
public static string SCENE_MENU = "Menu";
public static string SCENE_WORLD = "Demo";
or inside an enum
public enum SCENE_NAME{
Menu, Demo
}
and then use GetName on the enum to get the value
What is the best approach? 1: /storage/temp/135402-screenshot-1.png
With a customer editor script you could use a SceneAsset to store a Scene's path instead.
I will use a CustomEditor here since for starters it's easier to understand what happens there. Later you might want to switch it to a CustomPropertyDrawer wot a proper class or maybe even as Attribute.
Place this in anywhere in the Assets
public class SceneLoader : MonoBehaviour
{
public string ScenePath;
public void Load()
{
//e.g.
SceneManager.LoadSceneAsync(ScenePath);
}
}
Place this inside of a folder Editor (so it will not be included in a build where the UnityEditor namespace does not exist)
[CustomEditor(typeof(SceneLoader), true)]
public class ScenePickerEditor : Editor
{
private SerializedProperty _scenePath;
private void OnEnable()
{
_scenePath = serializezObject.FindProperty("ScenePath");
}
public override void OnInspectorGUI()
{
// Draw the usual script field
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(.FromMonoBehaviour((SceneLoader)target), typeof(SceneLoader), false);
EditorGUI.EndDisabledGroup();
// Loads current Values into the serialized "copy"
serializedObject.Update();
// Get the current scene asset for the current path
var currentScene = !string.IsNullOrWhiteSpace(_scenePath.stringValue) ? AssetDatabase.LoadAssetAtPath<SceneAsset>(_scenePath.stringValue) : null;
EditorGUI.BeginChangeCheck();
var newScene = (SceneAsset)EditorGUILayout.ObjectField("Scene", currentScene, typeof(SceneAsset), false);
if (EditorGUI.EndChangeCheck())
{
_scenePath.stringValue = newScene != Null ? AssetDatabase.GetAssetPath(newScene) : "";
}
// Write back changes to the actual component
serializedObject.ApplyModifiedProperties();
}
}
And e.g. to your button attach that SceneLoader component.
Than you can simply reference the target scene in the Inspector via drag and drop. Internally it instead stores the according ScenePath.
Now in onClick instead use that SceneLoader.Load.
Note:
As mentioned here only storing the scene path might not be "save" and breaks if you later move the according scene or rename it. So maybe it would be a good extension to also store according object reference as a kind of fallback.
You could than also use this approach and extend it to be a central manager instead like
// It could as well be a ScriptableObject object
// this makes e.g. Awake run already in edit mode
[ExecuteInEditMode]
public class ScenePathManager : MonoBehaviour
{
// I would prefere references but for ease of this post
// use a Singleton for access
public static ScenePathManager Instance;
public List<string> AvailableScenePaths = new List<string>();
private void Awake ()
{
Instance = this;
}
}
and in the editor script use a list (again there are more beautiful ways like ReorderableList bit this would get to complex here
[CustomEditor(typeof(ScenePathManager))]
public class ScenePathManagerEditor : Editor
{
private SerializedProperty _availablePaths;
private void OnEnable ()
{
_availablePaths = serializedObject.FindProperty("AvailablScenePaths");
}
public override OnInpectorGUI ()
{
// Draw the usual script field
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.ObjectField(.FromMonoBehaviour((SceneLoader)target), typeof(SceneLoader), false);
EditorGUI.EndDisabledGroup();
serializedObject.Update();
//Do the same thing as before but this time in a loop
for(var i=0; i<_availablePaths.arraySize; i++)
{
var _scenePath = _availablePaths.GetArrayElementAtIndex(i);
// Loads current Values into the serialized "copy"
serializedObject.Update();
// Get the current scene asset for the current path
var currentScene = !string.IsNullOrWhiteSpace(_scenePath.stringValue) ? AssetDatabase.LoadAssetAtPath<SceneAsset>(_scenePath.stringValue) : null;
EditorGUI.BeginChangeCheck();
var newScene = (SceneAsset)EditorGUILayout.ObjectField("Scene", currentScene, typeof(SceneAsset), false);
if (EditorGUI.EndChangeCheck())
{
_scenePath.stringValue = newScene != Null ? AssetDatabase.GetAssetPath(newScene) : "";
}
}
serializedObject.ApplyModifiedProperties();
}
}
Than you could reference all needed scenes in that manager and than on your SceneLoader instead have a Popup field (like for enums) in order to select the scene you want
[CustomEditor (typeof (SceneLoader))]
public class SceneLoaderEditor : Editor
{
private SerializedProperty _scenePath;
private void OnEnable ()
{
_scenePath = serializedObject.FindProperty("ScenePath");
}
public override void OnInpectorGUI ()
{
//Let me shorten it a bit this time ^^
serializedObject.Update();
var availablePaths = ScenePathManager.Instance ? ScenePathManager.Instance.AvailableScenePaths : new List<string>();
var currentIndex = availablePaths.FirstOrDefault(path => string.Equals(path, _scenePath.stringValue)));
var newIndex = EditorGUILayout.PopupField("Scene", currentIndex, availabePaths.ToArray());
_scenePath.stringValue = availablePaths[newIndex];
serializedObject.ApplyModifiedProperties();
}
}
This should than give you a selection dropdown for the scene.
Note this might, however, without the object reference as backing field break evem faster of any of those strings or indexes change...
But you could use this with your manager also without the whole SceneAsset approach but only for simple strings.
Typed on my smartphone so no warranty but I hope I make my point clear
Say I had the following class
public class Scene{
public static var title="new scene";
public function Scene(){}
public static function start() { trace("scene started"); }
}
How can you access the Scene class's static variables and functions like this?
var i:Class = Scene;
trace(i.title);
i.start();
I'm trying to figure out how variables assigned with Class work in actionscript.
Any tips would be welcome. Thanks.
Static methods are called from the class:
trace(Scene.title);
Scene.start();
Singleton patterns enable constructor, local reference, and potentially abstraction through interface classes.
Example of Scene as a singleton:
package
{
public class Scene
{
private static var instance:Scene = new Scene();
public static function getInstance():Scene
{
return instance;
}
public var title:String = "new scene";
public function Scene()
{
if (instance)
throw new Error("Scene is a singleton and can only be accessed through Scene.getInstance()");
}
public function start():void
{
trace("scene started.");
}
}
}
Your example implementation would now be:
var i:Scene = Scene.getInstance();
trace(i.title);
i.start();
This is how you can access the dynamic class (Scene) & it's properties / methods :
var myDynamicClasses:Array = [Scene]; // Required
var i:Class = Class(getDefinitionByName("Scene"));
trace(i.title);
i.start.call();
This could throw an error, if the first line is not included. Because, when the compiler notices the class Scene (not the one from adobe's package) is not being used it ignores it. Thus it would be not available for dynamic initialization.
We could force the compiler to include these classes by putting these class names in variables or declare an array as above as a quick hack.
If you have many dynamic classes, you could add a reference to them in this array & each class will be included by the compiler for dynamic initialization.
var i:Class = Scene;
trace(i.title);
Should throw an error because the compiler can no longer assume that i is a scene when it gets to line 2. If you were to coerce the Class object, it should work.
var i:Class = Scene;
trace((Scene(Class).title);