【Unity】Scriptable object 入門と活用例

411 Views

October 03, 17

スライド概要

ScriptableObjectとはどういった物か、ScriptableObjectを利用すると何が良いのかといった基本的な内容から、実際にScriptableObjectを活用した開発の効率化について紹介します。

このスライドは『ヴァルキリーコネコクト』のクオリティを支える開発技術最前線で講演した内容のスライドです。 http://www.a-tm.co.jp/recruit/news/event-6944/

profile-image

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

シェア

またはPlayer版

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

ダウンロード

関連スライド

各ページのテキスト
1.

Scriptable Object 入門と活用例 unity

2.

Unityと オブジェクト unity

3.

using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; public class CallSO : MonoBehaviour { public BaseSO so{ get; set; } public void Call (Text label) { so.Push (label); } } ●シーンにGameObjectを配置する ●GameObjectにComponentを追加する

4.

音を出す 移動する ゲーム進行管理 他のオブジェクト と接触 エンジンに モデルの 描画を依頼 物理演算 ●コンポーネントが、オブジェクトの振る舞いを決める ●オブジェクト同士が干渉してゲームを作る

5.

●GameObjectに追加したコンポーネントの フィールドや参照関係を埋めて、振る舞いを調整

6.

●(比較的静的な)世界が出来る Scene / Game Object / Component の関係

7.

Scene / Game Object / Component の関係 Scene GameObject GameObject Component Component Component Component

8.

Prefab GameObject Component Component ●PrefabでGame Objectをファイル(アセット)として書き出す

9.

Instantiate ●Prefabをシーンに配置したり、動的にPrefabをInstantiate(複製)して ゲームを表現するのが、Unityの最も一般的な使い方 Prefab (GameObject & Monobehaviour) では面倒なケース

10.

Prefab (GameObject & Monobehaviour) では面倒なケース ●使用する際にInstantiateする ●再生停止時に変更した情報が失われる ●GameObjectがいつも一緒に生成される ●沢山のフィールドを持つとInstantiateのコストが上がる

11.

Scriptable Object ●UnityのObjectから派生したクラス

12.

Scriptable Objectとは? ●UnityのObjectから派生したクラス ●アセットとして保存(シリアライズ)できる ●Objectへの参照やフィールドの情報を保持したまま、 シリアライズできる ●保存したデータはInspectorから簡単に確認できる Scriptable Objectは、Object派生クラスの一つ

13.

Scriptable Objectは、Object派生クラスの一つ Object ScriptableObject Component Transform RectTransform Monobehaviour Renderer GameObject Texture Sprite アセットとしてプロジェクトに格納・使用できる

14.

アセットとしてプロジェクトに格納・使用できる Objectへの参照を保持したままシリアライズ

15.

Objectへの参照を保持したままシリアライズ

16.

なるほど… Prefabの下位互換か…

17.

ScriptableObjectは Prefab (GameObject & Monobehaviour) とは違うもの Monobehaviourを見直す

18.

Monobehaviourを見直す ●Monobehaviourはスクリプト ●様々なコールバックをエンジン(Unity)から受け取れる ●GameObjectに追加してはじめて動作する ●SceneもしくはPrefabに保存(シリアライズ)できる ●保存したデータはInspectorから簡単に確認できる ●ゲーム再生終了時にリセットされる PrefabにはGameObjectとTransformが追加される

19.

PrefabにはGameObjectとTransformが追加される Game Object Name Tag Layer Transform Vector3 Quaternion Component HP TYPE Scriptable Objectには余計なオブジェクトはつかない HP TYPE

20.

HP TYPE HP TYPE HP TYPE コールバックの量が違う Monobehaviour Scriptable Object

21.

Monobehaviour 64 コールバック Scriptable Object 4 コールバック ※スクリプトに定義したコールバックのみ呼ばれる 使用方法が違う 参照する Instantiateで

22.

Monobehaviour Instantiateで Sceneに生成 再生終了時に リセット Scriptable Object 参照する 再生終了時に リセットされない ●Scriptable Objectはスクリプト ●殆どのコールバックを受け取らない

