Enhancing Skills

Building a Game Engine in JavaScript: A Learn-at-Your-Pace Series for Developing Classic Games

Series Overview – see what I need

Goal: Create a basic 2D game engine using JavaScript and develop classic games like Pong, Space Invaders, and Tetris.

Format: Learn at your own pace, progressing through each section as you feel comfortable.

Part 1: Learn Basic JavaScript Commands

JavaScript Fundamentals

Understanding JavaScript is crucial for game development. This section covers the foundational concepts you need to build a game engine.

Key Concepts to Cover

Variables: var, let, and const

JavaScript offers three main ways to declare variables: var, let, and const. Each has different characteristics regarding scope, re-assignment, and hoisting. Let’s break down what you can and cannot do with each.

var

  • What it can do:
  • Declares variables globally or function-scoped.
  • Variables declared with var are hoisted, meaning they can be referenced before they are defined (though the value will be undefined until the actual declaration is executed).
  • Allows re-declaration of the same variable within the same scope.
var x = 10;
var x = 20; // This is allowed with 'var'
  • What it cannot do:
  • var does not respect block scope, which means it is not confined to {}. This can lead to unexpected behavior in loops and conditionals.
  • It cannot block access to variables declared inside loops or conditionals.
if (true) {
  var testVar = 'Inside block';
}
// 'Inside block' (var leaks outside the block)
console.log(testVar);

let

  • What it can do:
  • Declares variables that are block-scoped, meaning they only exist within the surrounding {} (block, function, or loop).
  • Allows reassignment of values.
let y = 10;
y = 20;  // Re-assignment is allowed
  • What it cannot do:
  • let cannot be redeclared within the same scope, which prevents accidental re-declarations.
  • let variables are not hoisted in the same way as var. Accessing them before the declaration results in a ReferenceError.
let z = 30;
// let z = 40;  // This will throw an error (z has already been declared)

const

  • What it can do:
  • Declares variables that are block-scoped, just like let.
  • Must be initialized when declared, and cannot be reassigned afterward.
  • Mutable data types (e.g., arrays, objects) can have their contents changed, but you cannot reassign the const variable to a new value.
const pi = 3.14;
pi = 3.14159;
// This will throw an error
// (assignment to a constant variable)
  • What it cannot do:
  • It cannot be re-declared or re-assigned within the same scope.
  • It cannot be hoisted before initialization.
const myArray = [1, 2, 3];
// This is allowed because we are
// modifying the contents of the array
myArray.push(4); 

Understanding the Main Data Types

JavaScript supports several primary data types, each serving different purposes. These include strings, numbers, booleans, arrays, and objects. Understanding these is critical for game logic and manipulating data.

1. Strings

Strings represent textual data and are enclosed in either single or double quotes. They are essential for displaying messages, player names, game titles, and more.

let playerName = "John";         // Double quotes
let gameTitle = 'Space Invaders'; // Single quotes

Operations:

Arithmetic Operations in JavaScript

Arithmetic operations are fundamental in programming and are used for calculations such as scoring, speed adjustments, and more. Here are examples of each arithmetic operator in JavaScript:

  1. Addition (+): Adds two numbers together.
   let a = 10;
   let b = 5;
   let sum = a + b;  // 10 + 5
   console.log(sum);  // Output: 15
  1. Subtraction (-): Subtracts one number from another.
   let difference = a - b;  // 10 - 5
   console.log(difference);  // Output: 5
  1. Multiplication (*): Multiplies two numbers.
   let product = a * b;  // 10 * 5
   console.log(product);  // Output: 50
  1. Division (/): Divides one number by another.
   let quotient = a / b;  // 10 / 5
   console.log(quotient);  // Output: 2
  1. Modulus (%): Returns the remainder of a division operation.
   let remainder = a % b;  // 10 % 5
   console.log(remainder);  // Output: 0

   // Example with a non-divisible number
   let c = 11;
   let remainder2 = c % b;  // 11 % 5
   console.log(remainder2);  // Output: 1

Summary of Arithmetic Operations

OperatorDescriptionExampleResult
+Addition10 + 515
-Subtraction10 - 55
*Multiplication10 * 550
/Division10 / 52
%Modulus (remainder)10 % 50
Modulus (with non-divisible)11 % 51

These basic arithmetic operations will be crucial as you develop your 2D game engine, allowing you to manipulate game scores, player positions, and more.

  • Concatenation: Combine strings using the + operator.
let greeting = 'Welcome, ' + playerName + '! Ready to play ' + gameTitle + '?';
console.log(greeting);  // Output: Welcome, John! Ready to play Space Invaders?
  • Template literals: Embed variables and expressions using backticks and ${} syntax.
