🐉

Phaser3のGraphicsクラスについて

2024/03/07に公開

はじめに

Graphicsクラスについて、改めて使い方についてまとめてみます。

Phaser API Documentation | Phaser.GameObjects.Graphics

この記事のソースはCodePenで確認できます。

主な改定履歴

  • 2024/05/22 「Graphicsクラスとマウス操作」を追加しました

基本的な使い方 その1

基本的な流れとしては、まず線のスタイル(lineStyle)と塗りのスタイル(fillStyle)を設定し、そのあと各メソッドを使って図形を描画していきます。

次のコード例では2つの三角形を描画しています。
メソッド名から分かる通り、始点から終点の座標を順番に指定し、最後にfillstrokeメソッドで塗と線を描画しています。
(両メソッドはそれぞれfillPathstrokePathメソッドのエイリアス(別名)です。)

各スタイルは途中で変更できます。
変更するとそれ以降の描画のみに反映され、すでに描画されている図形には影響しません。
コードの例では1つめ図形の描画後に、線と塗のスタイルを変更し、2つめの図形を描画しています。

なお、同じパスに対し線と塗りを一緒に描画する場合、パスの内側部分は線と塗りが重なるのでメソッドの使用順序に注意してください。
strokeメソッドの後にfillメソッドを使用すると、線の幅の半分が塗りで隠れます。

const LINE_WIDTH = 5; // ラインの太さ

// (1) コンストラクタで線と塗りのスタイルを指定
const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});

grph.beginPath()
  .moveTo(150, 100)
  .lineTo(100, 200)
  .lineTo(200, 200)
  .closePath()
  .fill()
  .stroke();

// 途中で、ペンと塗のスタイルの変更
grph.lineStyle(LINE_WIDTH + 15, 0x00A000, 1)
  .fillStyle(0xA0F0A0, 1);

grph.beginPath()
  .moveTo(150, 300)
  .lineTo(100, 400)
  .lineTo(200, 400)
  .closePath()
  .fill()
  .strokePath();

描画をリセットするにはclearメソッドを使用します。このとき、線と塗りのスタイルも初期化されます。

また、線と塗りのスタイルのデフォルト値を変更するにはsetDefaultStylesメソッドを使用します。
パラメータはコンストラクタと同じです。

基本的な使い方 その2

四角形、三角形、楕円のように、よく使う図形については、もっと簡単に描画するメソッドが用意されています。
始めに代表例として、四角形のメソッドを取り上げます。

const WIDTH = 200;
const HEIGHT = 100;
const LINE_WIDTH = 5; // ラインの太さ
let posX = 50;
let posY = 50;

const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});
grph.fillRect(posX, posY, WIDTH, HEIGHT).strokeRect(posX, posY, WIDTH, HEIGHT);

posY += 150;
const rect = new Phaser.Geom.Rectangle(posX, posY, WIDTH, HEIGHT);
grph.fillRectShape(rect).strokeRectShape(rect);

fillRectメソッドとstrokeRectメソッドは、図形の座標・サイズの値を直接パラメータとして渡し、四角形の線と塗りを描画するメソッドです。

一方で似た機能に、fillRectShapeメソッドとstrokeRectShapeメソッドがあります。
違いは、パラメータにRectangleのインスタンスのみを渡すようになっています。
このクラスは、名前空間Phaser.Geom配下にあるクラスで、単純に図形の情報だけを保持するクラスです。

Phaser API Documentation | Namespace: Geom

このように図形クラスが用意されている図形については、
fill***stroke***メソッドとfill***Shapestroke***Shapeメソッドは対になって用意されています。
Graphicsクラスのメ各メソッドと図形クラスの対応関係を下の表にまとめています。

