GTCS Game Engine:
Tutorial 1: Basic Application Structure
Tutorial 0 <-- Tutorial 1 --> Tutorial 2
Tutorial Homepage
Introduction
In this tutorial, we will look at the basic building blocks and programming structure to build games. We look at the different types of objects we use and their respective roles with games. We will also gain an understanding of the operation of our game loop.
Covered Topics: Object Types • Scene Object • Initialization • Game Loop
Demonstrations: Drawing a Renderable • Renderable with Motion
Complete source code for all tutorials can be downloaded from here.
Object Types
To begin with the GTCS Game Engine, we are concerned with four types of objects: Scene, Camera, Renderables and GameObjects. Each object type encapsulate different functionality.
- Scene: Encapsulates the logic of our game. It is the link that the game engine uses to execute the logic of our code in the form of a runloop. We covered the instantiation in tutorial 0. In this tutorial, we will cover the implementation.
- Camera: This object controls the specifics behind how the rendering engine does its job. Aspect ratio, clipping, view panning and perspective are among the things we can control with the camera.
- Renderable: These are the elements that get drawn on the screen. These elements include not only game sprites but backgrounds and text. These objects only control how an object "looks" on the screen. To control behavior and interactivity, the renderable needs to be associated with a game object.
- GameObject: Dictates how elements in our scene behave and how they interact with each other. The game object will have a renderable embedded within them. Without the renderable, nothing draws on the screen. In the same respect, a renderable without an encapsulating game object would not detect collisions with other objects or allow physics elements to be applied (such as gravity effects and elasticity).
We will look at each element by building a simple game scene to create a simple rectangle on our viewport as shown here.
Scene Object Structure
The scene is where you define what happens in your game. By overriding the engine's Scene class with the functionality you want, you will be able to add your game elements to the existing framework and create multiple game levels that . By convention, this class will be defined in a folder located within the src level, in Tutorial 0 this folder was referred to as your_game_folder.
The HTML5 engine implements a runloop. When you setup a scene object, you must adhere to a certain structure to make sure the runloop executes as expected. The structure of a scene currently consists of five parts: loading, initialization, drawing, updating, and moving to the next scene. A skeleton of the functions in a scene object are shown in the following code...
class your_game_scene_type extends engine.Scene {
constructor() {
super();
this.mCamera = null;
}
init() {
this.mCamera = new engine.Camera(
vec2.fromValues(50, 50), // position of the camera
100, // width of camera
[0, 0, 600, 600], // viewport (orgX, orgY, width, height)
2 // viewport boundary
);
}
draw() {
engine.clearCanvas([0.9, 0.9, 0.9, 1.0]); // canvas color
this.mCamera.setViewAndCameraMatrix();
}
update() {}
next() {
super.next();
}
}
window.onload = function () {
engine.init("GLCanvas");
let game = new your_game_scene_type();
game.start();
}
Note: The highlighted portions are specific to your game implementation.
- The constructor, window.onload and init() are where initialization takes place. These functions run only one time each and are where you define your variables and allocate resources.
- You update the status of your objects in the update() function. Update is part of the runloop and executes 60 times per second.
- Drawing occurs in the draw() function and occurs 60 times per second and always occurs after at least one update call has been made.
- Changing scenes occurs in the next() function. It is called one time, when you want the game to end or move to a different scene. In either case the resources of this Scene are unloaded.
Your scene subclass needs to inherit from the existing Scene object type. You must make sure that the name of this JavaScript file matches the path indicated by your index.html file from Tutorial 0.
Initialization
onload & Constructor
Before any game logic or rendering occurs, initialization occurs in three phases. First the window's onload() function is called, creating a new instance of your_game_scene_type. Then, in the constructor instance variables of objects are declared, but set to null. In the skeleton snippet above an instance variable for the scene's camera is declared. Once resources are introduced in Tutorial 2 the paths to resource files are declared in the constructor as well.
init()
The init() function is called by the engine to setup your various objects. In this example, we create our remaining core objects with simple values to aide in comprehension.
init(){
this.mCamera = new engine.Camera(
vec2.fromValues(50, 50), // position of the camera
100, // width of camera
[0, 0, 600, 600], // viewport (orgX, orgY, width, height)
2 // viewport boundary
);
this.mRenderableBox = new engine.Renderable();
this.mRenderableBox.setColor([1.0,0,0,1.0]);
this.mGameObj = new engine.GameObject(this.mRenderableBox);
this.mGameObj.getXform().setSize(10,10);
this.mGameObj.getXform().setPosition(70,70);
}
Camera and Viewport
The Camera object defines the viewport and is setup with 3 parameters. The position, the width and the location/size of the viewport.
- The position is identified with a (X,Y) vector representing the point where the camera is centered. This is in world coordinate space (WC).
- The camera width (also in WC) is the second parameter. The viewport is identified relative to the drawing canvas created in Tutorial 0.
- The orgX, orgY, width and height give the dimensions and location in pixel coordinate space for the area on the canvas that the viewport will draw to. It is not required that a viewport cover the entire canvas and you could have multiple viewports (cameras) draw to the same canvas.
- orgX and orgY are the lower-left coordinates on the canvas where you want the lower-left coordinates of the viewport to be.
- The width and height are how much of the canvas the viewport will occupy.
[Note: The camera height is not provided. The engine infers the height based on the width and the aspect ratio of the viewport.]
Renderable
The Renderable controls the "look" of objects in our game. The basic one we create in the above example renders only a solid square. We set its color to red using a "color" which is defined by a RGB+Alpha array. We cannot make interesting games if our renderables are limited to making solid rectangles. In future tutorials, we will look at more advanced renderables that can draw textures, animate and respond to lighting effects. The key take away in this tutorial is that a renderable is just for defining the appearance of the object.
this.mRenderable = new Renderable();
this.mRenderable.setColor([1.0, 0.0, 0.0, 1.0]);
These lines will allocate a new renderable object and set it's look (in this case, color). The parameters for setColor() is a vector with R,G,B,Alpha values ranging from 0.0 to 1.0.
GameObject
The GameObject is created with a reference to our renderable. We give the GameObject it's size and location in the viewport. Once the renderable is fully configured and incorporated into our GameObject, we will keep the reference in case we need to access it directly to change the appearance. Often, you will subclass GameObject to encapsulate it's renderable upon allocation.
this.mGameObj = new GameObject(this.mRenderableBox);
The size and position are in WC space with the position being the center of the object. In this example, since our size is 20x20 and position is (70,70), this will set our red rectangle from (60,60) at the lower-left corner to (80,80) at the upper-right corner.
this.mGameObj.getXform().setSize(20, 20);
this.mGameObj.getXform().setPosition(70, 70);
[Note: understanding different coordinate spaces is beyond the scope of these tutorials]
Game Loop
Updating
The update() function is called by the engine 60 times a second. This is where you will check for user interaction (i.e. keyboard presses and mouse clicks), check for collisions among GameObjects, update object locations etc. If another process or calculation delays execution, the game engine will compensate for the delay by looking at the current time and executing the update routine multiple times until updates have caught up with current time.
update() {
let xform = this.mGameObj.getXform();
if(xform.getXPos()<10){
xform.setXPos(70);
}else{
xform.incXPosBy(-0.5);
}
}
In this example, we want our GameObject to move left across our game screen and then jump back to its starting position. We have an if statements that determine when the GameObject hits the left edge. Note that we directly manipulate the transformation of the GameObject, decreasing its X position each update.
Drawing
The draw() function is called after the update cycle is complete. On an unencumbered system, this should be 60 times a second. If there are delays, multiple updates can occur before the draw routine is called.
draw() {
engine.clearCanvas([0.9, 0.9, 0.9, 1.0]);
this.mCamera.setViewAndCameraMatrix();
this.mGameObj.draw(this.mCamera)
}
In our draw function, we clear the screen, activate our camera for rendering and draw all of our objects (which is only one at this stage). We send a reference to our camera into the game object which will pull out the information it needs for behavior and send the camera to the enclosed renderable so that it can extract what it needs for drawing. Click here to see the results of drawing and updating the GameObject.
When viewing this in a web browser, you should see the following...
You will also notice that the coloring is darker than you were probably expecting. Our renderable object responds to a "virtual light source". By default, our only light source is ambient lighting and it is very dim so as not to interfere with other light sources you may define. We will look at adjusting ambient lighting in the next tutorial.
Conclusion
We have learned about the structure of the scene object and create a very basic scene with a single renderable.
In Tutorial 2, we will look at accepting user input and utilizing game resources such as bitmap images and audio. We will build on the renderable concept and look at new types of renderable objects that support bitmap textures.
Tutorial 0 <-- Tutorial 1 --> Tutorial 2