見出し画像

【Unity】それは危険! シーン間でデータを安全に共有する方法

こんにちは、鈴木ジョンです。
今日は、Unityのシーン間でデータを安全に共有する方法を、解説していきたいと思います。

なぜこんな記事を書こうと思ったかと言うと、シーン間でデータを共有する方法、これをGoogleで調べても、恐ろしいほど、その方法が出てこなかったからです。

検索して出てくる記事は、なぜか、安全ではない方法ばかりが勧められていました。もちろんその方法でも動くことは動くのですが、バグの原因になってしまったり、テストがしにくかったり、とデメリットも多いです。

安全ではないシーン間のデータの共有方法

・staticなオブジェクトを使用
・DontDestroyOnLoadを使用
・シングルトンパターンを使用
・PlayerPrefsを使用

これらの方法はいずれも、どこからでもアクセスできるオブジェクト、すなわちグローバルなオブジェクトを持つ方法になります。

単純です! 簡単です! 楽なんです!
ベテランのプログラマーでも、ついつい使っちゃいます。

でも、その分危険なんです。諸刃の剣なんです。
それでは、いったい何が危険なのか見ていきましょう。

グローバルなオブジェクトが危険な理由

私たちプログラマーは常日頃から、メンテナンスのしやすいプログラムを書くことを重要視しています。なぜなら、メンテナンス性が低いと、バグが発生しやすくなったり、バグの原因を見つけにくくなったりするからです。

メンテナンス性を上げるためには、いったいどうすればいいのでしょうか。身近にいい例がありました。

それは車です。

車は人の命を預かっているわけですから、安全でなくてはなりません。なるべくトラブルを発生させずに、トラブルが発生しても原因がすぐに特定できるようにしなければいけません。

車は安全を保障するために、小さな部品ごとに検査が行われます。そしてその小さな部品を組み立てて製品をつくります。

そうです。私たちプログラマーは、プログラムを大きくするのではなく、小さなプログラム(部品)を組み立てて大きくしていくべきなんです。

グローバルなオブジェクトとはその信念に逆らいます。
どこからでもアクセスできる、つまりは、すべてのプログラムが密な関係になってしまいます。それはもう大きな一つのプログラムでしかありません。

それでは、どうすれば安全にシーン間でデータを共有できるかを見ていきましょう。

安全にシーン間でデータを共有するには

先ほどの危険なデータの共有をこのように表します。

画像1

グローバルなオブジェクトGは常に他のシーンA・B・Cの依存になっています。Gの変更がすべてのシーンに影響してしまいます。テストをするときも必ずGが存在していないといいけません。

これならどうでしょうか?

画像2

シーンAが持っているデータをシーンBが参照する。これなら依存関係にあるのはAとBのみです。しかしまだ依存関係は残っています。BはAのデータ構造を把握していないといけません。

ではこれならどうでしょうか?

画像3

シーンAがシーンBにデータをセットをする。先ほどと同じように見えますが、AとBは依存していません。Bは自分のデータ構造を把握しているだけで大丈夫です。

シーンBのテストをする場合でも、シーンAは不要です。シーンBが自らテスト用のデータを準備すればいいからです。

これで他シーンとの依存関係が断ち切れました。本来シーンAからシーンBへデータの受け渡しを目的とした場合、こうあるべきです。

Unity上での書き方

他の言語では、上記の方法でシーン間のデータを共有する方法が用意されています。Unityには存在しないのでしょうか?

ありました! 公式のリファレンスにそれに適した関数がありました。

通常ではシーン切り替えの時にSceneManagerを呼び出します。

public void onClick()
{
    // シーン切り替え
    SceneManager.LoadScene("GameScene");
}

このSceneManagerには、実はイベントが用意されています。
シーン切り替えの前にSceneManager.sceneLoadedに呼び出したいメソッドを登録しましょう。今回はメソッド名をGameSceneLoadedとしました。

public void onClick()
{
    // イベントに登録
    SceneManager.sceneLoaded += GameSceneLoaded;

    // シーン切り替え
    SceneManager.LoadScene("GameScene");
}
private void GameSceneLoaded(Scene next, LoadSceneMode mode)
{
    // シーン切り替え時に呼ばれる
}

そうすることでシーン切り替え時にGameSceneLoadedのメソッドが呼ばれます。

それでは、GameSceneLoadedにデータを渡す処理を書いていきましょう。

private void GameSceneLoaded(Scene next, LoadSceneMode mode)
{
    // シーン切り替え後のスクリプトを取得
    var gameManager= GameObject.FindWithTag("GameManager").GetComponent<GameManagerScript>();
    
    // データを渡す処理
    gameManager.score = 100;

    // イベントから削除
    SceneManager.sceneLoaded -= GameSceneLoaded;
}

シーン切り替え後のゲームオブジェクトのスクリプトを取得して、そのスクリプトにデータを渡しています。

注意したいのが最後にイベントから削除していることです。
これを忘れてしまうとシーン切り替えのたびにこのメソッドが呼ばれてしまうことになります。

最終的にはこうなります。

public void onClick()
{
    // イベントに登録
    SceneManager.sceneLoaded += GameSceneLoaded;

    // シーン切り替え
    SceneManager.LoadScene("GameScene");
}
private void GameSceneLoaded(Scene next, LoadSceneMode mode)
{
    // シーン切り替え後のスクリプトを取得
    var gameManager= GameObject.FindWithTag("GameManager").GetComponent<GameManagerScript>();
    
    // データを渡す処理
    gameManager.score = 100;

    // イベントから削除
    SceneManager.sceneLoaded -= GameSceneLoaded;
}

これで危険な依存度の高いプログラムから解放されました。

最後に

グローバルなオブジェクトは決して悪ではありません。使いようによっては最高の武器になります。

ただプログラムが大きくなるにつれて、バグの原因になったり、テストがしにくくなったりと、問題も多くあります。

上手に使い分けて楽しいUnityライフを送りましょう。
以上、シーン間でデータを安全に共有する方法でした。


最後までご一読ありがとうございました。
もしよかったらフォロー、スキ、コメントで応援の方とプロフィール欄からTwitterのフォローをお願いします。

この記事が気に入ったらサポートをしてみませんか?