Time to get started on the core components of the game engine. We should be able to render something on the Canvas before the end of this article!
Note: please be aware that I’ll add links to specific pieces of code in the GIT repository history tree. This will make it easier for you to run the same code I did when describing a specific feature.
We’ll start by working on the classes that have no dependencies like Game, AssetLoader, and EventDispatcher then progress onto others that might extend these.
The Game class could:
accept a Scene as an optional constructor argument
keep a reference to all created scenes
have public methods for adding and removing scene(s)
have a public loop() method which will trigger the update() method on all stored scenes
have public methods that pause and resume the loop cycle; we’ll call these: play(), pause() and togglePause()
There are some improvements to be made here like checking if the added scenes are actual Scene instances, adding support for browsers that don’t have requestAnimationFrame, and maybe implementing a flux architecture to store data and prevent state mutation.
The flux architecture might come at a later time but for now, we’re treating the engine as a P.O.C. (proof of concept). That implies that I also won’t bother adding support for older browsers. But you can definitely use the rAF polyfill if you need it.
The AssetLoader class could:
be a singleton so we can retrieve the assets from any other class
expose a load method that will receive an array of paths to local assets; the method should return a Promise so we can detect when all assets are loaded
store the assets based on their name converted to camel case
support loading image and audio files
You would generally implement a getInstance() method on a singleton class, here we’re simply returning the previously created instance whenever we’re calling the class constructor.
We also have a dependency here on a Utils class, it currently just handles the asset “name to camel case” logic; it looks like this:
This one is actually a static class, and to enforce that we’re simply throwing an error in its constructor. That means trying to call new Utils() will fail. The class is fairly simple for now as it just has a single static method; we’ll probably add more later.
The EventDispatcher class could:
be a singleton, as all game classes need to be able to reference the same EventDispatcher instance. This is generally frowned upon as its basically a global event dispatcher but we’ll need it if we want to implement the flux architecture later on.
expose the on() and trigger() methods which will help us subscribe to and emit events.
You might think this one looks weirder than the rest, and there’s a good reason for that. It’s a bit tricky to extend a singleton using the ES6 syntax, read my article describing the issue here.
We’re not done yet; we still have a bit of coding to do before we can draw anything. According to our schema in the previous article we can now focus on implementing the Keyboard class which extends EventDispatcher.
The Keyboard class could:
be a singleton
expose getters which tell us if a certain key is pressed
It should be sufficient to expose getters for the keys A to Z, space, arrow keys, tab, enter, shift, ctrl, alt, esc and functional keys F1 to F12.
This class also uses KeyboardEvents, which looks like this:
This class holds some static methods which return event names that basically serve as constants. You might be wondering why I didn’t simply do:
export const KEY_UP = 'keyup';
It’s because this approach allows for the import of the class and through that class we gain access to all the Keyboard related static getters. We don’t need to import each and every constant separately.
Since we finally have the Keyboard class, we can focus on the Canvas next.
The Canvas class could:
be a singleton
be able to resize itself on window resize
allow access to the <canvas> tag and all its properties
expose the canvas ctx (the 2D context) which is used for drawing
At this point, we could draw on the Canvas directly by using the ctx property but that’s boring and silly because it wouldn’t be using any of the logic we worked so had to build in the other classes. Let’s focus on building one more class which will help us out, namely: Scene.
The Scene class could:
extend Canvas and keep a reference to itself for access in other classes that extend Scene
allow for initialization with the x, y, width and height properties which will need to have some defaults
allow for adding and removing of DisplayObjects
export an update() method which will in turn call the update() method of all stored DisplayObjects
clear itself on every update based on the provided x, y, width and height
make all drawing positions relative to the Scene instance — this means that if the Scene has { x: 20, y: 20 } and we add a DisplayObject with { x: 0, y: 0 } to it, that DisplayObject will be drawn on the Canvas at { x: 20, y: 20 }
hide any elements that are drawn outside of its viewport which is defined by x, y, width and height
provide a static wrap() method which will reset an objects x and y position if it exceeds the Scene boundaries
We have a Scene and based on its logic we can add an element to it, which will be updated and rendered… provided that the element has the update() and render() methods.
And now for the moment we’ve all been waiting for; this is how you draw a couple of moving squares the super complicated way…
Create a new folder in the js directory (on the same level as the engine directory) and name it square. Create an index.js file with this logic:
For the sake of brevity I created the Square class in the same file but we now have a basic engine for creating and rendering static or moving objects on the canvas.
The squares move on the x and y axis at a predefined speed and if you press SHIFT, that speed doubles. The objects’ position is wrapped to the parent Screen so you can play around with it. Don’t forget that you’re inheriting all the Keyboard functionality there, so have fun with that.
Obviously, there is room for improvement, like using “const” instead of “let” for all singleton instances, figuring out how to use private variables in classes that have multiple instances without causing overwrites (and if you’re thinking “WeakMap”… that will probably do more harm than good) and using a store for our game engine; just to mention a few.
We’ll continue with a discussion on how movement works in games, cover sprite sheets, sprites, animated sprites, controlling frame rate, display object pivot points, and another implementation example in the next article.