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

879 Views

May 08, 17

スライド概要

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

こんな人におすすめ
・開発のイテレーションとワークフローを改善したい中級~上級レベルのプログラマー

受講者が得られる知見
・ScriptableObjectの内部的な仕組み
・ScriptableObjectを効果的に使用するためのワークフロー

講演動画:https://youtu.be/NawYyrB_5No

profile-image

リアルタイム3Dコンテンツを制作・運用するための世界的にリードするプラットフォームである「Unity」の日本国内における販売、サポート、コミュニティ活動、研究開発、教育支援を行っています。ゲーム開発者からアーティスト、建築家、自動車デザイナー、映画製作者など、さまざまなクリエイターがUnityを使い想像力を発揮しています。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
2.

Ian Dundore Lead Developer Relations Engineer, Unity

3.

Scriptable Objects What They Are & Why To Use Them

4.

Scriptable Objects? • “A class, derived from Unity’s Object class, whose references and fields can be serialized.” • That statement is deceptively simple.

5.

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.

6.

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.

7.

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.

8.

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

9.

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;

10.

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.

11.

public class NPCHealthV2 : MonoBehaviour { public NPCHealthConfig config; } [System.NonSerialized] public int currentHealth;

12.

[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;

13.

[CreateAssetMenu] ? • Adds this to your “Create” menu:

14.

Use Case #1: Shared Data Container • ScriptableObject looks like this • MonoBehaviour looks like this

15.

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.

16.

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!

17.

Extra bonus • Your scenes and prefabs now save & load faster.

19.

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.

20.

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.

21.

Quick API Reminder

22.

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.)

23.

ScriptableObject callbacks • OnEnable • Called when the ScriptableObject is instantiated/loaded. • Executes during ScriptableObject.CreateInstance() call. • Also called in the Editor after script recompilation.

24.

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.

25.

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

26.

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.

27.

Native Object C# Object (Serialization, InstanceID) (Your Code)

28.

A Wild Reference Appears! Native Object C# Object (Serialization, InstanceID) (Your Code) C# Reference

29.

After Destroy() X Native Object C# Object (Serialization, InstanceID) (Your Code) C# Reference

30.

Common Scenarios

31.

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!

32.

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…

33.

class GameItem: ScriptableObject { public Sprite icon; public GameItemSlot slot; } public void OnEquip(GameCharacter c) { … } public void OnRemove(GameCharacter c) { … } class GameItemSlot: ScriptableObject {}

34.

It’s easy. • Slots are just content, like everything else. • Designers can add new values with no code changes.

35.

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

36.

Let’s add behavior!

37.

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…? }

38.

class GameItemEffect: ScriptableObject { public GameCharacterStat stat; public int statChange; }

39.

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?

40.

abstract class GameItemEffect: ScriptableObject { public abstract void OnEquip(GameCharacter c); public abstract void OnRemove(GameCharacter c); }

41.
[beta]
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);
}

42.
[beta]
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);
}

44.

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) { … }

45.

Nice editor workflow!

46.

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.

47.

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…

48.

class GameNPC: MonoBehaviour { public GameAI brain; } void Update() { brain.Update(this); } abstract class GameAI: ScriptableObject { abstract void Update(GameNPC me); }

49.

class PassiveAI: GameAI { … } class AggressiveAI: GameAI { … } class FriendlyAI: GameAI { … }

50.

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!

51.

So in sum…

52.

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.

53.

Thank you!