I recently felt the urge to take some time off from the long-term game project I had been working on for most of this year, so I decided it would be fun to have a little weekend hackathon focusing on a small project. After the recent game-jams I had participated in as part of a group, I wanted to work through this one on my own, without any real constraints with regard to the game format or theme. So, I set out to spend the majority of my free time last week, all in all about 4 days, making a game.
I have been a fan of the Total War series for years now, and it just happens that my all-time favorite entry, Medieval, is no longer playable on most modern machines. I remember the game had a reputation for being buggy back when it came out a decade ago, and time has certainly not improved this. I’m sure there are ways to make it cooperate via wine or something similar, but needless to say, it won’t just run on its own any more. Thus, I figured it was time to recreate the game experience.
In the week leading up to this, I had done some prep work; for example, I drew up a map of Europe to be used as the “board” of the game. Early on, I made the decision to go for a pixel / 8bit look, since that would allow me to crank out usable, if not final, art assets without loosing too much development time.
I also decided to use the Crafty.js framework, which had served me more or less well on prior occasions. For the uninitiated reader, Crafty is a component-based JavaScript framework for use with HTML5 / canvas, and I’ve found it to be pretty quick to pick up despite the lack of documentation. In most cases, their mailing list provided answers to any problems I encountered.
As is usually the case with projects like this, time is the enemy. With every day that I was working, plenty of new ideas came to me, but at the same time, I began to realize how much less I was going to be able to implement given the constraints I had. In the end, the biggest missing pieces were the lack of AI, which I hadn’t realistically expected to implement, and the severely underdeveloped combat mechanics and in-game economy. Still, overall I enjoyed the experience, and felt that I was able to accomplish a fairly basic, but still playable game. If you care to check it out, it is available online and should run fine in both Firefox and Chrome.
Some observations and problems I encountered during the hackathon:
Scaling
Since I was going for the 8-bit look, initially I targeted the classic VGA resolution of 320×200. I was hoping to just be able to scale up the whole game by a factor of 4, thus ending up with 1280×800 to be displayed on a modern screen. The results of this, however, looked pretty terrible. The default scaling in Crafty (which depends on the browsers’ scaling) uses interpolation, which introduces artifacts into pixel assets and also muddles the colors. Since there is no way to turn this off, I ended up having to scale all images by hand in GIMP. This was obviously not an ideal solution, as it not only increases the time effort, but also the file size and memory use at run-time.
Rendering to Canvas
After some experimenting, I was able to create components to render directly to the canvas. One use for this, is to replace a specific color in a entity on screen. This is, of course, one of the older tricks in the book, allowing you to reuse sprites with different color sets. The following component replaces all pixels that are of “src” color with “dest”:
Crafty.c('FactionColor', { src: [255, 0, 255], // color to replace dest: [0, 0, 0], // color to insert init: function() { this.bind('Draw', function(e) { // get image data for this entity var img = e.ctx.getImageData(this.x, this.y, this.w, this.h); // replace each pixel that matches src with dest for (var i = 0; i < img.data.length; i += 4) { if (img.data[i] == this.src[0] && img.data[i + 1] == this.src[1] && img.data[i + 2] == this.src[2]) { img.data[i] = this.dest[0]; img.data[i+1] = this.dest[1]; img.data[i+2] = this.dest[2]; } } // write back image data e.ctx.putImageData(img, this.x, this.y); }); } });
Rendering Text
Another problems I ran into was font rendering: for some reason, I couldn’t get them to render properly as canvas elements in Firefox, so I had to create them as part of the DOM. The following component displays a text over an entity, optionally only showing it on mouse-over:
Crafty.c('TextOverlay', { _text: null, init: function() { this.requires('Mouse'); this.color = this.color || '#ffffff'; this.fontFamily = this.fontFamily || 'Century,System'; this.fontSize = this.fontSize || '24px'; this.fontWeight = this.fontWeight || 'bold'; this._text = Crafty.e("2D, DOM, Text") .attr({ x: this.x, y: this.y, w: this.w, h: this.h, z: this.z, visible: true }) .textColor(this.color) .textFont({ family: this.fontFamily, size: this.fontSize, weight: this.fontWeight }) .unselectable(); this.attach(this._text); this.bind('MouseOver', function() { if (this.hover) { this._text.visible = true; } }); this.bind('MouseOut', function() { if (this.hover) { this._text.visible = false; } }); return this; }, text: function(value) { this._text.text(value); this._text.visible = !this.hover; return this; } });
Leave a Reply