スプライトの形に添ったパーティクルエミッタを作る②:random出力

前回の処理では辺からの出力しかできなかったので、今回はスプライトの範囲からパーティクルを出力する方法を探ってみます。

getRandomPointを持つクラスを作る

こちらのサンプルを見るとgetRandomPointが実装されているオブジェクトならなんでもエミッタゾーンとして設定できるようなので、getRandomPointを持つクラスを作ります。

    export class EmitterPoints {

        public points: Phaser.Math.Vector2[] = [];
        public pointsCopy: Phaser.Math.Vector2[] = [];
        public vec: Phaser.Geom.Point;

        constructor(p:Phaser.Math.Vector2[]) {
            this.points = p;
            for (let i = 0; i < this.points.length; i++) {
                this.pointsCopy.push( new Phaser.Math.Vector2(0,0));

            }
            this.vec= new Phaser.Geom.Point()
        }

        public getRandomPoint() {
            let p = this.pointsCopy[Math.floor(Phaser.Math.Between(0, this.points.length - 1))]
            this.vec.setTo(p.x,p.y)
            return this.vec;
        }
    }

しかし、こちらを実行してみてもうまく出力されませんでした。
サンプルと同じ形の実装のためにGetPixelを改造します。

public GetPixel(x: number, y: number, rotate: number, key: string) {
            
    if (this.vPoints[key] !== undefined) {
        let source = this.textures.get(key).getSourceImage()
        let points = this.vPoints[key]
        for (let i = 0; i < points.points.length; i++) {
            points.pointsCopy[i].set(((points.points[i].x - source.width / 2) * Math.cos(rotate) - (points.points[i].y - source.height / 2) * Math.sin(rotate)) ,
                ((points.points[i].y - source.height / 2) * Math.cos(rotate) + (points.points[i].x - source.width / 2) * Math.sin(rotate)) )

        }

        let r ={
            getRandomPoint: function (vec) {
                let p = points.getRandomPoint()
                vec.x = p.x ;
                vec.y = p.y ;
                return vec;
            }
        };

        this.emitter.setEmitZone({
            source: r,
            type: 'random',
            quantity: 50
        });

        this.emitter.explode(3000,x,y)

    } else {
        let vp: Phaser.Math.Vector2[] =[];

        let source = this.textures.get(key).getSourceImage()
        this.graphicsSub.clear();

        for (let i = 0; i < source.height; i++) {
            for (let j = 0; j < source.width; j++) {
                let pixel = this.textures.getPixel(j, i, key, 0)
                if (pixel.alpha > 0) {
                    vp.push(new Phaser.Math.Vector2( j,i))
                    let color = new Phaser.Display.Color();
                    color.setTo(0, 0, 0)

                    let point = this.point.clone();
                    point.set(x + ((j - source.width / 2) * Math.cos(rotate) - (i - source.height / 2) * Math.sin(rotate)), y + ((i - source.height / 2) * Math.cos(rotate) + (j - source.width / 2) * Math.sin(rotate)), 1.5)
                    let randompoint = this.point.clone()
                    randompoint.x = Phaser.Math.Between(x - source.width / 2, x + source.width / 2)
                    randompoint.y = Phaser.Math.Between(y - source.height / 2, y + source.height / 2)
                    this.tweens.add({
                        targets: point,
                        x: Phaser.Math.Between(0, this.sys.canvas.width),
                        y: Phaser.Math.Between(0, this.sys.canvas.height),
                        z: 0,
                        ease: 'Power1',
                        duration: 150 * Phaser.Math.Distance.Between(point.x, point.y, randompoint.x, randompoint.y),
                        onComplete: () => { this.geomPoints.splice(this.geomPoints.indexOf(point), 1) },
                    });

                    this.geomPoints.push(point)

                }
            }
        }
        this.vPoints[key]=new EmitterPoints(vp)
    }

一度作成したエミッタの座標を配列に入れておくようにしました。
実行結果がこちらです。 f:id:Phaser_3:20190129180915g:plain 先日実装したエフェクトはピクセルの一つ一つにtweenを設定していたのでパフォーマンスに問題がありましたが、こちらはparticleで実装しているので動作が早いです。

スプライトの形に添ったパーティクルエミッタを作る①:辺からの出力

輪郭取得を使ってスプライトの形に添ったパーティクルエミッタを作ってみたいと思います。

輪郭からpolygonクラスを作る

以前作った輪郭取得を改造し、取得時にphaser.geom.polygonを作成するようにします。

public poly: { [key: string]: Phaser.Geom.Polygon } = {};
~~
var poly = new Phaser.Geom.Polygon(pol);
this.poly[texture] = poly;

createverts後に作成したpolyを使ってemitterを作ります。

this.emitter = this.add.particles('2x2').createEmitter({
    x: 400,
    y: 300,
    scale: { start: 0.1, end: 0 },
    speed: { min: -100, max: 100 },
    tint: 0x000000,
    quantity: 50
});

this.emitter.setEmitZone({
    source: this.poly[key],
    type: 'edge',
    quantity: 50
});

実行結果がこちらです。 f:id:Phaser_3:20190128140813p:plain f:id:Phaser_3:20190128140801p:plain f:id:Phaser_3:20190128140845g:plain
輪郭に沿った出力ができています。
emitterには他に与えられた点群からランダムに出力するrandomというパラメータがあるのですが、getRandomPointという関数がないと使用できないらしく、randomでのエミッタ設定はできませんでした。
次回はrandomを使いスプライトの輪郭からではなく範囲内からエミットする方法を調べます。

Phaser3:物理ボディからエフェクトを作る②

前回に引き続きエフェクトを作ってみます。

ピクセル群の位置を調節する

前回の記事ではスプライト左上から中心(origin)までの距離分ピクセルがずれてしまっていたので、ずれを加味した座標を計算します。

let point = Phaser.Geom.Point.Clone(this.point)
point.setTo(x + ((j + -source.width / 2) * Math.cos(rotate) - (i - source.height / 2) * Math.sin(rotate)), y + ((i - source.height / 2) * Math.cos(rotate) + (j - source.width / 2) * Math.sin(rotate)))
         

さらにピクセルにtweenを設定します。

this.tweens.add({
    targets: point,
    x: Phaser.Math.Between(0,this.sys.canvas.width),
    y: Phaser.Math.Between(0, this.sys.canvas.height),
    ease: 'Power1',
    duration: 300,
    onComplete: () => { this.geomPoints.splice(this.geomPoints.indexOf(point), 1) },
});

実行結果がこちらです。 f:id:Phaser_3:20190125133406p:plain f:id:Phaser_3:20190125133424g:plain 墨をばらまいたようなエフェクトができました。
GraphicsSubをclearしていないので軌跡が残ってしまっているようです。
これはこれで面白いのですが求めているものとは違う感じがするのでupdateでclearを行うようにし、飛散する範囲も狭めました。
さらにTweenも見直し、ポイントを縮小して飛沫の減衰を表現したいのでFillRectShapeを用いるようにします。
pointはvector3で定義しなおし、zの値をポイントのスケールとして扱うようにしました。

}

