Dissecting the Liberated Pixel Cup Demo
Jonathan, July 10th, 2012
The Liberated Pixel Cup Demo (LPCD) was written by yours truly over the course of two weeks, prior to the art phase of the Liberated Pixel Cup contest. The demo had several intended purposes. First, to test the usability of the base tile set for building levels. Second, to show character sprites interacting with environments and to demonstrate animations. And third, to inspire. As there has been some interest in the construction of the demo, this article is an overview to how the demo was constructed. Before I go into any detail, it is worth noting that this demo was put together without really knowing how much time would have been available to work on it. Because of this, the demo progressed through several stages – each playable and a plausible endpoint – before arriving to what it is today. This is reflected in a few places in the source code, either in code that was written with the best of intentions or in code that was written to be the foundation for something that never came to be.
Here’s what I usually do:
I start by defining a dummy module using the object notation (I call this the ‘header’). Then I monkey patch all of my functions into it. As I add function definitions and the like, I update the module to reflect the expected structure. Function stubs have comments next to them outlining the expected arguments. I don’t use a closure to fake a private scope for the module. Instead, the module is organized to keep calls, callbacks, and different sorts of data separate. It makes testing your code much easier. If you want to scare people from touching something, throw some underscores in front of its name.
The program itself is split into several files, grouping code more or less by purpose. Header.js contains the module object definition, and the starting point of execution for the game engine. All of the remaining files are appended to the end of this file (the order doesn’t really matter). Assembly of the program (as well as minification) is automated via a make file.
The advantages of using this organizational scheme are:
- The header provides a simple reference and easy visualization of the program’s structure.
- Doesn’t do anything clever with language features to make it work.
- Looks cleaner to me.
The only disadvantage I can think of is that the header must be maintained as the program is written. It isn’t easy to tell if the header is maintained well, since the program can still run if function stubs are missing or some of the variables aren’t defined.
Levels are built using the program Tiled, with the level data exported to json. The levels are tiled on a 32×32 grid, which turned out to be a mistake. If I wrote this again, I would go with a 16×16 grid instead, to simplify the conversion of world coordinates to and from screen space coordinates. This is explained further in the section about the physics engine.
Tile boards are rendered upon two html5 canvas elements; an iframe between the two is where the actors are drawn. Level data may contain more than two layers, but will be automatically flattened into two layers when the level is rendered. Actors are represented with div elements; css is used to crop and position them. For actors inheriting from VisibleKind, Z-index is used to do depth sorting, which is why the actors are in an iframe. Depth sorting behavior is done on the actor’s _dirty method, which may be overridden.
Physics information is stored on a 16×16 conceptual grid. Originally, this was to be 32×32, but proved to be a mistake: in some cases, this would prevent the character from walking right up to the edge of something. Because many hours of work already spent building levels would be lost by making the whole engine use a 16×16 grid, I opted for a flimsy workaround. Physics info for tiles is now one of A, N, NE, E, SE, S, SW, W, NW; which describes the wall coverage in a given graphical tile’s conceptual subtiles.
Actors that prototype AnimateKind (which also happens to be the actors which can be the focused player) have a _move_to function that initiates the walk cycle. The walk cycle function is probably the most complex singular part of the game engine. This is in part due to the fact that the character’s coordinates are floating point values, not array indices. A good chunk of this code is used to make sure the character doesn’t appear to be walking through walls when cutting around a corner; this had the added side effect of the movement trajectory appearing to be adaptive to obstructions despite the lack of a real path finding algorithm. Part of the complexity of this function also comes from the fact that it is possible to call events on other actors when colliding into them.
There is an inheritance chain used in creating an actor, allowing different engine features to be implemented on the actors themselves while keeping the code isolated. This means that the code for things like human characters, monsters, treasure boxes, and etc are all responsible for rendering themselves in the graphics engine. These actor type constructors can be found on the header object in LPCD.ACTORS, and defined in the file actor_model.js. For the most part, these constructors are fairly concise, with the exceptions of VisibleKind and AnimateKind.
All actors inherit from AbstractKind. The most important aspect of this actor is the variable “_binding”, which determines if an actor is cleared from memory or not when a new level is loaded. This allows focused actors to travel from level to level. There was going to be a feature for persistent actors, allowing for things like items and treasure, though this was never implemented. Thus, PersistentKind exists, though I don’t believe anything actually uses it.
VisibleKind inherits from AbstractKind and is used to provide a presence for the actor in the graphics engine by creating a div element and inserting it into the iframe used to display actors. This object also provides world coordinates (since they’re needed for drawing) to the actor. This object does not make an actor responsive to collision detection.
AnimateKind inherits from ObjectKind. It provides the _gain_input_focus function, directional facing information, a _look_at function, and the walk cycle via the _move_to function. This does not implement any animation features, but is simply for animate objects. CritterKind and HumonKind both inherit from AnimateKind and implement animation specific features.
LEVEL SCRIPTING AND CHARACTER DEFINITIONS
Level scripts are found in the dynamics folder, and have the file name of the level they correspond to + “.js”. So for example, the starting level’s file name is “start1.json” (level data is found in the levels folder. I do not recommend viewing it via web browser), the corresponding dynamics script is “start1.json.js“. To make it easy to clean things up when the level changes; when the level is loaded, an iframe is created and the level dynamics script is loaded within that iframe. It is given access to LPCD.API via a global variable named API; but is left blind to the rest of the engine. This allows us to dispose of the script easily by deleting the iframe.
An amusing side effect of this is if you define within a dynamics script an actor that inherits from AnimateKind, and change your input focus to this new actor and leave the level; the object for the actor remains, but none of its member functions may be called anymore. However, anything in the prototype chain still works fine provided that it was defined in the engine itself. Because of this, characters.js is used to define game-specific characters and useful objects outside of the levels and instance them from the level dynamics script via the API.instance function. Because the code was defined outside of the level, the object remains functional after the level has been flushed.
Conveniently, this behavior is consistent between Firefox and Chrome. If this behavior for scripts in iframes is standardized, I imagine this was never an intended use case.
Overall, I’m quite pleased with how the demo turned out. There are some rough spots where it isn’t clear where things are happening (eg, flushing the level actors by changing the innerHTML property of a DOM element), which I had forgotten about prior to writing this article. Despite that, I think the code is pretty usable as a game engine, and should still be fairly easy to extend. Hopefully this article serves as a guide for others to tinker with the engine, to use the code in their own projects, or even to study in building something entirely new.