//import Phaser from "phaser";

const fontColor1 = '#FFFFFF';
const fontColor2 = '#FFFF90';

const textStyle: Phaser.Types.GameObjects.Text.TextStyle = {
  fontSize: '16px',
  color: fontColor1,
  padding: { x: 10, y: 5 }
};

//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;
  };
}

// 拡張メソッド 宣言
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;
  }
});

class Phaser3Graphics extends Phaser.Scene {
  constructor() {
    super({ key: 'phaser3graphics' });
  }

  //プロパティ
  private btnText: Phaser.GameObjects.Text[];
  private btnReset: Phaser.GameObjects.Text;

  create() {
    this.btnText = new Array<Phaser.GameObjects.Text>();
    let posY = 0;
    const methodlist = [
      {
        caption: "基本的な使い方 その1",
        method: () => this.method01_Basic1()
      },
      {
        caption: "基本的な使い方 その2 四角形",
        method: () => this.method02_Rect()
      },
      {
        caption: "基本的な使い方 その2 多角形",
        method: () => this.method03_Polygon()
      },
      {
        caption: "基本的な使い方 その2 円弧",
        method: () => this.method04_ArcAndSlice()
      },
      {
        caption: "基本的な使い方 その2 キャンバス操作",
        method: () => this.method05_changeCanvas()
      },
      {
        caption: "基本的な使い方 その2 グラデーション",
        method: () => this.method06_Gradient()
      },
      {
        caption: "「角の丸い四角形」のGeomクラスと、描画メソッドを自作する",
        method: () => this.method07_RoundedRectMethod()
      },
      {
        caption: "Graphicsクラスとマウス操作",
        method: () => this.method08_ClickableRectangle()
      }
    ];

    methodlist.forEach(e => {
      posY += 50;
      const btn = this.add.text(20, posY, e.caption, textStyle).once('pointerdown', () => {
        e.method();
        this.removeBtn();
      });
      btn.on('pointerover', () => {
        btn.setStyle({ color: fontColor2 })
      }).on('pointerout', () => {
        btn.setStyle({ color: fontColor1 })
      }).setInteractive();
      this.btnText.push(btn);
    });

    this.btnReset = this.add.text(10, 10, "戻る", textStyle)
      .once('pointerdown', () => this.scene.restart())
      .setInteractive().setVisible(false);
  }

  removeBtn() {
    this.btnText.forEach((e: Phaser.GameObjects.Text) => {
      e.destroy();
    });
    this.btnText = [];

    this.btnReset.setVisible(true);
  }

  // 基本的な使い方 その1
  method01_Basic1() {
    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();
  }

  // 基本的な使い方 その2 四角形
  method02_Rect() {
    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);
  }

  // 基本的な使い方 その2 多角形
  method03_Polygon() {
    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);
  }

  // 基本的な使い方 その2 円弧
  method04_ArcAndSlice() {
    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();
  }

  // 基本的な使い方 その2 キャンバス操作
  method05_changeCanvas() {
    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();
  }

  // 基本的な使い方 その2 グラデーション
  method06_Gradient() {
    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,);
  }

  // 「角の丸い四角形」の図形クラスと、描画メソッドを自作する
  method07_RoundedRectMethod() {
    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クラスとマウス操作
  method08_ClickableRectangle() {
    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);
  }
}

const config: Phaser.Types.Core.GameConfig = {
  type: Phaser.AUTO,
  width: 640,
  height: 480,
  backgroundColor: '#000060',
  pixelArt: false,
  scale: {
    mode: Phaser.Scale.FIT,
    autoCenter: Phaser.Scale.CENTER_HORIZONTALLY,
  },
  scene: [Phaser3Graphics]
};

new Phaser.Game(config);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/phaser/3.80.1/phaser.min.js