Raimo on Tour
Develop Motoric Games with HTML5 - The Making of VeloMaze

During the weekend I ended up joining a group called copypastel and decided to share my knowledge on game development in #nko3 competition. Despite the short amount of time and my aggressive down-scoping the project, I believe the result is worth sharing with my gaming-related and technical audience. I’m also going to share the technical background of the game and how to build this stuff on top of the various web technologies.

So what is VeloMaze? VeloMaze is maze owned by the most Node-like dinosaur, Velociraptor. Velociraptor wants the ball to move on in the maze, forever. Due to the continuum of the maze, it basically never ends. However, each time you pass a level, you are causing your next player more trouble: he or she will get another ball! Isn’t that fascinating? But that is what life in the maze is all about.

The game is especially good for groups in same place and everybody has a phone. It seems to be rather common today. Here’s also a pitch video of the system requirements of the game:

The most important system requirement is naturally accelerometer. Accelerometer is a device which measures acceleration. Devices which have an accelerometer usually return either the angle of the gravity or the gravity vector. This is possible in certain browsers, as shown in various internet posts:

and so on.

You can notice from the system requirement video that some laptops have an accelerometer too. It is included in fairly new MacBook Pro laptops (mine is from 2009 and it has one) to prevent hard drive breakage when you drop the device. I believe laptop-turning-based gaming is a field many people haven’t stepped at yet!

The technologies used in the game were: Node.js, express (serving static content), Socket.io (letting the client and server communicate about ball transfers back and forth), Sylvester (a vector library for the physics engine). The following diagram demonstrates how these technologies were glued together.

Development of the game itself was pretty easy, however full support for all the possible browser and accelerometer combinations would have been much more work than the 48 hours our team had. Therefore testing with newest Android, for example, was not done so I was positively surprised to realize that it works really well! But luck is only one of the key factors in success. In the following paragraphs I’m going to explain how the gameplay was built and what actually makes the application playable.

Reading the accelerometer is quite simple, but a bit cumbersome and the lack of standards is making it a bit harder than it should be. After trying to look for how to accomplish the task with various different platform and browser combinations in addition to having quick polls inside our team on what to support, I ended up with the following:

if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', function(e) {
leftRightAngle = e.gamma /90.0*Math.PI/2;
frontBackAngle = e.beta /90.0*Math.PI/2;
}, false);
} else if (window.OrientationEvent) {
window.addEventListener('MozOrientation', function(e) {
leftRightAngle = e.x * Math.PI/2;
frontBackAngle = e.y * Math.PI/2;
}, false);
} else {
setStatus('Your device does not support orientation reading. Please use Android 4.0 or later, iOS (MBP laptop is fine) or similar platform.');
}

The result works with a reasonably new Chrome and according to some people it also worked with a relatively new Safari on iOS (but not with the Safari I had at hand). I decided to avoid looking for a solution which would allow reading the accelerometer in all the possible browsers due to the fact that the coding part of Node Knockout was only 48 hours and the game mechanics were still unwritten.

I decided to use Sylvester, a vector and matrix math library for the collision detection. I could have used Box2D JS to save some time, but due to my existing experience with Sylvester and the simplicity of the needed collision tests, I used Sylvester. The code for testing if balls falls into the hole looks like this:

function checkBallHole(ball, hole, dropped) {
    var holeVector = $V([hole.x, hole.y]);
    var ballVector = $V([ball.x, ball.y]);
if (ballVector.distanceFrom(holeVector) < hole.r) { dropped(ballVector); } }

So nothing really complicated here: if your ball center is inside the whole, the callback “dropped” gets called. This code is run on every frame, so everybody who has developed games in their past knows that this implementation allows the ball to go over a hole during a frame. However, this is not a problem as you can actually get a ball over a hole also in real life if you push it fast enough.

The game has also walls, so the collision with those needs to be detected too. Sylvester provides a way to calculate the distance from a line segment, which I used in this case. The code in all its simplicity is below.

