What is the best way to implement gravity in order to make my character jump effectively

As a beginner in the world of coding, I am currently working on creating a game. However, one major challenge I am facing is simulating gravity to make my character jump effectively. Despite trying various methods, I have encountered disappointing results. Below is a snippet of my code:

#canvas {
  border: 1px solid #d3d3d3;
  background-color: #f1f1f1;
}
<body>

  <canvas id='canvas' width='512px' height='300px'></canvas>

  <script>
    let canvas = document.getElementById('canvas');
    let ctx = canvas.getContext('2d');
    let charX = 20;
    let charY = 130;
    let charSide = 20;
    let velocity = 0;
    let resistance = 0;
    let rightPressed = false;
    let leftPressed = false;
    let upPressed = false;
    let aPressed = false;
    let dPressed = false;

    function drawRect(x, y, width, height, color) {
      ctx.beginPath();
      ctx.rect(x, y, width, height);
      ctx.fillStyle = color;
      ctx.fill();
      ctx.closePath();
    }

    function drawGround(x, y, count) {
      if (count === undefined) {
        count = 1;
      }
      drawRect(x, y, 32 * count, canvas.height - y, '#684027');
      drawRect(x, y, 32 * count, 10, 'green');
    }

    function draw() {
      //Updates Game
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      //Draws Character
      drawRect(charX, charY, charSide, charSide, 'lime');
      //Draws Ground
      drawGround(0, 150, 16);
      //Moves Character
      if (charY = 130) {
        speed = 0;
        accelerate = 0;
      }
      if (rightPressed && charX < canvas.width - charSide) {
        charX += 2;
      } else if (leftPressed && charX > 0) {
        charX -= 2;
      } else if (upPressed && charY > 0 && charY < 131) {
        velocity = 0;
        velocity += 50;
        resistance++;
        velocity -= resistance;
        charY -= velocity;
      } else if (upPressed === false && charY > 129) {
        resistance = 0;
        velocity = 0;
      }
    }
    //Character Movement Logic
    document.addEventListener("keydown", keyDownHandler, false);
    document.addEventListener("keyup", keyUpHandler, false);

    function keyDownHandler(e) {
      if (e.keyCode == 39) {
        rightPressed = true;
      } else if (e.keyCode == 37) {
        leftPressed = true;
      } else if (e.keyCode == 38) {
        upPressed = true;
      } else if (e.keyCode == 65) {
        aPressed = true;
      } else if (e.keyCode == 68) {
        dPressed = true;
      }
    }

    function keyUpHandler(e) {
      if (e.keyCode == 39) {
        rightPressed = false;
      } else if (e.keyCode == 37) {
        leftPressed = false;
      } else if (e.keyCode == 38) {
        upPressed = false;
      } else if (e.keyCode == 65) {
        aPressed = false;
      } else if (e.keyCode == 68) {
        dPressed = false;
      }
    }
    //Animates Game
    setInterval(draw, 10);
  </script>

</body>

In an attempt to implement gravity for character jumping, I experimented with variables like velocity and resistance but unfortunately, it did not yield the desired outcome.

Answer №1

Elementary game physics

When it comes to animating, the key is to use requestAnimationFrame to control the timing of the animation. The example below illustrates how this can be achieved.

Gravity

The most basic form of gravity involves assuming a consistent frame rate. An object possesses both a y position and a y speed (delta y). Gravity acts as a constant force that incrementally increases the delta y each frame.

obj = {
   y : 0,  // position
   dy : 0, // speed
   size : 20, // height
   onGround : false,  // true if on the ground
   drag : 0.99, // with drag being 0.01 
}
const grav = 0.1;

On each frame (animation tick), apply gravity and update the position accordingly.

obj.dy += grav;
obj.y += obj.dy;

If the object reaches the ground, reset its delta y to zero.

if(obj.y + obj.size >= 150){ // collision with ground
   obj.y = 150 - obj.size;  // reposition on the ground
   obj.dy = 0;              // stop delta y
   obj.onGround = true;
}else{
   obj.onGround = false;
}

Subsequently, proceed to draw the character.

Jump

To execute a jump for the character, set the delta y to a negative value only when the character is grounded. The code above ensures that the character descends back down in due course.

if(keyboard.up && obj.onGround){
   obj.dy = -5;
}

Drag

To introduce resistance or drag proportional to the speed, incorporate a drag coefficient. Multiply the delta y by this coefficient after adding the gravitational force every frame.

obj.dy *= obj.drag;  // decrease speed due to drag

The example segment portrays a player capable of jumping off the ground and moving horizontally. There exists some drag during the jump, while significant drag applies when the player is stationed on the ground for horizontal movement.

Demonstration

const ctx = canvas.getContext('2d');

