ScriptableObject 초기화 - ScriptableObject chogihwa

Suggest a change

Success!

Thank you for helping us improve the quality of Unity Documentation. Although we cannot accept all submissions, we do read each suggested change from our users and will make updates where applicable.

Close

Submission failed

For some reason your suggested change could not be submitted. Please <a>try again</a> in a few minutes. And thank you for taking the time to help us improve the quality of Unity Documentation.

Close

Your name Your email Suggestion*

Cancel

Switch to Manual

Description

Reset to default values.

Reset is called when the user hits the Reset button in the Inspector's context menu or when adding the component the first time. This function is only called in editor mode. Reset is most commonly used to give good default values in the Inspector.

// Sets target to a default value.
// This could be used in a follow camera.

using UnityEngine;

public class Example : ScriptableObject { public GameObject target;

void Reset() { //Output the message to the Console Debug.Log("Reset"); if (!target) target = GameObject.FindWithTag("Player"); } }

  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges

  • Home /

4

Every video/guide about why you should use Scriptable Objects seems to very quickly gloss over the fact that the data in a Scriptable Object does not get reset when you exit play mode in Unity.

i.e. If I have player health stored in an SO and I enter play mode to test/debug my game, and my player loses some health in that play session, the health in the SO stays at that level next time I enter play mode. Obviously making it a nightmare to play test and debug.

Potential Solution 1 - Create Instances

I've seen some people say they just create an instance of the SO at runtime, then the changes get made to that instance and the instance dies when you exit play mode so you're all good... but that seems to have a huge problem unless I'm missing something. In most cases we're using SOs to hold shared data, so if you create a separate instance then that's no longer shared data - its a unique instance and other items referencing the SO will not see the same data. Now of course you could just create one instance and have all your other components refer to that one instance, but aren't you basically ending up with a singleton and defeating the point of using SOs in the first place?

Potential Solution 2 - Reset Values To Defaults

So given how bad the first "solution" seems to be, this is the only real option I can think of. The idea being that you have a script that runs when the game first loads (or on a per scene basis) and automatically resets values on every SO to be their correct default values.

But where should that script get its values from? Obviously I want to avoid hard coding them in the script. So these are the two best options I can think of:

1. It could pull them from an external file (XML etc) that I manually set the correct default values in.
2. Instead of getting the values from an external file, the script would reference another SO in my project that acts as a "template". This template SO would never be referenced elsewhere in my code so it would stick to whatever values I enter in the designer and never change. This approach seems better as it keeps everything easily visible and editable in the Unity designer, but it also feels a bit hacky and weird having to create two SOs for every SO. They would both be holding essentially the same data but one of them I just let get messed up by play testing and one of them I have to remember to never drag into any scenes or access in code (other than the initial loading script). Doesn't that end up being just as bad as using uninstantiated prefabs instead of SOs, where you have to remember certain prefabs are "special" and not to be used in scenes like others. I guess I could just have one master "default settings" SO that contains default values for all other SOs, and the script reads from that and populates the relevant SOs, which feels a bit less weird but still not ideal.

With either of those options though there's several issues I'm not keen on or unsure about. Like how this means for every single SO in the entire game, I need to remember to add its default values to my template/master list, and then add code to reset it to those values.

So yeah I'm keen to hear how other people deal with this stuff in the real world, and if I'm on the right track or if there's some better alternative I've not thought of (quite likely). I should point out I'm very new to game development (but have several years of experience with .NET desktop app development) so by all means correct things I've got wrong or point me in a different direction if I'm suggesting doing things in a weird way :)

I don't think you're suppose to change things in scriptable objects. They're for "stats" type data. Like an army tank prefab would link to the scriptableObject with data on that tank type.

Yeah the more I'm playing around and actually trying to use them in a project, the more I'm inclined to agree. What was tripping me up was that I was using player health as my first example, as that is something that multiple areas of my project are going to need to reference and I wanted to use a scriptable object for that because it is shared state (ins$$anonymous$$d of a singleton that all of these areas would have to reference).

But now I've realised I can just have my player/enemy health scriptable objects store the max health each entity should start with, then the actual runtime value of how much health the player currently has can be on that same scriptable object (so that everything can have access to it) but marked as [NonSerializable]. I'm not sure if this has any unwanted side effects though... but so far it seems to make it work how I want. Unity won't store that value in the SO asset, so it doesn't persist anywhere at all.

1

Best Answer

Answer by Meishin · Sep 10, 2019 at 04:26 AM

@Chris128,

If you don't want to instance SO the easiest way to deal with that is by creating a/any serialized private field used in editor, and another during runtime

 public class Ability : ScriptableObject
 {
     // Editor value
     [SerializeField] private float baseCoolDown = 1f;            // Base cooldown
     // Internal variables
     // Ability CoolDown
     private float coolDown;
     public float CoolDown { get { return coolDown; } }
 
     // Initialize coolDown with editor's value
     private void OnEnable()
     {
         coolDown = baseCoolDown;
     }
 
     // You can also use OnAfterDeserialize for the other way around
     public void OnAfterDeserialize() 
     {
     }
 }

Sure it kinda looks clumzy tho if your code gets more complicated it allows you to easily and safely control your class own properties.


Also instancing SO could make sense since you are changing your SO's values at runtime which means you're using SO as any custom class but with standardized initial values - like for spells in a fantasy game for ex, if i want my player A and B to have a cooldowns on this ability depending on their stats, i'll define a baseCooldown and then at initialization (or even when the ability is triggered) modify their cooldown depending on their stats (i.e. independantly for player A and B).

Yeah I ended up co$$anonymous$$g to that conclusion and doing basically the same thing but in reverse, by marking my runtime field with [NonSerialized]. Same end result really.

1

Answer by joshrs926 · Aug 03 at 01:28 PM

Here's another useful tidbit to add onto what has been said. Create an interface for clearing on exit play mode. Then in a static class create some methods to find all ScriptableObject assets in the project and, for each one, call its Reset method if it implements the interface. Use [InitializeOnLoadMethod] on a static method to subscribe those methods to EditorApplication.playModeStateChanged.

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 #if UNITY_EDITOR
 using UnityEditor;
 
 static class SOPlayModeResetter
 {
     [InitializeOnLoadMethod]
     static void RegisterResets()
     {
         EditorApplication.playModeStateChanged += ResetSOsWithIResetOnExitPlay; 
     }
 
     static void ResetSOsWithIResetOnExitPlay(PlayModeStateChange change)
     {
         if (change == PlayModeStateChange.ExitingPlayMode)
         {
             var assets = FindAssets<ScriptableObject>();
             foreach (var a in assets)
             {
                 if (a is IResetOnExitPlay)
                 {
                     (a as IResetOnExitPlay).ResetOnExitPlay();
                 }
             }
         }
     }
 
     static T[] FindAssets<T>() where T : Object
     {
         var guids = AssetDatabase.FindAssets($"t:{typeof(T)}");
         var assets = new T[guids.Length];
         for (int i = 0; i < guids.Length; i++)
         {
             var path = AssetDatabase.GUIDToAssetPath(guids[i]);
             assets[i] = AssetDatabase.LoadAssetAtPath<T>(path);            
         }
         return assets;
     }
 }
 #endif
 
 public interface IResetOnExitPlay
 {
     public void ResetOnExitPlay();
 }

And an example ScriptableObject:

 using UnityEngine;
 
 public class SOExample : ScriptableObject, IResetOnExitPlay
 {
     [SerializeField] int value;
     public int Value { get; set; }
 
     private void OnEnable()
     {
         Value = value;
     }
 
     public void ResetOnExitPlay()
     {
         Value = value;
     }    
 }

0

Answer by Djaydino · May 26, 2021 at 09:56 PM

Hi. We used a different way which worked for us :

have an editor script with a

     private static void OnPlayModeChanged(PlayModeStateChange state)
     {
         if (!EditorApplication.isPlaying)
         {
             
         }
        else
         {
             
         }
     {

When start play, store the things that you not want to change

And when stopped you can have a popup to accept / decline for some that you might want to change after all. (don't forget to use a EditorUtility.SetDirty(); on the scriptables )

0

Answer by Reijii · Dec 03, 2021 at 09:35 PM

Alternatively, you can save SO values as Preset in upper right corner

Upper right corner? I don't know where do you mean?

I have been checking this, and tried some things in my project. But still I don't find how I can use Presets to keep my SO state get reseted when I exit Play Mode. Can you expand the explanation?

Your answer

ScriptableObject 초기화 - ScriptableObject chogihwa

Hint: You can notify a user about this post by typing @username

Attachments: Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Ask the Experts - Unite 2022

Are you looking for some help with your project? Join us today, on November 1, from 9 am EDT to 2 pm EDT over on Discord for our

Unite 2022 Virtual Ask the Experts

Follow this Question