首页 > 解决方案 > 在基于时间的动画中取消运动的逻辑

问题描述

我在我的简单游戏中设计了一个运动引擎,您可以在其中拥有特定的坐标(x,y),然后单击即可前往任何目的地(x,y)。这个想法是你有speed并且fuel consumption整个运动也是基于时间的。所以最后,如果你决定从 A 点到 B 点,它将花费 X 时间(由速度决定)和 X 燃料量(由消耗决定)。我面临的问题是计算精度,可能不是到达目的地后停止旅行的最佳逻辑。

所做的计算是正确的,但它们的精度导致了一些问题。

在我包含的演示中可以看出,我当前的取消逻辑正在产生剩余物(例如,应该燃烧 20 升燃料,但我只剩下 0.12...):

if ( Math.floor( this.distance ) === 0 ) {

而我的前一个永远不会结束(因为它永远不会在没有小数位的情况下达到理想的 0):

if ( this.distance > 0 ) {

我的问题是,如何改进我的代码,使旅行始终以正确的点结束并且燃料始终处于应有的状态。

const tools = {
	distance: (p1, p2) => Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)),
	rftv: (p1, p2) => Math.atan2(p2.y - p1.y, p2.x - p1.x)
};

this.fuel = 200;
this.x = 100;
this.y = 50;
this.rad = 0; // radian angle between current and destination
this.speed = 100; // 100 pixels per second
this.consumption = 10; // 10 liters of fuel per 100 pixels
this.destination = {
	x: 220,
	y: 140
};

/*
Based on above

distance : 150
time : ( distance / speed ) => 150 / 100 => 1.5s
fuel : ( distance / consumption ) => 150 / 10 => 15 liters

So to summary, between two points we have 150 pixels of distance,
this trip should take 1.5s and burn 15 liters of fuel
*/

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

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

this.update = function() {
	this.rad = tools.rftv(
		{ x: this.x, y: this.y },
		{ x: this.destination.x, y: this.destination.y }
	);
  
  let step = this.speed * this.delta;
	this.x += Math.cos(this.rad) * step;
	this.y += Math.sin(this.rad) * step;

	this.fuel -= step / this.consumption;
};

this.move = function() {
	this.distance = tools.distance(
		{ x: this.x, y: this.y },
		{ x: this.destination.x, y: this.destination.y }
	);
	
	if ( Math.floor( this.distance ) === 0 ) {
		clearInterval(tsid);
		console.log('done', this.x, this.y, this.fuel, this.distance, '[ ' + (Date.now() - startedAt) + ' ]');
	} else {
		this.setDelta();
		this.update();
		console.log('going', this.x, this.y, this.fuel, this.distance, '[ ' + (Date.now() - startedAt) + ' ]');
	}
};



let tsid;
let startedAt = Date.now();
tsid = setInterval(function() {
	this.move();
}, 10);

标签: javascriptanimationmath

解决方案


  • 如果您的对象总是直接朝向目标行进,则不需要任何三角函数。只需使用矢量数学。

  • 逻辑上update应该调用move,而不是相反。

  • 距离计算应在与移动代码相同的函数中执行。

  • 检查距离move并设置完成标志。

  • 一步使用的燃料 = 消耗(使用量/像素)×步长(以像素为单位),所以this.fuel -= step / this.consumption;是不正确的。

代码:

this.fuel = 200;
this.x = 100;
this.y = 50;
// no need for rad
this.speed = 100;
this.consumption = 10;
this.destination = {
    x: 220,
    y: 140
};
this.complete = false; // completion flag

...

// swap functions

this.update = function() {
    this.update();

    if (this.complete) {
        clearInterval(tsid);
        console.log('done', this.x, this.y, this.fuel, this.distance, '[ ' + (Date.now() - startedAt) + ' ]');
    } else {
        this.setDelta();
        this.move();
        console.log('going', this.x, this.y, this.fuel, this.distance, '[ ' + (Date.now() - startedAt) + ' ]');
    }
};

this.move = function() {
    let step = this.speed * this.delta;
    let dist = tools.distance(
        { x: this.x, y: this.y },
        { x: this.destination.x, y: this.destination.y }
    );
    /*
       would be cleaner to replace this with:
       Math.hypot(this.destination.x - this.x, this.destination.y - this.y);
    */

    // check distance
    if (dist <= step) {
        step = dist;
        this.complete = true;
    }

    // vector math not trigonometry
    this.x += (this.destination.x - this.x) * (step / dist);
    this.y += (this.destination.y - this.y) * (step / dist);

    this.distance -= step;
    this.fuel -= step * this.consumption; // should be * not /
};

...

tsid = setInterval(function() {
    this.update();
}, 10);

推荐阅读