先看效果:
你有沒有想到王者榮耀?
當然,這個效果不是我首創的,改編於 PEP 官方的 demo。
如果在手機上檢視它那個的話,可以用左手控制飛船飛行,右手點選頁面發射子彈。它已經有了遊戲的雛形。(另外插一句, pointerEvent 事件非常好用的,推薦)
本文將用個人思路,從頭實現這個搖桿控制飛船效果,程式碼要比官方的清晰、好懂。
1. 總體原理
簡單分析一下這個效果的原理。
頁面上總共有兩個元件:飛船和搖桿。
搖桿用來控制飛船的移動。它又分為兩部分,杆座(底座)和杆頭。滑鼠按下初始化底座的位置,滑鼠移動時,移動杆頭的位置。
杆頭與底座的相對位置,為飛船的運動提供了速度大小和方向。
計算出距離以及夾角,並選取距離的十分之一,作為飛船的速度大小。
關鍵程式碼是:
var dx = control.xHead - control.xTail
var dy = control.yHead - control.yTail
var d = Math.sqrt(dx * dx + dy * dy)
ship.v = d * 0.1
ship.angle = Math.atan2(dy, dx)
複製程式碼
核心原理就這些。
本質上,效果核心是“控制”,因此最關鍵的就是要弄清楚控制器與被控制物二者變化對應關係。一旦掌握這個,剩下的就是敲敲程式碼啦。(說得輕巧。。)
2. 搖桿的實現
搖桿分為杆頭和底座。底座是兩個圓,杆頭是一個圓,二者都有自己的座標。
class Control{
constructor() {
this.xTail = 0
this.yTail = 0
this.xHead = 0
this.yHead = 0
this.visible = false
}
draw(context) {
if (!this.visible) return
context.save()
context.beginPath()
context.strokeStyle = "cyan"
context.lineWidth = 6
context.arc(this.xTail, this.yTail, 40, 0, Math.PI * 2)
context.stroke()
context.beginPath()
context.lineWidth = 2
context.arc(this.xTail, this.yTail, 60, 0, Math.PI * 2)
context.stroke()
context.beginPath()
context.arc(this.xHead, this.yHead, 40, 0, Math.PI * 2)
context.stroke()
context.restore()
}
}
複製程式碼
visible 屬性用來控制搖桿是否可見,底座的兩個圓的半徑是40和60,杆頭的圓也為40。
與滑鼠的互動操作,相對比較簡單了。與拖拽效果的實現類似:
- 滑鼠按下,搖桿可見,更新底座和杆頭的位置。
- 滑鼠移動,更新杆頭的位置
- 滑鼠抬起,搖桿不可以見。
var control = new Control()
var mouse = captureMouse(canvas) // 獲取滑鼠的實時位置,具體參見效果的完整程式碼
canvas.addEventListener('mousedown', function() {
control.xHead = control.xTail = mouse.x
control.yHead = control.yTail = mouse.y
control.visible = true
})
canvas.addEventListener('mousemove', function() {
if (control.visible) {
control.xHead = mouse.x
control.yHead = mouse.y
}
})
canvas.addEventListener('mouseup', function() {
control.visible = false
})
複製程式碼
效果如下:
3. 飛船的實現
飛船本身由兩個三角形組成,一個用於表示機身,一個用於表示噴出的火焰。並有速度和角度屬性,以便控制器操控。下面是部分程式碼:
class Ship{
constructor() {
this.x = 0
this.y = 0
this.v = 0
this.angle = 0
this.flag = false
}
draw(context) {
context.save()
context.translate(this.x, this.y)
context.rotate(this.angle)
context.beginPath()
context.moveTo(-15, -10)
context.lineTo(-15, 10)
context.lineTo(10, 0)
context.closePath()
context.lineWidth = 2
context.strokeStyle = "white"
context.stroke()
if (this.v > 0) {
context.beginPath()
context.moveTo(-15, -5)
context.lineTo(-15 - this.v * (this.flag ? 1 : 3) , 0)
context.lineTo(-15, 5)
context.closePath()
context.stroke()
this.flag = !this.flag
}
context.restore()
}
}
複製程式碼
這裡使用了 translate 來實現移動,使用了 rotate 來實現旋轉,當有速度時,開始噴出火焰。為了實現火焰閃爍效果,這裡使用 flag 表示火焰變短還是變長。同時,根據速度的大小決定火焰的長度,這樣符合直觀感覺。
飛船要動起來,需要根據速度和夾角更新 this.x 和 this.y,也就是要求出相應的水平速度和垂直速度。三個速度滿足如下三角關係:
因此有:
var vx = this.v * Math.cos(this.angle)
var vy = this.v * Math.sin(this.angle)
this.x += vx
this.y += vy
複製程式碼
把上述程式碼新增到 draw 方法裡後,飛船就飛起來了。
4. 最終效果
有了兩個元件後,接下來要讓二者配合起來。根據前面原理的說明,移動搖桿時,需要更新飛船的速度和角度。
canvas.addEventListener('mousemove', function() {
if (control.visible) {
control.xHead = mouse.x
control.yHead = mouse.y
var dx = control.xHead - control.xTail
var dy = control.yHead - control.yTail
var d = Math.sqrt(dx * dx + dy * dy)
ship.v = d * 0.1
ship.angle = Math.atan2(dy, dx)
}
})
複製程式碼
程式碼裡還有兩點沒有提到,一個是飛船飛出邊界和最大速度的限制問題,二者比較簡單,因此這裡省略了。
感謝你看到這裡,希望有所幫助。
本文完。
下面的內容是關於本系列的介紹。
2019年末,本人立了個flag,2020年要研究透canvas動畫技術。
(圖中二維碼是我的唯一微訊號,如有掘友想加的,麻煩備註下【掘金】哈。)
在這個系列,我想寫一些常見動畫知識,本文是第3篇,篇幅可能會長短不一。更多的請檢視我的個人主頁,或者《系列目錄》。
因為篇幅問題,根據以往的經驗,贊數不會太多,畢竟大家都喜歡給那種短時間看不完的文章點贊。嗯,我好像也是這樣。^_^
其實寫文章,主要還是給自己看的,算是自我進步的一個見證吧。抱著這種心態也許能好些。
另外關於canvas技術,我目前完整看完了3本書。算是過了基礎一關。
本系列一些文章可能會參考裡面的知識體系,對於一些屬於領域共識知識,如有區域性雷同,只能說:“自己憑本事學來的,怎能算抄襲。。。”。
開玩笑了,想法來源能提一句還是要提一句的。特別喜歡《精英日課》文章裡的一段話:
至於文章內容,canvas的API,本系列可能不會準備逐條介紹了,還請初學的童鞋見諒哈。MDN都有的,挺詳細的。同時,文章中遇到的還是會簡單提下。主要核心是闡述一些技巧和原理層面的知識個人理解吧。另外也打算分析一些codepen上炫酷動畫的實現原理,如果有時間可能會分析幾個動畫引擎,當然都是2D的。
再次感謝你閱讀到這裡。下一篇文章見。