図形 パラメータに
図形の属性を直接渡す
Graphicsクラスのメソッド名
パラメータに
図形クラスのインスタンスを渡す
Graphicsクラスのメソッド名
対応する
図形クラス名
四角形 fillRect / strokeRect fillRectShape / strokeRectShape Rectangle
三角形 fillTriangle / strokeTriangle fillTriangleShape / strokeTriangleShape Triangle
正円 fillCircle / strokeCircle fillCircleShape / strokeCircleShape Circle
楕円 fillEllipse / strokeEllipse fillEllipseShape / strokeEllipseShape Ellipse
正方形(点) fillPoint / - fillPointShape / - Point
- / lineBetween (※1) - / strokeLineShape Line
多角形 fillPoints / strokePoints - / - Polygon (※2)
角の丸い四角形 fillRoundedRect / strokeRoundedRect - / - - (※3)

※1: 名前がstroke***ではないですが、このメソッドが相当します。
※2: インスタンスをそのまま渡すのではなく、pointsプロパティを渡します(この後の節で紹介)。
※3: 対応するクラスはありません。この後の節で自作例を紹介します。

Graphicsクラスの各描画メソッドのパラメータは以下のようになります。(オプション引数については一部省略。)
対応する図形クラスがある場合は、そのコンストラクタについても基本的には同様です。

図形 パラメータ
四角形 左上のx,y座標、幅、高さ
三角形 最初の点のx,y座標、2番目の点のx,y座標、3番目の点のx,y座標
正円 正円の中心のx,y座標、半径
楕円 楕円の中心のx,y座標、幅、高さ
正方形(点) 点のx,y座標
始点のx,y座標、終点のx,y座標
多角形 点の座標をx,y交互に連続して並べた配列。またはGeom.Pointクラスの配列。
角の丸い四角形 左上のx,y座標、幅、高さ、丸める角の半径

この記事ではすべてのメソッドのサンプルコードは掲載しませんが、上記の表のような関係性を把握しておくとコードを書くときに楽かと思います。

多角形

多角形の描画の方法について見ていきます。

ソースコードの例のように、Polygonクラス生成する際、コンストラクタ引数に描画する図形の各頂点となる点(x,y)を列挙して配列で渡します。
その後、宣言した変数に対しpointsメソッドを使用し、描画するメソッドに渡します。
(これ以外にも文字列で渡す方法などもありますが、詳細はAPIドキュメントを参照のこと)

ここではついでにgenerateTextureメソッドについても紹介しておきます。
描画した図形をテクスチャとして保存し再利用できます。

const LINE_WIDTH = 5; // ラインの太さ

const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});

// 多角形を描画する
const poly = new Phaser.Geom.Polygon([100, 0, 200, 100, 150, 100, 150, 190, 50, 190, 50, 100, 0, 100, 100, 0]);
grph.fillPoints(poly.points).strokePoints(poly.points);

// 描画した図形からテクスチャを生成する
grph.generateTexture('txtr', 200, 200);

// テクスチャを描画する
const img = this.add.image(210, 100, 'txtr').setOrigin(0);

円弧

arcはコンパスで作図したような円弧を描くメソッドです。
パスは自動で閉じないので、複数続けて描画する場合は必ずclosePathメソッドを呼びます。

sliceはピザを切ったときのように必ず円の中心点を通ってパスを描きます。
こちらは自動でパスが閉じます。

const LINE_WIDTH = 5; // ラインの太さ
const RADIUS = 80;    // 角の円の半径
let posX = 150;
let posY = 120;

const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});

// 切られた正円(フタ状)
grph.beginPath()
  .arc(posX, posY, RADIUS, Math.PI / 4 * 5, Math.PI / 4 * 7)
  .closePath().fill().stroke();

// 切られた正円(ポット状)
grph.beginPath()
  .arc(posX, posY + 20, RADIUS, Math.PI / 4 * 5, Math.PI / 4 * 7, true)
  .closePath().fill().stroke();

// 線と塗りを変更
grph.lineStyle(LINE_WIDTH + 5, 0x603030, 1).fillStyle(0xFFFF90, 1);

