javascript - לשנות את מהירות הבקשה למסגרת האנימציה
יש לי את הקוד הזה כדי להזיז תמונה בקנבס ממיקום אחד לאחר:
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));
}
}
}
העניין עם הקוד הזה הוא: אני לא יכול לשלוט על המהירות, וזה די איטי... איך אני יכול לשלוט על המהירות ולשמור על הרעיון של לבחור את מיקום ההגעה? מצאתי נושא על זה אבל לא מצאתי משהו שעובד במקרה שלי, בהחלט כי צעד של פיקסל אחד קטן מדי אבל אני לא רואה איך אני יכול לעשות זאת.
[עריכה] הנה מה שרציתי לעשות (אני צריך להוסיף רשומה במשך 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 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()
}
];
תשובה #1
עדכון: הוספתי דוגמה נוספת לתנועה (תסתכל על הריבוע הטורקיז)
כדי לענות על ההערה שלך לגבי איך להגיע ל"מקום כלשהו" בזמן קבוע, ניתן לקבוע כמעט את כל הפונקציות ואז לפתור את המשוואה על ידי קיבוע הזמן. זה קל לתנועה ליניארית אך די קשה למקרים מורכבים, כמו תנועה לאורך פונקציות לא ליניאריות (למשל, ספירלה לוגריתמית).
לתנועה ליניארית במהירות קבועה (ללא תאוצה/האטה) מ-(x0, y0) ל-(x1, y1) בזמן dt ניתן להשתמש באינטרפולציה ליניארית:
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
};
};
}
כעת ניתן להשתמש בפונקציה זו כדי "לרכוש" נתיב. תחילה הגדירו את הנתיב על ידי יצירת הקטעים:
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
};
}
עריכה: דוגמה עובדת
רק הכנתי עבורך דוגמה עובדת זו. תראה איך אני לא מחדש את הקנבס או את הקונטקסט ומצייר על אותו דבר שוב ושוב. ואיך התנועה לא תלויה בקצב הפריימים, היא מוגדרת בפונקציית ליסאז׳וס.
"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() עבור סוג זה של תנועה.
מה שאתה צריך לעשות הוא זה.
- יש פונקציית תנועה שמתבססת על זמן אמת (
t), בדוגמה זו מסלול ליסזו:
function orbit(t)
{
return { "x": 34 * Math.sin(t * 0.84), "y": 45 * Math.cos(t * 0.23) };
}
המספרים האלה הם רק להראות. ניתן לפרמטר אותם ולהשתמש ב-currying כדי לקבע אותם ולקבל פונקציה של "orbit()" כזאת:
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);
באופן ברור, כל פונקציית תנועה תקפה. המגבלות היחידות הן:
- הפעל עם
tשמבטא זמן אמת; - לקבל אובייקט עם קואורדינטות
xו-yבזמןt.
תנועות פשוטות כבר צריכות להיות ברורות עד עכשיו כיצד ליישם. שים לב גם שבדרך זו קל מאוד לחבר כל תנועה.
- קבע בנקודה איזו בתנועה/אנימציה אתה:
בהתחלה שמור בצד את המילישניות בזמן אמת הנוכחיות, כך:
let mymovingobject = {
"started": performance.now(),
"movement": generateLissajousOrbit(34, 0.84, 45, 0.23)
};
כדי לקבל את x ו-y בכל רגע נתון, ניתן כעת לעשות כך:
let now = performance.now();
let pos = mymovingobject.movement(now - mymovingobject.started);
// pos.x and pos.y contain the current coordinates
תקבל תנועה בלתי תלויה במסגרת האנימציה (refresh) שתלויה אך ורק בזמן אמת, שהוא מרחב התפיסה הסובייקטיבי שלך.
אם למכונה יש תקיעה או שקצב הרענון משתנה מכל סיבה שהיא (המשתמש רק ביצע כיול מחדש של המסך, הזיז את החלון בין שולחנות עבודה ממסך של 120 הרץ למסך של 60 הרץ, או כל דבר אחר) .... התנועה עדיין תהיה בזמן אמת ותלויה באופן מלא במספר הפריימים בשנייה.
- עכשיו אתה יכול לבדוק את המיקום בכל רגע נתון ולהשתמש בו להצגה
בפונקציה שמטפלת ב-requestAnimationFrame(), אתה פשוט בודק את המיקום כפי שמוצג למעלה ואז מצייר את האובייקט ב-pos.x ו-pos.y, מבלי לחשוב בכלל על מה קצב הרענון בפועל.
אתה יכול גם לדלג על פריימים כדי להפחית את קצב הפריימים ולאפשר למשתמש להחליט על התדירות על ידי ספירת פריימים, כך:
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... מי יודע איך הם יהיו).
טכניקה זו מפרידה בין תדירות המסגרת הפיזית לבין דרישות המהירות הלוגיות (משחקיות).
מקווה שזה מתקבל על הדעת איכשהו.
תשובה #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))
