「這是我參與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玩家!