日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

canvas 多次画图效果_canvas练习之终极的奔跑小人

發(fā)布時(shí)間:2023/12/20 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 canvas 多次画图效果_canvas练习之终极的奔跑小人 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

這次做一個(gè)終極的練習(xí),先看一下最后的效果。

一個(gè)不停奔跑的小人,點(diǎn)擊鼠標(biāo)后會(huì)讓他跑到目的地,并且呈現(xiàn)不同的角度。下面來(lái)看一下如何一步步來(lái)實(shí)現(xiàn)它的。

準(zhǔn)備

網(wǎng)上下載了一張圖片,其中包含了小人面向不同角度奔跑的各個(gè)分解動(dòng)作。

新建一個(gè)html文件,放入常規(guī)的canvas元素以及script標(biāo)簽。

第0版

這一版的目的是在canvas上畫(huà)出素材里的其中一個(gè)小人。其中,事先經(jīng)過(guò)了測(cè)量,每一個(gè)小人橫向占70像素,縱向占92像素。

這里主要用到了CanvasRenderingContext2D的drawImage方法,特別是這個(gè)方法的九參數(shù)版本。

void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight); // image 繪制到上下文的元素 // sx 需要繪制到目標(biāo)上下文中的,image的矩形(裁剪)選擇框的左上角 X 軸坐標(biāo)。 // sy 需要繪制到目標(biāo)上下文中的,image的矩形(裁剪)選擇框的左上角 Y 軸坐標(biāo)。 // sWidth 需要繪制到目標(biāo)上下文中的,image的矩形(裁剪)選擇框的寬度。如果不說(shuō)明,整個(gè)矩形(裁剪)從坐標(biāo)的sx和sy開(kāi)始,到image的右下角結(jié)束。 // sHeight 需要繪制到目標(biāo)上下文中的,image的矩形(裁剪)選擇框的高度。 // dx image的左上角在目標(biāo)canvas上 X 軸坐標(biāo)。 // dy image的左上角在目標(biāo)canvas上 Y 軸坐標(biāo)。 // dWidth image在目標(biāo)canvas上繪制的寬度。 允許對(duì)繪制的image進(jìn)行縮放。 如果不說(shuō)明, 在繪制時(shí)image寬度不會(huì)縮放。 // dHeight image在目標(biāo)canvas上繪制的高度。 允許對(duì)繪制的image進(jìn)行縮放。 如果不說(shuō)明, 在繪制時(shí)image高度不會(huì)縮放

完成后效果如下圖,目前代碼很簡(jiǎn)單,節(jié)約篇幅,就不具體貼了。

第1版

這一版的目的是要讓小人奔跑起來(lái),但位置可以不變。也就是利用人的視覺(jué)停留,在canvas上不停地重繪小人,給人以奔跑的感覺(jué)。

這里以及后面的代碼都做了一個(gè)假設(shè)就是requestAnimationFrame是被系統(tǒng)穩(wěn)定調(diào)用的,沒(méi)有阻塞的情況發(fā)生,這樣子就簡(jiǎn)化了代碼,不需要根據(jù)兩次requestAnimationFrame調(diào)用的時(shí)差來(lái)進(jìn)行一些計(jì)算。

const image = new Image() image.src = 'walking.jpeg' let index = 0 //計(jì)數(shù),每次渲染時(shí)+1 const HSTEP = 70 //橫向每幀間隔距離 const VSTEP = 92 //縱向每幀間隔距離 let lastTime = 0 //記錄上次時(shí)間戳 image.onload = () => {window.requestAnimationFrame(walking) //啟動(dòng)動(dòng)畫(huà) } function walking(timestamp) {if (timestamp - lastTime > 80) { //滿時(shí)差后渲染lastTime = timestampcontext.drawImage(image, HSTEP * (index++ % 8), VSTEP * 2, HSTEP, VSTEP, 0, 0, HSTEP, VSTEP)}window.requestAnimationFrame(walking) }

里面有個(gè)魔鬼數(shù)字VSTEP * 2這個(gè)2是從上向下的第三組圖片,也即是小人面向正右方的分解動(dòng)作。

第2版

這次在上一版的基礎(chǔ)上增加了小人的坐標(biāo)移動(dòng)。所要多做的僅僅是在drawImage方法參數(shù)中改變畫(huà)圖位置的坐標(biāo)。

