先看效果:javascript
你有沒有想到王者榮耀?java
固然,這個效果不是我獨創的,改編於 PEP 官方的 demo。jquery
若是在手機上查看它那個的話,能夠用左手控制飛船飛行,右手點擊頁面發射子彈。它已經有了遊戲的雛形。(另外插一句, pointerEvent 事件很是好用的,推薦)git
本文將用我的思路,從頭實現這個搖桿控制飛船效果,代碼要比官方的清晰、好懂。github
簡單分析一下這個效果的原理。編程
頁面上總共有兩個組件:飛船和搖桿。canvas
搖桿用來控制飛船的移動。它又分爲兩部分,杆座(底座)和杆頭。鼠標按下初始化底座的位置,鼠標移動時,移動杆頭的位置。微信
杆頭與底座的相對位置,爲飛船的運動提供了速度大小和方向。動畫
計算出距離以及夾角,並選取距離的十分之一,做爲飛船的速度大小。ui
關鍵代碼是:
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)
複製代碼
核心原理就這些。
本質上,效果核心是「控制」,所以最關鍵的就是要弄清楚控制器與被控制物兩者變化對應關係。一旦掌握這個,剩下的就是敲敲代碼啦。(說得輕巧。。)
搖桿分爲杆頭和底座。底座是兩個圓,杆頭是一個圓,兩者都有本身的座標。
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
})
複製代碼
效果以下:
飛船自己由兩個三角形組成,一個用於表示機身,一個用於表示噴出的火焰。並有速度和角度屬性,以便控制器操控。下面是部分代碼:
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 方法裏後,飛船就飛起來了。
有了兩個組件後,接下來要讓兩者配合起來。根據前面原理的說明,移動搖桿時,須要更新飛船的速度和角度。
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的。
再次感謝你閱讀到這裏。下一篇文章見。