🏃 The Ultimate Guide to Movement in Limn Engine
Everything You Need to Know About Moving Objects in Limn Engine
📖 Introduction
Movement is the heartbeat of every game. Whether you're dodging bullets in a top-down shooter, leaping across platforms, or guiding a character through a vast open world, how things move defines the player's experience. Get it right, and your game feels smooth, responsive, and alive. Get it wrong, and it feels clunky and frustrating.
Limn Engine offers a rich toolkit for movement — from simple speed-based motion to complex physics and smooth gliding. In this guide, we'll explore every movement method available, when to use each one, and how they work under the hood.
🎯 The Two Core Movement Methods
Before we dive into the specific functions, it's important to understand the two fundamental ways Limn Engine handles movement:
| Method | How It Works | When to Use |
|---|---|---|
move() |
Moves an object using speedX and speedY
|
Standard movement — independent of angle |
moveAngle() |
Moves an object along its current angle
|
Directional movement — speed applied relative to angle |
These two methods are called automatically by the engine based on the angularMovement property of your component.
🧩 Method 1: Basic Movement with move()
The Concept
move() is the default movement method in Limn Engine. It works by taking the values stored in speedX and speedY and adding them to the component's x and y position every frame. This is the simplest way to move objects.
How it works internally:
When the engine calls move() on a component, it does this:
- If physics is enabled, it adds gravity to the vertical speed
- It adds
speedXtox(horizontal movement) - It adds
speedYtoy(vertical movement)
This means you control movement by setting speedX and speedY each frame. If you set speedX = 5, the object moves 5 pixels right every frame. If you set speedY = -3, it moves 3 pixels up every frame.
Why you need dt:
Without dt, movement speed depends on frame rate — faster computers move faster. By multiplying speed by dt (delta time), you convert "pixels per frame" to "pixels per second," making movement consistent on all computers.
The key principle: In move(), you control speed directly. The engine applies it automatically. You don't calculate positions manually — you set speeds.
What We're About to Do
We're going to create a player that moves with arrow keys. This is the most common type of movement in games — simple, responsive, and easy to understand. We'll use move() to handle the actual movement, and we'll use dt to make it frame-rate independent.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#1a1a2e");
const player = new Component(40, 40, "#5b8cff", 400, 300, "rect");
display.add(player);
function update(dt) {
// Reset speed each frame
player.speedX = 0;
player.speedY = 0;
// Arrow keys
if (display.keys[37]) player.speedX = -200 * dt; // Left
if (display.keys[39]) player.speedX = 200 * dt; // Right
if (display.keys[38]) player.speedY = -200 * dt; // Up
if (display.keys[40]) player.speedY = 200 * dt; // Down
// Keep player on screen
move.bound(player);
}
What Each Line Does
Line 1-4: Creates the game window, activates 60fps mode, sets the canvas size to 800x600, and sets the background to dark blue
Line 6-7: Creates a 40x40 blue square at the center of the screen and adds it to the game world
Line 9: function update(dt) {
- The game loop function, called 60 times per second
-
dt(delta time) is the time since the last frame, usually about 0.016 seconds - Using
dtmakes movement consistent on all computers
Line 10-11: player.speedX = 0; player.speedY = 0;
- Resets speed to 0 at the start of each frame
- Without this, the player would keep moving forever in the last direction
Line 13-16: if (display.keys[37]) player.speedX = -200 * dt;
-
display.keys[37]checks if the left arrow key is pressed -
37is the keycode for the left arrow -
player.speedX = -200 * dtsets horizontal speed to -200 pixels per second (moving left) -
39is right arrow,200 * dtmeans 200 pixels per second to the right -
38is up arrow,-200 * dtmeans moving up -
40is down arrow,200 * dtmeans moving down
Line 18: move.bound(player);
- Keeps the player on screen by clamping their position to the canvas edges
- If the player tries to go past an edge, it stops them
🧭 Method 2: Angular Movement with moveAngle()
The Concept
moveAngle() is the second movement method in Limn Engine. Instead of using speedX and speedY directly, it applies speed relative to the component's angle property. This is useful when you want objects to move in the direction they're facing.
How it works internally:
When the engine calls moveAngle() on a component, it does this:
- If physics is enabled, it adds gravity to the vertical speed
- It calculates the horizontal movement:
speedX * cos(angle) - It calculates the vertical movement:
speedY * sin(angle)
This means if you set angle = 45 degrees and speedX = 5, the object moves diagonally. As the angle changes, the direction of movement changes.
The key principle: In moveAngle(), the speed values are applied relative to the object's facing direction. This makes it perfect for rotating objects, orbiting, and directional movement.
What We're About to Do
We're going to create an object that moves in the direction it's facing. This is useful for orbiting objects, enemies that chase the player, and driving games.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#0a0a10");
const orbiter = new Component(20, 20, "cyan", 400, 300, "rect");
orbiter.angularMovement = true; // Uses moveAngle() instead of move()
orbiter.speedX = 2;
orbiter.speedY = 2;
display.add(orbiter);
function update(dt) {
// Rotate continuously
orbiter.angle += 0.02;
}
What Each Line Does
Line 6: const orbiter = new Component(20, 20, "cyan", 400, 300, "rect");
- Creates a 20x20 cyan square at the center of the screen
Line 7: orbiter.angularMovement = true;
-
This is the key line. Tells the engine to use
moveAngle()instead ofmove() - With this set to
true, the component's speed is applied relative to its angle
Line 8-9: orbiter.speedX = 2; orbiter.speedY = 2;
- Sets the speed values that will be applied in the direction the object is facing
Line 10: display.add(orbiter);
- Adds the orbiter to the game world
Line 13-14: function update(dt) { orbiter.angle += 0.02; }
- Increases the angle by 0.02 radians each frame
- As the angle changes, the direction of movement changes — creating an orbit
🚗 Method 3: Directional Movement (Car Example)
The Concept
Directional movement combines rotation with movement. The object can turn and then move forward in its new direction. This is how cars, tanks, and many game characters work.
How it works:
Limn Engine provides helper functions for directional movement:
-
move.forward()— Moves the object in the direction it's facing -
move.backward()— Moves the object opposite to the direction it's facing -
move.turnLeft()andmove.turnRight()— Rotate the object
These functions use the object's angle property internally. move.forward() calculates the direction using cos(angle) and sin(angle), then moves the object that way.
The key principle: Directional movement separates turning from moving. You turn first, then you move forward. This creates realistic movement like a car.
What We're About to Do
We're going to build a car that drives forward, backward, and turns. This demonstrates directional movement, rotation, and how to combine movement functions.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#1a1a2e");
const car = new Component(40, 20, "#ff6b6b", 400, 300, "rect");
car.changeAngle = true; // Enable rotation
display.add(car);
function update(dt) {
// Car controls
if (display.keys[38]) move.forward(car, 3); // Up: drive forward
if (display.keys[40]) move.backward(car, 2); // Down: reverse
if (display.keys[37]) move.turnLeft(car, 0.05); // Left: turn left
if (display.keys[39]) move.turnRight(car, 0.05); // Right: turn right
// Keep car on screen
move.bound(car);
}
What Each Line Does
Line 6-8: const car = new Component(40, 20, "#ff6b6b", 400, 300, "rect"); car.changeAngle = true; display.add(car);
- Creates a 40x20 red rectangle at the center (car shape)
-
changeAngle = true— Enables rotation on the car so it can turn - Adds the car to the game world
Line 11: if (display.keys[38]) move.forward(car, 3);
-
display.keys[38]checks if the up arrow is pressed -
move.forward(car, 3)— Moves the car forward 3 pixels in the direction it's facing
Line 12: if (display.keys[40]) move.backward(car, 2);
-
display.keys[40]checks if the down arrow is pressed -
move.backward(car, 2)— Moves the car backward 2 pixels
Line 13: if (display.keys[37]) move.turnLeft(car, 0.05);
-
display.keys[37]checks if the left arrow is pressed -
move.turnLeft(car, 0.05)— Rotates the car left by 0.05 radians (about 3 degrees)
Line 14: if (display.keys[39]) move.turnRight(car, 0.05);
-
display.keys[39]checks if the right arrow is pressed -
move.turnRight(car, 0.05)— Rotates the car right by 0.05 radians
Line 16: move.bound(car);
- Keeps the car on screen
✨ Method 4: Smooth Movement with glideTo()
The Concept
glideTo() creates smooth, easing-based movement to a target position. Instead of teleporting or moving at constant speed, the object starts fast and slows down as it approaches its destination. This is called "ease-out" and creates natural-looking motion.
How it works internally:
When you call move.glideTo(coin, 1000, 500, 300):
- Records the starting position and the current time
- Each frame, calculates how much time has passed (progress from 0 to 1)
- Applies cubic easing formula:
eased = 1 - (1 - progress)³ - Calculates the new position based on the eased value
- Updates the object's position
The key principle: glideTo() handles all the timing and easing for you. You just specify the target and how long it should take.
What We're About to Do
We're going to move a coin smoothly to random positions with easing. This creates professional-looking movement without manual lerp functions.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#1a1a2e");
const coin = new Component(30, 30, "gold", 400, 300, "rect");
display.add(coin);
const infoText = new Tctxt("18px", "Arial", "white", 20, 50);
infoText.setText("Press Space to move coin");
display.add(infoText);
infoText.fixed();
function update(dt) {
// Move coin to random position when Space is pressed
if (display.keys[32]) {
move.glideTo(coin, 1000, Math.random() * 700, Math.random() * 500);
display.keys[32] = false; // Prevent holding
}
}
What Each Line Does
Line 6-7: Creates a 30x30 gold square at the center and adds it to the game world
Line 9-12: Creates a text label at the top-left and locks it to the screen position so it doesn't move with the camera
Line 15-19: Checks if Space is pressed, then glides the coin to a random position over 1 second
⚡ Method 5: Physics-Based Movement
The Concept
Physics-based movement uses acceleration and deceleration to create realistic, weighty motion. Instead of moving at constant speed, objects smoothly speed up and slow down.
How it works:
-
move.accelerate()— Adds acceleration to speed, clamped to a maximum -
move.decelerate()— Reduces speed toward zero without overshooting
The key principle: You control acceleration, not speed directly. This creates realistic physics like a car.
What We're About to Do
We're going to create a player with smooth acceleration and deceleration, like a car. This creates realistic, weighty movement.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#1a1a2e");
const player = new Component(40, 40, "#5b8cff", 400, 300, "rect");
display.add(player);
function update(dt) {
// Accelerate or decelerate
if (display.keys[68]) { // D key
move.accelerate(player, 0.6, 0, 8, 0); // Accelerate right
} else if (display.keys[65]) { // A key
move.accelerate(player, -0.6, 0, 8, 0); // Accelerate left
} else {
move.decelerate(player, 0.4, 0); // Slow down
}
// Keep player on screen
move.bound(player);
}
What Each Line Does
Line 9: if (display.keys[68]) { move.accelerate(player, 0.6, 0, 8, 0); }
- Checks if the D key is pressed
-
move.accelerate()— Adds horizontal acceleration at 0.6 per frame, max speed 8
Line 11: else if (display.keys[65]) { move.accelerate(player, -0.6, 0, 8, 0); }
- Checks if the A key is pressed
- Negative acceleration moves the player left
Line 13: else { move.decelerate(player, 0.4, 0); }
-
move.decelerate()— Reduces horizontal speed toward zero at 0.4 per frame
Line 16: move.bound(player);
- Keeps the player on screen
🎯 Method 6: Complete Movement Demo
The Concept
In a real game, you'll often need to combine multiple movement methods. This demo shows how different movement techniques can work together in one game.
What We're About to Do
We're going to combine multiple movement methods into a single complete game.
The Code
const display = new Display();
display.perform();
display.start(800, 600);
display.backgroundColor("#1a1a2e");
// ── PLAYER ──
const player = new Component(40, 40, "#5b8cff", 400, 300, "rect");
display.add(player);
// ── ORBITER ──
const orbiter = new Component(20, 20, "cyan", 400, 300, "rect");
orbiter.angularMovement = true;
orbiter.speedX = 2;
orbiter.speedY = 2;
display.add(orbiter);
// ── UI ──
const infoText = new Tctxt("18px", "Arial", "white", 20, 50);
infoText.setText("Arrow keys to move, Space to glide, A/D to accelerate");
display.add(infoText);
infoText.fixed();
function update(dt) {
// ── PLAYER MOVEMENT (Arrow Keys) ──
player.speedX = 0;
player.speedY = 0;
if (display.keys[37]) player.speedX = -200 * dt;
if (display.keys[39]) player.speedX = 200 * dt;
if (display.keys[38]) player.speedY = -200 * dt;
if (display.keys[40]) player.speedY = 200 * dt;
move.bound(player);
// ── ORBITER (Angular Movement) ──
orbiter.angle += 0.02;
// ── GLIDE (Space Key) ──
if (display.keys[32]) {
move.glideTo(player, 1000, Math.random() * 700, Math.random() * 500);
display.keys[32] = false;
}
}
What Each Line Does
Lines 6-7: Creates the player with basic arrow key movement
Lines 10-14: Creates an orbiter with angular movement that orbits continuously
Lines 17-20: Creates a UI label that stays on screen
Lines 23-29: Player movement with arrow keys and boundary keeping
Lines 31-32: Orbiter rotation — increases angle each frame
Lines 34-38: Glide effect — when Space is pressed, the player glides to a random position
💡 Pro Tips
1. Always Use dt for Consistent Speed
// ✅ CORRECT: Frame-rate independent
player.speedX = 200 * dt;
// ❌ WRONG: Frame-rate dependent
player.speedX = 4;
Why this matters: On a fast computer, speedX = 4 moves at 240 pixels per second. On a slow computer, it moves at 120 pixels per second. Using dt ensures everyone plays at the same speed.
2. Normalize Diagonal Movement
let mx = 0, my = 0;
if (display.keys[37]) mx = -1;
if (display.keys[39]) mx = 1;
if (display.keys[38]) my = -1;
if (display.keys[40]) my = 1;
if (mx !== 0 && my !== 0) {
mx *= 0.707; // Prevent faster diagonal movement
my *= 0.707;
}
player.x += mx * 200 * dt;
player.y += my * 200 * dt;
Why this matters: Without normalization, moving diagonally is 1.4 times faster than moving in a single direction. The 0.707 factor (which is 1/√2) keeps the speed consistent.
3. Combine Movement Methods
// Use accelerate for smooth movement + bound to stay on screen
move.accelerate(player, 0.6, 0, 8, 0);
move.bound(player);
// Use pointTo to aim + forward to move
move.pointTo(enemy, player.x, player.y);
move.forward(enemy, 2);
Why this works: Each function handles one aspect of movement. Combining them gives you complex behavior with simple code.
📊 Movement Method Reference
| Method | What It Does | When to Use |
|---|---|---|
move.bound() |
Keeps object on screen | Every game |
move.boundTo() |
Custom boundaries | Arena games |
move.forward() |
Move in facing direction | Driving games |
move.backward() |
Move opposite facing direction | Driving games |
move.turnLeft() |
Rotate left | Driving games |
move.turnRight() |
Rotate right | Driving games |
move.pointTo() |
Face a target | Aiming, tracking |
move.circle() |
Orbital motion | Orbiting enemies |
move.glideTo() |
Smooth movement | Animations, cutscenes |
move.accelerate() |
Smooth acceleration | Physics-based movement |
move.decelerate() |
Smooth deceleration | Physics-based movement |
move.project() |
Projectile physics | Arrows, fireballs |
move.hitObject() |
Platform collision | Platformers |
move.teleport() |
Instant position | Respawns, checkpoints |
🎯 The One-Line Summary
"Limn Engine offers 15+ movement methods — from simple speed-based movement to complex physics, smooth gliding, and angular motion — so you can build any game mechanic."
Draw your game into existence — one movement at a time. 🏃🚀
Top comments (0)