const image = new Image() image.src = 'walking.jpeg' let index = 0 //計(jì)數(shù),每次渲染時(shí)+1 const HSTEP = 70 //橫向每幀間隔距離 const VSTEP = 92 //縱向每幀間隔距離 const MOVESTEP = 10 //每幀移動(dòng)距離 let lastTime = 0 //記錄上次時(shí)間戳 let xPos = 0 // x坐標(biāo)位置 image.onload = () => {window.requestAnimationFrame(walking) //啟動(dòng)動(dòng)畫(huà) } function walking(timestamp) {if (timestamp - lastTime > 80) { //滿時(shí)差后渲染lastTime = timestampif (xPos < canvas.width - HSTEP) {xPos = index * MOVESTEP}context.clearRect(0, 0, canvas.width, canvas.height)context.drawImage(image, HSTEP * (index++ % 8), VSTEP * 2, HSTEP, VSTEP, xPos, 0, HSTEP, VSTEP)}window.requestAnimationFrame(walking) }

第3版

這次也是小步迭代,增加了小人縱向移動(dòng),換了另一個(gè)角度的動(dòng)作。

const image = new Image() image.src = 'walking.jpeg' let index = 0 //計(jì)數(shù),每次渲染時(shí)+1 const HSTEP = 70 //橫向每幀間隔距離 const VSTEP = 92 //縱向每幀間隔距離 const MOVESTEP = 10 //每幀移動(dòng)距離 let lastTime = 0 //記錄上次時(shí)間戳 let xPos = 0 // x坐標(biāo)位置 let yPos = 0 // y坐標(biāo)位置 image.onload = () => {window.requestAnimationFrame(walking) //啟動(dòng)動(dòng)畫(huà) } function walking(timestamp) {if (timestamp - lastTime > 80) { //滿時(shí)差后渲染lastTime = timestampif (xPos < canvas.width - HSTEP) {xPos = index * MOVESTEP}if(yPos < canvas.height - VSTEP) {yPos = index * MOVESTEP}context.clearRect(0, 0, canvas.width, canvas.height)context.drawImage(image, HSTEP * (index++ % 8), VSTEP * 5, HSTEP, VSTEP, xPos, yPos, HSTEP, VSTEP)}window.requestAnimationFrame(walking) }

魔鬼數(shù)字換成了5哈。

最終版

先定義個(gè)class用來(lái)存放x、y軸的坐標(biāo),這個(gè)類(lèi)的兩個(gè)實(shí)例分別對(duì)應(yīng)了當(dāng)前的坐標(biāo)和目的地的坐標(biāo)。

class Position {constructor(x, y) {this.x = xthis.y = y} }let currentPos = new Position(0, 0) //當(dāng)前位置 let targetPos = new Position(0, 0) //目標(biāo)位置

移動(dòng)時(shí)的相關(guān)新建變量有

let angle = 0 let movingAngleType = 2 //角度類(lèi)型一共8種 let xMoveStep //x軸每次移動(dòng)距離 let yMoveStep //y軸每次移動(dòng)距離

其中angle在鼠標(biāo)點(diǎn)擊時(shí),即可通過(guò)當(dāng)前位置和鼠標(biāo)點(diǎn)擊位置計(jì)算得出。movingAngleType就對(duì)應(yīng)了八種奔跑角度的分解動(dòng)作。xMoveStep和yMoveStep是每次渲染時(shí)x軸和y軸移動(dòng)的距離,跟anglue一樣,也只要在點(diǎn)擊的時(shí)候計(jì)算出來(lái)就行。

每次渲染時(shí),計(jì)算目標(biāo)點(diǎn)和當(dāng)前點(diǎn)的距離差,如果大于步進(jìn),就加上步進(jìn),如果小于,就將目標(biāo)點(diǎn)位置賦給當(dāng)前點(diǎn)。

剩下的事情就是計(jì)算了,這一次好好的把三角函數(shù)復(fù)習(xí)了一遍。這個(gè)例子中的角度是在-90° ~ 270°之間。Math.asin計(jì)算結(jié)果是在-π ~ π 之間的,而canvas的坐標(biāo)軸原點(diǎn)位于左上角,并且根據(jù)位置不同要如下計(jì)算

if (targetPos.x < currentPos.x) {angle = Math.PI - angle }

上面這段代碼就把360°的坐標(biāo)軸分在了-90° ~ 270°之間,而不是0 ~ 360°,影響到了移動(dòng)角度類(lèi)型,也即是從上至下的八個(gè)動(dòng)作的選取。-75° ~ -15°是右下角度,-15° ~ 15°是右角度,依次類(lèi)推。

完整的代碼如下

