先看效果: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的。
再次感谢你阅读到这里。下一篇文章见。