// 切り取られたピザ
posY += 200;
grph.slice(posX, posY, RADIUS, Math.PI / 6, Math.PI / 6 * 11)
  .fill().stroke();
// ピザひとかけら
grph.slice(posX + 30, posY, RADIUS, Math.PI / 6, Math.PI / 6 * 11, true)
  .fill().stroke();

パラメータについては、中心点のx,y座標・半径の順番で、続いて第4,5引数が「開始・終了角度 (ラジアン単位)」、第6引数は「回転方向を反転するか否か」になります。

キャンバス操作

以下のメソッドを使うと、描画する図形の表示位置、角度、スケールを変更できます。
各対象メソッドを呼び出した後に描画される図形が対象で、既存の図形には影響しません。
これにより、図形を描画するメソッドの引数を変えずに、図形の表示のみを変形できます。
楕円を書くfillEllipseメソッドの引数がすべて同じなことに注目してください。

メソッド 機能
rotateCanvas 以降に描画する図形を回転して表示する。
scaleCanvas 以降に描画する図形のスケールを変更して表示する。
translateCanvas 以降に描画する図形の位置を移動して表示する。
const WIDTH = 150;
const HEIGHT = 50;
const LINE_WIDTH = 5; // ラインの太さ

const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});

grph.setPosition(150, 150);
const drawFunc = () => {
  grph.fillEllipse(0, 0, WIDTH, HEIGHT).strokeEllipse(0, 0, WIDTH, HEIGHT);
};

drawFunc();
grph.translateCanvas(200, 0); // 以降、右に250pxシフト
drawFunc();
grph.scaleCanvas(0.5, 0.5); // 以降、スケールを0.5倍に
drawFunc();
grph.rotateCanvas(Math.PI / 3); // 以降、時計回り60度
drawFunc();

グラデーション

lineGradientStylefillGradientStyleメソッドはそれぞれ線と塗りのスタイルにグラデーションを指定できる機能です。
パラメータの色指定の部分は、左上、右上、左下、右下の順番で指定します。
ただし、すべての図形に対しきれいにグラデーションが表示されるわけではなく、API ドキュメントによると線については「単一の線でのみ」、塗については「長方形と三角形でのみ」使用するのが最適とのことです。

const LINE_WIDTH = 20; // ラインの太さ

const grph = this.add.graphics();
grph.lineGradientStyle(LINE_WIDTH, 0xFFFFFF, 0xFFFFFF, 0x00FF00, 0x00FF00);
// 単一の線でのみ使用するのが最適です
grph.lineBetween(50, 40, 250, 40);

grph.fillGradientStyle(0xFF0000, 0x0000FF, 0xFFFF00, 0x00FFFF);
// 長方形と三角形でのみ使用するのが最適です    
grph.fillTriangle(150, 100, 250, 200, 50, 200);
grph.fillRect(50, 250, 200, 150,);

「角の丸い四角形」のクラス・メソッドの自作

上で紹介した描画メソッドと図形クラスの対応表ですが、「角の丸い四角形」だけはクラスが用意されておらず、描画する際にはGraphicsクラスのfillRoundedRect,strokeRoundedRectメソッドに個別でパラメータを渡すしかありません。

しかし、他の図形と同じく専用の図形クラスと描画メソッドがあった方が都合がよさそうです。

ここではその図形クラスの自作例を紹介します。
また、そのクラスのインスタンスをパラメータとして渡て描画できるGraphicsクラスのメソッドについても作成してみます。

「角の丸い四角形」図形クラス

「角の丸い四角形」と四角形の違いは「丸の半径」の有無だけですので、四角形のPhaser.Geom.Rectangleクラスから継承してRoundedRectangleクラスを新たに定義します。

//Rectangleクラスから継承し、新たにRoundedRectangleクラスを定義
class RoundedRectangle extends Phaser.Geom.Rectangle {
  radius: number;
  constructor(x?: number, y?: number, width?: number, height?: number, radius?: number) {
    super(x, y, width, height);
    this.radius = radius;
    return this;
  };
}

