欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3+Canvas實現(xiàn)坦克大戰(zhàn)游戲(一)

 更新時間:2022年03月18日 09:53:53   作者:Ethan_Zhou  
這篇文章將利用Vue3和Canvas編寫一個童年經(jīng)典游戲—坦克大戰(zhàn),文中的示例代碼講解詳細(xì),感興趣的小伙伴快來跟隨小編一起學(xué)習(xí)一下吧

前言

記得幾年前剛做前端開發(fā)的時候,跟著師傅用純 es5 實現(xiàn)了這款坦克大戰(zhàn),可以說我入行前端是從 javaScript 小游戲開始的,時間已匆匆過去了數(shù)年,前端發(fā)展日新月異,各種新框架、新概念層出不窮,很容易就迷失在對各種新技術(shù)的盲目學(xué)習(xí)和應(yīng)用中,真正的編程是什么呢?值得思考的問題。

我準(zhǔn)備用 vue3 重新實現(xiàn)一下這款游戲,順便回顧和梳理下自己的知識體系。

W/上 S/下 A/左 D/右 F/射擊

讓我們開始吧!

架構(gòu)搭建

項目技術(shù)選型為 vue3、vite、less、pnpm、ts,按照vue3 官網(wǎng)文檔來新建項目,注意:雖然我用了 vue3 實際上只是強行嘗鮮,主體內(nèi)容都是 js 用到的框架特性有限。

$ pnpm create vite <project-name> -- --template vue
$ cd <project-name>
$ pnpm install
$ pnpm add -D less
$ pnpm dev

Canvas 構(gòu)造函數(shù)

游戲的核心為 canvas 畫布和坦克元素,我們定義兩個構(gòu)造函數(shù)

canvas 構(gòu)造函數(shù)的定義參數(shù)、方法:dom、dimension 尺寸、renderTo 渲染函數(shù)、drawText 文本繪制函數(shù)、drawImageSlice 圖片繪制函數(shù)

畫布繪制

canvas 圖層按照一般的游戲設(shè)計優(yōu)化理念,需要為靜態(tài)背景和動態(tài)元素單獨用不同的 canvas 圖層表示,每次更新時只需要重新繪制動態(tài)元素就好了,我抽象出一個渲染函數(shù)

// 渲染
this.renderTo = function renderTo(container_id) {
  if (!is_rendered) {
    let container = document.getElementById(container_id)
    //畫布起始坐標(biāo)
    dom = document.createElement('canvas') // 創(chuàng)造canvas畫布
    dom.setAttribute('class', 'canvas')
    ctx = dom.getContext('2d')
    dom.setAttribute('width', container.clientWidth)
    dom.setAttribute('height', container.clientHeight)
    // 畫布尺寸
    dimension = {
      x: container.clientWidth,
      y: container.clientHeight,
    }
    container.insertBefore(dom, container.firstChild) // 插入cantainer容器
  }
}

文本渲染

想要知道畫布中的具體位置坐標(biāo),可以定義一個函數(shù),當(dāng)鼠標(biāo)滑動時候執(zhí)行來將當(dāng)前位置坐標(biāo)繪制出來

this.drawText = function drawText(text, offset_left, offset_top, font) {
  ctx.font = font || '25px Calibri'
  ctx.fillStyle = '#fff'
  ctx.fillText(text, offset_left, offset_top)
}

畫布重繪前的 clear

每次重繪前需要先擦掉整個畫布

this.clear = function clear() {
  ctx.clearRect(0, 0, dimension.x, dimension.y)
}

核心:繪制函數(shù)

坦克、子彈、建筑等元素等繪制都是通過這個函數(shù)來完成的,實現(xiàn)遠(yuǎn)離是利用來雪碧圖,通過坐標(biāo)抓取特定位置的圖片元素來獲取各種不同坦克等元素的UI;

通過 rotate 旋轉(zhuǎn)元素來實現(xiàn)坦克的轉(zhuǎn)向;

this.drawImageSlice = function drawImage(img_ele, sx, sy, sWidth, sHeight, x, y, rotatation) {
  ctx.save()
  ctx.translate((2 * x + sWidth) / 2, (2 * y + sHeight) / 2) // 改變起始點坐標(biāo)
  ctx.rotate((Math.PI / 180) * rotatation) // 旋轉(zhuǎn)
  x = x || 0
  y = y || 0
  ctx.drawImage(img_ele, sx, sy, sWidth, sHeight, -sWidth / 2, -sHeight / 2, sWidth, sHeight)
  ctx.restore() // 復(fù)原
}

BattleCity 構(gòu)造函數(shù)

BattleCity 構(gòu)造函數(shù)定義坦克的各種配置信息,和方法函數(shù)