let message = `Player: ${playerName}, Game: ${gameTitle}`;
console.log(message);  // Output: Player: John, Game: Space Invaders

2. Numbers

Numbers in JavaScript represent both integers and floating-point values. They are used for scoring, speed, and other quantitative measures in games.

let score = 100;        // Integer
let speed = 4.5;       // Floating-point

Operations:



; // Output: 8

3. Booleans

Booleans represent a logical entity and can have two values: true or false. They are essential for control flow in your game, such as checking game states (e.g., game over).

let isGameOver = false; // Indicates whether the game is over

Conditional Statement Example:

if (isGameOver) {
    console.log('Game Over!');
} else {
    console.log('Keep playing!');  // Output: Keep playing!
}

4. Arrays

Arrays are ordered collections of values, which can be of any data type, and are zero-indexed (the first element is at index 0). Arrays are useful for storing lists, such as high scores or levels.

let highScores = [100, 200, 300];  // Array of numbers
let levels = ['Easy', 'Medium', 'Hard'];  // Array of strings

Operations:

  • Access elements by index: Retrieve values using their index.
console.log(highScores[1]);  // Output: 200 (the second element)
  • Modify elements: Change values by assigning a new value to an index.
highScores[0] = 150;  // Updates the first element
console.log(highScores);  // Output: [150, 200, 300]
  • Common methods:
  • .push(): Adds a new element to the end of the array.
highScores.push(400);  // Adds 400 to the array
console.log(highScores);  // Output: [150, 200, 300, 400]
  • .pop(): Removes the last element of the array.
highScores.pop();  // Removes the last element (400)
console.log(highScores);  // Output: [150, 200, 300]
  • .length: Gets the number of elements in the array.
console.log(highScores.length);  // Output: 3

5. Objects

Objects are collections of key-value pairs, allowing you to group related data together. They are fundamental for representing complex entities like players, enemies, or items in your game.

let player = {
    name: 'John',
    score: 150,
    isActive: true,
    inventory: ['Sword', 'Shield'],
};

Operations:

  • Access properties: Use dot notation or brackets to retrieve values.
console.log(player.name);    // Output: John
console.log(player['score']); // Output: 150
  • Modify properties: Change values by assigning a new value to a property.
player.score = 200;  // Updates the score
console.log(player.score);  // Output: 200
  • Add properties: Introduce new properties as needed.
player.health = 100;  // Adds a new property 'health'
console.log(player.health);  // Output: 100
  • Delete properties: Remove a property from the object.
delete player.inventory;  // Removes the inventory property
console.log(player.inventory);  // Output: undefined

Understanding these data types and their operations will empower you to implement logic and functionality within your JavaScript-based game engine effectively.


Global Variables

Understand the scope of global variables and how they can be accessed throughout your application:

for (let i = 0; i < 10; i++) {
 console.log(i); // Prints numbers from 0 to 9
}
console.log(i); // Error, `i` is no longer accessable

Operations and Bitwise

In JavaScript, operations include arithmetic, logical, and bitwise operations. These operations are essential when building a game engine because they enable the manipulation of values, conditions, and interactions between different elements in the game.

Arithmetic Operations

JavaScript supports common arithmetic operations such as:

OperationDescriptionExample
+Addition5 + 2 // 7
-Subtraction5 - 2 // 3
*Multiplication5 * 2 // 10
/Division5 / 2 // 2.5
%Modulo (remainder)5 % 2 // 1
**Exponentiation5 ** 2 // 25

Example:

const x = 10;
const y = 3;
console.log(x + y); // Output: 13
console.log(x * y); // Output: 30
console.log(x ** y); // Output: 1000 (10 raised to the power 3)

Bitwise Operations

Bitwise operations allow you to manipulate individual bits in a binary representation of numbers. This can be very useful when working with low-level operations or optimizing code for performance, especially when managing flags or binary states (on/off).

OperatorDescriptionExample
&AND5 & 1 // 1
|OR5 | 1 // 5
^XOR5 ^ 1 // 4
~NOT (bitwise complement)~5 // -6
<<Left Shift5 << 1 // 10
>>Right Shift5 >> 1 // 2
  • AND (&): Compares each bit of two numbers and returns 1 if both bits are 1.
  • OR (|): Compares each bit and returns 1 if either bit is 1.
  • XOR (^): Compares each bit and returns 1 if the bits are different.
  • NOT (~): Inverts all the bits.
  • Left Shift (<<): Shifts bits to the left, adding zeros from the right.
  • Right Shift (>>): Shifts bits to the right.