// Simple keyboard event handler
const keyboard = (() => {
  document.addEventListener("keydown", keyHandler);
  document.addEventListener("keyup", keyHandler);
  const keyboard = {
    right: false,
    left: false,
    up: false,
    any : false,
  };
  function keyHandler(e) {
    const state = e.type === "keydown"
    if (e.keyCode == 39) {
      keyboard.right = state;
    } else if (e.keyCode == 37) {
      keyboard.left = state;
    } else if (e.keyCode == 38) {
      keyboard.up = state;
      e.preventDefault();
    }
    if(state) { keyboard.any = true } // must reset when used
  }
  return keyboard;
})();

// define the player.
// update() updates position and responds to keyboard input
// draw() displays the player
// start() establishes initial position and state
const player = {
  x: 0,
  y: 0,
  dx: 0, // delta x and y
  dy: 0,
  size: 20,
  color: 'lime',
  onGround: false,
  jumpPower: -5,  // intensity of jump (smaller value implies higher jumps eg. -10 smaller than -5)
  moveSpeed: 2,
  update() {
    // react based on keyboard state
    if (keyboard.up && this.onGround) { this.dy = this.jumpPower }
    if (keyboard.left) { this.dx = -this.moveSpeed }
    if (keyboard.right) { this.dx = this.moveSpeed }
 
    // apply gravity, drag, and move the player
    this.dy += world.gravity;
    this.dy *= world.drag;
    this.dx *= this.onGround ? world.groundDrag : world.drag;
    this.x += this.dx;
    this.y += this.dy;

    // check for ground contact and boundary limits
    if (this.y + this.size >= world.ground) {
      this.y = world.ground - this.size;
      this.dy = 0;
      this.onGround = true;
    } else {
      this.onGround = false;
    }
    if (this.x > ctx.canvas.width) {
      this.x -= ctx.canvas.width;
    } else if (this.x + this.size < 0) {
      this.x += ctx.canvas.width;
    }
  },
  draw() {
    drawRect(this.x, this.y, this.size, this.size, this.color);
  },
  start() {
    this.x = ctx.canvas.width / 2 - this.size / 2;
    this.y = world.ground - this.size;
    this.onGround = true;
    this.dx = 0;
    this.dy = 0;
  }
}
// define world
const world = {
  gravity: 0.2, // gravity strength per frame
  drag: 0.999, // adjust to modify drag
  groundDrag: 0.9, // tweak to alter ground movement
  ground: 150,
}
// establish starting point
player.start();
// initiate first frame logic after running all other code
requestAnimationFrame(mainLoop); // commence upon readiness

// From OP
function drawRect(x, y, width, height, color) {
  ctx.beginPath();
  ctx.rect(x, y, width, height);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.closePath();
}

function drawGround(x, y, count = 1) {
  drawRect(x, y, 32 * count, canvas.height - y, '#684027');
  drawRect(x, y, 32 * count, 10, 'green');
}
// indication display
var showI = true;
// main animation loop
function mainLoop(time) { // time supplied by requestAnimationFrame        
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  drawGround(0, world.ground, 16);
  player.update();
  player.draw();
  if(showI){
     if(keyboard.any){
         keyboard.any = false;
         showI = false;
     }
     ctx.textAlign = "center";
     ctx.font = "24px arial";
     ctx.fillStyle = "#000";
     ctx.fillText("Up arrow to jump. Left right to move",ctx.canvas.width / 2, 80);
  }
  requestAnimationFrame(mainLoop);
}

// ensure window focus for keyboard inputs
window.focus();
#canvas {
  border: 1px solid #d3d3d3;
  background-color: #f1f1f1;
}
<canvas id='canvas' width='512px' height='300px'></canvas>

Answer №2

Here are a few suggestions to improve your project:

  • One key issue lies in the velocities and resistances of your objects. Try adjusting these factors for better results.
  • Consider running your animations based on time instead of updating positions with every draw call. This will ensure consistency regardless of rendering rate.
  • Create an encapsulated object model that includes properties affected by gravity. This organizational strategy can help maintain sanity during code development.
  • Explore tutorials available online for helpful techniques and tips on addressing these challenges.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

A step-by-step guide on how to substitute document.write with document.getElementById('ElementID').innerHTML

As someone who is not a programmer, I often find myself attempting to grasp code and make adjustments through trial and error. Currently, I am faced with the challenge of modifying the document.write function in the function pausescroller within a Joomla m ...

When rendering, HTML characters are not translated

My JavaScript code generates the following HTML structure: <div contenteditable="false" tabindex="0" class="ProseMirror"> <p> didn't project a significant increase</p> </div> When rendered in the browser, it shows the cha ...

Is it possible to replace the catch function in JavaScript with a custom function?

