Phaser 3 : リボントレイルを作る③ tween.updateToを使ってメッシュをアニメーションさせる

前回に引き続きリボントレイルエフェクトを作っていきます。 f:id:Phaser_3:20181218154547p:plain

gpnotes.hatenablog.jp

パスの制御点を渡し変形できるようにする

パスの制御点を操作してリアルタイムにパスを変形できるようにしたいと思います。
公式のサンプル内にドラッグによる変形のサンプルが複数あります。
パスを変形するためにははじめにVector2オブジェクトを渡す必要があるようで、作成後のパスから制御点を取得することはできないようです。
始点、終点、二つの中間制御点を設定してcubicBazierを作成します。

//各ポイントの設定
public tail: Phaser.Math.Vector2;
public head: Phaser.Math.Vector2;
public controlPoint1: Phaser.Math.Vector2;
public controlPoint2: Phaser.Math.Vector2;

public CreateCurve() {
    //制御点を作成
    this.tail = new Phaser.Math.Vector2(100, 100);
    this.head = new Phaser.Math.Vector2(500, 500);
    this.controlPoint1 = new Phaser.Math.Vector2(Phaser.Math.Between(this.tail.x, this.head.x), Phaser.Math.Between(this.tail.y, this.head.y));
    this.controlPoint2 = new Phaser.Math.Vector2(Phaser.Math.Between(this.controlPoint1.x, this.head.x), Phaser.Math.Between(this.controlPoint1.x, this.head.y));

    this.curve = new Phaser.Curves.CubicBezier(this.tail, this.controlPoint1, this.controlPoint2, this.head);
    this.graphics = this.scene.add.graphics();

}

制御点にtweenを設定する

次は各制御点にtweenを設定します。
ひとまずheadをめがけて各制御点を連動させてtweenさせるようにしてみました。

//ポイント用のtween
public tweens: Phaser.Tweens.Tween[]=[];
public t1: Phaser.Tweens.Tween
public t2: Phaser.Tweens.Tween
public t3: Phaser.Tweens.Tween
public t4: Phaser.Tweens.Tween
~~~
//各制御点にtweenを作成する
this.t1 = this.scene.tweens.add({
    targets: this.tail,
    x: this.controlPoint1.x,
    y: this.controlPoint1.y,
    duration: 5000,
    repeat: -1
})

this.t2 = this.scene.tweens.add({
    targets: this.controlPoint1,
    x: this.controlPoint2.x,
    y: this.controlPoint2.y,
    duration: 5000,
    repeat: -1
})

this.t3 = this.scene.tweens.add({
    targets: this.controlPoint2,
    x: this.head.x,
    y: this.head.y,
    duration: 5000,
    repeat: -1
})

updateにメッシュ変形用の処理を記述します。

update() {
    //制御点のtweenをアップデートする

    this.curvePoints = this.curve.getPoints(100);

    this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points));
}

実行結果がこちらです。 f:id:Phaser_3:20181218160537g:plain アニメーションの質はともかく、パスのtweenに沿ってメッシュが変形しているのがわかると思います。

tween.updateToを使う

tweenは作成時の値を参照するので、「制御点が次の制御点をめがけてtweenする」といった処理を書いた場合、制御点のtween中の座標めがけてtweenしてくれる、といった挙動にはなりません。
tween.updateToを用いれば、tweenの目標値をupdateで更新することができます。

update() {
    //制御点のtweenをアップデートする
    this.t1.updateTo("x", this.controlPoint1.x, true)
    this.t1.updateTo("y", this.controlPoint1.y, true)

    this.t2.updateTo("x", this.controlPoint2.x, true)
    this.t2.updateTo("y", this.controlPoint2.y, true)

    this.t3.updateTo("x", this.head.x, true)
    this.t3.updateTo("y", this.head.y, true)

    this.t4.updateTo("x", this.scene.input.x, true)
    this.t4.updateTo("y", this.scene.input.y, true)

    this.curvePoints = this.curve.getPoints(100);

    this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points));
}

