Upgrading to melonJS 2.0

11/20/2014 Andre Antonio Schmitz 0 Comments


With the release of melonJS 2.0, is time to conduct a upgrade to this solid and awesome version of the engine, packed with nice features, as:

- New Shape based collision (replaced the Tile based collision)
- Added support for Tiled 0.10 allowing shape transforms (scaling and rotation) and TMX Tileset Animations
- Automatic collision response handling
- Physics bodies now support multiple shapes
- Many fixes and improvements with collision detection in isometric maps
- Initial WebGL support (alpha quality)
- Fixed various bugs and issues

Check the the melonJS Blog for a official announcement and get the lastest version in melonJS Site.

The recommended is to migrate the current version used in the game to one immediately more recent facilitating the upgrade process (some melonJS versions have API breaks). For example, if your game uses melonJS 1.0.2, you need to update first to 1.1.0 version (check the post Upgrading to melonJS 1.1) and after to 2.0.2 version.

Below we will check some of the necessary changes to upgrade from melonJS 1.1.0 (previous version) to melonJS 2.0.2 (current version when writing this post). For a complete list of changes visit the melonJS Upgrade Guide.


Before the Upgrade


The melonJS 2.0 ends a cycle of major changes started from version 1.0, in order to make the engine most concise, fast, robust, cleaner and future proof, with the replacement of the naive collision mechanism. It's a major release and include some breaking changes so you should do adjustments in your game code. Probably, the most work to be done for this version is the adjustment of all Tiled maps, due the replacement of Tile based collision to Shape based collision, finalizing the implementing of the new collision mechanism!

Before starting the migration, check the Upgrade Guide and the Changelog of engine and decide if the amount of work for the upgrade compensates the incredible new features of this melonJS version!

Don't forget to always have a backup of the last game version before starting the upgrade process!


Piece of Cake


Start renaming the functions, objects and variables changed in this version, which can be easily performed via a Search and Replace (or Refactor) available in your preferred Code Editor (or IDE):

// Old Code -> New Code
debugPanel -> me.debug.Panel
me.Sprite.resize() -> me.Sprite.scale()
me.PolyShape -> me.Polygon
me.game.world.collide -> me.collision.check
The me.Body now supports multiple shapes and the me.Body.getShape() function now needs an index as argument, returning the shape specified at the index:

// Before melonJS 2.0 - Body has only one shape
var bounds = this.body.getShape().bounds;

// After melonJS 2.0 - Body has only one shape, see index = 0
var bounds = this.body.getShape(0).bounds;
The functions flipX and flipY have been removed from me.Entity and me.Body and are available only through the entity renderable component:

// Before melonJS 2.0
game.Entity = me.Entity.extend({
    init: function(x, y, settings){
        // Call the constructor
        this._super(me.Entity, 'init', [x, y , settings]);

        // Set the entity renderable
        this.renderable = game.texture.createSpriteFromName("sign.png");

        // Check for entity renderable flip - passed as constructor param
        this.flipX(settings.flip); 
    }
});

// After melonJS 2.0
game.Entity = me.Entity.extend({
    init: function(x, y, settings){
        // Call the constructor
        this._super(me.Entity, 'init', [x, y , settings]);

        // Set the entity renderable
        this.renderable = game.texture.createSpriteFromName("sign.png");

        // Check for entity renderable flip - passed as constructor param
        this.renderable.flipX(settings.flip); 
    }
});
Update the HUD pattern making it a floating container, because a bug in previous version allowed non-floating containers to always be within the viewport. For this, only add this.floating = true; in the HUD Container init function.


Let's Rock


With the new collision mechanism, the entities now automatically respond to collision, not requiring that me.Body have a explicit onCollision function callback, being necessary directly in me.Entity:

// Before melonJS 2.0
var entity = me.Entity.extend({
    init: function(x, y, settings) {
        // Call the constructor
        this._super(me.Entity, 'init', [x, y , settings]);

        // Set the default velocity
        this.body.setVelocity(3, 15);

        // Set the collision callback function
        this.body.onCollision = this.onCollision.bind(this); 
    },
   
    // Update logic
    update: function(dt) {
        // Change velocity 
        this.body.vel.x -= this.body.accel.x * me.timer.tick;
 
        // Apply physics to the body (this moves the entity)
        this.body.update();

        // Handle collisions against other shapes
        me.collision.check(this, true, this.collideHandler.bind(this), true);

        this._super(me.Entity, "update", [dt]);
        return true; 
    },

    // Collision handler
    collideHandler: function(response) {
        // Check for collision with enemy
        if (response.b.body.collisionType === me.collision.types.ENEMY_OBJECT) { 
            // Change velocity 
            this.body.vel.y -= this.body.maxVel.y * me.timer.tick;

            // Change position
            this.pos.x -= 20;

            // Update the entity bounds since we manually changed the position
            this.updateBounds();
        }
    },

    // Collision callback
    onCollision: function(res, obj) {
        // Disable collision
        this.body.setCollisionMask(me.collision.types.NO_OBJECT);

        // Remove from game
        me.game.world.removeChild(this);
    }
});
In this way, the callbacks used in this.body.onCollision and me.collision.check() are joined into a single callback onCollision defined in me.Entity (check the new signature of me.collision.check() function in the Online Documentation):

