Vue3+Canvas實(shí)現(xiàn)坦克大戰(zhàn)游戲(二)
前言
接著上篇講,本篇主要給大家講解一下子彈擊中物體、物體銷毀、敵方坦克構(gòu)建生成、運(yùn)動(dòng)算法、爆炸效果、以及障礙物的生成;了解了這些我相信你可以不依賴游戲引擎實(shí)現(xiàn)大部分小游戲的開(kāi)發(fā)。
W/上 S/下 A/左 D/右 F/射擊
讓我們開(kāi)始吧!
敵方坦克的生成
我們可以使用 for 循環(huán)和Tank 構(gòu)造函數(shù),批量制造多個(gè)敵方坦克,x,y 為其在畫(huà)布中的坐標(biāo),direction 為坦克當(dāng)前方向,type 為精靈圖中截取坦克圖片的信息,包含了坐標(biāo),尺寸,類型等
for(let t=0; t<20; t++) { let tank = new Tank(new TankConfig({ x: 100 + t*30, y: 100, direction: DIRECTION.DOWN, type: TANK_TYPE.ENEMY0, is_player: 0 })) tank.star(); } ENEMY0: { type: 'ENEMY1', dimension: [30, 30], image_coordinates: [129, 1, 129, 33] }
坦克移動(dòng)的算法
emove 函數(shù)就是就是物體移動(dòng)狀態(tài)下每幀都會(huì)執(zhí)行的函數(shù),將根據(jù)當(dāng)前方向修改下幀的坐標(biāo),當(dāng)下幀坐標(biāo)到達(dá)地圖邊緣時(shí)將執(zhí)行 dir 函數(shù)更新坦克方向,如果是子彈則將銷毀。
this.emove = function (d, obstacle_sprites) { this.direction = d switch (d) { case DIRECTION.UP: if ( (obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites ) { this.y -= this.speed if (this.y <= 10) { this.dir() } } else { this.dir() } break ...
dir 函數(shù)用來(lái)隨機(jī)修改移動(dòng)的方向,當(dāng)然這個(gè)函數(shù)不能每幀都調(diào)用,否則坦克將像無(wú)頭蒼蠅一樣了;那么什么時(shí)候需要修改方向呢,我們認(rèn)為當(dāng)坦克和物體相撞的時(shí)候或者到達(dá)地圖邊緣時(shí)修改方向是合理的;子彈也可以認(rèn)為是一個(gè)物體,所以子彈到達(dá)坦克周邊一定距離時(shí)也將引起坦克規(guī)避動(dòng)作,也就是將觸發(fā)dir 函數(shù)。
this.dir = function () { if(Math.floor(Math.random()*10) === 0 || Math.floor(Math.random()*10) === 1) { this.direction = DIRECTION.UP; }; if(Math.floor(Math.random()*10) === 2 || Math.floor(Math.random()*10) === 3) { this.direction = DIRECTION.DOWN; }; if(Math.floor(Math.random()*10) === 4 || Math.floor(Math.random()*10) === 5) { this.direction = DIRECTION.LEFT; }; if(Math.floor(Math.random()*10) === 6 || Math.floor(Math.random()*10) === 7) { this.direction = DIRECTION.RIGHT; }; if(Math.floor(Math.random()*10) === 8 || Math.floor(Math.random()*10) === 9) { this.dir(); } }
子彈擊中物體的算法
我們定義 checkRangeOverlap 函數(shù)來(lái)判斷兩個(gè)物體是否相撞,uiobjs 為畫(huà)布中所有的元素對(duì)象列表,包含 坦克、子彈、障礙物等;
this.getFrontPoints() 函數(shù)將根據(jù)當(dāng)前方向返回包含三個(gè)頂端點(diǎn)坐標(biāo)數(shù)組,形成判斷區(qū)域;
uiobjs[o].getCornerPoints() 函數(shù)返回當(dāng)前元素的四個(gè)邊角坐標(biāo)數(shù)組,形成判斷區(qū)域;
然后getFrontPoints() 的三個(gè)點(diǎn)坐標(biāo) 將分別和 uiobjs[o].getCornerPoints() 的四邊坐標(biāo)進(jìn)行點(diǎn)對(duì)點(diǎn)判斷,根據(jù)x, y 數(shù)值滿足下方四個(gè)條件時(shí)則認(rèn)為此點(diǎn)坐標(biāo)在元素內(nèi)部。
// 判斷點(diǎn)坐標(biāo)是否在區(qū)域內(nèi)部,以識(shí)別是否相撞 CanvasSprite.prototype.checkRangeOverlap = function (uiobjs) { for (let o in uiobjs) { let obstacle_cp = uiobjs[o].getCornerPoints() let fp = this.getFrontPoints() for (let idx = 0; idx < fp.length; idx++) { if ( fp[idx].x > obstacle_cp[0].x && fp[idx].x < obstacle_cp[1].x && fp[idx].y > obstacle_cp[0].y && fp[idx].y < obstacle_cp[2].y ) { return true } } } return false }
// 返回當(dāng)前物體頂端三坐標(biāo) CanvasSprite.prototype.getFrontPoints = function () { let front_point switch (this.direction) { case DIRECTION.UP: front_point = [ new Point(this.x, this.y), new Point((2 * this.x + this.width) / 2, this.y), new Point(this.x + this.width, this.y), ] break ... 縮略,下左右方向 } return front_point }
// 返回元素四邊坐標(biāo)形成的矩形區(qū)域范圍 CanvasSprite.prototype.getCornerPoints = function () { var corner_points switch (this.direction) { case DIRECTION.UP: corner_points = [ new Point(this.x, this.y), new Point(this.x + this.width, this.y), new Point(this.x + this.width, this.y + this.height), new Point(this.x, this.y + this.height), ] break ... 縮略,下左右方向 } return corner_points }
啊 坦克搞多了!但是可以看到子彈擊中效果是成功的,你發(fā)現(xiàn)了沒(méi),擊中后有一個(gè)爆炸效果,這個(gè)是怎么實(shí)現(xiàn)的呢?
爆炸效果的實(shí)現(xiàn)
當(dāng)識(shí)別到擊中后將此坦克將觸發(fā)explode 爆炸效果,然后每幀 判斷 isDestroied 是否銷毀,后續(xù)每幀將 explosion_count++ 從 0 到 5,然后更改alive 狀態(tài)為0 代表已銷毀。
if (s instanceof Tank && s.alive && s.isDestroied()) { s.explode() } this.isDestroied = function () { return explosion_count > 0 } this.explode = function () { if (explosion_count++ === 5) { this.alive = 0 } }
然后 explosion_count 的數(shù)值,從0 到 5 代表爆炸效果圖的5幀
this.getImg = function () { if (explosion_count > 0) { return { width: TANK_EXPLOSION_FRAME[explosion_count].dimension[0], height: TANK_EXPLOSION_FRAME[explosion_count].dimension[1], offset_x: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[0], offset_y: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[1], } } else { return { width: width, height: height, offset_x: this.type.image_coordinates[0], offset_y: this.type.image_coordinates[1], } } }
到現(xiàn)在我們的坦克游戲已經(jīng)基本可玩了,只不過(guò)現(xiàn)在是一片大平原,毫無(wú)遮攔,我們?cè)摓楫?huà)布增加一些障礙物如墻體,石頭等,增加游戲可玩性。
生成障礙物(石墻、磚墻等)
有了之前tanke 和 子彈的構(gòu)建函數(shù),現(xiàn)在這個(gè) Block 就簡(jiǎn)單多了,只需要定義好基本的信息,如坐標(biāo),寬高、狀態(tài)、方向,然后借用 apply 來(lái)使用 CanvasSprite 上的通用方法。
let Block = function (x, y, direction, type) { type = type || BLOCK_TYPE.BLOCK_BRICK let alive = 1 let width = type.dimension[0] let height = type.dimension[1] let explosion_count = 0 this.type = type this.x = x this.y = y this.genre = 'block' this.direction = direction || DIRECTION.UP CanvasSprite.apply(this, [ { alive: 1, border_y: HEIGHT, border_x: WIDTH, speed: 0, direction: direction, x: x, y: y, width: width, height: height, }, ]) this.isDestroied = function () { return explosion_count > 0 } this.explode = function () { if (explosion_count++ === 5) { this.alive = 0 } } this.getImg = function () { if (explosion_count > 0) { return { width: TANK_EXPLOSION_FRAME[explosion_count].dimension[0], height: TANK_EXPLOSION_FRAME[explosion_count].dimension[1], offset_x: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[0], offset_y: TANK_EXPLOSION_FRAME[explosion_count].image_coordinates[1], } } else { return { width: width, height: height, offset_x: type.image_coordinates[0], offset_y: type.image_coordinates[1], } } } this._generateId = function () { return uuidv4() } sprites[this._generateId()] = this }
定義好后,使用時(shí)只需批量生成Block 的實(shí)例,將坐標(biāo),類型等信息傳遞進(jìn)去就可以在下一幀渲染出來(lái)。
for(let i=0; i<=2; i++) { for(let j=0; j<=2; j++) { let block = new Block(j*16, 140 + i*16, DIRECTION.UP, BLOCK_TYPE.BLOCK_STONE) } }
好了我們看看最終的效果吧!
總結(jié)
ok 坦克大戰(zhàn)基本上完成了,你可以修改子彈發(fā)射速度,敵方坦克數(shù)量,障礙物也可以自己隨意畫(huà),可玩性還是挺好的,當(dāng)然還有很多細(xì)節(jié)可以完善,如 預(yù)制幾種不同的地圖,做成通關(guān)類,預(yù)制幾條生命等。如果你想試一下,可以 star 下 github 倉(cāng)庫(kù)自己來(lái)改造自己的坦克大戰(zhàn)吧。
以上就是Vue3+Canvas實(shí)現(xiàn)坦克大戰(zhàn)游戲(二)的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Canvas坦克大戰(zhàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在vue項(xiàng)目中引入scss并使用scss樣式詳解
SCSS是一種CSS預(yù)處理語(yǔ)言,定義了一種新的專門(mén)的編程語(yǔ)言,編譯后形成正常的css文件,為css增加一些編程特性,這篇文章主要給大家介紹了關(guān)于在vue項(xiàng)目中引入scss并使用scss樣式的相關(guān)資料,需要的朋友可以參考下2022-07-07使用vant-picker實(shí)現(xiàn)自定義內(nèi)容,根據(jù)內(nèi)容添加圖標(biāo)
這篇文章主要介紹了使用vant-picker實(shí)現(xiàn)自定義內(nèi)容,根據(jù)內(nèi)容添加圖標(biāo),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-12-12Vue項(xiàng)目中引入外部文件的方法(css、js、less)
本篇文章主要介紹了Vue項(xiàng)目中引入外部文件的方法(css、js、less),非常具有實(shí)用價(jià)值,需要的朋友可以參考下2017-07-07vue實(shí)現(xiàn)列表無(wú)縫滾動(dòng)
這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)列表無(wú)縫滾動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-06-06vue項(xiàng)目在安卓低版本機(jī)顯示空白的原因分析(兩種)
本文給大家?guī)?lái)vue項(xiàng)目在安卓低版本機(jī)顯示空白的原因分析,根據(jù)各自需求給大家?guī)?lái)了兩種原因分析,大家可以參考下2018-09-09手把手教你vue實(shí)現(xiàn)動(dòng)態(tài)路由
動(dòng)態(tài)路由可以根據(jù)不同用戶登錄獲取不一樣的路由層級(jí),可隨時(shí)調(diào)配路由,下面這篇文章主要給大家介紹了關(guān)于vue實(shí)現(xiàn)動(dòng)態(tài)路由的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07Vite處理html模板插件之vite-plugin-html插件使用
這篇文章主要給大家介紹了關(guān)于Vite處理html模板插件之vite-plugin-html插件使用的相關(guān)資料,Vite是一個(gè)現(xiàn)代化的前端構(gòu)建工具,而vite-plugin-html是Vite的一個(gè)插件,用于在構(gòu)建時(shí)自動(dòng)生成HTML文件,需要的朋友可以參考下2023-10-10