【Unity】RPGを作るチュートリアルその66 メニュー画面の実装方針を決める

【Unity】RPGを作るチュートリアルその66 メニュー画面の実装方針を決める

シンプルなRPGをUnityで作るチュートリアルシリーズの66回目です。

第65回ではゲームで使うマップを作成しました。

今回はメニュー画面の機能を作るにあたって、方針を決めてUI作成に着手します。

 

 

制作環境

MacBook Pro 2023 Apple M2 Max

Unity6 (6000.0.30f1) Silicon

 

作業内容と順序

シンプルなRPGを作る上でどんな作業が必要か、どんな順番で作っていくと良さそうか、別ページで検討しました。基本的にこの流れに沿って進めていきます。

 

チュートリアルの一覧

このシリーズ全体の一覧は以下のページにまとめています。

 

前回の内容

前回はゲームで使うマップを作成しました。

 

メニュー画面の実装方針

マップを移動している際にキャラクターの情報を確認したり、アイテムを使用したりできるように、メニュー画面を作成していきます。必要な項目としては、

  • アイテムの使用
  • 魔法の使用
  • 装備の選択
  • ステータスの確認
  • セーブ
  • ゲームの終了
  • メニューを閉じる

あたりでしょうか。キャンセルキーでもメニューを閉じられるようにしておきます。

メニュー画面はドラクエのように、スプライトの上にオーバーレイ表示するようにします。それぞれの機能はおいおい作っていくとして、まずはメニュー画面のトップを表示する仕組みから作っていきたいと思います。

 

UIの作成

トップレベルのUI

メニューのトップレベルのUIから作成していきます。Hierarchyウィンドウから「Canvas」の下にメニュー画面用の親オブジェクトとして空のゲームオブジェクトを作成します。名前は [MenuScreen] にしました。

メニューの親オブジェクトを作成
メニューの親オブジェクトを作成

 

Inspectorウィンドウでは、アンカーの設定で [stretch – stretch] を選択し、縦横ともに引き伸ばして画面全体を覆うようにします。

全体を覆うように
全体を覆うように

 

メニューの背景

続いてメニュー画面の背景用画像のゲームオブジェクトとしてImageを作成します。名前は [MenuBackground] にしました。

背景用画像のゲームオブジェクト
背景用画像のゲームオブジェクト

 

Inspectorウィンドウでは、「Rect Transform」のコンポーネントでアンカーを左上に、「Pos X」を [32] に、「Pos Y」を [-32] に設定します。また、「Width」を [224] に、「Height」を [360] に設定しましょう。「Image」のコンポーネントでは「Source Image」の項目で [message_window] の画像を設定します。

位置と画像の設定
位置と画像の設定

 

メニューの項目

次にメニューの項目を作成します。親オブジェクトを作成して、カーソルの画像、メニュー名のテキスト、という構成にしましょう。戦闘画面のコマンドの項目と同じ構成なので、コピペすると楽です。親オブジェクトは空のゲームオブジェクトで名前が [MenuItem] 、カーソル用画像はImageで名前が [Cursor] 、項目名のテキストはText – TextMesh Proで名前が [MenuText] としています。

メニュー項目
メニュー項目

 

親オブジェクトでは、アンカーを左上に、「Width」を [216] に、「Height」を [64] に設定します。

Rect Transformの設定
Rect Transformの設定

 

カーソルの画像については、「Rect Transform」のコンポーネントでアンカーを左中段に、「Pos X」を [24] に、「Pos Y」を [0] に設定します。また、「Width」を [16] に、「Height」を [16] に設定しましょう。「Image」のコンポーネントでは「Source Image」の項目で [cursor] の画像を設定します。

位置と画像の設定
位置と画像の設定

 

項目名のテキストについては、「Rect Transform」のコンポーネントでアンカーを中段ストレッチに、「Left」を [56] に、「Height」を [50] に設定します。

Rect Transformの設定
Rect Transformの設定

 

