Announcing Clockwork Rumble!

Clockwork Rumble is officially the new Sileni Studios game! It’s been under development for about a month, and we are now finally ready to show it to the public! We are very excited to share the game with you.

Clockwork Rumble is a fast-paced build-and-destroy physics game, in which you play a small robot in a huge clockwork machine, and you try to protect your power source from other robots who try to destroy it.

To do this, you build a fort around your Heart using tetris-like blocks, and then shoot at the other player with rocket launchers and grenades. Between protecting your heart from enemy fire, collecting powerups, shooting the enemy and taking care of environmental hazards, you’ll be a busy little robot!

Clockwork Rumble is a fast-paced, 1v1 local multiplayer game, featuring exciting 10-minute battles on a variety of maps. Build your defenses smart, take into account the environment and think on your feet, and you will be victorious!

The following infographic further illustrates the gameplay of Clockwork Rumble:

Castle Quest Release!

After 2 years of development, Castle Quest will finally be released 13/12/2013 at 20:00 CET (Central European Time)!

We will reset the game, so everyone will start from scratch again and everyone will be equal. We hope to see you in-game in great numbers, as there will be tournaments and great prizes for the first people to found their own kingdom and conquer the mainland.

Alpha starting again Saturday 2013-08-24 20:00 CET

Castle Quest is back, with a second alpha phase starting this Saturday 2013-08-24 at 20:00 CET! This time, the alpha will be completely open, so feel free to invite any of your friends to join. You will not need a registration key to play.

After the last alpha phase, we made several big adjustments to the game to respond to the feedback from players. One of the main problems turned out to be longevity: people rushed through the game too quickly, and settled with a particular army composition they were happy with, without experimenting with possibly better compositions. To remedy this, we made several adjustments to the core gameplay:

  • Auras were removed. They were fun the first few levels, but eventually became a brainless chore, as you just replaced your worst aura by the new one. Instead, first hit now depends on the age of the creature (see next point).
  • Creatures are now born as younglings, will age as they fight and will eventually die. An average creature has a lifespan of about 30 battles, and goes through 3 life stages: youngling, adult and veteran. Adults have a higher first hit chance than younglings, and veterans have a higher first hit chance than adults. Thus, the creature age replaces the aura as the mechanic that determines who has the highest chance to attack first in combat. The fact that creatures die also pushes you to experiment with different army compositions. Summoning a creature has also become immediate, and does not require a ritual anymore.
  • Each creature is born with unique DNA. Sometimes, creatures will evolve interesting traits that give them an advantage: creatures might live longer, become a veteran faster, get first hit chance bonuses and so on. If you have summoned a creature with interesting DNA, you can sacrifice it before it dies to preserve the DNA in an amulet, which can be traded with other players. But you can also combine two amulets to create children that inherit the properties of both parents! This way, you can create powerful bloodlines of creatures that can be sold or traded for, and that can really improve your power!
  • Rituals now take a certain time to complete, instead of taking a number of battles. For example, an improvement will take 6 hours. However, when you are online during the ritual, certain events can occur that will allow you to reduce the total ritual time. Also, as a kingdom you can start boosts that further decrease the time needed for a ritual. But whether you use the boosts or not, you will still have to wait a certain amount of time before the ritual finishes.

With these changes, we hope to give the game a much longer lifespan, without annoying players or making the game boring. On one side, rituals take time, so you will not be able to clear the game in a couple of days anymore. On the other side, people who want to play a lot still have plenty to do while waiting for their rituals to finish: they can work on breeding more powerful creatures and can experiment with their army composition.

We hope to see you back in great numbers tomorrow!

Castle Quest Beta Signup!

Castle Quest is nearing alpha/beta release, so we set up a registration form for anyone interested in participating in the alpha/beta of Castle Quest! If you subscribe, we will also keep you up to date on development progress as we near the alpha test. The alpha is currently scheduled for April 2013, and we will be inviting people from this list to participate in it. Be the first to playtest Castle Quest: subscribe now!

Subscribe here!

To whet your appetite, here’s a concept art sketch of the Dragon Nether Creature from the game:

Maximal compression for Websockets

While I was working on a proof-of-concept for a real-time websockets-based browser game, I came upon this curious issue that is packet compression in javascript. Say you have a real-time first person shooter, in which the server has to send a snapshot of the server state to each client 20 times per second. This snapshot will contain the location, direction, weapon etc of each player in the game at that point.