create() {
    this.graphicsSub = this.add.graphics();
    this.point = new Phaser.Math.Vector3(0, 0,0);
    this.matter.world.setBounds();

    this.cameras.main.setBackgroundColor(0xffffff);

    this.graphics = this.add.graphics();
    this.graphics.fillStyle(0x000000);
    let textures = ["bar", "block1", "block2", "block3", "rect"]

    let rand = ["bar", "block1", "block2", "block3", "rect"]

    this.obj = this.CreateVerts(Phaser.Math.Between(0, this.sys.canvas.width), Phaser.Math.Between(0, this.sys.canvas.height / 2), rand[Phaser.Math.FloorTo(Phaser.Math.Between(0, rand.length - 1))])
    this.obj.setVelocity(6, 3);
    this.obj.setAngularVelocity(0.01);
    this.obj.setBounce(1);
    this.obj.setFriction(0, 0, 0);
}
        
update() {
    let rand = ["bar", "block1", "block2", "block3", "rect"]

    if (this.input.activePointer.isDown) {
        this.GetPixel(this.obj.x, this.obj.y, this.obj.rotation, this.obj.texture.key)
        this.obj.destroy();
        this.obj = this.CreateVerts(Phaser.Math.Between(0, this.sys.canvas.width), Phaser.Math.Between(0, this.sys.canvas.height / 2), rand[Phaser.Math.FloorTo(Phaser.Math.Between(0, rand.length - 1))])
        this.obj.setAngularVelocity(1);

    }

    this.graphicsSub.clear();

    for (let p of this.geomPoints) {
        this.graphicsSub.fillRect(p.x, p.y , p.z,p.z)
    }

}

