Optimization of Kinetic Continuity in 3D Parkour Environments: Swing and Landing Mechanics

In game development, “feel” is everything. When building a parkour engine, movement must feel continuous. If a player performs an action and the character suddenly “snaps” or “teleports” from one height to another, the immersion is shattered.

Recently, while developing a parkour prototype using Three.js, I encountered a critical failure in the Swing Mechanic. This article documents the evolution of that feature: a journey from a rigid, ground-only movement to a fully realized, height-aware physics system.


Phase 1: Solving Input Rigidity

Initially, the swing mechanic was functionally primitive. The system was logically bound to a single state: the player had to be standing on the floor to interact with the environment.

The Problem: State-Dependent Input

The input handler restricted the KeyF (Swing) action inside a conditional block that only checked if the player’s state was ‘ground’. This created a “dead zone” where jumping or falling made the mechanic unreachable.

The Problematic Code:

code JavaScript

// ❌ THE PROBLEM: Input is locked to the 'ground' state
function onPress(code) {
    if (state === 'ground') {
        switch (code) {
            case 'KeyE': tryHang(); break;
            case 'KeyF': trySwing(); break; // Logic fails if state is 'air'
        }
    }
}

The Fix: Universal Input Triggering

To achieve true parkour flow, a player must be able to grab a bar mid-air. I refactored the input logic to decouple the swing trigger from the grounded state.

The Optimized Code:

code JavaScript

// ✅ THE FIX: Decoupling input from the 'ground' state
function onPress(code) {
    if (state === 'ground') { /* ...ground-only actions... */ }
    
    // The swing trigger is now state-agnostic
    if (code === 'KeyF') {
        trySwing(); // Works during 'ground' OR 'air' states
    }
}

Phase 2: The “Teleportation Pop” Bug

Once the player could grab the bar from the air, a much more jarring issue emerged: Kinematic Discontinuity. If a player jumped from a high building and grabbed a bar, they would instantly “teleport” to the floor the moment they touched the bar.

The Problem: Hard-Coded Coordinate Snapping

The bug originated in the trySwing function. Upon engagement, the system was hard-coding the player’s vertical position (Y_pos) to 0 (the ground plane).

The Problematic Code:

code JavaScript

// ❌ THE PROBLEM: Hard-coded ground snap
function trySwing() {
    // ... logic to find bar ...
    
    // This '0' forced the player to the floor instantly.
    pos.set(targetX, 0, targetZ); 
    
    state = 'swing';
}

The resulting visual “jitter” followed this sequence:

  1. Grab: Player engages the bar mid-air.
  2. Teleport: Character instantly snaps to Y=0Y=0 (the ground).
  3. Correction: The physics engine realizes the player is actually above a block.
  4. Pop: The character is violently “yanked” back up to the block.

Phase 3: The Advanced Physics Solution

To achieve a professional “feel,” I implemented a three-tier optimization strategy: Height Memory, Sinusoidal Interpolation, and Smart Landing.

Step 1: Temporal Height Memory

We moved away from hard-coded values by implementing Height Memory. The system now captures the player’s exact vertical position (Y_start) at the moment of engagement.

The Optimized Code:

code JavaScript

// ✅ Inside trySwing()
swingStartY = pos.y; // Capture the exact height of the jump/fall

// Snap to the BAR height, not the floor, preserving verticality
pos.set(targetX, bar.y, targetZ);

Step 2: Sinusoidal Path Interpolation

A natural swing follows an arc, not a straight line. I used Linear Interpolation (LERP) to transition between the starting height (Y_start) and the landing height (Y_land), combined with a Sine Wave to simulate gravitational momentum.

The Optimized Code:

code JavaScript

// ✅ THE FIX: Smooth height interpolation via Sinusoidal Arc
function updateSwing() {
    const t = clamp(activeAction.time / duration, 0, 1);
    const u = t * t * (3 - 2 * t); // Smoothstep easing

    // LERP: Move from captured start height to predicted landing height
    const heightTransition = THREE.MathUtils.lerp(swingStartY, swingLandY, u);
    
    // Sinusoidal Arc: Adds a "swing boost" to create a physical curve
    const arcHeight = Math.max(swingStartY, swingLandY, 1.0) * 0.5;
    pos.y = heightTransition + arcHeight * Math.sin(Math.PI * u);
}

Step 3: Smart Landing (Conditional State Switching)

Finally, we addressed the “end-of-animation” transition. If the swing ends over a platform, we snap to it. If the swing ends in mid-air, we must avoid snapping to the ground and instead transition the player into a natural falling state.

The Optimized Code:

code JavaScript

// ✅ THE FIX: Conditional landing logic
function finishSwing() {
    if (swingLandY > 0) {
        // Scenario A: Surface detected -> Transition to Grounded
        pos.x = swingTargetPos.x;
        pos.y = swingLandY;
        state = 'ground';
    } else {
        // Scenario B: No surface detected -> Transition to Airborne (Natural Fall)
        state = 'air';
        airMode = 'fall';
    }
}

Conclusion

By moving away from hard-coded coordinate snapping and embracing a State-Based Physics model, we transformed a glitchy, broken mechanic into a fluid movement system.

Key Takeaway for Developers: In any physics-based engine, always respect the player’s current momentum and position. Never force a coordinate unless you have mathematically accounted for the transition from the previous state.


Keywords: Three.js, Game Development, Physics Engine, JavaScript, Parkour Game, Bug Fixing, Lerp, Kinematics, Animation.