Phaser3 ゲームオブジェクトの拡張② : maskを使う
前回に引き続きオブジェクト拡張を使いながらアニメーションを作っていきます。
maskを使う
maskを使って黒電話のダイアル部分を作ってみます。
まずはダイアルの下地のグラフィックスを用意します。
create(){ ~~ //グラフィックスオブジェクトを追加 let dialGraphics = this.add.graphics(); dialGraphics.fillStyle(0xf8a8a8a); //ベースの円を作成 dialGraphics.fillStyle(0x2a2a2a); dialGraphics.fillCircle(0, 0, 110); dialGraphics.fillStyle(0x8a8a8a); dialGraphics.fillCircle(0, 0, 30); ~~ }
次にダイアルの穴の部分に当たる位置にグラフィックスを追加します。
先に作ったテキストオブジェクトに重なるようにサークルを配置しました。
//ダイアルの穴部分 let g = this.add.graphics({}); for (let i = 0; i < 10; i++) { g.fillStyle(0xff00ff); g.fillCircle(texts[i].x + this.baseContainer.x, texts[i].y + this.baseContainer.y, 15); } let t = g.generateTexture("dialtemp"); g.destroy();
テクスチャ化したダイアル用グラフィックを用い、imageを拡張した自作のダイアル用クラスを作成します。
ダイアル用クラスの中にはダイアルを回すアニメーション用の関数Dialを用意しました。
export class DialMask extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) { super(scene, x, y, texture, frame); } //ダイヤルを回すアニメーション public Dial(n:number) { this.scene.tweens.add({ targets: this, angle: n, duration: 100, }) } //ダイヤルの角度が0でなければ0までもどす update() { if (!this.scene.tweens.isTweening(this)) { if (this.angle != 0) { this.angle -= 10; } } } } ~~ this.dial = new DialMask(this,this.sys.canvas.width/2, this.sys.canvas.height/2, "dialtemp"); ~~
以上でダイアル部分の穴ができたので、ダイアルグラフィックのマスクとして使用します。 image.maskでマスクを作ります。
dialGraphics.mask = new Phaser.Display.Masks.BitmapMask(this, this.dial);
上記の実行結果がこちらです。 ダイアル部分の穴にあたる部分に円が表示されており、ダイアルの数字が隠されてしまっています。
mask.invertAlphaで画像を切り抜く
maskはmaskとして扱われた画像のアルファ値に従って元画像を描画する機能なので、上の処理だとちょうどダイアル部分に円を描く処理になってしまっていました。
phaser3で追加された機能のmask.invertAlphaを使えばアルファ値を反転させダイアル部分に穴をあけることができます。
dialGraphics.mask.invertAlpha = true;
実行結果がこちらです。
狙い通りにダイアルに穴をあけられていることがわかります。
Phaser2ではグラフィックスオブジェクトに穴をあけたいと思ったらシェーダーを書くなどして対応しなければならなかったので、この機能は非常にありがたいです。
.onでテキストにクリック処理を追加する
ダイアルの数字をクリックすることでダイアルをアニメーションさせます。
DialMaskクラスにはアニメーション用の関数Dialを実装してあるので、数字クリックでDialを実行させます。
phaserのGameObjectにはonなどのイベントリスナが用意されているので、クリック時のイベントpointerdownにDialを登録します。
インプット処理を追加するオブジェクトはsetinteractive()を実行しないとインプット処理が有効にならないので、忘れずに実行しましょう。
ダイアルの数字に応じて回転角度を変化させています。
for (let i = 10; i > 0; i--) { let t = this.add.text(0, 0, i.toString()); texts.push(t); t.setColor("#ffffff") t.setOrigin(0.5); //テキストがクリックされた際にダイヤル関数を実行 t.setInteractive(); t.on('pointerdown', function () { this.dial.Dial((10 - texts.indexOf(t)) * 10 + 90); }, this); }
以上の実行結果がこちらです。
mask部分がアニメーションしているのがわかると思います。
今回のソースはこちらです。
/// <reference path="../app.ts" /> /// <reference path="./Astar/AStar.ts" /> namespace MyGame { export class BaseContainer extends Phaser.GameObjects.Container { constructor(scene: Phaser.Scene, x?: number, y?: number, children?: Phaser.GameObjects.GameObject[]) { super(scene, x, y, children); } } export class DialMask extends Phaser.GameObjects.Image { constructor(scene: Phaser.Scene, x: number, y: number, texture: string, frame?: number | string) { super(scene, x, y, texture, frame); } //ダイヤルを回すアニメーション public Dial(n:number) { this.scene.tweens.add({ targets: this, angle: n, duration: 100, }) } //ダイヤルの角度が0でなければ0までもどす update() { if (!this.scene.tweens.isTweening(this)) { if (this.angle != 0) { this.angle -= 10; } } } } export class MyScene1 extends Phaser.Scene { //グラフィックオブジェクトを用意 public graphics: Phaser.GameObjects.Graphics; public baseContainer: BaseContainer; //ダイヤル用のマスク public dial: DialMask; constructor() { super({ key: 'MyScene1', active: false }); } preload() { this.load.image("block", "assets/images/block1.png") //背景を白に this.cameras.main.setBackgroundColor("#ffffff"); } create() { this.baseContainer = new BaseContainer(this); //グラフィックスオブジェクトを追加 this.graphics = this.add.graphics(); //コンテナを宣言 this.baseContainer.add(this.graphics); //ベースの円を作成 this.graphics.fillStyle(0x1a1a1a); this.graphics.fillCircle(0, 0, 110); //※拡張で作成したオブジェクトはexistingでシーンへの追加を忘れないようにする this.add.existing(this.baseContainer); //文字の作成 let texts: Phaser.GameObjects.Text[] = []; for (let i = 10; i > 0; i--) { let t = this.add.text(0, 0, i.toString()); texts.push(t); t.setColor("#ffffff") t.setOrigin(0.5); //テキストがクリックされた際にダイヤル関数を実行 t.setInteractive(); t.on('pointerdown', function () { this.dial.Dial((10 - texts.indexOf(t)) * 10 + 90); }, this); } texts[0].text = "0"; let circle = new Phaser.Geom.Circle(0, 0, 80); //円周上に配置 Phaser.Actions.PlaceOnCircle(texts, circle, Phaser.Math.DEG_TO_RAD * 90, Phaser.Math.DEG_TO_RAD * 320); this.baseContainer.add(texts); this.baseContainer.x = this.sys.canvas.width / 2; this.baseContainer.y = this.sys.canvas.height / 2; this.add.group(this.baseContainer, { runChildUpdate: true }); //ダイアルの穴部分 let g = this.add.graphics({}); for (let i = 0; i < 10; i++) { g.fillStyle(0xff00ff); g.fillCircle(texts[i].x + this.baseContainer.x, texts[i].y + this.baseContainer.y, 15); } let t = g.generateTexture("dialtemp"); g.destroy(); this.dial = new DialMask(this,this.sys.canvas.width/2, this.sys.canvas.height/2, "dialtemp"); //グラフィックスオブジェクトを追加 let dialGraphics = this.add.graphics(); dialGraphics.fillStyle(0xf8a8a8a); //ベースの円を作成 dialGraphics.fillStyle(0x2a2a2a); dialGraphics.fillCircle(0, 0, 110); dialGraphics.fillStyle(0x8a8a8a); dialGraphics.fillCircle(0, 0, 30); dialGraphics.mask = new Phaser.Display.Masks.BitmapMask(this, this.dial); dialGraphics.mask.invertAlpha = true; dialGraphics.x = this.sys.canvas.width / 2 dialGraphics.y = this.sys.canvas.height / 2 this.add.group(this.dial, {runChildUpdate:true}) } update() { } } }