javascript - Ubah kecepatan request animation frame
Saya memiliki kode ini untuk memindahkan gambar di kanvas dari satu posisi ke posisi lain:
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));
}
}
}
Masalah dengan kode ini adalah: saya tidak bisa mengontrol kecepatan, dan cukup lambat... Bagaimana saya bisa mengontrol kecepatan dan tetap menjaga ide memilih posisi kedatangan? Saya menemukan topik tentang itu tetapi saya tidak menemukan apa pun yang bekerja dalam kasus saya, pasti karena langkah 1 piksel terlalu kecil tapi saya tidak melihat bagaimana saya bisa melakukannya.
[EDIT] Inilah yang ingin saya lakukan (Saya harus menambahkan catatan selama 2 detik saat lingkaran merah menyusut). Saya jelas melakukannya dengan mengikuti instruksi pid. Terima kasih lagi kepadanya.
(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>Untuk pengambilan gambar, jika kita beranggapan bahwa saya memiliki fungsi takeSnapshot() yang mengembalikan sebuah foto, saya akan melakukan:
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()
}
];
Jawaban #1
EDIT: menambahkan contoh gerakan lain (lihat kotak biru muda)
Untuk menjawab komentar Anda tentang bagaimana mencapai "suatu tempat" dalam jumlah waktu tertentu, Anda dapat melinierkan sebagian besar fungsi dan kemudian menyelesaikan persamaan dengan menetapkan waktu. Ini mudah untuk gerakan linier tetapi agak sulit untuk kasus yang kompleks, seperti bergerak sepanjang fungsi non-linier (misalnya spiral logaritmik).
Untuk gerakan linier dengan kecepatan konstan (tanpa percepatan/pelambatan) dari (x0, y0) ke (x1, y1) dalam waktu dt Anda dapat menggunakan interpolasi linier:
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
};
};
}
Fungsi ini sekarang dapat digunakan untuk "merakit" sebuah jalur. Pertama, tentukan jalur tersebut dengan menghasilkan segmennya:
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)
}
];
Perhatikan bagaimana total waktu jalur sekarang akan ditangani (menggunakan duration) dan diterjemahkan ke waktu lokal segmen:
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
};
}
EDIT: contoh yang berfungsi
Saya baru saja membuat contoh kerja ini untuk Anda. Lihat bagaimana saya tidak membuat ulang kanvas atau konteks dan menggambar di yang sama berulang-ulang. Dan bagaimana gerakan tidak bergantung pada frame rate, itu ditentukan dalam fungsi lissajous.
"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>Anda sebaiknya tidak mengandalkan requestAnimtionFrame() untuk jenis pergerakan seperti ini.
Yang harus kamu lakukan adalah ini.
- Miliki fungsi gerakan yang didasarkan pada waktu nyata (
t), dalam contoh ini sebuah orbit Lissajous:
function orbit(t)
{
return { "x": 34 * Math.sin(t * 0.84), "y": 45 * Math.cos(t * 0.23) };
}
Angka-angka itu hanya untuk pertunjukan. Anda bisa memparameternya dan menggunakan currying untuk menetapkannya dan mendapatkan fungsi "orbit()" seperti ini:
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) };
};
}
Dengan cara ini, Anda dapat menghasilkan orbit Lissajous arbitrer:
let movement = generateLissajousOrbit(34, 0.84, 45, 0.23);
Jelas, fungsi pergerakan apa pun sah. Satu-satunya batasannya adalah:
- panggil dengan
tyang mengekspresikan waktu nyata; - menerima sebuah objek dengan koordinat
xdanypada waktut.
Gerakan yang lebih sederhana seharusnya sudah jelas sekarang tentang cara menerapkannya. Juga perlu dicatat bahwa dengan cara ini sangat mudah untuk menyambungkan gerakan apa pun.
- Tentukan pada titik mana dalam animasi/gerakan Anda berada:
Saat memulai, sisihkan milidetik waktu nyata saat ini, seperti ini:
let mymovingobject = {
"started": performance.now(),
"movement": generateLissajousOrbit(34, 0.84, 45, 0.23)
};
Untuk mendapatkan x dan y pada waktu tertentu, Anda sekarang dapat melakukan sebagai berikut:
let now = performance.now();
let pos = mymovingobject.movement(now - mymovingobject.started);
// pos.x and pos.y contain the current coordinates
Anda akan mendapatkan gerakan independen (frame animasi) yang sepenuhnya bergantung pada waktu nyata, yang merupakan ruang persepsi subyektif Anda.
Jika mesin mengalami gangguan atau tingkat penyegaran berubah karena alasan apa pun (pengguna baru saja mengkalibrasi ulang monitor, memindahkan jendela dari satu desktop ke desktop lain dari monitor 120 Hz ke 60 Hz, atau apa pun) .... gerakan tetap akan terkait waktu nyata dan sepenuhnya terpisah dari kecepatan bingkai.
- sekarang Anda dapat mengambil posisi di polling kapan saja dan menggunakannya untuk rendering
Dalam fungsi yang menangani requestAnimationFrame(), Anda cukup memeriksa posisi seperti yang ditunjukkan di atas dan kemudian menggambar objek di pos.x dan pos.y, tanpa pernah memikirkan apa sebenarnya laju penyegaran itu.
Anda juga dapat melewati frame untuk mengurangi laju frame dan membiarkan pengguna menentukan frekuensi dengan menghitung frame, seperti ini:
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
}
Mampu mengurangi laju bingkai sangat penting saat ini karena monitor dengan frekuensi tinggi. Aplikasi Anda akan melompat dari 60 piksel/detik menjadi 120 piksel/detik hanya dengan mengganti monitor. Ini bukan yang Anda inginkan.
Fasilitas requestAnimationFrame() terlihat seperti obat mujarab untuk menggulirkan dengan mulus, tetapi kenyataannya Anda mengikat diri pada keterbatasan perangkat keras yang sama sekali tidak diketahui (pikirkan tentang monitor modern pada tahun 2035... siapa yang tahu seperti apa mereka nantinya).
Teknik ini memisahkan frekuensi bingkai fisik dari kebutuhan kecepatan logis (permainan).
Semoga itu masuk akal entah bagaimana.
Jawaban #2
const run() {
requestAnimationFrame (run);
lete elens [8];
const ax (Math.cos(3 frm) rad width) / height; (Math.sin(4 frm) rad height) / width;
const ay
e.x + (ax pointer.xe.x) / 10;
e.y+ (ay pointer.ye.y) / 10;
for (let 11; i < N; i++) {
let e= elems[1];
let ep elems[i-1];
(ep.xe.x e.x (Math.cos(a) (1001))/5)/4;
const a Math.atan2(e.yep.y, e.x ep.х);
e.y + (ep.ye.y (Math.sin(a) (1001))/5)/4;
consts e.use.setAttributeNS(
(1624 (11))/50;
null,
"transfore",
translate($((ep.x + e.x) / 2),$((ep.ye.y) / 2)) rotate($(
(188/ Math.PI) a
1 ) translate(${0},$(0)) scale($(s),$(s))
