DEV Community

Cover image for Level 2 Tutorial โ€” Limn Engine Intermediate Guide
Kehinde Owolabi
Kehinde Owolabi

Posted on

Level 2 Tutorial โ€” Limn Engine Intermediate Guide

๐ŸŸก Level 2 Tutorial โ€” Limn Engine Intermediate Guide

Physics, Tilemaps, Camera Follow & More

Welcome to Level 2: Intermediate of Limn Engine! You've mastered the basics โ€” moving objects, collision, text, and simple games. Now it's time to add physics, tilemaps, camera work, and polish that makes your games feel professional.

A special thank you to **GyaanSetu Javascript* for featuring our work and helping us share Limn Engine with the JavaScript community. Your support means the world to us. ๐Ÿ™*


What You'll Learn

Topic What You'll Build
1. Physics & Gravity Realistic falling and jumping
2. hitBottom() & move.hitObject() Floor and platform collision
3. move.glideX / glideY / glideTo Smooth point-to-point movement
4. move.project Projectile motion (arcs, bouncing)
5. Camera follow Track the player
6. Camera zoom Zoom in/out
7. Sprite & AnimatedSprite Spritesheet animation
8. TileMap layers with addMap() Multi-layer levels
9. Tctxt styled text UI Professional HUD styling
10. Accelerate & decelerate Vehicle-style momentum
11. Platform game tutorial Complete platformer
12. Quick reference All the essentials

Prerequisites

Before starting Level 2, you should be comfortable with:

  • โœ… Creating a Display and starting the game
  • โœ… Adding and moving Components
  • โœ… Keyboard input (display.keys)
  • โœ… Basic collision (crashWith())
  • โœ… move.bound() for boundaries

If you're not confident with these, complete Level 1: Beginner first.


1. Physics & Gravity

Enabling physics on a Component tells Limn Engine to apply gravity automatically every frame. The engine accumulates downward speed into a gravitySpeed value that compounds over time, creating realistic freefall motion.

How It Works

When player.physics = true, the engine's internal move() method:

  1. Adds this.gravity to this.gravitySpeed every frame
  2. Adds this.gravitySpeed to the Component's Y position
  3. After 10 frames of gravity 0.4, the object falls at 4px per frame
const player = new Component(40, 56, "blue", 100, 50, "rect");
display.add(player);

player.physics = true;  // Enable gravity accumulation
player.gravity = 0.4;   // Added to gravitySpeed each frame
player.bounce = 0.2;    // 20% energy kept on floor impact

function update(dt) {
    player.hitBottom(); // Clamp to canvas floor and bounce

    // Left/right movement still works normally alongside physics
    if (display.keys[39]) player.speedX = 4;
    if (display.keys[37]) player.speedX = -4;
    else if (!display.keys[39]) player.speedX = 0;
}
Enter fullscreen mode Exit fullscreen mode

Properties Explained

Property Type Purpose
physics boolean Enable gravity accumulation in move()
gravity number Amount added to gravitySpeed per frame
gravitySpeed number Accumulated downward speed โ€” set negative to jump
bounce 0โ€“1 Fraction of speed kept on floor impact

Visual Example

Frame 1:  gravitySpeed = 0.4  (falling slow)
Frame 2:  gravitySpeed = 0.8  (falling faster)
Frame 3:  gravitySpeed = 1.2  (falling faster)
Frame 10: gravitySpeed = 4.0  (full speed)
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Important: You must call player.hitBottom() inside update() โ€” the engine applies gravity but does NOT automatically stop the player at any surface.


2. hitBottom() and move.hitObject()

hitBottom() clamps a physics Component to the canvas floor. move.hitObject() does the same but uses another Component as the landing surface.

hitBottom()

player.hitBottom(); // Uses canvas height
player.hitBottom(400); // Custom ground at y=400
Enter fullscreen mode Exit fullscreen mode

move.hitObject()

const player = new Component(36, 48, "cyan", 100, 0, "rect");
const platform = new Component(200, 20, "#555", 200, 300, "rect");
display.add(player);
display.add(platform);

player.physics = true;
player.gravity = 0.5;
player.bounce = 0.05;

function update(dt) {
    // Land on the platform
    move.hitObject(player, platform);

    // Also stop at the canvas floor (safety net)
    player.hitBottom();
}
Enter fullscreen mode Exit fullscreen mode

Visual Explanation

