q5play logo

Teach with

q5play

Level up from block-based coding!

$6
per student
/ semester
Total: $180
Buy Now
+
LMS Bundle
  • q5 Web Editor with dedicated support for q5play
  • Assign, collect, and grade students' q5play projects
$3
per student
/ semester
Additional: $90 [50% discount applied]
Subscribe
interactive-textbook

Interactive Textbook

The Learn q5play interactive textbook makes Object Oriented Programming concepts tangible!

Available in JavaScript and Python editions.

Contains over 50 pages of reference documentation packed with 150 code examples that students can experiment with right in their web browser. Works great on iPads and Chromebooks.

Teachers can evaluate this resource for free.

game design

GDF Curriculum

q5play Game Design Fundamentals gives students a broad introduction to the art of game design.

Contains 7 lessons with slides and activities that facilitate class discussion and student collaboration.

Even if you're not a "gamer", you can still get competently teach game design!

certificate

How it Works

After purchase of the q5play Educational License, you'll be given a class ID for students to use.

No personal info is required for students to create an account on q5play.org and they can keep using their account after the licensing term ends.

Students fully own the content they make with q5play, and can even sell their games under the terms of the q5play Creator License for free.

teaching

Codevre Teach

Codevre's q5 Web Editor has powerful IDE features wrapped in a minimal UI that's easy for students to navigate. Inline documentation helps students learn about q5play's features as they code. No install required!

Codevre Teach provides an all-in-one LMS that teachers can use to assign, collect, and grade q5play projects.

q5play
Edu License
LMS Bundle
Academic use of q5play.js
Academic use of the Learn q5play interactive textbook
COPPA compliant q5play.org student accounts (no personal data)
q5play Game Design Fundamentals curriculum
q5play Visual Studio Code Extension
2 hour Professional Development session for teachers
COPPA compliant codevre.com student accounts (no personal data)
Cloud storage for over 2,000 projects made with Codevre's q5 Web Editor
LMS teachers can use to assign, collect & grade student's q5play projects
$6
per student /
semester
Total: $180
Buy Now

Looking for a school or district wide license?

Request a Quote

Compare q5play to...

Scratch 🐯

Scratch is fantastic platform for students ages 8–11, but its guardrails quickly become limiting for older students ready to graduate from blocks to text-based coding.

Scratch has no built-in physics engine, so something as simple as rolling a ball down a slope requires significant math that's tedious to implement in Scratch's block-based environment.

Many of the featured projects on the Scratch website were evidently made by adults challenging themselves to push beyond Scratch's practical limits. This sets unrealistic expectations for students.

In contrast, q5play empowers students with beginner-friendly APIs that make professional-grade physics accessible to anyone. By handling common boilerplate tasks under the hood, students can focus on the creative aspects of game design.

Technical Comparison

q5play vs. Scratch 🐯

Manual Math vs. Built-in Physics

Something as basic as rolling a ball down a slope is a nightmare to implement in Scratch.

Naive attempts are typically inefficient and brittle, with user code that manually manages velocity, gravity, and collision resolution. Often the result is jittering, tunneling, and other glitches inexplicable to beginners.

// Scratch blocks (Ball sprite)
// Students must manually manage vectors, slopes, and collision resolution.
when green flag clicked
set [slope_angle v] to (15)
set [vx v] to (0)
set [vy v] to (0)

forever
  // 1. Calculate discrete gravity and slope acceleration
  change [vx v] by ((sin of (slope_angle)) * (0.5))
  change [vy v] by ((cos of (slope_angle)) * (-0.5))
  change x by (vx)
  change y by (vy)

  // 2. Manual collision resolution (Prone to severe jitter and tunneling)
  if <touching [Floor v]?> then
    repeat until <not <touching [Floor v]?>>
      change y by (1)
    end
    // 3. Apply fake friction and bounce decay
    set [vx v] to ((vx) * (0.8))
    set [vy v] to ((vy) * (-0.5))
  end
end

q5play eliminates this frustration by using Box2D for physics. Gravity, collision detection, and resolution are handled automatically by the engine. This allows students to create interesting interactions with just a few lines of code, without needing to understand the underlying physics math.

await Canvas();
world.gravity.y = 10;

let ball = new Sprite(-100, -80, 30);
ball.bounciness = 0.5;

let floor = new Sprite(0, 50, 300, 10, 'static');
floor.rotation = 15;

Broadcast Spaghetti vs. Centralized Game Logic

Because Scratch scripts cannot easily communicate, the engine relies heavily on asynchronous broadcast messages to trigger events across dozens of isolated sprite scripts.

This creates "Broadcast Spaghetti": a tangled web of trigger responses that makes it hard for students to track the flow of their own code. Worse, because Scratch processes these scripts asynchronously, it causes race conditions. If a trigger response tries to delete a sprite that another area of code depends on, it could lead to game-breaking, unpredictable bugs.

// Scratch blocks (Coin sprite)
when I receive [collect_coin v]
change [score v] by (1)
delete this clone

// Scratch blocks (Player sprite)
forever
  if <touching [Coin v]?> then
    // Which script processes first? The engine has to guess.
    broadcast [collect_coin v]
  end
end

In q5play, logic is deterministic and centralized. The game flow lives in a single update loop and executes naturally from top to bottom. By using built-in contact methods like overlaps, q5play guarantees that interactions are handled safely, predictably, and in the exact order the programmer specifies.

q5.update = function () {
    // Synchronous, predictable, and structurally clean
    if (player.overlaps(coin)) {
        coin.delete();
        score++;
    }
};

Unity ⚙️

Unity is the industry standard for professional game development, but its editor-first workflow and vast enterprise toolset introduces significant cognitive overhead for beginners.

For university-level Game Design and CS courses, Unity’s complex GUI can obscure fundamental game programming concepts. Students often spend more time navigating the menu mazes of this specific software than mastering core logic.

Unity has to compile after every minor edit students make, which only takes a few seconds, but these micro-interruptions add up as friction that stifles creative experimentation. In contrast, q5play runs instantly in the browser, resulting in a more rewarding feedback loop.

Under the hood, q5play uses the same Box2D v3 physics engine as Unity, so even if your primary goal is teaching Unity, q5play can serve as a pedagogical stepping stone. Once students have a solid foundation in programming logic and physics simulation via q5play, transitioning to Unity becomes far less overwhelming. Although adult students will be able to breeze through learning q5play, it's not just a toy engine for kids. It's designed to be a joy to use for developers of all ages interested in rapidly prototyping games and sharing them on the web.

At the high school level, teaching Unity is often unfeasible. Unity is a massive desktop application, so it can't run on Chromebooks or iPads. q5play runs in the browser, requiring no installation. Additionally, while teaching Unity demands extensive training due to its notoriously steep learning curve, educators can comfortably get up to speed with q5play in a matter of hours. By lowering the barrier to entry, students can accomplish more in a few weeks with q5play than they could after months of struggling with Unity.

Technical Comparison

q5play vs. Unity ⚙️

Context Switching vs. Unified Workflow

Creating a 2D physics interaction in Unity requires constant context switching. A developer must navigate the Inspector to configure Rigidbodies, adjust physics materials, define Layer Collision Matrices, assign Tags, and then switch to an IDE to bind it all together with C#.

Even idiomatic, well-written Unity C# requires boilerplate to cache components and safely hook into the physics step:

// Unity C#
[RequireComponent(typeof(Rigidbody2D))]
public class Player : MonoBehaviour {
    private Rigidbody2D rb;

    void Awake() {
        rb = GetComponent<Rigidbody2D>();
    }

    void OnCollisionEnter2D(Collision2D col) {
        if (col.gameObject.CompareTag("Floor")) {
            Debug.Log("Landed!");
        }
    }
}

In q5play, that entire workflow, creating the player with a physics collider and assigning an image, is accomplished in just a few lines of highly readable code. This unified approach eliminates Editor-IDE friction, making it ideal for rapid prototyping:

let player = new Sprite(0, 0, 50);
player.img = 'assets/player.webp';

q5.update = function () {
    if (player.collides(floor)) {
        console.log('landed!');
    }
};

Editor-Driven Components vs. Code-First State

Unity enforces a strict Component-based architecture (GameObjects and MonoBehaviours). While essential for scaling AAA games, this scatters game state across serialized Inspector variables, prefabs, and isolated scripts. For students or developers testing a quick mechanic, tracing how a variable mutates across these layers can be needlessly complex.

q5play is built on straightforward, code-first Entity principles. A Sprite is a unified object where physical properties (x, y, velocity, mass, bounciness) are manipulated directly in the script. This 1:1 mapping between the typed code and the on-screen outcome reinforces foundational programming paradigms and allows developers to test gameplay systems instantly without configuring Editor serialization.

Input Complexity vs. Readability

Handling inputs in Unity for production requires robust solutions like the newer Input System package, which entails configuring Action Maps, creating input assets, and passing callback contexts. It is brilliant for handling simultaneous controller support, but massive overkill for testing a jump mechanic in a prototype.

Even using the legacy immediate-mode input requires verbose code:

// Unity C#
if (Input.GetKeyDown(KeyCode.Space)) {
    GetComponent<Rigidbody2D>().AddForce(new Vector2(0, 10), ForceMode2D.Impulse);
}

q5play handles device inputs natively with a syntax tailored for readability. Keyboard, mouse, and touch states are accessible directly within the update loop, requiring zero prior configuration:

if (kb.presses('space')) {
    player.vel.y = -10;
}

Engine Bloat vs. Web-Native Performance

Unity is fundamentally designed as a heavy-duty production engine. Exporting a Unity game to the web, even with modern WebGPU support, relies on IL2CPP (compiling C# to C++ to WebAssembly) alongside heavy rendering pipelines. This results in significant compilation times and large payload sizes, which creates friction for peer review, game jams, and portfolio building.

q5play is web-native from the ground up. It leverages modern WebGPU rendering alongside Box2D compiled to WebAssembly to deliver hardware-accelerated performance directly in the browser.

Because the library is lightweight, there are zero compilation cycles. The code executes instantly on a page refresh. For courses prioritizing iteration velocity, peer playtesting, and portfolio deployment, q5play provides a much tighter, more rewarding design loop than wrangling WebGL builds in a traditional engine.

p5.js 🌸

p5.js is a fantastic tool for creative coding, but it's not a game engine. It has no built-in support for game physics, collision resolution, sprite management, state machines, or hardware game controllers.

q5, the underlying graphics library q5play is built on, was heavily inspired by p5.js but utilizes WebGPU rendering. q5 offers up to ~100× better performance than p5's fastest renderer (Canvas2D).

For teachers already familiar with p5.js, the transition is virtually frictionless. q5's API is largely compatible with p5, so existing knowledge carries over pretty seamlessly.

Importantly, Codevre's q5 Web Editor supports using q5play in Python, whereas OpenProcessing and the p5.js Web Editor do not.

Technical Comparison

q5play vs. p5.js 🌸

Manual Math vs. Box2D Physics

Without a physics engine, p5.js requires writing gravity and collision math entirely by hand. Adding a tilted floor means transforming to floor-local space to detect the collision, computing the surface normal to reflect velocity correctly, and nudging the ball out of the floor along that normal:

// p5.js
let ball = { x: 150, y: 50, r: 25, vx: 0, vy: 0 };
let floor = { cx: 200, cy: 300, w: 300, h: 20, angle: 15 };

function setup() {
    createCanvas(400, 400);
}

function draw() {
    clear();

    // 1. Apply gravity to velocity FIRST
    ball.vy += 0.2;

    // 2. Apply velocity to position BEFORE checking collisions
    ball.x += ball.vx;
    ball.y += ball.vy;

    // 3. Transform the NEW position into the floor's local space
    let rad = (floor.angle * Math.PI) / 180;
    let dx = ball.x - floor.cx;
    let dy = ball.y - floor.cy;

    let localX = dx * Math.cos(-rad) - dy * Math.sin(-rad);
    let localY = dx * Math.sin(-rad) + dy * Math.cos(-rad);

    // 4. Transform velocity into local space
    let localVx = ball.vx * Math.cos(-rad) - ball.vy * Math.sin(-rad);
    let localVy = ball.vx * Math.sin(-rad) + ball.vy * Math.cos(-rad);

    // 5. Collision Check
    let floorTop = -floor.h / 2;

    if (Math.abs(localX) < floor.w / 2 + ball.r && localY + ball.r > floorTop && localY - ball.r < floor.h / 2) {
        if (localVy > 0) {
            // Only bounce if it's moving downwards into the floor

            // Bounce (restitution)
            localVy = -localVy * 0.5;

            // Friction (so it doesn't slide like it's on pure ice)
            localVx *= 0.99;

            // Push ball safely out of the floor
            localY = floorTop - ball.r;

            // 6. Convert the corrected local values BACK to world space
            ball.x = floor.cx + localX * Math.cos(rad) - localY * Math.sin(rad);
            ball.y = floor.cy + localX * Math.sin(rad) + localY * Math.cos(rad);

            ball.vx = localVx * Math.cos(rad) - localVy * Math.sin(rad);
            ball.vy = localVx * Math.sin(rad) + localVy * Math.cos(rad);
        }
    }

    // --- DRAWING ---
    push();
    translate(floor.cx, floor.cy);
    rotate(rad);
    fill(100);
    rect(-floor.w / 2, -floor.h / 2, floor.w, floor.h);
    pop();

    fill(255, 100, 100);
    circle(ball.x, ball.y, ball.r * 2);
}

In q5play, Box2D handles gravity, collision detection, and normal resolution automatically:

await Canvas(400);
world.gravity.y = 10;

let ball = new Sprite(0, -150, 50);
ball.bounciness = 0.5;

let floor = new Sprite(0, 150, 400, 10, STATIC);
floor.rotation = 10;

Manual vs. Automatic Animations

Loading a spritesheet animation in p5.js requires manually tracking a frame index, advancing it each draw call with a timer, and cropping the spritesheet with 9-argument image() to render each frame:

// p5.js
let sheet;
let frameW = 32,
    frameH = 32,
    numFrames = 8;
let frameIndex = 0,
    frameTimer = 0,
    frameDelay = 6;
let player = { x: 200, y: 200 };

function preload() {
    sheet = loadImage('assets/run.png');
}

function setup() {
    createCanvas(400, 400);
}

function draw() {
    clear();

    frameTimer++;
    if (frameTimer >= frameDelay) {
        frameTimer = 0;
        frameIndex = (frameIndex + 1) % numFrames;
    }

    let sx = frameIndex * frameW;
    image(sheet, player.x - frameW / 2, player.y - frameH / 2, frameW, frameH, sx, 0, frameW, frameH);
}

In q5play, animations can be loaded from a sprite sheet with a single line of code. The ani property of a Sprite is an instance of the built-in Animation class, which automatically handles frame timing and rendering:

let player = new Sprite(0, 0);
player.addAni('assets/run.png', 8);
player.ani.frameDelay = 6;

Arrays vs. Groups Entity Management

Managing groups of enemies in vanilla p5.js means storing object states in arrays and writing manual for loops to update, draw, and check collisions for every single entity.

// p5.js
let enemyImg;
let enemies = [];

function preload() {
    enemyImg = loadImage('assets/enemy.webp');
}

function setup() {
    createCanvas(400, 400);
    for (let i = 0; i < 5; i++) {
        enemies.push({ x: random(width), y: random(height), w: 48, h: 48, vx: random(-2, 2), vy: random(-2, 2) });
    }
}

function draw() {
    clear();
    for (let e of enemies) {
        e.x += e.vx;
        e.y += e.vy;
        image(enemyImg, e.x - e.w / 2, e.y - e.h / 2, e.w, e.h);
    }
}

In q5play, a Group manages shared behavior and images for all its sprites automatically:

await Canvas(400, 400);

let enemies = new Group();
enemies.img = 'assets/enemy.webp';

for (let i = 0; i < 5; i++) {
    let e = new enemies.Sprite(jit(200), jit(200), 48);
    e.vel.x = jit(2);
    e.vel.y = jit(2);
}

Microsoft MakeCode Arcade 🕹️

Microsoft's MakeCode Arcade is an engaging starting point for young coders, but it's too restrictive for students ready to graduate from block-based coding.

While constraints can inspire creativity, MakeCode Arcade's fixed 160×120 screen resolution, basic arcade physics, and inability to load external images are frustrating bottlenecks for older students with greater ambitions. These severe limitations exist because MakeCode is narrowly designed to remain compatible with ultra-low-spec handheld devices, that schools can purchase. q5play allows students to fully utilize the modern computing power already sitting on their desks.

Although MakeCode technically supports switching between blocks, JavaScript, and Python, the platform was clearly designed with block coding as the primary interface. The text-based coding experience is marred by incredibly verbose namespacing, overuse of enums, and callback heavy design patterns. Furthermore, as a closed ecosystem, MakeCode prevents students from exporting their games as standalone web pages, integrating third-party APIs, or interacting with the broader browser environment.

q5play bridges the massive gap between MakeCode Arcade and the complexity of engines like Unity and Godot. By providing professional-grade Box2D physics simulation under the hood without the intimidating overhead of a full AAA toolset, it serves as the ideal next step for students ready to explore modern game design.

Technical Comparison

q5play vs. Microsoft MakeCode Arcade 🕹️

Retro Limitations vs. High-Resolution Assets

MakeCode Arcade forces students to make assets through its built-in pixel-art editor. While convenient for first-time coders, it inherently limits a game's visual identity. Importing high-resolution external images is effectively walled off in the standard web interface.

// MakeCode Arcade: Assets are hardcoded and limited to low-res pixel art grids.
let player = sprites.create(
    img`
  . . . . . . . . . .
  . . . f f f f . . .
  . . f e f f e f . .
  f f f f f f f f f f
  `,
    SpriteKind.Player
);

q5play allows students to decouple their visual assets from their code, seamlessly loading high-resolution images in modern formats like ".webp" and ".avif". This lets students collaborate with digital artists and use assets from the vibrant ecosystem of game art available online.

let player = new Sprite();
player.img = 'player.avif';

But q5play also has a spriteArt function for creating pixel art with text and q5 has displayMode(MAXED, PIXELATED) to achieve a classic retro look when desired. With q5play, students have the freedom to choose their own artistic style.

Callbacks vs. Frame-by-Frame Control

MakeCode Arcade relies heavily on an event-driven architecture. Students register asynchronous callbacks (onOverlap, onEvent) that trigger when specific actions occur. This is an excellent way to introduce event listeners, but as games grow in complexity, scattering logic across multiple asynchronous blocks can make it difficult for students to debug the chronological flow of their game state. Unpredictable execution order can lead to race conditions. Although MakeCode Arcade does have a main update loop, students can not check input or contact states in it.

// MakeCode Arcade
controller.A.onEvent(ControllerButtonEvent.Pressed, function () {
    player.vy = -200;
});

sprites.onOverlap(SpriteKind.Player, SpriteKind.Coin, function (player, coin) {
    coin.destroy();
    info.changeScoreBy(1);
});

q5play has its own event system, but it primarily encourages students to check inputs and collisions directly within the main update loop. This promotes a more intuitive, top-to-bottom flow of logic that is easier to reason about and debug as game states become more complex.

if (contro.presses('a')) {
    player.vel.y = -10;
}

if (player.overlaps(coin)) {
    coin.delete();
    score++;
}

Arcade Physics vs. Box2D v3

The most significant technical limitation of MakeCode Arcade is its physics engine. It utilizes a bare-bones, axis-aligned bounding box (AABB) model. Objects exist as rigid rectangles that cannot rotate. While perfect for recreating grid-based classics like Pac-Man, it cannot handle angular momentum, sloped terrain, or dynamic mass. Furthermore, its lack of Continuous Collision Detection (CCD) means fast-moving objects can easily "tunnel" through walls.

// MakeCode Arcade: Movement is strictly linear. No rotation or dynamic mass.
let crate = sprites.create(img`...`, SpriteKind.Enemy);
crate.vy = 100; // Falls straight down
crate.setBounceOnWall(true); // Basic linear bounce, no energy loss modeling

q5play is powered by Box2D v3, the same industry-standard 2D physics simulator used in the dynamic, physics-driven games that dominates the modern indie market. This unlocks entirely new genres of mechanics for students. Objects possess mass, friction, and restitution (bounciness). Characters can tumble down angled slopes, joints can swing like pendulums, and angular velocity can be manipulated.

let crate = new Sprite(200, 50, 40, 40);
crate.mass = 5;
crate.bounciness = 0.6;
crate.friction = 0.8;

// the crate will naturally tumble and rotate down this slope
let ramp = new Sprite(200, 300, 300, 20, 'static');
ramp.rotation = 15;

q5.update = function () {
    // Apply a rotational force (torque) dynamically
    if (kb.pressing('right')) {
        crate.applyTorque(10);
    }
};

Code.org Game Lab 🧪

Code.org’s Game Lab was an important stepping stone for a generation of students. But despite millions in annual funding from Big Tech corporations and billionaires, it hasn't received a meaningful update in a decade.

Its custom fork of p5.play v1 operates within the fixed resolution of a 400×400 canvas. On typical student hardware its performance is so poor, it can't handle in realtime more than a dozen sprites at once.

Although Game Lab technically supports switching between blocks and JavaScript code, it's a closed sandbox that prevents projects from interacting with the broader browser environment, integrating external JS libraries, or exporting to a standalone web page.

Code.org's shift towards "Hour of AI" initiatives, left their creative coding tools in the dust. These days, Game Lab can only offer disappointment to students capable of more ambitious work with modern web technologies.

If you're looking for a natural upgrade from Code.org's p5.play v1 fork, look no further! q5play takes everything you loved about Code.org Game Lab's p5.play v1 API and puts it in an open platform designed to empower students to build real games backed by professional-grade Box2D physics simulation.

Technical Comparison

q5play vs. Code.org Game Lab 🧪

Explicit Configuration vs. Smart Defaults

A core philosophy of Game Lab is explicit configuration. Sprites are rendered as generic squares without physics colliders unless a student manually defines them. This explicit approach requires students to manage the underlying state of every object constantly. If a student wants to see a collider's shape, they must manually enable debug mode.

In this Game Lab example, creating a bouncing ball requires manually defining the shape, setting the bounciness, and manipulating velocity every frame:

// Code.org Game Lab
var ball = createSprite(20, 50);
ball.setCollider('circle', 0, 0, 25);
ball.debug = true;
ball.bounciness = 0.8;

var floor = createSprite(200, 350);
floor.rotation = 10;
floor.setCollider('rectangle', 0, 0, 400, 10);
floor.debug = true;
floor.immovable = true;

function draw() {
    background('white');

    // Gravity must be applied manually every frame
    ball.velocityY += 0.5;
    ball.bounce(floor);

    drawSprites();
}

By contrast, q5play operates on "smart defaults" driven by an underlying physics engine (Box2D). Sprites are automatically assigned colliders that match their visual dimensions. Because physics are simulated globally rather than per-object, properties like gravity are defined once for the world, resulting in cleaner, more readable code that mirrors professional game engines:

await Canvas(400, 400);
world.gravity.y = 10; // Defined globally once

let ball = new Sprite(0, -150, 50);
ball.bounciness = 0.5;

let floor = new Sprite(0, 150, 400, 10, STATIC);
floor.rotation = 10;

Algorithmic Pairwise vs. Global Simulation

Game Lab teaches collision through explicit, pairwise function calls. The sprite.collide(other) method resolves displacement between two specific sprites at the exact moment it is called in the draw loop.

While this effectively teaches beginners about the order of operations, it quickly becomes unmanageable for complex scenes. To stack multiple boxes, a student must manually check collisions from the bottom up. If the order is wrong, or if a dynamic structure (like a pyramid of crates) is introduced, the pairwise logic fails, and objects clip through one another.

// Code.org Game Lab: Order-dependent pairwise logic
var box1 = createSprite(200, 50, 50, 50);
var box2 = createSprite(200, 150, 50, 50);
var floor = createSprite(200, 380, 400, 20);
floor.immovable = true;

var vy1 = 0,
    vy2 = 0;

function draw() {
    background('white');
    vy1 += 0.5;
    vy2 += 0.5;
    box1.y += vy1;
    box2.y += vy2;

    // Brute-force order of operations is required
    if (box2.collide(floor)) vy2 = 0;
    if (box1.collide(box2)) vy1 = 0;
    if (box1.collide(floor)) vy1 = 0;

    drawSprites();
}

In q5play, the Box2D engine handles collision mathematically and globally. Because objects possess mass, friction, and restitution by default, they interact organically. Stacking a tower of boxes requires zero explicit collision code in the draw loop; the engine resolves all contacts simultaneously during each physics step. This enables students to focus higher up the design stack, rather than getting bogged down in the minutiae of collision order.

await Canvas(400, 400);
world.gravity.y = 10;

let box1 = new Sprite(0, -200, 50);
let box2 = new Sprite(0, -100, 50);
let floor = new Sprite(0, 150, 400, 10, STATIC);

// No explicit collision checks needed; Box2D handles stacking natively.

Phaser 👽

Phaser is a capable JavaScript game engine, but its steep learning curve and boilerplate requirements make it a poor fit for introductory courses.

For example, loading animations from a sprite sheet requires a substantial amount of configuration code in Phaser, whereas the same task takes only a few lines in q5play.

Phaser's team ported the Box2D v3 physics simulator to JavaScript and open sourced it, which was a meaningful contribution to the web gaming ecosystem. However, this decision was driven by the constraints of mobile advertising platforms, which don't allow WebAssembly. Since q5play is built for full game development rather than ad delivery, it uses Box2D v3 compiled to WASM, which offers 2–3x better performance compared to Phaser's JavaScript port.

Technical Comparison

q5play vs. Phaser 👽

Adding Animations

Loading and playing a sprite sheet animation in Phaser requires preloading the asset, defining an animation configuration object, creating the physics sprite, and then playing the animation:

// Phaser
this.load.spritesheet('player', 'assets/player.png', {
    frameWidth: 32,
    frameHeight: 48
});

this.anims.create({
    key: 'walk',
    frames: this.anims.generateFrameNumbers('player', { start: 0, end: 3 }),
    frameRate: 10,
    repeat: -1
});

this.player = this.physics.add.sprite(100, 450, 'player');
this.player.anims.play('walk', true);

In q5play:

let player = new Sprite(0, 0, 32, 48);
player.addAnis({ walk: { row: 0, frames: 4, frameDelay: 6 } });
player.changeAni('walk');

Binding vs. Direct Keyboard Input

Supporting both arrow keys and WASD in Phaser requires calling createCursorKeys() for arrow keys and then separately registering each WASD key with addKeys() using verbose Phaser.Input.Keyboard.KeyCodes constants, all in the scene's create method before any input can be read:

// Phaser
// Inside create():
this.cursors = this.input.keyboard.createCursorKeys();
this.wasd = this.input.keyboard.addKeys({
    up: Phaser.Input.Keyboard.KeyCodes.W,
    left: Phaser.Input.Keyboard.KeyCodes.A,
    down: Phaser.Input.Keyboard.KeyCodes.S,
    right: Phaser.Input.Keyboard.KeyCodes.D
});

// Inside update():
if (this.cursors.left.isDown || this.wasd.left.isDown) {
    this.player.setVelocityX(-200);
} else if (this.cursors.right.isDown || this.wasd.right.isDown) {
    this.player.setVelocityX(200);
} else {
    this.player.setVelocityX(0);
}
if (this.cursors.up.isDown || this.wasd.up.isDown) {
    this.player.setVelocityY(-500);
}

In q5play, no setup is required. Direction names (up, down, left, right) refer to the arrow and WASD keys.

q5.update = function () {
    if (kb.pressing('left')) player.vel.x = -5;
    else if (kb.pressing('right')) player.vel.x = 5;
    else player.vel.x = 0;

    if (kb.presses('up')) player.vel.y = -10;
};

Pygame 🐍

Pygame is a Python library for game development and a common recommendation for educators who teach Python. However, it is considerably more low-level than q5play: there's no built-in physics engine and no animation system. Students end up writing a substantial amount of infrastructure code before they can build anything meaningful.

Pygame also requires a local Python installation, which creates friction in school environments. Projects cannot be easily shared via a URL without relying on third-party compilation tools like pygbag. Students must otherwise distribute files or set up a runtime environment for others to run their work, which is cumbersome.

q5play supports Python and runs entirely in the browser with no installation or setup required. Students can share their games with a single link. For CS teachers who want their students to write Python while building engaging, shareable projects, q5play is the more practical choice.

Technical Comparison

q5play vs. Pygame 🐍

Manual Loops vs. Automated Physics Math

Adding a tilted floor in Pygame requires the same coordinate-space transformation as p5.js; there is no physics engine, so students must transform to floor-local space, compute the surface normal, reflect velocity, and nudge the ball out along that normal. On top of all that, Pygame requires a full game loop with manual event processing and explicit draw calls every frame:

# Pygame
import pygame, math

pygame.init()

screen = pygame.display.set_mode((400, 400))
clock = pygame.time.Clock()
ball = {'x': 150, 'y': 50, 'r': 25, 'vx': 0, 'vy': 0}
floor = {'cx': 200, 'cy': 300, 'w': 300, 'h': 10, 'angle': 10}
running = True

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
    ball['vy'] += 0.5  # gravity
    rad = math.radians(floor['angle'])
    dx = ball['x'] - floor['cx']
    dy = ball['y'] - floor['cy']
    lx = dx * math.cos(-rad) - dy * math.sin(-rad)
    ly = dx * math.sin(-rad) + dy * math.cos(-rad)
    if abs(lx) < floor['w'] / 2 and ly + ball['r'] > 0 and ly - ball['r'] < floor['h']:
        nx = -math.sin(rad)
        ny = math.cos(rad)
        dot = ball['vx'] * nx + ball['vy'] * ny
        if dot < 0:
            ball['vx'] -= 2 * dot * nx
            ball['vy'] -= 2 * dot * ny
        overlap = ball['r'] - ly
        ball['x'] -= overlap * math.sin(rad)
        ball['y'] += overlap * math.cos(rad)
    ball['x'] += ball['vx']
    ball['y'] += ball['vy']
    hw = floor['w'] / 2
    corners = [(-hw, 0), (hw, 0), (hw, floor['h']), (-hw, floor['h'])]
    rotated = [
        (floor['cx'] + x * math.cos(rad) - y * math.sin(rad),
         floor['cy'] + x * math.sin(rad) + y * math.cos(rad))
        for x, y in corners
    ]
    screen.fill((220, 220, 220))
    pygame.draw.polygon(screen, (50, 50, 50), rotated)
    pygame.draw.circle(screen, (0, 100, 200), (int(ball['x']), int(ball['y'])), ball['r'])
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

In q5play's Python mode:

Canvas(400, 400)
world.gravity.y = 10

ball = Sprite.new(0, -150, 50)

floor = Sprite.new(0, 150, 400, 10, STATIC)
floor.rotation = 10

Separated Checks vs. Synchronous Event Loops

Keyboard input in Pygame splits across two separate mechanisms: continuous state requires polling pygame.key.get_pressed() each frame, while single-press detection requires checking KEYDOWN events inside the event loop. Logic that belongs together ends up in two different places:

# Pygame, continuous and single-press input live in different places
while running:
    for event in pygame.event.get():
        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_SPACE:  # jump: only detectable here
                player['vy'] = -10

    keys = pygame.key.get_pressed()
    if keys[pygame.K_LEFT] or keys[pygame.K_a]:
        player['vx'] = -5
    elif keys[pygame.K_RIGHT] or keys[pygame.K_d]:
        player['vx'] = 5
    else:
        player['vx'] = 0
    # ... physics update, draw calls

In q5play's Python mode, pressing and presses unify all input in the same place:

if kb.pressing('left') or kb.pressing('a'):
    player.vel.x = -5
elif kb.pressing('right') or kb.pressing('d'):
    player.vel.x = 5
else:
    player.vel.x = 0

if kb.presses('space'):
    player.vel.y = -10

KaPlay 💥

Kaboom.js was a JavaScript game library that gained some traction in hobbyist communities. It was subsequently rebranded as Kaplay following a change in maintainership.

KaPlay's API introduces a non-standard mental model of creating objects that does not transfer to other libraries or real-world codebases.

Technical Comparison

q5play vs. KaPlay 💥

Function Arrays vs. Declarative Object Creation

KaPlay's core API requires students to create game objects by passing an array of function calls to an add() function, where each element is the return value of a component factory:

// Kaplay
add([
    rect(40, 40),
    pos(100, 200),
    color(0, 0, 255),
    area(),
    body() // huh?
]);

This pattern (calling functions inside an array literal) is not how object oriented programming is typically done in JavaScript or any other programming language. It introduces a non-standard mental model that does not transfer to other libraries or real-world codebases.

In q5play, the same physics-enabled object is created with standard JS class instantiation and property assignment:

let box = new Sprite(100, 200, 40, 40);
box.color = 'blue';

Object Literals vs. Setup Parameters

Even basic setup requires knowledge of object literal syntax. Resizing the canvas means passing a configuration object to the kaplay() initializer:

// Kaplay
kaplay({
    width: 640,
    height: 480
});

For beginners who have not yet learned about objects and key-value pairs, this is a barrier on day one, before a single sprite has been drawn. In q5play, the canvas is created with a straightforward function call:

await Canvas(640, 480);

Separate Listeners vs. Per-Frame Input

Keyboard input in Kaplay requires registering separate onKeyDown and onKeyRelease event handlers for each direction:

// Kaplay
onKeyDown('left', () => {
    player.vel.x = -5;
});
onKeyDown('right', () => {
    player.vel.x = 5;
});
onKeyRelease('left', () => {
    player.vel.x = 0;
});
onKeyRelease('right', () => {
    player.vel.x = 0;
});

In q5play, the same logic is a single conditional block inside the update loop:

q5.update = function () {
    if (kb.pressing('left')) player.vel.x = -5;
    else if (kb.pressing('right')) player.vel.x = 5;
    else player.vel.x = 0;
};