Class: TonMenu
RPGなどのゲームで見られる「ウィンドウシステム」「コマンドメニュー」を実装するためのクラスです。
単純なテキスト選択肢から、アイテムリスト、設定画面のような複雑なUIまで幅広く対応します。
かなり難しいですが、自作するより遥かに楽なので、サンプルを触って理解してみましょう。
アーキテクチャと概念
TonMenu システムは、主に 「メニュー本体 (TonMenu)」 と 「管理クラス
(TonMenuManager)」 の2つで構成されています。
flowchart TD
Game[ゲームシーン] -->|Update/Draw| Manager[TonMenuManager]
subgraph StackStruct ["TonMenuManager (スタック構造)"]
Manager -->|管理| Stack
Stack -->|前面| MenuA["TonMenu (Active)"]
Stack -->|背面| MenuB["TonMenu (Inactive/Paused)"]
end
MenuA -->|描画| ItemsA[アイテムとレイアウト]
MenuB -->|描画| ItemsB[アイテムとレイアウト]
実際のゲーム画面におけるTonMenuManager、TonMenu、TonMenuItem、TonMenuElementの関係性
この図は、TonMenuシステムの全体像を表しています。なぜ「Manager」と「Menu」が分かれているのでしょうか?
それは、複数のメニュー(アイテム画面、装備画面など)が重なり合う状況を管理するためです。「Stack(スタック)」という構造を使うことで、新しいメニューを開いたときに古いメニューを「下」に保存し、閉じれば元に戻るという、RPGでよくある挙動を自動的に処理してくれます。Game
SceneはManagerだけに指示を出せばよく、個々のメニューの状態を細かく気にする必要がありません。
- TonMenuManager: 入力を受け付け、現在アクティブなメニューに操作を伝えます。また、ウィンドウの重なり順(スタック)を管理します。
- TonMenu: 1つのメニューウィンドウを表します。アイテム(選択肢)のリストを持ち、カーソル移動やスクロール制御を行います。
Quick Start
Step 1: マネージャーの初期化
classDiagram
class ゲームシーン {
TonMenuManager _menuManager
}
ゲームシーン --> TonMenuManager : 所有
このクラス図は「所有(Ownership)」の関係を示しています。ゲームシーンが
TonMenuManagerを持っている、つまり「マネージャーの生存期間はシーンと同じ」であることを意味します。シーンが始まるときにマネージャーを作り、シーンが終われば一緒に消える、という管理の責任範囲を表しています。
private TonMenuManager _menuManager;
public void Initialize()
{
// マネージャーの生成
_menuManager = new TonMenuManager();
}
Step 2: メニューの作成
graph LR
Create[new TonMenu] --> Config[設定]
Config --> |SetContentScale| Size[コンテンツ(画像・テキスト)サイズ調整]
Config --> |SetCursorIcon| Icon[カーソル画像]
Config --> |SetLoopable| Loop[カーソルループ設定]
メニューを作る際の流れです。いきなり完成品を作るのではなく、まず「空のメニュー」を生成し、そこへ「コンテンツ(画像・テキスト)サイズ」「カーソル画像」「カーフルループ設定」といったオプションを後付けで設定していく様子を表しています。これにより、必要な機能だけを選んでカスタマイズできる柔軟な設計になっています。
// 位置(50,50), ウィンドウサイズ(240x150), 1列4行, 各アイテムサイズ(200x50), 空白選択不可
var menu = new TonMenu(new Rectangle(50, 50, 240, 150), 1, 4, 200, 50, false);
// 外観設定
menu.SetContentScale(0.8f); // コンテンツ(画像・テキスト)サイズ
menu.SetCursorIcon("finger"); // カーソル画像
menu.SetLoopable(true); // カーソル上下ループ有効
Step 3: アイテムの追加
sequenceDiagram
participant Code
participant Menu
participant Item
Code->>Item: new TonMenuItem()
Code->>Item: SetLayout(Text/Icon...)
Code->>Item: OnDecided = () => { ... }
Code->>Menu: AddItem(Item)
プログラムコードがどのようにアイテム(選択肢)を作ってメニューに渡すかを示しています。重要なのは、「決定時の動作(OnDecided)」をアイテム作成時に決めている点です。「このアイテムが選ばれたら何をするか」というロジックをアイテム自身に持たせることで、メニュー側は中身を気にせず「選ばれたら実行するだけ」というシンプルな構造を保てます。
// アイテム追加
menu.AddItem(CreateTextItem("たたかう", () => {
Console.WriteLine("攻撃!");
}));
// テキストのみのアイテム作成ヘルパー
private TonMenuItem CreateTextItem(string text, Action action)
{
// メニューアイテムを作成
var item = new TonMenuItem();
// 空のレイアウトパネルを作成してテキストを追加
var layout = new TonMenuPanel(TonMenuPanel.LayoutType.Free);
// レイアウトパネルにテキスト要素を追加
layout.AddChild(new TonMenuText(text));
// メニューアイテムにレイアウトを設定
item.SetLayout(layout);
// 決定時の動作を設定
item.OnDecided = action;
return item;
}
Step 4: メニューの表示 (Push)
graph TD
TonMenuManager -->|Push| Stack
Stack -->|Active!| New_TonMenu
Stack -->|Pause...| Old_TonMenu
「Push(プッシュ)」という操作の概念図です。お皿を積み重ねるように、新しいメニュー(New TonMenu)を既存のメニュー(Old TonMenu)の上に載せます。
これにより、下のメニューは削除されずに「一時停止(Paused)」状態で待機できます。キャンセルボタンで上のメニューをどかせば、下のメニューがそのままの状態で復帰する。これがスタック管理の利点です。
// メニューを開く
_menuManager.Push(menu);
Step 5: ループ処理 (Update/Draw)
graph TD
GameUpdate[Game.Update] --> ManagerUpdate[Manager.Update]
ManagerUpdate -->|Input| ActiveMenu[ActiveMenu.Control]
GameDraw[Game.Draw] --> ManagerDraw[Manager.Draw]
ManagerDraw -->|1. Callback| DrawWindow[ウィンドウ・スクロールカーソル描画]
ManagerDraw -->|2. Draw| DrawContents[TonMenuItem描画]
ゲームループ(毎フレーム繰り返される処理)の中で、メニューがどう動くかを表しています。
Update(更新): 操作を受け付けるのは「一番上のアクティブなメニュー」だけです。下のメニューが勝手に動かないように制御されています。
Draw(描画):
逆に描画は、ウィンドウの背景を描いてから中身を描く、という順序で丁寧に行われます。これにより、ウィンドウ枠の上に文字が綺麗に乗ることになります。ユーザはウィンドウとスクロールカーソルの描画を自分で行う必要があります。これはある程度デザイン上の汎用性をもたせるためです。逆にTonMenuItem内に画像を配置した場合、標準機能ではこれをアニメーションさせたりすることはできません。カーソルもです。
public void Update(GameTime gameTime)
{
// 入力制御などを一任
_menuManager.Update();
}
public void Draw()
{
// メニュー描画
// 引数のコールバックで「メニューごとのウィンドウ背景」を描画します
_menuManager.Draw((menu) =>
{
if (menu.IsActive)
Ton.Gra.FillRoundedRect("window_bg_active", menu.WindowRect...);
else
Ton.Gra.FillRoundedRect("window_bg_inactive", menu.WindowRect...);
});
}
Information
ライフサイクルイベント
状態変化のタイミングで様々なコールバックが発火します。
stateDiagram-v2
[*] --> Inactive : 生成
Inactive --> Active : Push / Resume (OnEnter/OnResume)
Active --> Paused : Submenu Push (OnPause)
Paused --> Active : Submenu Pop (OnResume)
Active --> Inactive : Pop (OnExit)
Active --> Active : カーソル移動 (OnSelectionChanged)
Active --> Active : 決定操作 (OnDecided)
メニューが生まれてから消えるまでの「一生」の状態変化(ステートマシン)です。
単に開閉するだけでなく、「サブメニューが開かれて裏に回ったとき(Paused)」や「再び表に出てきたとき(Active)」といった細かい状態があります。これらのタイミングでイベント(OnPause,
OnResume)が呼ばれるため、細やかな演出が可能になります。
| イベント | タイミング | 主な用途 |
|---|---|---|
OnEnter |
メニューが表示され、操作可能になった瞬間 | ヘルプ表示、開始SE |
OnExit |
メニューが閉じられる(Pop)瞬間 | 後始末、終了SE |
OnPause |
サブメニューが開かれ、背後に回る時 | 装飾の解除 |
OnResume |
サブメニューが閉じられ、最前面に戻った時 | ヘルプ復帰 |
OnPostDraw |
描画完了後 | スクロール矢印などのカスタム描画 |
Methods
TonMenuのコンストラクタ。
rect: メニューの表示領域(X, Y, Width, Height)column: 列数(横のアイテム数。列数は固定)row: 行数(縦のアイテム数=表示行数)width: 1アイテムあたりの幅height: 1アイテムあたりの高さbAllowBlankSelect: アイテムがない空白部分へのカーソル移動を許可するかbAllowMultiSelect: 複数選択を許可するか(現在は予約)
使用するフォントを設定します。
fontId: 使用するフォントのID
テキストの基本色を設定します。
defaultColor: 通常時の色disabledColor: 無効(Enabled=false)時の色
カーソルの背景色と枠色を設定します。
selectedColor: 選択時の背景色frameColor: 枠の色(省略時は白)
カーソル用の画像アイコンを設定します。
iconName: アイコン画像名(Ton.Gra.LoadTextureでロードしたもの)offset: 表示位置オフセット(省略時は(0,0))
内部のテキストやアイコンの描画倍率を設定します。
scale: 描画スケール(1.0が等倍)
カーソルが端でループするか設定します。
isLoop: カーソル移動をループさせるかどうか
パネルレイアウト内のテキスト描画オフセットを設定します。
offset: テキスト描画時のX座標オフセット(px)
メニューにアイテムを追加します。
item: 追加するメニュー項目
メニューの内容をクリアし、カーソル位置やスクロールをリセットします。
現在選択中のアイテムを取得します。空白選択時はnullを返すことがあります。