「TextMeshPro – Text (UI)」のコンポーネントでは、テキストを [アイテム] に設定します。他の項目についてはコマンドの時と同じ設定のままにしています。

テキストの設定
テキストの設定

 

メニュー項目の複製

作成した「MenuItem」を元に、項目を複製して設定を行います。Rect Transformに関しては「Pos Y」の値を変えて下方向にずらしていきます。メニュー項目の子オブジェクトに関しては、カーソルの名前をメニューに対応する形で変更します。

メニュー項目を作成
メニュー項目を作成

 

メニュー項目 親オブジェクト名 親オブジェクトのPos Y カーソルオブジェクトの名前 メニューテキスト
アイテム MenuItem 0 CursorItem アイテム
魔法 MenuMagic -48 CursorMagic 魔法
装備 MenuEquipment -96 CursorEquipment 装備
ステータス MenuStatus -144 CursorStatus ステータス
セーブ MenuSave -192 CursorSave セーブ
ゲームの終了 MenuQuitGame -240 CursorQuitGame ゲームの終了
閉じる MenuClose -288 CursorClose 閉じる

 

メニュー制御のスクリプトを作成

UIが作成できたら、メニューを制御するスクリプトを作成していきます。今回作りたいのは、

  • メニューの現在の状態を表現する列挙型
  • メニュー項目を表現する列挙型
  • メニューのUIを制御するクラス向けのインタフェース
  • メニューのトップ画面のUIを制御するクラス
  • メニューの各ウィンドウを制御するクラス向けのインタフェース
  • メニューのトップ画面を制御するクラス
  • メニュー全体を管理するクラス

です。戦闘画面のUIを作成した時と同様に、各画面に関して、動作の制御とUIの制御を行うクラスを作成していきます。

 

メニューの現在の状態を表現する列挙型

メニューの画面がどの状態になっているのかを表現する列挙型(Enum)を作成します。これは戦闘画面で作成した「BattlePhase」と同じようなイメージです。

Projectウィンドウから「Assets/Scripts/Enums」のフォルダを開き、空のスクリプトファイルを作成します。名前は [MenuPhase] にしました。

メニューの状態のEnumを作成
メニューの状態のEnumを作成

 

作成した「MenuPhase」の中身は以下のように記載しました。

Closedはメニューを閉じている状態、Topはメニュー画面のトップ、その他の項目は「閉じる」以外のUIと対応させる形になっています。

 

メニュー項目を表現する列挙型

メニューのトップ画面にある項目について、どれが選択されたか分かるようにEnumとして実装したいと思います。こちらも戦闘画面の「BattleCommand」と同じようなイメージです。

同じくProjectウィンドウから「Assets/Scripts/Enums」のフォルダにて、空のスクリプトファイルを作成します。名前は [MenuCommand] にしました。

メニュー項目のEnumを作成
メニュー項目のEnumを作成

 

作成した「MenuCommand」の中身は以下のように記載しました。

「MenuPhase」と似たような項目になりましたが、トップ画面で区別する用途で使用します。

 

メニューのUIを制御するクラス向けのインタフェース

戦闘画面のUIと同様に、共通で実装したいメソッドについてインタフェースを作りたいと思います。表示、非表示に関しては共通しているので、これをインタフェース内に入れるイメージです。

Projectウィンドウから「Assets/Scripts」のフォルダに移動し、メニュー関連のスクリプトファイルを格納するためのフォルダを作成します。名前は [Menu] にしました。

フォルダの作成
フォルダの作成

 

作成した「Menu」フォルダに移動し、空のスクリプトファイルを作成します。名前は [IMenuUIController] にしました。

インタフェースの作成
インタフェースの作成

 

作成した「IMenuUIController」の中身は以下のように記載しました。

画面の動きに関しては戦闘画面と同じような構成で進めたいので、このインタフェースでも表示と非表示のメソッドを実装しています。

 

メニューのトップ画面のUIを制御するクラス

メニューのトップ画面のUIを制御するクラスを作成します。戦闘のコマンド画面と同じように、カーソルの表示、非表示を制御する動作がメインです。

