はじめに
ScriptableObjectを使っていると、こんな挙動に出会うことがありますよね。
- 値を書き換えたのに再生したら元に戻る
- インスペクターでは変わっているのに保存されない
- シーンを切り替えたらデータが消えた
- 同じScriptableObjectのはずなのに反映されない
私も最初は「バグかな?」と思って、かなり時間を溶かしました…☕
でもこれ、実はバグではなくUnityの仕様による“罠”であることがほとんどです。
特にScriptableObjectは便利すぎるがゆえに、仕組みを深く理解しないまま使い始めてしまいがちです。
その結果、「保存されると思っていた」「ずっと保持されると思っていた」という思い込みでハマります。
このあたりを曖昧にしたままだと、開発が進むほど「再現しない不具合」が増えていきます。
ここからは、原因をひとつずつ整理しながら、
「なぜそうなるのか」「どう判断すればいいのか」まで踏み込んで解説していきます。
結論:まず最初に確認すべきポイント
時間をかけて原因を探る前に、まずここをチェックすると一気に解決するケースが多いです。
実際の開発でも、だいたいこの中に原因があります。
- ScriptableObjectの変更を保存しているか(SetDirty / SaveAssetsを書いているか)
- Play中の変更を保存できると思っていないか
- Dictionaryなどシリアライズ非対応の型を使っていないか
- シーン遷移で参照が切れていないか
- 別のインスタンスを操作していないか
ここで一つ大事なポイントがあります。
ScriptableObjectは「自動で保存される便利な箱」ではありません。
あくまで「アセットとして扱われるデータ」であり、保存や保持には明確なルールがあります。
例えば、カフェで作業していて「ちゃんと保存したつもりのメモが消えてた」みたいな経験ありませんか?
ScriptableObjectもそれに近くて、「保存したつもり」が一番危険です。

