Jump to content

Scale and set anchor for physics body in P2 that uses loadPolygon


mattbrand
 Share

Recommended Posts

I'm using P2 physics, and I'm creating a relatively intricate border around the screen that I want balls to bounce against. I am scaling all assets on the screen for 3 different aspect ratios, depending on the user's screen size.

I create the border like this:

bgOverlay = otherSpriteGroup.create(gameWidth / 2, gameHeight / 2, "bgOverlay");
bgOverlay.anchor.setTo(0.5, 0.5);
bgOverlay.scale.setTo(scale.x, scale.y);
bgOverlay.body.clearShapes();
bgOverlay.body.loadPolygon("physicsData", "bgOverlay");
bgOverlay.body.setCollisionGroup(otherCollisionGroup);
bgOverlay.body.collides(bubbleCollisionGroup, otherHitBubble, this);
bgOverlay.body.static = true;

The trick is that I scale all assets using a Phaser Point, scale.x and scale.y. But when I load in the polygon data using bgOverlay.body.loadPolygon, it takes in actual pixel values, and I can't scale bgOverlay.body. I am not sure how to scale the numbers in the physics data. Any ideas?

Link to comment
Share on other sites

Hi, I have solution, that is pretty complex - once you created body with collider, you cannot scale it, but you can scale it while crating it. My solution is in TypeScript. It can handle objects with compound colliders too - for eaxample: one sprite with two circle colliders and one polygon collider. Objects in game I was working on were obstacles, so this is reason, why I use Obstacle in code below.