Without hitBottom:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Player falls through floor  โ”‚
โ”‚  โ†“                           โ”‚
โ”‚  โ†“                           โ”‚
โ”‚  โ†“                           โ”‚
โ”‚  ๐Ÿ’€ Player disappears        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

With hitBottom:
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  Player falls               โ”‚
โ”‚  โ†“                           โ”‚
โ”‚  โ†“                           โ”‚
โ”‚  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• โ”‚ โ† Floor
โ”‚  โœ… Player lands & bounces   โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

Quick Reference

Method Parameters Purpose
hitBottom() optional groundY Clamp to canvas floor and bounce
move.hitObject(id, floor) Component, floor Component Treat another Component's top edge as landing surface

3. move.glideX / glideY / glideTo

The glide functions ease a Component from its current position to a target coordinate over a specified duration using a cubic ease-out curve.

How It Works

The movement starts fast and decelerates smoothly as it approaches the target โ€” no manual lerp code required.

const box = new Component(50, 50, "#7fffb2", 0, 200, "rect");
display.add(box);

// On any keypress, glide the box to a new position
window.addEventListener("keydown", () => {
    // Glide horizontally
    move.glideX(box, 2000, 700);    // to x=700 over 2 seconds

    // Glide vertically
    move.glideY(box, 1500, 400);    // to y=400 over 1.5 seconds

    // Or both axes together in perfect sync
    move.glideTo(box, 2000, 700, 400); // id, ms, x, y
});
Enter fullscreen mode Exit fullscreen mode

Visual Timeline

Time:  0ms    500ms   1000ms   1500ms   2000ms
X:     0      200     400      550      700
       โ†‘       โ†‘       โ†‘        โ†‘        โ†‘
       Start   Fast    Medium   Slow     Arrive
              (Easing out โ€” decelerates naturally)
Enter fullscreen mode Exit fullscreen mode

Method Reference

Method Parameters Easing
move.glideX(id, ms, x) Component, duration ms, target x Cubic ease-out
move.glideY(id, ms, y) Component, duration ms, target y Cubic ease-out
move.glideTo(id, ms, x, y) Component, duration ms, x, y Both axes in sync

๐Ÿ’ก Tip: Glide runs independently of your game loop. You call it once and the Component arrives at the target position precisely at the end of the duration regardless of frame rate.


4. move.project โ€” Projectile Motion

move.project() launches a Component as a physics projectile โ€” you specify velocity, launch angle, and gravity, and the engine handles the arc and bouncing.

const ball = new Component(20, 20, "orange", 100, 450, "rect");
display.add(ball);
ball.bounce = 0.5;

window.addEventListener("dblclick", () => {
    // Launch at 45ยฐ with velocity 12 โ€” arcs and bounces on canvas floor
    move.project(ball, 12, 45, 0.3);

    // Custom ground height โ€” bounce on a platform at y=400
    move.project(ball, 12, 45, 0.3, 400);
});
Enter fullscreen mode Exit fullscreen mode

Visual Arc

    45ยฐ Launch
        โ†—
       /  \
      /    \
     /      \
    /        \
   /          \
  โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• Ground
  --- Bounce --- Bounce --- Stop
Enter fullscreen mode Exit fullscreen mode

Parameter Reference

Parameter Type Purpose
velocity number Launch speed in pixels per frame
angle degrees 0ยฐ = right, 90ยฐ = up, 45ยฐ = classic arc
gravity number Added to speedY every frame โ€” controls arc steepness
ground number (optional) Custom floor Y โ€” defaults to display.canvas.height

๐Ÿ’ก Tip: Angle 0ยฐ launches horizontally to the right, 90ยฐ launches straight up. Angles above 90ยฐ launch backward and upward.


5. Camera Follow

display.camera.follow(target) translates the canvas context so the view tracks a Component โ€” keeping the player centred on screen as they move through a world larger than the canvas.

display.camera.worldWidth = 3000; // Level is 3000px wide
display.camera.worldHeight = 1000; // Level is 1000px tall

function update(dt) {
    if (display.keys[68]) player.speedX = 4;
    if (display.keys[65]) player.speedX = -4;
    else if (!display.keys[68]) player.speedX = 0;

    // Smooth follow โ€” lerps 10% of the gap each frame (recommended)
    display.camera.follow(player, true);

    // Hard follow โ€” snaps to player exactly
    // display.camera.follow(player);
}
Enter fullscreen mode Exit fullscreen mode

