DEV Community

Cover image for Build Your First Browser Game in 60 Seconds with Limn Engine
Kehinde Owolabi
Kehinde Owolabi

Posted on

Build Your First Browser Game in 60 Seconds with Limn Engine

🎮 Build Your First Browser Game in 60 Seconds with Limn Engine

A Complete Coin Collector Game in Under 100 Lines of JavaScript


🎯 Live Demo

See the final result first: [limn-engine-doc.vercel.app/test8.html](http://limn-engine-doc.vercel.app/test8.html]\(https://limn-engine-doc.vercel.app/test8.html\))

Play the game, move the blue square with arrow keys, collect all 10 gold coins, and win!


📖 Introduction

Most game engines require npm installs, build tools, and hours of setup before you see anything on screen. Limn Engine is different — one script tag, one canvas, and you're ready to build.

In this tutorial, you'll build a complete coin collector game in under 100 lines of code. You'll learn:

  • How to set up Limn Engine

  • How to create and move a player

  • How to generate coins at random positions

  • How to detect collisions

  • How to track score and win the game

  • How to restart with one keypress

The result: A fully playable game that you can share with anyone.


🚀 Step 1: Download Limn Engine

First, you need to get the Limn Engine file.

  1. Go to the Limn Engine website: [limn-engine-doc.vercel.app](https://limn-engine-doc.vercel.app)

  2. Click the download button to get epic.js

  3. Place epic.js in the same folder as your HTML file

Why this matters: Limn Engine is a single file. No npm, no build tools, no complex setup. Just one JavaScript file and you're ready to go.


📁 Step 2: Create Your HTML File

Create a new HTML file and include the Limn Engine script. This is the foundation of your game.


<!doctype html>

<html>

<head>

<script src="epic.js"></script>

</head>

<body>

<script>

// All your game code goes here

</script>

</body>

</html>

Enter fullscreen mode Exit fullscreen mode

Let's break this down so you understand what's happening:

  • <!doctype html> — This tells the browser to use the latest version of HTML. Think of it as saying "Hey browser, I'm using modern HTML, please render this correctly."

  • <html> — This is the root element of your page. Everything else goes inside it.

  • <head> — This section contains information about your page that the browser needs before it starts rendering. Things like the title, character set, and in our case, the script we're about to load.

  • <script src="epic.js"> — This is where we load the Limn Engine. The src attribute tells the browser to fetch a file called epic.js from the same folder as your HTML file. This gives you access to all the Limn Engine features like Display, Component, Tctxt, and everything else we'll use.

  • <body> — This is where the visible content of your page goes. The canvas will be created here automatically when we start the display.

  • <script> — This is where we write our game code. The browser will run this code after it loads epic.js.

What happens if you skip this step: If you don't include epic.js, the browser will throw an error saying Display is not defined because it doesn't know what a Display is.


🎮 Step 3: Create the Display

The Display is your game window. It creates the canvas, manages the game loop, and handles input from the keyboard and mouse.


const display = new Display();

display.perform();

display.start(800, 600);

display.backgroundColor("green");

Enter fullscreen mode Exit fullscreen mode

Let me explain each line so you understand what it's doing:

  • const display = new Display(); — This creates a new Display object. Think of it as telling the engine "I want a game window." This object will manage everything about your game.

  • display.perform(); — This turns on performance mode. Without this, your game runs at about 50 frames per second. With it, you get 60 frames per second and a special rendering system that makes your game faster. Always call this before start().

  • display.start(800, 600); — This actually creates the canvas on your page. The two numbers tell the engine how big you want the game window: 800 pixels wide and 600 pixels tall. After this line, you'll see a blank canvas on your page.

  • display.backgroundColor("green"); — This sets the background color of your canvas. You can use any color name or hex code here. If you skip this, the background will be transparent (white in most browsers).

A helpful way to think about it: Creating the Display is like setting up a stage. perform() is like turning on the lights, start() is like raising the curtain, and backgroundColor() is like painting the backdrop.


🧩 Step 4: Create the Player

A Component is any game object — player, enemy, coin, wall. Think of it as anything that appears on screen and can move or be interacted with.


const player = new Component(40, 40, "blue", 400, 300, "rect");

display.add(player);

Enter fullscreen mode Exit fullscreen mode

Let me walk you through what each part means:

  • new Component(40, 40, "blue", 400, 300, "rect") — This creates a new Component. The five pieces of information are:

  • 40, 40 — The width and height of the component in pixels. This creates a 40×40 square.

  • "blue" — The color of the component. This can be a color name, hex code, or even an image path.

  • 400, 300 — The starting position on the screen. This puts the player at the center of an 800×600 canvas.

  • "rect" — The type of component. This tells the engine to draw a rectangle. Other options include "image" and "text".

  • display.add(player); — This tells the engine to add the player to the game world. Without this line, nothing would appear on screen because the engine wouldn't know about your player.

What if you want to change the player's position? You can set player.x and player.y to any number. For example, player.x = 100; moves the player to the left edge.


📝 Step 5: Create the UI (Score and Win Text)

Tctxt is a special text component that gives you more control over how text looks. It can have backgrounds, padding, alignment, and more.


const scoreText = new Tctxt("24px", "Arial", "white", 20, 50);

scoreText.setText("Score: 0 / 10");

display.add(scoreText);

const winText = new Tctxt("48px", "Arial", "gold", 400, 300);

winText.align = "center";

winText.setText("YOU WIN!");

winText.hide();

display.add(winText);

const restartText = new Tctxt("20px", "Arial", "white", 400, 380);

restartText.align = "center";

restartText.setText("Press R to play again");

restartText.hide();

display.add(restartText);

Enter fullscreen mode Exit fullscreen mode

Let me explain each piece:

Score Text:

  • new Tctxt("24px", "Arial", "white", 20, 50) — Creates text at position (20, 50) from the top-left corner. The font is 24 pixels tall and uses the Arial font in white color.

  • .setText("Score: 0 / 10") — Sets the actual text content. This is separate from creating the object so you can update it later.

  • display.add(scoreText) — Adds it to the game world so it appears on screen.

Win Text (hidden at start):

  • .align = "center" — Centers the text horizontally. This is why the x position is 400 (the center of an 800-pixel screen).

  • .hide() — Makes the text invisible at the start. This is important because the player hasn't won yet.

  • .show() — Will be called later when the player wins.

Restart Text:

  • Similar to the win text, but positioned slightly lower at y=380.

  • Also hidden at start and will appear when the player wins.

Why use Tctxt instead of a regular Component? Tctxt gives you features like alignment, background color, and padding that normal text components don't have. This makes your UI look much more polished.


🪙 Step 6: Create the Coins

Coins are also Components. We'll create 10 coins at random positions using a loop.


const totalCoins = 10;

let coins = [];

for (let i = 0; i < totalCoins; i++) {

let coin = new Component(30, 30, "gold",

Math.random() * 700 + 50,

Math.random() * 500 + 50, "rect");

display.add(coin);

coins.push(coin);

}

Enter fullscreen mode Exit fullscreen mode

Let me walk through what's happening:

  • const totalCoins = 10; — This sets how many coins we want. We use a variable so we can change it easily later.

  • let coins = []; — This creates an empty array to store all the coin objects. We need this so we can check collisions with each coin and remove collected ones.

  • for (let i = 0; i < totalCoins; i++) — This loop runs 10 times. Each time, it creates one new coin.

  • Math.random() * 700 + 50 — This creates a random number between 50 and 750. Why 50 and 750? We want the coins to appear away from the edges so they're reachable. The canvas is 800 pixels wide, so 50 and 750 give a nice margin.

  • Math.random() gives a number between 0 and 1.

  • Multiply by 700 gives a number between 0 and 700.

  • Add 50 gives a number between 50 and 750.

  • Math.random() * 500 + 50 — Same logic for the Y position. The canvas is 600 pixels tall, so this gives numbers between 50 and 550.

  • display.add(coin) — Adds the coin to the game world.

  • coins.push(coin) — Stores the coin in our array for later use.

What if you want more coins? Just change totalCoins to any number. The loop will create that many coins.


🎯 Step 7: Game State

We need variables to track the game status.


let score = 0;

let gameActive = true;

Enter fullscreen mode Exit fullscreen mode

What these mean:

  • score — This starts at 0 and increases by 1 each time you collect a coin. It's what we show on the score text.

  • gameActive — This is a boolean (true/false). When true, the game updates normally. When false, the game stops. This prevents the player from continuing after winning.

Why do we need gameActive? Without it, the player could keep moving and the score could change even after winning. It gives us control over when the game ends.


🔄 Step 8: The Game Loop

The update() function runs every frame, which is 60 times per second. This is where all the action happens.


function update(deltaTime) {

// Restart with R key

if (display.keys[82]) {

location.reload();

return;

}

if (!gameActive) return;

Enter fullscreen mode Exit fullscreen mode

Let me explain the flow:

  • function update(deltaTime) — This is where Limn Engine calls your game code every frame. The deltaTime parameter tells you how much time has passed since the last frame (usually about 0.016 seconds at 60fps).

  • if (display.keys[82]) — The display.keys array stores which keys are currently pressed. 82 is the keycode for the letter 'R'. When you press R, this condition becomes true.

  • location.reload(); — This reloads the page, which restarts the game. It's a simple way to reset everything.

  • return; — This exits the update function early. Without this, the game would keep running even as it reloads.

  • if (!gameActive) return; — If the game is not active (because you've won), this exits the function early. This stops the player from moving or collecting more coins after winning.

What is keycode 82? Every key on the keyboard has a numeric keycode. 82 is 'R', 37-40 are arrow keys, 32 is space. You can find a full list of keycodes online.


⌨️ Step 9: Player Movement

Now we add the controls to move the player with arrow keys.


// Player movement

if (display.keys[37]) player.speedX = -400 * deltaTime;

else if (display.keys[39]) player.speedX = 400 * deltaTime;

else player.speedX = 0;

if (display.keys[38]) player.speedY = -400 * deltaTime;

else if (display.keys[40]) player.speedY = 400 * deltaTime;

else player.speedY = 0;

move.bound(player);

Enter fullscreen mode Exit fullscreen mode

Here's what each part does:

  • display.keys[37] — Keycode 37 is the left arrow key. If it's pressed, we set the player's horizontal speed to negative (move left).

  • -400 * deltaTime — This moves the player at 400 pixels per second. Multiplying by deltaTime makes the movement smooth and consistent regardless of frame rate. On a fast computer, deltaTime is smaller, so the player moves less per frame but more often. On a slow computer, deltaTime is larger, so the player moves more per frame but less often. The result is the same speed on both.

  • move.bound(player); — This keeps the player inside the canvas boundaries. Without this, the player would walk off the screen and disappear. It's like having an invisible wall at the edges.

Keycodes explained:

  • 37 = Left arrow

  • 39 = Right arrow

  • 38 = Up arrow

  • 40 = Down arrow


🪙 Step 10: Coin Collection

This is where we check if the player touches a coin and handle what happens.


// Coin collection

for (let i = 0; i < coins.length; i++) {

if (player.crashWith(coins[i])) {

[display.camera](http://display.camera).shake(3, 3);

coins[i].hide();

coins.splice(i, 1);

score++;

scoreText.setText("Score: " + score + " / " + totalCoins);

break;

}

}

Enter fullscreen mode Exit fullscreen mode

Let me break this down carefully:

  • for (let i = 0; i < coins.length; i++) — This loops through every coin in our array. We use i to track which coin we're looking at.

  • if (player.crashWith(coins[i])) — This checks if the player overlaps with the current coin. crashWith() is a built-in collision detection function. It returns true if two components are touching.

  • display.camera.shake(3, 3); — This makes the screen shake slightly. The two numbers are the intensity on the X and Y axes. A small shake like this gives satisfying feedback to the player.

  • coins[i].hide(); — This makes the coin invisible. It's still in memory but not drawn on screen.

  • coins.splice(i, 1); — This removes the coin from the array. splice(i, 1) means "remove 1 item at position i".

  • score++; — This adds 1 to the score.

  • scoreText.setText("Score: " + score + " / " + totalCoins); — This updates the score display.

  • break; — This exits the loop early. Why? Because we've already found and collected a coin, so we don't need to check the rest of the coins this frame.

What happens if you don't use break? The player could collect multiple coins in one frame if they overlap with several at once, which would feel unfair and break the game.


🏆 Step 11: Win Condition

Finally, we check if the player has collected all the coins.


// Win condition

if (score === totalCoins && gameActive) {

gameActive = false;

[winText.show](http://winText.show)();

[restartText.show](http://restartText.show)();

scoreText.hide();

}

}

Enter fullscreen mode Exit fullscreen mode

Here's the logic:

  • if (score === totalCoins && gameActive) — This checks two things:

  • The score equals the total number of coins (10)

  • The game is still active

  • gameActive = false; — This stops the game. The player won't be able to move or collect more coins.

  • winText.show(); — This makes the "YOU WIN!" text appear.

  • restartText.show(); — This makes the "Press R to play again" text appear.

  • scoreText.hide(); — This hides the score display since you don't need it anymore.

Why do we check gameActive? Without this check, the win condition could trigger multiple times. Once you win, the game is no longer active, so the condition won't run again.


📝 Complete Code

Here's the entire game in one file. You can copy this and run it immediately.


<!doctype html>

<html>

<head>

<script src="epic.js"></script>

</head>

<body>

<script>

let gameActive = true;

const display = new Display();

const player = new Component(40, 40, "blue", 400, 300, "rect");

const scoreText = new Tctxt("24px", "Arial", "white", 20, 50);

const restartText = new Tctxt("20px", "Arial", "white", 400, 380);

display.perform()

display.start(800, 600);

let score = 0;

const totalCoins = 10;

let coins = [];

// Player

display.add(player);

display.backgroundColor("green")

// Score text

scoreText.setText("Score: 0 / " + totalCoins);

display.add(scoreText);

// Win text (hidden at start)

const winText = new Tctxt("48px", "Arial", "gold", 400, 300);

winText.align = "center";

winText.setText("YOU WIN!");

winText.hide();

display.add(winText);

restartText.align = "center";

restartText.setText("Press R to play again");

restartText.hide();

display.add(restartText);

// Create coins

for (let i = 0; i < totalCoins; i++) {

let coin = new Component(30, 30, "gold",

Math.random() * 700 + 50,

Math.random() * 500 + 50, "rect");

display.add(coin);

coins.push(coin);

}

function restart() {

location.reload()

}

function update(deltaTime) {

// Restart with R key

if (display.keys[82]) {

restart();

return;

}

if (!gameActive) return;

// Player movement

if (display.keys[37]) player.speedX = -400 * deltaTime;

else if (display.keys[39]) player.speedX = 400 * deltaTime;

else player.speedX = 0;

if (display.keys[38]) player.speedY = -400 * deltaTime;

else if (display.keys[40]) player.speedY = 400 * deltaTime;

else player.speedY = 0;

move.bound(player);

// Coin collection

for (let i = 0; i < coins.length; i++) {

if (player.crashWith(coins[i])) {

[display.camera](http://display.camera).shake(3, 3);

coins[i].hide();

coins.splice(i, 1);

score++;

scoreText.setText("Score: " + score + " / " + totalCoins);

break;

}

}

// Win condition

if (score === totalCoins && gameActive) {

gameActive = false;

[winText.show](http://winText.show)();

[restartText.show](http://restartText.show)();

scoreText.hide();

}

}

</script>

</body>

</html>

Enter fullscreen mode Exit fullscreen mode

🎯 What You've Learned

| Concept | Why It Matters | How It Works |

|---------|----------------|--------------|

| Limn Engine Setup | No npm, no build tools — just one script tag | Include epic.js and start coding |

| Components | Every game object is a Component | new Component() creates one |

| Player Movement | Arrow keys with deltaTime for consistent speed | display.keys reads input, speedX/speedY moves |

| Collision Detection | crashWith() detects overlaps | Returns true when two components touch |

| Random Placement | Math.random() creates variety | Gives different positions every time |

| UI with Tctxt | Styled text with background and padding | Supports alignment, colors, and show/hide |

| Win Condition | Game state management | gameActive controls the game flow |

| Restart | One keypress to reload | location.reload() resets everything |


🚀 What's Next: Article 2 — "Build a Top-Down Shooter with Particles, AI, and Screen Shake"

Now that you've built your first game, it's time to level up. In the next tutorial, you'll build a complete top-down space shooter that demonstrates more advanced Limn Engine features.

What You'll Build


┌─────────────────────────────────────────────────────────────────┐

│ │

│ Score: 0 Lives: 3 Bullets: 12 │

│ │

│ 👾 (Enemy) │

│ │

│ 🟦 (Player) │

│ ↑ │

│ (WASD to move) │

│ (Space to shoot) │

│ │

│ ✨ (Particle trail behind player) │

│ │

└─────────────────────────────────────────────────────────────────┘

Enter fullscreen mode Exit fullscreen mode

What You'll Learn

| Concept | Why It Matters |

|---------|----------------|

| Particle Systems | Visual effects make games feel alive |

| Enemy AI | Enemies that chase the player |

| Bullet Management | Limiting ammunition for balance |

| Screen Shake | Impact feedback |

| Invincibility Frames | Player protection after being hit |

| Game States | Lives, score, game over |

| Camera Follow | Keeping the player centered |

| UI with Tctxt | Displaying lives and bullets |

Key Functions You'll Use

| Function | Purpose |

|----------|---------|

| display.camera.follow() | Camera follows player |

| move.bound() | Keep player on screen |

| component.crashWith() | Collision detection |

| move.particles.explosion() | Explosion effects |

| move.particles.blood() | Blood effects |

| display.camera.shake() | Screen shake |

| particles.emit() | Custom particle effects |

| component.destroy() | Remove objects |

| display.stop() | End the game |

🎮 Preview the Game

Live Demo: limn-engine-doc.vercel.app/test9.html

Play the game, move with WASD or arrow keys, shoot with Space, and survive as long as possible!


🎮 Controls

  • WASD or arrow keys — Move the player
  • Space — Shoot (limited bullets, auto-recharge)
  • Enemies chase the player
  • Collect score by destroying enemies
  • Lose lives when enemies touch you

🔗 Resources

Resource Link
Live Demo (Article 1) limn-engine-doc.vercel.app/test8.html
Live Demo (Article 2) limn-engine-doc.vercel.app/test9.html
Download Limn Engine limn-engine-doc.vercel.app
Limn Engine Docs limn-engine-doc.vercel.app
Beginner Guide limn-engine-doc.vercel.app/beginner.html
Discord Community discord.gg/ZqnUtTQb8

Draw your game into existence — one line of code at a time. 🎮🚀

Top comments (0)