阅读 4717

【canvas】摇杆操控飞船的动画原理

先看效果:

你有没有想到王者荣耀?

当然,这个效果不是我首创的,改编于 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。

与鼠标的交互操作,相对比较简单了。与拖拽效果的实现类似:

  1. 鼠标按下,摇杆可见,更新底座和杆头的位置。
  2. 鼠标移动,更新杆头的位置
  3. 鼠标抬起,摇杆不可以见。
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本书。算是过了基础一关。

1.《HTML5 Canvas核心技术》

2.《HTML5+JavaScript动画基础》

3.《WebGL编程指南》

本系列一些文章可能会参考里面的知识体系,对于一些属于领域共识知识,如有局部雷同,只能说:“自己凭本事学来的,怎能算抄袭。。。”。

开玩笑了,想法来源能提一句还是要提一句的。特别喜欢《精英日课》文章里的一段话:

至于文章内容,canvas的API,本系列可能不会准备逐条介绍了,还请初学的童鞋见谅哈。MDN都有的,挺详细的。同时,文章中遇到的还是会简单提下。主要核心是阐述一些技巧和原理层面的知识个人理解吧。另外也打算分析一些codepen上炫酷动画的实现原理,如果有时间可能会分析几个动画引擎,当然都是2D的。

再次感谢你阅读到这里。下一篇文章见。

关注下面的标签,发现更多相似文章
评论