まずは上の5つを上から順に確認してみてください。
ここだけで解決することも珍しくありません。
ScriptableObjectが更新されない主な原因
ここからは、実際によく起きる原因を1つずつ分解していきます。
「なんとなく動かない」状態から、「どこが原因か判断できる」状態に変えていきましょう。
スクリプト変更が保存されていない
症状
インスペクターでは値が変わっているのに、Unityを再起動したりPlayすると元に戻る
原因
スクリプトからの変更は、Unityが「変更された」と認識していないことがあります。
この状態をDirty状態になっていないといいます。
インスペクターで手動変更した場合は自動で保存対象になりますが、
スクリプトから変更した場合は明示的に通知しないと保存されません。
解決方法
- 変更後に
EditorUtility.SetDirty(targetObject);を呼ぶ - さらに確実に保存するなら
AssetDatabase.SaveAssets();を実行する
流れとしてはこんな感じです。
// 値変更
data.value = 10;
// 変更を通知
EditorUtility.SetDirty(data);
// ファイルとして保存
AssetDatabase.SaveAssets();
注意点
これらはエディタ専用APIなので、ビルド後には使えません。
再発防止
- エディタ拡張にまとめておく
- 「ScriptableObjectを書き換えたら必ずSetDirty」とルール化する
ここでつまずく人がかなり多いです。
「インスペクターでは変わってるのに保存されない」場合、まずここを疑ってみてください。
実行時の変更は保存されない
症状
Playモード中に値を変更すると一見反映されるが、停止すると元に戻る
原因
ScriptableObjectは実行時(Play中)の変更をアセットとして保存しない仕様です。
見た目では変更されたように見えても、それはメモリ上の一時的な値にすぎません。
この挙動は「バグ」ではなく、Unityの設計です。
ゲーム実行中のデータは基本的にセッション終了でリセットされる前提になっています。
解決方法
- 保存が必要なデータは別の仕組みで管理する
- ScriptableObjectは「初期データ」として扱う
例えばプレイヤーのレベルや所持アイテムなど、ゲーム終了後も残したいデータは以下のような方法で保存します。
- PlayerPrefs
- JSONファイル保存
- 専用のセーブシステム
専用の保存システムを使うと、実装がかなり楽になります。
Easy Save
✅ アセットストアでチェックする
注意点
Editorでは「変更が残るように見える」ことがあり、ここで勘違いしやすいです。
ただしビルド後は確実に保存されないため、挙動の違いに注意が必要です。
再発防止
- ScriptableObjectは「設定データ専用」と割り切る
- 「保存が必要か?」を設計段階で判断する
ここを理解していないと、あとからセーブ周りを全部作り直すことになります。
早めに線引きしておくのがかなり大事です。
シリアライズできないデータを使っている
症状
値を設定しているのに保存されない、または再読み込みすると中身が消えている
原因
Unityのシリアライズ(データ保存)にはルールがあり、
対応していない型はそもそも保存対象になりません。
代表的な例は次の通りです。
- Dictionary<K, V>
- HashSet<T>
- 多次元配列(int[,] など)
- ネストされたコレクション(List<List<T>>)
見た目ではちゃんと値が入っているのに、保存されないのが厄介なポイントです。
解決方法
- Listなどシリアライズ可能な形に変換する
- キーと値を別々のリストで管理する
- ISerializationCallbackReceiverを使って変換処理を書く
例えばDictionaryを保存したい場合は、
List<KeyType> keys;
List<ValueType> values;
のように分解して保存し、復元時に再構築する形になります。
注意点
「エラーが出ない=正しく保存されている」ではありません。
このパターンは静かに壊れるので、かなり危険です。
シリアライズの仕組みをしっかり理解しておくと、原因の切り分けが一気に楽になります。
再発防止
- 「その型は保存できるか?」を最初に確認する
- 複雑なデータは最初から保存用フォーマットを設計する
特に中級者になるとDictionaryを使いたくなる場面が増えますが、
ここでつまずく人はかなり多いです。
ScriptableObjectがアンロードされている
症状
シーンを切り替えたらデータが消える、もしくは突然初期化されたように見える
原因
ScriptableObjectはどこからも参照されなくなるとメモリからアンロードされます。
そして再ロードされたとき、シリアライズされていないデータは復元されません。
特に以下の条件で発生しやすいです。
- シーン遷移で参照元のGameObjectが破棄される
- 一時的なスクリプトからのみ参照している
- Dictionaryなど非シリアライズデータを持っている
エディタでは問題が起きにくいのに、実機やビルドでだけ発生することもあります。
これはエディタが内部的に参照を保持しているケースがあるためです。
解決方法
「参照を切らさない」ことがポイントです。
- 常駐オブジェクト(DontDestroyOnLoad)で参照を保持する
- ScriptableObjectをリストで持つ管理用クラスを用意する
よく使われる構成はこんな感じです。
- RuntimeDataHolder(空のGameObject)を作る
- そこにScriptableObjectの参照リストを持たせる
- 起動時に生成してDontDestroyOnLoadする
注意点
「たまたま動いている状態」が一番危険です。
参照設計が曖昧だと、プロジェクトが大きくなるほど不具合が増えます。
再発防止
- どこが参照を持つかを明確にする
- シーンをまたぐデータは必ず常駐管理にする
「なぜ消えたのか分からない」という状態になったら、
ほぼこのアンロードが原因だと思ってOKです。
参照しているインスタンスが違う
症状
値を変更したはずなのに反映されない、または別の場所では古い値のままになっている
原因
同じScriptableObjectだと思っていても、実際には別のインスタンスを操作しているケースがあります。
特に次のような状況で起きやすいです。
- Resources.Load や Instantiate で別インスタンスを生成している
- プレハブごとに別のScriptableObjectを参照している
- Inspectorで似た名前のアセットを間違えて設定している
ScriptableObjectは「アセット」なので、本来は1つのデータを共有する設計が基本です。
ですが、読み込み方法や参照の仕方によっては簡単に分裂します。
解決方法
- 参照元を1箇所に集約する
- どのScriptableObjectを使っているか明確にする
- ResourcesやAddressablesの使い方を見直す
例えばこんなチェックをすると分かりやすいです。
- InstanceIDをログに出して比較する
- インスペクターで同じアセットを参照しているか確認する
注意点
MonoBehaviourの感覚で扱うと混乱しやすいポイントです。
ScriptableObjectは「インスタンス」ではなくアセットそのものを共有する仕組みです。
再発防止
- ScriptableObjectは基本的に「1アセット=1データ」で設計する
- 生成せず、参照して使うことを徹底する
「値が変わらない」と感じたとき、実は違うデータを見ているだけというケースはかなり多いです。
3分でできる診断チェックリスト
ここまで読んでも原因がはっきりしないときは、次のチェックを上から順に確認してみてください。
実務でもこの順番で切り分けると、かなり短時間で原因にたどり着きます。
- □ ScriptableObjectの変更後に SetDirty / SaveAssets を呼んでいるか
- □ Playモード中の変更を保存しようとしていないか
- □ Dictionaryやネスト構造などを使っていないか
- □ シーン遷移で参照が切れていないか
- □ 同じScriptableObjectアセットを参照しているか
チェックのコツは、「上から順に潰す」ことです。
例えば、
- 最初の2つ → 保存の問題
- 3つ目 → シリアライズの問題
- 4つ目 → ライフサイクルの問題
- 5つ目 → 参照の問題
という感じで、原因の種類ごとに分かれています。
なんとなく全部試すよりも、カテゴリごとに切り分ける方が圧倒的に速いです。

