2011-02-12

Game Programming With RX-JS

I'm quite excited by Microsoft's RX (Reactive Extensions) for JavaScript. I 'm also nostalgic for old school Commodore 64 games such as Bruce Lee, Archon and especially Wizard of Wor. Would'nt it be be cool to be able to play WoW(!) online.. So, why not give it a try and start writing it in RX-JS.

First, I want to be able to control the Pixel Man (his real name remains unknown to me):

1. Keyboard Control

So, I started by defining some streams. This is actually the hardest part, so bear with me for a while.

I started by constructing a ticker stream that will generate an event every 100 ms. The movements stream was created by mapping the ticker events into the current direction using CombineLatest, and filtered out the undefined values by using Where(identity). Finally, the position stream is a kind of a sum of all movements, with the starting value of startPos.

At this point I might mention that the Point class that represents position and movement vectors (x/y pairs) has some methodsfor adding and multiplication, as can be seen in the defining function of the position stream. Please have a look at the full source code.

3. Graphics

So far this has been functional programming, without any side effects or mutable state. As you can see, I'm tracking keyboard state and Pixel Man position without any explicit state variables, thanks to RX!

To make the Pixel Man materialize and start moving, we need to select some graphics framework and assign side-effects to the position stream. So, I'll start by initializing Raphael and adding the Pixel Man on a black background:

var bounds = Rectangle(0, 0, 640, 480)

var r = Raphael(10, 10, bounds.width, bounds.height);

r.rect(bounds.x, bounds.y, bounds.width, bounds.height)

.attr({fill : "#000"})

var startPos = Point(100, 100)

var man = r.image("man1.png", startPos.x, startPos.y, 40, 40)

Now that I've got the man on the stage and I've got the streams set up, I can make him move with a nice one-liner:

position.Subscribe(function (pos) {

man.attr({x : pos.x, y : pos.y}) })

4. Animation and Rotation

It was a bit dull to see the Pixel Man float around, even though he was in my control, so I added animation:

var animation = movements.Scan(1, function(prev, _) {

return prev % 2 + 1})

animation.Subscribe(function (index) {

man.attr({src : "man" + index + ".png"})})

Now that was cool, wasn't it? No poking around the code, just one new stream and a side effect. The stream maps movements in to an alternating series of 1's and 2's. The side-effect alters the image between the two png's that I've got.

Finally, I made the man look where he's going, instead of just looking right:

var angle = direction.Where(identity).Select(function(vec) {

return vec.getAngle()})

angle.Subscribe(function(angle) {

man.rotate(angle * 360 / (2 * Math.PI) + 180, true) })

Same thing here: a stream of angles and a side effect that rotates the Pixel Man.

5. Some fixing

I was happy with the simplicity and elegance of how I implemented animation and rotation. However, I wasn't so happy with the man turning upside-down when he was moving to the right.. So, I replaced the elegant code with something a little less elegant, but still quite simple:

Easy wasn't it? After some learning and having mastered the keyboard state, it was quite trivial to make the Pixel Man move, rotate and animate.

Now I have a black screen with two keyboard-controlled Pixel Men. I've played it with my daughter and she coined it the Robot Game. At 1 year and 11 months it's freaking awesome to be able to control a robot.

<1 week later>

After I wrote this, I hacked some more features into the game. Now there are two players and also some enemies there.