public GetPixel(x: number, y: number, rotate:number,key: string) {
    let source = this.textures.get(key).getSourceImage()
    this.graphicsSub.clear();
    for (let i = 0; i < source.height; i++) {
        for (let j = 0; j < source.width; j++) {
            let pixel = this.textures.getPixel(j, i, key, 0)
            if (pixel.alpha > 0) {
                let color = new Phaser.Display.Color();
                color.setTo(0, 0, 0)

                let point = this.point.clone();
                point.set(x + ((j  -source.width / 2) * Math.cos(rotate) - (i - source.height / 2) * Math.sin(rotate)), y + ((i - source.height / 2) * Math.cos(rotate) + (j - source.width / 2) * Math.sin(rotate)),1.5)
                let randompoint = this.point.clone()
                randompoint.x = Phaser.Math.Between(x - source.width / 2, x + source.width / 2)
                randompoint.y = Phaser.Math.Between(y - source.height / 2, y + source.height / 2)
                this.tweens.add({
                    targets: point,
                    x: Phaser.Math.Between(0, this.sys.canvas.width),
                    y: Phaser.Math.Between(0, this.sys.canvas.height),
                    z:0,
                    ease: 'Power1',
                    duration: 150* Phaser.Math.Distance.Between(point.x,point.y,randompoint.x,randompoint.y),
                    onComplete: () => { this.geomPoints.splice(this.geomPoints.indexOf(point), 1) },
                });

                this.geomPoints.push(point)

            }
        }
    }
}

実行結果がこちらです。 f:id:Phaser_3:20190125144841g:plain 粉砕エフェクトができていると思います。
今回のソースはこちらです。

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

namespace MyGame {

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

    export class Vert {
        public point: Phaser.Math.Vector2;
        public next1: Phaser.Math.Vector2;
        public next2: Phaser.Math.Vector2;
        constructor(p: Phaser.Math.Vector2, n1: Phaser.Math.Vector2, n2: Phaser.Math.Vector2, ) {
            this.point = p;
            this.next1 = n1;
            this.next2 = n2;
        }
    }


    export class MyScene1 extends Phaser.Scene {

        //グラフィックオブジェクトを用意
        public player: Phaser.GameObjects.Image;
        public beamGroup: Phaser.GameObjects.Group;

        public graphics: Phaser.GameObjects.Graphics;
        public graphicsSub: Phaser.GameObjects.Graphics;
        public points: Phaser.Math.Vector2[] = [];
        public closepath: Phaser.Math.Vector2[] = [];
        public pixels: number[][];
        public imageData: ImageData;
        public pixelsCopy: number[][];

        public border: Phaser.Math.Vector2[] = [];
        public verts: { [key: string]: Phaser.Math.Vector2[] } = {};
        public canvas: Phaser.Textures.CanvasTexture;
        public obj: Phaser.Physics.Matter.Sprite;
        public point: Phaser.Math.Vector3;
        public geomPoints:Phaser.Math.Vector3[]=[]


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

        preload() {
            this.load.image("bar", "assets/images/bar.png");
            this.load.image("block1", "assets/images/block1.png");
            this.load.image("block2", "assets/images/block2.png");
            this.load.image("block3", "assets/images/block3.png");
            this.load.image("rect", "assets/images/rect32.png");
            this.load.image("pixel", "assets/images/2x2.png");
        }

