melonJS
I started working with melonJS last year, after evaluating half a dozen HTML5/canvas game engines. My first choice was actually Akihabara. But not wanting to use the then-stable version, because it was quite out-of-date, I attempted to build it from source and start developing a game on it. I spent a full day with this game engine before giving up (so I did put some effort into it!) The problem was just an unfortunate state of development at the time. I don't know if things have changed much over there, but Akihabara isn't a game engine I hear of much any more these days. It's all about Impact, Lime, Cocos2d-html5, etc.
But I'm here to evangelize
melonJS. It's a game engine very similar to Lime, but with its own philosophy and design patterns. This article is meant to illustrate that point, and provide a few examples of useful (and not very obvious) patterns you can employ to do some interesting things in your games.
Like most open source projects, melonJS was started by
a single developer as a means to build something that interested him; a game. Over time, that game turned into another game, and another. Soon, it was clear that the common pieces of code could be useful as a general framework for writing games that make use of the HTML5 canvas. melonJs 0.9.0 (the initial public release) was made available in July 2011. The latest stable version is 0.9.6, released on February 26, 2013. The next version is currently scheduled for release at the end of this month.
Game Objects
In this article, I'm going to talk a lot about game objects. There is no shortage of terminology used to describe these things, including entities, actors, components, ... In any case, a game object is an instantiation of some base class which provides some common functionality for the game engine. In melonJS there are two methods which every game object must implement:
update()
This is the method called by the game engine when it is time for the object to update its internal state. This is usually the time for handling things like player input processing, movement, collision detection, animation, etc. Invisible objects (either explicitly made invisible or objects outside of the viewport) are never updated. This method should return boolean true to signal to the game engine that the object is requesting a redraw. Which leads us to the second required method;
draw(context, rect)
This method accepts two arguments; a
CanvasContext2d, and a rectangle indicating the area of the context to be redrawn.
The rect is only important if you are doing incremental updates, i.e. you have me.sys.dirtyRegion enabled. Since this feature is not fully implemented, it is not recommended to use, so ignore the
rect argument for now. :) [Update 2013-08-29: dirtyRegion has been removed entirely from melonJS as of version 0.9.9.)
The
context is where you can do all kinds of magic directly on the canvas using its APIs. Rotate the context, set drawing opacity, set line width, create complex paths, stroke and fill, draw images; everything that the HTML5 canvas API supports can be done at this point. And it will all nicely occur at the proper Z-level, because game objects are sorted.
The Base Classes
Any object (including an empty one) can be used as a base class for game objects. melonJS provides a few different classes that you can use directly or even extend with more functionality. The most popular of these is the
me.ObjectEntity class. This class implements methods and properties that are quite useful for platformers, such as
doJump() and
canBreakTile. These are less useful for other game genres like puzzles and shooters.
The next base classes to consider are
me.GUI_Object and the similar (but strangely very different)
me.HUD_Object. What's very special about these classes is that they both enable a special flag to inform the engine of how to interpret the position of the object when drawing (or when interacting with the object through the mouse/touch interface). The property is called
floating, and it is a boolean that determines whether the position should be treated in world coordinates (false) or screen coordinates (true). (See the section below on world coordinates vs screen coordinates.)
All of these base classes, for reasons of convenience, inherit from
me.Rect; the rectangle class. This is the same class passed to the
draw() method in the
rect argument. It handles the object's position (using a position vector; which is an object instantiated from the
me.Vector2d class), width, and height. It also has a lot of useful geometry methods for comparing and moving rectangles in 2D space.
World Coordinates vs. Screen Coordinates
"World coordinates" defines the space consumed by a full map in your game. Often, your maps will be far too large to fit entirely inside the viewport (AKA the "screen"). "Screen coordinates" defines the space that is actually visible within the viewport. The distinction becomes apparent when you consider two different types of game object that you might add to the game: An NPC object is probably going to live within world coordinate space, because it can move around within the world, and scrolls with the world as the viewport moves. And a score counter, which is probably going to be anchored to one corner of the screen, and will not scroll around with the map.
So an object using world coordinates will have its position vector interpreted as being relative to the upper left corner of the map (the "world"). And an object using screen coordinates will have its position vector interpreted as being relative to the upper left corner of the screen.
The blue arrows indicate a HUD item (lives counter) that is using screen coordinates, and the red arrows indicate the player object (Master Higgins) that is using world coordinates. The pink outline represents the viewport (the "screen") and the darkened areas are typically not viewable.
The
floating property manages all of the complexity for us; we just set it to true or false, and the engine does the rest.
Writing Your Own Game Objects
As I mentioned earlier, it is possible to use any base class to create a game object. It is sometimes desirable to extend a more heavyweight object like me.ObjectEntity, and sometimes you would rather have more control over what the object is capable of.
One particularly good example is used in the last game demo I built, Mini Sim Hotel. When you touch the screen to scroll the viewport, I have a simple indicator dot that appears where you are touching. This was really awesome for the presentation I gave, where the video was projected, and viewers could see exactly what I was interacting with.
The touch indicator was built with me.ObjectEntity, but I could have just as easily used a lower level object, like the new me.Renderable class. The update method returns true when there is a touch in progress, and the draw method draws a circle using the canvas API. That's it!
Some less obvious patterns you can use game objects for includes synchronizing melonJS with other libraries, like physics engines. I did this originally with Chipmunk-js, but this use of game objects has mostly been supplanted with the plugin API (which deserves its very own blog post!) These kinds of objects only need to implement the update method, and always return false (nothing to draw).
Another example is the
me.Tween module. This module creates private game objects to keep tweened animations synchronized with the engine. This practice could be extended to keyframe animations,
cool-down timers, and a load of other nifty utilities.
Next Steps
melonJS is still fairly young, and actively developed. But the simplicity (only two methods required for game objects) is here to stay; one of the goals of the engine is to remain very lightweight. If a feature requires additional states for a game object apart from update phase and draw phase, that feature will be scaled down or entirely redesigned until it fits neatly inside the melonJS paradigm.
In fact, I have been working on rewriting the collision detection, and struggled with a way to implement a fast multiple-phase collision detection algorithm when I literally have only one phase to work with; the update phase! It's a practice of patience, but in the end, melonJS will benefit from remaining small and nimble.
Hopefully you now have a better understanding of what game objects are and how to use them to create awesome things that may not even be visible!