let TankConfig = function (cfg) {
  this.explosion_count = cfg.explosion_count
  this.width = cfg.type.dimension[0]
  this.height = cfg.type.dimension[1]
  this.missle_type = cfg.missle_type || MISSILE_TYPE.NORMAL
  this.x = cfg.x || 0
  this.y = cfg.y || 0
  this.direction = cfg.direction || DIRECTION.UP
  this.is_player = cfg.is_player || 0
  this.moving = cfg.moving || 0
  this.alive = cfg.alive || 1
  this.border_x = cfg.border_x || 0
  this.border_y = cfg.border_y || 0
  this.speed = cfg.speed || TANK_SPEED
  this.direction = cfg.direction || DIRECTION.UP
  this.type = cfg.type || TANK_TYPE.PLAYER0
}

實現(xiàn)坦克的移動

用鍵盤的 W、S、A、D、來表示上下左右方向鍵,按下鍵盤則會觸發(fā)對應(yīng)坦克實例的 move 函數(shù),用于計算移動后的位置坐標(biāo)信息,注意:對邊界條件的判斷,不可使其超出戰(zhàn)場邊界。

CanvasSprite.prototype.move = 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 <= 5) {
            if (!this.out_of_border_die) {
              this.y = 0
            } else {
              // this.alive = 0;
              this.explode()
              document.getElementById('steelhit').play()
            }
          }
        }
        break
      case DIRECTION.DOWN:
        if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {
          this.y += this.speed
          if (this.y + this.height >= this.border_y - 10) {
            if (!this.out_of_border_die) {
              this.y = this.border_y - this.height
            } else {
              // this.alive = 0;
              this.explode()
              document.getElementById('steelhit').play()
            }
          }
        }
        break
      case DIRECTION.LEFT:
        if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {
          this.x -= this.speed
          if (this.x <= 5) {
            if (!this.out_of_border_die) {
              this.x = 0
            } else {
              // this.alive = 0;
              this.explode()
              document.getElementById('steelhit').play()
            }
          }
        }
        break
      case DIRECTION.RIGHT:
        if ((obstacle_sprites && !this.checkRangeOverlap(obstacle_sprites)) || !obstacle_sprites) {
          this.x += this.speed
          if (this.x + this.width >= this.border_x - 10) {
            if (!this.out_of_border_die) {
              this.x = this.border_x - this.width
            } else {
              // this.alive = 0;
              this.explode()
              document.getElementById('steelhit').play()
            }
          }
        }
        break
    }
  }

坦克發(fā)射子彈的邏輯

首先需要定義子彈的配置信息以及構(gòu)造函數(shù);

let MissileConfig = function (cfg) {
  this.x = cfg.x
  this.y = cfg.y
  this.type = cfg.type || MISSILE_TYPE.NORMAL
  this.width = cfg.width || this.type.dimension[0]
  this.height = cfg.height || this.type.dimension[1]
  this.direction = cfg.direction || DIRECTION.UP
  this.is_from_player = cfg.is_from_player
  this.out_of_border_die = cfg.out_of_border_die || 1 // 判斷邊界類型
  this.border_x = cfg.border_x || 0
  this.border_y = cfg.border_y || 0
  this.speed = cfg.speed || TANK_SPEED
  this.alive = cfg.alive || 1
}
    var Missile = function (MissileConfig) {
      var x = MissileConfig.x
      var y = MissileConfig.y
      var width = MissileConfig.width
      var height = MissileConfig.width
      var direction = MissileConfig.direction
      this.type = MissileConfig.type
      this.is_from_player = MissileConfig.is_from_player || 0
      var explosion_count = 0
      CanvasSprite.apply(this, [
        {
          alive: 1,
          out_of_border_die: 1,
          border_y: HEIGHT,
          border_x: WIDTH,
          speed: MISSILE_SPEED,
          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: this.type.image_coordinates[0],
            offset_y: this.type.image_coordinates[1],
          }
        }
      }
      this.getHeadCoordinates = function () {
        var h_x, h_y
        switch (this.direction) {
          case DIRECTION.UP:
            h_x = this.x + this.width / 2 - this.type.dimension[0] / 2
            h_y = this.y - this.type.dimension[1] / 2
            break
          case DIRECTION.DOWN:
            h_x = this.x + this.width / 2 - this.type.dimension[0] / 2
            h_y = this.y + this.height - this.type.dimension[1] / 2
            break
          case DIRECTION.LEFT:
            h_x = this.x
            h_y = this.y + this.width / 2 - this.type.dimension[0] / 2
            break
          case DIRECTION.RIGHT:
            h_x = this.x + this.height
            h_y = this.y + this.width / 2 - this.type.dimension[0] / 2
        }
        console.log({
          x: h_x,
          y: h_y,
        })
        return {
          x: h_x,
          y: h_y,
        }
      }
      this._generateId = function () {
        return uuidv4()
      }
      sprites[this._generateId()] = this
    }