23.

●Scriptable Objectはスクリプト ●殆どのコールバックを受け取らない ●Game Objectにアタッチする必要はない ●インスタンス毎に異なるアセットとして保存できる ●保存したデータはInspectorから簡単に確認できる ●ゲーム再生時にリセットされない Monobehaviour VS Scriptable Object スクリプト? コールバック Monobehaviour ●YES ●多い Scriptable Object ●YES ●少ない

24.

Monobehaviour スクリプト? コールバック Game Objectに 追加する必要 余計なオブジェクト 保持するフィールドの値 ゲーム終了時の動作 ●YES ●多い ●ある ●GameObjectとTransformが付く ●簡単に確認できる ●リセットされる Scriptable Object ●YES ●少ない ●ない ●インスタンスのみで保存 ●簡単に確認できる ●リセットされない どんな感じで 使うの?

25.

どんな感じで 使うの? Scriptable Objectを使ってみる Scriptable Objectのキーワード ●ScriptableObject ●CreateAssetMenu

26.

●ScriptableObject ●CreateAssetMenu PrefabをScriptableObjectに変更 ・キャラクターのPrefabをScriptableObjectに変更してみる ・最大HPとATKとDEFを持つ ・HPの状況によって行う挙動

27.

・キャラクターのPrefabをScriptableObjectに変更してみる ・最大HPとATKとDEFを持つ ・HPの状況によって行う挙動 public class CharacterAI : MonoBehaviour { [SerializeField, Range (0, 100)] int MaxHP = 100;

28.

} [SerializeField, Range (0, 100)] int MaxHP = 100; [SerializeField, Range (0, 100)] int HPThreshold = 25; public int hp{ get; set; } public AIState goodhealth; public AIState badHealth; } [CreateAssetMenu] public class CharacterConfig : ScriptableObject { public int MaxHP = 100; public int HPThreshold = 25; public AIState goodHealth; public AIState badHealth; } public class CharacterAI : MonoBehaviour { [SerializeField] CharacterConfig config; public int hp{ get; set; } }

29.

{ public int MaxHP = 100; public int HPThreshold = 25; public AIState goodhealth; public AIState badHealth; } CharacterConfig config; public int hp{ get; set; } } Scriptable Objectを継承する

30.

Scriptable Objectを継承する [CreateAssetMenu] public class CharacterConfig : ScriptableObject { public int MaxHP = 100; public int HPThreshold = 25; public AIState goodhealth; public AIState badHealth; } ●ScriptableObjectからアセットを作成するメニューを追加

31.

●ScriptableObjectからアセットを作成するメニューを追加 public class CharacterAI : MonoBehaviour { [SerializeField] CharacterConfig config; public int hp{ get; set; } } フィールドを公開

32.

Scriptable Objectの持つコールバック ●Awake スクリプト開始時に呼ばれる ●OnEnable オブジェクトロード時に呼ばれる ●OnDisable アンロード時に呼ばれる

33.

●OnEnable オブジェクトロード時に呼ばれる ●OnDisable アンロード時に呼ばれる ●OnDestroy インスタンス破棄時に呼ばれる Demo

34.

Demo

35.

unity

36.

unity

37.

で? Scriptable Objectは 何の役に立つの?

38.

で? Scriptable Objectは 何の役に立つの? 例えばScriptable Object活用のアイディア ●シーン間・GameObject間で使いまわせるデータテーブル ●ロード・インスタンス化の高速化 ●動作の切り替え ●ステータスの共有

39.

●動作の切り替え ●ステータスの共有 ●オブジェクトのバインド

40.

ScriptableObject ●Scene間で使用するインスタンスを共有

41.

●同じSOを参照するならば、同じインスタンスを参照

42.

SPEED UP!! ●Scriptable Objectのパラメータを変更は、参照元Prefabにも反映

43.

●ScriptableObjectのデータ共有は高速化にもメリット Serialize Deserialize

44.

Serialize Deserialize ●UnityはシーンやPrefabを丸ごとシリアライズし、丸ごとデシリアライズする

