Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

【Unite 2017 Tokyo】ScriptableObjectを使ってプログラマーもアーティストも幸せになろう


Published on

講演者:イアン・ダンドア(Unity Technologies)




Published in: Technology
  • Hello! Get Your Professional Job-Winning Resume Here - Check our website!
    Are you sure you want to  Yes  No
    Your message goes here

【Unite 2017 Tokyo】ScriptableObjectを使ってプログラマーもアーティストも幸せになろう

  1. 1. Ian Dundore
  2. 2. Scriptable Objects
  3. 3. Scriptable Objects? • “A class, derived from Unity’s Object class, whose references and fields can be serialized.” • That statement is deceptively simple.
  4. 4. Well, what’s a MonoBehaviour? • It’s a script. • It receives callbacks from Unity. • At runtime, it is attached to GameObjects. • Its data is saved into Scenes and Prefabs. • Serialization support; can be easily viewed in the Inspector.
  5. 5. Okay, what’s a ScriptableObject? • It’s a script. • It doesn’t receive (most) callbacks from Unity. • At runtime, it is not attached to any specific GameObject. • Each different instance can be saved to its own file. • Serialization support; can be easily viewed in the Inspector.
  6. 6. It’s all about the files. • MonoBehaviours are always serialized alongside other objects • The GameObject to which they’re attached • That GameObject’s Transform • … plus all other Components & MonoBehaviours on the GameObject • ScriptableObjects can always be saved into their own unique file. • This is makes version control systems much easier to use.
  7. 7. Shared Data should not be duplicated • Consider a MonoBehaviour that runs an NPC’s health. • Determines current & max health. • Changes AI behavior when health is low. • Might look like this
  8. 8. public class NPCHealth : MonoBehaviour { [Range(10, 100)] public int maxHealth; [Range(10, 100)] public int healthThreshold; public NPCAIStateEnum goodHealthAi; public NPCAIStateEnum lowHealthAi; [System.NonSerialized] public int currentHealth; }
  9. 9. Problems • Changing any NPC in a Scene or Prefab? • Tell everyone else not to change that scene or prefab! • Want to change all NPCs of some type? • Change the prefab (see above). • Change every instance in every Scene. • Someone mistakenly edits MaxHealth or HealthThreshold somewhere? • Write complex content-checking tools or hope QA catches it.
  10. 10. public class NPCHealthV2 : MonoBehaviour { public NPCHealthConfig config; [System.NonSerialized] public int currentHealth; }
  11. 11. [CreateAssetMenu(menuName = "Content/Health Config")] public class NPCHealthConfig : ScriptableObject { [Range(10, 100)] public int MaxHealth; [Range(10, 100)] public int HealthThreshold; public NPCAIStateEnum GoodHealthAi; public NPCAIStateEnum LowHealthAi; }
  12. 12. [CreateAssetMenu] ? • Adds this to your “Create” menu:
  13. 13. Use Case #1: Shared Data Container • ScriptableObject looks like this • MonoBehaviour looks like this
  14. 14. Benefits • Clean separation of concerns. • Changing the Health Config changes zero other files. • Make changes to all my Cool NPCs in one place. • Optional: Make a custom Property Drawer for NPCHealthConfig • Can show the ScriptableObject’s data inline. • Makes designers’ lives easier.
  15. 15. Potential benefit • Editing ScriptableObject instances during play mode? • No problem! • Can be good — let designers iterate while in play mode. • Can be bad — don’t forget to revert unwanted changes!
  16. 16. Extra bonus • Your scenes and prefabs now save & load faster.
  17. 17. Unity serializes everything • When saving Scenes & Prefabs, Unity serializes everything inside them. • Every Component. • Every GameObject. • Every public field. • No duplicate data checking. • No compression.
  18. 18. More data saved = slower reads/writes • Disk I/O is one of the slowest operations on a computer. • Yes, even in today’s world of SSDs. • A reference to a ScriptableObject is just one small property. • As the size of the duplicated data grows, the difference grows quickly.
  19. 19. Quick API Reminder
  20. 20. Creating ScriptableObjects • Make new instances: • ScriptableObject.CreateInstance<MyScriptableObjectClass>(); • Works both at runtime and in the Editor. • Save ScriptableObjects to files: • New asset file: AssetDatabase.CreateAsset(); • Existing asset file: AssetDatabase.AddObjectToFile(); • Use the [CreateAssetMenu] attribute, like before. • (Unity Editor only.)
  21. 21. ScriptableObject callbacks • OnEnable • Called when the ScriptableObject is instantiated/loaded. • Executes during ScriptableObject.CreateInstance() call. • Also called in the Editor after script recompilation.
  22. 22. ScriptableObject callbacks (2) • OnDestroy • Called right before the ScriptableObject is destroyed. • Executes during explicit Object.Destroy() calls, after OnDisable.
 • OnDisable • Called when the ScriptableObject is about to be destroyed. • Executes during explicit Object.Destroy() calls, before OnDestroy. • Executed just before Object is garbage-collected! • Also called in the Editor before script recompilation.
  23. 23. ScriptableObject lifecycle • Created and loaded just like other assets, such as Textures & AudioClips. • Kept alive just like other assets. • Will eventually get unloaded: • Via Object.Destroy or Object.DestroyImmediate • Or, when there are no references to it and Asset GC runs • e.g. Resources.UnloadUnusedAssets or scene changes
  24. 24. Warning! Unity is not a C# Engine. • ScriptableObjects, like other UnityEngine.Object classes, lead a dual life. • C++ side manages serialization, identity (InstanceID), etc. • C# side provides an API to you, the developer.
  25. 25. Native Object (Serialization, InstanceID) C# Object (Your Code)
  26. 26. Native Object (Serialization, InstanceID) C# Object (Your Code) C# Reference A Wild Reference Appears!
  27. 27. Native Object (Serialization, InstanceID) C# Object (Your Code) C# Reference After Destroy() X
  28. 28. Common Scenarios
  29. 29. Plain Data Container • We saw this earlier. • Great way to hold design data, or other authored data. • For example, use it to save your App Store keys. • Bake data tables in expensive formats down to ScriptableObjects. • Convert that JSON blob or XML file during your build!
  30. 30. Friendly, Easy-to-Extend Enumerations • Use different instances of empty ScriptableObjects to represent distinct values of the same type. • Basically an enum, but turns into content. • Consider, for example, an RPG Item…
  31. 31. class GameItem: ScriptableObject { public Sprite icon; public GameItemSlot slot; public void OnEquip(GameCharacter c) { … } public void OnRemove(GameCharacter c) { … } } class GameItemSlot: ScriptableObject {}
  32. 32. It’s easy. • Slots are just content, like everything else. • Designers can add new values with no code changes.
  33. 33. Adding data to existing content is simple. • Can always add some fields to the GameItemSlot class. • Maybe we want to add some types of items the user can’t equip. • Just add a bool isEquippable flag to existing GameItemSlot class
  34. 34. Let’s add behavior!
  35. 35. class GameItem: ScriptableObject { public Sprite icon; public GameItemSlot slot; public GameItemEffect[] effects; public void OnEquip(GameCharacter c) { // Apply effects here…? } public void OnRemove(GameCharacter c) { // Remove effects here…? } }
  36. 36. class GameItemEffect: ScriptableObject { public GameCharacterStat stat; public int statChange; }
  37. 37. Should GameItemEffect just carry data? • What if designers want to do something other than just add stats? • Every effect type’s code has to go into GameItem.OnEquip • But ScriptableObjects are just classes… • Why not embed the logic in the GameItemEffect class itself?
  38. 38. abstract class GameItemEffect: ScriptableObject { public abstract void OnEquip(GameCharacter c); public abstract void OnRemove(GameCharacter c); }
  39. 39. class GameItemEffectAddStat: GameItemEffect { public GameStat stat; public int amountToAdd; public override void OnEquip(GameCharacter c) { c.AddStat(statToChange, amountToAdd); } public override void OnRemove(GameCharacter c) { c.AddStat(statToChange, -1 * amountToAdd); } }
  40. 40. class GameItemEffectTransferStat: GameItemEffect { public GameStat statToDecrease; public GameState statToIncrease; public int amountToTransfer; public override void OnEquip(GameCharacter c) { c.AddStat(statToReduce, -1 * amountToAdd); c.AddStat(statToIncrease, amountToTransfer); } public override void OnRemove(GameCharacter c) { c.AddStat(statToReduce, amountToAdd); c.AddStat(statToIncrease, -1 * amountToTransfer); } }
  41. 41. class GameItem: ScriptableObject { public Sprite icon; public GameItemSlot slot; public GameItemEffect[] effects; public bool OnEquip(GameCharacter c) { for(int i = 0; i < effects.Length; ++i) { effects[i].OnEquip(c); } } public bool OnRemove(GameCharacter c) { … } }
  42. 42. Nice editor workflow!
  43. 43. Serializable game logic! • Each effect now carries only the data it needs. • Applies its operations through a simple (testable?) interface. • Designers can drag & drop everything. • Add new logic without refactoring existing content.
  44. 44. Serializable… delegates? • Consider a simple enemy AI, with a few different types of behavior. • Could just pack this all into a MonoBehaviour, use an enum or variable to determine AI type. • Or…
  45. 45. class GameNPC: MonoBehaviour { public GameAI brain; void Update() { brain.Update(this); } } abstract class GameAI: ScriptableObject { abstract void Update(GameNPC me); }
  46. 46. class PassiveAI: GameAI { … } class AggressiveAI: GameAI { … } class FriendlyAI: GameAI { … }
  47. 47. Easier to implement, extend & test • Imagine our designers, later on, wanted to add an AI that would attack you when you attacked one of its friends. • With this model: • Add a new AI type • Allow the designer to define an array of friends. • When one is attacked, set the current AI module to an AggressiveAI. • No changes to other code or content needed!
  48. 48. So in sum…
  49. 49. ScriptableObjects are great! • Use them to make version control easier. • Use them to speed up data loading. • Use them to give your designers an easier workflow. • Use them to configure your logic via content.
  50. 50. Thank you!