I wrote a version of the Arcade classic Centipede using AngularJS and the HTML5 canvas. You can play the game here and view the source code on GitHub.
This blog post is about my experiences writing this game using AngularJS. The problems I overcame, lessons I learnt and the fun I had.
Note that all of the code for the game is available on GitHub for you to take a look at.
The first thing we’ll look at is the index.html – the only page in the game. It is pretty simple and looks as per below…
..with various other script tags following on from this for each code file in the game.
I’m not going to go into too much detail about AngularJS as there are lots of other resources on the web that already cover that, so we’ll just go with a quick summary of the above.
As per most AngularJS pages I have defined my ng-app at the top (gameApp). I also have one ng-controller (appController) and one directive (centipede-game).
The appController code (shown below) very simply handles the transition from the introduction (instructions) screen to the main game. As soon as the user presses a key then we set the $scope.instructionsDisplayed to true. Any further keypresses are then passed off to the keyPressHandlerService – More on this later!
Once the instructions have been displayed, and dismissed via the key-press, then the centipede-game directive is displayed on screen.
This directive is the gateway into the game.
You can see that the template just adds an instance of the HTML5 canvas element to the page, however the link function is the main area of interest as this handles our ‘game loop’. There are various (no doubt better) ways to program the game loop, however I have kept this implementation quite simple. Every 50 milliseconds the gameLoop method is invoked and the ‘animation’ variable incremented, continuously looping from zero to three. This animation value is then passed into our gameService and renderService to handle all the game logic and screen rendering respectively. (More on this later.)
One final point to note about this directive is I’m being a good boy, ensuring the promise created by the $interval service is correctly disposed of when the scope is destroyed. 🙂
I don’t intend to go through every line of code in the code base – you’d soon get bored – So I’ll just go over some of the key parts to the code and let you browse the GitHub code at your leisure.
The full code base is shown below…
The gameApp.js you have already seen. This defined the gameApp angular module along with the sole directive and controller in the app.
The constants folder contains all the ‘constant services’ in the game. Generally speaking these are pretty simple areas of the code, for example the characterDirection constant service defines each direction the character can move in. As is the nature of an AngularJS constant service, there isn’t really any logic to see!
The sprite.js file is worth a mention though – This contains a reference to every sprite in our sprite sheet. The first sprite is a ‘red mushroom’ at 100% health, then one at 75% health etc.
Whenever we want to render a particular sprite to the screen we reference it as defined in sprite.js. For example, if we wanted to render the full strength purple mushroom at screen co-ordinates 100, 200 then we would use the below code.
This leads us nicely on to our graphicsEngineService which is the first of the ‘Angular factory services’ we will look at.
The graphicsEngineService holds a reference to our HTML5 canvas and handles all the rendering to that canvas – any text that needs to be displayed and/or sprites from our sprite sheet.
Let’s take a look at the drawImage function. A function that allows us to render a particular image at a specific location (x and y co-ordinate).
The first thing to note is we check whether the caller is passing us ‘screen’ or ‘world’ co-ordinates. This is a common concept in games development. If working in ‘screen’ co-ordinates then we are working on a pixel by pixel basis, however if working in ‘world’ co-ordinates then we are working on the basis of each mushroom/player/enemy taking up one space on the board. As we need to work in pixels when accessing the HTML5 canvas then we convert any world co-ordinates to screen co-ordinates.
Finally we just need to render the chosen image (sprite) on to the canvas. The canvas drawImage function handles this for us – all we have to do is pass in the following parameters…
-  A reference to our sprite sheet
- [2, 3] The location of the sprite, within the sprite sheet
- [4, 5] The sprite width and height as defined in the sprite sheet
- [6, 7] The location on screen we wish to render the sprite
- [8, 9] The size to render the sprite as on screen (In this case the same size as defined in our sprite sheet, however we could alter these values to stretch or shrink the image).
For those that don’t know about sprite sheets I refer you to the following video by Code And Web (The makers of TexturePacker)…
SpriteSheets – TheMovie – Part 1 by CodeAndWeb
So, assuming you are still reading this, I’m guessing you have already tried playing the game? As such you will notice the various characters on screen ‘animate’ as they move around – for example the centipede’s legs move back and forth. Also you may have noticed that the characters transition (relatively) smoothly from one location to the next as opposed to suddenly jumping to the next location. This is where the ‘animation’ variable in our centipedeGame directive (mentioned earlier) plays its part…
Every iteration of the game loop (every 50 milliseconds), we increment our animation count by one – looping from zero to three and then back to zero again. Think of this as the ‘frame count’ for each movement sequence – where there are four frames for each movement. So, if the spider moves left one space then this is handled over 4 frames – each frame the spider will move an extra quarter of the distance to the desired location. Combine this with displaying a slightly different graphic for each of the frames ensures that the movement is smooth and the spider looks like it is walking. Although this has simplified the explanation, this is pretty much what is happening!
Similarly the other moving entities in the game (the player, bullets fired, centipede, flea and snail) are handled in the same way – The only difference might be the speed they travel. The player moves twice as fast as the spider, moving to its new location within 2 frames, whereas the bullets travel an entire space each frame.
Let’s take a look at how this works…
Earlier we saw our game loop within the directive.
This passes the animation into the gameService.update() function. If you take a look at that code then you’ll see there is a check on the current state of the game – e.g. are we handling the ‘player death sequence’ or a ‘transition to the next level’. Assuming we are in ‘normal game mode’ then we ultimately call the moveCharacters() function, which in turn calls update() on each ‘moving character’ service…
Thus each service can handle the logic for updating its own state itself – e.g. the bulletService will move all the bullets and check for collisions, the centipedeService will move all the centipedes, the fleaService will move the flea down, or if the flea doesn’t exist then perform a check to see if it should be created.
At this stage we are not rendering anything to the screen, however this is immediately handled after all services have handled their above movement phase. Taking a look at the gameLoop you can see the call to the renderService.draw() function…
…which simply offloads the rendering to each service…
Again you’ll notice that the ‘animation’ value is passed into the draw method. This is so that each service can determine how far the character(s) have moved in this animation sequence and thus position the correct sprite at the appropriate location. For example the snailService will use a different sprite depending on the animation value – and increase the x co-ordinate more for each animation, thus animating the snail across the screen.
(Note: You will also see that two images are drawn because the snail is bigger and takes up 2 sprites.)
Of course, you could use a game engine like Phaser and write a lot less code and produce something much quicker – it depends what your goals are – for me this was about playing with AngularJS and seeing what I could do with it and the HTML5 canvas element.
Now, one thing we haven’t talked about yet is how to control the player. Interestingly this is an area where I encountered a few problems!
To control the player I decided to keep it simple – arrow keys for moving and the space-bar for firing. If we take a look at the index.html then you’ll see I am using the AngularJS ng-keydown and ng-keyup built in directives to capture the keyboard interaction.
And, looking in the controller, you may recall from earlier, we are simply handing this information off to a specific service (keyPressHandlerService) to handle the keyboard interaction. That same service can then be queried by the playerService to determine which direction the player should move.
I haven’t included the code from the keyPressHandler service here because it is actually quite long and more complex than one would have hoped! When I first started implementing the game I just monitored the keydown event and moved the character whenever it fired, however this meant a very ‘user unfriendly experience’ because holding down the left arrow (for example) would move the character to the left once, then pause, and then continually move the player left. This is because there is a delay between the first keydown event and the second, but then all others come in quick succession. Try it now for yourself in any input box or word processing app etc – you will experience the same behaviour. What I wanted was for the character to continually move all the time the key was pressed. I therefore took a slightly different approach of tracking all the keys currently being pressed, and then on each game loop, we could check which keys are being pressed.
That almost gave me the final solution – I just had one more issue – If the user quickly pressed and released a button then the keydown and keyup would fire before the playerService had a chance to check what key was being pressed, so I had to also track whether the key press had been handled. If on keyup it had not been handled by the playerService then the keyPressHandlerService would keep track of that keypress until it had been handled by the playerService.
One good thing about this though is that I took the opportunity to learn a bit about AngularJS unit testing using Jasmine! You’ll see I have a Tests folder in the solution with a SpecRunner.html page which runs all the tests in my KeyPressHandlerSpec.js file.
Now, if I was using a game engine like Phaser, then this is the sort of thing that is completely handled for us – However my intention was to write this purely in AngularJS without any game engines – and that I achieved! 🙂
With more experience, and the determination to write this blog post, I revisited the repository in October 2014 and converted it to be 100% AngularJS – and although the repository is by no means a master class in AngularJS, it is certainly a lot neater than it was – which I believe is a testament to how great AngularJS is!
So, would I recommend writing a game in AngularJS with no game engine?
Answer: It depends on your goal!
If you want to produce a HTML5 game quickly (e.g. you are entering a Ludum Dare type game jam) then I’d probably recommend using a game engine like Phaser. Phaser would let you concentrate on the game without worrying about handling keypresses – You wouldn’t even have to worry about things like collision detection as there are built in physics engines within Phaser.
However if you want to get involved in every low level aspect of the game then, as this blog post shows, it is possible to complete a game using just AngularJS and no games engine.
In the future I see myself using Phaser when time is of the essence (e.g Game Jam Event) – However I certainly see myself taking a similar approach to this blog post again, as you ultimately have more power over the mechanics of the game – and indeed I could reuse some of the logic from this game for future games anyway.
My next blog post talks about an extra direction I went with this project – Still using AngularJS, but converting the game to run as a Windows Store App.
- If you want to learn how to do HTML5 games from scratch properly then I would highly recommend Jake Gordons web site Code inComplete
- If you want to read about HTML5 game development in general then I’d recommend signing up to the GameDevJSWeekly mailing list.
- If you want to learn about AngularJS then there are loads of resources out there now, however my favourite is the Dan Wahlin weekly newsletter.