45.

●シリアライズは本当にそのままシリアライズする オブジェクト: 102個 コンポーネント: 510個 フィールド数: すごい デシリアライズのコスト プライスレス Sceme

46.

●完全に同一のオブジェクトがあっても、丸ごとシリアライズ Scene

47.

●共通項目はScriptableObjectを使い、デシリアライズの項目を減らす staticでは駄目なのか? ●シリアライズ出来ない為ホットリロード時にリセットされる ●Inspector等から調整するには特別なコードが必要 ●アクセスするにはスクリプトが必要 ●同一スクリプト異Prefabで少し面倒くさい ●スクリプトで全て管理するなら、デメリットは

48.

●スクリプトで全て管理するなら、デメリットは 無視出来るかもしれない staticでは駄目なのか? ●シリアライズ出来ない為ホットリロード時にリセットされる ●Inspector等から調整するには特別なコードが必要 ●アクセスするにはスクリプトが必要 ●同一スクリプト異Prefabで少し面倒くさい ●プログラム実行中にプログラムを書き換えても 即座に動作に反映される挙動。 static等のシリアライズ出来ない項目は失われる

49.

●同一スクリプト異Prefabで少し面倒くさい ●スクリプトで全て管理するなら、デメリットは 無視出来るかもしれない NPC Config 1 弱い NPC Config 2 強い

50.

NPC Config 2 強い ●同一ScriptでもSOを切り替える事でパラメータを切替 勇者 行動 逃げる たたかう

51.

たたかう ●挙動の差替もできる AI 追跡AI ・視認可能な距離 ・視認可能な角度 ・攻撃開始する距離 固定砲台AI ・視認可能な距離 ・回転速度 ・攻撃開始する距離 ・距離に応じて使用する弾頭

52.

●パラメータと振る舞いを設定 追跡 固定砲台 ●ユニット毎にAIを設定

53.

●ユニット毎にAIを設定 追跡AI ・視認可能な距離 ・追跡範囲 OR 砲台AI ・視認可能な距離 ・視認可能な角度 ・攻撃開始する距離 ●ユニット毎のパラメータはユニット自身が持つ

54.

●ユニット毎のパラメータはユニット自身が持つ (SOを上書きすると、全てのユニットに影響する為) ゲーム進行 ●インスタンスを共有

55.

●インスタンスを共有 マネージャー 参照 呼出 登録 Player NPC ●ScriptableObjectにGameObjectを登録、ScriptableObject経由で呼び出す

56.

●ScriptableObjectにGameObjectを登録、ScriptableObject経由で呼び出す マネージャー Prefab/Scene Player Scene NPC ●シーンを跨いだオブジェクトも簡単に参照

57.

●シーンを跨いだオブジェクトも簡単に参照 ●ScriptableObjectはゲーム停止時にリセットされない ●ゲームをプレイしながらパラメータを調整

58.

●ScriptableObjectはゲーム停止時にリセットされない ●ゲームをプレイしながらパラメータを調整 Demo

59.

ScriptableObject

60.

[CreateAssetMenu] public class TankConfig : ScriptableObject { public float speed; public float rotate; }

61.
[beta]
public class TankController : MonoBehaviour
{
[SerializeField] TankConfig config;
public void Update ()
{
var v = Input.GetAxis ("Vertical");
var x = Input.GetAxis ("Horizontal");
if (v != 0)
GetComponent<NavMeshAgent> ().Move (transform.forward * v * config.speed);
if (x != 0)
transform.localRotation *= Quaternion.AngleAxis (x * config.rotate, Vector3.up);
}
}
62.

unity

63.

unity

64.

unity

65.

unity

66.

NPC Config 1 弱い NPC Config 2 強い

67.

unity

68.

unity

69.

unity

70.

unity

71.

AI 動作1 動作2

72.

public class TankController : MonoBehaviour { [SerializeField] TankAIBase ai; public void Update () { ai.Do(gameObject); } } public abstract class TankAIBase : ScriptableObject { public virtual void Do (GameObject obj) { } }

73.

