Game Engine
GitHubOfficialFirst committed by jhaugaDistributed by GitHub
- Skill
- Tool integration
- Software development
- MIT
- No issues found
AutoGineer summary
Written by AutoGineer from the skill’s own files, not by the author
The Game Engine skill provides a structured approach to developing interactive 2D and 3D games for the web. It covers the entire lifecycle of game development, from core engine architecture—such as the game loop and rendering pipelines—to specific implementations of physics, collision detection, and input handling. The skill includes reference documentation on web APIs, algorithms, and 3D theory, alongside practical templates for common game types like platformers and maze games.
What it does
- Provides workflows for building game engines and games from scratch.
- Explains core concepts like game loops, rendering, and physics.
- Offers reference material for WebGL, Canvas, and Web Audio APIs.
- Includes starter templates for 2D platformers and breakout-style games.
- Covers advanced topics like raycasting, collision detection, and multiplayer networking.
- Guides users through asset optimization and game publishing.
When to use it
- When you need to build a web-based game or a custom game engine.
- When implementing game mechanics like physics, collision, or input controls.
- When you need to understand 3D rendering pipelines or WebGL fundamentals.
- When you are looking for starter code for 2D or 3D web game projects.
- When you need to optimize game performance or handle cross-browser audio issues.
Advantages
- Centralizes essential game development knowledge for the web.
- Provides ready-to-use templates to accelerate project setup.
- Offers clear, step-by-step procedures for common game development tasks.
- Includes troubleshooting guides for common rendering and performance issues.
- Supports both 2D (Canvas) and 3D (WebGL/Three.js) development workflows.
Usage examples
- “How do I set up a basic game loop using requestAnimationFrame?”
- The agent provides a code structure that initializes a canvas, defines an update function for game logic, and a render function for drawing, tied together by a recursive requestAnimationFrame call.
- “Can you help me implement AABB collision detection for my 2D platformer?”
- The agent explains the Axis-Aligned Bounding Box algorithm and provides a JavaScript function to check if two rectangular objects overlap.
- “What are the steps to publish my web game?”
- The agent outlines a checklist including asset compression, code minification, cross-browser testing, and choosing a distribution platform.
Install
Download the skill as a ZIP:
https://autogineer.com/agent-skills/github/game-engine/downloadUnzip it into the cross-client skills folder of your project:
mkdir -p .agents/skills && unzip game-engine.zip -d .agents/skills
Documentation by jhauga
Game Engine Skill
Build web-based games and game engines using HTML5 Canvas, WebGL, and JavaScript. This skill includes starter templates, reference documentation, and step-by-step workflows for 2D and 3D game development with frameworks such as Phaser, Three.js, Babylon.js, and A-Frame.
When to Use This Skill
Building a game engine or game from scratch using web technologies
Implementing game loops, physics, collision detection, or rendering
Working with HTML5 Canvas, WebGL, or SVG for game graphics
Adding game controls (keyboard, mouse, touch, gamepad)
Creating 2D platformers, breakout-style games, maze games, or 3D experiences
Working with tilemaps, sprites, or animations
Adding audio to web games
Implementing multiplayer features with WebRTC or WebSockets
Optimizing game performance
Publishing and distributing web games
Prerequisites
Basic knowledge of HTML, CSS, and JavaScript
A modern web browser with Canvas/WebGL support
A text editor or IDE
Optional: Node.js for build tooling and local development servers
Core Concepts
The following concepts form the foundation of every web-based game engine.
Game Loop
Every game engine revolves around the game loop -- a continuous cycle of:
Process Input - Read keyboard, mouse, touch, or gamepad input
Update State - Update game object positions, physics, AI, and logic
Render - Draw the current game state to the screen
Use requestAnimationFrame for smooth, browser-optimized rendering.
Rendering
Canvas 2D - Best for 2D games, sprite-based rendering, and tilemaps
WebGL - Hardware-accelerated 3D and advanced 2D rendering
SVG - Vector-based graphics, good for UI elements
CSS - Useful for DOM-based game elements and transitions
Physics and Collision Detection
2D Collision Detection - AABB, circle, and SAT-based collision
3D Collision Detection - Bounding box, bounding sphere, and raycasting
Velocity and Acceleration - Basic Newtonian physics for movement
Gravity - Constant downward acceleration for platformers
Controls
Keyboard - Arrow keys, WASD, and custom key bindings
Mouse - Click, move, and pointer lock for FPS-style controls
Touch - Mobile touch events and virtual joysticks
Gamepad - Gamepad API for controller support
Audio
Web Audio API - Programmatic sound generation and spatial audio
HTML5 Audio - Simple audio playback for music and sound effects
Step-by-Step Workflows
Creating a Basic 2D Game
Set up an HTML file with a
<canvas>elementGet the 2D rendering context
Implement the game loop using
requestAnimationFrameCreate game objects with position, velocity, and size properties
Handle keyboard/mouse input for player control
Implement collision detection between game objects
Add scoring, lives, and win/lose conditions
Add sound effects and music
Building a 3D Game
Choose a framework (Three.js, Babylon.js, A-Frame, or PlayCanvas)
Set up the scene, camera, and renderer
Load or create 3D models and textures
Implement lighting and shaders
Add physics and collision detection
Implement player controls and camera movement
Add audio and visual effects
Publishing a Game
Optimize assets (compress images, minify code)
Test across browsers and devices
Choose distribution platform (web, app stores, game portals)
Implement monetization if needed
Promote through game communities and social media
Game Templates
Starter templates are available in the assets/ folder. Each template provides a complete, working example that can be used as a starting point for a new project.
| Template | Description |
|---|---|
paddle-game-template.md | 2D Breakout-style game with pure JavaScript |
2d-maze-game.md | Maze game with device orientation controls |
2d-platform-game.md | Platformer game using Phaser framework |
gameBase-template-repo.md | Game base template repository structure |
simple-2d-engine.md | Simple 2D platformer engine with collisions |
Reference Documentation
Detailed reference material is available in the references/ folder. Consult these files for in-depth coverage of specific topics.
| Reference | Topics Covered |
|---|---|
basics.md | Game development introduction and anatomy |
web-apis.md | Canvas, WebGL, Web Audio, Gamepad, and other web APIs |
techniques.md | Collision detection, tilemaps, async scripts, audio |
3d-web-games.md | 3D theory, frameworks, shaders, WebXR |
game-control-mechanisms.md | Touch, keyboard, mouse, and gamepad controls |
game-publishing.md | Distribution, promotion, and monetization |
algorithms.md | Raycasting, collision, physics, vector math |
terminology.md | Game development glossary |
game-engine-core-principles.md | Core design principles for game engines |
Troubleshooting
| Issue | Solution |
|---|---|
| Canvas is blank | Check that you are calling drawing methods after getting the context and inside the game loop |
| Game runs at different speeds | Use delta time in update calculations instead of fixed values |
| Collision detection is inconsistent | Use continuous collision detection or reduce time steps for fast-moving objects |
| Audio does not play | Browsers require user interaction before playing audio; trigger playback from a click handler |
| Performance is poor | Profile with browser dev tools, reduce draw calls, use object pooling, and optimize asset sizes |
| Touch controls are unresponsive | Prevent default touch behavior and handle touch events separately from mouse events |
| WebGL context lost | Handle the webglcontextlost event and restore state on webglcontextrestored |
Files
- ATTRIBUTION.mdAdded by AutoGineer851 B
- LICENSELicence file added by AutoGineer1.0 KiB
- SKILL.md6.2 KiB
- assets/2d-maze-game.md13.0 KiB
- assets/2d-platform-game.md58.2 KiB
- assets/gameBase-template-repo.md9.9 KiB
- assets/paddle-game-template.md38.4 KiB
- assets/simple-2d-engine.md14.0 KiB
- references/3d-web-games.md23.9 KiB
- references/algorithms.md24.5 KiB
- references/basics.md10.9 KiB
- references/game-control-mechanisms.md19.1 KiB
- references/game-engine-core-principles.md21.6 KiB
- references/game-publishing.md13.7 KiB
- references/techniques.md27.4 KiB
- references/terminology.md19.4 KiB
- references/web-apis.md51.4 KiB
references/techniques.md
Game Development Techniques
A comprehensive reference covering essential techniques for building web-based games, compiled from MDN Web Docs.
Async Scripts
Source: MDN - Async Scripts for asm.js
What It Is
Async compilation allows JavaScript engines to compile asm.js code off the main thread during game loading and cache the generated machine code. This prevents recompilation on subsequent loads and gives the browser maximum flexibility to optimize the compilation process.
How It Works
When a script is loaded asynchronously, the browser can compile it on a background thread while the main thread continues handling rendering and user interaction. The compiled code is cached so future visits skip recompilation entirely.
When to Use It
Medium or large games that compile asm.js code.
Any game where startup performance matters (which is virtually all games).
When you want the browser to cache compiled machine code across sessions.
Code Examples
HTML attribute approach:
<script async src="file.js"></script>JavaScript dynamic creation (defaults to async):
const script = document.createElement("script");
script.src = "file.js";
document.body.appendChild(script);Important: Inline scripts are never async, even with the async attribute. They compile and run immediately:
<!-- This is NOT async despite the attribute -->
<script async>
// Inline JavaScript code
</script>Using Blob URLs for async compilation of string-based code:
const blob = new Blob([codeString]);
const script = document.createElement("script");
const url = URL.createObjectURL(blob);
script.onload = script.onerror = () => URL.revokeObjectURL(url);
script.src = url;
document.body.appendChild(script);The key insight is that setting src (rather than innerHTML or textContent) triggers async compilation.
Optimizing Startup Performance
Source: MDN - Optimizing Startup Performance
What It Is
A collection of strategies for improving how quickly web applications and games start up and become responsive, preventing the app, browser, or device from appearing frozen to users.
How It Works
The core principle is avoiding blocking the main thread during startup. Work is offloaded to background threads (Web Workers), startup code is broken into small micro-tasks, and the main thread is kept free for user events and rendering. The event loop must keep cycling continuously.
When to Use It
Always -- this is a universal concern for all web applications and games.
Critical for new apps since it is easier to build asynchronously from the start.
Essential when porting native apps that expect synchronous loading and need refactoring.
Key Techniques
1. Script Loading with defer and async
Prevent blocking HTML parsing:
<script defer src="app.js"></script>
<script async src="helper.js"></script>2. Web Workers for Heavy Processing
Move data fetching, decoding, and calculations to workers. This frees the main thread for UI and user events.
3. Data Processing
Use browser-provided decoders (image, video) instead of custom implementations.
Process data in parallel whenever possible, not sequentially.
Offload asset decoding (e.g., JPEG to raw texture data) to workers.
4. Resource Loading
Do not include scripts or stylesheets outside the critical rendering path in the startup HTML -- load them only when needed.
Use resource hints:
preconnect,preload.
5. Code Size and Compression
Minify JavaScript files.
Use Gzip or Brotli compression.
Optimize and compress data files.
6. Perceived Performance
Display splash screens to keep users engaged.
Show progress indicators for heavy sites.
Make time feel faster even if absolute duration stays the same.
7. Emscripten Main Loop Blockers (for ported apps)
emscripten_push_main_loop_blocker();
// Establish functions to execute before main thread continues
// Create queue of functions called in sequencePerformance Targets
| Metric | Target |
|---|---|
| Initial content appearance | 1-2 seconds |
| User-perceptible delay | 50ms or less |
| Sluggish threshold | Greater than 200ms |
Users on older or slower devices experience longer delays than developers -- always optimize accordingly.
WebRTC Data Channels
Source: MDN - WebRTC Data Channels
What It Is
WebRTC data channels let you send text or binary data over an active connection to a peer. In the context of games, this enables players to send data to each other for text chat or game state synchronization, without routing through a central server.
How It Works
WebRTC establishes a peer-to-peer connection between two browsers. Once established, a data channel can be opened on that connection. Data channels come in two flavors:
Reliable Channels:
Guarantee that messages arrive at the peer.
Maintain message order -- messages arrive in the same sequence they were sent.
Analogous to TCP sockets.
Unreliable Channels:
Make no guarantees about message delivery.
Messages may not arrive in any particular order.
Messages may not arrive at all.
Analogous to UDP sockets.
When to Use It
Reliable channels: Turn-based games, chat, or any scenario where every message must arrive in order.
Unreliable channels: Real-time action games where low latency matters more than guaranteed delivery (e.g., position updates where stale data is worse than missing data).
Use Cases in Games
Player-to-player text chat communication.
Game status information exchange between players.
Real-time game state synchronization.
Peer-to-peer multiplayer without a dedicated game server.
Implementation Notes
The WebRTC API is primarily known for audio and video communication but includes robust peer-to-peer data channel capabilities.
Libraries are recommended to simplify implementation and work around browser differences.
Full WebRTC documentation is available at MDN WebRTC API.
Audio for Web Games
Source: MDN - Audio for Web Games
What It Is
Audio provides feedback and atmosphere in web games. This technique covers implementing audio across desktop and mobile platforms, addressing browser differences and optimization strategies.
How It Works
Two primary APIs are available:
HTMLMediaElement -- The standard
<audio>element for basic audio playback.Web Audio API -- An advanced API for dynamic audio manipulation, positioning, and precise timing.
When to Use It
Use
<audio>elements for simple, linear playback (background music without complex control).Use the Web Audio API for dynamic music, 3D spatial audio, precise timing, and real-time manipulation.
Use audio sprites when targeting mobile or when you have many short sound effects.
Key Challenges on Mobile
Autoplay policy: Browsers restrict autoplay with sound. Playback must be user-initiated via click or tap.
Volume control: Mobile browsers may disable programmatic volume control to preserve OS-level user control.
Buffering/preloading: Mobile browsers often disable buffering before playback initiation to reduce data usage.
Technique 1: Audio Sprites
Combines multiple audio clips into a single file, playing specific sections by timestamp, borrowed from the CSS sprites concept.
HTML:
<audio id="myAudio" src="mysprite.mp3"></audio>
<button data-start="18" data-stop="19">0</button>
<button data-start="16" data-stop="17">1</button>
<button data-start="14" data-stop="15">2</button>
<button data-start="12" data-stop="13">3</button>
<button data-start="10" data-stop="11">4</button>
<button data-start="8" data-stop="9">5</button>
<button data-start="6" data-stop="7">6</button>
<button data-start="4" data-stop="5">7</button>
<button data-start="2" data-stop="3">8</button>
<button data-start="0" data-stop="1">9</button>JavaScript:
const myAudio = document.getElementById("myAudio");
const buttons = document.getElementsByTagName("button");
let stopTime = 0;
for (const button of buttons) {
button.addEventListener("click", () => {
myAudio.currentTime = button.dataset.start;
stopTime = Number(button.dataset.stop);
myAudio.play();
});
}
myAudio.addEventListener("timeupdate", () => {
if (myAudio.currentTime > stopTime) {
myAudio.pause();
}
});Priming audio for mobile (trigger on first user interaction):
const myAudio = document.createElement("audio");
myAudio.src = "my-sprite.mp3";
myAudio.play();
myAudio.pause();Technique 2: Web Audio API Multi-Track Music
Load and synchronize separate audio tracks with precise timing.
Create audio context and load files:
const audioCtx = new AudioContext();
async function getFile(filepath) {
const response = await fetch(filepath);
const arrayBuffer = await response.arrayBuffer();
const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
return audioBuffer;
}Track playback with synchronization:
let offset = 0;
function playTrack(audioBuffer) {
const trackSource = audioCtx.createBufferSource();
trackSource.buffer = audioBuffer;
trackSource.connect(audioCtx.destination);
if (offset === 0) {
trackSource.start();
offset = audioCtx.currentTime;
} else {
trackSource.start(0, audioCtx.currentTime - offset);
}
return trackSource;
}Handle autoplay policy in playback handlers:
playButton.addEventListener("click", () => {
if (audioCtx.state === "suspended") {
audioCtx.resume();
}
playTrack(track);
playButton.dataset.playing = true;
});Technique 3: Beat-Synchronized Track Playback
For seamless transitions, sync new tracks to beat boundaries:
const tempo = 3.074074076; // Time in seconds of your beat/bar
if (offset === 0) {
source.start();
offset = context.currentTime;
} else {
const relativeTime = context.currentTime - offset;
const beats = relativeTime / tempo;
const remainder = beats - Math.floor(beats);
const delay = tempo - remainder * tempo;
source.start(context.currentTime + delay, relativeTime + delay);
}Technique 4: Positional Audio (3D Spatialization)
Use the PannerNode to position audio in 3D space:
Position objects in game world space.
Set direction and movement of audio sources.
Apply environmental effects (cave reverb, underwater muffling, etc.).
Particularly useful for WebGL 3D games to tie audio to visual objects and the player's viewpoint.
Decision Matrix
| Technique | Use When | Pros | Cons |
|---|---|---|---|
| Audio Sprites | Many short sounds, mobile | Reduces HTTP requests, mobile-friendly | Seeking accuracy reduced at low bitrates |
Basic <audio> | Simple linear playback | Broad support | Limited control, autoplay restrictions |
| Web Audio API | Dynamic music, 3D positioning, precise timing | Full control, real-time manipulation, sync | More complex code |
| Positional Audio | 3D immersive games | Realism, player immersion | Requires WebGL context awareness |
2D Collision Detection
Source: MDN - 2D Collision Detection
What It Is
2D collision detection algorithms determine when game entities overlap or intersect based on their shape types (rectangle-to-rectangle, rectangle-to-circle, circle-to-circle, etc.). Rather than pixel-perfect detection, games typically use simple generic shapes called "hitboxes" that cover entities, balancing visual accuracy with performance.
How It Works
Each algorithm checks the geometric relationship between two shapes. If any overlap is detected, a collision is reported. The approach varies by shape type.
When to Use It
Use AABB for simple rectangular entities without rotation.
Use circle collision for round entities or when you need fast, simple checks.
Use the Separating Axis Theorem (SAT) for complex convex polygons.
Use broad-phase narrowing (quad trees, spatial hashmaps) when you have many entities.
Algorithm 1: Axis-Aligned Bounding Box (AABB)
Collision detection between two axis-aligned rectangles (no rotation). Detects collision by ensuring there is no gap between any of the 4 sides of the rectangles.
class BoxEntity extends BaseEntity {
width = 20;
height = 20;
isCollidingWith(other) {
return (
this.position.x < other.position.x + other.width &&
this.position.x + this.width > other.position.x &&
this.position.y < other.position.y + other.height &&
this.position.y + this.height > other.position.y
);
}
}Algorithm 2: Circle Collision
Collision detection between two circles. Takes the center points of two circles and checks whether the distance between them is less than the sum of their radii.
class CircleEntity extends BaseEntity {
radius = 10;
isCollidingWith(other) {
const dx =
this.position.x + this.radius - (other.position.x + other.radius);
const dy =
this.position.y + this.radius - (other.position.y + other.radius);
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < this.radius + other.radius;
}
}Note: The circle's x and y coordinates refer to their top-left corner, so you must add the radius to compare their actual centers.
Algorithm 3: Separating Axis Theorem (SAT)
A collision algorithm that detects collisions between any two convex polygons. It works by projecting each polygon onto every possible axis and checking for overlap. If any axis shows a gap, the polygons are not colliding.
SAT is more complex to implement but handles arbitrary convex polygon shapes.
Collision Performance: Broad Phase and Narrow Phase
Testing every entity against every other entity is computationally expensive (O(n^2)). Games split collision detection into two phases:
Broad Phase -- Uses spatial data structures to quickly identify which entities could be colliding:
Quad Trees
R-Trees
Spatial Hashmaps
Narrow Phase -- Applies precise collision algorithms (AABB, Circle, SAT) only to the small list of candidates from the broad phase.
Base Engine Code
CSS for collision visualization:
.entity {
display: inline-block;
position: absolute;
height: 20px;
width: 20px;
background-color: blue;
}
.movable {
left: 50px;
top: 50px;
background-color: red;
}
.collision-state {
background-color: green !important;
}JavaScript collision checker and entity system:
const collider = {
moveableEntity: null,
staticEntities: [],
checkCollision() {
const isColliding = this.staticEntities.some((staticEntity) =>
this.moveableEntity.isCollidingWith(staticEntity),
);
this.moveableEntity.setCollisionState(isColliding);
},
};
const container = document.getElementById("container");
class BaseEntity {
ref;
position;
constructor(position) {
this.position = position;
this.ref = document.createElement("div");
this.ref.classList.add("entity");
this.ref.style.left = `${this.position.x}px`;
this.ref.style.top = `${this.position.y}px`;
container.appendChild(this.ref);
}
shiftPosition(dx, dy) {
this.position.x += dx;
this.position.y += dy;
this.redraw();
}
redraw() {
this.ref.style.left = `${this.position.x}px`;
this.ref.style.top = `${this.position.y}px`;
}
setCollisionState(isColliding) {
if (isColliding && !this.ref.classList.contains("collision-state")) {
this.ref.classList.add("collision-state");
} else if (!isColliding) {
this.ref.classList.remove("collision-state");
}
}
isCollidingWith(other) {
throw new Error("isCollidingWith must be implemented in subclasses");
}
}
document.addEventListener("keydown", (e) => {
e.preventDefault();
switch (e.key) {
case "ArrowLeft":
collider.moveableEntity.shiftPosition(-5, 0);
break;
case "ArrowUp":
collider.moveableEntity.shiftPosition(0, -5);
break;
case "ArrowRight":
collider.moveableEntity.shiftPosition(5, 0);
break;
case "ArrowDown":
collider.moveableEntity.shiftPosition(0, 5);
break;
}
collider.checkCollision();
});Tilemaps
Source: MDN - Tilemaps
What It Is
Tilemaps are a fundamental technique in 2D game development that constructs game worlds using small, regular-shaped images called tiles. Instead of storing large monolithic level images, the game world is assembled from a grid of reusable tile graphics, providing significant performance and memory benefits.
How It Works
Core structure:
Tile Atlas (Spritesheet): All tile images stored in a single atlas file. Each tile is assigned an index used as its identifier.
Tilemap Data Object: Contains tile size (pixel dimensions), image atlas reference, map dimensions (in tiles or pixels), a visual grid (array of tile indices), and an optional logic grid (collision, pathfinding, spawn data).
Special values (negative numbers, 0, or null) represent empty tiles.
When to Use It
Building 2D game worlds of any kind (platformers, RPGs, strategy games, puzzle games).
Games inspired by classics like Super Mario Bros, Pacman, Zelda, Starcraft, or Sim City.
Any scenario where a grid-based world offers logical advantages for pathfinding, collision, or level editing.
Rendering Static Tilemaps
For maps fitting entirely on screen:
for (let column = 0; column < map.columns; column++) {
for (let row = 0; row < map.rows; row++) {
const tile = map.getTile(column, row);
const x = column * map.tileSize;
const y = row * map.tileSize;
drawTile(tile, x, y);
}
}Scrolling Tilemaps with Camera
Convert between world coordinates (level position) and screen coordinates (rendered position):
// These functions assume camera points to top-left corner
function worldToScreen(x, y) {
return { x: x - camera.x, y: y - camera.y };
}
function screenToWorld(x, y) {
return { x: x + camera.x, y: y + camera.y };
}Key principle: Only render visible tiles to optimize performance. Apply the camera offset transformation during rendering.
Tilemap Types
Square Tiles (most common):
Top-down view for RPGs and strategy games (Warcraft 2, Final Fantasy).
Side view for platformers (Super Mario Bros).
Isometric Tilemaps:
Creates the illusion of a 3D environment.
Popular in simulation and strategy games (SimCity 2000, Pharaoh, Final Fantasy Tactics).
Layers
Multiple visual layers enable:
Reusing tiles across different background types.
Characters appearing behind or in front of terrain (walking behind trees).
Richer worlds with fewer tile variations.
Example: A rock tile rendered on a separate layer over grass, sand, or brick backgrounds.
Logic Grid
A separate grid for non-visual game logic:
Collision detection: Mark walkable vs. blocked tiles.
Character spawning: Define spawn point locations.
Pathfinding: Create navigation graphs.
Tile combinations: Detect valid patterns (Tetris, Bejeweled).
Performance Optimization
Only render visible tiles -- Skip off-screen tiles entirely.
Pre-render to canvas -- Render the map to an off-screen canvas element and blit as a single operation.
Offcanvas buffering -- Draw a section larger than the visible area (2x2 tiles bigger) to reduce redraws during scrolling.
Chunking -- Divide large tilemaps into sections (e.g., 10x10 tile chunks), pre-render each as a "big tile."
Controls: Gamepad API
Source: MDN - Controls Gamepad API
What It Is
The Gamepad API provides an interface for detecting and using gamepad controllers in web browsers without plugins. It exposes button presses and axis changes through JavaScript, allowing console-like control of browser-based games.
How It Works
Two fundamental events handle the controller lifecycle:
gamepadconnected-- fired when a gamepad is connected.gamepaddisconnected-- fired when disconnected (physically or due to inactivity).
Security note: User interaction with the controller is required while the page is visible for the event to fire (prevents fingerprinting).
Gamepad object properties:
| Property | Description |
|---|---|
id | String containing controller information |
index | Unique identifier for the connected device |
connected | Boolean indicating connection status |
mapping | Layout type ("standard" is the common option) |
axes | Array of floats (-1 to 1) representing analog stick positions |
buttons | Array of GamepadButton objects with pressed and value properties |
When to Use It
When building games that should work with console controllers.
When supporting Xbox 360, Xbox One, PS3, or PS4 controllers on Windows and macOS.
When you want dual input support (keyboard + gamepad).
Code Examples
Basic setup structure:
const gamepadAPI = {
controller: {},
turbo: false,
connect() {},
disconnect() {},
update() {},
buttonPressed() {},
buttons: [],
buttonsCache: [],
buttonsStatus: [],
axesStatus: [],
};Button layout (Xbox 360):
const gamepadAPI = {
buttons: [
"DPad-Up", "DPad-Down", "DPad-Left", "DPad-Right",
"Start", "Back", "Axis-Left", "Axis-Right",
"LB", "RB", "Power", "A", "B", "X", "Y",
],
};Event listeners:
window.addEventListener("gamepadconnected", gamepadAPI.connect);
window.addEventListener("gamepaddisconnected", gamepadAPI.disconnect);Connection and disconnection handlers:
connect(evt) {
gamepadAPI.controller = evt.gamepad;
gamepadAPI.turbo = true;
console.log("Gamepad connected.");
},
disconnect(evt) {
gamepadAPI.turbo = false;
delete gamepadAPI.controller;
console.log("Gamepad disconnected.");
},Update method (called every frame):
update() {
// Clear the buttons cache
gamepadAPI.buttonsCache = [];
// Move the buttons status from the previous frame to the cache
for (let k = 0; k < gamepadAPI.buttonsStatus.length; k++) {
gamepadAPI.buttonsCache[k] = gamepadAPI.buttonsStatus[k];
}
// Clear the buttons status
gamepadAPI.buttonsStatus = [];
// Get the gamepad object
const c = gamepadAPI.controller || {};
// Loop through buttons and push the pressed ones to the array
const pressed = [];
if (c.buttons) {
for (let b = 0; b < c.buttons.length; b++) {
if (c.buttons[b].pressed) {
pressed.push(gamepadAPI.buttons[b]);
}
}
}
// Loop through axes and push their values to the array
const axes = [];
if (c.axes) {
for (const ax of c.axes) {
axes.push(ax.toFixed(2));
}
}
// Assign received values
gamepadAPI.axesStatus = axes;
gamepadAPI.buttonsStatus = pressed;
return pressed;
},Button detection with hold support:
buttonPressed(button, hold) {
let newPress = false;
if (gamepadAPI.buttonsStatus.includes(button)) {
newPress = true;
}
if (!hold && gamepadAPI.buttonsCache.includes(button)) {
newPress = false;
}
return newPress;
},Parameters:
button-- the button name to listen for.hold-- if true, holding the button counts as continuous action; if false, only new presses register.
Usage in a game loop:
if (gamepadAPI.turbo) {
if (gamepadAPI.buttonPressed("A", "hold")) {
this.turbo_fire();
}
if (gamepadAPI.buttonPressed("B")) {
this.managePause();
}
}Analog stick input with threshold (prevent stick drift):
if (gamepadAPI.axesStatus[0].x > 0.5) {
this.player.angle += 3;
this.turret.angle += 3;
}Getting all connected gamepads:
const gamepads = navigator.getGamepads();
// Returns an array where unavailable/disconnected slots contain null
// Example with one device at index 1: [null, [object Gamepad]]Crisp Pixel Art Look
Source: MDN - Crisp Pixel Art Look
What It Is
A technique for rendering pixel art without blurriness on high-resolution displays by mapping individual image pixels to blocks of screen pixels without smoothing interpolation. Retro pixel art requires preserving hard edges during scaling, but modern browsers default to smoothing algorithms that blend colors and create blur.
How It Works
The CSS image-rendering property controls how browsers scale images. Setting it to pixelated enforces nearest-neighbor scaling, which preserves the crisp, blocky look of pixel art instead of applying bilinear or bicubic smoothing.
Key CSS values:
pixelated-- preserves crisp edges for pixel art.crisp-edges-- alternative supported on some browsers.
When to Use It
Retro-style games with pixel art assets.
Any game where you want a deliberately blocky, pixelated visual style.
When scaling small sprite images to larger display sizes.
Technique 1: Scaling <img> Elements with CSS
<img
src="character.png"
alt="pixel art character, upscaled with CSS, appearing crisp" />img {
width: 48px;
height: 136px;
image-rendering: pixelated;
}Technique 2: Crisp Pixel Art in Canvas
Set the canvas width/height attributes to the original pixel art resolution, then use CSS width/height for scaling (e.g., 4x scale: 128 pixels to 512px CSS width).
<canvas id="game" width="128" height="128">A cat</canvas>canvas {
width: 512px;
height: 512px;
image-rendering: pixelated;
}const ctx = document.getElementById("game").getContext("2d");
const image = new Image();
image.onload = () => {
ctx.drawImage(image, 0, 0);
};
image.src = "cat.png";Technique 3: Arbitrary Canvas Scaling with Correction
For non-integer scale factors, image pixels must align to canvas pixels at integer multiples:
const ctx = document.getElementById("game").getContext("2d");
ctx.scale(0.8, 0.8);
const image = new Image();
image.onload = () => {
// Correct formula: dWidth = sWidth / xScale * n (where n is an integer)
ctx.drawImage(image, 0, 0, 128, 128, 0, 0, 128 / 0.8, 128 / 0.8);
};
image.src = "cat.png";When using drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight):
dWidthmust equalsWidth / xScale * ndHeightmust equalsHeight / yScale * mWhere
nandmare positive integers (1, 2, 3, etc.)
Known Limitations
devicePixelRatio misalignment: When devicePixelRatio is not an integer (e.g., at 110% browser zoom), pixels may render unevenly because CSS pixels cannot perfectly map to device pixels. This creates a non-uniform appearance without an easy solution.
Best Practices
Use integer scale factors (2x, 3x, 4x) whenever possible.
Preserve the aspect ratio -- scale width and height equally.
Test across different browser zoom levels.
Avoid fractional canvas scale factors or drawImage dimensions.
Include descriptive
aria-labelattributes on canvas elements for accessibility.
Requirements and compatibility
Written for: GitHub Copilot
Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta
“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.
Security
No issues found by our static scan· No issues found
A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.
Attribution and licence
- First committed by
jhauga
- Publisher
- GitHub (Official)
- Distributed by
- GitHub
- Source
- github/awesome-copilot · skills/game-engine · commit 797337f
- Licence
- MIT License (redistribution allowed)
- LICENSE (added by AutoGineer from LICENSE)
- ATTRIBUTION.md (written by AutoGineer)
- Synced
- Contributors
- jhauga
AutoGineer is not affiliated with or endorsed by jhauga or GitHub.
Download ZIP
The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download game-engine.zip