        public CreateVerts(x: number, y: number, texture: string) {
            this.points = [];
            this.closepath = [];
            this.border = [];

            //phaserのテクスチャマネージャからsourceimageを取得する
            let source = this.textures.get(texture).getSourceImage();;

            if (this.verts[texture] === undefined) {

                //canvasを作成しテクスチャを描画
                this.canvas = this.textures.createCanvas(texture + "_canvas", source.width, source.height);
                this.canvas.clear()
                this.canvas.draw(0, 0, source as HTMLImageElement);

                //imagedataを取得
                this.imageData = this.canvas.imageData;
                this.pixels = new Array();
                this.pixelsCopy = new Array();
                //ピクセルを配列に入れる
                for (let i = 0; i < this.canvas.imageData.data.length; i += 4) {
                    let p = [this.canvas.imageData.data[i], this.canvas.imageData.data[i + 1], this.canvas.imageData.data[i + 2], this.canvas.imageData.data[i + 3]]
                    this.pixels.push(p);
                }

                //ピクセルを配列にいれなおす
                for (let i = 0; i < this.imageData.height; i++) {
                    this.pixelsCopy[i] = new Array();
                    for (let j = 0; j < this.imageData.width; j++) {
                        this.pixelsCopy[i].push(0)
                    }
                }

                let xx = 0;
                let yy = 0;

                //アルファ値の取得
                for (let i = 0; i < this.pixels.length; i++) {

                    this.pixelsCopy[yy][xx] = this.pixels[i][3]
                    xx++;

                    if (xx === source.width) {
                        xx = 0;
                        yy++;
                    }
                }

                let origin = 0;
                let tmp = new Phaser.Math.Vector2(0);

                while (this.pixels[origin][3] === 0) {
                    origin++;
                }

                let mov = new Phaser.Math.Vector2(0, 0);
                tmp = this.GetBoarder(new Phaser.Math.Vector2(origin, 0), mov);

                this.border.push(new Phaser.Math.Vector2(origin, 0))

                let prevmov = new Phaser.Math.Vector2(mov.x, mov.y);

                while (!(tmp.x === origin && tmp.y === 0)) {
                    tmp = this.GetBoarder(tmp, mov);
                    if (!(prevmov.x === mov.x && prevmov.y === mov.y)) {
                        this.border.push(new Phaser.Math.Vector2(tmp.x, tmp.y));
                    }
                    prevmov.set(mov.x, mov.y);
                }

                //取得した輪郭からverticesを作る
                let verts = ""
                for (let p of this.border) {
                    verts += (p.x + x) + " " + (p.y + y) + " "
                }


                var poly = this.add.polygon(x, y, verts, 0x0000ff, 0.2);
                this.verts[texture] = this.border;

                return this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false }, label: texture }).setTint(0x05FBFF, 0x1E00FF0);

            } else {
                let verts = ""

                for (let p of this.verts[texture]) {
                    verts += (p.x + x) + " " + (p.y + y) + " "
                }

                return this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false }, label: texture}).setTint(0x05FBFF, 0x1E00FF0);
                
            }

        }

        create() {
            this.graphicsSub = this.add.graphics();
            this.point = new Phaser.Math.Vector3(0, 0,0);
            this.matter.world.setBounds();

            this.cameras.main.setBackgroundColor(0xffffff);

            this.graphics = this.add.graphics();
            this.graphics.fillStyle(0x000000);
            let textures = ["bar", "block1", "block2", "block3", "rect"]

            let rand = ["bar", "block1", "block2", "block3", "rect"]

            this.obj = this.CreateVerts(Phaser.Math.Between(0, this.sys.canvas.width), Phaser.Math.Between(0, this.sys.canvas.height / 2), rand[Phaser.Math.FloorTo(Phaser.Math.Between(0, rand.length - 1))])
            this.obj.setVelocity(6, 3);
            this.obj.setAngularVelocity(0.01);
            this.obj.setBounce(1);
            this.obj.setFriction(0, 0, 0);
        }
        
        update() {
            let rand = ["bar", "block1", "block2", "block3", "rect"]

            if (this.input.activePointer.isDown) {
                this.GetPixel(this.obj.x, this.obj.y, this.obj.rotation, this.obj.texture.key)
                this.obj.destroy();
                this.obj = this.CreateVerts(Phaser.Math.Between(0, this.sys.canvas.width), Phaser.Math.Between(0, this.sys.canvas.height / 2), rand[Phaser.Math.FloorTo(Phaser.Math.Between(0, rand.length - 1))])
                this.obj.setAngularVelocity(1);

            }

            this.graphicsSub.clear();

            for (let p of this.geomPoints) {
                this.graphicsSub.fillRect(p.x, p.y , p.z,p.z)
            }

        }

        public GetPixel(x: number, y: number, rotate:number,key: string) {
            let source = this.textures.get(key).getSourceImage()
            this.graphicsSub.clear();
            for (let i = 0; i < source.height; i++) {
                for (let j = 0; j < source.width; j++) {
                    let pixel = this.textures.getPixel(j, i, key, 0)
                    if (pixel.alpha > 0) {
                        let color = new Phaser.Display.Color();
                        color.setTo(0, 0, 0)

                        let point = this.point.clone();
                        point.set(x + ((j  -source.width / 2) * Math.cos(rotate) - (i - source.height / 2) * Math.sin(rotate)), y + ((i - source.height / 2) * Math.cos(rotate) + (j - source.width / 2) * Math.sin(rotate)),1.5)
                        let randompoint = this.point.clone()
                        randompoint.x = Phaser.Math.Between(x - source.width / 2, x + source.width / 2)
                        randompoint.y = Phaser.Math.Between(y - source.height / 2, y + source.height / 2)
                        this.tweens.add({
                            targets: point,
                            x: Phaser.Math.Between(0, this.sys.canvas.width),
                            y: Phaser.Math.Between(0, this.sys.canvas.height),
                            z:0,
                            ease: 'Power1',
                            duration: 150* Phaser.Math.Distance.Between(point.x,point.y,randompoint.x,randompoint.y),
                            onComplete: () => { this.geomPoints.splice(this.geomPoints.indexOf(point), 1) },
                        });

                        this.geomPoints.push(point)

                    }
                }
            }

        }


        //衝突を処理するための関数
        public GetCollide(body): Phaser.Physics.Arcade.Body {
            if (body.parent === body) {
                return body;
            }
            while (body.parent !== body) {
                body = body.parent;
            }
            return body;

        }

        public GetBoarder(v: Phaser.Math.Vector2, m: Phaser.Math.Vector2): Phaser.Math.Vector2 {
            let r: Phaser.Math.Vector2 = null;

            //左下のピクセルから調べていく
            let dir1 = () => {
                if (v.x > 0 && v.y < this.imageData.height - 1) {
                    if (this.pixelsCopy[v.y + 1][v.x - 1] !== 0) {
                        m.set(-1, 1);
                        return new Phaser.Math.Vector2(v.x - 1, v.y + 1);
                    }
                }
                return null;
            }

            //下
            let dir2 = () => {
                if (v.y < this.imageData.height - 1 && !(m.x == 0 && m.y == -1)) {
                    if (this.pixelsCopy[v.y + 1][v.x] !== 0) {
                        m.set(0, 1);
                        return new Phaser.Math.Vector2(v.x, v.y + 1);
                    }
                }
                return null;
            }

            //右下
            let dir3 = () => {
                if (v.x < this.imageData.width - 1 && v.y < this.imageData.height - 1) {
                    if (this.pixelsCopy[v.y + 1][v.x + 1] !== 0) {
                        m.set(1, 1);
                        return new Phaser.Math.Vector2(v.x + 1, v.y + 1);
                    }
                }
                return null;

            }

            //右
            let dir4 = () => {
                if (v.x < this.imageData.width - 1) {
                    if (this.pixelsCopy[v.y][v.x + 1] !== 0) {
                        m.set(1, 0);
                        return new Phaser.Math.Vector2(v.x + 1, v.y);
                    }
                }
                return null;
            }

            //右上
            let dir5 = () => {
                if (v.x < this.imageData.width - 1 && v.y > 0) {
                    if (this.pixelsCopy[v.y - 1][v.x + 1] !== 0) {
                        m.set(1, -1);
                        return new Phaser.Math.Vector2(v.x + 1, v.y - 1);
                    }
                }
                return null;
            }

            //上
            let dir6 = () => {
                if (v.y > 0) {
                    if (this.pixelsCopy[v.y - 1][v.x] !== 0) {
                        m.set(0, -1);
                        return new Phaser.Math.Vector2(v.x, v.y - 1);
                    }
                }
                return null;
            }


            //左上
            let dir7 = () => {
                if (v.x > 0 && v.y > 0) {
                    if (this.pixelsCopy[v.y - 1][v.x - 1] !== 0) {
                        m.set(-1, -1);
                        return new Phaser.Math.Vector2(v.x - 1, v.y - 1);
                    }
                }
                return null;
            }

            //左
            let dir8 = () => {
                if (v.x > 0) {
                    if (this.pixelsCopy[v.y][v.x - 1] !== 0) {
                        m.set(-1, 0);
                        return new Phaser.Math.Vector2(v.x - 1, v.y);
                    }
                }
                return null;
            }

            if (m.x === -1 && m.y === 1) {
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
            }

            if (m.x === 0 && m.y === 1) {
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === 1 && m.y === 1) {
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === 1 && m.y === 0) {
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === 1 && m.y === -1) {
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === 0 && m.y === -1) {
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === -1 && m.y === -1) {
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === -1 && m.y === 0) {
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
            }

            if (m.x === 0 && m.y === 0) {
                r = dir1();
                if (r !== null) {
                    return r;
                }
                r = dir2();
                if (r !== null) {
                    return r;
                }
                r = dir3();
                if (r !== null) {
                    return r;
                }
                r = dir4();
                if (r !== null) {
                    return r;
                }
                r = dir5();
                if (r !== null) {
                    return r;
                }
                r = dir6();
                if (r !== null) {
                    return r;
                }
                r = dir7();
                if (r !== null) {
                    return r;
                }
                r = dir8();
                if (r !== null) {
                    return r;
                }
            }
            return null;
        }
    }
}

