GTCS Game Engine:
Tutorial 2: Resources & User Input

Tutorial 1 <-- Tutorial 2 --> Tutorial 3
Main tutorial guide page


Introduction

In tutorial 1, we looked at creating the basic structure for our game scene. We created a camera object and a game object with a simple renderable. In this tutorial, we will look at resource management, a more advanced renderable type and user input.

Covered Topics: Loading ResourcesKeyboard inputAudio & MouseText

Demonstrations: Drawing with a TextureKeyboardAudio & MouseText Renderables

Complete source code for all tutorials can be downloaded from here.


Loading Resources

Images, audio and other file types are the resources that your game will use. We make requests to the engine to load files in loadScene() by providing the file paths where the files can be located. The engine will asynchronously load the files into RAM and provide a means of accessing them so we can start using them. The loadScene() function is always called prior to initialization() and you are guaranteed that the resources have fully loaded when initialization() is called. Once the resources are loaded, different classes of objects will retrieve resources as needed by providing the string path to the resource. These variables identify each unique resource.

As a matter of convention, the game engine has a folder in it's hierarchy called "assets". While you can technically use any folder to store resources (as long as the path is properly referenced in the loading and unloading), we will use this folder to store our resource files. This will keep things consistent and organized.

[Note: The assets folder is also used by the game engine for it's own assets such as default fonts. Do not delete, move or modify the existing files or hierarchy.]

Constructor

To facilitate the loading of resources, we create constants to identify the file paths for our resources and provide a consistent way for referencing the resource. By convention, we store resource files in the "assets" directory of our game engine file hierarchy. The game engine supports PNG image files, WAV audio files and text files. We are going to load a 64x64 pixel image to use as a texture for drawing our game element.

Figure 2-1: Texture

[Note: When creating resources for texture maps, the dimensions must be on the order of perfect powers of 2. Examples of usable image sizes include 64x64, 512x2048 or 16x128.]

function MyGameScene() {
	this.mCamera = null;
	this.mRenderable = null;
	this.mGameObject = null;
    
	this.kTexture = "assets/minion_portal.png"
};
gEngine.Core.inheritPrototype(MyGameScene, Scene);
Code snippet 2-1: Scene constructor

The code above declares three instance variables that will be used in our scene as well as a string constant that identifies the path to an image resource file.

loadScene() and unloadScene()

The loadScene() function will queue our texture for loading by using the gEngine.Textures.loadTexture() function. There is also gEngine.Textures.loadAudio() function for loading audio files and a gEngine.Textures.loadTextFile() function for loading text files. The files we identify will load asynchronously. We parallel the loading of resources with the cleanup and deallocation of resources in unloadScene() which will be called on program exit.

function loadScene() {
	gEngine.Textures.loadTexture(this.kTexture);
};

function unloadScene() {
	gEngine.Textures.unloadTexture(this.kTexture);
};
Code snippet 2-2: Load and Unload

initialization()

The initialization() function is mostly the same as it was in the previous tutorial. Now we create a TextureRenderable instead. This new renderable has all of the same functionality as the Renderable but allows us to utilize a bitmap image instead of using a solid color.

MyGameScene.prototype.initialize = function () {
	this.mCamera = new Camera(
		vec2.fromValues(50, 40),    // position of the camera
		100,                        // width of camera
		[0, 0, 500, 400]            // viewport (orgX, orgY, width, height)
	);
	// set the background color of our view to medium grey
	this.mCamera.setBackgroundColor([0.8, 0.8, 0.8, 1]);
    
	// create a new "texture" renderable object
	this.mRenderable = new TextureRenderable(this.kTexture);
    
	// create a new game object with the new renderable
	this.mGameObject = new GameObject(this.mRenderable);
	this.mGameObject.getXform().setSize(16, 16);
	this.mGameObject.getXform().setPosition(30, 50);
};
Code snippet 2-3: Initialize

The GameObject is created just as before, but now, we have a reference to our new texture-based renderable. We also removed the behavior code for the GameObject. We no longer set a front direction or a speed. We do this so that the GameObject does not move independently. We will soon be adding code to move the GameObject with keyboard controls. Without changing anything else in our code, viewing this in a web browser will give you results that you can vew here. The image looks darker than we probably expected. We need to add lighting to the scene but that will come shortly.

Figure 2-2: TextureRenderable


Keyboard Input

Tracking user events involves polling for the status of keys and the mouse using a number of functions provided by gEngine.Input. To find the state of a particular key, we use gEngine.Input.isKeyPressed(). To find out if the mouse button is pressed, we use gEngine.Input.isButtonPressed(). In this example, we control movement of our TextureRenderable with the keyboard.

MyGameScene.prototype.update = function () {
	// Check for user keyboard input to control GameObject
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.A)) {
		this.mGameObject.getXform().incXPosBy(-0.5);
	}
        
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.D)) {
		this.mGameObject.getXform().incXPosBy(0.5);
	}
     
	if (gEngine.Input.isKeyClicked(gEngine.Input.keys.Q)) {
		gEngine.GameLoop.stop();
	}
	this.mGameObject.update()
};
Code snippet 2-4: Update