f:id:Phaser_3:20181218161204g:plain マウスの移動に追従してメッシュが軌跡を描いているのがわかると思います。
しかしなめらかなトレイルの描き方の実装にはもう少し調査が必要そうです。
今回のソース全文はこちらです。

/// <reference path="../app.ts" />

namespace MyGame {

    interface XY {
        x: number;
        y: number;
    }

    export class Trail extends Phaser.GameObjects.Container {

        //グラフィックオブジェクトを用意
        public graphics: Phaser.GameObjects.Graphics;
        public mesh: Phaser.GameObjects.Mesh;

        public curve: Phaser.Curves.CubicBezier;

        public curvePoints: Phaser.Math.Vector2[] = [];

        public drawDebug: boolean = false;

        public points: number = 100;

//各ポイントの設定
public tail: Phaser.Math.Vector2;
public head: Phaser.Math.Vector2;
public controlPoint1: Phaser.Math.Vector2;
public controlPoint2: Phaser.Math.Vector2;

        //ポイント用のtween
        public tweens: Phaser.Tweens.Tween[]=[];
        public t1: Phaser.Tweens.Tween
        public t2: Phaser.Tweens.Tween
        public t3: Phaser.Tweens.Tween
        public t4: Phaser.Tweens.Tween

        constructor(scene: Phaser.Scene, x?: number, y?: number, children?: Phaser.GameObjects.GameObject[]) {
            super(scene, x, y, children);

        }

        Init() {

            this.CreateCurve();

            this.curvePoints = this.curve.getPoints(this.points);

            //頂点配列の作成
            let array = this.CreateVerts(5, this.points);

            //uv配列の作成
            let uv = this.CreateUVs(array.length / 12);

            this.mesh = this.scene.make.mesh({
                key: 'block',
                x: 0,
                y: 0,
                vertices: array,
                uv: uv,
            });


        }

        public CreateVerts(width: number, points: number): number[] {
            let arr = [];

            let pointArray: Phaser.Geom.Point[][] = new Array();

            for (let i = 0; i < points; i++) {
                let mul = width
                let vecA = new Phaser.Math.Vector2(this.curvePoints[i + 1].x - this.curvePoints[i].x, this.curvePoints[i + 1].y - this.curvePoints[i].y).normalize();

                var point1 = new Phaser.Geom.Point(-vecA.y * mul + this.curvePoints[i + 1].x, vecA.x * mul + this.curvePoints[i + 1].y);
                var point2 = new Phaser.Geom.Point(vecA.y * mul + this.curvePoints[i + 1].x, -vecA.x * mul + this.curvePoints[i + 1].y);

                pointArray.push([point1, point2]);

            }

            for (let i = 0; i < pointArray.length - 1; i++) {


                //左上
                arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y);

                //左下
                arr.push(pointArray[i][0].x, pointArray[i][0].y);

                //右下
                arr.push(pointArray[i][1].x, pointArray[i][1].y);

                //左上
                arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y);

                //右下
                arr.push(pointArray[i][1].x, pointArray[i][1].y);

                //右上
                arr.push(pointArray[i + 1][1].x, pointArray[i + 1][1].y);

            }