Projectウィンドウから「Assets/Scripts/Menu」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [TopMenuUIController] にしました。

UIを制御するクラスの作成
UIを制御するクラスの作成

 

作成した「TopMenuUIController」の中身は以下のように記載しました。

やっていることは戦闘画面の「CommandUIController」とほとんど同じで、対応するカーソル画像への参照が増えているのと、項目の判別に「MenuCommand」を使用している部分が変わっています。

 

メニューの各ウィンドウを制御するクラス向けのインタフェース

戦闘画面のウィンドウと同様に、共通で実装したいメソッドについてインタフェースを作りたいと思います。こちらも初期設定、表示、非表示のメソッドを入れます。

Projectウィンドウから「Assets/Scripts/Menu」のフォルダにて、空のスクリプトファイルを作成します。名前は [IMenuWindowController] にしました。

メニューのウィンドウ用インタフェース
メニューのウィンドウ用インタフェース

 

作成した「IMenuWindowController」の中身は以下のように記載しました。

UI制御クラス用のインタフェースと同様に、戦闘画面のウィンドウ用インタフェースとほとんど同じです。コントローラの初期化時にはメニューの管理クラスも渡すようにしています。

現時点では「MenuManager」を実装していないのでコンパイルエラーが出力されますが、後ほど作成していきます。

 

メニューのトップ画面を制御するクラス

戦闘画面のコマンドウィンドウを制御するクラスを参考に、メニューのトップ画面を制御するクラスを作成していきます。

Projectウィンドウから「Assets/Scripts/Menu」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [TopMenuWindowController] にしました。

ウィンドウを制御するクラスの作成
ウィンドウを制御するクラスの作成

 

作成した「TopMenuWindowController」の中身は以下のように記載しました。

動作もほとんど「CommandWindowController」と同じで、戦闘用のクラスを使っていた部分をメニュー用のクラスに切り替えています。上矢印キー、下矢印キーを押した時の動作も同じ流れで、今回はメニューの項目数が6つなのでそれに合わせてインデックスの確認を行うようにしています。

メニューのトップでキャンセルボタンが押された場合は、メニュー画面を閉じるようにします。フェーズの切り替えも行いたいので、「MenuManager」への通知も行います。閉じる処理に関しては、同じフレーム内でキャンセルボタンの入力が検知されるため、処理順によってはメニューを閉じる処理の後に、メニューを開く処理が呼ばれる可能性があるため、1フレームずらしています。

 

メニュー全体を管理するクラス

メニュー全体を管理するクラスでは、

  • 各ウィンドウへの参照を保持するフィールド
  • 現在のフェーズを保持するフィールド
  • キー入力を確認するメソッド

を追加していきます。

Projectウィンドウから「Assets/Scripts/Menu」のフォルダにて、MonoBehaviourのスクリプトファイルを作成します。名前は [MenuManager] にしました。

メニュー全体を管理するクラス
メニュー全体を管理するクラス

 

作成した「MenuManager」の中身は以下のように記載しました。

メニュー画面の管理クラスでは、メニューに関する状態を保持するのと、メニューボタンの検知を行います。また、各ウィンドウからの通知を受け取って、必要な制御クラスに通知を行うようにします。

メニューの表示中はキャラクターが移動できないように「CharacterMoverManager」を使って動きを止めています。メニューを閉じるコールバックを受け取ったら再び動けるようにしています。

 

既存のクラスの変更

「MenuManager」でゲームの状態を条件にメニューを開くようにしたので、どこかでゲームの状態を初期化するようにしたいと思います。ゲームの状態を保持する「GameStateManager」はstaticなクラスのため、別のクラスから状態変更のメソッドを呼び出すようにします。

 

CharacterStatusSetterの変更

ゲーム全体の進行状態を初期化するクラスについては全体をつなげる作業の時に実装予定なので、暫定的に「CharacterStatusSetter」で状態もセットしちゃいます。あくまで暫定の予定なので、将来的に忘れないように気をつけます(不安)

