重写动画是基于时间的(不是基于平坦的速度)

时间:2018-05-24 07:49:00

标签: javascript animation math time

一段时间以来,我正在使用动画作为子弹,动画使子弹从起点到终点以正弦轨迹运行。动画的唯一问题是它使用平面speed参数移动,我想转换它,所以它使用时间代替。这样你就可以说子弹会持续2秒,否则你不会知道它什么时候到来。

经过一番研究后,我认为子弹需要这些:

  • 时间(旅行需要多长时间,例如2秒)
  • 已过去(自开始以来经过了多长时间)
  • 开始(旅行开始时)

这些将允许您计算自子弹射击以来经过的时间,并根据假设行进的时间您可以知道在特定时间点应该在哪里(例如在1.2秒之后)

由于脚本使子弹以正弦方式传播,我不知道如何实现它。

剧本:



var cvs = document.querySelector('canvas'),
    ctx = cvs.getContext('2d'),
    w, h, cx, cy,
    resize = function() {
        w = cvs.width = window.innerWidth;
        cx = w / 2;
        h = cvs.height = window.innerHeight;
        cy = h / 2;
    },
    tools = {
        rnd: (min, max) => Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min),
        flt: (min, max, dec) => parseFloat((Math.random() * (min - max) + max).toFixed(dec)),
        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)
    },
    loop = function() {
        ctx.fillStyle = 'rgba(0, 0, 0, 1)';
        ctx.fillRect(0, 0, w, h);

        for (var i = pool.length - 1; i >= 0; i--) {
            // move bullet
            pool[i].move();

            // bullet
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].x, pool[i].y, pool[i].r, Math.PI * 2, 0);
            ctx.fillStyle = 'hsl(100, 100%, 50%)';
            ctx.fill();
            ctx.closePath();
            ctx.restore();

            // start location
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].ix, pool[i].iy, pool[i].r, Math.PI * 2, 0);
            ctx.strokeStyle = 'hsl(100, 100%, 50%)';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();

            // end location
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].dx, pool[i].dy, pool[i].r, Math.PI * 2, 0);
            ctx.strokeStyle = 'hsl(100, 100%, 50%)';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();

            // remove bullet when it arrives
            if (pool[i].remaining <= 0) {
                pool.splice(i, 1);
            }
        }

        requestAnimationFrame(loop);
    },
    pool = [],
    last_spawn = 0,
    spawn_interval = 0,
    spawn_limit = 1,
    spawn = function() {
        if (Date.now() - last_spawn > spawn_interval) {
            last_spawn = Date.now();
            for (var i = 0; i < spawn_limit; i++) {
                pool.push(new particle());
            }
        }
    },
    particle = function() {
        var exvec = tools.rnd(20, w - 20),
            eyvec = tools.rnd(20, h - 20),
            svecs = {
                x: cx,
                y: cy
            },
            evecs = {
                x: exvec,
                y: eyvec
            },
            rad = tools.rftv(svecs, evecs),
            distance = tools.distance(svecs, evecs);
        this.time = 2 * 1000; // time in seconds for example 2 seconds === 2 * 1000 = 2000 ms
        this.elapsed = 0; // how much time passed since it started
        this.started = Date.now(); // time of departure

        this.ix = cx; // start x axis
        this.iy = cy; // start y axis
        this.dx = exvec; // end x axis
        this.dy = eyvec; // end y axis
        this.x = cx; // current x axis
        this.y = cy; // current y axis
        this.r = 10; // radius of bullet
        this.rad = rad; // needed for computation
        this.period = distance / 2; // how many axis changes
        this.distance = 0; // how much distance bullet travelled
        this.total = distance; // how much distance there is in total to be made
        this.remaining = distance; // difference between total and made
        this.amplitude = distance / 2; // how big hump
        this.speed = 2; // flat speed increase
        this.move = function() { // this is function for to calculate move
            this.elapsed = Date.now() - this.started;
            this.distance += this.speed;
            this.remaining = this.total - this.distance;

            this.x = this.ix + Math.cos(this.rad) * this.distance;
            this.y = this.iy + Math.sin(this.rad) * this.distance;

            const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;

            this.x += Math.sin(this.rad) * deviation;
            this.y -= Math.cos(this.rad) * deviation;
        };
    };

resize();
loop();

window.onresize = function() {
    resize();
};

spawn();
&#13;
body {
	overflow:hidden;
}

canvas {
	position:absolute;
	top:0;
	left:0;
}
&#13;
<canvas></canvas>
&#13;
&#13;
&#13;

最重要的东西是this.move函数内部,因为它完成整个移动,我已经实现了时间的计算(希望它是正确的)但我不知道如何修改当前的移动代码,所以它受时间和不是speed

1 个答案:

答案 0 :(得分:2)

AFAIU您的问题,您的动画基于requestAnimationFrame,不保证按固定时间间隔进行计划,因此使用固定速度的逻辑

        this.distance += this.speed;