// After melonJS 2.0
var entity = me.Entity.extend({
    init: function(x, y, settings) {
        // Call the constructor
        this._super(me.Entity, 'init', [x, y , settings]);

        // Set the default velocity
        this.body.setVelocity(3, 15);       
    },
   
    // Update logic
    update: function(dt) {
        // Change velocity 
        this.body.vel.x -= this.body.accel.x * me.timer.tick;
 
        // Apply physics to the body (this moves the entity)
        this.body.update();

        // Handle collisions against other shapes
        me.collision.check(this);

        this._super(me.Entity, "update", [dt]);
        return true; 
    },    

    // Collision callback
    onCollision: function(response, other) {
        // Check for collision with enemy
        if (other.body.collisionType === me.collision.types.ENEMY_OBJECT) { 
            // Change velocity 
            this.body.vel.y -= this.body.maxVel.y * me.timer.tick;

            // Change position
            this.pos.x -= 20;

            // Update the entity bounds since we manually changed the position
            this.updateBounds();
        }

        // Disable collision
        this.body.setCollisionMask(me.collision.types.NO_OBJECT);

        // Remove from game
        me.game.world.removeChild(this);
    }
});
The Number.prototype.random(min, max) function has changed to match the standard Javascript function Math.random(), using the range [min, max), i.e, from min (inclusive) up to but not including max (exclusive):

// Before melonJS 2.0 - Random Number between 0 and 5
var random = Number.prototype.random(0, 5);

// After melonJS 2.0 - Random Number between 0 and 5 (Normal Syntax)
var random = Number.prototype.random(0, 6);

// After melonJS 2.0 - Random Number between 0 and 5 (Short Syntax)
var random = (0).random(6);


Come Get Some


Completing the new changes to collision system, the legacy Tile based collision mechanism has been replaced with a Shape based collision mechanism, using the same SAT implementation of the entities (added in version 1.1). This change requires adjusting the layer collision of all maps from Tiled, which can be a time consuming work, depending on the complexity and number of maps!

Before melonJS 2.0, you used a Tiled Tile Layer to define the world collision shapes, in accordance with the tiles types supported by engine (using the "metatiles" solid, platform, ladder, slope and breakable):

Tiled Level with Tiles Collision Layer

After melonJS 2.0, you should use a Tiled Object Layer and the world collision shapes can be defined using all standard objects from Tiled, like Polyline, Polygon (only convex with clockwise winding) and Ellipse, giving greater freedom and creativity to define the game collisions, not getting stuck with fixed shapes!

Tiled Level with Shape Collision Layer

Remember that you must use a Tiled Object Layer with name "collision" so the engine detects the collision layer properly. With this change in collision shapes the engine no more support builtin Platforms and Ladders tiles and they must be implemented using the new onCollision response callback:

// melonJS 2.0 - me.Entity onCollision callback
onCollision: function(response, other) {
    if (other.body.collisionType === me.collision.types.WORLD_SHAPE) {
        // Simulate a platform - property defined in Tiled Object
        if (other.type === "platform") {
            if (this.body.falling && !me.input.isKeyPressed('down') &&
               (response.overlapV.y > 0) && 
               (~~this.body.vel.y >= ~~response.overlapV.y)) {
                // Disable collision on the x axis
                response.overlapV.x = 0;

                // Respond to the Platform (it is solid)
                return true;
            }

            // Don't respond to the Platform (pass through)
            return false;
        }
    }

    // Make the object solid
    return true;
}
With the new Shape collision mechanism, some functions related to collisions have been removed from engine:

// Functions removed from Engine
me.Body.onslope
me.Body.onladder
me.Body.disableTopLadderCollision
me.Body.canBreakTile
me.Body.onTileBreak
me.Body.collisionMap
me.game.collisionMap
These functions can now be implemented through smart use of the onCollision callback with a similar logic as previously demonstrated to simulate a platform.


Work Done


After the completion of the steps above, you must run the game performing various tests to detect possible adjustments or fixes to be made. For possible questions, use the melonJS Forum or analyze directly the source code on GitHub Repo.

Even with the extra work, the upgrade compensates for the awesome shape based collisions, bug fixes and performance enhancements. And you can enjoy to review some "obscure" or "hackish" code, using the best practices you acquired in recent times or to implement new features in your game.

The melonJS 2.0 is a solid and stable version, demonstrating the maturity and evolution that the engine achieved during the last three years. Congrats melonJS Team for the job well done!

0 comentários :