Smooth vs Instant Follow

Mode Visual Effect Use Case
smooth = true Gentle trailing camera Platformers, exploration games
smooth = false Instant centering Arcade games, precise control

How It Works

Smooth Follow:
Player โ†’ โ†’ โ†’ โ†’ โ†’ โ†’ โ†’
Camera โ†’ โ†’ โ†’ โ†’ โ†’ โ†’ (10% lag behind)
        โ†‘
    Camera follows but never catches up completely

Instant Follow:
Player โ†’ โ†’ โ†’ โ†’ โ†’ โ†’ โ†’
Camera โ†’ โ†’ โ†’ โ†’ โ†’ โ†’ (perfect sync)
Enter fullscreen mode Exit fullscreen mode

Properties

Property Purpose
camera.worldWidth World size โ€” camera won't show outside this
camera.worldHeight World size โ€” camera won't show outside this
camera.x / camera.y Current camera offset (read or set manually)

6. Camera Zoom

display.camera.setZoom(amount) scales the entire canvas context. Call it once and the zoom stays until you change it.

// Zoom in once โ€” stays zoomed in
display.camera.setZoom(1.5);

// Zoom out โ€” stays zoomed out
display.camera.setZoom(0.5);

// Back to normal
display.camera.setZoom(1);
Enter fullscreen mode Exit fullscreen mode

How It Actually Works

setZoom() calls display.context.scale(amount, amount) on the canvas context. The scale transform persists until the context is restored or the canvas is cleared.

// Internal implementation:
setZoom(amount) {
    display.context.scale(amount, amount);
}
Enter fullscreen mode Exit fullscreen mode

Interactive Zoom Example

let zoomLevel = 1.0;

// Adjust zoom with keys โ€” only when key is pressed
window.addEventListener("keydown", (e) => {
    if (e.key === "+" || e.key === "=") {
        zoomLevel = Math.min(zoomLevel + 0.1, 2.5);
        display.camera.setZoom(zoomLevel);
    }
    if (e.key === "-") {
        zoomLevel = Math.max(zoomLevel - 0.1, 0.5);
        display.camera.setZoom(zoomLevel);
    }
});
Enter fullscreen mode Exit fullscreen mode

Visual Effect

Zoom 0.5 (Zoom Out):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  See more of world   โ”‚
โ”‚  โ”Œโ”€โ”€โ”€โ”               โ”‚
โ”‚  โ”‚ P โ”‚  (smaller)    โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”˜               โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Zoom 1.0 (Normal):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”       โ”‚
โ”‚  โ”‚  Player   โ”‚       โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜       โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Zoom 2.0 (Zoom In):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”‚
โ”‚  โ”‚    Player       โ”‚  โ”‚
โ”‚  โ”‚    (close-up)   โ”‚  โ”‚
โ”‚  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Enter fullscreen mode Exit fullscreen mode

When to Call setZoom()

Scenario When to Call
Static zoom Once at startup
Zoom on key press Only when key is pressed
Zoom on game event Only when the event happens
Continuous zoom effect Every frame (if you want gradual zoom)

๐Ÿ’ก Tip: The zoom persists automatically. You only need to call setZoom() again if you want to change the zoom level.


7. Sprite & AnimatedSprite

Sprite animates a horizontal spritesheet by cycling through evenly spaced frames. AnimatedSprite extends it with named animation clips (idle, run, jump).

Sprite (Basic)

// Sprite(src, frameW, frameH, frameCount, frameSpeed, x, y)
const explosion = new Sprite("explode.png", 64, 64, 8, 4, 300, 200);
display.add(explosion);
Enter fullscreen mode Exit fullscreen mode

AnimatedSprite (Named Clips)

const hero = new AnimatedSprite("hero_sheet.png", 64, 64, 200, 300);

// Define animation clips
hero.addAnimation("idle",   0,  3, 10, true);  // loop
hero.addAnimation("run",    4, 11,  5, true);  // loop
hero.addAnimation("jump",  12, 15,  4, false); // one-shot
hero.addAnimation("attack",16, 19,  3, false); // one-shot

display.add(hero);

let attacking = false;