然后再定義一個 fire 開發(fā)函數(shù),當(dāng)開火后,會使用 window.requestAnimationFrame() 來達(dá)到循環(huán)的效果,每次重繪最新的位置信息

this.fire = function (boolean_type) {
    if (!this.missle || !this.missle.alive) {
      var coor = this.getCannonCoordinates()
      this.missle = new Missile(
        new MissileConfig({
          x: coor.x,
          y: coor.y,
          direction: this.direction,
          type: this.miss_type,
          is_from_player: boolean_type,
        })
      )
      if (boolean_type) {
        document.getElementById('shoot').play()
      }
    }
  }

總結(jié)

利用 requestAnimationFrame 來實現(xiàn)循環(huán)刷新畫布,通過修改各元素位置坐標(biāo)值,在下一次畫布重繪時更新視圖,這是階段交互的基本邏輯;

到這里已經(jīng)實現(xiàn)了坦克移動和發(fā)射子彈的效果。

以上就是Vue3+Canvas實現(xiàn)坦克大戰(zhàn)游戲(一)的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Canvas坦克大戰(zhàn)的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue加載完成后的回調(diào)函數(shù)方法

    vue加載完成后的回調(diào)函數(shù)方法

    今天小編就為大家分享一篇vue加載完成后的回調(diào)函數(shù)方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-09-09
  • Vue頁面內(nèi)公共的多類型附件圖片上傳區(qū)域并適用折疊面板(示例代碼)

    Vue頁面內(nèi)公共的多類型附件圖片上傳區(qū)域并適用折疊面板(示例代碼)

    本文中實現(xiàn)的附件上傳區(qū)域支持超多類型附件分類型上傳,并且可根據(jù)特定條件具體展示某些類型的附件上傳,本文給大家分享Vue頁面內(nèi)公共的多類型附件圖片上傳區(qū)域并適用折疊面板的示例代碼,需要的朋友參考下吧
    2021-12-12
  • 基于vue3+antDesign2+echarts?實現(xiàn)雷達(dá)圖效果

    基于vue3+antDesign2+echarts?實現(xiàn)雷達(dá)圖效果

    這篇文章主要介紹了基于vue3+antDesign2+echarts?實現(xiàn)雷達(dá)圖,本文通過實例代碼圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-08-08
  • vue.js綁定class和style樣式(6)

    vue.js綁定class和style樣式(6)

    這篇文章我們將一起學(xué)習(xí)vue.js實現(xiàn)綁定class和style樣式,感興趣的小伙伴們可以參考一下
    2016-12-12
  • 詳解Unity webgl 嵌入Vue實現(xiàn)過程

    詳解Unity webgl 嵌入Vue實現(xiàn)過程

    Unity webgl嵌入到前端網(wǎng)頁中,前端通過調(diào)用Unity webgl內(nèi)方法實現(xiàn)需要展示的功能,前端點擊Unity webgl內(nèi)的交互點,Unity webgl返回給前端一些需要的數(shù)據(jù),這篇文章主要介紹了Unity webgl 嵌入Vue實現(xiàn)過程,需要的朋友可以參考下
    2024-01-01
  • Vue+Element ui 根據(jù)后臺返回數(shù)據(jù)設(shè)置動態(tài)表頭操作

    Vue+Element ui 根據(jù)后臺返回數(shù)據(jù)設(shè)置動態(tài)表頭操作

    這篇文章主要介紹了Vue+Element ui 根據(jù)后臺返回數(shù)據(jù)設(shè)置動態(tài)表頭操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • Vue項目中封裝組件的簡單步驟記錄

    Vue項目中封裝組件的簡單步驟記錄

    眾所周知組件(component)是vue.js最強大的功能之一,它可以實現(xiàn)功能的復(fù)用,以及對其他邏輯的解耦,下面這篇文章主要給大家介紹了關(guān)于Vue項目中封裝組件的相關(guān)資料,需要的朋友可以參考下
    2021-09-09
  • vue 虛擬DOM快速入門

    vue 虛擬DOM快速入門

    這篇文章主要介紹了vue 虛擬DOM的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用vue框架,感興趣的朋友可以了解下
    2021-04-04
  • vue-cli-service和webpack-dev-server的區(qū)別及說明

    vue-cli-service和webpack-dev-server的區(qū)別及說明

    這篇文章主要介紹了vue-cli-service和webpack-dev-server的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue中v-model對select的綁定操作

    vue中v-model對select的綁定操作

    這篇文章主要介紹了vue中v-model對select的綁定操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08

最新評論