TCJSgame Movement Utility: The Complete Guide (Correcting Previous Tutorial Mistakes)
If you've been following my TCJSgame tutorials, I need to make an important correction. In previous articles, I incorrectly explained how certain movement functions work. Specifically, glideX, glideY, and glideTo MUST be called in the update() function (the game loop) to work properly. This article corrects those mistakes and provides the definitive guide to TCJSgame's movement system.
🚨 Important Correction to Previous Tutorials
What I Got Wrong: I previously stated that glide functions were "one-time actions" that could be called outside the game loop.
The Truth: glideX, glideY, and glideTo need continuous updates in the update() function because they manage movement over multiple frames. Calling them once outside the loop will start the movement but won't properly manage its completion.
Let's fix this misunderstanding once and for all.
🎯 Understanding TCJSgame's Game Loop
TCJSgame runs at approximately 50 FPS using this core loop:
// Inside Display class:
this.interval = setInterval(() => this.updat(), 20); // ~50 FPS
// The updat() method calls YOUR update() function:
updat() {
this.clear();
this.frameNo += 1;
// ... camera setup ...
try {
update(); // ← YOUR GAME LOGIC GOES HERE
} catch (e) {}
// ... render components ...
}
Key Insight: Your update() function runs ~50 times per second. Any movement that needs frame-by-frame adjustment must be managed within this function.
🔧 Movement Functions: Loop vs. One-Time
MUST BE IN update() LOOP:
function update() {
// These need continuous frame-by-frame updates:
// 1. GLIDE FUNCTIONS (Corrected!)
move.glideX(object, duration, targetX);
move.glideY(object, duration, targetY);
move.glideTo(object, duration, targetX, targetY);
// 2. BOUNDARY FUNCTIONS
move.bound(object);
move.boundTo(object, left, right, top, bottom);
// 3. PHYSICS FUNCTIONS
move.hitObject(object, otherObject);
move.accelerate(object, accelX, accelY, maxX, maxY);
move.decelerate(object, decelX, decelY);
// 4. CONTINUOUS ROTATION
move.circle(object, angleIncrement); // If you want continuous rotation
// 5. CONTINUOUS DIRECTION TRACKING
move.pointTo(object, targetX, targetY); // If target moves
}
CAN BE OUTSIDE LOOP (One-time actions):
// These work as instant actions:
move.teleport(object, x, y); // Instant position change
move.setX(object, x); // Set X position
move.setY(object, y); // Set Y position
move.position(object, "center"); // Pre-defined positioning
move.circle(object, 45); // Set static rotation angle
move.project(object, 10, 45, 0.1); // Projectile with built-in animation
🛠️ The Glide Functions: Correct Implementation
Here's the correct way to use glide functions, fixing my previous tutorials:
The WRONG Way (From Previous Tutorials):
// ❌ INCORRECT - This doesn't work properly!
button.onclick = () => {
move.glideTo(player, 2, 400, 300); // Called once
};
The RIGHT Way:
// ✅ CORRECT - Manages glide state in update()
let isGliding = false;
let glideTargetX = 0;
let glideTargetY = 0;
let glideDuration = 0;
let glideStartTime = 0;
// Trigger glide start (can be outside loop)
button.onclick = () => {
isGliding = true;
glideTargetX = 400;
glideTargetY = 300;
glideDuration = 2; // seconds
glideStartTime = Date.now();
};
// Manage glide in update() loop
function update() {
if (isGliding) {
// Calculate progress
const elapsed = (Date.now() - glideStartTime) / 1000;
const progress = Math.min(elapsed / glideDuration, 1);
// Apply glide (this sets speedX/speedY)
move.glideTo(player, glideDuration, glideTargetX, glideTargetY);
// Check if glide is complete
if (progress >= 1) {
isGliding = false;
player.speedX = 0;
player.speedY = 0;
player.x = glideTargetX; // Ensure exact position
player.y = glideTargetY;
}
}
}
📝 Complete Movement Reference (Corrected)
1. Basic Positioning (Outside Loop)
// Instant positioning - works anywhere
move.teleport(player, 100, 200);
move.setX(enemy, 300);
move.setY(collectible, 150);
// Pre-defined positions
move.position(scoreText, "top", 10); // Top center, 10px margin
move.position(healthBar, "right", 20); // Right center
move.position(title, "center"); // Absolute center
2. Continuous Movement (Inside Loop)
function update() {
// Keyboard-controlled movement
if (display.keys[39]) { // Right arrow
move.accelerate(player, 0.5, 0, 5); // Max speed: 5
} else if (display.keys[37]) { // Left arrow
move.accelerate(player, -0.5, 0, 5);
} else {
move.decelerate(player, 0.3, 0); // Friction
}
// Jump with physics
if (display.keys[38] && player.y >= groundLevel) {
player.speedY = -10;
}
// Apply gravity
player.gravity = 0.5;
}
3. Glide Functions with State Management
class GlideController {
constructor() {
this.activeGlides = new Map();
}
startGlide(object, targetX, targetY, duration) {
this.activeGlides.set(object, {
targetX, targetY, duration,
startX: object.x, startY: object.y,
startTime: Date.now()
});
}
update() {
const now = Date.now();
for (const [object, glide] of this.activeGlides.entries()) {
const elapsed = (now - glide.startTime) / 1000;
const progress = Math.min(elapsed / glide.duration, 1);
if (progress >= 1) {
// Glide complete
object.x = glide.targetX;
object.y = glide.targetY;
object.speedX = 0;
object.speedY = 0;
this.activeGlides.delete(object);
} else {
// Update glide in the loop
move.glideTo(object, glide.duration, glide.targetX, glide.targetY);
}
}
}
}
// Usage
const glideController = new GlideController();
button.onclick = () => {
glideController.startGlide(player, 500, 200, 3); // 3-second glide
};
function update() {
glideController.update();
// ... other update logic
}
4. Boundary Management (Inside Loop)
function update() {
// Keep object within canvas
move.bound(player);
// Or within specific bounds
move.boundTo(
enemy,
100, // left
700, // right
50, // top
500 // bottom
);
// Screen wrapping
if (player.x > 800) player.x = 0;
if (player.x < 0) player.x = 800;
if (player.y > 600) player.y = 0;
if (player.y < 0) player.y = 600;
}
🎮 Practical Examples (Corrected)
Example 1: Platformer Character (Fixed)
const player = new Component(32, 32, "blue", 100, 300);
function update() {
// Horizontal movement with acceleration
if (display.keys[39]) { // Right
move.accelerate(player, 0.5, 0, 8);
} else if (display.keys[37]) { // Left
move.accelerate(player, -0.5, 0, 8);
} else {
move.decelerate(player, 0.8, 0);
}
// Jump
if (display.keys[38] && isOnGround(player)) {
player.speedY = -12;
}
// Apply gravity
player.gravity = 0.6;
// Keep on screen
move.boundTo(player, 0, 768, 0, 432);
// Check ground collision
if (player.y >= 400) {
player.y = 400;
player.speedY = 0;
player.gravitySpeed = 0;
}
}
Example 2: Smooth Menu Navigation (Fixed)
let currentSelection = 0;
let menuItems = [];
let isNavigating = false;
// Setup menu
for (let i = 0; i < 5; i++) {
const item = new Component(200, 50, "gray", 100, 100 + i * 70);
item.text = `Option ${i + 1}`;
menuItems.push(item);
display.add(item);
}
function update() {
// Navigation with smooth gliding
if (!isNavigating) {
if (display.keys[40]) { // Down arrow
currentSelection = (currentSelection + 1) % menuItems.length;
isNavigating = true;
// Glide all items (called in loop via state management)
menuItems.forEach((item, index) => {
const targetY = 100 + ((index - currentSelection) * 70);
// Store glide state and manage in loop
startGlide(item, item.x, targetY, 0.3);
});
// Reset flag after glide duration
setTimeout(() => { isNavigating = false; }, 300);
}
}
// Update active glides
updateGlides();
}
🚨 Common Mistakes and Solutions
Mistake 1: Glide Outside Loop
// ❌ WRONG
move.glideTo(object, 2, 400, 300); // Single call
// ✅ CORRECT
let glide = { active: true, targetX: 400, targetY: 300, duration: 2 };
function update() {
if (glide.active) {
move.glideTo(object, glide.duration, glide.targetX, glide.targetY);
// Check completion and set glide.active = false when done
}
}
Mistake 2: Not Resetting Speeds
// ❌ WRONG - Object keeps moving after glide
move.glideTo(object, 2, 400, 300);
// ✅ CORRECT - Stop movement after glide
let glideComplete = false;
function update() {
if (!glideComplete) {
move.glideTo(object, 2, 400, 300);
if (Math.abs(object.x - 400) < 1 && Math.abs(object.y - 300) < 1) {
object.speedX = 0;
object.speedY = 0;
glideComplete = true;
}
}
}
Mistake 3: Multiple Conflicting Glides
// ❌ WRONG - Overwrites previous glide
button1.onclick = () => move.glideTo(object, 2, 100, 100);
button2.onclick = () => move.glideTo(object, 2, 200, 200); // Cancels first
// ✅ CORRECT - Queue or replace glides
let currentGlide = null;
function startNewGlide(targetX, targetY) {
if (currentGlide) {
// Cancel previous glide
object.speedX = 0;
object.speedY = 0;
}
currentGlide = { targetX, targetY, startTime: Date.now() };
}
🔧 Debugging Movement Issues
Add this debug utility to your games:
class MovementDebug {
static logMovement(object, name) {
console.log(`${name}:`, {
position: `(${object.x.toFixed(1)}, ${object.y.toFixed(1)})`,
speed: `(${object.speedX.toFixed(2)}, ${object.speedY.toFixed(2)})`,
gravity: object.gravity,
physics: object.physics,
angle: (object.angle * 180 / Math.PI).toFixed(1) + '°'
});
}
static drawTrajectory(object, ctx) {
// Draw predicted path
ctx.beginPath();
ctx.moveTo(object.x, object.y);
// Simulate next 60 frames
let simX = object.x;
let simY = object.y;
let simSpeedX = object.speedX;
let simSpeedY = object.speedY;
for (let i = 0; i < 60; i++) {
simSpeedY += object.gravity;
simX += simSpeedX;
simY += simSpeedY;
ctx.lineTo(simX, simY);
}
ctx.strokeStyle = 'rgba(255, 0, 0, 0.5)';
ctx.stroke();
}
}
// Usage in update()
function update() {
if (debugMode) {
MovementDebug.logMovement(player, "Player");
MovementDebug.drawTrajectory(player, display.context);
}
}
📚 Conclusion and Apology
To everyone who followed my previous tutorials: I apologize for the incorrect information about glide functions. Game development is a learning process, and even tutorial writers make mistakes. The key takeaway is:
TCJSgame's glideX, glideY, and glideTo functions need to be managed in the update() loop because they control movement over multiple frames.
Remember:
-
Continuous movement = needs to be in
update() -
Instant actions = can be outside
update() - Always test boundary cases and completion states
- Use state management for complex movement sequences
I've updated all my TCJSgame tutorials with corrections, and I'm committed to providing accurate information moving forward. If you find any other issues, please reach out on GitHub or Dev.to!
Try the corrected examples in the TCJSgame Playground and share your creations with #TCJSgameCorrected.
Learning in public means admitting mistakes. Thank you for your understanding as we improve these resources together.
Top comments (0)