class Position {constructor(x, y) {this.x = xthis.y = y} } const canvas = document.getElementById('canvas') const context = canvas.getContext('2d') const image = new Image() image.src = 'walking.jpeg' let index = 0 //計(jì)數(shù),每次渲染時(shí)+1 const HSTEP = 70 //橫向每幀間隔距離 const VSTEP = 92 //縱向每幀間隔距離 const MOVESTEP = 10 //每幀移動(dòng)距離 const TIMESTAMP_THRESHOLD = 80 // 時(shí)差閾值 const FRAMES_LENGTH = 8 let lastTime = 0 //記錄上次時(shí)間戳 let currentPos = new Position(0, 0) //當(dāng)前位置 let targetPos = new Position(0, 0) //目標(biāo)位置 let angle = 0 let movingAngleType = 2 //角度類(lèi)型一共8種 let xMoveStep //x軸每次移動(dòng)距離 let yMoveStep //y軸每次移動(dòng)距離 image.onload = () => {window.requestAnimationFrame(walking) //啟動(dòng)動(dòng)畫(huà) } function windowToCanvas(canvas, x, y) {var bbox = canvas.getBoundingClientRect();var style = window.getComputedStyle(canvas);return {x: (x - bbox.left - parseInt(style.paddingLeft) - parseInt(style.borderLeft))* (canvas.width / parseInt(style.width)),y: (y - bbox.top - parseInt(style.paddingTop) - parseInt(style.borderTop))* (canvas.height / parseInt(style.height))}; } function walking(timestamp) { //抽象成:在什么位置,畫(huà)什么圖,完全由狀態(tài)數(shù)據(jù)決定 UI = f(state)if (timestamp - lastTime > TIMESTAMP_THRESHOLD) { //滿時(shí)差后渲染lastTime = timestamp//計(jì)算步進(jìn),MOVESTEP要投射到x軸和y軸上// 這里xMoveStep一開(kāi)始沒(méi)取絕對(duì)值,調(diào)試了好久if (Math.abs(targetPos.x - currentPos.x) > Math.abs(xMoveStep)) { currentPos.x += xMoveStep} else {currentPos.x = targetPos.x}if (Math.abs(targetPos.y - currentPos.y) > Math.abs(yMoveStep)) {currentPos.y += yMoveStep} else {currentPos.y = targetPos.y}context.clearRect(0, 0, canvas.width, canvas.height)context.drawImage(image, HSTEP * (index++ % FRAMES_LENGTH), VSTEP * movingAngleType,HSTEP, VSTEP, currentPos.x, currentPos.y, HSTEP, VSTEP)}window.requestAnimationFrame(walking) }canvas.addEventListener('click', e => {const position = windowToCanvas(canvas, e.clientX, e.clientY)targetPos.x = position.x - HSTEP / 2 //鼠標(biāo)點(diǎn)擊點(diǎn)為人物的中心,所以減去一半寬度targetPos.y = position.y - VSTEP / 2 //鼠標(biāo)點(diǎn)擊點(diǎn)為人物的中心,所以減去一半高度//計(jì)算angleangle = Math.asin((targetPos.y - currentPos.y) / Math.sqrt(Math.pow(targetPos.y - currentPos.y, 2)+ Math.pow(targetPos.x - currentPos.x, 2)))if (targetPos.x < currentPos.x) {angle = Math.PI - angle}xMoveStep = MOVESTEP * Math.cos(angle) //只用計(jì)算一次yMoveStep = MOVESTEP * Math.sin(angle) //判斷哪種角度的奔跑if(angle >-5 * Math.PI /12 && angle < -Math.PI /12) {movingAngleType = 7 //右上} else if(angle >= -Math.PI / 12 && angle <= Math.PI / 12 ) {movingAngleType = 2 //右} else if(angle > Math.PI /12 && angle < 5 * Math.PI / 12) {movingAngleType = 5 //右下} else if(angle >= 5 * Math.PI /12 && angle <= 7 * Math.PI /12) {movingAngleType = 0 //下} else if(angle > 7 * Math.PI /12 && angle < 11 * Math.PI /12) {movingAngleType = 4 //左下} else if(angle >= 11 * Math.PI /12 && angle <= 13 * Math.PI /12) {movingAngleType = 1 //左} else if(angle > 13 * Math.PI /12 && angle < 17 * Math.PI /12) {movingAngleType = 6 //左上} else {movingAngleType = 3 //上} })

代碼貼到了github上

swordrain/running?github.com補(bǔ)充一下,其實(shí)還有一種思路是不用計(jì)算小人的坐標(biāo),而是移動(dòng)canvas坐標(biāo)軸,每次都只在(0, 0)處繪制小人即可。

總結(jié)

以上是生活随笔為你收集整理的canvas 多次画图效果_canvas练习之终极的奔跑小人的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。