Now, pressing on the 'A' key will move the GameObject to the left. Pressing the 'D' key moves the GameObject to the right. When the 'Q' key is pressed, keyboard input is no longer processed by the game and motion is stopped. After gEngine.GameLoop.stop() is called, the game loop stops and the unloadScene() function is called and all resources are cleared from memory.

Test the results here.

[Note: When the game loop stops, unloadScene() is called where you can then instantiate a completely different Scene object and call gEngine.Core.startScene(nextScene) to start a new game loop. This is how you can implement a game with multiple levels.]


Audio & Mouse Input

Audio implementation is similar to images.

  1. Create a string constant to refer to the resource and identify its file location
  2. Request the engine to load the resource in loadScene() function
  3. Use the audio
  4. Free the resource when it is no longer needed in unloadScene()
function MyGameScene() {
	this.mCamera = null;
	this.mRenderable = null;
	this.mGameObject = null;
    
	this.kTexture = "assets/minion_portal.png";
	this.kGameBGSong = "assets/BGClip.mp3";
	this.kGameCueSound = "assets/BlueLevel_cue.wav";
}
gEngine.Core.inheritPrototype(MyGameScene, Scene);

MyGameScene.loadScene() {
	// need to stop the audio in case it is playing	
	gEngine.AudioClips.stopBackgroundAudio();

	gEngine.Textures.loadTexture(this.kTexture);
	gEngine.AudioClips.loadAudio(this.kGameBGSong);
	gEngine.AudioClips.loadAudio(this.kGameCueSound);
};

MyGameScene.unloadScene() {
	gEngine.Textures.unloadTexture(this.kTexture);
	gEngine.AudioClips.unloadAudio(this.kGameBGSong);
	gEngine.AudioClips.unloadAudio(this.kGameCueSound);
};
Code snippet 2-5: Scene constructor, Load & Unload

To play audio, we use two functions, gEngine.AudioClips.playBackgroundAudio() and gEngine.AudioClips.playACue(). The playBackgroundAudio() function allows you to play a sound clip and when the end of the clip is reached, it will loop back to the beginning. This continuous play option works well for background music. The playACue() function plays once and stops when the end of the clip is reached. This is used for quick sound effects like collisions or scoring achievements.

MyGameScene.prototype.update = function () {
	// Check for user keyboard input to control GameObject
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.A)) {
		this.mGameObject.getXform().incXPosBy(-0.5);
	}
        
	if (gEngine.Input.isKeyPressed(gEngine.Input.keys.D)) {
		this.mGameObject.getXform().incXPosBy(0.5);
	}
     
	if (gEngine.Input.isKeyClicked(gEngine.Input.keys.Q)) {
		gEngine.GameLoop.stop();
	}
     
	if (gEngine.Input.isKeyClicked(gEngine.Input.keys.P)) {
		if(!gEngine.AudioClips.isBackgroundAudioPlaying())
			gEngine.AudioClips.playBackgroundAudio(this.kGameBGSong);
		else
			gEngine.AudioClips.stopBackgroundAudio();
    
    if (gEngine.Input.isButtonClicked(gEngine.Input.mouseButton.Left)) {
		gEngine.AudioClips.playACue(this.kGameCueSound);
    }
};
Code snippet 2-6: Update