function update(dt) {
    // Switch animations based on state
    if (display.keys[68]) hero.playAnimation("run");
    else hero.playAnimation("idle");

    // One-shot attack
    if (display.keys[90] && !attacking) { // Z key
        hero.playAnimation("attack");
        attacking = true;
        setTimeout(() => attacking = false, 300);
    }

    // Must call every frame to advance frames
    hero.updateAnimation();

    // hero.paused is true when a one-shot animation finishes
}
Enter fullscreen mode Exit fullscreen mode

Spritesheet Format

One image file: hero_sheet.png
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚Frame0โ”‚Frame1โ”‚Frame2โ”‚Frame3โ”‚Frame4โ”‚Frame5โ”‚Frame6โ”‚Frame7โ”‚
โ”‚Idle0 โ”‚Idle1 โ”‚Run0  โ”‚Run1  โ”‚Run2  โ”‚Jump0 โ”‚Jump1 โ”‚Attackโ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Each frame = 64x64 pixels
Total width = 8 ร— 64 = 512 pixels
Enter fullscreen mode Exit fullscreen mode

AnimatedSprite Methods

Method Parameters Purpose
addAnimation(name, s, e, spd, loop) string, int, int, int, boolean Define a named clip
playAnimation(name) string Switch to that clip โ€” only resets if name changed
updateAnimation() None Advance the frame counter โ€” call every frame
paused boolean True when a one-shot animation finishes
faceLeft() / faceRight() None Flip sprite horizontally

๐Ÿ’ก Tip: One-shot animations set paused = true when they reach their last frame, which you can check to know when the animation finished.


8. TileMap Layers with addMap()

The TileMap in Limn Engine is a multi-layer system where each layer is an independent 2D number array. Layers are SWITCHABLE, not stackable โ€” you show one layer at a time.

How Layers Actually Work

// โš ๏ธ Important: Layers are SWITCHABLE, not STACKABLE
// Only ONE layer is visible at a time!

display.tileFace.show(0);  // Layer 0 visible (layers 1, 2 hidden)
display.tileFace.show(1);  // Layer 1 visible (layers 0, 2 hidden)
display.tileFace.show(2);  // Layer 2 visible (layers 0, 1 hidden)
Enter fullscreen mode Exit fullscreen mode

Setup

// All layers share these tile templates
display.tile = [
    new Component(64, 64, "#2d6a2d", 0, 0), // tile 1 = grass
    new Component(64, 64, "#8B4513", 0, 0), // tile 2 = dirt
    new Component(64, 64, "#228B22", 0, 0), // tile 3 = tree
    new Component(64, 64, "#FFD700", 0, 0), // tile 4 = coin
];

// Layer 0 โ€” Overworld
display.map = [
    [1, 1, 2, 1, 1],
    [2, 1, 1, 2, 1],
    [2, 2, 2, 2, 2],
];
display.tileMap();
display.tileFace.show(0); // Show overworld

// Layer 1 โ€” Dungeon (different map)
display.tileFace.addMap([
    [1, 0, 1, 0, 1],
    [0, 2, 0, 2, 0],
    [1, 0, 1, 0, 1],
]);
display.tileFace.show(1); // Switch to dungeon (overworld hidden)

// Layer 2 โ€” Boss Room
display.tileFace.addMap([
    [1, 1, 1, 1, 1],
    [1, 4, 4, 4, 1],
    [1, 1, 1, 1, 1],
]);
display.tileFace.show(2); // Switch to boss room
Enter fullscreen mode Exit fullscreen mode

Visual Layering (Switchable)

Layer 0 (Overworld):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŸฉ๐ŸŸซ๐ŸŸซ๐ŸŸฉ๐ŸŸฉ                 โ”‚
โ”‚  ๐ŸŸซ๐ŸŸฉ๐ŸŸฉ๐ŸŸซ๐ŸŸฉ                 โ”‚
โ”‚  ๐ŸŸซ๐ŸŸซ๐ŸŸซ๐ŸŸซ๐ŸŸซ                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Layer 1 (Dungeon):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŸฉโฌœ๐ŸŸฉโฌœ๐ŸŸฉ                 โ”‚
โ”‚  โฌœ๐ŸŸซโฌœ๐ŸŸซโฌœ                 โ”‚
โ”‚  ๐ŸŸฉโฌœ๐ŸŸฉโฌœ๐ŸŸฉ                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Layer 2 (Boss Room):
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚  ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ                 โ”‚
โ”‚  ๐ŸŸฉ๐Ÿช™๐Ÿช™๐Ÿช™๐ŸŸฉ                 โ”‚
โ”‚  ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ๐ŸŸฉ                 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Only ONE layer is visible at a time!
Enter fullscreen mode Exit fullscreen mode