Phaser3 : 物理ボディからエフェクトを作る①

以前作ったアルファ値取得の処理を使い、物理ボディが粉砕されるようなエフェクトを作ってみたいと思います。

物理ボディの位置と回転に応じてピクセルを描画する

前回のcreateVertsを少し改造し、作成した物理ボディを返すようにします。

作成時にテクスチャの名前でラベルを設定します。

return this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false }, label: texture }).setTint(0x05FBFF, 0x1E00FF0);

次に物理ボディの現在位置と角度からpointの集合を作るGetPixel関数を作ります。
fillpointShapeの部分で座標を物理ボディの角度に応じて回転させています。

public GetPixel(x: number, y: number, rotate:number,key: string) {
    let source = this.textures.get(key).getSourceImage()
    this.graphicsSub.clear();
    for (let i = 0; i < source.height; i++) {
        for (let j = 0; j < source.width; j++) {
            let pixel = this.textures.getPixel(j, i, key, 0)
            if (pixel.alpha > 0) {
                let color = new Phaser.Display.Color();
                color.setTo(0, 0, 0)
                this.graphicsSub.fillStyle(color.color, 1);
                
                //回転させる
                this.graphicsSub.fillPointShape(new Phaser.Math.Vector2(x+(j*Math.cos(rotate)-i*Math.sin(rotate)), y+(i*Math.cos(rotate)+j*Math.sin(rotate))), 1);
            }
        }
    }
}

