Skip to content

UI ヘルパーとレイアウト

ゲームでよく使う UI パーツ(HP バー、メッセージウィンドウ、選択メニューなど)を簡単に作れるヘルパー関数と、ノードの配置を自動計算するレイアウトヘルパーを紹介します。

HP バー

HP や MP のゲージを 3 ノード構成(背景 + 塗り + ラベル)で作成します。残量に応じて色が自動で変わります。

typescript
import { createHpBar } from '@uzuhq/engine-2d';

const hpBar = createHpBar(engine, {
  id: 'hp',
  x: 20,
  y: 400,
  width: 200,
  height: 18,
  label: 'HP',
  labelFont: 'bold 11px sans-serif',
  colors: { high: '#22c55e', mid: '#eab308', low: '#ef4444' },
});

// 更新(現在値, 最大値)
hpBar.update(75, 100); // 75% → 緑
hpBar.update(40, 100); // 40% → 黄色
hpBar.update(15, 100); // 15% → 赤

テキストウィンドウ

タイプライター演出(1 文字ずつ表示)付きのメッセージ表示です。RPG の会話シーンに最適です。

typescript
import { createTextWindow } from '@uzuhq/engine-2d';

const tw = createTextWindow(engine, {
  id: 'msg',
  x: 20,
  y: 300,
  width: 560,
  height: 80,
  font: 'bold 16px sans-serif',
  charDelay: 25, // 1 文字あたりのミリ秒
});

// メッセージ表示(タイプライター完了まで await)
await tw.show('勇者のこうげき!');

// 非表示
tw.hide();

選択メニュー

キーボード(↑↓ + Enter)とタップの両方に対応した選択メニューです。選択結果のインデックスを返します。

typescript
import { createMenu } from '@uzuhq/engine-2d';

const menu = createMenu(engine, {
  id: 'cmd',
  x: 20,
  y: 440,
  width: 270,
  font: 'bold 18px sans-serif',
});

// メニューを開く(選択されるまで await)
const index = await menu.open(['たたかう', 'まほう', 'にげる']);
// index: 0, 1, 2

menu.close();

仮想ゲームパッド

モバイル端末向けの D-pad + アクションボタンです。engine.raw() でスクリーン空間に描画され、押下中は engine.simulateKeyDown() で合成キー入力を注入します。そのため、既存の isKeyDown() ベースのコードがそのまま動きます。

typescript
import { createVirtualGamepad } from '@uzuhq/engine-2d';

const gamepad = createVirtualGamepad(engine, {
  buttons: ['a', 'b'],
  keyMap: { a: 'KeyZ', b: 'KeyX' },
  autoHide: true, // 非タッチデバイスで自動非表示
});

// 状態の取得
gamepad.isPressed('a'); // boolean
gamepad.direction(); // { x, y } 正規化ベクトル

// 表示制御
gamepad.show();
gamepad.hide();

// 破棄
gamepad.destroy();

設定

プロパティデフォルト説明
dpadMode'4way' | '8way''8way'D-pad の方向モード
buttons('a' | 'b')[]['a', 'b']表示するボタン
alphanumber0.35透明度
autoHidebooleanfalseタッチ非対応デバイスで自動非表示
keyMapobjectArrow + Z/X各入力に対応するキーコード

スクロールコンテナ

ドラッグ操作でスクロールできるコンテナです。内部で clipRect を使って描画領域を制限し、スクロールバーも表示されます。アイテムリストやインベントリ画面に便利です。

typescript
import { createScrollContainer } from '@uzuhq/engine-2d';

const scroll = createScrollContainer(engine, {
  id: 'list',
  x: 20,
  y: 100,
  width: 260,
  height: 340,
  contentHeight: 800, // スクロール可能な全体の高さ
});

// アイテムを追加(自動的にコンテナの子になる)
scroll.addItem('item-0', {
  type: 'text',
  x: 10,
  y: 10,
  text: 'Item 1',
  fill: '#fff',
});
scroll.addItem('item-1', {
  type: 'text',
  x: 10,
  y: 50,
  text: 'Item 2',
  fill: '#fff',
});

// プログラムでスクロール
scroll.setScrollY(100);

// コンテンツの高さを変更
scroll.setContentHeight(400);

// 全アイテム削除
scroll.clear();

// コンテナ自体を破棄
scroll.destroy();

設定

プロパティデフォルト説明
idstring必須コンテナ ID
x, ynumber必須位置
width, heightnumber必須表示領域サイズ
contentHeightnumber必須スクロール可能な全体高さ
bgColorstring'rgba(0,0,20,0.85)'背景色
borderColorstring'#4a5568'枠線色
scrollbarWidthnumber6スクロールバー幅
scrollbarColorstring'#64748b'スクロールバー色

レイアウトヘルパー

ノードの位置を自動計算する純粋関数です。addMany() と組み合わせて使います。

横並び: layoutRow(opts, children)

typescript
import { layoutRow } from '@uzuhq/engine-2d';

const cards = layoutRow({ x: 50, y: 300, gap: 8 }, [
  { id: 'c1', type: 'sprite', image: 'card1', width: 48, height: 64 },
  { id: 'c2', type: 'sprite', image: 'card2', width: 48, height: 64 },
  { id: 'c3', type: 'sprite', image: 'card3', width: 48, height: 64 },
]);
// → c1.x = 50, c2.x = 106, c3.x = 162

engine.addMany(cards);

縦並び: layoutColumn(opts, children)

typescript
import { layoutColumn } from '@uzuhq/engine-2d';

const buttons = layoutColumn({ x: 100, y: 50, gap: 10 }, [
  { id: 'b1', type: 'rect', width: 200, height: 40, fill: '#333' },
  { id: 'b2', type: 'rect', width: 200, height: 40, fill: '#333' },
  { id: 'b3', type: 'rect', width: 200, height: 40, fill: '#333' },
]);
// → b1.y = 50, b2.y = 100, b3.y = 150

engine.addMany(buttons);

オプション

プロパティ説明
xnumber起点の x 座標
ynumber起点の y 座標
gapnumber?ノード間の間隔(デフォルト 0)
centerInnumber?指定した幅(または高さ)の中央に揃える

アセット管理

画像のロードとキャッシュを管理します。

初期ロード

createEngine()assets.images で指定した画像は engine.ready で完了を待てます。

typescript
const engine = createEngine({
  canvas,
  assets: {
    images: { player: '/sprites/player.png', enemy: '/sprites/enemy.png' },
  },
});
await engine.ready;

追加ロード

ゲーム中に追加で画像を読み込めます。

typescript
// 単体
await engine.loadImage('boss', '/sprites/boss.png');

// バッチ(進捗コールバック付き)
await engine.preload(
  {
    bg: '/sprites/bg.png',
    items: '/sprites/items.png',
    ui: '/sprites/ui.png',
  },
  (loaded, total) => {
    const pct = Math.floor((loaded / total) * 100);
    engine.update('loading', { text: `Loading... ${pct}%` });
  },
);

キャッシュの取得

typescript
const img = engine.image('player'); // HTMLImageElement | null