簡體   English   中英

請求 animation 框架的變化速度

[英]Change speed of request animation frame

我有這段代碼可以將 canvas 中的圖像從 position 移動到另一個:

class Target {
    constructor(img, x_init, y_init, img_width = 100, img_height = 100) {
        this.img = img;
        this.x = x_init;
        this.y = y_init;
        this.img_width = img_width;
        this.img_height = img_height;
    }
    

    get position() {
        return this.x
    }

    move(canvas, x_dest, y_dest) {
        ctx = canvas.getContext('2d');
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.drawImage(this.img, this.x, this.y, this.img_width, this.img_height);
        if (this.x != x_dest) {
            if (this.x > x_dest) {
                this.x -=1;
            } else {
                this.x +=1;
            }
        }
        if (this.y != y_dest) {
            if (this.y > y_dest) {
                this.y -=1;
            } else {
                this.y +=1;
            }
        }
        if (this.x != x_dest || this.y != y_dest) {
            //setTimeout(this.move.bind(target, canvas, x_dest, y_dest), 0);
            window.requestAnimationFrame(this.move.bind(target, canvas, x_dest, y_dest));
        }
    }

}

這段代碼的問題是:我無法控制速度,而且速度很慢......我如何控制速度並保持 select 到達 position 的想法? 我找到了關於那個的話題,但我沒有找到任何適合我的情況,肯定是因為 1 像素的步長太小了,但我看不到我該怎么做。