Below is the code snippet: function xyz() { try { var a = someexecutions(); handlesuccess(a) } catch (err) { handleerror(err) } } This type of function is repeated numerous times in my codebase and I am looking for a way to improve it. f ...

Setting the iDisplayLength property in jQuery Datatables to -1 will display all rows in the table

Using jQuery Datatables, I am trying to populate a table with entries from a server via ajax. The data retrieval works perfectly, and I am able to display them in the table. However, I am facing an issue where I want to show all rows/entries at once. I hav ...

adjusting the height of a div using jQuery UI's layout feature

Recently, I have been playing around with the adjustable grids plugin (jquery ui layout) to set the width of each div using the plugins. However, I've encountered a challenge when it comes to adjusting the height of inner divs within the layout. Speci ...

What is the method to utilize Selenium with Python for extracting JavaScript elements?

I'm having trouble parsing the "disabled" attribute using Beautiful Soup. Is there a way to achieve this with Selenium in Python? The specific website I am trying to parse is: Size ...

Securing routes with passport.js in a MEAN Stack setting

I am facing an issue with securing individual routes in my admin panel using passport.js. The user signup functionality is working fine, and I am able to login successfully. However, the req.isAuthenticated() function always returns false, preventing me fr ...

Use this jQuery-animated menu to make hyperlinks hover with style!

Currently immersed in my exam project, I have integrated an animated jQuery navigation. I am aiming to set the text color to white when hovering over both the link itself and the menu icon that appears upon hovering over the <li> elements. Despite ...

Is there a way to initiate the focusout event on all input fields within a form simultaneously?

I need a way to trigger focusout every time a user moves to the next field in a form. I don't want to use a class element because it will apply to all fields with that class. Here is an example of what I want to avoid: $("#element1").focusou ...

I'm wondering why my second div is displaying a different size compared to the rest, even though they all share the same container-fluid class

Within the HTML snippet provided, utilizing Bootstrap, all three child divs possess the container-fluid class. However, why does the 2nd div differ from the others? Refer to this example Div Example. Div1 and div4 exhibit the same characteristics, as do d ...

Is there a glitch in the three.js loadOBJMTL loader?

Encountering an issue with the OBJMTL loader in three.js. I'm working with obj/mtl/jpeg files and getting load errors that look like this: "THREE.OBJMTLLoader: Unhandled line 4033/5601/6659" OBJMTLLoader.js:347 Seems like there is a problem with a c ...

Including a property to a designated React component type at any depth within the DOM hierarchy

Looking to incorporate a "position" prop into the 'Child' components and utilize it within the 'Parent' component, the sole function of the 'PositionWrapper' is to add this one prop and return the modified children. Encounteri ...

Obtain the value of the input

I need assistance with the input below: <input value="25" data-hidden-value="25" name="associations[agencies][ids][]" data-hidden-name="associations[agencies][ids][]" class="string" type="hidden"> This particular input is generated dyna ...

Challenges arising from the offset in an overflowing Div

Encountering issues with JQuery Offset when utilized within a div that has a fixed height and overflow. This particular div contains two columns, a main column and a sidebar. The objective is to have one of the sidebar divs scroll within its container unt ...

Crafty Checkbox Selector [ leveraging jquery ]

I have created some fake checkboxes using elements like <span> after the real checkbox and they are working fine! However, I am having trouble counting the checked checkboxes when a checkbox is clicked. I have tried using methods like: $(".elm:chec ...

Using AJAX to dynamically edit and update PHP data

The information displayed on my index.php page is fetched from a database using the mysqli_fetch_array function. The data is presented in a table format with fields like Name, Age, Email, and Update. An edit button allows users to modify the data. Here is ...

Django: Implementing Subdirectory Structure for Static Files

My static files are organized as follows: /static base.css /core /css style.css /js stuff.js When accessing these files locally using href="/static/core/css/style.css", everything works correctly. However, when deploying live, ...

Postgres error in NodeJS: Cannot resolve address - ENOTFOUND

My connection string for accessing my AWS database is pg://user:pass@localhost:port/table. It works perfectly fine when connecting to localhost, but as soon as I attempt to connect to the AWS server, everything falls apart. Even a simple connection code r ...

The start-up process of AngularJS applications with Bootstrap

Curious about the predictability of the initialization flow in AngularJS apps? Want to understand the order of execution of different blocks within an HTML document? I came across a question on bootstrapping in Angular JS, but it didn't delve into th ...

The challenge of using <p></p> tags with Bootstrap 4, HTML, and CSS

Having trouble separating multiple paragraphs within a division? Let's take a look at the code snippet in question: <div class="row"> <div class="col-md-12 d-flex justify-content-center padding-top"> <p>IMPORTANT LEGAL D ...