updateでマウスクリック時にGetPixelを実行します。

update() {
    if (this.input.activePointer.isDown) {
        this.GetPixel(this.obj.x, this.obj.y, this.obj.rotation,this.obj.texture.key)
    }

実行結果がこちらです。 f:id:Phaser_3:20190124152740g:plain 物理ボディのoriginが中心になっているのでピクセル群と少しずれがでていますが、物理ボディの位置と回転に従ってピクセルを生成できているのがわかります。
次回はこの処理をエフェクトとして作りこんでいきたいと思います。

Phaser3 :スプライトに自動生成した物理ボディをつけてみる

前回スプライトからメッシュを自動で生成しましたが、スプライトに物理ボディをつける実装をやっていなかったので、今回紹介したいと思います。

matterで物理ボディ付きのスプライトを作成する

matter.add.spriteのオプションでカスタム物理ボディを追加する

以前の記事で作成したCreateVertsを改造し、作成したvertsを連想配列に保存し、verts作成済のテクスチャデータが指定された場合にそこから呼び出すようにします。

public verts: { [key: string]: Phaser.Math.Vector2[] } = {};
~~
public CreateVerts(x: number, y: number, texture: string) {
    this.points = [];
    this.closepath = [];
    this.border = [];

    //phaserのテクスチャマネージャからsourceimageを取得する
    let source = this.textures.get(texture).getSourceImage();;

    if (this.verts[texture] === undefined) {

        //canvasを作成しテクスチャを描画
        this.canvas = this.textures.createCanvas(texture + "_canvas", source.width, source.height);

        //canvasをクリア
        this.canvas.clear()
        this.canvas.draw(0, 0, source as HTMLImageElement);

        //imagedataを取得
        this.imageData = this.canvas.imageData;
        this.pixels = new Array();
        this.pixelsCopy = new Array();
        //ピクセルを配列に入れる
        for (let i = 0; i < this.canvas.imageData.data.length; i += 4) {
            let p = [this.canvas.imageData.data[i], this.canvas.imageData.data[i + 1], this.canvas.imageData.data[i + 2], this.canvas.imageData.data[i + 3]]
            this.pixels.push(p);
        }

        //ピクセルを配列にいれなおす
        for (let i = 0; i < this.imageData.height; i++) {
            this.pixelsCopy[i] = new Array();
            for (let j = 0; j < this.imageData.width; j++) {
                this.pixelsCopy[i].push(0)
            }
        }

        let xx = 0;
        let yy = 0;

        //アルファ値の取得
        for (let i = 0; i < this.pixels.length; i++) {

            this.pixelsCopy[yy][xx] = this.pixels[i][3]
            xx++;

            if (xx === source.width) {
                xx = 0;
                yy++;
            }
        }

        let origin = 0;
        let tmp = new Phaser.Math.Vector2(0);

        while (this.pixels[origin][3] === 0) {
            origin++;
        }

        let mov = new Phaser.Math.Vector2(0, 0);
        tmp = this.GetBoarder(new Phaser.Math.Vector2(origin, 0), mov);

        this.border.push(new Phaser.Math.Vector2(origin, 0))

        let prevmov = new Phaser.Math.Vector2(mov.x, mov.y);

        while (!(tmp.x === origin && tmp.y === 0)) {
            tmp = this.GetBoarder(tmp, mov);
            if (!(prevmov.x === mov.x && prevmov.y === mov.y)) {
                this.border.push(new Phaser.Math.Vector2(tmp.x, tmp.y));
            }
            prevmov.set(mov.x, mov.y);
        }

        //取得した輪郭からverticesを作る
        let verts = ""
        for (let p of this.border) {
            verts += (p.x + x) + " " + (p.y + y) + " "
        }


        var poly = this.add.polygon(x, y, verts, 0x0000ff, 0.2);
        this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false } }).setTint(0x05FBFF, 0x1E00FF0);
        this.verts[texture] = this.border;

    } else {
        let verts = ""

        for (let p of this.verts[texture]) {
            verts += (p.x + x) + " " + (p.y + y) + " "
        }

        this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false } }).setTint(0x05FBFF, 0x1E00FF0);
                
    }