[編輯] 這就是我想做的(當紅色圓圈縮小時,我必須在 2 秒內添加一條記錄)。 我顯然是按照 pid 指令完成的。 再次感謝他。

 (function() { function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } var canvas = document.getElementById("calibrator"); var ctx = canvas.getContext('2d'); canvas.width = window.innerWidth; canvas.height = window.innerHeight; const points = [{ "x": 0, "y": 0 }, { "x": canvas.width / 2 - 100, "y": 0 }, { "x": canvas.width - 100, "y": 0 }, { "x": 0, "y": canvas.height / 2 - 100 }, { "x": canvas.width / 2 - 100, "y": canvas.height / 2 - 100 }, { "x": canvas.width - 100, "y": canvas.height / 2 - 100 }, { "x": 0, "y": canvas.height - 100, }, { "x": canvas.width / 2 - 100, "y": canvas.height - 100 }, { "x": canvas.width - 100, "y": canvas.height - 100 } ] function generateLinear(x0, y0, x1, y1, dt) { return (t) => { let f0, f1; f0 = t >= dt? 1: t / dt; // linear interpolation (aka lerp) f1 = 1 - f0; return { "x": f1 * x0 + f0 * x1, // actually is a matrix multiplication "y": f1 * y0 + f0 * y1 }; }; } function generateShrink(x0, y0, x1, y1, r0, dt) { return (t) => { var f0 = t >= dt? 0: dt - t; var f1 = t >= dt? 1: dt / t; var f2 = 1 - f1; return { "x": f2 * x0 + f1 * x1, "y": f2 * y0 + f1 * y1, "r": f0 * r0 }; }; } function create_path_circle() { var nbPts = points.length; var path = []; for (var i = 0; i < nbPts - 1; i++) { path.push({ "duration": 2, "segment": generateShrink(points[i].x, points[i].y, points[i].x, points[i].y, 40, 2) }); path.push({ "duration": 0.5, "segment": generateShrink(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, 0, 0.5) }); } path.push({ "duration": 2, "segment": generateShrink(points[nbPts - 1].x, points[nbPts - 1].y, points[nbPts - 1].x, points[nbPts - 1].y, 40, 2) }) return path; } function create_path_target() { var nbPts = points.length; var path = []; for (var i = 0; i < nbPts - 1; i++) { path.push({ "duration": 2, "segment": generateLinear(points[i].x, points[i].y, points[i].x, points[i].y, 2) }); path.push({ "duration": 0.5, "segment": generateLinear(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y, 0.5) }); } path.push({ "duration": 2, "segment": generateLinear(points[nbPts - 1].x, points[nbPts - 1].y, points[nbPts - 1].x, points[nbPts - 1].y, 2) }) return path; } const path_target = create_path_target(); const path_circle = create_path_circle(); function renderImage(img, img_width, img_height) { return (pos) => { ctx = canvas.getContext('2d'); ctx.drawImage(img, pos.x, pos.y, img_width, img_height); } } function renderCircle() { return (pos) => { ctx = canvas.getContext('2d'); ctx.beginPath(); ctx.arc(pos.x + 50, pos.y + 50, pos.r, 0, 2 * Math.PI); ctx.fillStyle = "#FF0000"; ctx.fill(); ctx.stroke(); } } function generatePath(path) { let i, t; // fixup timing t = 0; for (i = 0; i < path.length; i++) { path[i].start = t; t += path[i].duration; path[i].end = t; } return (t) => { while (path.length > 1 && t >= path[0].end) { path.shift(); // remove old segments, but leave last one } return path[0].segment(t - path[0].start); // time corrected }; } var base_image = new Image(); base_image.src = 'https://www.pngkit.com/png/full/17-175027_transparent-crosshair-sniper-scope-reticle.png'; const sprites = [ { "move": generatePath(path_circle), "created": performance.now(), "render": renderCircle() }, { "move": generatePath(path_target), "created": performance.now(), "render": renderImage(base_image, 100, 100) } ]; const update = () => { let now; ctx.fillStyle = "#FFFFFF"; ctx.fillRect(0, 0, canvas.width, canvas.height); // put aside so all sprites are drawn for the same ms now = performance.now(); for (var sprite of sprites) { sprite.render(sprite.move((now - sprite.created) / 1000)); } window.requestAnimationFrame(update); }; window.requestAnimationFrame(update); })();
 <:DOCTYPE html> <html> <head> <title>Calibration</title> <link rel="stylesheet" href="https.//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min:css"> </head> <body> <canvas id="calibrator"></canvas> <video id="stream"></video> <canvas id="picture"></canvas> <script src="https.//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min:js"></script> <script src="https.//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min:js"></script> <script src="https.//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script> <script src="calibration.js"></script> </body> </html>

對於拍攝,如果我們假設我有一個 takeSnapshot() function 返回圖片,我會這樣做:

    function film(dt) {
    return (t) => {
        if (t >= dt) {
            return false;
        } else {
            return true;
        }
    }
}

function create_video_timeline() {
    var nbPts = points.length;
    var path = [];
    for (var i = 0 ; i < nbPts - 1; i++) {
        path.push(
            {
                "duration": 2,
                "segment": film(2)
            }
        );
        path.push(
            {
                "duration":0.5,
                "segment": film(0)
            }
        );
    }
    path.push(
        {
            "duration": 2,
            "segment": film(2)              
        }
    )
    return path;
}

const video_timeline = create_video_timeline();

function getSnapshot() {
    return (bool) => {
        if (bool) {
            data.push(takepicture());
        } 
    }
}

const sprites = [
    
    {
        "move": generatePath(path_circle),
        "created": performance.now(),
        "render": renderCircle()
    },
    {
        "move": generatePath(path_target),    
        "created": performance.now(),
        "render": renderImage(base_image, 100, 100)
    },
    {
        "render": getSnapshot(),
        "move": generatePath(video_timeline),
        "created": performance.now()
    }
];

編輯:添加了另一個運動示例(看青色方塊)

要回答您關於如何在固定時間內到達“某處”的評論,您可以線性化大多數函數,然后通過固定時間來求解方程。 這對於線性移動很容易,但對於復雜的情況則相當困難,例如沿非線性函數移動(例如對數螺旋)。

對於在時間dt內從(x0, y0)(x1, y1)以恆定速度(無加速/減速)的線性運動,您可以使用線性插值:

function generateLinear(x0, y0, x1, y1, dt)
{
  return (t) => {
    let f0, f1;

    f0 = t >= dt ? 1 : t / dt; // linear interpolation (aka lerp)
    f1 = 1 - f;

    return {
      "x": f0 * x0 + f1 * x1, // actually is a matrix multiplication
      "y": f0 * y0 + f1 * y1
    };
  };
}

這個 function 現在可用於“組裝”路徑。 首先通過生成段來定義路徑:

const path = [
  {
    "duration": dt1,
    "segment": generateLinear(x0, y0, x1, y1, dt1)
  },
  {
    "duration": dt2,
    "segment": generateLinear(x1, y1, x2, y2, dt2)
  },
  {
    "duration": dt3,
    "segment": generateLinear(x2, y2, x3, y3, dt3)
  }
];

請注意現在將如何處理總路徑時間(使用duration )並將其轉換為段本地時間:

function generatePath(path)
{
  let t;

  // fixup timing
  t = 0;
  for (i = 0; i < path.length; i++)
  {
    path[i].start = t;
    t += path[i].duration;
    path[i].end = t;
  }

  return (t) => {
    while (path.length > 1 && t >= path[0].end)
    {
      path.shift(); // remove old segments, but leave last one
    }

    return path[0].segment(t - path[0].start); // time corrected
  };
}

編輯:工作示例

我剛剛為您准備了這個工作示例。 看看我如何不重做 canvas 或上下文,並一遍又一遍地利用相同的內容。 以及運動如何不依賴於幀率,它在 lissajous function 中定義。

 "use strict"; const cvs = document.querySelector("#cvs"); const ctx = cvs.getContext("2d"); function generateLissajous(dx, dy, tx, ty) { return (t) => { return { "x": 150 + dx * Math.sin(tx * t), "y": 75 + dy * Math.cos(ty * t) }; }; } function generateLinear(x0, y0, x1, y1, dt) { return (t) => { let f0, f1; f0 = t >= dt? 1: t / dt; // linear interpolation (aka lerp) f1 = 1 - f0; return { "x": f1 * x0 + f0 * x1, // actually is a matrix multiplication "y": f1 * y0 + f0 * y1 }; }; } function generatePath(path) { let i, t; // fixup timing t = 0; for (i = 0; i < path.length; i++) { path[i].start = t; t += path[i].duration; path[i].end = t; } return (t) => { let audio; while (path.length > 1 && t >= path[0].end) { path.shift(); // remove old segments, but leave last one } if (path[0].hasOwnProperty("sound")) { audio = new Audio(path[0].sound); audio.play(); delete path[0].sound; // play only once } return path[0].segment(t - path[0].start); // time corrected }; } function generateRenderer(size, color) { return (pos) => { ctx.fillStyle = color; ctx.fillRect(pos.x, pos.y, size, size); }; } const path = [ { "duration": 3, "segment": generateLinear(20, 20, 120, 120, 3) }, { "sound": "boing.ogg", "duration": 3, "segment": generateLinear(120, 120, 120, 20, 3) }, { "sound": "boing.ogg", "duration": 2, "segment": generateLinear(120, 20, 20, 120, 2) } ]; const sprites = [ { "move": generateLissajous(140, 60, 1.9, 0.3), "created": performance.now(), "render": generateRenderer(10, "#ff0000") }, { "move": generateLissajous(40, 30, 3.23, -1.86), "created": performance.now(), "render": generateRenderer(15, "#00ff00") }, { "move": generateLissajous(80, 50, -2.3, 1.86), "created": performance.now(), "render": generateRenderer(5, "#0000ff") }, { "move": generateLinear(10, 150, 300, 20, 30), // 30 seconds "created": performance.now(), "render": generateRenderer(15, "#ff00ff") }, { "move": generatePath(path), "created": performance.now(), "render": generateRenderer(25, "#00ffff") } ]; const update = () => { let now, sprite; ctx.fillStyle = "#000000"; ctx.fillRect(0, 0, 300, 150); // put aside so all sprites are drawn for the same ms now = performance.now(); for (sprite of sprites) { sprite.render(sprite.move((now - sprite.created) / 1000)); } window.requestAnimationFrame(update); }; window.requestAnimationFrame(update);
 canvas { border: 1px solid red; }
 <canvas id="cvs"></canvas>


您不應該依賴requestAnimtionFrame()進行這種運動。

你應該做的是這個。

  1. 有一個基於實時 ( t ) 的運動 function,在本例中為李薩如軌道:
function orbit(t)
{
  return { "x": 34 * Math.sin(t * 0.84), "y": 45 * Math.cos(t * 0.23) };
}

這些數字只是為了展示。 您可以參數化它們並使用柯里化來固定它們並獲得“orbit()”function,如下所示:

function generateLissajousOrbit(dx, tx, dy, ty)
{
  return (t) => { // this is the orbit function
    return { "x": dx * Math.sin(t * tx), "y": dy * Math.cos(t * ty) };
  };
}

這樣,您可以生成任意的李薩如軌道:

let movement = generateLissajousOrbit(34, 0.84, 45, 0.23);

顯然,任何運動 function 都是有效的。 唯一的限制是:

  • 用表示實時的t調用;
  • 在時間t接收具有xy坐標的 object 。

更簡單的動作現在應該很明顯如何實現。 另請注意,這種方式非常容易插入任何動作。

  1. 確定您在動畫/運動中的哪一點:

開始時將當前的實時毫秒數放在一邊,如下所示:

let mymovingobject = {
  "started": performance.now(),
  "movement": generateLissajousOrbit(34, 0.84, 45, 0.23)
};

要在任何給定時間獲取xy ,您現在可以執行以下操作:

let now = performance.now();
let pos = mymovingobject.movement(now - mymovingobject.started);

// pos.x and pos.y contain the current coordinates

您將獲得完全取決於實時的刷新(動畫幀)獨立運動,這是您的主觀感知空間。

如果機器出現故障或刷新率因任何原因發生變化(用戶剛剛重新校准顯示器,將 window 在桌面上從 120 Hz 移動到 60 Hz 顯示器,或其他任何東西)......移動仍然是實時的綁定並且完全獨立於幀速率。

  1. 現在您可以在任何給定時間輪詢 position 並將其用於渲染

在處理requestAnimationFrame()的 function 中,您只需如上所示輪詢 position,然后在pos.xpos.y處繪制 object 和實際刷新率是什么。

您還可以跳過幀以降低幀速率,並讓用戶通過計算幀來決定頻率,如下所示:

let frame = 0;

function requestAnimationFrameHandler()
{
  if (frame % 2 === 0)
  {
    window.requestAnimationFrame();
    return; // quick bail-out for this frame, see you next time!
  }

  // update canvas at half framerate
}

由於有高頻監視器,能夠降低幀速率在今天尤為重要。 只需更換顯示器,您的應用程序就會從 60 像素/秒躍升至 120 像素/秒。 這不是你想要的。

requestAnimationFrame()工具看起來像是平滑滾動的靈丹妙葯,但事實是您將自己束縛在完全未知的硬件約束中(想想 2035 年的現代顯示器......誰知道它們會怎樣)。

這種技術將物理幀頻率與邏輯(游戲)速度要求分開。

希望這是有道理的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM