Revising TCJSgame Movement: A Guide for North East India Developers
Correcting a Common Misconception about TCJSgame Movement
In recent tutorials, we inadvertently misguided our readers regarding the proper usage of certain movement functions in TCJSgame. This article aims to rectify those mistakes and provide a comprehensive guide to TCJSgame's movement system.
Understanding the TCJSgame Game Loop
TCJSgame operates at approximately 50 FPS with the following core loop:
// Inside Display class: this.interval = setInterval(() => this.update(), 20); // ~50 FPS // The update() method calls YOUR update() function: updat() { this.clear(); this.frameNo += 1; // ... camera setup ... try { update(); // YOUR GAME LOGIC GOES HERE } catch (e) {} // ... render components ... } Your update() function runs ~50 times per second. Any movement that requires frame-by-frame adjustment must be managed within this function.
Movement Functions: Loop vs. One-Time
MUST BE IN update() LOOP:
GLIDE FUNCTIONS (Corrected!)- manage movement over multiple frames:
move.glideX(object, duration, targetX); move.glideY(object, duration, targetY); move.glideTo(object, duration, targetX, targetY); BOUNDARY FUNCTIONSmove.bound(object); move.boundTo(object, left, right, top, bottom); PHYSICS FUNCTIONSmove.hitObject(object, otherObject); move.accelerate(object, accelX, accelY, maxX, maxY); move.decelerate(object, decelX, decelY); CONTINUOUS ROTATIONmove.circle(object, angleIncrement); CONTINUOUS DIRECTION TRACKINGmove.pointTo(object, targetX, targetY); CAN BE OUTSIDE LOOP (One-time actions):
TELEPORT, SET X/Y, POSITION, CIRCLE, PROJECTILE- work as instant actions:
move.teleport(object, x, y); move.setX(object, x); move.setY(object, y); move.position(object, "center"); move.circle(object, 45); move.project(object, 10, 45, 0.1); The Glide Functions: Correct Implementation
Here's the correct way to use glide functions:
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; } } } Practical Examples: Corrected
Platformer Character (Fixed)
const player = new Component(32, 32, "blue", 100, 300); function update() { // Horizontal movement with acceleration 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.8, 0); // Friction } // Jump with physics if (display.keys[38] && player.y >= groundLevel) { 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; } } 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:
// Single call move.glideTo(object, 2, 400, 300); 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
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 function update() { if (debugMode) { MovementDebug.logMovement(player, "Player"); MovementDebug.drawTrajectory(player, display.context); } } } Conclusion and Apology
We sincerely apologize for the incorrect information about glide functions in our previous tutorials. Game development is a learning process, and even tutorial writers make mistakes. The key takeaway is:
TCJSgame's glideX, glideY, and glideTo functionsneed to be managed in theupdate()loop because they control movement over multiple frames.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
We have updated all our TCJSgame tutorials with corrections, and we are 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.