惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Project Zero
Project Zero
Security Latest
Security Latest
G
GRAHAM CLULEY
C
CXSECURITY Database RSS Feed - CXSecurity.com
云风的 BLOG
云风的 BLOG
月光博客
月光博客
V
Visual Studio Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
宝玉的分享
宝玉的分享
阮一峰的网络日志
阮一峰的网络日志
雷峰网
雷峰网
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
爱范儿
爱范儿
Attack and Defense Labs
Attack and Defense Labs
罗磊的独立博客
D
DataBreaches.Net
TaoSecurity Blog
TaoSecurity Blog
T
Threatpost
S
Secure Thoughts
T
The Exploit Database - CXSecurity.com
P
Palo Alto Networks Blog
Cisco Talos Blog
Cisco Talos Blog
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 聂微东
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
NISL@THU
NISL@THU
Spread Privacy
Spread Privacy
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
PCI Perspectives
PCI Perspectives
P
Proofpoint News Feed
Google DeepMind News
Google DeepMind News
V
V2EX
WordPress大学
WordPress大学
Recorded Future
Recorded Future
Stack Overflow Blog
Stack Overflow Blog
AI
AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
T
The Blog of Author Tim Ferriss
D
Docker
Latest news
Latest news
C
CERT Recently Published Vulnerability Notes
D
Darknet – Hacking Tools, Hacker News & Cyber Security
B
Blog RSS Feed
V2EX - 技术
V2EX - 技术
小众软件
小众软件
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Level 2 Tutorial — Limn Engine Intermediate Guide
Kehinde Owolabi · 2026-06-27 · via DEV Community

🟡 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;
}

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)

⚠️ 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

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();
}

Visual Explanation

Without hitBottom:
┌─────────────────────────────┐
│  Player falls through floor  │
│  ↓                           │
│  ↓                           │
│  ↓                           │
│  💀 Player disappears        │
└─────────────────────────────┘

With hitBottom:
┌─────────────────────────────┐
│  Player falls               │
│  ↓                           │
│  ↓                           │
│  ═══════════════════════════ │ ← Floor
│  ✅ Player lands & bounces   │
└─────────────────────────────┘

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
});

Visual Timeline

Time:  0ms    500ms   1000ms   1500ms   2000ms
X:     0      200     400      550      700
       ↑       ↑       ↑        ↑        ↑
       Start   Fast    Medium   Slow     Arrive
              (Easing out — decelerates naturally)

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);
});

Visual Arc

    45° Launch
        ↗
       /  \
      /    \
     /      \
    /        \
   /          \
  ════════════════════ Ground
  --- Bounce --- Bounce --- Stop

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);
}

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)

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);

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);
}

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);
    }
});

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)   │  │
│  └────────────────┘  │
└─────────────────────┘

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);

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
}

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

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)

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

Visual Layering (Switchable)

Layer 0 (Overworld):
┌─────────────────────────────┐
│  🟩🟫🟫🟩🟩                 │
│  🟫🟩🟩🟫🟩                 │
│  🟫🟫🟫🟫🟫                 │
└─────────────────────────────┘

Layer 1 (Dungeon):
┌─────────────────────────────┐
│  🟩⬜🟩⬜🟩                 │
│  ⬜🟫⬜🟫⬜                 │
│  🟩⬜🟩⬜🟩                 │
└─────────────────────────────┘

Layer 2 (Boss Room):
┌─────────────────────────────┐
│  🟩🟩🟩🟩🟩                 │
│  🟩🪙🪙🪙🟩                 │
│  🟩🟩🟩🟩🟩                 │
└─────────────────────────────┘

Only ONE layer is visible at a time!

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
}

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

⚠️ 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);


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
}

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);
}

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

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>

✅ 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. 🎮