私もデバッグ中に「全部おかしい気がする…」となることがありますが、
このチェックリストに戻ると冷静に原因を特定できます🙂
よくある誤解と落とし穴
ScriptableObjectでハマる原因の多くは「仕様を知らないこと」よりも、思い込みです。
ここでは特に勘違いされやすいポイントを整理しておきます。
インスペクターで変わった=保存されたと思っている
インスペクター上で値が変わると、「ちゃんと保存された」と思いがちです。
ですが、スクリプト経由の変更は保存されていない場合があります。
この違いの正体が「Dirtyフラグ」です。
インスペクター操作は自動でDirtyになりますが、スクリプト変更は手動対応が必要です。
見た目に騙されやすいので、かなり注意が必要です。
ScriptableObjectは常にメモリに残ると思っている
「アセットだからずっと残る」と思いがちですが、実際は違います。
参照がなくなると普通にアンロードされます。
特にシーン遷移を挟む構成だと、意図せず参照が切れて消えることがあります。
どんな型でも保存できると思っている
C#としては問題ない型でも、Unityのシリアライズでは保存できないものがあります。
代表例:
- Dictionary
- ネストしたList
「エラーが出てないから大丈夫」と思っていると、後でデータが消えて混乱します。
MonoBehaviourと同じ感覚で使っている
これもかなり多いです。
MonoBehaviourは「インスタンスを生成して使う」ものですが、
ScriptableObjectはアセットを共有して使うものです。
この違いを理解していないと、
- 別インスタンス問題
- 保存されない問題
- 消える問題
すべてに引っかかります。

