#世にも奇妙な
先日ScriptableObjectを使用していたところ奇妙な現象が起こりました。
こういうスクリプトを書きました。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class SO : ScriptableObject
{
}
[CreateAssetMenu(fileName = "SOInherit", menuName = "CreateSO")]
public class SOInherit : SO
{
public string val;
}
ScriptableObjectのベースクラスとそれを継承したクラスを一つのファイルに書いたわけです。
これをProjectビュー上で右クリックから作成しました。
そして普通に動作することを確認した後、Unityエディタを再起動しました。
すると、
このようなAssertが表示されScriptableObjectとしての動作もしなくなっていたのです!
何もしてないのに壊れた...
#解決法
タイトルまんまなんですがScriptableObjectは1つのファイルに1つのクラスだけを書くようにします。
#原因
なんで1ファイルに複数クラスを書いてはいけないのでしょうか?
ファイル分割後のScriptableObjectファイルの中身を見てみましょう。
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d740773b00796084096feb7e6b5b3618, type: 3}
m_Name: SOInherit
m_EditorClassIdentifier:
val: "\u5024\u3092\u3053\u3053\u306B\u5165\u308C\u308B\u3002"
注目すべきはm_Scriptのguidです。
続いてSOInherit.csのmetaファイルを見てみます。
fileFormatVersion: 2
guid: d740773b00796084096feb7e6b5b3618
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
guidの値を見てみるとSOInherit.assetのm_Scriptに保持されているguidと一致しています。
Unityはguidという一意なidをそれぞれのアセットに割り振っており、このidを利用してアセットの参照を実現しています。
ScriptableObjectはスクリプトファイルのguidを保持することで自身がそのクラスのインスタンスであることを表現しているわけです。
ですがこの時一つのファイルに複数のクラスが存在していたらどうでしょう?guidはアセット毎、つまりファイル毎に割り振られるわけですからScriptableObjectがスクリプトファイルのguidを保持していても、どのクラスのインスタンスかは分からないのです。
ScriptableObjectを作成してから再起動するまでは正常に動作していましたがScriptプロパティを見てみるとNoneになっていることからやはり参照は上手く機能していないことが分かります。
#まとめ
言いたいことはタイトルが全てです。MonoBehaviourが1クラス1ファイルなのは有名ですがScriptableObjectはあまり知られていない(と思う)上に再起動するまで不具合に気づけない分厄介です。お気をつけあれ。
#追記
1つのファイルに複数のクラスを記述した場合でもファイル名と同じ名前のScriptableObjectクラスが存在する場合、そのクラスのインスタンスのアセットファイルは問題なく読み込めるようです。
UnityがScriptableObjectを読み込む際はまずguidでどのスクリプトファイルにクラスが記述されているかを調べ、そのスクリプトファイルと同名のクラスのインスタンスであるものとして読み込まれるようです。
なので1つのスクリプトファイルに1つのクラスしか記述していなくてもファイル名とクラス名が異なる場合にはScriptableObjectを上手く読み込むことができませんのでご注意を。
いずれにせよ1つのファイルに複数クラスを記述するとかファイル名とクラス名を別にするとかトリッキーなことはしてはいけませんよということです。