Graphicsクラスの「角の丸い四角形」描画メソッド

Phaser.GameObjects.Graphicsクラスに対し拡張メソッドを宣言・定義します。
パラメータですが、上で作成したRoundedRectangleクラスと合わせて、Rectangleと「半径の値」を渡しても使用できるようにしてあります。

// 拡張メソッド 宣言
declare module "phaser" {
  namespace GameObjects {
    interface Graphics {
      fillRoundedRectShape(rect: Phaser.Geom.Rectangle | RoundedRectangle, radius?: number): this;
      strokeRoundedRectShape(rect: Phaser.Geom.Rectangle | RoundedRectangle, radius?: number): this;
    }
  }
}

// 拡張メソッド 定義
Object.defineProperty(Phaser.GameObjects.Graphics.prototype, 'fillRoundedRectShape', {
  value: function (rect: Phaser.Geom.Rectangle | RoundedRectangle, radius?: number) {
    this.fillRoundedRect(rect.x, rect.y, rect.width, rect.height,
      rect instanceof RoundedRectangle ? rect.radius : radius);
    return this;
  }
});
Object.defineProperty(Phaser.GameObjects.Graphics.prototype, 'strokeRoundedRectShape', {
  value: function (rect: Phaser.Geom.Rectangle | RoundedRectangle, radius?: number) {
    this.strokeRoundedRect(rect.x, rect.y, rect.width, rect.height,
      rect instanceof RoundedRectangle ? rect.radius : radius);
    return this;
  }
});

最後に、上で定義したクラスなどの使用例です。

const WIDTH = 200;
const HEIGHT = 100;
const LINE_WIDTH = 5; // ラインの太さ
const RADIUS = 40;    // 角の円の半径
let posX = 50;
let posY = 50;

const grph = this.add.graphics({
  lineStyle: { width: LINE_WIDTH, color: 0xE00000, alpha: 1 },
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});

const rect = new Phaser.Geom.Rectangle(posX, posY, WIDTH, HEIGHT);
grph.fillRoundedRectShape(rect, RADIUS).strokeRoundedRectShape(rect, RADIUS);

posY += 150;
const rndRect = new RoundedRectangle(posX, posY, WIDTH, HEIGHT, RADIUS);
grph.fillRoundedRectShape(rndRect).strokeRoundedRectShape(rndRect);

Graphicsクラスのマウス操作

Graphicsクラスの描画範囲をマウスで操作できるようにする場合、
単にsetInteractiveメソッドを呼んだだけでは有効になりません。
以下の例のように引数に図形クラスを渡し、ポインタの検知範囲を明示します。
第二引数のContainsは境界内にポインタがあるかどうかをチェックするための静的メソッドです。
Rectangle以外の場合も各図形クラスに同名のContainsメソッドがあるのでそれを指定します。

const X = 50;
const Y = 100;
const WIDTH = 100;
const HEIGHT = 50;
const grph = this.add.graphics({
  fillStyle: { color: 0xFFFFFF, alpha: 1 }
});
const caption = this.add.text(X + WIDTH + 10, Y,
  "左の長方形にマウスポインタをあててください",
  { padding: { x: 5, y: 10 } });
const rect = new Phaser.Geom.Rectangle(X, Y, WIDTH, HEIGHT);
grph.fillRectShape(rect);

grph.on(Phaser.Input.Events.POINTER_OVER, () => {
  caption.setTint(0xFF6060);
}).on(Phaser.Input.Events.POINTER_OUT, () => {
  caption.setTint(0xFFFFFF);
}
).on(Phaser.Input.Events.POINTER_DOWN, () => {
  caption.setText('クリックされた!');
})

// 検知範囲を指定してマウス操作を有効にする
grph.setInteractive(rect, Phaser.Geom.Rectangle.Contains);

Discussion