This tutorial demonstrates how to build a game using the engine, but it's highly abstract, most parts need to be modified according to your own game.
On your HTML page, include the engine JavaScript, then use a wrapper such as div, to wrap a canvas, and make sure that your wrapper has an id of "game", and your canvas has an id of "canvas", other wise the engine won't operate as planned; of couse, you need to include your game specific JavaScript as well, it's recommended to include it in the body rather than in the head.
<head>
……
<script src="engine.js" type="text/javascript"></script>
……
</head>
<body>
……
<div id="game">
<canvas id="canvas"></canvas>
</div>
<script src="your_game.js" type="text/javascript"></script>
……
</body>
Then in your game script, you can wirte a subclass of Game to start.
class YourGame extends Game {
……
}
var game = new YourGame();
game.start();
Now it's time to start working on your game.
Your game must consist of something, right? Thus you need to declare them first in the game constructor. You also need to define how to load and deload everything.
class YourGame extends Game {
constructor(){
super(60); //Set framerate to 60 Hz.
this.something = null; //Your objects.
}
load(){
this.something = new Something(); //Initialize your objects.
}
deload(){
this.something.destroy(); //Destroy your objects.
this.something = null;
}
}
A real game will look more complicated than this, but you get the idea. Make sure you destroy or recycle your stuff on deload, it's important.
A game is basically a running loop, you need to update your objects according to your game's logic, and eventually draw them out. So that's what you need to do in this step,
class YourGame extends Game {
……
update(){
super.update();
this.something.update();
}
draw(){
super.draw();
this.something.draw();
}
……
}
The original Game update() and draw() are important in loop management, make sure you invoke them first in your overrode version. A real game's update() can be much more complicated than this, but draw() is usually simple, you sould keep rendering details in your object classes.
Similar to your game, objects in it needs to be initialized at the start, updated and drawed in every game loop, and eventually be destroyed.
class Something extends Sprite {//Sprite and its many subclasses are the most used ones, if you want to have more flexibility, inherit GObject.
constructor(){
super(x, y, w, h);//You should use actual numbers here, to define its location and size.
//Define attributes
}
update(){
super.update();
//Do stuff
}
actual_draw() {
super.actual_draw();
//Do stuff
}
destroy(){
super.destroy();
//Do stuff
}
……
}
One thing to note here is that you should always override actual_draw() rather than draw(), because classes higher in the inheritance tree does rotation for you, which need to do stuff before and after actual_draw(). Also, keep in mind that you need to invoke the inherited versions of functions first, but if you do need to completely rewrite one or two, read the source codes of engine first to ensure that you won't break something.
This tutorial demonstrates how to use engine built-in collision detection, but it's highly abstract, most parts need to be modified according to your own game.
Collision detection are provided by CollidableGObject class.
Collision detection are provided by CollidableGObject class.
class Something extends Sprite{
……
move_prediction() {
return new Something();
//You should return a instance that has the intended position and velocity set,
//so the engine could invoke the specific collision determination function of this class,
//this instance will be destroyed by engine automatically after that.
}
collides(CGO) {
super.collides(CGO);
//Do stuff that determines whether it collides with the CollidableGObject passed in, your return value should be boolean.
//Sometimes you will probably need to rewrite this function without calling super function.
//For most of the situations, you don't need to add new codes to or rewrite this function.
}
resolve(collision) {
super.resolve(collision);
//Do stuff that handles the collision, this is a instance of class Collision.
//Check out class Collision's source codes for more detail.
}
……
}
CGO_update() is a static function of CollidableGObject, it uses quad-tree and other acceleration techniques to discard unnecessary collision checks.
class YourGame extends Game{
……
update(){
……
CollidableGObject.CGO_update();
……
}
……
}
One thing you need to pay attention to is that your should invoke CGO_update() before any CollidableGObject actually updates in your game update. If you do have all the logic done correctly, the collision detection should be operating smoothly.
This tutorial demonstrates how to use engine built-in input listening, but it's highly abstract, most parts need to be modified according to your own game.
This step doesn't need to inherit any class, it's a composition. Just keep a reference of engine's input manager, and the subscriber index of your object.
class Something{
constructor(){
……
this.iesm = input_event_subscription_manager;
//input_event_subscription_manager is a global variable of engine, but it's not necessarily always the same one, it can be swapped.
this.si = this.iesm.add_subscriber(this);
……
}
destroy(){
……
this.iesm.remove_subscriber(this.si);
this.si = null;
this.iesm = null;
……
}
}
Aside from subscribe, remember to unsubscribe when you destroy the object.
The engine's input manager will always be invoking its subscriber's handle_input_event() function.
class Something{
……
handle_input_event(event) {
switch (event.type) {
case IEType.……://IEType is a enummeration object constant in engine.
try {
this.input_event_subscription_manager.set_exclusive(this.si, IEType.……);
//Do something about the input
this.input_event_subscription_manager.release_exclusive(this.si, IEType.……);
} catch (e) {console.log(e);}
break;
}
}
……
}
There is a set_exclusive() and release_exclusive() process, the former tells the engine that it's the only subscriber that handles the certain type of input, until it releases the lock. This provides some flexibility, for certain reasons you might need to not release lock until some conditions meet, or maybe you don't need to set lock becuase various instances needs the same input.
For more details and usage, check out the engine source code at here.