“These things are all about getting the timing right..”
It was previously highlighted that we would want to include:
game.initialise()
game.update()
game.render()
These are the basic steps for any game development. We initialise the variables we need to start, when the game starts it runs in a loop, each iteration of the loop calls the update function, and the render function.
The update function contains the code that updates the selection, positions, and sizes of the images on screen, and the render function then uses this updated data to draw the image.
The update function can take user input, or use character behaviour, or the physics and mechanics of the game to determine these positions for each frame.
In this project, I have created a ‘PlayableContext’ object. This will be the ‘game’ area that we are referring to. It includes the environment, the space for user input, the interactions between the player and the levels and the other game characters and items. It essentially is the digital space that the game takes place.
In the GameJS/js/ folder, add a script and call it ‘playablecontext.js’.
Open the script in a text editor, and add the following code and save it:
export class PlayableContext
{
constructor()
{
}
}
export default PlayableContext;
What we have done here is create an empty class called ‘PlayableContext’ and made it ready for ‘export’. With the class defined as an export class, it can then be ‘imported’, or ‘included’ in another script file. This is the modularisation we were talking about earlier. By making the game files modular, it is a lot easier to manage.
This is just an empty class for now. A class is like a code object. This object is whatever we define it. Similar to how a variable ‘x’ can hold a number, or a string of letters to make a word (called a ‘string’), a class object can hold a collection of variables and also functions, to utilise how the object can be used in code.
In the example above, we have defined the space for the class object ‘PlayableContext’. Within that space we included a ‘constructor’ function which initialises the object as defined in the constructor function whenever a new instance of the object is created. The constructor is automatically called whenever the object is created.
After the class definition I have included the line of code stating
export default PlayableContext;
We already have mentioned the class is an export class in the class definition, but by adding this line at the end of the script we are stating that we want to export the default exportable objects in this script and export it to be referred to or accessed by the name ‘PlayableContext’.
We can go ahead and add the structure we were talking about earlier, the functions that we will call to initialise, update, and render the game, and in the initialise function of the PlayableContext we want to include the code to create a handle to the canvas object from the html web page. With the handle to the canvas object we can then draw our images to the canvas.
The PlayableContext.js script file will then look like this:
import { Player } from ‘./player.js’;
import { Datagram } from ‘./datagram.js’;
import { InputController } from ‘./userinput.js’;
import { FileManager } from ‘./filemanagement.js’;
export class PlayableContext
{
constructor()
{
this.player = new Player();
this.data = new Datagram();
this.files = new FileManager();
this.inputController = new InputController();
}
initialise()
{
// Initialise Datagram
this.data.initialise();
// Image Loading
this.files.initialiseNinjaFrames();
// Canvas
this.canvas = document.getElementById(“playContext”);
this.canvas.width = 1280;
this.canvas.height = 680;
this.canvas.style.outline = “none”;
this.canvas.tabIndex = 1000;
this.canvas.focus();
}
update()
{
}
render()
{
}
}
export default PlayableContext;
Then save the ‘playablecontext.js’ script and reopen ‘app.js’.
We need to add the following changes to app.js:
import { PlayableContext } from ‘./playablecontext.js’;
let bInitialised = false;
let gameContext = new PlayableContext();
function runApp()
{
if(bInitialised === false)
{
gameContext.initialise();
bInitialised = true;
}
gameContext.update();
gameContext.render();
}
setInterval(runApp, 20);
What we have added here is ‘import { PlayableContext } from ‘./playablecontext.js’;’
This will import the exported ‘PlayableContext’ from the file located in the same directory ‘./’ with the name ‘playablecontext.js’.
Another addition: let gameContext = new PlayableContext();
We have added an instance of PlayableContext, or the PlayableContext object and stored it in the variable name ‘gameContext’. We can now refer to our version of the PlayableContext object as ‘gameContext’.
We then can include the call to the initialise, update and render functions using the reference to gameContext, our version of the PlayableContext object, with the commands.
gameContext.initialise();
gameContext.update();
gameContext.render();
We are going to go ahead and add a few more scripts all at once to start creating the structure of the game. So far, we have the ‘app.js’ which contains the entry point and control for the game object. We have the ‘playablecontext.js’ which is the game object. The script files we want to add are:
player.js
datagram.js
filemanagement.js
sprite.js
animation.js
animationcollection.js
When each of these are created and added to the GameJS/js/ folder, we want to create the exportable class in each of these files.
I recommend for ease of use and managing multiple files that you look at installing a text editor that allows you to have multiple tabs open. We will be managing multiple different script files, you could end up opening and closing the same scripts many times, or having individual windows open, but a text editor that allows you to open multiple tabs is going to save a lot of stress in the long run. My personal favourite is Notepad++, it includes code extensions which also come in quite handy, it can be downloaded from
https://notepad-plus-plus.org/
The exportable class code for each of these script files: // In player.js
export class Player
{
constructor()
{
}
initialise()
{
}
update()
{
}
render()
{
}
}
export default Player;
Then copy and paste if you like renaming ‘Player’ to the script name, but with a capital letter for the beginning of the word to signify it is the name of an object. i.e. // in datagram.js
export class Datagram
{
constructor()
{
}
initialise()
{
}
update()
{
}
render()
{
}
}
export default Datagram;
And do the same for FileManager object in the filemanagement.js script, Sprite in the sprite.js script, Animation in the animation.js and AnimationCollection in the animationcollection.js.
The object names are case sensitive as is the script filenames and the references to them. To avoid headache later I name the script files all lowercase and refer to them with lowercase names, as with the folders in the game directory. Functions have lower case names, and objects have a capital letter at the beginning of each word. This standardisation quickly becomes useful when trying to remember what you did or didn’t capitalise when naming, and by having a naming convention it saves you looking up each reference every time. Basically, everything is lowercase except for object names, and where an object name contains two or more words, each word begins with a capital eg. AnimationCollection.
We can see here the script structure will start to take form like this:
A Sprite is a single image, or an animation frame.
An Animation is a collection of sprites or animation frames that together make up an animation.
An AnimationCollection is all of the animations that a character or item is capable of, and the animations can be selected and cycled through appropriate for the purpose in-game.
The filemanagement script contains a FileManager class object, which will be used to load the individual image files and create the required image frames linked to the right image, which are then used to create the animations and animation collection.
And the Datagram object from the datagram script is the collection of game data that will be used throughout the game with the access to the common variables required throughout the game. For example, the screen size, gravity velocity, specific colours used in game, these variables will be required to be accessed in different areas of the game code, but we don’t want to risk duplicating the variables or being required to use the numerical values of the top of our head for each time we need to refer to them. The purpose of the datagram is that it groups all the data into one object that can be shared and accessed among the various game objects.
The Player object in the player.js script will contain all the code required to change the player animations, interpret the user input, and update the player character accordingly.
We need to add to the Datagram code in datagram.js. This is the Data object that will store all of the required reference variables that we will be required to access. It helps to store this data somewhere so we don’t have to remember the numbers of the top of our head as we get further into development, we can instead just refer to the information stored in the datagram object.
export class Datagram
{
constructor()
{
this.screenSize = { width: 0, height: 0 };
this.cameraPosition = { x: 0, y: 0 };
this.cameraView = { width: 0, height: 0 };
this.playerScreenPosition = { x: 0, y: 0 };
this.playerRelativePosition = { x: 0, y: 0 };
this.playerSize = { width: 0, height:0 };
this.playerSpeedMax = { x: 0, y: 0 }
this.frameCount = 0;
this.frameMaxCount = 60;
this.idleFrameWidth = 65;
this.idleFrameHeight = 115;
}
initialise()
{
this.screenSize = { width: 1280, height: 680 };
this.cameraPosition = { x: 0, y: 0 };
this.cameraView = { width: 1280, height: 680 };
this.playerScreenPosition = { x: 400, y: 50 };
this.playerRelativePosition = { x: 0, y: 0 };
this.playerSize = { width: 116, height:220 };
this.playerSpeedMax = { x: 0, y: 0};
this.frameCount = 0;
this.frameMaxCount = 60;
}
}
export default Datagram;
With these files in place, we are ready to start loading the animations with the animation frames and setting the animations to play through.
Brent McEwan