First I define a few interfaces:

    export interface IPoint {
        x: number;
        y: number;
    }

    export interface ICircle {
        radius: number;
        x: number;
        y: number;
    }

    export interface IRect {
        width: number;
        height: number;
        x: number;
        y: number;
    }

    export interface IPoly {
        points: number[];
    }

    export interface ICollision {
        type: string;
        data: ICircle | IRect | IPoly;
    }

    export interface IObstacle {
        collisions: ICollision[];
        touchPoint: IPoint;
        scale?: number;
    }

 Simply: Obstacle is defined with array of colliders (ICollision) and optional scale if different from 1. You can ingnore touchPoint - it was game specific.

 Then I have class P2Sprite extending Phaser.Sprite:

    export class P2Sprite extends Phaser.Sprite {

        protected _savePosition = new Phaser.Point(0, 0);
        protected _saveScale: number = 1;

        // -------------------------------------------------------------------------
        public setColliders(collisions: ICollision[], scale:number, flipped: boolean): void {

            this._saveScale = scale;

            let body = <Phaser.Physics.P2.Body>this.body;

            let axOffset = (0.5 - this.anchor.x) * this.width * (flipped ? -1 : 1);
            let ayOffset = (0.5 - this.anchor.y) * this.height;

            body.clearShapes();

            for (let i = 0; i < collisions.length; i++) {
                let c = collisions[i];

                switch (c.type) {
                    case "circle": {
                        let obj = <ICircle>c.data;
                        body.addCircle(
                            obj.radius * scale,
                            (flipped ? this.width - obj.x : obj.x) * scale - this.width / 2 + axOffset,
                            obj.y * scale - this.height / 2 + ayOffset);
                    }
                        break;


                    case "rect": {
                        let obj = <IRect>c.data;
                        body.addRectangle(
                            obj.width * scale,
                            obj.height * scale,
                            (flipped ? this.width - obj.x - obj.width / 2 : obj.x + obj.width / 2) * scale - this.width / 2 + axOffset,
                            (obj.y + obj.height / 2) * scale - this.height / 2 + ayOffset);
                    }
                        break;


                    case "poly": {
                        let defPts = (<IPoly>c.data).points;
                        let points: number[] = [];
                        if (!flipped) {
                            for (let i = 0; i < defPts.length; i += 2) {
                                points[i] = defPts[i] * scale + axOffset;
                                points[i + 1] = defPts[i + 1] * scale + ayOffset;
                            }
                        } else {
                            for (let i = defPts.length - 2, j = 0; i >= 0; i -= 2, j += 2) {
                                points[j] = (this.width - defPts[i]) * scale + axOffset;
                                points[j + 1] = defPts[i + 1] * scale + ayOffset;
                            }
                        }
                        let polyData = [{ "shape": points }];
                        body.loadPolygon(null, polyData);
                    }
                        break;


                    default:
                        console.error("unknown type of collision " + c.type);
                        break;
                }
            }
        }

        // -------------------------------------------------------------------------
        public setPosition(x: number, y: number): void {
            let body = <Phaser.Physics.P2.Body>this.body;

            body.x = x;
            body.y = y;

            this._savePosition.set(x, y);
        }

        // -------------------------------------------------------------------------
        public setRotation(angle: number): void {
            let body = <Phaser.Physics.P2.Body>this.body;

            body.angle = angle;
        }

        // -------------------------------------------------------------------------
        public setCollisionGroup(group: Phaser.Physics.P2.CollisionGroup): void {
            (<Phaser.Physics.P2.Body>this.body).setCollisionGroup(group);
        }

        // -------------------------------------------------------------------------
        public collides(group: Phaser.Physics.P2.CollisionGroup | Phaser.Physics.P2.CollisionGroup[],
            callback?: Function, callbackContext?: any): void {

            (<Phaser.Physics.P2.Body>this.body).collides(group, callback, callbackContext);
        }
    }

 In setColliders is core of what you are looking for - it takes array of IColliders and adjusts it to scale and horizontal flip. Vertical flip was not needed, as horizontal flip with 180 degrees rotation can simulate it.

 Now, how to use it. Obstacles are defined like this:

    const OBSTACLE_DEFS: { [id: string]: IObstacle; } = {

        Letajici0: {
            collisions: [
                { type: "circle", data: { radius: 35, x: 255, y: 102 } },
                { type: "circle", data: { radius: 35, x: 253, y: 159 } },
                { type: "poly", data: { points: [245,66, /**/ 245, 191, /**/ 203, 173, /**/ 150, 128, /**/ 203, 83] } },
                { type: "rect", data: { width: 131, height: 8, x: 23, y: 125 } }
            ],
            touchPoint: { x: 130, y: 0 }
        },

        Bonus2: {
            collisions: [
                { type: "circle", data: { radius: 27, x: 47, y: 91 } },
                { type: "circle", data: { radius: 27, x: 87, y: 51 } }
            ],
            touchPoint: { x: 0, y: 0 },
            scale: 0.75
        }
    };

 Here are two obstacles with names Letajici0 and Bonus2. First one has compound collider from 4 coliders - 2x circle, 1x polygon. 1x rectangle. Again, ignore touchPoint. Second has 2 circles and should be scaled to 0.75.

 Obstacle class extends P2Sprite and can process these data:

    export class Obstacle extends P2Sprite {

        private _touchPoint = new Phaser.Point();

        private _tag: eObstacleTag;

        // -------------------------------------------------------------------------
        public constructor(game: Phaser.Game, name: string, flipped: boolean, tag: eObstacleTag) {
            super(game, 0, 0, "Sprites", name);

            this._tag = tag;

            // create sprite
            let frame = game.cache.getFrameByName("Sprites", name);

            //	enable the physics body on this sprite and turn on the visual debugger
            let p2 = <Phaser.Physics.P2>game.physics.p2;
            p2.enable(this, Play.DEBUG);

            // check if obstacle is defined
            let obstacle = OBSTACLE_DEFS[name];
            if (typeof obstacle === "undefined") {
                console.error("obstacle " + name + " not defined");
                return;
            }

            let scale = (typeof obstacle.scale !== "undefined") ? obstacle.scale : 1;

            // change sprite anchor and scale
            this.anchor.set(frame["anchorX"], frame["anchorY"]);
            this.scale.set(scale, scale);

            // set compound collision shapes
            this.setColliders(obstacle.collisions, scale, flipped);


            let body = <Phaser.Physics.P2.Body>this.body;
            body.static = true;

            // flip sprite
            if (flipped) {
                this.scale.x = -1;
            }

            //this._sprite.visible = false;
            //this.alpha = 0.5;

            // touch point
            this._touchPoint.set(obstacle.touchPoint.x, obstacle.touchPoint.y);

            // child sprite
            //let marker = new Phaser.Sprite(game, this._touchPoint.x, this._touchPoint.y, "Sprites", "Marker");
            //marker.anchor.set(0.5, 0.5);
            //this.addChild(marker);
        }
    }

 Again, ignore touchPoint and tag. What is important: it takes IObstacle definition from OBSTACLE_DEFS and creates colliders for it. I use the same name for frame and for IObstale key in OBSTALE_DEFS. Set anchor and sprite scale before you set colliders. Setting anchor in my example can look weird, but I have atlas frames extended with default anchor - just change it to more traditinal way like: this.anchor.set(0.5, 0.5);

 

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...