Example:

const a = 5;  // Binary: 101
const b = 3;  // Binary: 011
console.log(a & b);  // Output: 1 (Binary: 001)
console.log(a | b);  // Output: 7 (Binary: 111)
console.log(a ^ b);  // Output: 6 (Binary: 110)
console.log(~a);     // Output: -6
console.log(a << 1); // Output: 10 (Binary: 1010)
console.log(a >> 1); // Output: 2 (Binary: 10)

Logical Operations

Logical operators evaluate expressions and return a boolean result (true or false).

OperatorDescriptionExample
&&ANDtrue && false // false
||ORtrue || false // true
!NOT!true // false

Example:

const isGameActive = true;
const isPlayerAlive = false;
console.log(isGameActive && isPlayerAlive); // Output: false
console.log(isGameActive || isPlayerAlive); // Output: true
console.log(!isPlayerAlive); // Output: true

Combining Logical and Bitwise Operations

You can combine logical and bitwise operations to handle more complex conditions, particularly when you need to work with multiple states.

Example:

const FLAG_A = 1; // 001
const FLAG_B = 2; // 010
const FLAG_C = 4; // 100

let flags = FLAG_A | FLAG_C; // Combine FLAG_A and FLAG_C
console.log(flags); // Output: 5 (Binary: 101)

if (flags & FLAG_A) {
    console.log('Flag A is set');
}
if (flags & FLAG_B) {
    console.log('Flag B is set');
} else {
    console.log('Flag B is not set');
}

Use in Game Development

Operations like these can be essential in managing game states, flags, or complex physics systems. For example, you might use bitwise operations to manage multiple status effects on a character in a game (like a “stunned” or “poisoned” state) by combining and checking bits.

This section will expand your understanding of how JavaScript handles arithmetic, logical, and bitwise operations, providing the tools for optimized and efficient programming, especially when working with game mechanics.

Looping

For Loop: Iterate over a set of values:

for (let i = 0; i < 10; i++) {
 console.log(i); // Prints numbers from 0 to 9 
}

While Loop: Continue executing as long as a condition is true:

let count = 0;
while (count < 5) {
console.log(count); // Prints numbers from 0 to 4 count++;
}

Do-While Loop: Execute at least once before checking the condition:

let num;
do { 
num = Math.random(); // Generate a random number 
} while (num < 0.5); // Loop continues until num is >= 0.5

Array Iteration: Use methods like .forEach() and .map():

const numbers = [1, 2, 3, 4, 5];
numbers.forEach((num) => { 
console.log(num); // Prints each number 
});

Conditional Logic

If Statement: Execute code based on a condition:

const score = 85;
if (score >= 90) {
 console.log('Grade: A');
 }

Else If and Else: Create complex conditions:

if (score >= 90) {
 console.log('Grade: A'); 
} else if (score >= 80) { 
console.log('Grade: B'); 
} else { 
console.log('Grade: C'); 
}

Ternary Operator: Use the question mark for conditional logic:

// Assigns 'Adult' or 'Minor' based on age
const isAdult = age >= 18 ? 'Adult' : 'Minor'; 

Switch Statement: Simplify multiple conditions:

const fruit = 'apple'; 
switch (fruit) { 
case 'banana': console.log('Banana is yellow.'); 
break; 
case 'apple': console.log('Apple is red.'); 
break; 
default: console.log('Unknown fruit.'); 
}

Functions

Function Declaration: Define reusable blocks of code:

function add(a, b) { 
return a + b; // Returns the sum of a and b 
}
  • Arrow Functions: Shorter syntax for writing functions:
// Returns the product of x and y
const multiply = (x, y) => x * y; 

Lists: Arrays, Maps & Sets

Arrays: Use arrays to store a list of values:

const fruits = ['apple', 'banana', 'cherry']; // An array of strings
console.log(fruits[1]);
// Outputs: banana

Multidimensional Arrays: Create arrays of arrays to represent grids or tables:

const matrix = [ 
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9] 
];
console.log(matrix[1][2]);
 // Outputs: 6

Array Methods: Use various methods to manipulate arrays:

  • Push: Add an element to the end:
fruits.push('date');
console.log(fruits);
// Outputs: ['apple', 'banana', 'cherry', 'date']
  • Pop: Remove the last element:
fruits.pop();
console.log(fruits);
// Outputs: ['apple', 'banana', 'cherry']
  • Shift: Remove the first element:
fruits.shift();
console.log(fruits);
// Outputs: ['banana', 'cherry']
  • Unshift: Add an element to the beginning:
fruits.unshift('kiwi');
console.log(fruits);
// Outputs: ['kiwi', 'banana', 'cherry']
  • Slice: Create a subarray:
 // Creates a new array from index 1 to 3 (exclusive) 
const citrus = fruits.slice(1, 3);
console.log(citrus);
// Outputs: ['banana', 'cherry']

Sets

  • Sets: Create a collection of unique values. Useful for storing non-duplicate items:
const uniqueNumbers = new Set([1, 2, 3, 4, 4, 5]);
// Adds 6 to the set
uniqueNumbers.add(6);
// Ignored since 4 is already in the set
uniqueNumbers.add(4);
console.log(uniqueNumbers); 
// Outputs: Set { 1, 2, 3, 4, 5, 6 }

Maps

  • Maps: Use key-value pairs to store data. Allows keys of any type:
const myMap = new Map();
myMap.set('name', 'John');
myMap.set('age', 30);
myMap.set('city', 'New York');
console.log(myMap.get('name'));
// Outputs: John 
console.log(myMap.size);
// Outputs: 3

Classes and Inheritance

Class Structure: Define classes to create objects:

class GameCharacter { 
constructor(name, health) { 
 this.name = name;
 this.health = health; 
 } 
 attack() {
 console.log(`${this.name} attacks!`);
  } 
}

Inheritance: Create a class that extends another class:

class Player extends GameCharacter {
 constructor(name, health, level) {
 super(name, health);
 this.level = level; 
}
 levelUp() {
this.level++; // Increases player level
console.log(`${this.name} leveled up to ${this.level}!`); 
 }
}

Modules

Export: Export functions or variables from a module:

export const pi = 3.14; // Exports the variable pi 
export function calculateArea(radius) { 
  return pi * radius * radius; // Exports the function
 }

Import: Import functions or variables into another module:

// Imports pi and calculateArea from math.js
import { pi, calculateArea } from './math.js';
console.log(pi); 

Event Handling

Event Listeners: Use window.addEventListener to capture and respond to user events:

window.addEventListener('click', (event) => { 
  console.log(`Clicked at coordinates: 
${event.clientX}, ${event.clientY}`);
 });
Practical Exercises
  • Create a simple program that uses loops and conditionals to generate a list of numbers and their classifications (e.g., even or odd).
  • Develop a basic calculator that takes user input and performs arithmetic operations based on selected operations.
  • Implement a class representing a game character, demonstrating inheritance and method overrides.

You can try the examples here: -> https://onecompiler.com/javascript

Part 2: Building the Game Engine

Setting Up Your Development Environment
  • Set up your project structure and choose a text editor (e.g., Visual Studio Code) is what I use.
  • Familiarize yourself with HTML5 Canvas for rendering graphics.

Creating the Game Loop
  • Implement a basic game loop using requestAnimationFrame:
  function gameLoop() { // Update game state and render
    // Calls gameLoop again for the next frame
    requestAnimationFrame(gameLoop);
  }
  requestAnimationFrame(gameLoop); // Starts the game loop
Handling Input
  • Learn how to capture keyboard and mouse input.
  • Implement character movement based on input.
Managing Game States
  • Create a system for managing different game states (e.g., menu, play, pause).
  • Implement transitions between these states.

Part 3: Developing Classic Games

Building Pong
  • Use your game engine to create Pong.
  • Focus on physics, collision detection, and scoring.
Creating Space Invaders
  • Develop Space Invaders using sprites and simple AI.
  • Implement shooting mechanics and enemy behavior.
Developing Tetris
  • Create Tetris with tetromino logic, line clearing, and scoring.
  • Implement game over conditions and a scoring system.

Part 4: Advanced Topics

Enhancing Your Engine
  • Add sound effects and music using the Web Audio API.
  • Implement animations and transitions for a more polished look.
Optimization Techniques
  • Learn about performance optimizations for smoother gameplay.
  • Profile your game and identify bottlenecks.

Part 5: Final Projects

Capstone Project
  • Create a more complex game or enhance one of the classic games you’ve built.
  • Focus on features like power-ups, levels, and achievements.

Additional Resources

Books
  • “Eloquent JavaScript” by Marijn Haverbeke
  • “JavaScript Game Development” by Chris DeLeon
Online Courses
  • Platforms like Udemy or Coursera may offer game development courses focusing on JavaScript.
Tutorials
  • Follow online tutorials for each classic game to see practical examples.

Practice and Community

  • Share your projects on platforms like GitHub for feedback and collaboration.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.