Runtime Switching

// Switch layers based on game state
function goToDungeon() {
    display.tileFace.show(1); // Dungeon visible
    // Overworld tiles are hidden
}

function goToBossRoom() {
    display.tileFace.show(2); // Boss room visible
    // Dungeon tiles are hidden
}

function goToOverworld() {
    display.tileFace.show(0); // Overworld visible
    // Dungeon and boss room hidden
}
Enter fullscreen mode Exit fullscreen mode

Adding Foreground Objects

// Tiles are for ground/walls. Use fake.add() for objects on top:
const tree = new Component(64, 64, null, 400, 300, "image");
tree.setImage("tree.png");
fake.add(tree); // Renders on top of tilemap

const coin = new Component(30, 30, null, 500, 200, "image");
coin.setImage("coin.png");
fake.add(coin); // Renders on top of tilemap
Enter fullscreen mode Exit fullscreen mode

โš ๏ธ Important: Layers do NOT stack! show(1) replaces show(0) โ€” only one layer is visible at a time. This is a design choice for level switching, not a limitation.

Runtime Editing

// Edit a layer at runtime โ€” fake.refresh() called automatically
display.tileFace.remove(1, 0, 2); // remove coin at grid(1,0) on layer 2
display.tileFace.add(3, 2, 1, 1); // add tree at grid(2,1) on layer 1

// Collision still works across all layers
if (display.tileFace.crashWith(player, 2)) {
    // player touched a dirt tile (type 2)
}

// Get all tiles of a type
const allCoins = display.tileFace.tiles(4);
Enter fullscreen mode Exit fullscreen mode

9. Tctxt โ€” Styled Text UI

Tctxt is the correct class for any on-screen text โ€” it gives you font size, font family, colour, alignment, optional background fill with padding, stroke, and baseline control.

const scoreText = new Tctxt(
    "22px",                    // font size
    "Arial",                   // font family
    "white",                   // text colour
    20, 40,                    // x, y screen position
    "left",                    // "left" / "center" / "right"
    false,                     // false=fill text, true=stroke/outline
    "alphabetic",              // text baseline
    "rgba(0,0,0,0.6)",         // background colour โ€” null to disable
    14, 6                      // paddingX, paddingY
);
scoreText.setText("Score: 0");
display.add(scoreText);

function update(dt) {
    scoreText.setText("Score: " + score);
    scoreText.fixed(); // lock to screen when camera moves
}
Enter fullscreen mode Exit fullscreen mode

Parameter Reference

Parameter Example Purpose
size "22px" Font size as CSS string
font "Arial" Font family name
align "left" "left", "center", or "right"
stroke false false = filled text, true = outlined
background "rgba(0,0,0,0.6)" Background fill (null to disable)
padX / padY 14, 6 Pixel padding inside background

๐Ÿ’ก Tip: The base Component.setText() method can draw text but offers no control over font, alignment, or background. Tctxt replaces it for any UI work.


10. Accelerate & Decelerate

move.accelerate() and move.decelerate() give you vehicle-style movement where speed builds up gradually to a maximum and bleeds off smoothly to zero.

function update(dt) {
    if (display.keys[68]) {
        // Accelerate right, max speed 8, no vertical change
        move.accelerate(player, 0.6, 0, 8, 0);
    } else if (display.keys[65]) {
        // Accelerate left
        move.accelerate(player, -0.6, 0, 8, 0);
    } else {
        // Friction โ€” reduce speed by 0.4 per frame
        move.decelerate(player, 0.4, 0);
    }
    move.bound(player);
}
Enter fullscreen mode Exit fullscreen mode

Visual Comparison

Instant Movement:
Speed:  โ”€โ”€โ”€โ”€   (jumps to max instantly)
         โ†‘
    Press key โ†’ Speed = 8 immediately

Accelerate/Decelerate:
Speed:  โ†—โ”€โ”€โ”€โ†˜   (gradually speeds up, smoothly stops)
        โ†‘   โ†‘
    Press key  Release key
Enter fullscreen mode Exit fullscreen mode

Method Reference

Method Parameters Purpose
move.accelerate(id, ax, ay, mx, my) Component, accelX, accelY, maxX, maxY Add acceleration per frame up to max speed
move.decelerate(id, dx, dy) Component, decelX, decelY Reduce speed to zero without reversing direction

