Я экспериментирую с механикой движения в простой игре и нашел очень приличную статью с примером того, как сделать ее «реалистичной», играя со скоростью и трением (это очень популярный подход). Таким образом, объект начинает медленно, ускоряется до предела, и как только вы отпускаете ключ, он начинает терять скорость до 0.
ключевая часть добавляет «газ»:
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;
}
и использование этого дросселя для увеличения скорости, ограниченной трением
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;
}
Хотя это выглядит очень красиво и прилично, это нарушает мою логику в отношении движения, основанного на времени. Он обязателен, чтобы игрок мог видеть скорость в секунду, поскольку он позволяет планировать обновления, планировать поездки и расход топлива.
Итак, текущая реализация выглядит так:
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;
}
Теперь, когда вы запускаете включенную демо-версию, можно заметить, что из-за фрикционной крышки наблюдается своего рода «максимальная» скорость. Вопрос в том, можно ли как-нибудь установить эту шапку на this.speed * this.delta
? или использовать какой-то другой подход, чтобы заставить корабль двигаться с максимальной скоростью с объявленными параметрами (например, 100 пикселей в секунду)?
Идея состоит в том, чтобы оставить ускорение и замедление как есть, но как только корабль достигнет максимальной скорости, это будет объявленная скорость (и отображается для пользователя). Затем эта скорость используется для расчета времени, необходимого для перемещения из пункта А в пункт В, и количества топлива, которое будет использовано. В настоящее время это кажется случайным.
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();
})();
----- редактировать
Я реализовал предложенное решение, но даже с этой формулой максимальная скорость немного меньше заявленной, в результате чего другие объекты движутся с фактической скоростью 100px в секунду, а в долгосрочной перспективе просто быстрее. это новое демо:
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();
})();