我想嘗試使用 Canvas 進行簡單的實體模擬,它允許您在 HTML 上繪製圖片。今天我們要討論的是斜投影,或者物體沿著對角線向上拋出時的移動。
*這裡我們假設您對 Canvas 有基本的了解。
準備畫布
首先,準備好Canvas。任何尺寸都可以。
<canvas id="canvas" width="640" height="480"></canvas>
const canvas = document.getElementById('canvas'), ctx = canvas.getContext('2d');
畫布被匯出為光柵影像,因此在高解析度顯示器上查看時會顯得模糊。可以透過將其繪製得大一點然後使用 CSS 將其縮小來解決,就像普通圖像一樣。
const dpr = window.devicePixelRatio || 1, width = canvas.width, height = canvas.height; // 以像素比例縮放 Canvas canvas.width *= dpr; canvas.height *= dprx // 使用縮寫 +pcan can CSS.放 Canvas 繪製本身 ctx.scale(dpr, dpr);
這樣,即使在智慧型手機上觀看,您也能看到清晰的影像。
座標系設定
畫布座標向下為正,原點在左上角。這很難處理,所以我們將其轉換為向上的正座標,這在數學和物理中是常用的。此外,由於這是一個斜投影,原點將設置在左下方。
// 翻轉 y 座標 ctx.scale(1, -1); // 沿著 y 軸向下平移高度 ctx.translate(0, -height);
如果我們將它與高解析度支援一起寫在一行中,它看起來像這樣。
ctx.transform(dpr, 0, 0, -dpr, 0, 高度* dpr);
這樣就完成了座標系的設定。
畫布動畫
Canvas 沒有直接指定動畫的方法,但您可以透過重複擦除和繪製來輕鬆創建動畫,就像在翻書一樣。
下面是一個圓圈向右移動的動畫範例。
let x = 80, y = 240; // 定位圓圈 const tick = () => { // 清除螢幕 ctx.clearRect(0, 0, canvas.width, canvas.height); // 繪製圓圈 ctx.beginPath(); ctx.arc(x, y , 250 月); ; requestAnimationFrame(tick); }; requestAnimationFrame(tick);
請求動畫框架
由於是按照瀏覽器的渲染時序執行的(並且不保證間隔是恆定的),因此移動速度會根據環境而有所不同。
為了使其以恆定的速度移動,您可以使用自上次呼叫以來的時間。
let x = 80, y = 240, // 圓的位置 vx = 60, // X方向的速度[px/s] lastTime; // 上次呼叫的時間[ms] const tick = (time) => { if (!lastTime) lastTime = time; const dt = (time) => { if (!lastTime) lastTime = time; const dt = (time - ) /時間 - >> 清除時間 - + //> 清除時間 - 字節。 x.clearRect(0, 0, canvas.width, canvas.height); // 繪製點 ctx.beginPath(); ctx.arc(x, y, 25, 0, Math.PI * 2); ctx.fill(); lastTime = time;
看
筆
SHIN Inc. (@shin-inc) 製作的 Canvas 動畫
在 CodePen 上。
現在我們有一個以恆定速度移動的動畫(儘管作為物理模擬,這並不令人滿意 - 稍後會詳細介紹)。
添加 Y 軸運動還允許對角線運動。
自由落體
現在我們已經準備好了,我們將實際建立模擬器。讓我們從模擬自由落體開始。
被掉落的物體被視為無尺寸的點質量,重力加速度計算為 9.8m/s²。如果有加速度,則除了位置更新之外,還需要速度更新。
另外,由於 1px = 1m 太大,我們將其計算為 10px = 1m。
let x = 32, y = 40, // 點的位置[m] vy = 0, // Y方向的速度[m/s] lastTime; // 上次呼叫的時間[ms] const g = -9.8, // 重力加速度 scale = .1; // m/像素 const tick = (time) = Times (time);用時間[s] // 改變位置和速度 y += vy * dt; vy += g * dt; // 清除螢幕 ctx.clearRect(0, 0, canvas.width, canvas.height); // 繪製點 ctx.begin(0); ctx.arc(x / scale, Mathx. lastTime = time; requestAnimationFrame(tick); };請求動畫幀(勾選);
看
筆
SHIN Inc.(@shin-inc)的《自由落體》
在 CodePen 上。
您可以看到它在下落時是如何加速的。然而,如果我們保持這種狀態,它就會穿過地面(y=0),所以我們將添加一個停止條件。
let x = 32, y = 40, // 點的位置[m] vy = 0, // Y方向的速度[m/s] lastTime; // 上次呼叫的時間[ms] const g = -9.8, // 重力加速度 scale = .1; // m/像素 const tick = (time) = Times (time);用時間[s] // 改變位置和速度 y += vy * dt y = Math.max(y, 0); // y 總是大於或等於 0 vy += g * dt; // 清除螢幕 ctx.clearRect(0, 0, canvas.width, canvas.hectx); 繪製, 0, Math.PI * 2); ctx.fill(); // 如果 y 大於 0,才執行下一步處理。
一旦它撞到地面就會自動停止。
看看筆
SHIN Inc.(@shin-inc)的《自由落體》(停止)
在 CodePen 上。
斜投影
現在我們來模擬斜投影,這也是我們最初的目標。另外,我們會做一些小改動,以便日後更容易改進。
首先,單獨的更新和繪圖。
let stop = false; // 檢查是否停止 const update = (dt) => { x += vx * dt; y += vy * dt; y = Math.max(y, 0); /* vx += 0; 這次沒有橫向加速度*/ vy += g * dty = 0 & 則忽略一次/ = s = 則是第一次。 } }; const draw = () => { // 清除螢幕 ctx.clearRect(0, 0, canvas.width, canvas.height); // 繪製點 ctx.beginPath(); ctx.arc(x / scale, y / scale, 1, 0, Math.arc(x / scale, y / scale, 1, 0, Math.PI; Time) lastTime = time; const dt = (time - lastTime) / 1000; // 已用時間 [s] update(dt); draw(); if (!stop) { lastTime = time; requestAnimationFrame(tick); } };
如果你就這樣放著,畫()
或者 更新()
透過替換,可以執行不同的動畫。
由於這是一個斜投影,所以初始位置在左下方。如果停止條件設為y=0,那麼就會從一開始就停止移動,所以我們只在第一次繪製(dt=0)時忽略判斷。
這正是我們至今一直在做的事。 打鉤()
我將用這個替換它。這次我們還有沿著 x 軸的速率。
let x = 0, y = 0, // 點的位置[m] vx = 10, // X方向的速度[m/s] vy = 25, // Y方向的速度[m/s] lastTime, // 上次呼叫的時間[ms] stop = false; // 檢查是否停止 const g = -9.8, . vx * dt; y += vy * dt; y = Math.max(y, 0); /* vx += 0; 這次沒有水平加速度 */ vy += g * dt; if (dt && y === 0) { stop = true; // 如果 y = 0 cts } (但忽略第一次)就清除第 0. canvas.width, canvas.height); // 繪製點 ctx.beginPath(); ctx.arc(x / scale, y / scale, 1, 0, Math.PI * 2); ctx.fill(); }; const tick = (time) => { if (!lastTime) last date(dt); draw(); if (!stop) { lastTime = time; requestAnimationFrame(tick); } }; requestAnimationFrame(tick);
看
筆
SHIN Inc.(@shin-inc)的《拋射運動》
在 CodePen 上。
你怎麼認為?你可以看到點以一條美麗的拋物線移動。
分別用 x 和 y 來指定初速度比較難以理解,如果可以用初速和角度來指定的話,會比較容易使用。
const v = 25, // 初速[m/s] deg = 60; // 角[°] let vx = v * Math.cos(deg * Math.PI / 180), vy = v * Math.sin(deg * Math.PI / 180);
下面我們準備了一個可以改變初始速度和角度的模擬器。觀察距離和高度如何根據角度而變化。
看看筆
SHIN Inc.(@shin-inc)的拋射運動模擬器
在 CodePen 上。
任務
斜投影模擬器已成功完成。然而,如同前面所提到的 請求動畫框架
由於它的規格,它不能說是一個非常好的模擬器。對於斜投影或自由落體等模擬,這可能在誤差範圍內,但由於物理模擬涉及通過數值計算進行積分,因此每個步驟(增量)之間的間隔可能會發生變化這一事實會帶來準確性問題。即使在相同條件下進行模擬,不同的人也可能得到不同的結果。
為了解決這個問題,需要固定FPS並根據幀數進行計算。也就是說,在呼叫回調的時候,需要計算距離上次已經過了多少幀,並針對這些幀進行更新計算。
另外,這次我們模擬了一個質點,但如果同時模擬多個質點,我們就需要更有效地管理質點。
我們希望下次能夠解決這些問題。
繼續閱讀 → 使用 HTML Canvas 進行實體模擬第 2 部分