上記処理内のthis.matter.add.spriteで物理ボディ付きのスプライトを作成しています。
第五引数でオプションを指定し、typeにfromvertsを選ぶことで用意した頂点配列を使用することが可能です。
オプションのvertsで使用する頂点配列を指定します。

this.matter.add.sprite(x, y, texture, 0, { shape: { type: 'fromVerts', verts: verts, flagInternal: false } }).setTint(0x05FBFF, 0x1E00FF0);

画面クリックでランダムな位置に物理ボディを作成する処理を追加しました。

update() {
    let rand = ["bar","block1","block2","block3","rect"]
    if (this.input.activePointer.isDown) {
        this.CreateVerts(Phaser.Math.Between(0, this.sys.canvas.width), Phaser.Math.Between(0, this.sys.canvas.height/2), rand[Phaser.Math.FloorTo(Phaser.Math.Between(0,rand.length))])

    }
}

上記の実行結果がこちらです。 f:id:Phaser_3:20190123170804p:plain f:id:Phaser_3:20190123170815g:plain 輪郭取得の精度を上げれば落ちものパズルなどにも使えそうです。

phaser3:プログレスバーを作る

今回はloadの機能を使ってプログレスバーを作ってみます。

loadの挙動

ロード状況へのアクセスの仕方

load.onでロード中の処理を記述することができます。
progressは一つのファイルのロードが終わったときに、fileprogressはファイルのロードに入ったときに、completeはすべてのファイルのロードが終わったときに実行されます。

