Upgrading to melonJS 1.1
With the release of melonJS 1.1, is time to conduct a upgrade to this new version of the engine, packed with really nice features, as:
- New blazing-fast inheritance pattern (Jay Inheritance)
- New SAT (Separate Axis Theorem) based collision algorithm
- Super fast collision detection across a spatial partitioning algorithm (QuadTree)
- Added collision filtering support
- Entity object redesign with a full polygon shape based physic body implementation
- Several objects renamed for a cleaner and standardized API
- Fixed various bugs and issues
Check 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 0.9.11, you need to update first to 1.0.2 version (check the post Upgrading to melonJS 1.0) and after to 1.1.0 version.
Below we will check some of the necessary changes to upgrade from melonJS 1.0.2 (previous version) to melonJS 1.1.0 (current version when writing this post). For a complete list of changes visit the melonJS Upgrade Guide.
Before the Upgrade
The melonJS 1.1 is a major release and include several breaking changes which forces you to make several adjustments in your game code.
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 me.ObjectEntity -> me.Entity me.SpriteObject -> me.Sprite me.ObjectContainer -> me.Container me.video.getWidth -> me.video.renderer.getWidth me.video.getHeight -> me.video.renderer.getHeight me.video.getSystemCanvas -> me.video.renderer.getCanvas me.video.getSystemContext -> me.video.renderer.getContext me.video.getScreenCanvas -> me.video.renderer.getScreenCanvas me.video.getScreenContext -> me.video.renderer.getScreenContext me.video.applyRGBFilter -> me.video.renderer.applyRGBFilter me.video.getContext2d -> me.CanvasRenderer.getContext2d
If you used the onDestroyEvent callback from me.Renderable, the same should be renamed to onDeactivateEvent. Note that the function onDestroyEvent remains only to me.ScreenObject.
In preparation to WebGL support (probably available in version 1.2.0), the call to me.video.init has a new parameter informing the type of renderer (currently accepts only me.video.CANVAS):
// Before melonJS 1.1 me.video.init("game", 400, 300, true, "auto"); // After melonJS 1.1 me.video.init("game", me.video.CANVAS, 400, 300, true, "auto");
Let's Rock
The inheritance pattern has changed significantly, moving from John Resig Simple Inheritance to the new and fast Jay Inheritance, forcing various adjustments in the code. In the melonJS 1.0 and previous, you have something like:
// Before melonJS 1.1 var sprite = me.SpriteObject.extend({ // Property active: yes, // Constructor init : function(x, y, sprite) { this.parent(x, y, sprite); }, // Draw draw: function(context) { // Draw only if is active if (this.active) this.parent(context); } });
To make performance improvements and enforce better rules you need to invoke a _super method (instead of parent), passing the base class itself, a string representing the method name, and an array of the arguments:
// After melonJS 1.1 var sprite = me.Sprite.extend({ // Constructor init : function(x, y, sprite) { this._super(me.Sprite, "init", [x, y, sprite]); // Properties must be set in the init method only! this.active = yes; }, // Draw draw: function(renderer) { // Draw only if is active if (this.active) this._super(me.Sprite, "draw", [renderer]); } });
At first this change can scare, but the benefits with increased performance and reduced memory consumption compensate. For a more detailed explanation of the great new inheritance pattern, see the post about Jay Inheritance.
For better performance, the me.Renderable, me.Rect, me.PolyShape and me.Ellipse now requires (x,y) integer values over a me.Vector2d instance:
// Before melonJS 1.1 var myRenderable = new me.Renderable(new me.Vector2d(20, 50), 200, 300); var myRect = new me.Rect(new me.Vector2d(30, 60), 150, 50); // After melonJS 1.1 var myRenderable = new me.Renderable(20, 50, 200, 300); var myRect = new me.Rect(30, 60, 150, 50);The me.AnimationSheet now requires x, y and a settings hash to its constructor:
// Before melonJS 1.1 var animation = new me.AnimationSheet(10, 20, me.loader.getImage("sprite"), 64, 32); // After melonJS 1.1 var animation = new me.AnimationSheet(10, 20, { image: me.loader.getImage("sprite"), spritewidth: 64, spriteheight: 32 });
Come Get Some
For a faster execution time, the me.Entity (me.ObjectEntity in the previous version) has been redesigned using a composition approach (lowering the amount of object properties), instead of inheritance based design used in the previous version. In this way, the me.Entity has a new me.Body child object that will hold all physic and collision properties and functions.
// Before melonJS 1.1 var entity = me.ObjectEntity.extend({ // Constructor init: function(x, y, settings) { this.parent(x, y, settings); // Set the gravity this.gravity = 2; // Set the velocity this.setVelocity(5, 8); }, // Update logic update: function(dt) { // Apply gravity this.vel.y += this.gravity; // Update entity Position this.updateMovement(); this.parent(dt); return true; } }); // After melonJS 1.1 var entity = me.Entity.extend({ // Constructor init: function(x, y, settings) { this._super(me.Entity, "init", [x, y, settings]); // Set the gravity this.body.gravity = 2; // Set the velocity this.body.setVelocity(5, 8); }, // Update logic update: function(dt) { // Apply gravity this.body.vel.y += this.body.gravity; // Update entity Position this.body.update(); this._super(me.Entity, "update", [dt]) return true;; } });
Thus, the following functions have been renamed or assigned through the me.Body child object:
// Physics Old Code -> Physics New Code this.accel -> this.body.accel this.canBreakTile -> this.body.canBreakTile this.disableTopLadderCollision -> this.body.disableTopLadderCollision this.falling -> this.body.falling this.gravity -> this.body.gravity this.jumping -> this.body.jumping this.maxVel -> this.body.maxVel this.onladder -> this.body.onladder this.onslope -> this.body.onslope this.shapes -> this.body.shapes this.vel -> this.body.vel this.addShape -> this.body.addShape this.getShape -> this.body.getShape this.setShape -> this.body.setShape this.setFriction -> this.body.setFriction this.setMaxVelocity -> this.body.setMaxVelocity this.setVelocity -> this.body.setVelocity this.collisionMap -> this.body.collisionMap // Collision Old Code -> Collision New Code this.updateMovement() -> this.body.update() me.game.world.collide(this) -> me.collision.check(this, true, this.collideHandler.bind(this), true)
The melonJS 1.1 has now a full SAT collision algorithm, for polygon collision detection and more accurate collision response. The previous collision check function has therefore been deprecated and replaced by a new me.collision.check function. Refined collision filtering is now possible through the use of me.body.setCollisionMask function (that defines what should collide with what).
// Before melonJS 1.1 var entity = me.ObjectEntity.extend({ init: function(x, y, settings) { // Call the constructor this.parent(x, y , settings); // Default velocity this.setVelocity(3, 15); }, // Update logic update: function(dt) { // Change velocity this.vel.x -= this.accel.x * me.timer.tick; // Check for collision with environment this.updateMovement(); // Check for collision with enemies or objects var res = me.game.world.collide(this); if (res) { // Change velocity this.vel.y -= this.maxVel.y * me.timer.tick; // Change position this.pos.x -= 20; } this.parent(dt); return true; }, // Collision callback onCollision: function() { // Disable collision this.collidable = false; // Remove from game me.game.world.removeChild(this); } }); // After melonJS 1.1 var entity = me.Entity.extend({ init: function(x, y, settings) { // Call the constructor this._super(me.Entity, 'init', [x, y , settings]); // 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; // Check for collision with environment this.body.update(); // Check for collision with enemies or objects me.collision.check(this, true, this.collideHandler.bind(this), true); this._super(me.Entity, "update", [dt]); return true; }, // Collision handler collideHandler: function(response) { // 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 preparation to support WebGL, the me.video is now agnostic of rendering target, by adding the me.CanvasRenderer object, passing it to the draw calls of all the objects added to the world container and other subsequent containers.
// Before melonJS 1.1 draw: function(context) { // Set the context color context.fillStyle = "#FFFFFF"; // Draw a simple rectangle context.fillRect(this.pos.x, this.pos.y, this.width, this.height); } // After melonJS 1.1 - Getting directly the context draw: function(renderer) { // Get the renderer context var context = renderer.getContext(); // Set the context color context.fillStyle = "#FFFFFF"; // Draw a simple rectangle context.fillRect(this.pos.x, this.pos.y, this.width, this.height); } // After melonJS 1.1 - Using me.CanvasRenderer methods (recommended) draw: function(renderer) { // Draw a simple rectangle, using the integrated function renderer.fillRect(this.pos.x, this.pos.y, this.width, this.height, "#FFFFFF"); }
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 collision detection, fast inheritance, bug fixes and performance enhancements, in addition to being better prepared for the next melonJS version (in development), with the replacement of the current limited tile collision layer by a shape based collision layer and the probable addition of WebGL support! 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.
0 comentários :