メインコンテンツまでスキップ

はじめに

キャンバス内のユーザーインターフェースは、エンティティのツリーです。そのルートにはScreenコンポーネントを持つエンティティがあり、インターフェースが存在する空間を定義します。その下で、Elementコンポーネントを持つエンティティが、目に見える矩形になります。イメージ、テキスト、そして他のエレメントをまとめる不可視のグループです。ボタンやスクロールビューなどのコンポーネントが、その上に振る舞いを追加します。このページでは、各環境でUIをセットアップし、ラベルと、クリック回数を数えるボタンを載せたパネルという小さなインターフェースを構築します。

画面中央の暗いパネル。オレンジ色の「Click me」ボタンの上に「Hello, PlayCanvas!」というテキストがある

セットアップ​

スクリーンとエレメントは、それらのコンポーネントシステムを含むアプリケーションであれば描画されます。入力に応答するには、アプリケーションにElementInputも必要です。これは、マウス、タッチ、XRのイベントをエレメント上のイベントに変換するオブジェクトです。

アプリケーションを作成するときに、UIのコンポーネントシステム、フォントとテクスチャのハンドラー、ElementInputを登録します。

const canvas = document.getElementById('application');
const device = await pc.createGraphicsDevice(canvas, {
deviceTypes: [pc.DEVICETYPE_WEBGPU]
});

const options = new pc.AppOptions();
options.graphicsDevice = device;

// ElementInputをマウスとタッチのデバイスより先に作成し、UIのイベントハンドラーで
// stopPropagation()を呼び出したときに、それらのデバイスからもイベントが隠されるようにする
options.elementInput = new pc.ElementInput(canvas);
options.mouse = new pc.Mouse(canvas);
options.touch = new pc.TouchDevice(canvas);

options.componentSystems = [
pc.CameraComponentSystem,
pc.ScreenComponentSystem,
pc.ElementComponentSystem,
pc.ButtonComponentSystem
];
options.resourceHandlers = [
pc.FontHandler,
pc.TextureHandler
];

const app = new pc.AppBase(canvas);
app.init(options);
app.setCanvasFillMode(pc.FILLMODE_FILL_WINDOW);
app.setCanvasResolution(pc.RESOLUTION_AUTO);
app.start();

window.addEventListener('resize', () => app.resizeCanvas());

他のUIコンポーネントを使う場合は、そのシステムも追加します。pc.ScrollViewComponentSystem、pc.ScrollbarComponentSystem、pc.LayoutGroupComponentSystem、pc.LayoutChildComponentSystem、さらにスプライトにはpc.SpriteHandlerとpc.TextureAtlasHandlerが必要です。pc.Applicationはすべてのシステムとハンドラーを自動的に登録しますが、こちらもElementInputは作成しないため、オプションで渡してください。その他のセットアップについてはエンジンをスタンドアロンで使用するを参照してください。

最初のインターフェース​

上の画像のインターフェースは、パネルを1つ載せたスクリーンです。パネルはラベルとボタンを保持し、ボタンは自身のテキストを保持します。テキストにはフォントアセットが必要です。例ではarial.jsonというフォントアセットを読み込みます。これは任意のフォントファイルから生成できます。

アプリケーションを上記のようにセットアップしたうえで、次のように記述します。

// テキスト用のフォントを読み込む
const font = new pc.Asset('arial', 'font', { url: 'fonts/arial.json' });
app.assets.add(font);
await new Promise((resolve) => {
font.ready(resolve);
app.assets.load(font);
});

const camera = new pc.Entity('camera');
camera.addComponent('camera', { clearColor: new pc.Color(0.1, 0.11, 0.13) });
app.root.addChild(camera);

// キャンバスに合わせてスケーリングする、スクリーン空間のスクリーン
const screen = new pc.Entity('screen');
screen.addComponent('screen', {
screenSpace: true,
referenceResolution: [1280, 720],
scaleMode: pc.SCALEMODE_BLEND,
scaleBlend: 0.5
});
app.root.addChild(screen);

// スクリーン中央のパネル
const panel = new pc.Entity('panel');
panel.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [0.5, 0.5, 0.5, 0.5],
pivot: [0.5, 0.5],
width: 420,
height: 240,
color: new pc.Color(0.16, 0.18, 0.23),
opacity: 0.9
});
screen.addChild(panel);

// パネルの上端付近のラベル
const label = new pc.Entity('label');
label.addComponent('element', {
type: pc.ELEMENTTYPE_TEXT,
fontAsset: font.id,
text: 'Hello, PlayCanvas!',
fontSize: 36,
anchor: [0.5, 1, 0.5, 1],
pivot: [0.5, 1]
});
label.setLocalPosition(0, -40, 0);
panel.addChild(label);

