game-physicskinematics

How quick must an Object be in is y component to reach a specific peak


I want to add a boost to the player (bottom left) that reaches a specific peak(Point (x,y)). The players horizontal speed (v_x) is constant but its vertical speed can vary. I want to calculate the needed end speed v_y that it needs to reach the point (x,y) (this has to be the peak) which is also given. I thought maybe I can get the angle between Vector player and Vector point and calculate with the kinematic formulars but it didnt give me the right result.

enter image description here


Solution

  • Jump? How high?

    Gravity is constant, but the distance to the obstacle is changing as we approach.

    At some point there is a distance to the obstacle where the jump velocity is the minimum needed to clear it.

    Further back will require a greater velocity to stay in the air long enough to clear it, or closer the velocity must be greater to clear in the reaming time.

    It is unclear if you want the optimal jump velocity to clear an object, which will also define when to jump.

    So we will just make it Given gravity as acceleration and a horizontal velocity what vertical velocity is required to jump an obstacle of height and distance.

    Getting jump velocity

    To keep it simple as we go we first normalize the horizontal velocity

    Terms used

    Steps used to solve

    The equation for velocity at time when falling (free fall) is v + at

    With the equation f'(t) = v + at the anti-derivative of that equation which is f(t) = vt + (1/2)at^2 tells use how high we are at any time t

    Because we have normalized horizontal velocity s the distance to the obstacle is equivalent to time to obstacle t or distance to obstacle d

    We can also use the anti-derivative to solve a height for a given distance h = vd+(1/2)ad^2 (replacing t with d)

    Now we have all the terms we want in the one equation. h = vd+(1/2)ad^2

    We want the jump velocity so rearrange h = vd+(1/2)ad^2 in terms of v we get v = -ad/2+h/d

    One last thing. The horizontal velocity is normalized and thus always 1. We need to use any velocity. We can do this by dividing the distance to obstacle d by the horizontal velocity (speed) s. In other words how long in seconds to cover distance d at speed s

    The final result is v = -a(d/s)/2+h/(d/s)

    As code

    Lets put that into code (JavaScript)

    The function jumpObstacle(jumper, obstacle) will set the jump velocity of the object jumper to clear the object obstacle only if the jumper is moving towards the obstacle.

    The Jumper has a jumpClear to give a clearance and a max jump velocity maxJumpVel. This means that the jump may not clear the obstacle.

    The function returns the jump height or 0 if no jump.

    // Assuming +y is up
    const gravity = 9.0;
    const jumper = {
        vx: 1,
        vy: 0,
        x: 0,
        y: 0,
        maxJumpVel: 3,  // max jump velocity y
        jumpClear: 0.5, // clearance when jumping. May not if max jump to high
    };
    const wall = {
        x: 4,
        y: 0,   // base
        h: 4,  // height
    };
    function jumpObstacle(jumper, obstacle) {
        const dist = obstacle.x - jumper.x;
        if (jumper.vx && Math.sign(jumper.vx) === Math.sign(dist)) {
            const nd = dist / jumper.vx;                    // normalize dist (time)
            const h = (obstacle.h + obstacle.y + jumper.jumpClear) - jumper.y; // height to clear
            const vy = -(gravity * nd) / 2 + h / nd;
            jumper.vy = Math.min(vy, jumper.maxJumpVel);
            return jumper.y + vy * nd + gravity * nd * nd * 0.5;
        }
        return 0;
    }
    

    Demo

    I am not sure if the answer is as clear it should be. The demo implements the above as a interactive demo.

    Click or Tap the animation when box is in range of wall. The jumpObstacle function will calculate the jump velocity needed to clear the wall.

    I have also added the width of the box, and the x jump clearance to ensure that the jump visually clears the wall.

    If the jumper can not clear the wall it will flash red. Try again next time around.

    The red box under the ground indicates the approx distance that the jumper is able to clear the wall.

    Note that the coordinates are Y+ is down

    Note The lowest jump speed I manged to was 271px per second

    // Note y is + down the page
    requestAnimationFrame(update);
    const ctx = canvas.getContext("2d");
    ctx.fillStyle = "red";
    const h = 120, w = 200;
    const ground = h - 10;
    canvas.addEventListener("click", () => {
        jumper.onGround && (jumper.jumpNextFrame = true); 
    });
    
    const gravity = 900.0;
    const jumper = {
        vx: 60,
        vy: 0,
        x: 0,
        y: ground,
        h: 24,
        w: 8,
        jumpTime: 0,    
        jumpNextFrame: false,
        get onGround() { return this.y >= ground },
        maxJumpVel: -490, 
        jumpClear: -5,
        jumpClearX: 5,
        jumpFail: 0,
        draw(ctx) { 
            this.jumpFail-- > 0 && ctx.fillRect(this.x - this.w / 2, this.y - this.h, this.w, this.h);
            ctx.strokeRect(this.x - this.w / 2, this.y - this.h, this.w, this.h);
        },
        jump(time, what) {
            this.jumpNextFrame = false;
            this.jumpTime = time;
            const h = jumpObstacle(this, what);
            if (h < 0 || h > what.h) { 
                this.jumpTime = 0;
                this.jumpFail = 10;
                this.vy = 0;
            } else {
                console.clear();
                console.log("Jump vel: " + (-this.vy).toFixed(0) + "px per second");
            }
        },
        update(time) {
            this.x = this.vx * time;
            if (this.x > w ) { this.x = this.x % w }  
            if (this.jumpTime > 0) {
                const t = time - this.jumpTime;
                this.y = ground + this.vy * t + gravity * t * t * 0.5;
                if (this.y > ground) {
                    this.vy = 0;
                    this.jumpTime = 0;
                    this.y = ground;
                }            
            }
        }
    };
    const wall = {
        x: 140,
        y: ground,   
        h: 34,  
        draw(ctx) { ctx.strokeRect(this.x - 1, this.y - this.h, 2, this.h) },
    };
    function jumpObstacle(jumper, obstacle) {
        const dist = (obstacle.x - (jumper.x - jumper.w)) + jumper.jumpClearX;
        if (jumper.vx && Math.sign(jumper.vx) === Math.sign(dist)) {
            const nd = (dist / jumper.vx) ;                    
            const h = -((obstacle.h + obstacle.y + jumper.jumpClear) - jumper.y); 
            const vy = -(gravity * nd) / 2 + h / nd;
            jumper.vy = vy < jumper.maxJumpVel ? jumper.maxJumpVel : vy;
            return (jumper.vy * nd + gravity * nd * nd * 0.5) - h;
        }
        return 0;
    }
    
    function update(time) {
        time /= 1000;
        ctx.clearRect(0, 0, w, h);
        ctx.strokeRect(-1, ground, w + 2, h - ground + 2); // draw ground
        ctx.fillRect(wall.x - 50, ground + 2, 30, 2);
        if (jumper.jumpNextFrame) { jumper.jump(time , wall) }
        jumper.update(time);
        jumper.draw(ctx);
        wall.draw(ctx);
        requestAnimationFrame(update);
    }
      console.log("Click animation to jump. Will flash red is not able to clear wall");
    canvas { border: 1px solid black; }
    <canvas id="canvas" width ="200" height="120"></canvas>