When you compress this using JSON, you get quite a big packet to send every 50ms. JSON is a very comfortable format for sending data, but it is not a very well compressed format. This blog post explains the problem very well in great detail. Eventually, the author proposes to avoid the object hierarchy altogether, instead flattening the data into one single array, which is processed on receival. This does reduce the packet size considerably, as no field names must be stored, but it also makes working with this array very unwieldy, and prone to mistakes.

To solve this issue, I wrote a little library that converts an object hierarchy to a flat array and back again, based on a template. This will only work if your data is highly structured, but this is usually the case for packets that are sent repeatedly. This way, you only once need to define a template of the object hierarchy of your data (including arrays), and can work in your code with the original hierarchy directly, instead of the flat array.

Here is an example application:

    // template
    var template = {
        field1: 0,
        field2: [{
            field21: 0,
            field22: 0,
            field23: {
                field231: 0,
                field232: 0
            }
        }],
        field3: 0
    };

    // data
    var data = {
        field1: 1.0,
        field2: new Array(
            {
                field21: 2.1,
                field22: 2.2,
                field23: {
                    field231: 2.31,
                    field232: 2.32
                }
            },
            {
                field21: 2.1,
                field22: 2.2,
                field23: {
                    field231: 2.31,
                    field232: 2.32
                }
            }
        ),
        field3: 3.0
    };

    // flatten to single array
    console.log(PacketFlattener.flatten(data, template));

    // resulting array: [1, 2, 2.1, 2.2, 2.31, 2.32, 2.1, 2.2, 2.31, 2.32, 3]

So how does this work? PacketFlattener will traverse the entire hierarchy based on the template, and just store the data, not the field names or hierarchy. It will also store the length of the arrays it encounters, as it needs this data to restore the hierarchy later. Below you can find the code that does all this:

window.PacketFlattener = {

    flattenData: function(data, template, array) {
        if (typeof(data) != typeof(template)) {
            console.log("Found mismatch between template type " + typeof(template) + " and data type " + typeof(data) + " during flatten");
        }
        else if (typeof(template) == "number" || typeof(template) == "string" || typeof(template) == "boolean") {
            array.push(data);
        }
        else if (typeof(template) == "object") {
            if (template instanceof Array) {
                array.push(data.length);
                for (var i = 0; i < data.length; ++i) {
                    arguments.callee(data[i], template[0], array);
                }
            }
            else if (template.constructor && template.constructor === Object) {
                for (var key in template) {
                    if (typeof(data[key]) == "undefined") arguments.callee(template[key], template[key], array);
                    else arguments.callee(data[key], template[key], array);
                }
            }
        }
    },

    unflattenData: function(array, template, data, templateKey, dataKey, idx) {
        if (typeof(template[templateKey]) == "number" || typeof(template[templateKey]) == "string" || typeof(template[templateKey]) == "boolean") {
            data[dataKey] = array[idx++];
        }
        else if (typeof(template[templateKey]) == "object") {
            if (template[templateKey] instanceof Array) {
                var n = array[idx++];
                data[dataKey] = new Array();
                data[dataKey].length = n;
                for (var i = 0; i < n; ++i) {
                    idx = arguments.callee(array, template[templateKey], data[dataKey], 0, i, idx);
                }
            }
            else if (template[templateKey].constructor && template[templateKey].constructor === Object) {
                data[dataKey] = {};
                for (var key in template[templateKey]) {
                    idx = arguments.callee(array, template[templateKey], data[dataKey], key, key, idx);
                }
            }
        }
        return idx;
    },

    flatten: function(data, template) {
        var array = [];
        this.flattenData(data, template, array);
        return array;
    },

    unflatten: function(array, template) {
        var container = {};
        this.unflattenData(array, {data: template}, container, "data", "data", 0);
        return container.data;
    }
};

After performing this flattening step, you can still use any compression method to further increase the compression rate. This blog post suggests to use BISON, a compression algorithm that will work best with numbers, as it stores each number as a single-precision character. This makes it the perfect compression buddy for the PacketFlattener! Using BISON after PacketFlattener will result in very concise packets that can comfortably be sent 20 times every second.

Finally, remember that the easiest way to reduce bandwidth is to not have to send any data at all – there are many ways to reduce the amount of data that needs to be sent, such as delta compression. This blog post sums up some ways to further reduce the packet size. These tricks will work fine in conjunction with PacketFlattener.

PacketFlattener can also be found on Github.

Evo Dash released!