[CreateAssetMenu] public class TanksStand : TankAIBase{} [CreateAssetMenu] public class TankRotate : TankAIBase { public float rotate = 2; public override void Do (GameObject obj) { obj.transform.localRotation *= Quaternion.AngleAxis (rotate, Vector3.up); } }

74.
[beta]
[CreateAssetMenu]
public class TankMove : TankAIBase
{
public float speed = 0.5f;
public override void Do (GameObject obj)
{
obj.GetComponent<NavMeshAgent>().Move(obj.transform.forward * speed);
}
}
75.

unity

76.

unity

77.

Manager

78.

Manager public class TankController : MonoBehaviour { public void Move(float accel)

79.
[beta]
public class TankController : MonoBehaviour
{
public void Move(float accel)
{
GetComponent<NavMeshAgent>().Move(transform.forward * (accel * 0.2f));
}
}
[CreateAssetMenu]
public class TankData : ScriptableObject
{
public TankController tankController{get; set;}
public void Up () { tankController.Move(5); }
public void Down () { tankController.Move(-5); }
}
[DefaultExecutionOrder (-100)]
public class EventTriggerBehaviour : MonoBehaviour
80.
[beta]
[DefaultExecutionOrder (-100)]
public class EventTriggerBehaviour : MonoBehaviour
{
[SerializeField]
UnityEvent onAwake = new UnityEvent (), onDestroy = new UnityEvent ();
void Awake ()
{
onAwake.Invoke ();
}
void OnDestroy ()
{
onDestroy.Invoke ();
}
81.

unity

82.

unity

83.

unity

84.

unity

85.

unity

86.

unity

87.

応用 もっとScriptable Objectを 使いこなすTips

88.

●Scriptable Objectのアイコンを変えて、一覧性を上げる

89.

●ScriptableObjectのコードアイコンを変更すると、SOのアイコンが変わる ソースコード

90.

ソースコード [クラス名] Icon Assets/Gizmos

91.

●エディター拡張でScriptableObjectのUIを変更 拡張するクラスを登録 [CustomEditor (typeof(Config))] public class ConfigEditor : Editor { [CreateAssetMenu] public class Config : ScriptableObject {

92.

[CustomEditor (typeof(Config))] public class ConfigEditor : Editor { public override void OnInspectorGUI () { base.OnInspectorGUI(); // レイアウト記述 } } [CreateAssetMenu] public class Config : ScriptableObject { public Item[] items = new Item[0]; [System.Serializable] public class Item { public Texture texture; public int param; public string name; } } PropertyDrawerを使う

93.

[CreateAssetMenu] public class Config : ScriptableObject { public Item[] items = new Item[0]; [System.Serializable] public class Item { [PreviewTexture] public Texture texture; [Range(0, 100)] public int param; public string name; } } OnValidateを使う エディターで値を 変更時に呼ばれる

94.

エディターで値を 変更時に呼ばれる [CreateAssetMenu] public class Data : ScriptableObject { [SerializeField] int count; void OnValidate () { count = Mathf.Clamp (count, 0, 99); } }

95.

●アセットを外部ファイルから作る 何故EXCEL? ●数値編集・入力で最も優れたツール ●データテーブルを作るのに便利 ●バランスの良いゲームは、

96.

●データテーブルを作るのに便利 ●バランスの良いゲームは、 ランダムではなく都合の良いテーブルを使う事がある ●Excelは実行時に読むようなデータ構造ではない

97.

●エンジンが読みやすいScriptable Objectに変換する AssetPostprocessorで アセットのインポート処理に 割り込み 既にあればLoadAssetAtPath 無ければCreateAssetで ScriptableObjectを取得

98.
[beta]
既にあればLoadAssetAtPath
無ければCreateAssetで
ScriptableObjectを取得
EditorUtility.SetDirtyで
ScriptableObjectを生成
インポート処理に割込
public class param1_importer : AssetPostprocessor
{
static void OnPostprocessAllAssets (
string[] importedAssets, string[] deletedAssets, string[]
string[] movedFromAssetPaths)
{
ExcelItem data = (ExcelItem)AssetDatabase.LoadAssetAtPath (exportPath, typeof(ExcelItem));
if (data == null) {
data = ScriptableObject.CreateInstance<ExcelItem> ();
AssetDatabase.CreateAsset ((ScriptableObject)data, exportPath);
99.
[beta]
ExcelItem data = (ExcelItem)AssetDatabase.LoadAssetAtPath (exportPath, typeof(ExcelItem));
if (data == null) {
data = ScriptableObject.CreateInstance<ExcelItem> ();
AssetDatabase.CreateAsset ((ScriptableObject)data, exportPath);
}
// インポート処理
ScriptableObject obj = AssetDatabase.LoadAssetAtPath (
exportPath, typeof(ScriptableObject)) as ScriptableObject;
EditorUtility.SetDirty (obj);
}
}
ScriptableObject作成
変更を反映
100.

unity

101.

●ScriptedImporterでアセットをScriptableObjectとして使う ScriptedImporter

102.

●アセットのように使える

103.

{"index":100, "name":"layer test"}

104.

unity

105.

unity

106.
[beta]
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
  m_ObjectHideFlags: 0
  m_PrefabParentObject: {fileID: 0}
  m_PrefabInternal: {fileID: 0}
  m_GameObject: {fileID: 0}
  m_Enabled: 1
  m_EditorHideFlags: 0
  m_Script: {fileID: 11500000, guid: 64e7a24fcd53d4ec4b7372e57df90a95, type: 3}
  m_Name: Data
  m_EditorClassIdentifier:
  index: 0
  name:
107.

●ScriptableObjectはエディターではYAMLフォーマット

108.
[beta]
開発
リリース
ScriptableObjectの中身を独自にデシリアライズ
コールバックを受ける
インターフェース
[CreateAssetMenu]
public class SampleDictionary : ScriptableObject, ISerializationCallbackReceiver
{
const string path = "item.json";
public Item item = new Item ();
public void OnBeforeSerialize ()
{
var json = JsonUtility.ToJson (item);
File.WriteAllText (path, json);
}
public void OnAfterDeserialize ()
109.

public void OnAfterDeserialize () { if (File.Exists (path)) { var json = File.ReadAllText (path); JsonUtility.FromJsonOverwrite (json, item); } } } エディターで値を設定 動的に値を変更可能 再生前に巻き戻る

110.

再生前に巻き戻る ●Scriptable Objectのリセット アイディア ●ゲーム開始時にScriptable Objectをリセットする処理を追加 ●シリアライズするデータとゲーム中に触るデータは分ける ●ゲーム再生終了時にScriptable Objectをアンロード

111.

ゲーム開始時にScriptable Objectをリセット public abstract class ResettableScriptableObject : ScriptableObject { public abstract void Reset (); } [CreateAssetMenu] public class SaveData : ResettableScriptableObject { public int count = 0; public override void Reset ()

112.

public class SaveData : ResettableScriptableObject { public int count = 0; public override void Reset () { //初期化コード count = 0; } } ゲーム開始時にScriptable Objectをリセット public class DataResetter : MonoBehaviour { public ResettableScriptableObject[] resettableScriptableObjects; private void Awake () { for (int i = 0; i < resettableScriptableObjects.Length; i++) { resettableScriptableObjects[i].Reset ();

113.

for (int i = 0; i < resettableScriptableObjects.Length; i++) { resettableScriptableObjects[i].Reset (); } } } データを分ける public abstract class ResettableScriptableObject : ScriptableObject { [SerializeField] int _count = 0; public int count{get; set;} void OnEnable() { count = _count; }

114.

count = _count; } } アンロードする public class ResettableScriptableObject : ScriptableObject { protected virtual void OnEnable() { #if UNITY_EDITOR if (EditorApplication.isPlayingOrWillChangePlaymode == true ){ UnityEditor.EditorApplication.playModeStateChanged += (state) => { if ( EditorApplication.isPlayingOrWillChangePlaymode == false ) { Resources.UnloadAsset(this); } }; } #endif }

115.

if ( EditorApplication.isPlayingOrWillChangePlaymode == false ) { Resources.UnloadAsset(this); } } }; } #endif } } アンロードする [CreateAssetMenu] public class Config : ResettableScriptableObject { public float _count; }

116.

Reset ●Resetメソッド呼び出し により初期化 ●起動するシーン内に リセットを呼び出す コンポーネントが必要 ●実行中の再初期化は 比較的容易 ●ゲーム再生時でも直接 値を操作できる データ分け ●アクセス時に初期化 ●実行中の再初期化は容易 ●実行中に値を編集したい 場合は、特別なコードが 必要になる Unload ●再生終了時に初期化 ●実行中の再初期化は システムのリソース管理 の理解が必要 ●再生前にプロジェクトの セーブが必要

117.

生存戦略 Scriptable Objectの 寿命とアンロード

118.

Scriptable Objectの 寿命とアンロード Unity ●ScriptableObjectをデシリアライズ ●インスタンスIDを付与 (参照関係の解決に使用する) C# ●デシリアライズしたデータを スクリプトに流し込む ●コールバックの実体を実行 ●動作を実装

119.

●UnityエンジンはC++、スクリプトはC# 参照があった時に呼ばれる Awake OnEnable OnDisable OnDestroy Scriptable Objectのライフサイクル

120.

OnDestroy Scriptable Objectのライフサイクル オブジェクトのデシリアライズ 参照関係の解決 コンポーネントの初期化(Awake) コンポーネントの初期化(OnEnable) コンポーネントの初期化(Start) コンポーネント実行(Update) ここにSOのOnEnable Scriptable Objectのライフサイクル

121.

コンポーネント実行(Update) Scriptable Objectのライフサイクル Sprite Texture Image Material Shader Scriptable Object ●参照されると関係するオブジェクトをロード

122.

●参照されると関係するオブジェクトをロード Sprite Texture Image Material Shader Scriptable Object ●テーブルが生きてるならば、同一のインスタンスにアクセスする

123.

●テーブルが生きてるならば、同一のインスタンスにアクセスする C# Instance Scriptable Object 例) event

124.

C# Instance Scriptable Object Scriptable Object ●再度0bjectを作ると、新しく参照関係を構築する

125.

●再度0bjectを作ると、新しく参照関係を構築する Scene 1 Scene 2 ●移動先のシーンでSOが参照されている場合、破棄されない

126.

●移動先のシーンでSOが参照されている場合、破棄されない Instance A Instance Bが 新しく作られる 破棄 Scene 1 Scene 2 Scene 3 ●参照されていないシーンに移動すると、アセットは破棄され、 参照されると別インスタンスIDを持って再びロードされる

127.

●参照されていないシーンに移動すると、アセットは破棄され、 参照されると別インスタンスIDを持って再びロードされる 明確に破棄したい場合 ●Destroy CreateInstanceで生成したインスタンスを破棄 ●UnloadUnusedAsset 被参照のアセットを解放 ●Unload 指定したアセットを強制開放

128.

どんな時に注意すべき?

129.

●データテーブルとして使用してる時? NO、静的なデータでは問題は起こらない

130.

●データテーブルとして使用してる時? NO、静的なデータでは問題は起こらない ●オブジェクトのバインド? Unloadで開放する場合は少し注意が必要。UnloadUnusedAssetsを使う場合はOK

131.

●オブジェクトのバインド? Unloadで開放する場合は少し注意が必要。UnloadUnusedAssetsを使う場合はOK Scene 1 Scene 2 Scene 3 ●ゲーム進行等を保持する場合 非参照の状況でインスタンスが破棄される可能性があるので注意

132.

●ゲーム進行等を保持する場合 非参照の状況でインスタンスが破棄される可能性があるので注意 Scene 1 Unload! Scene 3 ●逆に開放したい場合、シーン移行直前でSOをアンロードすると、 次のシーンでは新鮮なインスタンスにアクセスする

133.

Pluggable AI With Scriptable Objects LIVE TRAINING ARCHIVE unity

134.

unity

135.

Thank you unity