メニューを作る(1)・メニュー階層の実装(Unityメモ)
料理ではなく機能選択の方です。
ゲームのGUIを考える
ゲームに限らずアプリケーションで行う作業は、手段としてはGUIを使うことがメインとなります。時間をかけて作るのならGUIはきちんと考えておきたいです。
ただし、ゲームの世界観として業務システムを使うような雰囲気を出したいので、作成するゲームのGUIも一般のアプリケーションに似た考え方で作ることにしました。
具体的には、Web UIのガイドライン抜粋をざっくりと適用することにしました。以下のルールでGUIを考えることにします。
Webでのガイドライン抜粋(WCAG2.1)を簡略化したルール
・色相だけで区別しない。明度込みで区別できる
・モノクロでも区別できる
・マウスオーバーしなくても区別できる
・色以外の区別手段を設ける
AppleやGoogleのアプリケーションガイドラインを簡略化したルール
・肯定的な動作は右側
・動詞で提示。はい/いいえは不可
・破壊的な動作は赤系の色で強調(不可逆な動作、削除や進撃など)
配色とコントラスト比
<コントラスト比:相対輝度の比、WCAG2.1を基に記載>
・AAA:テキスト - 背景間は7.0:1以上
・AA :テキスト - 背景間は4.5:1以上
・A :テキスト - 背景間は3.0:1以上
細字:推奨AAA、最低AA
太字:推奨AA、最低A
・地の色、プライマリ、セカンダリ、ターシャリを用意。地の色とのコントラスト比を以下の値以上になるようにした。
(1.0倍) 地の色:背景色
1.5倍 プライマリ:強調色 ※背景色として使用
4.5倍 セカンダリ:警告色、赤系 ※背景色として使用
7.0倍 ターシャリ:テキストの色
※プライマリのコントラスト比については、セカンダリとプライマリの比が3.0倍になるようにした
配色を考える
上記のルールを基に配色を考えました。モノクロで考えるので、不可逆な動作への警告系の表示は色に頼らず反転表示を使って表すことにします。
ゲームのもう一つの世界観として植物系のモチーフを使いたいため、地の色で緑系の色を使うことにしました。警告は反転表示で表すため、明度を変えておけば色に赤系を使っても識別できそう。
あくまで疑似的なチェックですが、色覚チェックはChromeの開発者ツールで確認しました。
https://accessible-usable.net/2021/07/entry_210711.html
配色を変えるときも同じような考え方で設計できそうです。
ちなみに配色については、以下の記事を読んで少し考慮してみました。
ユースケースとウィンドウの直交表
周回ゲームにおいては、周回するまでの準備がゲームの大半を占めます。この準備作業がそのままゲームメニューにあたります。
当初は画面ベースのシーン単位でメニューを考えたのですが、同じ情報を扱うウィンドウをまとめていくと、シーンではうまくメニューを整理することができませんでした。次にデータベース単位でメニューを考えたのですが、当然のようにプレイヤーの操作とデータベースが入り混じって混沌としてしまいました。
そこで、プレイヤーが作業したいと想定されること、つまりユーザのユースケースを洗い出し、ユースケースとウィンドウの直交表としてUIの体系(システム)を考えることにしました。
UIのレイアウトを考える
自分しか遊ばない前提でシステムを作っているので、裏を返せば自分が作ってみたいUIを試せる、ということになります。この非実在性がゲーム的と言えなくもないかも。
スマホ操作を視野に入れて大まかなレイアウトを考え、同時に片手持ちで使いやすそうな操作を考えました。どうせならと横向きの片手持ちというロマンを追い求めました。実装が難しそうならあきらめます。特にトラックパット風の入力機能。
ビューとコンテントの分離
おおまかにメニュー構成とレイアウトが決まったところで実装を始めました。
以前にも取り上げたUnity Screen Navigatorを使おうかと思ったのですが、安直に採用してもおそらく効率的にはGUIを組めないことが想像できました。というのも、Unity Screen Navigatorを使ったとしてもユースケースを実現するための画面はイチから実装してゆく必要があります。要するに上位の設計であるユースケースの分離と整理はUnity Screen Navigatorの範囲外なので、画面実装の手間を減らすにはユースケースを自前で整理する必要があります。
画面遷移の実装については、Unity Screen Navigatorの作者自身がビューとロジックの分離についての記事を書いています。
そこでビューとロジックの分離にならって、ビューとコンテントの分離を行うことにしました。発想は以下の記事を基にしました。
タイトルで少し損していますが、設計の指針となる良い記事です。個人的には以下の点に気づくことができました。
・モデル=DB上のデータと、表示に必要なデータは別
・モデルとは別に、モデルの優先度とグループ化の情報が必要
この「モデルを表示するために必要な情報」をコンテントと呼ぶことにして、制作中のゲームでは、モデル・コンテント・ビューに分けてシステムを設計するようにしています。
さて、実装中のGUIにおいては、ビューという画面表示の動作と、ビューを動作させるためのデータであるコンテントとを分離し、共通のフォーマットでコンテントを記述できることを狙っています。ユースケースを実現するためのコンテントを追加すれば、ビューには手を加えなくても良い、という目論見です。要するにHTMLとブラウザみたいな関係です。
今回はユースケースを、それを実現するための引数となる「スロット」と、スロットの情報を作るための「操作」に分解して整理しました。それに従い、スロットと操作の組み合わせで記述できるようにコンテントを構成するクラスを定義しました。
スロットの情報を作る際に、もっと細かい情報が必要となることがあるので、スロットは子スロットを持てるようにしました。つまりスロットは木構造をとります。スロットという枝に操作という葉がついているイメージです。HTMLの要素と属性のような関係にも似ています。
コンテントを構成するクラスの設計はユースケースとウィンドウの直交表から考えましたが、結局はメニューの木構造に似たデータ構造になりました。
メニューのデータ構造をコードで実装するとこんな感じです。このデータ構造をビューによりGUIとして動作させます。
void SetMenuSchema()
{
//メニュー階層のContentを設定
// "scene"=スロット選択の操作はデフォルトで追加される
var party_schema = new UISlot
{
asset = "party",
ope = new List<UIOpe> { new UIOpe { asset = "select" }, new UIOpe { asset = "edit" } },
slot = new List<UISlot> {
new UISlot{asset="formation", ope= new List<UIOpe>{ new UIOpe{ asset="select"}, new UIOpe{asset="info"} } },
new UISlot{asset="unit", ope= new List<UIOpe>{ new UIOpe{ asset="list"}, new UIOpe { asset = "filter" }, new UIOpe {asset = "sort"}, new UIOpe{asset="info"} } },
new UISlot{asset="action", ope= new List<UIOpe>{ new UIOpe{ asset="select"}, new UIOpe{asset="info"} } },
new UISlot{asset="allot", ope= new List<UIOpe>{ new UIOpe{ asset="assign"} } },
}
};
var sortie_schema = new UISlot
{
asset = "sortie",
ope = new List<UIOpe> { new UIOpe { asset = "confirm" } },
slot = new List<UISlot> {
party_schema,
new UISlot{asset="map", ope= new List<UIOpe>{ new UIOpe{ asset="region"}, new UIOpe {asset = "stage"}, new UIOpe{asset="info"} } },
new UISlot{asset="task", ope= new List<UIOpe>{ new UIOpe{ asset="list"}, new UIOpe { asset = "filter" }, new UIOpe {asset = "sort"}, new UIOpe{asset="info"} } },
}
};
var product_schema = new UISlot
{
asset = "product",
ope = new List<UIOpe> {new UIOpe { asset = "select" }},
slot = new List<UISlot> {
new UISlot{ asset="resourceA", ope = new List<UIOpe>{new UIOpe { asset = "view" }, new UIOpe { asset = "edit" }},
slot=new List<UISlot>{ new UISlot { asset = "tree" }, new UISlot { asset = "unit" }, new UISlot { asset = "worker" } },
},
new UISlot{ asset="resourceB", ope = new List<UIOpe>{new UIOpe { asset = "view" }, new UIOpe { asset = "edit" }},
slot=new List<UISlot>{ new UISlot { asset = "tree" }, new UISlot { asset = "unit" }, new UISlot { asset = "worker" } },
},
}
};
MenuSchema = new UISlot
{
asset = "home",
ope = new List<UIOpe>(),
slot = new List<UISlot> { sortie_schema, product_schema }
};
// "scene"=スロット選択の操作を追加
InsertMenuOpe(MenuSchema);
}
現在時点での実装
ひとまずスロットと操作の切り替えができるようになりました。選択中のスロット名の表示はこれから実装します。
右下の横方向にシーン(トップメニュー、一番大きなユースケース)、右側の縦方向にスロット(サブウィンドウ)一覧、左下の横方向に操作を表示しています。現時点では切り替えたい項目をクリックした後、Goボタンのクリックで切り替えを実行しています。シーンやスロットを切り替えると、それに応じて左下の操作リストが切り替わります。
出撃のために必要なスロット(情報)には、「派遣パーティ」、「出撃先マップ」、「受ける任務」の3点があります。これらのうち、パーティ選択ではさらに細かいスロットがネストして、マップや任務ではスロットがネストしません。
スロットがネストしない場合は、操作リストの切り替えだけが行われます。スロットがネストする場合は、子スロットが表示されます。
上のGIFでは、まず「出撃」シーン→「パーティ」スロットと進んでいくと、パーティスロットの子スロットと、パーティスロットの操作を表示しています。その後「ユニット」スロットを選択してGoで進むと、左下の操作リストがユニットのものに切り替わります。
TODO(その2に続く)
記事と動画を作るうちにTODOがいくつか増えました。
・操作の実装
・スロットのパンくずリストの表示
→現在のスロットとパンくずリストの見やすい表示方法を考える
・選択中のシーンやスロットの強調表示
・切り替えたい項目の選択を「トラックパッド」で行いたい
この記事が気に入ったらサポートをしてみませんか?