The foundation of any game is the game loop. This is what drives the game forward and allows for all the interactions to take place. By repeating a block of code over and over a program can come to life and interactivity can take place.
// Game Loop
function update() {
setTimeout(update, 1000/60);
elapsedTime();
logic();
render();
}
The two core pieces of code that need to be executed in this block are the game logic and frame rendering. Rendering is what is drawn to the screen in each loop of the game known as a frame. Everything rendered within a frame is represented as different shapes and images derived from the game logic. Every frame is cleared and redrawn so clean animations can take place.
// Render Frame
function render() {
// Clears the screen
context.clearRect(0, 0, winWidth, winHeight);
// Draw a box
context.beginPath();
context.rect(xPos - width/2, yPos - height/2, width, height);
context.fill();
// Game loop time
context.fillText("Elapsed Time: "+elapsed, 10, 10);
context.fillText("FPS: "+fps, 10, 20);
}
The other essential piece of code is the game logic. This block of code is what the game actually does between rendering each frame. This area of the code is where all the magic happens and needs to be highly tuned so the game runs as fast as possible. Otherwise the game will look choppy and the experience will be ruined.
// Game Logic
function logic() {
// Move box
xPos += xVel * percent;
yPos += yVel * percent;
// Check within window width
if(xPos < 0) {
xPos = 0;
xVel *= -1;
} else if(xPos > winWidth) {
xPos = winWidth;
xVel *= -1;
}
// Check within window height
if(yPos < 0) {
yPos = 0;
yVel *= -1;
} else if(yPos > winHeight) {
yPos = winHeight;
yVel *= -1;
}
}
Now lets take a look at the elapsedTime() function. This function counts the time between each frame so we know how much to adjust the position of each frame animation and the delay of any timings. If we do not make this adjutments then each frame will move at an irregular speed depending on what the computer's cpu is doing. If there are a lot of programs the computer is working on then the animation will look choppy.
// Frame Delay
function elapsedTime() {
// Retrieve latest timestamp
end = Date.now();
// Store delay between latest time and previous time
elapsed = end - start;
// Store latest timestamp for next frame
start = end;
// Calculate delay per second as a percentage
percent = elapsed/1000;
// fps
calcFPS();
}
Calculating the timing is important for anything that is tied to a fixed duration. An example of this can be seen when we want to calculate the frames per second. By displaying the frames per second it allows us to check what is slowing down our game in real time.
// Frames Per Second
function calcFPS() {
// Increment frame delay
fpsTime += elapsed;
// Increment frame count
frameCount ++;
// Checks frame delay per second
if(fpsTime > 1000) {
// Reset frame delay
fpsTime -= 1000;
// Store frames per second count
fps = frameCount;
// Reset frame count
frameCount = 0;
}
}
The last core piece of the game loop is the part where it repeats. By making our update() function utilize the setTimeout() JavaScript function we can set which function to call and what delay between frames we want. Therefore allowing us to set our target frame rate. To achieve 60 frames per second we deivide it by 1000 milliseconds.
Now lets take a look at our current game loop running.
This is the full source code all together. Run this JavaScript code by calling the setup() function.
var canvas;
var context;
var winWidth = 0, winHeight = 0;
// Elapsed Time
var percent = 0;
var elapsed = 0, start, end;
var fps = 0, frameCount = 0, fpsTime = 0;
// Box
var xPos = 0, yPos = 0;
var width = 50, height = 50;
var xVel = 100, yVel = 100;
function setup() {
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
winWidth = 300;
winHeight = winWidth/3*2;
canvas.width = winWidth;
canvas.height = winHeight;
// Reset initial time
start = Date.now();
update();
}
// Game Loop
function update() {
setTimeout(update, 1000/60);
elapsedTime();
logic();
render();
}
// Render Frame
function render() {
// Clears the screen
context.clearRect(0, 0, winWidth, winHeight);
// Draw a box
context.beginPath();
context.rect(xPos - width/2, yPos - height/2, width, height);
context.fill();
// Game loop time
context.fillText("Elapsed Time: "+elapsed, 10, 10);
context.fillText("FPS: "+fps, 10, 20);
}
// Game Logic
function logic() {
// Move box
xPos += xVel * percent;
yPos += yVel * percent;
// Check within window width
if(xPos < 0) {
xPos = 0;
xVel *= -1;
} else if(xPos > winWidth) {
xPos = winWidth;
xVel *= -1;
}
// Check within window height
if(yPos < 0) {
yPos = 0;
yVel *= -1;
} else if(yPos > winHeight) {
yPos = winHeight;
yVel *= -1;
}
}
// Frame Delay
function elapsedTime() {
// Retrieve latest timestamp
end = Date.now();
// Store delay between latest time and previous time
elapsed = end - start;
// Store latest timestamp for next frame
start = end;
// Calculate delay per second as a percentage
percent = elapsed/1000;
// fps
calcFPS();
}
// Frames Per Second
function calcFPS() {
// Increment frame delay
fpsTime += elapsed;
// Increment frame count
frameCount ++;
// Checks frame delay per second
if(fpsTime > 1000) {
// Reset frame delay
fpsTime -= 1000;
// Store frames per second count
fps = frameCount;
// Reset frame count
frameCount = 0;
}
}