1

I am experimenting with movement mechanics in simple game and i found very decent article with example of how to make it "realistic" by playing with velocity and friction (its very popular approach). So the object is starting slow, speeding up to a cap and once you release key it starts to loose speed until 0.

key part is adding "throttle":

if(keys[38]) {
    spaceship.ax = Math.cos(spaceship.r) * 0.05;
    spaceship.ay = Math.sin(spaceship.r) * 0.05;
} else {
    spaceship.ax = spaceship.ay = 0;
}

and utilising that throttle to increase speed capped by friction

var friction = 0.97;
function updatePosition(obj) {
    //update velocity
    obj.vx += obj.ax;
    obj.vy += obj.ay;

    //apply friction
    obj.vx *= friction;
    obj.vy *= friction;

    //update position
    obj.x += obj.vx;
    obj.y += obj.vy;
}

Although it looks very nice and feels decent, it breaks my logic that regards move based on time. Its mandatory so player can see the speed per second, as it allows to make planned upgrades, plan travels and fuel usage.

So current implementation looks like this:

this.now = undefined;
this.delta = undefined;
this.then = Date.now();

this.setMoveDelta = function() {
  this.now = Date.now();
  this.delta = (this.now - this.then) / 1000;
  this.then = this.now;
};

this.speed = 100; // 100 pixels per second
this.move = function() {
    this.setMoveDelta();
    var partialDistance = this.speed * this.delta;
    this.x += Math.cos(this.rad) * partialDistance;
    this.y += Math.sin(this.rad) * partialDistance;
}

Now when you run included demo it can be noticed that there is sort of "max" speed due to friction cap. The question is can this cap be set to this.speed * this.delta somehow? or use some other approach to make ship move at max speed with declared parameters (for example 100 px per second) ?

The idea is to leave accelerating and slowing down as is but once ship reaches max speed it would be the declared speed (and displayed for user). That speed is then used to calculate time needed to travel from point A to B and how much fuel will be used. Currently it feels random.

var canvas = document.createElement('canvas'),
 ctx = canvas.getContext('2d'),
 w = 400,
 h = 400;
canvas.width = w;
canvas.height = h;

document.body.appendChild(canvas);

var spaceship = {
 x: w / 2, y: h / 2,
 vx: 0, vy: 0,
 ax: 0, ay: 0,
 r: 0,
 draw: function(){
  ctx.save();
  ctx.translate(this.x, this.y);
  ctx.rotate(this.r);
  ctx.fillStyle = 'white';
  ctx.fillRect(-10, -5, 20, 10);
  ctx.restore();
 }
};

var friction = 0.97;

function updatePosition(obj) {
 //update velocity
 obj.vx += obj.ax;
 obj.vy += obj.ay;

 applyFriction(obj);

 //update position
 obj.x += obj.vx;
 obj.y += obj.vy;
}

//user interactivity

var keys = [];
document.addEventListener('keydown', function(e){
 keys[e.which] = true;
});
document.addEventListener('keyup', function(e){
 keys[e.which] = false;
});

function applyFriction(obj){
 obj.vx *= friction;
 obj.vy *= friction;
}

(function animloop(){
 requestAnimationFrame(animloop, canvas);
 ctx.fillStyle = '#000';
 ctx.fillRect(0, 0, w, h);

 //rotation
 if(keys[37]) spaceship.r -= 0.05;
 if(keys[39]) spaceship.r += 0.05;

 //thrust
 if(keys[38]){
  spaceship.ax = Math.cos(spaceship.r) * 0.05;
  spaceship.ay = Math.sin(spaceship.r) * 0.05;
 }else{
  spaceship.ax = spaceship.ay = 0;
 }

 updatePosition(spaceship);
 spaceship.draw();
})();

-----edit

I implemented suggested solution but even with this formula, max speed is slightly less then declared resulting other objects moving with actual 100px per second, being simply faster in long run. this is new demo:

this.now = undefined;
this.delta = undefined;
this.then = Date.now();

this.setMoveDelta = function() {
  this.now = Date.now();
  this.delta = (this.now - this.then) / 1000;
  this.then = this.now;
};

this.speed = 100; // 100 pixels per second

var secondObj = {
 x: 0,
 y: 250,
 r: 0,
 active: false,
 draw: function(){
  ctx.save();
  ctx.translate(this.x, this.y);
  ctx.rotate(this.r);
  ctx.fillStyle = 'white';
  ctx.fillRect(-10, -5, 20, 10);
  ctx.restore();
 }
};

var canvas = document.createElement('canvas'),
 ctx = canvas.getContext('2d'),
 w = 1200,
 h = 400;
canvas.width = w;
canvas.height = h;

document.body.appendChild(canvas);

var spaceship = {
 x: 0, y: 200,
 vx: 0, vy: 0,
 ax: 0, ay: 0,
 r: 0,
 draw: function(){
  ctx.save();
  ctx.translate(this.x, this.y);
  ctx.rotate(this.r);
  ctx.fillStyle = 'white';
  ctx.fillRect(-10, -5, 20, 10);
  ctx.restore();
 }
};