            return arr;
        }

        public UpdateVerts(width: number, points: number): number[] {
            let arr = [];

            let pointArray: Phaser.Geom.Point[][] = new Array();

            for (let i = 0; i < points; i++) {
                let mul = width
                let vecA = new Phaser.Math.Vector2(this.curvePoints[i + 1].x - this.curvePoints[i].x, this.curvePoints[i + 1].y - this.curvePoints[i].y).normalize();

                var point1 = new Phaser.Geom.Point(-vecA.y * mul + this.curvePoints[i + 1].x, vecA.x * mul + this.curvePoints[i + 1].y);
                var point2 = new Phaser.Geom.Point(vecA.y * mul + this.curvePoints[i + 1].x, -vecA.x * mul + this.curvePoints[i + 1].y);


                pointArray.push([point1, point2]);

            }

            for (let i = 0; i < pointArray.length - 1; i++) {


                //左上
                arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y);

                //左下
                arr.push(pointArray[i][0].x, pointArray[i][0].y);

                //右下
                arr.push(pointArray[i][1].x, pointArray[i][1].y);

                //左上
                arr.push(pointArray[i + 1][0].x, pointArray[i + 1][0].y);

                //右下
                arr.push(pointArray[i][1].x, pointArray[i][1].y);

                //右上
                arr.push(pointArray[i + 1][1].x, pointArray[i + 1][1].y);

            }

            
            return arr;
        }

        public CreateUVs(points: number): number[] {
            let arr = [];
            let uvincr = 1 / points;

            for (let i = 0; i < points; i++) {
                //左上
                arr.push(0);
                arr.push(uvincr * i);

                //左下
                arr.push(0);
                arr.push(uvincr * (i + 1));

                //右下
                arr.push(1);
                arr.push(uvincr * (i + 1));

                //左上
                arr.push(0);
                arr.push(uvincr * i);

                //右下
                arr.push(1);
                arr.push(uvincr * (i + 1));

                //右上
                arr.push(1);
                arr.push(uvincr * i);
            }


            return arr;
        }

        public CreateCurve() {
            //制御点を作成
            this.tail = new Phaser.Math.Vector2(100, 100);
            this.head = new Phaser.Math.Vector2(500, 500);
            this.controlPoint1 = new Phaser.Math.Vector2(Phaser.Math.Between(this.tail.x, this.head.x), Phaser.Math.Between(this.tail.y, this.head.y));
            this.controlPoint2 = new Phaser.Math.Vector2(Phaser.Math.Between(this.controlPoint1.x, this.head.x), Phaser.Math.Between(this.controlPoint1.x, this.head.y));

            this.curve = new Phaser.Curves.CubicBezier(this.tail, this.controlPoint1, this.controlPoint2, this.head);
            this.graphics = this.scene.add.graphics();



            //各制御点にtweenを作成する
            this.t1 = this.scene.tweens.add({
                targets: this.tail,
                x: this.controlPoint1.x,
                y: this.controlPoint1.y,
                duration: 5000,
                repeat: -1
            })

            this.t2 = this.scene.tweens.add({
                targets: this.controlPoint1,
                x: this.controlPoint2.x,
                y: this.controlPoint2.y,
                duration: 5000,
                repeat: -1
            })

            this.t3 = this.scene.tweens.add({
                targets: this.controlPoint2,
                x: this.head.x,
                y: this.head.y,
                duration: 5000,
                repeat: -1
            })

            this.t4 = this.scene.tweens.add({
                targets: this.head,
                x: this.scene.input.x,
                y: this.scene.input.y,
                duration: 5000,
                repeat: -1
            })



        }

        update() {
            //制御点のtweenをアップデートする
            this.t1.updateTo("x", this.controlPoint1.x, true)
            this.t1.updateTo("y", this.controlPoint1.y, true)

            this.t2.updateTo("x", this.controlPoint2.x, true)
            this.t2.updateTo("y", this.controlPoint2.y, true)

            this.t3.updateTo("x", this.head.x, true)
            this.t3.updateTo("y", this.head.y, true)

            this.t4.updateTo("x", this.scene.input.x, true)
            this.t4.updateTo("y", this.scene.input.y, true)

            this.curvePoints = this.curve.getPoints(100);

            this.mesh.vertices = Float32Array.from(this.UpdateVerts(5, this.points));
        }

    }

    export class MyScene1 extends Phaser.Scene {

        //グラフィックオブジェクトを用意
        public trail: Trail;

        constructor() {
            super({ key: 'MyScene1', active: false });
        }

        preload() {
            this.load.image("block","assets/images/block.png")
        }

        create() {
            this.trail = new Trail(this);
            this.trail.Init();
            this.cameras.main.setBackgroundColor(0xffffff);
            this.add.existing(this.trail);
            this.add.group(this.trail, { runChildUpdate: true })

        }        
    }
}