I have a problem when changing values and trying to play game these values disappear. For example, if I enable bool in editor and the start game bool disables itself. I use custom editor to change these values.
This is my settings script and [system.serializable] doesn't help:
using UnityEngine;
[System.Serializable]
public class Settings : MonoBehaviour
{
public static bool testMode;
public static string androidUnitID;
public static string iphoneUnitID;
}
And this is my editor script:
using UnityEngine;
using UnityEditor;
[CustomEditor(typeof(Settings))]
class TestEditor : Editor {
public override void OnInspectorGUI() {
GUILayout.Label("Settings", EditorStyles.boldLabel);
Settings.testMode = EditorGUILayout.Toggle("Test Mode", Settings.testMode);
Settings.androidUnitID = EditorGUILayout.TextField("Android UnitID", Settings.androidUnitID);
Settings.iphoneUnitID = EditorGUILayout.TextField("Android UnitID", Settings.iphoneUnitID);
}
First I'm changing values on editor:
Then when play mode is active all values disappear:
Is there any way to fix this problem?
Thank you for your help.
Unity doesn't support serialising static variables "out-of-the-box" (you can rewrite the serialisation to accommodate it, but effort vs. reward and all that). So, based on that, what you're essentially seeing is the values that you're setting being stored in the class during Edit, but when Unity goes into Play mode, all the class are re-initialised, deserialised and prepared for game-play. Your static values are lost.
It's the same reason why changed values during game play aren't kept when you go back the other way to Edit mode. Edit mode and Play mode are essentially two separate processes.
Your best bet here would be to create a ScriptableObject, with those fields you've marked as static in your Settings class, create a reference to the new ScriptableObject, and then, if you want, modify the values directly from the ScriptableObject.
Something along the lines of:
[CreateAssetMenu ( fileName = "Settings", menuName = "Settings/Create Settings SO", order = 1 )]
public class SettingsScriptableObject : ScriptableObject
{
public bool testMode;
public string androidUnitID;
public string iphoneUnitID;
//... Any further configuration/settings items you want should go here.
}
You would then modify your Settings class:
public class Settings : MonoBehaviour
{
public SettingsScriptableObject SettingsSO;
}
And your editor script:
[CustomEditor(typeof(Settings))]
class TestEditor : Editor
{
public override void OnInspectorGUI()
{
if ( target == null ) return;
var settings = target as Settings;
GUILayout.Label("Settings", EditorStyles.boldLabel);
settings.SettingsSO.testMode = EditorGUILayout.Toggle("Test Mode", settings.SettingsSO.testMode);
settings.SettingsSO.androidUnitID = EditorGUILayout.TextField("Android UnitID", settings.SettingsSO.androidUnitID);
settings.SettingsSO.iphoneUnitID = EditorGUILayout.TextField("Android UnitID", settings.SettingsSO.iphoneUnitID);
}
}
There's an excellent video at Unity about the Edit and Play modes, and how ScriptableObjects can be used to overcome the serialisation problem.
Related
I have created a simple scriptable object with a list, 1 button and something to the list, second load its into the text ui. When I test this on Unity, it works when I already build the app from the build settings, android on the other hand doesnt. Anyone know why, or does it even work on android?
Thanks!
2 main scripts:
https://drive.google.com/file/d/1QS9SjzEJ5BdIhu3PPpg-ePSqktm_6Um8/view?usp=sharing, https://drive.google.com/file/d/1T9iV_zK8NlvCI2jFkEEAafkrMFAsoFMa/view?usp=sharing
EDIT:
proof:
https://drive.google.com/file/d/1rkN4W-u3G6-Uj7ReSkVRVtLKI4zgCwEh/view?usp=sharing
mp4 file btw
Scriptable Objects are platform-independent which means they work on any platform.
Here's what Unity's official Scriptable Object's documentation says:
When you use the Editor, you can save data to ScriptableObjects while editing and at run time because ScriptableObjects use the Editor namespace and Editor scripting. In a deployed build, however, you can’t use ScriptableObjects to save data, but you can use the saved data from the ScriptableObject Assets that you set up during development.
In order to save data to Scriptable Object (when you click on Save button in UI) you have to save the Scriptable Object to player prefs.
Something like this:
create auxiliary class i.e SaveSystemData as a separate file and mark it as [Serializable] (must do this) and add lists from Scriptable object from it
[Serializable]
public sealed class SaveSystemData
{
public List<string> List1;
public List<string> List2;
public List<string> List3;
public List<string> List4;
public List<string> List5;
}
reference that class in your Scriptable object instead of lists
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName ="ScriptableSaver", menuName ="SaveSystem")]
public class SaveSystem : ScriptableObject
{
public SaveSystemData saveSystemData;
}
save SaveSystemData to JSON and put it into player prefs when you click on Save button in UI
private const string SAVED_DATA = "SavedData";
public void SaveGame()
{
SOScript.saveSystemData.List1 = DictionaryWords;
SOScript.saveSystemData.List2 = DictionaryMeaning;
SOScript.saveSystemData.List3 = DictionaryInRussian;
SOScript.saveSystemData.List4 = DictionaryAddInfo;
SOScript.saveSystemData.List5 = DictionaryAVN;
PlayerPrefs.SetString(SAVED_DATA, JsonUtility.ToJson(SOScript.saveSystemData))
}
load SaveSystemData from JSON
public void LoadGame()
{
if (PlayerPrefs.HasKey(SAVED_DATA))
{
SOScript.saveSystemData = JsonUtility.FromJson<SaveSystemData>(PlayerPrefs.GetString(SAVED_DATA));
}
DictionaryWords = SOScript.saveSystemData.List1;
DictionaryMeaning = SOScript.saveSystemData.List2;
DictionaryInRussian = SOScript.saveSystemData.List3;
DictionaryAddInfo = SOScript.saveSystemData.List4;
DictionaryAVN = SOScript.saveSystemData.List5;
}
That's it in general
I'm creating a system where I have my cards in ScriptableObject, but each card has a unique function. What I thought of was creating a new class for each skill that inherits from "skills". So I put this script for each letter in my ScriptableObject, but now I'm trying to access it and I can't, because I couldn't use the AddComponent.
public class Cards : ScriptableObject
{
public Sprite cardSprite;
public int cardAttack;
public int cardHealth;
public int cardCost;
public Object cardAbility;
}
As you realized, you can't use AddComponent with ScriptableObjects, because it can only be used to attach components to GameObjects.
ScriptableObjects however do support a somewhat similar concept of main assets and sub assets.
You can use AssetDatabase.AddObjectToAsset to add new sub assets, and they'll appear as nested children below the main asset in the Project view.
public abstract class Skill : ScriptableObject
{
#if UNITY_EDITOR
protected static void AddToCard<TSkill>(MenuCommand command) where TSkill : Skill
{
Card card = (Card)command.context;
TSkill skill = CreateInstance<TSkill>();
skill.name = skill.GetType().Name;
card.skill = skill;
string path = AssetDatabase.GetAssetPath(card);
AssetDatabase.AddObjectToAsset(skill, path);
AssetDatabase.ImportAsset(path);
}
[MenuItem("CONTEXT/Card/Remove Skill")]
protected static void RemoveSkill(MenuCommand command)
{
Card card = (Card)command.context;
string path = AssetDatabase.GetAssetPath(card);
Skill skill = AssetDatabase.LoadAssetAtPath<Skill>(path);
AssetDatabase.RemoveObjectFromAsset(skill);
AssetDatabase.ImportAsset(path);
}
#endif
}
using UnityEditor;
using UnityEngine;
[CreateAssetMenu]
public class ExampleSkill : Skill
{
#if UNITY_EDITOR
[MenuItem("CONTEXT/Card/Add Skill/Example Skill")]
private static void AddToCard(MenuCommand command) => AddToCard<ExampleSkill>(command);
#endif
}
using UnityEngine;
[CreateAssetMenu]
public class Card : ScriptableObject
{
public Skill skill;
}
A scriptable object is a data container. A material or lighting settings are scriptable objects. You can save them as assets and use them as settings for other scripts, but not attach them to GameObjects. Read more here.
To create ScriptableObjects in the asset browser, you need to add the following before the start of your class.
[CreateAssetMenu(fileName = "DefaultFileName", menuName = "ScriptableObjects/NameOfYourObjects", order = 1)]
To add them to a script, you use public Cards cards; then you can drag them from your asset browser to the script in the inspector.
The only things you can add to a GameObject are MonoBehaviors
I am developing a small nuclear reactor simulator game. I have a bunch of reactor component classes: HeatVent, HeatExchanger, UraniumCell etc. They are not deriving from MonoBehaviour since they don't have any Unity logic, but they do implement a shared interface IReactorComponent. What I want to do is to be able to create prefabs of such components (simple heat vent, advanced heat vent, doubled uranium cell etc.) The prefabs would have different sprites and something like that, but the main issue is to define what reactor component class the prefab is related to, because I can't just drag'n'drop a non-MonoBehaviour script on inspector. Also, I want to be able to set settings in the inspector (for example, HeatVent has CoolAmount and HeatCapacity properties, UraniumCell has FuelAmount, HeatProduce and PowerProduce properties).
I have read about factory method pattern and as I understood, I have to create a fabric class that derives from MonoBehaviour for each reactor component class like HeatVentBehaviour, HeatExchangerBehaviour etc. Yes, that completely solves my issue with prefabs but is there any way to not create an additional MonoBehaviour wrap for each class? If I had 15 IReactorComponent classes, I would need to create 15 fabrics which feels like not the greatest solution.
Sounds like what you are looking for is ScriptableObject!
Instances of those are assets so they don't live in a scene but in the Assets folder and basically behave a little bit like prefabs except: They already exist and do not need to be instantiated anymore.
Mostly they are used as just configurable data containers. They have an Inspector so you can easily fill them with your desired data and references to other assets (e.g. the related prefab in your case).
But in addition you can as well let them implement behavior like your interface and thereby change the behavior of your scene objects by using different implementations of a method from different ScriptableObjects!
For the factory you then only need to figure out for which method to use which ScriptableObject instance e.g. either by having different methods or by having a Dictionary where you fill in your SO references.
Just as an example how this might look like (make sure each MonoBehaviour and ScriptableObject has its individual script file with matching name)
SpawnManager.cs
public class SpawnManager : MonoBehaviour
{
[SerializeField] private ReactorComponentBehaviour _behaviourPrefab;
[SerializeField] private BaseReactorComponent[] _components;
public bool TrySpawn<T>(out T component, out ReactorComponentBehaviour componentBehaviour) where T : IReactorComponent
{
component = default(T);
componentBehaviour = default;
var foundComponent = components.FirstOrDefault(c => c.GetType() == typeof(T));
if(foundComponent == null)
{
Debug.LogError($"No component found of type {T.GetType().Name}!");
return false;
}
// Here Instantiate doesn't spawn anything into the scene but
// rather creates a copy of the ScriptableObject asset
// This is just to avoid that any changes in the fields during the game
// would change the original ScriptableObject asset and thereby ALL related behavior instances
component = Instantiate ( (T) foundComponent);
// This now indeed spawns the related MonoBehaviour + GameOver
componentBehaviour = Instantiate (behaviourPrefab);
componentBehaviour.Init(component);
return true;
}
}
BaseReactorComponent.cs
public abstract class BaseReactorComponent : ScriptableObject, IReactorComponent
{
public abstract void WhateverIReactorComponentNeeds();
// Common fields and methods e.g.
public Sprite Icon;
}
HeatVent.cs
[CreateAssetMenu]
public class HeatVent : BaseReactorComponent
{
public int CoolAmount;
public int HeatCapacity;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
UraniumCell.cs
[CreateAssetMenu]
public class UraniumCell : BaseReactorComponent
{
public int FuelAmount;
public int HeatProduce;
public int PowerProduce;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
And finally you need only one base prefab with the
ReactorComponentBehavior.cs
public class ReactorComponentBehavior : MonoBehaviour
{
[SerializeField] private Image _image;
private IReactorComponent _component;
public void Init(IReactorComponent component)
{
_componemt = component;
// Do other stuff like e.g. adjust visuals according to the component etc
_image.sprite = component.Icon;
}
// And then use whatever this behavior should do with the assigned component
}
So in the end you would use that like e.g.
if(spawManagerReference.TrySpawn<HeatVent>(out var component, out var componentBehaviour)
{
// Do something with the behavior e.g. set its position, parent etc
}
else
{
Debug.LogError($"Failed to get a {nameof(HeatVent)}!");
}
If then at some point you still want different additional behaviours you could let them inherit from the common ReactorComponentBehavior and rather reference the prefabs inside the BaseReactorComponent itself .. then every component can bring its own prefab but still have a common core behaviour
If I want to make automated changes in editor, I generally use OnValidate. Over the years I've run into many issues with it so I'm looking for an alternative.
Example: There is a ladder object with a ladder script on it. When I change the Size variable on the ladder component, I want it to also change the size of the SpriteRenderer, which is set to Tiled mode. Unfortunately, OnValidate doesn't like you using SendMessage and you can get funky results.
I am NOT looking to solve this issue specifically. I already use an editor script to solve it. I am simply looking for a way to automate changes without using OnValidate so that I have more freedom.
I thought maybe an editor script could do this, but I'd really like to avoid having to do that for every single case individually. Maybe there is a way to do an editor script that could handle all scripts trying to do this? Maybe an editor script that works with interfaces?
EDIT: Changed the title and text of the post to be clearer.
I came up with a solution. I created a base class that all other classes in my game will inherit from. It looks like this:
public class BaseClass : MonoBehaviour {
[HideInInspector] public bool NeedsAutoUpdate;
protected void OnValidate() { NeedsAutoUpdate = !Application.isPlaying; }
public virtual void AutoUpdate() { NeedsAutoUpdate = false; }
}
So it's set up to track when OnValidate has happened only if it's not in play mode. If you want it to run in play mode too, just replace "!Application.isPlaying" with "true". Then there's a virtual AutoUpdate() which sets it back to false. Then I made an editor script.
[CustomEditor(typeof(BaseClass),true), CanEditMultipleObjects]
public class BaseClassEditor : Editor {
public override void OnInspectorGUI() {
base.OnInspectorGUI();
var Objs = serializedObject.targetObjects;
foreach (var Obj in Objs) {
var Base = Obj as BaseClass;
if (Base == null) continue;
EditorApplication.delayCall += () => {
if (Base.NeedsAutoUpdate) { Base.AutoUpdate(); }
};
}
}
}
This will check if anything that inherits from the base class needs an auto update, and then run it. Using delay call ensures inspectors are fully updated before it does this to avoid the issues that OnValidate has. Note that if you have a class inherit from BaseClass, but that class already has it's own CustomEditor then you'll need to have that custom editor inherit from BaseClassEditor instead of Editor to make it work.
Lastly, on any class I want to AutoUpdate, I make sure it is inheriting from BaseClass (at least somewhere down the line) and add an override of AutoUpdate.
public override void AutoUpdate() {
base.AutoUpdate();
// Make changes here
}
I'm having trouble showing a public field of a ScriptableObject which is a child of the component I'm inspecting. While I can easily do this in another way, I need this method to work for other variables. (ReorderableLists)
I simplified the problem, maybe I was just doing something obvious wrong, but I can't see what I'm doing wrong.
Code + error:
http://answers.unity3d.com/storage/temp/70243-error.png
class SomeComponent : MonoBehaviour{
public MyScriptable scriptable; //instantiated and saved as asset
}
[Serializable] class MyScriptable : ScriptableObject{
[SerializeField] public float value = 0.1f;
}
[CustomEditor(typeof(SomeComponent))] class SomeComponentEditor : Editor{
public override void OnInspectorGUI() {
if((target as SomeComponent).scriptable==null) (target as SomeComponent).scriptable = ScriptableObject.CreateInstance(typeof(MyScriptable)) as MyScriptable;
EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptable"));
//shows the asset
EditorGUILayout.PropertyField(serializedObject.FindProperty("scriptable").FindPropertyRelative("value"));
//error
}
}
To fix your code you can do this:
using UnityEditor;
using UnityEngine;
class SomeComponent : MonoBehaviour
{
public MyScriptable myScriptable;
}
class MyScriptable : ScriptableObject
{
public float myChildValue = 0.1f;
}
[CustomEditor(typeof(SomeComponent))]
class SomeComponentEditor : Editor
{
public override void OnInspectorGUI()
{
SomeComponent someComponent = target as SomeComponent;
if (someComponent.myScriptable == null)
someComponent.myScriptable = CreateInstance<MyScriptable>();
SerializedProperty myScriptableProp = serializedObject.FindProperty("myScriptable");
EditorGUILayout.PropertyField(myScriptableProp);
SerializedObject child = new SerializedObject(myScriptableProp.objectReferenceValue);
SerializedProperty myChildValueProp = child.FindProperty("myChildValue");
EditorGUILayout.PropertyField(myChildValueProp);
child.ApplyModifiedProperties();
}
}
To inspect a child object, you first need to create a SerializedObject version of it, which then can be searched for properties as usual.
Also, the Serializable attribute is not needed on classes which derive from ScriptableObject and the SerializeField attribute is only needed when serializing private fields; public fields are serialized by default in Unity.
Without knowing the original context of your code, your approach seems a little peculiar to me. ScriptableObject instances are meant to be used as asset files in your Unity project. Have you used the CreateAssetMenu attribute yet? Usually, you would create your assets manually via the menu and then plug them into your components. The way you are doing it, it won't be written to disk, so why use ScriptableObject and not just a normal class? But maybe it all makes sense in your actual context, so never mind if I'm wrong.
Hack solution found:
SerializedObject newserobj = new SerializedObject(serializedObject.FindProperty("scriptable").objectReferenceValue );
EditorGUILayout.PropertyField(newserobj.FindProperty("value"));
newserobj.ApplyModifiedProperties();