【unite 2017...

53

Upload: unitytechnologiesjapan

Post on 22-Jan-2018

15.551 views

Category:

Technology


1 download

TRANSCRIPT

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

Ian DundoreLead Developer Relations Engineer, Unity

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

Scriptable ObjectsWhat They Are & Why To Use Them

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

Scriptable Objects?

• “A class, derived from Unity’s Object class, whose references and fields can be serialized.”

• That statement is deceptively simple.

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

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.

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

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.

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

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.

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

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

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

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

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

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.

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

public class NPCHealthV2 : MonoBehaviour { public NPCHealthConfig config;

[System.NonSerialized] public int currentHealth; }

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

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

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

[CreateAssetMenu] ?

• Adds this to your “Create” menu:

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

Use Case #1: Shared Data Container

• ScriptableObject looks like this

• MonoBehaviour looks like this

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

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.

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

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!

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

Extra bonus

• Your scenes and prefabs now save & load faster.

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

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.

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

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.

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

Quick API Reminder

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

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

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

ScriptableObject callbacks

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

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

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.

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

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

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

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.

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

Native Object (Serialization, InstanceID)

C# Object (Your Code)

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

Native Object (Serialization, InstanceID)

C# Object (Your Code)

C# Reference

A Wild Reference Appears!

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

Native Object (Serialization, InstanceID)

C# Object (Your Code)

C# Reference

After Destroy()

X

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

Common Scenarios

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

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!

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

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…

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

class GameItem: ScriptableObject { public Sprite icon; public GameItemSlot slot;

public void OnEquip(GameCharacter c) { … } public void OnRemove(GameCharacter c) { … }

}

class GameItemSlot: ScriptableObject {}

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

It’s easy.

• Slots are just content, like everything else.

• Designers can add new values with no code changes.

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

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

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

Let’s add behavior!

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

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

} }

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

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

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

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?

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

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

}

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

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

} }

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

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

} }

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

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

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

Nice editor workflow!

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

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.

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

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…

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

class GameNPC: MonoBehaviour { public GameAI brain;

void Update() { brain.Update(this);

} }

abstract class GameAI: ScriptableObject { abstract void Update(GameNPC me);

}

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

class PassiveAI: GameAI { … }

class AggressiveAI: GameAI { … }

class FriendlyAI: GameAI { … }

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

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!

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

So in sum…

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

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.

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

Thank you!