キャラクターのステータスをセットするタイミングで、ゲームの状態を移動中に切り替えます。

 

EncounterManagerの変更

また、戦闘完了後にゲームの状態を切り替えるにあたって、「EncounterManager」でも状態切り替えの処理を入れます。

戦闘に勝利または逃走した場合はそのまま移動できるようにしたいので、戦闘終了のコールバックであるOnFinishedBattle()のメソッドで状態を切り替えます。敗北した場合は村長との会話イベントを挟みたいので、またそのタイミングで実装予定です。

 

スクリプトのアタッチ

スクリプトを保存したらゲームオブジェクトにアタッチしていきます。

 

TopMenuUIControllerのアタッチ

メニューのトップ画面のUIを制御する「TopMenuUIController」は、メニュー項目の背景オブジェクトである「MenuBackground」にアタッチします。カーソル画像への参照もアサインしましょう。

スクリプトのアタッチ
スクリプトのアタッチ

 

TopMenuUIControllerのアタッチ

メニューのトップ画面の動作を制御する「TopMenuWindowController」は個別のゲームオブジェクトにアタッチします。メニューに関する制御系のゲームオブジェクトはまとめておきたいので、「Managers」の下に「MenuManager」を作成し、そのさらに子オブジェクトとして「TopMenuWindowController」のゲームオブジェクトを作成します。

ゲームオブジェクトの作成
ゲームオブジェクトの作成

 

作成した「TopMenuWindowController」のゲームオブジェクトに、「TopMenuWindowController」のスクリプトをアタッチします。また、「Ui Controller」のフィールドに「MenuBackground」をアサインします。

スクリプトのアタッチと参照のアサイン
スクリプトのアタッチと参照のアサイン

 

MenuManagerのアタッチ

先ほど作成した「MenuManager」のゲームオブジェクトに「MenuManager」のスクリプトをアタッチします。「Top Menu Window Controller」の項目には「TopMenuWindowController」のゲームオブジェクトをアサインします。

スクリプトのアタッチと参照のアサイン
スクリプトのアタッチと参照のアサイン

 

動作確認

動作確認を行うにあたって、メニューのトップ画面は非表示にしておきます。「MenuBackground」のチェックボックスを外して非表示にしましょう。

メニューを非表示に
メニューを非表示に

 

ゲームを実行して、キャンセルボタン(ESCキーまたはXキー)を押してメニューが表示されることを確認します。また、メニュー表示中にキャンセルボタンを押すとメニューが閉じることも確認しておきましょう。「閉じる」の項目を選択した状態で決定ボタン(Zキー、エンターキー、スペースキー)を押してもメニューが閉じます。

メニューの表示を確認
メニューの表示を確認

 

メニューが表示されない場合は、参照切れがないか、スクリプトで変更を適用していない点がないかご確認ください。今回めっちゃたくさんのスクリプトを作成したり変更したりしたので、ひとつひとつ上から確認すると安全です。

メニューの表示、非表示が確認できたら今回は完了です。

 

今回のブランチ

 

まとめ

今回はメニュー画面の機能を作るにあたって、方針を決めてUI作成に着手しました。まずはメニューの表示やトップ画面の動作の大枠を作成したので、項目ごとに動作を追加していきます。

次回はメニュー画面の機能のうち、アイテムの表示、使用の機能を実装していきます。

     

ゲーム開発の攻略チャートを作りました!

CTA-IMAGE

「ゲームを作ってみたいけど、何から手を付けていいか分からない!」


そんなお悩みをお持ちの方向けに、todoがアプリをリリースした経験を中心に、ゲーム作りの手順や考慮すべき点をまとめたe-bookを作成しました。ゲーム作りはそれ自体がゲームのように楽しいプロセスなので、「攻略チャート」と名付けています。


ゲームを作り始めた時にぶつかる壁である「何をしたら良いのか分からない」という悩みを吹き飛ばしましょう!