并不适合您,您希望根据实际经过的时间计算this.distance

        this.elapsed = Date.now() - this.started

如果是这样,则改变是微不足道的:对于任何给定时刻,当前距离应该是整个距离的相同部分,因为当前经过时间是整个时间的一部分。所以就你的代码而言:

        this.distance = distance * this.elapsed / this.time;

或者更新您的演示:

&#13;
&#13;
var cvs = document.querySelector('canvas'),
    ctx = cvs.getContext('2d'),
    w, h, cx, cy,
    resize = function () {
        w = cvs.width = window.innerWidth;
        cx = w / 2;
        h = cvs.height = window.innerHeight;
        cy = h / 2;
    },
    tools = {
        rnd: (min, max) => Math.floor(Math.random() * (Math.floor(max) - Math.ceil(min) + 1)) + Math.ceil(min),
        flt: (min, max, dec) => parseFloat((Math.random() * (min - max) + max).toFixed(dec)),
        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)
    },
    globalStart = Date.now(),
    loop = function () {
        ctx.fillStyle = 'rgba(0, 0, 0, 1)';
        ctx.fillRect(0, 0, w, h);

        var globalElapsed = Date.now() - globalStart;
        ctx.font = '48px serif';
        ctx.fillStyle = 'hsl(50, 100%, 50%)';
        var text = (pool.length > 0) ? globalElapsed / 1000 : "End";
        ctx.fillText(text, 40, 50);

        for (var i = pool.length - 1; i >= 0; i--) {
            // move bullet
            pool[i].move();

            // bullet
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].x, pool[i].y, pool[i].r, Math.PI * 2, 0);
            ctx.fillStyle = 'hsl(100, 100%, 50%)';
            ctx.fill();
            ctx.closePath();
            ctx.restore();

            // start location
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].ix, pool[i].iy, pool[i].r, Math.PI * 2, 0);
            ctx.strokeStyle = 'hsl(100, 100%, 50%)';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();

            // end location
            ctx.save();
            ctx.beginPath();
            ctx.arc(pool[i].dx, pool[i].dy, pool[i].r, Math.PI * 2, 0);
            ctx.strokeStyle = 'hsl(100, 100%, 50%)';
            ctx.stroke();
            ctx.closePath();
            ctx.restore();

            // remove bullet when it arrives
            if (pool[i].remaining <= 0) {
                pool.splice(i, 1);
            }
        }

        requestAnimationFrame(loop);
    },
    pool = [],
    last_spawn = 0,
    spawn_interval = 0,
    spawn_limit = 1,
    spawn = function () {
        if (Date.now() - last_spawn > spawn_interval) {
            last_spawn = Date.now();
            for (var i = 0; i < spawn_limit; i++) {
                pool.push(new particle());
            }
        }
    },
    particle = function () {
        var exvec = tools.rnd(20, w - 20),
            eyvec = tools.rnd(20, h - 20),
            svecs = {
                x: cx,
                y: cy
            },
            evecs = {
                x: exvec,
                y: eyvec
            },
            rad = tools.rftv(svecs, evecs),
            distance = tools.distance(svecs, evecs);
        this.time = 10 * 1000; // time in seconds for example 2 seconds === 2 * 1000 = 2000 ms
        this.elapsed = 0; // how much time passed since it started
        this.started = Date.now(); // time of departure

        this.ix = cx; // start x axis
        this.iy = cy; // start y axis
        this.dx = exvec; // end x axis
        this.dy = eyvec; // end y axis
        this.x = cx; // current x axis
        this.y = cy; // current y axis
        this.r = 10; // radius of bullet
        this.rad = rad; // needed for computation
        this.period = distance / 2; // how many axis changes
        this.distance = 0; // how much distance bullet travelled
        this.total = distance; // how much distance there is in total to be made
        this.remaining = distance; // difference between total and made
        this.amplitude = distance / 4; // how big hump
        // this.speed = 2; // flat speed increase
        this.move = function () { // this is function for to calculate move
            this.elapsed = Date.now() - this.started;
            // this.distance += this.speed;
            this.distance = distance * this.elapsed / this.time;
            this.remaining = this.total - this.distance;

            this.x = this.ix + Math.cos(this.rad) * this.distance;
            this.y = this.iy + Math.sin(this.rad) * this.distance;

            const deviation = Math.sin(this.distance * Math.PI / this.period) * this.amplitude;

            this.x += Math.sin(this.rad) * deviation;
            this.y -= Math.cos(this.rad) * deviation;
        };
    };

resize();
loop();

window.onresize = function () {
    resize();
};

spawn();
&#13;
body {
	overflow:hidden;
}

canvas {
	position:absolute;
	top:0;
	left:0;
}
&#13;
<canvas></canvas>
&#13;
&#13;
&#13;

请注意

  1. 我将this.time更改为10秒到秒,因此动画更容易跟踪
  2. 我添加了globalStartglobalElapsed来绘制loop
  3. 内的实际时间
  4. 我缩小了曲线的大小以更好地适应更小的区域