We are extremely proud to finally release our first game! Evo Dash is now available on iOS and Android, both for cell phones and tablets. It’s been an incredibly rollercoaster ride from the first prototype made for the Ludum Dare competition of this August to the final release on iOS and Android, and I am very happy with how the game turned out.

Visit the official promo page to buy the game!

If you like the game, do not hesitate to review it on the store, share it with your friends and spread the love! We do not have a big marketing budget so we can use all the help we can get.

Evo Dash!

If you’ve been following our Twitter, this is old news already, but it’s time to make this official with a Blog post on our website. The last month and a half, we have been busy working on a side-project called Evo Dash, as the result of Karel’s entry in the Ludum Dare August 2012 competition. You can play the original web version here.

We are now getting ready to release this game on iPhone and iPad (Android coming later), and we have also released our official trailer:

The original Evo Dash was built on HTML5 canvas, but the new version was ported to Unity, because unfortunately, Canvas is still way too slow nowadays on mobile devices. Unity allows us to use its 3D capabilities to greatly boost the graphics, and also port the game easily to other platforms.

It’s been a great ride so far, and we are very proud of the result! Once Evo Dash is released, we will go back to Castle Quest development of course; we are by no means abandoning the project!

particle systems in canvas

Particle systems are a very cheap and fun way to seriously boost the quality of your graphics. They allow you to create beautiful visual effects, such as explosions, lasers, fire, and so on. The basic idea behind particle systems is very simple: by drawing lots (hundreds or thousands) of very small (a few pixels) squares or circles, which behave individually according to some rules, you can generate a visual effect where the sum of the parts is much more than the parts separately.

For Castle Quest, we implemented a powerful particle system that allows us to make spectacular spell effects. The following movie demonstrates two creatures fighting, using their full array of weaponry to kill the opponent:

Particle systems are used to great effect in most modern-day video games. They are especially suitable to be run on the GPU, since they are the perfect example of highly parallelizable operations: all particles must be updated and drawn according to the same simple algorithm, so the GPU can take full advantage of its parallelization optimizations.

However, HTML5 canvas is not (yet) hardware accelerated, so there are some limits to how much you can do with particles in the browser, even with today’s surprisingly fast javascript engines.

A naive implementation of a particle system could draw a particle on screen with the following code:

var grad = ctx.createRadialGradient(x, y, 0, x, y, size);
var a1 = alpha;
var a2 = alpha - 0.4;
if (a2 &lt; 0.0) a2 = 0.0;
grad.addColorStop(0.0, "rgba(" + r + "," + g + "," + b + ", " + a1 + ")");
grad.addColorStop(0.5, "rgba(" + r + "," + g + "," + b + ", " + a2 + ")");
grad.addColorStop(1.0, "rgba(" + r + "," + g + "," + b + ", 0.0)");
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(x, y, size, 0, 6.2831853, true);
ctx.fill();
ctx.closePath();

This code will draw an arbitrarily sized particle with a given color to the screen as a small circle that becomes transparent near the edge of the circle (to avoid sharp edges).

This works. It’s actually pretty damn fast in Chrome: Chrome can easily render 1000 particles on screen at ~33 FPS. But in Firefox and IE, this method slows down to a crawl. A couple hundred particles will work, but any more will quickly reduce the FPS.

Because we want to at least support 1000 particles on the screen (less affects the quality of the visuals), an alternative approach was needed. It is known that most canvas implementations are faster at blitting images to the screen than rendering shapes, so we can leverage this fact by changing the way the particle system works.

Instead of drawing all particles on screen using paths, we draw all possible combinations of sizes and colors onto a hidden canvas, and just copy the appropriate part of this canvas  to our main screen for each particle. The image to the right shows an example sprite sheet for an explosion.

In order to compare the performance of the three major browsers (Chrome 17, Firefox 11 and Internet Explorer 9), I have generated graphs of the average render time for 100, 1000 and 10000 particles on the three browsers:

Chrome is clearly the winner for this benchmark. In the naive implementation, Chrome is 8 times faster than Firefox and 6 times faster than IE9. For 10000 particles, Firefox crashed once while trying to render them; probably collapsing under their weight, as it took about 6 seconds to render one frame.

At 24FPS, about 41ms of rendering time for one frame is permitted without FPS loss. Note that all three browsers stay far below this for 1000 particles with the improved method. Chrome even almost manages to render 10000 particles at 24FPS, causing only a slight FPS drop. This is quite impressive, as a couple 100 particles are already sufficient to create spectacular particle effects.

With this optimization, the particle system is performant enough to cause no slowdowns on lower-end systems, and in the three major browsers.