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 Resources • Keyboard input • Audio & Mouse • Text
Demonstrations: Drawing with a Texture • Keyboard • Audio & Mouse • Text 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.
[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.]
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.
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.
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.
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.
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.
- Create a string constant to refer to the resource and identify its file location
- Request the engine to load the resource in loadScene() function
- Use the audio
- Free the resource when it is no longer needed in unloadScene()
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.
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.
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.
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.
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.
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