“这是我参与8月更文挑战的第6天,活动详情查看: [8月更文挑战]”web
这几天学习canvas看了不少基础API,也用了很多,这里再作一个总结练习,作一个稍微有趣的:喵咪跳蛋糕小游戏,以便来再巩固总结canvas的学习,而且这个游戏也是我十分喜好的,不过由于我的canvas水平是刚入门,因此只能作一个简版的了。canvas
基础感受差很少了,进阶的话还不知道怎么搞,后面看看开源库吧。
废话很少说,先来看看效果图:数组
PC/移动的在线试玩地址(Github Page须要代理,否则进入可能会很慢或者进不了):websocket
试玩:在线试玩markdown
由于接触canvas时间较短,作的时间也不长,也缺少素材,因此效果很通常,不过也算勉强合格,至少能有基本的游戏逻辑。socket
又要逼逼赖赖说一下用的API
了,这里其实没用到什么新的API
,就是多学习了一个绘制文本的API
,这个API
的效果仍是很是棒的,这里介绍fillText
了吧,估摸着strokeText
也差不了多少:oop
先说一下fillText
的4个参数:学习
参数 | 做用 |
---|---|
text | 规定在画布上输出的文本。 |
x | 开始绘制文本的 x 坐标位置(相对于画布)。 |
y | 开始绘制文本的 y 坐标位置(相对于画布)。 |
maxWidth* | 可选。容许的最大文本宽度,以像素计。 |
ctx.font = '30px "微软雅黑"'
ctx.fillText('Hello', 30, 50)
复制代码
其实,使用fillText
这类API
不只仅只靠这几个参数,像经过fillStyle
、font
等咱们也能够修改绘制的w文本,好比我如今说的这个比较有用的textAlign
属性:
颇有意思,textAlign
的left
和right
等属性是以坐标点来划分的。一张图看懂这个属性:优化
更多的属性具体还能够看看文本相关的API
介绍:Canvas文本APIMDN文档。ui
接下来,我就说一下我使用canvas
来作喵咪跳蛋糕小游戏的思路。其实,我以为吧,canvas
不少API
其实单独使用起来是不难的,关键是组合起来完成某一个东西,难度就比较大了,有些还须要数学知识。
废话就很少说了,开始来看看喵咪是怎么跳蛋糕的。
喵咪跳蛋糕小游戏是这样的:
因此,思路基本清楚了:
要画的的是猫咪和蛋糕,而后就是实现碰撞检测计算新位置便可!
为啥喜欢在作某一些Demo时,我比较喜欢首先画一个方格地图呢,由于方便观察和计算物体绘画的点,对于刚学习的我来讲,用处仍是很是大的。
initBoard(len) {
const num = Math.floor(this.cvs.width / len)
for (let i = num ; i >= 0 ; i --) {
this.ctx.beginPath()
this.ctx.strokeStyle = '#ccc'
this.ctx.lineTo(i * len, 0)
this.ctx.lineTo(i * len, this.cvs.width)
this.ctx.stroke()
this.ctx.beginPath()
this.ctx.lineTo(0, i * len)
this.ctx.lineTo(this.cvs.width, i * len)
this.ctx.stroke()
}
}
复制代码
方格能够弄小一点,经过观察方格,咱们也能够更方便的给物体设置大小、位置、移动速度(以几个方格为标准)等属性。
小喵咪的建立很简单,由于小喵咪除了在Y轴
的方向会由于叠加蛋糕变化外,其余是不会变化的,因此咱们只须要一开始把喵咪画出来,然你后动态增长y
值便可:
this.role = { x: 0, y: 0, t: 0 }
this.initRole(this.cvs.width - 10)
复制代码
initRole(y) {
this.role.x = this.cvs.width / 2
this.role.y = y
this.role.t = this.cvs.width - y
}
paintRole() {
this.ctx.beginPath()
this.ctx.arc(this.role.x - 10, this.role.y, 10, 0, 2 * Math.PI)
this.ctx.fillStyle = 'red'
this.ctx.fill()
}
复制代码
这里我用这个小球表示咱们的Role
喵咪,能够看到喵咪已经在底部中间的位置了,如今就差点击跳动了,这里的话实现方式应该会有不少种,我这里就随便简单实现了下:
paintRole() {
this.ctx.beginPath()
if (this.isClick) {
// 判断角色是否已到落地点(落地点可能在方块上)
if (this.role.y >= this.cvs.width - this.role.t && this.type === 'down') {
this.speed = 2.8
this.role.y = this.cvs.width - this.role.t
this.isClick = false
this.type = 'up'
return
}
this.role.y -= this.speed
if (this.speed <= 0) {
this.type = 'down'
} else {
this.type = 'up'
}
this.speed -= .1
}
this.ctx.arc(this.role.x - 10, this.role.y, 10, 0, 2 * Math.PI)
this.ctx.fillStyle = 'red'
this.ctx.fill()
}
复制代码
固然,paintRole
这个方法会一直经过定时器不停的重绘,都入门一段时间了,这些代码逻辑就不贴了。
这里的主要思路就是:
点击的时候是朝上减速的方向,而后又落体加速,因此我这里就直接经过speed--
来实现了;
而后再落下的时候即type
为down
的时候,咱们要计算小球的当前位置和落地点位置(这里使用的是属性t
来记录,由于落地点可能由于蛋糕的叠加而不一样,因此单独储存),计算到达落地点后重置状态便可。
建立蛋糕的方式也很简单,就是从一边到另外一边,先从简单的状况开始:蛋糕只是变化X轴
的位置,那么咱们每次绘图时增长x
的值便可。
paintCake() {
if (this.cake.x >= this.cvs.width) {
this.cake.x = -this.cake.w
}
this.cake.x ++
this.ctx.beginPath()
this.ctx.fillStyle = 'skyblue'
this.ctx.fillRect(this.cake.x, this.cake.y, this.cake.w, this.cake.h)
}
复制代码
这样便可实现方块的移动了,如今咱们就要实现检测的机制了,这里的话检测稍微麻烦一点,由于小球不只撞着要死,落体运动踩在蛋糕时是会叠加的。
碰撞的检测仍是挺麻烦的,这里说一下思路就行:
X轴
范围里面的话,当Y轴
距离大于等于了蛋糕的Y轴
点时即踩上蛋糕,其余碰撞则为失败。record
对象数组来记录;Y轴
点。看一下代码的简单实现:
check() {
if (
this.role.x + 5 >= this.cake.x &&
this.role.x - 5 <= this.cake.x + this.cake.w &&
this.role.y >= this.cake.y
) {
if (this.type === 'down') {
if (this.cakes.length) {
// 增长断定范围,方便重叠蛋糕
const last = this.cakes[this.cakes.length - 1]
const range = 5
if (
(this.cake.x > last.x - range && this.cake.x < last.x + range) ||
(this.cake.x > last.x + last.w - range && this.cake.x < last.x + last.w + range)
) {
this.scoreRatio += 1
this.cake.x = last.x
this.paintRatioText()
} else {
this.scoreRatio = 1
}
}
this.moveEnd = true
this.score += this.scoreRatio
this.cakes.push({ x: this.cake.x, y: this.cake.y })
this.initCake(this.cake.y - 10)
this.initRole(this.role.y - 10)
} else {
this.ctx.font = '30px "微软雅黑"'
this.ctx.textAlign = 'center'
this.ctx.fillStyle = 'red'
this.ctx.fillText('Game Over!', this.cvs.width / 2, this.cvs.width / 2)
clearTimeout(this.timer)
this.timer = null
this.end = true
}
}
}
复制代码
由于踩蛋糕,若是在同一位置的话,应该是分越加越高,可是按1px
来计算的话,这样难以彻底重叠,因此我设置了一个范围区间;
这里我引入了一个range
值,只要当前踩中的和上一个的位置偏差不超过这个range
,则算两块蛋糕重叠,并适当作连击提示:
// 绘制连击
paintRatioText() {
this.showRatioText = true
this.ctx.font = '15px "微软雅黑"'
this.ctx.textAlign = 'left'
this.ctx.fillStyle = 'red'
this.ctx.fillText('X' + this.scoreRatio, this.role.x + 30, this.role.y - 15)
setTimeout(() => {
this.showRatioText = false
}, 1e3)
}
复制代码
// 重绘记录,也就是把踩中的蛋糕再绘上去
paintReocrd() {
this.cakes.forEach(cake => {
this.ctx.beginPath()
this.ctx.fillStyle = 'orange'
this.ctx.fillRect(cake.x, cake.y, this.cake.w, this.cake.h)
})
}
复制代码
由于蛋糕是会一个一个重叠上去的,因此咱们得在必定高度后,下降蛋糕的高度,方法有两种:
Y
值给改了;我这里用的第二个方法,由于想到就作了,没考虑太多...
// 移动蛋糕,以避免叠过高了超出范围
check() {
...
if(this.cakes.length && this.cakes.length % 10 === 0 && this.moveEnd) {
this.moving = true
this.moveEnd = false
setTimeout(() => {
this.moving = false
this.cake.y += this.movingY
this.role.t -= this.movingY
this.movingY = 0
}, 550)
}
this.initCake(this.cake.y - 10)
this.initRole(this.role.y - 10)
...
}
复制代码
initRole(y) {
this.role.x = this.cvs.width / 2
this.role.y = y
this.role.t = this.cvs.width - y
}
initCake(y) {
this.cake.x = -this.cake.w
this.cake.y = y
}
// 移动蛋糕
moveCake() {
const y = this.cakes.length > 10 ? 2 : 1
this.cakes = this.cakes.map(cake => {
return {
...cake,
y: cake.y + y
}
})
}
// 移动角色
moveRole() {
const y = this.cakes.length > 10 ? 2 : 1
this.movingY += y
this.role.y += y
}
复制代码
这里我经过moving
这个状态来保存:蛋糕是否在下移状态,当下移完成时,新蛋糕才从新绘制。
而后使用movingY
来保存下移的距离,使得小球和当前蛋糕的Y
轴距离同步。
这样的话,游戏的基本逻辑就已经大体完成了,剩下的就是一些优化部分了。
例如,咱们能够把小球和蛋糕替换成功图片,而且把方格背景去掉。
像喵咪的话,咱们还能够找一组素材,这里我找了一张跳的和一张坐的,这样的话,跳起来自由落体运动就不会看起来很单一,显得人物比较丰富。
图片的话,仍是须要预先加载的,不然没有图片就开始游戏逻辑很傻瓜的。
也能够加一些蛋糕依据速度的变化而变快,出来的方向不一样等玩法,让游戏更加的丰富。
若是能够的话在连击时能够加一些特效的效果,甚至加上websocket
使得可对战,固然这些都是后话了。
游戏是简单完成了,效果得话很通常,多是由于我绘画的一些逻辑没处理的太好吧,因此看起来只是功能比较正常,视觉就差了火候。
不过也好了,canvas
完了几天入门应该仍是入门了的,API
的使用仍是很是简单的,只是要经过这些API
组合起来使用要完成某个功能就比较麻烦了。
因此,我觉着吧,canvas
这玩儿意,我以为首先对代码设计的要好是一方面,另外一方面以为数学要好,否则计算坐标宽度等仍是挺打脑袋的。
后续的话也不知道咋个进阶,太难的很难作出来,精力也不够,还要学Vue
恰饭,再去了解了解开源库吧。
再看看效果把:
试玩:在线试玩。
好了,canvas
入门达成:得到称号:新手canvas玩家!