function impactBallByWall(ball, wall) {
    var ballVector = $V([ball.x, ball.y]);
    var wallSegment = Line.Segment.create(
$V([wall.sx, wall.sy]),
$V([wall.dx, wall.dy])); var collisionPoint = wallSegment.pointClosestTo(ballVector)
.to2D(); var dist = collisionPoint.distanceFrom(ballVector); if (dist < ball.r) { var inverseMassSum = 1/100.0; var differenceVector = collisionPoint.subtract(ballVector); var collisionNormal = differenceVector.multiply(1.0/dist); var penetrationDistance = ball.r-dist; var separationVector = collisionNormal.multiply(
penetrationDistance/
(inverseMassSum)); var collisionVelocity = $V([ball.vx, ball.vy]); var impactSpeed = collisionVelocity.dot(collisionNormal); if (impactSpeed >= 0) { var impulse = collisionNormal.multiply(
(-1.4)*impactSpeed/(inverseMassSum)); var newBallVelocity = $V([ball.vx, ball.vy]).add(
impulse.multiply(inverseMassSum)); ball.vx = newBallVelocity.e(1); ball.vy = newBallVelocity.e(2); } } }

I made several false (but close enough to the truth) assumptions when developing the ball and wall collision. First of all, the walls have zero thickness (instead of actual 5 pixels) and again, I am NOT calculating what happens between the frames. This obviously leads to the problem that balls are able to get through the walls in the game. This could be tested fairly easily by creating a line segment of the ball’s movement during the frame and figuring if the ball delta segment intersects the wall segment. Then one would have to calculate the position where the ball would have collided with the wall. In the code snippet above, it would be then assigned to the variable “collisionPoint” (see the picture below).

I am a big friend of Canvas and WebGL, but we planned to use DOM and jQuery for rendering because we didn’t need any of the effects in canvas or WebGL apart from rolling the ball (which would have been quite neat to do, bummers!). Using the DOM for rendering the scenery made scaling a bit hard, but other than that it was really easy to implement. I wrote the following function for drawing any sprite in our game.

    setElementPosition: function(element, sprite) {
        sprite.width = (maze.getSquareWidth() * sprite.r * 2);
        sprite.height = (maze.getSquareHeigth() * sprite.r * 2);
        var x = sprite.x;
        var y = sprite.y;
        var newLeft = (x * maze.getSquareWidth()  - element.width() / 2.0);
        var newTop = (y * maze.getSquareHeigth()  - element.height() / 2.0);
        if (thresholded(element.css('left') - newLeft, 5) !== 0) {
            element.css('left', parseInt(newLeft) + 'px');
        }
        if (thresholded(element.css('top') - newTop, 5) !== 0) {
            element.css('top', parseInt(newTop) + 'px');
        }
        element.css('width', sprite.width + 'px');
        element.css('height', sprite.height + 'px');
        element.find('div').each(function () {
         $(this).css('width', sprite.width + 'px');
         $(this).css('height', sprite.height + 'px');
        });
        element.find('.location').html('('+parseInt(sprite.x*10)/10.0+','+parseInt(sprite.y*10)/10.0+')');
    },

I made a real-time scaling for the viewport of the game, which is why the width and height are calculated on every frame. That is unfortunately not visible in the game, because of the failed attempt to control browser rotation programmatically (there is no interface for that, so it needs be hacked). So we just ended up instructing people to turn off the automatic browser rotation in their phone, as shown in the image below.

So all this accelerometer reading, physics engine running and DOM rendering is then wrapped in a main loop. I placed all the main loop code in a function “update” and call it every 100 milliseconds (I know, this is not often enough, but it looked good on my machine so forgot the value there) like this.

  window.setInterval(function() { update(); }, 100);

The full source code of the client can be found here.

BTW, I am very disappointed that the new Retina MacBook Pros do not have an accelerometer (like one of our players reported), because they have SSD drives with no moving parts (http://support.apple.com/kb/HT1935)! So it might be the industry of laptop-turning-based games is coming to an end…  - @raimo_t

  1. raimo-t posted this