違和感を感じたときは、「これはアセットなのか?インスタンスなのか?」と考えるクセをつけると、一気に理解が進みます。
ScriptableObjectの正しい使い方
ここまでの内容を踏まえると、ScriptableObjectは「何でも保存できる便利な箱」ではなく、
用途をきちんと分けて使うことで真価を発揮する仕組みだと分かってきます。
使い方を整理すると、こんなイメージです。
| 用途 | 適しているか | 理由 |
|---|---|---|
| ゲームの設定値(攻撃力・速度など) | ◎ | アセットとして管理できる |
| マスターデータ(敵情報・アイテム情報) | ◎ | 複数の場所から共有できる |
| プレイヤーの進行状況 | △ | 保存処理が別途必要 |
| 実行時に変化する一時データ | × | アンロードやリセットの影響を受ける |
つまり、
- 変わらないデータ → ScriptableObject
- 変わるデータ → 別管理
この線引きがとても重要です。
設計で迷ったときの判断基準
実務でよく使う判断のコツをまとめておきます。
- ゲーム終了後も残す必要がある → ScriptableObjectではなくセーブシステム
- 複数のオブジェクトで共有する → ScriptableObjectが適している
- シーンをまたぐ → 参照管理が必要
- 頻繁に書き換える → 別のデータ管理を検討
このあたりを最初に決めておくと、あとから作り直すことが減ります。
開発効率を上げるツール
シリアライズやインスペクター周りの問題で悩むなら、ツールを使うのも一つの手です。
Odin Inspector
✅ アセットストアでチェックする
Odinを使うと、
- Dictionaryのシリアライズ対応
- インスペクターの拡張
- データの可視化
などが簡単にできるようになります。
「ScriptableObjectは便利だけど扱いづらい」と感じているなら、かなり相性がいいです。
一段深い理解につながる考え方
ScriptableObjectは単なるデータではなく、設計の中心になる存在です。
例えば、
- ゲームのバランス調整
- データ駆動設計
- チーム開発での分業
こういった場面で力を発揮します。

「なぜ保存されないのか」だけでなく、「どう使うべきか」まで理解できると、
ScriptableObjectはかなり強力な武器になります。
まとめ
ScriptableObjectが更新されない・保存されない問題は、ほとんどが仕様を知らないことによるものです。
ポイントをもう一度整理しておきます。
- 保存されない → Dirty設定をしていない
- 再生後に戻る → 実行時の変更は保存されない仕様
- データが消える → アンロードまたは非シリアライズ
- 反映されない → 参照しているインスタンスが違う
この4つに分けて考えるだけで、かなりスムーズに原因を特定できます。
特に意識しておきたいのは次の2つです。
- ScriptableObjectは「保存機能」ではない
- ScriptableObjectは「アセットを共有する仕組み」
この前提を外さなければ、大きくハマることは減ります。
開発を続けていると、「なんで動かないんだろう」と手が止まる瞬間が何度も出てきます。
そんなときは今回のチェックポイントに立ち返ると、かなりの確率で解決できます。
少しずつでいいので、「なんとなく使う」から「仕組みで判断する」に変えていくと、
ScriptableObjectはかなり頼れる存在になってくれます🙂
よくある質問(FAQ)
- QScriptableObjectはセーブデータとして使える?
- A
基本的にはおすすめしません。
ScriptableObjectはアセットとして扱われるため、ビルド後のゲームでは書き換えて保存することができないからです。プレイヤーの進行状況や所持アイテムなど、ゲーム終了後も残したいデータは以下のような方法で管理するのが一般的です。
- PlayerPrefs
- JSONやバイナリファイル
- 専用のセーブシステム
ScriptableObjectは「初期データ」や「設定データ」として使うのが適しています。
- QDictionaryを使いたい場合はどうすればいい?
- A
Unityの標準機能ではDictionaryはシリアライズできません。
そのため、そのままでは保存されず、データが消える原因になります。対応方法は主に2つです。
- Listに分解して保存する
- カスタムシリアライズを実装する
もしくは、インスペクター拡張ツールを使うと簡単に扱えるようになります。
- QEditorでは動くのにBuildで動かないのはなぜ?
- A
これはUnity開発でよくある現象です。
主な原因は次の通りです。
- エディタ専用API(SetDirtyなど)を使っている
- ScriptableObjectの変更を保存できると思っている
- 参照やロード方法が異なる
特にScriptableObjectは、ビルド後は読み取り専用として扱うのが基本です。
Editorでうまくいっているからといって、そのまま動くとは限らない点に注意が必要です。










※当サイトはアフィリエイト広告を利用しています。リンクを経由して商品を購入された場合、当サイトに報酬が発生することがあります。
※本記事に記載しているAmazon商品情報(価格、在庫状況、割引、配送条件など)は、執筆時点のAmazon.co.jp上の情報に基づいています。
最新の価格・在庫・配送条件などの詳細は、Amazonの商品ページをご確認ください。