11. Platform Game Tutorial

This tutorial puts together physics, jumping, accelerated movement, a floor platform, smooth camera follow, and a Tctxt score display into a complete playable platformer.

<!DOCTYPE html>
<html>
<head>
    <title>Platform Game</title>
    <script src="epic.js"></script>
</head>
<body>
    <script>
        const display = new Display();
        display.start(800, 400);
        display.backgroundColor("#1a1a2e");
        display.camera.worldWidth = 3000;

        const player = new Component(36, 48, "cyan", 100, 100, "rect");
        player.physics = true;
        player.gravity = 0.5;
        player.bounce = 0.05;
        display.add(player);

        const floor = new Component(3000, 20, "#444", 0, 380, "rect");
        display.add(floor);

        // A few platforms at different heights
        const p1 = new Component(200, 16, "#666", 400, 300, "rect");
        const p2 = new Component(150, 16, "#666", 800, 240, "rect");
        display.add(p1);
        display.add(p2);

        const scoreUI = new Tctxt(
            "18px","Arial","white",14,28,
            "left",false,"alphabetic",
            "rgba(0,0,0,0.5)",10,4
        );
        scoreUI.setText("Distance: 0");
        display.add(scoreUI);

        function update(dt) {
            // Horizontal movement with acceleration
            if (display.keys[68] || display.keys[39]) {
                move.accelerate(player, 0.7, 0, 7, 0);
            } else if (display.keys[65] || display.keys[37]) {
                move.accelerate(player, -0.7, 0, 7, 0);
            } else {
                move.decelerate(player, 0.5, 0);
            }

            // Jump โ€” only when grounded (gravitySpeed >= 0)
            if ((display.keys[87] || display.keys[38]) && player.gravitySpeed >= 0) {
                player.gravitySpeed = -11;
            }

            // Floor and platform collisions
            move.hitObject(player, floor);
            move.hitObject(player, p1);
            move.hitObject(player, p2);
            player.hitBottom();

            display.camera.follow(player, true);
            scoreUI.setText("Distance: " + Math.floor(player.x));
            scoreUI.fixed();
        }
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

โœ… What You Get:

  • WASD or arrow keys to move
  • W/Up to jump
  • Multi-platform levels
  • Smooth camera follow
  • Live distance counter
  • Physics-based movement

12. Quick Reference

Feature Code
Enable physics player.physics=true; player.gravity=0.4;
Floor collision player.hitBottom();
Platform collision move.hitObject(player, floor);
Jump if(player.gravitySpeed >= 0) player.gravitySpeed = -10;
Glide to position move.glideTo(obj, 2000, x, y);
Projectile launch move.project(ball, 12, 45, 0.3);
Camera follow display.camera.follow(player, true);
Zoom (call once) display.camera.setZoom(1.5);
AnimatedSprite hero.addAnimation("run",4,11,5,true); hero.playAnimation("run"); hero.updateAnimation();
TileMap layer (switchable) tileFace.addMap(arr); tileFace.show(1);
Accelerate move.accelerate(obj,0.6,0,8,0);
Decelerate move.decelerate(obj,0.4,0);

What's Next?

You've completed Level 2: Intermediate! ๐ŸŽ‰

Level Description
๐ŸŸข Level 1 โ€” Beginner โœ… Basics complete
๐ŸŸก Level 2 โ€” Intermediate โœ… You are here
๐ŸŸ  Level 3 โ€” Advanced Particles, circle collision, dynamic tilemaps, screen shake
๐Ÿ”ด Level 4 โ€” 10x Dualโ€‘renderer, performance optimization, engine extension

Special Thanks

"A heartfelt thank you to **GyaanSetu Javascript* for featuring Limn Engine and helping us share this tutorial with the JavaScript community. Your support means the world to us."*


Useful Links


Draw your game into existence โ€” one physics simulation at a time. ๐ŸŽฎ

Top comments (1)

Collapse
 
nazar_boyko profile image
Nazar Boyko

Quick one on the tilemap layers. You flag twice that they're switchable rather than stackable, and I'm curious about the reasoning, since the usual reason to have layers at all is drawing a background, a collision layer, and a foreground on top of each other at once. Switching between whole maps reads more like scenes than layers to me. Was dropping the stacking a performance call, or just not the use case you were building for?