//ロードが進行したときの処理
this.load.on('progress', function (value) {
    progressBar.clear();
    progressBar.fillStyle(0xffffff, 1);
    progressBar.fillRect(250, 280, 300 * value, 30);
});

//ファイルのロードに入ったときの処理
this.load.on('fileprogress', function (file) {
    text.text = file.key;
});

//すべてのロードが完了したときの処理
this.load.on('complete', function () {
    text.text = 'complete';
});

実行結果がこちらです。 f:id:Phaser_3:20190122153325g:plain f:id:Phaser_3:20190122153339p:plain

今回のソースはこちらです。

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

namespace MyGame {



    export class MyScene1 extends Phaser.Scene {


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

        preload() {

            //プログレスバー用のgraphics
            let progressBar = this.add.graphics();
            let progressBox = this.add.graphics();
            progressBox.fillStyle(0x222222, 0.8);
            progressBox.fillRect(240, 270, 320, 50);

            let text = this.add.text(this.sys.canvas.width / 2, 350,"load");
            text.setOrigin(0.5);

            //ロードが進行したときの処理
            this.load.on('progress', function (value) {
                progressBar.clear();
                progressBar.fillStyle(0xffffff, 1);
                progressBar.fillRect(250, 280, 300 * value, 30);
            });

            //ファイルのロードに入ったときの処理
            this.load.on('fileprogress', function (file) {
                text.text = file.key;
            });

            //すべてのロードが完了したときの処理
            this.load.on('complete', function () {
                text.text = 'complete';
            });

            this.load.image('block', 'assets/images/block.png');
            for (var i = 0; i < 500; i++) {
                this.load.image('block' + i, 'assets/images/block.png');
            }
        }
    }
}

Phaser3 : event

今回はEventを使用してみます。

Events

EventはPhaser.Events.EventEmitterから作ることができます。

create() {
    //イベントエミッタ
    this.emitter = new Phaser.Events.EventEmitter();
}

onでエミットするイベントのkeyと実行する処理を設定し、emitでキーを指定して実行します。

create() {
    //イベントエミッタ
    this.emitter = new Phaser.Events.EventEmitter();

    //イベントを登録
    this.emitter.on("text", ()=>this.AddText(true), this);

    //イベントを実行
    this.emitter.emit("text");
}

またeventsはオブジェクトにも設定することができます。
textからさらにイベントを実行してtextを作成するAddText関数を作成しました。

public AddText(flg:boolean) {

    let text = this.add.text(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "event");
    text.setTint(0xff00ff, 0xffff00, 0x0000ff, 0xff0000);

    if (flg) {
        //テキストにイベントを登録してエミット
        text.on("text", () => this.AddText(false), this);
        text.emit("text")
    } else {
        text.y += 50;
        text.text = "more";

    }
}

実行結果がこちらです。 f:id:Phaser_3:20190121154837p:plain

今回のソースはこちらです。

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

namespace MyGame {



    export class MyScene1 extends Phaser.Scene {

        public emitter: Phaser.Events.EventEmitter;

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

        preload() {
            
        }

        create() {
            //イベントエミッタ
            this.emitter = new Phaser.Events.EventEmitter();

            //イベントを登録
            this.emitter.on("text", ()=>this.AddText(true), this);

            //イベントを実行
            this.emitter.emit("text");
        }


        public AddText(flg:boolean) {

            let text = this.add.text(this.sys.canvas.width / 2, this.sys.canvas.height / 2, "event");
            text.setTint(0xff00ff, 0xffff00, 0x0000ff, 0xff0000);

            if (flg) {
                //テキストにイベントを登録してエミット
                text.on("text", () => this.AddText(false), this);
                text.emit("text")
            } else {
                text.y += 50;
                text.text = "more";

            }
        }
    }
}