今回はUnityでゲームステージの選択→操作キャラクターの選択→ゲーム開始という流れを作ってみたいと思います。
ステージ1とステージ2を選択出来るようにし、キャラクターは3人から選んでその後ゲームを開始し、選んだゲームステージに選んだキャラクターを登場させるようにします。
今回の機能を作成すると以下のようなものが出来上がります。
シンプルな作りになっています。
機能作成の流れ
今回の機能を作る流れを確認しておきます。
作成するシーンは
ステージ選択をするシーン
キャラクター選択をするシーン
ステージ1シーン
ステージ2シーン
の4つを作成します。
ステージ選択シーン→キャラクター選択シーン→ステージシーン
というシーンの移動がありますので、シーンを移動しても選択したステージ、キャラクターを保持しておく必要があります。
シーン間でのデータ共有をするにはそれらのデータを持つスクリプトを設定したゲームオブジェクトをシーンを移動しても残すか、ScriptableObjectを使ってデータを見えるかしてシーンごとにそのデータを取得出来るようにするなど色々な方法があります。
今回の場合はScriptableObjectを使ってゲームで使用するデータを保持して置くファイルを作成し、そのファイルを使ってシーン間でデータを確認出来るようにします。
そのデータを使用するスクリプトのフィールドにそのファイルを設定出来るようにし個々でアクセスしてもいいんですが、スクリプト毎に設定をするのも大変なので、そのデータを保持しているスクリプトを別途作成し、それをゲームオブジェクトに取り付け、そのゲームオブジェクトをシーンを移動しても残るようにしておいて、そのスクリプトが持つデータを確認したい時は必ずそのデータを持つスクリプトを介して取得するようにします。
長ったらしい説明で分かり辛いですが、要はデータを持つスクリプトはひとつだけにして、それを介してどのステージか?どのキャラクターか?を確認するということになります。
なお今回のUIの操作はマウスで選択するようにしていますので、ゲームパッドなどを使用してUIを操作する場合はそれ用に改造する必要が出てきます。
ステージ選択とキャラクター選択機能を作成
ステージ選択とキャラクター選択をする機能を作成していきます。
シーンの作成
まずは4つのシーンを作成します。
Assetsフォルダ内で右クリックからCreate→Sceneを選択し名前をSelectCharacterTitleとします。
同じように他3つのシーンを作成し、SelectCharacter、SelectCharacterStage1、SelectCharacterStage2とします。
シーンの名前は適当に付けてください。
Scenes In Buildにシーンを登録する
4つのシーンが出来たのでScenes In Buildにそれらのシーンを登録します。
Unityメニュー→Build Settingsを選択し開いたウインドウのScenes In Buildに4つのシーンをドラッグ&ドロップして登録します。
SelectCharacterTitleシーンを一番初めに実行したいので一番上にドラッグしておきます。
全てのシーンで使用するデータの作成
全てのシーンで使用するデータをScriptableObjectを使って作成しておきます。
ScriptableObjectに関しては以下の記事を参照してください。
新しくMyGameManagerDataスクリプトを作成し、ScriptableObjectを継承するようにします。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace SelectCharacter { [CreateAssetMenu(fileName = "MyGameManagerData", menuName = "MyGameManagerData")] public class MyGameManagerData : ScriptableObject { // 次のシーン名 [SerializeField] private string nextSceneName; // 使用するキャラクタープレハブ [SerializeField] private GameObject character; // データの初期化 private void OnEnable() { // タイトルシーンの時だけリセット if (SceneManager.GetActiveScene().name == "SelectCharacterTitle") { nextSceneName = ""; character = null; } } public void SetNextSceneName(string nextSceneName) { this.nextSceneName = nextSceneName; } public string GetNextSceneName() { return nextSceneName; } public void SetCharacter(GameObject character) { this.character = character; } public GameObject GetCharacter() { return character; } } } |
UnityのAssetメニューからMyGameManagerDataファイルが作成出来るようにCreateAssetMenuアトリビュートを付けます。
今回データとして使用するのはステージとキャラクターのプレハブなのでそれらをフィールドで用意し、SetterとGetterでデータにアクセス出来るようにしておきます。
フィールドにはSerializeFieldアトリビュートを取り付けてインスペクタで設定出来るようにしていますが、これはデータの書き換えがインスペクタで確認出来るようにしているだけで付けなくても構いません。
OnEnableメソッドではSceneManager.GetActiveScene()で現在アクティブなシーンを取得し、その名前がSelectCharacterTitleシーンだった時だけデータの初期化をしています。
これはSelectCharacterTitleシーンが開始された時にデータを初期化したい為です。
他のシーンで初期化していないのは他のシーンだけで動きを確認したい時にデータがリセットされているとセットし直さないといけない為です。
これでファイルを作る準備が出来たので、Assetsフォルダ内で右クリックからCreate→MyGameManagerDataを選択します。
これでMyGameManagerDataファイルが出来ました。
ステージ選択機能を作成する
次はステージ選択をする機能をSelectCharacterTitleシーン内に作成していきます。
SelectCharacterTitleシーンを開きます。
ヒエラルキー上で右クリックしCreate Emptyを選択し、名前をMyGameManagerとします。
新しくスクリプトを作成し名前をMyGameManagerとし、MyGameManagerゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; namespace SelectCharacter { public class MyGameManager : MonoBehaviour { // 世界に一つだけのMyGameManager private static MyGameManager myGameManager; // ゲーム全体で管理するデータ [SerializeField] private MyGameManagerData myGameManagerData = null; private void Awake() { // 世界に一つだけのMyGameManagerにする処理 if (myGameManager == null) { myGameManager = this; DontDestroyOnLoad(this); } else { Destroy(gameObject); } } // MyGameManagerDataを返す public MyGameManagerData GetMyGameManagerData() { return myGameManagerData; } } } |
MyGameManagerスクリプトはシーン全体で使用するMyGameManagerスクリプトを保持します。
staticでmyGameManagerを宣言しているので、このスクリプトは共有されます。
myGameManagerDataはMyGameManagerDataファイルをインスペクタで設定出来るようにします。
AwakeメソッドでmyGameManagerがnullの時に自身のスクリプトをmyGameManagerに入れ、DontDestroyOnLoadを使って他のシーンに遷移してもこのスクリプトが設定されたゲームオブジェクトMyGameManagerゲームオブジェクトは残るようにします。
既にmyGameManagerが設定されていれば自身のスクリプトが設定されたゲームオブジェクトを削除します。
これは全てのシーンにこのシーンで作成したMyGameManagerスクリプトが設定されたMyGameManagerゲームオブジェクトを配置しますが、他のシーンから遷移してきたときにこのシーンのMyGameManagerゲームオブジェクトを削除し、MyGameManagerゲームオブジェクトは常に一つしか存在しないようにする為です。
全てのシーンにMyGameManagerゲームオブジェクトを配置するのはそれぞれのシーン個別で機能を確認する時の為です。
GetMyGameManagerDataメソッドは自身が持つMyGameManagerDataを返します。
これでMyGameManagerDataのデータを確認するには必ずMyGameManagerゲームオブジェクトのMyGameManagerスクリプトのGetMyGameManagerメソッドを介す必要があります。
MyGameManagerゲームオブジェクトのMyGameManagerスクリプトのMyGameManagerDataに先ほど作成したMyGameManagerDataファイルを設定しておきます。
シーン遷移スクリプトの作成
シーン遷移処理を行うSceneTransitionスクリプトを作成します。
新しくスクリプトを作成し、SceneTransitionという名前にし、MyGameManagerゲームオブジェクトに取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace SelectCharacter { public class SceneTransition : MonoBehaviour { private MyGameManagerData myGameManagerData; private void Start() { myGameManagerData = FindObjectOfType<MyGameManager>().GetMyGameManagerData(); } public void GoToOtherScene(string stage) { // 次のシーンデータをMyGameManagerに保存 myGameManagerData.SetNextSceneName(stage); // キャラクター選択シーンへ SceneManager.LoadScene("SelectCharacter"); } } } |
フィールドでMyGameManagerDataを保持出来るようにします。
StartメソッドでFindObjectOfTypeを使用してMyGameManagerスクリプトを取得し、GetMyGameManagerDataメソッドを使ってMyGameManagerDataを取得します。
GoToOtherSceneメソッドは個々のステージ選択ボタンを押した時に引数でどのステージなのか?という文字列を受け取りそのシーン名をMyGameManagerDataのSetNextSceneNameメソッドでデータを設定しています。
その後にキャラクター選択のシーンであるSelectCharacterシーンに遷移させます。
ステージ選択ボタンの作成
次にステージ選択ボタンを作成します。
ヒエラルキー上で右クリックからUI→Buttonを選択し、名前をStage1とします。
Stage1の子要素のTextを選択し、インスペクタのTextにStage1と入力します。
Stage1を選択した状態でCtrl+Dキーを押し複製したら名前をStage2とします。
Stage2の子要素のTextを選択し、インスペクタのTextにStage2と入力します。
ボタンの位置は適当に移動させます。
Stage1ゲームオブジェクトを選択し、インスペクタのOnClickの+を押しボタンがクリックされた時に実行するメソッドを設定出来るようにします。
None(Object)となっている部分にMyGameManagerゲームオブジェクトをドラッグ&ドロップし、No Functionとなっている部分をSceneTransitionスクリプトのGoToOtherSceneメソッドに設定します。
GoToOtherSceneメソッドに渡す引数にはSelectCharacterStage1を入力します。
同じようにStage2ゲームオブジェクトを選択し同様に設定をして引数の部分だけSelectCharacterStage2とします。
これでSelectCharacterTitleシーンの作成が完了しました。
キャラクター選択機能を作成する
次はキャラクターを選択するSelectCharacterシーンを作成していきます。
キャラクタープレハブの作成
選択をするキャラクターはAnimatorControllerの設定やキャラクター操作スクリプト等を施し、Assetsフォルダにドラッグ&ドロップしてプレハブにしておきます。
キャラクターのアニメーションの操作やキャラクター移動スクリプト等は今回の記事とはまた別なので
や
等を参照し作成してください。
今回はスタンダードアセットのEthanを使ってAnimatorControllerの設定と移動スクリプトを取り付け、それをプレハブにしました。
違うキャラクターであることをわかりやすくする為Ethanのモデルのマテリアルを別にした他二人のキャラクターを作成しそれもプレハブにしました。
Ethan1は初期のマテリアルで、Ethan2はモデルに設定したマテリアルの色を赤にし、Ethan3はマテリアルの色を青にして作成しました。
それ以外のアニメーションとキャラクター移動スクリプトは同じものが設定されています。
今回の場合は選んだキャラクタープレハブがインスタンス化され登場されるのを確認するだけなので、キャラクターに見立てて適当なCube等をプレハブにしておくだけでもいいです。
キャラクター選択用ボタンの作成
次はキャラクター選択用ボタンを作成します。
ヒエラルキー上で右クリックからUI→Panelを選択し、名前をSelectCharacterPanelとします。
SelectCharacterPanelのインスペクタのAdd ComponentからLayout→Horizontal Layout Groupを選択し、取り付けます。
Horizontal Layout Groupコンポーネントは子要素のゲームオブジェクトを横に整列させる時に使用します。
Child AlignmentをMiddle Centerにします。
キャラクター選択ボタンを押した時の処理を行うChooseCharacterスクリプトを作成しSelectCharacterPanelゲームオブジェクトに取り付けます。
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 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.EventSystems; using UnityEngine.UI; namespace SelectCharacter { public class ChooseCharacter : MonoBehaviour { private MyGameManagerData myGameManagerData; private GameObject gameStartButton; private void Start() { // 世界に一つだけのMyGameManagerからMyGameManagerDataを取得する myGameManagerData = FindObjectOfType<MyGameManager>().GetMyGameManagerData(); // ゲームスタートボタンを取得する gameStartButton = transform.parent.Find("ButtonPanel/GameStart").gameObject; // ゲームスタートボタンを無効にする gameStartButton.SetActive(false); } // キャラクターを選択した時に実行しキャラクターデータをMyGameManagerDataにセット public void OnSelectCharacter(GameObject character) { // ボタンの選択状態を解除して選択したボタンのハイライト表示を可能にする為に実行 EventSystem.current.SetSelectedGameObject(null); // MyGameManagerDataにキャラクターデータをセットする myGameManagerData.SetCharacter(character); // ゲームスタートボタンを有効にする gameStartButton.SetActive(true); } // キャラクターを選択した時に背景をオンにする public void SwitchButtonBackground(int buttonNumber) { for (int i = 0; i < transform.childCount; i++) { if (i == buttonNumber - 1) { transform.GetChild(i).Find("Background").gameObject.SetActive(true); } else { transform.GetChild(i).Find("Background").gameObject.SetActive(false); } } } } } |
gameStartButtonはゲーム開始ボタンを入れます。
StartメソッドではMyGameManagerDataを取得します。
また階層を辿ってGameStartボタンのゲームオブジェクトを取得します(GameStartボタンは後で作成します)。
GameStartボタンはキャラクターを選択するまで表示したくないので、SetActiveを使って非アクティブにしておきます。
OnSelectCharacterメソッドは個々のキャラクターボタンを押した時に呼び出し、そのキャラクターに対応したキャラクタープレハブを受け取りそれをMyGameManagerDataにセットします。
1 2 3 | EventSystem.current.SetSelectedGameObject(null); |
を実行しているのは以下のように選択したキャラクターのボタンが選択状態になったままになり一度何もない所でクリックしないと改めて選択状態に出来ない為、EventSystemのSetSelectedGameObjectでnullを設定し、選択しているUIをリセットしています。
只これはボタンをマウスで選択するという前提の処理で、ゲームパッド等でボタンを選択する時は必ずどれかのUIが選択状態にないと出来ないので、ゲームパッドにも対応する場合はこの処理は削除してください。
ボタンの状態の遷移に関しては今回はあまり気にしていなかったのでいい加減な作りになっております・・・・((+_+))
一度どれかのキャラクターボタンを押したらゲームを開始出来る状態になるので、GameStartボタンをアクティブにします。
SwitchButtonBackgroundメソッドも個々のキャラクターボタンを押した時に呼び出し、押したボタンのBackgroundだけをアクティブにし、それ以外のボタンのBackgroundは非アクティブにし、選択したキャラクターがどれであるかをわかりやすくします。
SelectCharacterPanelを選択した状態で右クリックからUI→Panelを選択し、名前をCharacter1とします。
Character1のWidthとHeightを128にします(適当な数値を設定してください)。
Character1を選択した状態で右クリックからUI→Panelを選択し、名前をBackgroundとします。
BackgroundのRectTransformのWidthとHeightを135とします。
Anchor PresetsでMiddle Centerを選択します。
ImageのColorを赤色にしておきます。
Backgroundはそのキャラクターを選択した時にそのボタンの回りに選択した事がわかるように表示する背景に使用します。
なので、最初は見えない状態にしておく為、名前の横のチェックを外しておきます。
Character1を選択した状態で右クリックからUI→Buttonを選択し、名前をCharacterButtonとします。
CharacterButtonの子要素のTextは使わないので選択した状態でDelキーを押し削除します。
キャラクター選択ボタン用のスプライト作成
キャラクター選択ボタンに表示するスプライトを用意します。
今回は以下のような128×128画像を用意しました。
以下の画像は自由に使ってください。
上の3つの画像をUnityに取り込んだらインスペクタでTexture TypeをSprite(2D and UI)にします。
CharacterButtonのImageのSource ImageにEthan1を設定します。
またButtonのHighlighted ColorとSelected Colorを赤色にします。
これはマウスポインタがボタンの上に来た時と、ボタンを選択した時に赤色にする為ですが、ここら辺は適当に設定しております。
ここまで出来たらCharacter1ゲームオブジェクトを選択した状態でCtrl+Dキーを2回押し、同じボタンを2つ複製します。
複製したものをCharacter2とCharacter3とします。
Character2とCharacter3の子要素のCharacterButtonのImageのSource Imageをそれぞれ該当するキャラクターのスプライトに変更します。
次にSelectCharacterPanelを選択した状態で右クリックからUI→Panelを選択し、名前をButtonPanelとします。
シーンビューでSelectCharacterPanelとButtonPanelのサイズを調整し、パネルが重ならないようにしておきます。
ButtonPanelを選択した状態で右クリックからUI→Buttonを選択し、名前をGameStartとします。
GameStartの子要素のTextを選択し、インスペクタのTextにゲームスタートを入力します。
GameStartボタンは最初は表示させないのでインスペクタで名前の横のチェックを外し最初は非アクティブにしておきます。
GameStartボタンに新しくGameStartButtonスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; namespace SelectCharacter { public class GameStartButton : MonoBehaviour { private SceneTransition sceneTransition; private void Start() { sceneTransition = FindObjectOfType<SceneTransition>(); } public void OnGameStart() { // MyGameManagerDataに保存されている次のシーンに移動する sceneTransition.GameStart(); } } } |
OnGameStartはGameStartボタンを押した時に呼び出すようにします。
シーンの遷移はSceneTransitionスクリプトに任せているので、SceneTransitionのGameStartメソッドを呼び出します。
SceneTransitionスクリプトにGameStartメソッドを作ります。
1 2 3 4 5 6 | public void GameStart() { // MyGameManagerDataに保存されている次のシーンに移動する SceneManager.LoadScene(myGameManagerData.GetNextSceneName()); } |
ここまでのUIの階層は以下のようになっています。
記事内では記載していませんが、別途説明文を表示するTextも表示されています。
ボタンを押した時に実行する処理を設定する
キャラクター選択ボタンとゲームスタートボタンが出来たので次はそれらのボタンを押した時に実行する処理を設定します。
まずはCharacter1の子要素のCharacterButtonのOnClickに設定をします。
設定の手順はステージ選択のボタンを押した時と同じように行っていきます。
OnClickの+を2回押し、None(Object)の所にSelectCharacterPanelをドラッグ&ドロップします。
No FunctionではChooseCharacterスクリプトのOnSelectCharacterメソッドとSwitchButtonBackgroundメソッドを指定します。
OnSelectCharacterメソッドに渡す引数にはキャラクター1用のプレハブ、SwitchButtonBackgroundメソッドに渡す引数はキャラクター1だとわかるように1の値を渡します。
Character2とCharacter3の子要素のCharacterButtonも同じように設定し、個々のキャラクタープレハブとそのキャラクターの番号を渡すようにします。
次にGameStartゲームオブジェクトを選択しインスペクタのButtonのOnClickを設定します。
+を押してNone(Object)に自身に取り付けているGameStartスクリプトをドラッグ&ドロップし、No FunctionにOnGameStartメソッドを設定します。
SelectCharacterTitleシーンのMyGameManagerゲームオブジェクトをCtrl+Cキーでコピーし、SelectCharacterシーンのヒエラルキーでCtrl+Vキーを押して貼り付けます。
これでキャラクター選択機能が出来ました。
ステージ1とステージ2のシーンの作成
SelectCharacterStage1とSelectCharacterStage2はとりあえずキャラクターのプレハブをインスタンス化したときに乗れる床を作成しておきます。
それぞれのシーンのMain Camera等のそのシーンでしか存在しないゲームオブジェクトに新しくGameStartスクリプトを作成し取り付けます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.Collections; using System.Collections.Generic; using UnityEngine; namespace SelectCharacter { public class GameStart : MonoBehaviour { private MyGameManagerData myGameManagerData; // Start is called before the first frame update void Start() { myGameManagerData = FindObjectOfType<MyGameManager>().GetMyGameManagerData(); Instantiate(myGameManagerData.GetCharacter(), Vector3.zero, Quaternion.identity); } } } |
StartメソッドでMyGameManagerゲームオブジェクトで保持しているMyGameManagerDataを取得し、登場させるキャラクタープレハブをインスタンス化させています。
これで機能が出来上がりました。
終わりに
今回はMyGameManagerゲームオブジェクトをシーン遷移時に引き継ぐようにし、必ずMyGameManagerゲームオブジェクトに取り付けているMyGameManagerスクリプトからMyGameManagerDataを取得していますが、個々のスクリプトでMyGameManagerData型のフィールドを用意し個々のスクリプトのインスペクタで設定出来るようにすることも出来ます。
例えばGameStartスクリプトだと以下のような感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | using System.Collections; using System.Collections.Generic; using UnityEngine; namespace SelectCharacter { public class GameStart : MonoBehaviour { [SerializeField] private MyGameManagerData myGameManagerData = null; // Start is called before the first frame update void Start() { Instantiate(myGameManagerData.GetCharacter(), Vector3.zero, Quaternion.identity); } } } |
個人的にはこちらの方がわかりやすいです(設定をする必要がありますが)。
でもたまに別のやり方もやっておかないとやり方を忘れてしまうので・・・・。