I want to assign each item I create with a GameObject of some sort and then when I create a new object (called Item) it will have the assigned GameObject as a child.
For that I have a class of a scriptable object which holds a public GameObject called "gameObj" within it:
public abstract class ItemObject : ScriptableObject
{
public int id;
public GameObject gameObj;
}
Then, in another class I want to have something of this sort:
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
this.gameObject.transform.GetChild(0) = item.gameObj; //WRONG CODE, NEED HELP HERE
}
}
The purpose is to set the gameObj from the given ItemObject.item as the GameObject for the GroundItem.
The purpose is in the end to have lots of scriptable items of all sorts (like bread, sword, stone etc) and each one will have a GameObject assigned to it, and once I create a new GroundItem game object I will simply assign the scriptable object item and its child will have the game object itself (which includes all the visuals, special scripts etc).
For reference, in the following link the person is doing this from minute 4 to minute 6, but with a sprite instead of a game object.
Anyone knows how it should be written? Is it even possible?
You would probably mean
public class GroundItem : MonoBehaviour, ISerializationCallbackReceiver
{
public ItemObject item;
public void OnBeforeSerialize()
{
if(!item) return;
if(!item.gameObj) return;
// make according object a child of this object
item.gameObj.transform.parent = transform;
// make it the firs child
item.gameObj.transform.SetSiblingIndex(0);
}
}
However, in general ScriptableObjects are assets and GameObjects are instances in the scene hierarchy -> you can't simply reference scene objects in assets, doing so anyway might lead to unexpected behavior.
Is there a good reason why this has to be done in ISerializationCallbackReceiver?
I think what you actually rather want to achieve would be e.g.
public class GroundItem : MonoBehaviour
{
public ItemObject item;
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
private ItemObject _lastItem;
// This is called whenever something is changed in this objects Inspector
// like e.g. item ;)
private void OnValidate()
{
// Delay the call in order to not get warnings for SendMessage
UnityEditor.EditorApplication.delayCall += DelayedOnValidate;
}
private void DelayedOnValidate()
{
// remove the callback since we want to be sure it is always added only once
UnityEditor.EditorApplication.delayCall -= DelayedOnValidate;
// if item hasn't changed nothing to do
if (item == _lastItem) return;
// otherwise first destroy current child if any
if (_lastItem && transform.childCount > 0)
{
if (Application.isPlaying) Destroy(transform.GetChild(0).gameObject);
else DestroyImmediate(transform.GetChild(0).gameObject);
}
// is an item referenced and does it even have a gameObject ?
if (item && item.gameObj)
{
// instantiate the new one as child of this object
var obj = Instantiate(item.gameObj, transform);
// set as first child (if needed)
obj.transform.SetSiblingIndex(0);
if (!Application.isPlaying)
{
// if in edit mode mark this object as dirty so it needs to be saved
UnityEditor.EditorUtility.SetDirty(gameObject);
UnityEditor.SceneManagement.EditorSceneManager.MarkSceneDirty(UnityEditor.SceneManagement.EditorSceneManager.GetActiveScene());
}
}
_lastItem = item;
}
#endif
}
Which would now look like
Related
I followed a tutorial in which someone wrote "public Inventory Container;" with which he could access data from the Inventory-Script. Why is this possible? I thought you can only access a non-instantiated script if it's static. Thanks for the help
First Script:
[CreateAssetMenu(fileName = "New Inventory", menuName = "Inventory System/Inventory")]
public class InventoryObject : ScriptableObject
{
public Inventory Container;
public void AddItem(Item _item, int _amount)
{
if (_item.buffs.Length > 0)
{
Container.Items.Add(new InventorySlot(_item.Id, _item, _amount));
return;
}
for (int i = 0; i < Container.Items.Count; i++) //Checks if already has the item.
{
if(Container.Items[i].item.Id == _item.Id)
{
Container.Items[i].AddAmount(_amount); /*If the item already exits, it just increases the number of it.
AddAmount works, because the Container consists of InventorySlot.*/
return;
}
}
Container.Items.Add(new InventorySlot(_item.Id, _item, _amount)); /*If Item doesn't exist, it adds it using the InventorySlot Constructor.*/
}
}
Script being accessed:
[System.Serializable]
public class Inventory
{
public List<InventorySlot> Items = new List<InventorySlot>();
}
Inventory is Serializable, which means when you change the Container in the inspector when editing your game, Unity will serialize that instance.
Then, when the game starts Unity will deserialize it and assign it back to Container for whichever instance of InventoryObject you were editing.
See The Unity documentation for further details:
Inspector window
When you view or change the value of a GameObject’s component field in the Inspector window, Unity serializes this data and then displays it in the Inspector
window.
I use Scriptable Objects to create Items for my Unity Game. Let's say I create a Stick, a Sword and a Helmet. Now to use them, I need to drag them into a list and on game start, each item get's an ID assigned which is also the position in the list, as I simply loop through the list to assign ID's. Now I can access the item by using the index it has or, as shown in most of the tutorials about ScriptableObjects, we can then add the item and the id to a Dictionary to better access them.
Stick is 0, Sword is 1 and Helmet is 2. Now I decide to remove the sword with a patch so helmet gets the ID of 1. Now every savegame is broken as swords become helmets.
How can I handle assigning fixed ID's which wont change and wont be taken by another item until I really want to assign it again? I could of course hard-code the id for every ScriptableObject I create but as you can imagine, managing hard-coded IDs seems not like a good idea.
Here is my take on it :
Create a custom attribute "ScriptableObjectIdAttribute", you can use it in front of whatever you need to be an id.
Make a custom property drawer in order to initialize the id the first time you access the object in the inspector and also to make it non-editable by the inspector.
(Optional) Make a base class with that Id property and extend all your ScriptableObjects which need a unique Id from it.
BaseScriptableObject.cs :
using System;
using UnityEditor;
using UnityEngine;
public class ScriptableObjectIdAttribute : PropertyAttribute { }
#if UNITY_EDITOR
[CustomPropertyDrawer(typeof(ScriptableObjectIdAttribute))]
public class ScriptableObjectIdDrawer : PropertyDrawer {
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) {
GUI.enabled = false;
if (string.IsNullOrEmpty(property.stringValue)) {
property.stringValue = Guid.NewGuid().ToString();
}
EditorGUI.PropertyField(position, property, label, true);
GUI.enabled = true;
}
}
#endif
public class BaseScriptableObject : ScriptableObject {
[ScriptableObjectId]
public string Id;
}
Weapon.cs :
using System;
using UnityEngine;
[CreateAssetMenu(fileName = "weapon", menuName = "Items/Weapon")]
public class Weapon : BaseScriptableObject {
public int Attack;
public int Defence;
}
Result :
Create a string "ID" variable, check if it is empty when the Scriptable Objects Validates. If it is empty Create a GUID, if it is not, do nothing.
public string UID;
private void OnValidate()
{
#if UNITY_EDITOR
if (UID == "")
{
UID = GUID.Generate().ToString();
UnityEditor.EditorUtility.SetDirty(this);
}
#endif
}
This is how i did it, it also sets the scriptable object to dirty so it does not reset when you restart the unity editor.
You can go a step further and make the UID field read only to prevent changing it by accident.
This is what I ended up using for a simple, duplicate safe method.
public class YourObject : ScriptableObject
{
public string UID;
private void OnValidate()
{
if (string.IsNullOrWhiteSpace(UID))
{
AssignNewUID();
}
}
private void Reset()
{
AssignNewUID();
}
public void AssignNewUID()
{
UID = System.Guid.NewGuid().ToString();
#if UNITY_EDITOR
UnityEditor.EditorUtility.SetDirty(this);
#endif
}
}
How do I change one or two attribute values of a script so that it effects all the gameObjects that this script is attached to?
For example making attributes SphereSmall and SphereBig global
public Vector3 SphereSmall = new Vector3 (0.001f, 0.001f, 0.001f);
public Vector3 SphereBig = new Vector3 (0.0015f, 0.0015f, 0.0015f);
Two gameObjects has this script attached to and I changed the attribute of public variables SphereSmall and SphereBig on one GameObject. I want this value to be changed in second GameObject as well.
I'm new to unity as well, but here are some solutions:
1) If you're talking about multiple instances of the same object, simply create a prefab out of that object and update the properties of the prefab.
https://docs.unity3d.com/Manual/CreatingPrefabs.html
2) If you're talking about instances of different objects, you can try using the multi-select functionality of the unity editor, this will let you edit all common properties. This is easy if you have lower number if instances and are grouped under a parent object in the Hierarchy pane.
3) If you're talking about instances of different objects and you don't mind seeing the effect of your updates only at run-time: you can try using the ScriptableObject class:
[CreateAssetMenu]
public class CommonObjectProperties : ScriptableObject
{
public Vector3 SphereSmall = new Vector3(1, 0.001f, 0.001f);
public Vector3 SphereBig = new Vector3(0.0015f, 0.0015f, 0.0015f);
}
After you create this script, go to Assets > Create > Common Object Properties:
Now you can use this ScriptableObject instance to add the common values to your objects and after you update them inside the ScriptableObject instance, they will update across all objects.
ObjectScript is the MonoBehaviour script to put on your objects:
public class ObjectScript : MonoBehaviour
{
public CommonObjectPropertis commonProps;
public Vector3 ObjectScriptSmallSphere;
private void Start()
{
ObjectScriptSmallSphere = commonProps.SphereSmall;
}
}
How it should look in the designer:
I'm sure there are plenty of other ways to do this, best of luck!
You could add [ExecuteInEditMode] to your script so it runs while the editor is running. From there you could check if the values are being changed and then update them. Something like this:
[ExecuteInEditMode]
public class MyScript : MonoBehaviour
{
public int myValue;
private int oldValue;
void Update ()
{
if (oldValue != myValue)
{
MyScript[] objects = FindObjectsOfType<MyScript>();
foreach(MyScript myObject in objects)
{
myObject.myValue = myValue
}
oldValue = myValue;
}
}
}
This isn't best practice though so I would recommend looking into using prefab variants instead.
So, I have this crazy idea to have enums pointing to gameobjects.
Here's what I want to do:
/* These enums would hold gameobjects instead of ints */
enum exampleEnum{
AddPanel,
ListPanel
}
public class GUIManager : MonoBehaviour {
void Start()
{
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
Is there any way to make this work? Or a workaround?
This might be possible with something other than an enum but I don't know of it if there is and I'm looking through the web for a such a solution.
This would satisfy your requirement, works for any amount of enum values or panels.
// Add this to each of your panels, the enum field can be integrated into your other behaviours as well
public class EnumPanel : MonoBehaviour
{
// configurable from the Editor, the unity way.
public ExampleEnum Type;
}
// Assign all your panles in the editor (or use FindObjectsByType<EnumPanel> in Start())
public class GUIManager : MonoBehaviour
{
// configurable from the Editor, the unity way.
public EnumPanel[] Panels;
void Start()
{
// Optionally find it at runtime, if assigning it via the editor is too much maintenance.
// Panels = FindObjectsByType<EnumPanel>();
EnablePanel(ExampleEnum.AddPanel);
}
void EnablePanel(ExampleEnum panelType)
{
foreach(var panel in Panels)
{
if(panel.Type == panelType)
EnablePanel(panel.gameObject);
}
}
void EnablePanel(GameObject panel)
{
panel.setActive(true);
}
}
I don't know why the answer from: #Paradox Forge was wrong but maybe this will help you.
System.Collections.Generic.Dictionary
I don't have a lot of time to explain the dictionary class but this is how you can use it.
This will cost some performance but has really nice readability
public class GUIManager : MonoBehaviour {
public enum exampleEnum{
AddPanel,
ListPanel
}
//For readability you can also add "using System.Collections.Generic;" on the top of your script
private System.Collections.Generic.Dictionary<exampleEnum,GameObject> exampleDictionary = new System.Collections.Generic.Dictionary<exampleEnum, GameObject>();
private GameObject SomeGameObject;
private GameObject SomeOtherGameObject;
void Start()
{
//You have to add all the enums and objects you want to use inside your GUIManager.
exampleDictionary.Add (exampleEnum.AddPanel, SomeGameObject); //Add panel will be linked to SomeGameObject
exampleDictionary.Add (exampleEnum.ListPanel, SomeOtherGameObject); //List Panel will be linked to SomeOtherGameObject
EnablePanel(exampleEnum.AddPanel);
}
void EnablePanel(exampleEnum examplePanel)
{
if (!exampleDictionary.ContainsKey (examplePanel)) //If the given panel does not exist inside the dictionary
return; //Leave the method
GameObject panelToEnable = exampleDictionary [examplePanel]; //Will return the GameObject linked to given panel
panelToEnable.SetActive(true); //Enable the gameobject
}
}
If you want to know more about the Dictionary class go to: Dictionary
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]