In this example, clicking the mouse button will trigger our cue audio to play. If the "P" key is clicked, we use isBackgroundAudioPlaying() function to determine if the background audio is already playing. If it isn't, we call the playBackgrounAudio()function to start playing our background sound. If the background audio is already playing, we stop it with stopBackgroundAudio(). Test the results here.

[Note: The mouse input will only respond if the mouse is clicked while hovering over the WebGL canvas in the browser.]


Text

To create text, we are going to use a special kind of TextureRenderable called a FontRenderable. It uses a texture that has all the characters of a font and will copy the appropriate portions of the texture to the screen based on a string value we provide. Several font textures are automatically loaded by the engine and are available to us.

First we declare our new variable for the text we will display.

function MyGameScene() {
	this.mCamera = null;
	this.mRenderable = null;
	this.mGameObject = null;
	this.mMessage = null;
    
	this.kTexture = "assets/minion_portal.png";
	this.kGameSong = "assets/BGClip.mp3";
	this.kGameCueSound = "assets/BlueLevel_cue.wav";
}
gEngine.Core.inheritPrototype(MyGameScene, Scene);
Code snippet 2-7: Scene Constructor

We initialize the variable by allocating a new FontRenderable object where we can set its initial text during creation. We also delete the code that sets the renderables direction and speed (our keyboard controls the location now). The code below sets the color, size and position of the text. It uses the same functions that we have used for other renderables. We can change the text after initialization using the setText() function.

MyGameScene.prototype.initialize = function () {
	this.mCamera = new Camera(
		vec2.fromValues(50, 40),    // position of the camera
		100,                        // width of camera
		[0, 0, 500, 400]            // viewport (orgX, orgY, width, height)
	);
	// set the background color of our view to medium grey
	this.mCamera.setBackgroundColor([0.8, 0.8, 0.8, 1]);
    
	// create a new "texture" renderable object
	this.mRenderable = new TextureRenderable(this.kTexture);
    
	// create a new game object with the new renderable
	this.mGameObject = new GameObject(this.mRenderable);
	this.mGameObject.getXform().setSize(16, 16);
	this.mGameObject.getXform().setPosition(30, 50);
    
	this.mMessage = new FontRenderable("Sample Text");
	this.mMessage.setColor([1, 0, 0, 1]);
	this.mMessage.getXform().setPosition(10, 70);
	this.mMessage.setTextHeight(5);
	this.mMessage.setText("This is sample text");
    
	gEngine.DefaultResources.setGlobalAmbientIntensity(3);
};
Code snippet 2-9: Initialize

You will also notice a call to gEngine.DefaultResources.setGlobalAmbientIntensity(3);. Unlike the resources we request the engine to load, there are some resources that the engine provides by default. The texture that makes up the default font we use is an example of a default resource that we never need to load or unload. An ambient lighting source is another default resource. This light is always there but it is normally set very dim (we saw this in our previous examples). It is very dim so that when you do define other lighting sources, the ambient wont conflict with your design.

[Note: Changing the ambient light intensity only affects renderables drawn on the canvas. You will notice that the background color neither dims nor brightens with this setting. The background is not a renderable.]

For the first time in a while, we need to make modifications to our draw() function. We now have a second renderable.

MyGameScene.prototype.draw = function () {
	// Clear the screen
	gEngine.Core.clearCanvas([0.8, 0.8, 0.8, 1.0]);
    
	// Activate our camera
	this.mCamera.setupViewProjection();
    
	// Draw our objects
	this.mGameObject.draw(this.mCamera);
	this.mMessage.draw(this.mCamera);
};
Code snippet 2-10: Draw

Click here to see the results or our sample code. Use the 'A' and 'D' keys on the keyboard to move the sprite left and right. Press the mouse button to play a sound cue and press 'P' to activate/deactivate the background music.

Figure 2-3: TextureRenderable and FontRenderable with ambient lighting

Increasing the ambient lighting intensity has brightened our sprite. We will keep that for future projects.


Conclusion

We have learned about user input and working with resources. We have moved from using solid boxes to textures as renderables. Fundamentally, our scene is quite similar to what we've done in the previous tutorial.

In tutorial 3, we will take a look at renderables that support spritesheets and animating textures. We will also see how to detect collisions between GameObjects.


Tutorial 1 <-- Tutorial 2 --> Tutorial 3

2/11/2016 - David Watson, Proofread by Adedayo Odesile & Jeb Pavleas