var friction = 0.97;

function updatePosition(obj) {
 //update velocity
 obj.vx += obj.ax;
 obj.vy += obj.ay;

 applyFriction(obj);

 //update position
 obj.x += obj.vx;
 obj.y += obj.vy;
}

//user interactivity

var keys = [];
document.addEventListener('keydown', function(e){
 keys[e.which] = true;
 setTimeout(function() {
 secondObj.active = true;
 }, 600);
});
document.addEventListener('keyup', function(e){
 keys[e.which] = false;
});

function applyFriction(obj){
 obj.vx *= friction;
 obj.vy *= friction;
}

var is = function(c, num) {
 if(parseInt(c) < num + 1 || parseInt(c) > num - 1) {
    return true;
 }
 return false;
};

(function animloop(){
 requestAnimationFrame(animloop, canvas);
 ctx.fillStyle = '#000';
 ctx.fillRect(0, 0, w, h);

 //rotation
 if(keys[37]) spaceship.r -= 0.05;
 if(keys[39]) spaceship.r += 0.05;

 //thrust
 this.setMoveDelta();
 if(keys[38]){
  spaceship.ax = Math.cos(spaceship.r) * (this.speed * this.delta * (1-0.97));
  spaceship.ay = Math.sin(spaceship.r) * (this.speed * this.delta * (1-0.97));
 }else{
  spaceship.ax = spaceship.ay = 0;
 }
 
 updatePosition(spaceship);
 spaceship.draw();
 
 if(secondObj.active) {
 secondObj.x += Math.cos(0) * ( this.speed * this.delta );
}
 secondObj.draw();
})();
Mevia
  • 1,517
  • 1
  • 16
  • 51
  • see [Newton - D'Lambert physics for non relativistic speeds](https://stackoverflow.com/a/19764828/2521214) you are just missing the `dt` time step (elapsed time from last iteration or timer interval) in your equations ... – Spektre Mar 14 '19 at 09:50
  • well thats why i asked question to receive help with "how to" incorporate it, also i dont think that `dt` which is delta, without speed would achieve what i need. – Mevia Mar 14 '19 at 10:20
  • in the linked answer are both `speed` and `dt` ... the integration in vector form is: `vel += acc * dt; pos += vel * dt;` where `acc` is acceleration in `[m/s^2]` , `vel` is speed in `[m/s]` and `pos` is positon in `[m]` ... the `dt` is scalar time step in `[s]` .... so you set the `acc` with driving forces (`acc = F/m`) , gravity, thrusters etc... and compute the rest .... you can add also frictions `F = k*|vel|^2` or `F = k*|vel|^3` depending on environment (gas,liquid) – Spektre Mar 14 '19 at 10:32
  • is there any chance you could make a demo in form of answer with all these in place, ( i am a very beginner and got lost in all those calculations ), thank you in advance for your effort ;) just remember that there must be a "top speed" somewhere that is achieved at max – Mevia Mar 14 '19 at 10:38
  • something like this: [Can't flip direction of ball without messing up gravity](https://stackoverflow.com/a/53637567/2521214) ? You just call the `ball_update` in some timer and rerender the screen... – Spektre Mar 14 '19 at 10:40
  • I got many answers with similar physics from a quick search here few that might be interesting for you: [How to calculate rocket?](https://stackoverflow.com/a/53986426/2521214) and [How to implement a constraint solver for 2-D geometry?](https://stackoverflow.com/a/40827518/2521214) – Spektre Mar 14 '19 at 10:45

1 Answers1

1

Now speed is root_of(obj.vx^2 + obj.vy^2)/this.delta and max speed is 0.05/(1-0.97)/this.delta. The first since vx and vy is in movement per each time delta. The latter since speed increase of 0.05 is balanced by speed decrease of speed*(1-0.97).

The answer is that speed cap could be set by adjusting eithe the acceleration constant 0.05 or the friction constant 0.97. Let's use the accelereration one:

acceleration constant = max_speed * this.delta * (1-0.97)

Mats Lind
  • 914
  • 7
  • 19
  • I updated my answer with new demo that uses proposed formula, play demo, then open in full page and start moving (just forward) you will see that even thought your speed should be capped at 100px per second, the other object is moving faster and will outrun you eventually, do you know why is that and how to fix it ? – Mevia Mar 19 '19 at 14:53
  • Sorry, my answer should be acceleration = max_speed * this.delta * (1/0.97-1) when friction is applied after "//update velocity" as you do it. If you put applyFriction() before "//update velocity", my answer should be the right one. Not a big difference but I can see it as well when I run the snippet. – Mats Lind Mar 20 '19 at 13:11
  • this is perfect explanation! and works perfectly too, thank you ;) – Mevia Mar 20 '19 at 15:53