// 下端付近のボタン:入力を受け取るイメージエレメントと、ホバー時と押下時に
// イメージにティントをかけるButtonコンポーネント
const button = new pc.Entity('button');
button.addComponent('element', {
type: pc.ELEMENTTYPE_IMAGE,
anchor: [0.5, 0, 0.5, 0],
pivot: [0.5, 0],
width: 200,
height: 60,
color: new pc.Color(1, 0.55, 0.2),
useInput: true
});
button.addComponent('button', {
imageEntity: button,
hoverTint: new pc.Color(1, 0.7, 0.45),
pressedTint: new pc.Color(0.8, 0.4, 0.1)
});
button.setLocalPosition(0, 40, 0);
panel.addChild(button);

const buttonText = new pc.Entity('text');
buttonText.addComponent('element', {
type: pc.ELEMENTTYPE_TEXT,
fontAsset: font.id,
text: 'Click me',
fontSize: 28,
color: new pc.Color(0.1, 0.1, 0.1),
anchor: [0.5, 0.5, 0.5, 0.5],
pivot: [0.5, 0.5]
});
button.addChild(buttonText);

// クリック回数を数える
let clicks = 0;
button.button.on('click', () => {
clicks++;
label.element.text = `Clicked ${clicks} times`;
});
Basic Button

全体の仕組み​

上のインターフェースのエンティティツリーは次のとおりです。

screen Screenコンポーネント:インターフェースの空間を定義するルート
└── panel イメージエレメント
├── label テキストエレメント
└── button 入力を受け取るイメージエレメントと、Buttonコンポーネント
└── text テキストエレメント

このツリーからいくつかのルールが導かれ、このセクションの他のページはそれらを前提としています。

  • エレメントはスクリーンの単位でレイアウトされます。 この例のようにスケーリングするスクリーン空間のスクリーンでは、1単位は1280 × 720の基準解像度における1ピクセルで、すべてがキャンバスに合わせて拡大・縮小します。スクリーンを参照してください。
  • 子は親を基準に配置されます。 各エレメントのアンカーは親の点または辺を選び、ピボットはそこに合わせるエレメント側の点を選びます。ラベルはパネルの上端にアンカーされているため、パネルを移動したりサイズを変更したりすると、ラベルも一緒に動きます。y軸は上向きです。エレメントを参照してください。
  • ヒエラルキーが描画順になります。 親は子より先に描画され、兄弟同士では前にあるものが後ろにあるものより先に描画されるため、ボタンのテキストはボタンの上に描画されます。描画順とパフォーマンスを参照してください。
  • 入力が有効なエレメントだけが入力を受け取ります。 ボタンのイメージエレメントはuseInputがオンになっており、そのイベントは祖先へとバブリングします。入力を参照してください。

環境ごとの命名​

どの環境も同じコンポーネントを操作するため、プロパティの名前は1つですが、その表記は4通りあります。

環境表記例
Engineキャメルケースのプロパティ。ベクトルと色はpc.Vec2、pc.Vec4、pc.Colorのオブジェクトで、addComponentに渡す場合は配列も使えるfontSize: 36, anchor: [0.5, 1, 0.5, 1]
Editor各単語の先頭が大文字(Title Case)のインスペクターのフィールドFont Size, Anchor
Reactキャメルケースのprops。ベクトルは配列、色はCSSの色文字列fontSize={36}, anchor={[0.5, 1, 0.5, 1]}, color="#ff8c33"
Web Componentsケバブケースの属性。ベクトルはスペース区切りの数値font-size="36", anchor="0.5 1 0.5 1"

Reactのpropも、Web Componentsの属性もないプロパティがいくつかあります。例えば、Reactはkeyというpropを予約しており、<pc-element>にはlayersやrectの属性がありません。これらは代わりにエンジンのコンポーネントに設定します。ReactではuseParent()または<Entity ref>から得られるエンティティを通して、Web ComponentsではwhenReadyが解決した後に要素のcomponentプロパティを通して設定します。

環境によって異なるデフォルト値​

コンポーネントのデフォルト値はエンジンのものですが、Reactはその一部を変更しています。そのため、オプションを指定せずに作成したスクリーンやエレメントは、どこでも同じ見た目になるわけではありません。

EngineReactWeb Components
オプションなしのスクリーンワールド空間、スケールモードはNone、640 × 320スクリーン空間、スケールモードはBlend、基準解像度1280 × 720ワールド空間、スケールモードはNone、640 × 320
エレメントのアンカーとピボット左下:0, 0, 0, 0と0, 0左下左下
エレメントのサイズ32 × 3232 × 3232 × 32
入力デバイスAppOptionsに渡したものマウス、タッチ、エレメント入力マウス、キーボード、エレメント入力

このページの例のように、依存するプロパティは明示的に設定してください。エディターのインスペクターには新しいエンティティのすべての値が表示されるため、そこで確認してください。

関連情報​