今回は、UnityのScriptableObjectを使って、シーンを移動した時もデータを使えるようにしたいと思います。
ScriptableObjectについては
でやりました。
↑の記事で作成したMyDataスクリプトのファイルであるData.assetを使用しますので、あらかじめ作成しておいてください。
シーン間の移動
まずはシーンをMain、First、Second、Thirdという名前で4つ作成します。
First、Second、Thirdシーンをボタンを押す事によって、相互に移動出来るような機能を作成していきます。
MainシーンはFirstシーンをロードするだけのシーンです。
その他のシーンは相互にボタンを押して移動出来るようにし、シーン間の移動の際はフェードイン、フェードアウトをして移動します。
今回はUnityのライブトレーニングを参考にさせて頂きました。
シーン間のデータの移動でScriptableObjectを使いますが、今回のようにメインとなるシーンに別のシーンを追加して実行しないで、
今再生しているシーンを破棄して、次のシーンを読み込むようにしても実装出来ましたが、一応、参考サイトの流れに沿ってみました。
Mainシーン
Mainシーンの中身を作成していきます。
Mainシーンには空のゲームオブジェクトを作成し、名前をManagementとします。
Managementには新しくLoadSceneスクリプトを作り取り付けます。
↑のように設定します。
Mainシーンにはヒエラルキー上で右クリック→UI→EventSystemを取り付けておきます。
他のシーンでUIのボタンを設置しますが、他のシーンのEventSystemは削除します。
これは、現在のシーンにEventSystemが存在した時に、次のシーンを読み込んで、そのシーンにもEventSystemがあるとエラーが発生する為です。
その為、あらかじめMainシーンにEventSystemを取り付けておき、他のシーンとの競合を避けます。
LoadSceneスクリプトに処理を記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | using UnityEngine; using System.Collections; using UnityEngine.SceneManagement; public class LoadScene : MonoBehaviour { void Start() { StartCoroutine(LoadNewScene ("First")); } // 新しいシーンをロード IEnumerator LoadNewScene(string sceneName) { // シーン読み込み処理 AsyncOperation async = SceneManager.LoadSceneAsync (sceneName, LoadSceneMode.Additive); while (!async.isDone) { yield return null; } // 読み込みが終わったら読みこんだシーンをアクティブにする SceneManager.SetActiveScene (SceneManager.GetSceneAt (SceneManager.sceneCount - 1)); } } |
Startメソッドですぐにコルーチンを開始し、LoadNewSceneメソッドを実行しています。
コルーチンに関しては
を参照してください。
コルーチンは非同期処理をしてくれるものです。
LoadNewSceneメソッドでは非同期に引数で受け取ったシーンを読み込んでいます。
先ほど紹介したライブトレーニング内では、AsyncOperationの変数に入れてその後isDoneで読み込みが終わったかどうか確認するのではなく、
1 2 3 | yield return SceneManager.LoadSceneAsync(sceneName); |
という感じで書いていたので、こちらでもいけるのかもしれません。
First、Second、Thirdシーン
SecondとThirdシーンはFirstシーンをコピーして設定を変えるだけなのでFirstシーンだけ解説したいと思います。
サンプルのUIを作成
まずはヒエラルキー上で右クリック→UI→Canvasを選択します。
Canvasの子要素にUI→Textを作成し、名前をSceneTitleとし、Text内に「シーン1」と記述します。
Canvasの子要素にUI→Textを作成し、名前をDataTextとします。
Canvasの子要素にUI→Buttonを作成し、名前をChangeDataとし、ChangeDataの子要素のText内に「データを書き換える」と記述します。
Canvasの子要素に空のゲームオブジェクトを作成し、名前をButtonとします。
ButtonPanelの子要素にUI→Buttonで2つのボタンを作成し、名前をSecondSceneButton、ThirdSceneButtonとします。
この名前はわかりやすいように遷移先のボタン名を入れています。
SecondSceneButtonの子要素のTextには「シーン2を読み込む」、ThirdSceneButtonの子要素のTextには「シーン3を読み込む」を入力します。
Canvasの子要素にUI→Imageを作成します。
このImageはシーン移動時のフェード用のイメージでアルファ値(透明度)を変更してフェードイン、フェードアウトを実現します。
Imageは他のUI要素も隠す必要があるので、Canvasの一番下に移動させ、UIの中で一番手前に表示されるようにしておきます。
またImageは一番手前にUI全体を覆うように配置しているので、後ろにあるボタン等が押せなくなってしまいます。
そこで、ImageのRaycast Targetのチェックを外しておきます。
チェックを外すとImageはマウスクリック等の反応から無視されるようになります。
またImageのColorをクリックし、RGBを0、Aを255に設定します。
するとカメラに映る映像は真っ暗になります。
シーンが始まる時は真っ暗な状態なので、Second、Thirdシーンのフェード用のImageも同じように設定します。
出来た階層が、
↑のようになります。
配置を調整して、
↑のようにしました。
シーン移動ボタンを押した時の処理スクリプトClickButton
シーンを移動するボタンを押した時の処理スクリプトClickButtonを作成します。
ClickButtonはボタンを押した時に呼び出すスクリプトで、UIの大元であるCanvasに設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | using System.Collections; using UnityEngine.SceneManagement; using UnityEngine.UI; public class ClickButton : MonoBehaviour { // フェードスピード public float fadeSpeed = 1.0f; // フェードイメージ public Image fadeImage; // アンロードするシーン private Scene unLoadScene; // ボタンパネル public GameObject buttonPanel; void Start() { // スタート時にボタンUIを非表示 buttonPanel.SetActive (false); StartCoroutine(Fade(0f)); } public void ButtonEvent(string sceneName) { buttonPanel.SetActive (false); // フェードアウト→ロードシーン→アンロードシーン StartCoroutine(FadeAndLoadScene (sceneName)); } IEnumerator FadeAndLoadScene(string sceneName) { // 他のシーンへ遷移する時にフェードアウト yield return StartCoroutine(Fade (1f)); unLoadScene = SceneManager.GetActiveScene (); // フェードアウトが完了したら新しいシーンを読み込む yield return StartCoroutine(LoadNewScene(sceneName)); // フェードアウトが完了したら現在のシーンを破棄 StartCoroutine (UnLoadScene ()); } IEnumerator Fade(float alpha) { // 目的のアルファ値になるまで徐々に変化させる while (!Mathf.Approximately (fadeImage.color.a, alpha)) { fadeImage.color = new Color (0f, 0f, 0f, Mathf.MoveTowards (fadeImage.color.a, alpha, fadeSpeed * Time.deltaTime)); yield return null; } buttonPanel.SetActive (true); } // 新しいシーンをロード IEnumerator LoadNewScene(string sceneName) { // シーン読み込み処理 AsyncOperation async = SceneManager.LoadSceneAsync (sceneName, LoadSceneMode.Additive); while (!async.isDone) { yield return null; } SceneManager.SetActiveScene (SceneManager.GetSceneAt (SceneManager.sceneCount - 1)); } // 前のシーンをアンロード IEnumerator UnLoadScene() { AsyncOperation async = SceneManager.UnloadSceneAsync (unLoadScene); while (!async.isDone) { yield return null; } } } |
スクリプトが長いので少しづつ解説します。
フィールドの宣言部
fadeSpeedはフェードイン、フェードアウトのスピードを設定します。
fadeImageはフェードに使うヒエラルキーのImageオブジェクトを設定します。
unLoadSceneは新しいシーンを読み込んだ時に、現在のシーンを入れておく為のもので、後でアンロードします。
buttonPanelはヒエラルキーのButtonオブジェクトを設定します。
Startメソッド
Startメソッドではまずボタンパネルを非アクティブにします。
これはフェード中にボタンが押せるようにすると不具合が発生するので、フェード中はシーン遷移ボタンを表示しないようにします。
その後コルーチンを使ってFadeメソッドを呼び出し、Imageのアルファ値を0にしてImageが完全に透明になるようにします。
スクリプトの名前がClickButtonですが、シーン遷移の処理も詰め込んでいます。
ButtonEventメソッド
ButtonEventメソッドは、シーン移動ボタンを押した時に実行させる予定のメソッドです。
引数として読み込むシーン名を受け取ります。
ボタンが押されたらすぐにボタンを非表示にし、Imageの不透明度を上げて、カメラに映る他のUIやゲームオブジェクトを見えないようにします。
次にFadeAndLoadSceneをコルーチンで実行します。
FadeAndLoadScene
FadeAndLoadSceneではフェードアウトをまず実行し、現在のシーンをunLoadSceneに保存しておきます。
フェードアウトが完了したら次のシーンをロードし、現在のシーンをアンロードします。
Fadeメソッド
Fadeメソッドは引数として最終的に変更するアルファ値を受け取ります。
whileの条件ではImageのアルファ値と最終的なアルファ値とを比較し、違う場合は繰り返し実行します。
yield return nullに関してはコルーチンの記事を参照してください。
フェードが終わったら、シーン遷移ボタンを表示します。
LoadNewSceneメソッド
LoadNewSceneは引数で受け取ったシーンを読み込みます。
読み込みが終わったらシーンをアクティブにします。
UnLoadSceneメソッド
UnLoadSceneでは現在のシーンをアンロードし、ヒエラルキー上から消します。
UnLoadSceneAsyncは非同期でシーンを読み込む処理ですが、わたくしが主に使っているUnity5.3.4f1では定義されていません。(^_^;)
そんな時はUnLoadSceneを使いますが、5.6バージョンでは非奨励の処理になっていますね。
データ表示更新のShowDataスクリプト
DataTextゲームオブジェクトにShowDataスクリプトを新しく作り、設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; using UnityEngine.UI; public class ShowData : MonoBehaviour { public MyData data; // Use this for initialization void Start () { GetComponent <Text>().text = data.myName + " : " + data.hp + " : " + data.attackPower; } public void UpdateText() { GetComponent <Text>().text = data.myName + " : " + data.hp + " : " + data.attackPower; } } |
publicでMyData型のdataを宣言し、インスペクタでData.assetを設定します。
Startメソッドでdataのフィールドを表示しています。
UpdateTextメソッドも同じ事をしていますが、こちらはデータの更新があった時の他のスクリプトから実行されます。
あ・・・、同じ処理ならStartメソッド内でUpdateTextメソッド呼べばいいね・・・・(-.-)
データの書き換えをするChangeDataスクリプト
ChangeDataゲームオブジェクト(ボタン)にChangeDataスクリプトを新しく作り、取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | using UnityEngine; using System.Collections; public class ChangeData : MonoBehaviour { public MyData data; // データの書き換え public void Change() { data.hp = Random.Range (10, 100); data.attackPower = Random.Range (5f, 50f); // ShowDataスクリプトの取得とテキストUIの更新 ShowData sData = FindObjectOfType (typeof(ShowData)) as ShowData; sData.UpdateText (); } } |
publicでMyData型のdataフィールドを宣言し、インスペクタでData.assetを設定します。
Changeメソッドが呼ばれると、dataのフィールド値をランダムに書き換え、先ほど作成したShowDataスクリプトをFindObjectOfTypeで探し、UpdateTextメソッドを実行し、テキストの中身を更新します。
ShowDataをFindObjectOfTypeで検索していますが、publicでShowDataスクリプトを設定しておいた方が処理が早いかもしれません。
次にChangeDataボタンのインスペクタのOn Clickで、自身に取り付けたChageDataスクリプトのChangeメソッドを設定します。
ChangeDataボタンのインスペクタは↑のようになります。
ボタンを押した時に実行するメソッドを指定するやり方は、
を参照してください。
シーン移動ボタンを押した時に実行するメソッドの設定
SecondSceneButtonとThirdSceneButtonのインスペクタのOn ClickにCanvasをドラッグ&ドロップし、実行するメソッドにButtonEventを設定します。
設定されると引数に渡す文字列の入力欄が表れるので、そこに遷移するシーン名を記述します。
↑はSecondSceneButtonの設定で、Secondを入力し、ButtonEventの引数に渡ります。
Secondシーン、Thirdシーンではボタンを押した時に渡す文字列などを変更します。
これで機能が完成しました。
シーンをビルドセッティングに登録する
ゲームに使うシーンはあらかじめビルドセッティングに登録しておく必要があります。
Unityのメニュー項目→Build Settings…を選択します。
Scenes In Buildに作成した4つのシーンをドラッグ&ドロップします。
Mainシーンをドラッグし、一番上に移動させて一番最初に再生するようにします。
またData.assetのmyNameに何か名前を指定しておいてください。
これでビルドセッティングも出来たので、ビルドして機能を確認してみます。
シーン間でのデータの共有を確認
それでは実行してみます。
↑のようになりました。
シーンを移動してのデータ共有が簡単になりました!
もちろんゲームを終了した時はData.assetのデータも消えるので、別途何かにデータを保存する必要はありますね。
ScriptableObjectを使った例として次回以降でRPGのワールドマップ→戦闘シーン→ワールドマップという一連の流れを作ってみたいと思います。