<option id="mwy0y"><strong id="mwy0y"></strong></option>
  • <ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <ul id="mwy0y"></ul>
  • <del id="mwy0y"><dfn id="mwy0y"></dfn></del><ul id="mwy0y"><sup id="mwy0y"></sup></ul>
  • <abbr id="mwy0y"></abbr>

    千鋒教育-做有情懷、有良心、有品質(zhì)的職業(yè)教育機(jī)構(gòu)

    400-811-9990
    手機(jī)站
    千鋒教育

    千鋒學(xué)習(xí)站 | 隨時(shí)隨地免費(fèi)學(xué)

    千鋒教育

    掃一掃進(jìn)入千鋒手機(jī)站

    領(lǐng)取全套視頻
    千鋒教育

    關(guān)注千鋒學(xué)習(xí)站小程序
    隨時(shí)隨地免費(fèi)學(xué)習(xí)課程

    上海
    • 北京
    • 鄭州
    • 武漢
    • 成都
    • 西安
    • 沈陽
    • 廣州
    • 南京
    • 深圳
    • 大連
    • 青島
    • 杭州
    • 重慶
    當(dāng)前位置:長(zhǎng)沙千鋒IT培訓(xùn)  >  技術(shù)要點(diǎn)  >  長(zhǎng)沙前端培訓(xùn):一招教你用vue3+canvas實(shí)現(xiàn)坦克大戰(zhàn)

    長(zhǎng)沙前端培訓(xùn):一招教你用vue3+canvas實(shí)現(xiàn)坦克大戰(zhàn)

    來源:千鋒教育
    發(fā)布人:千鋒長(zhǎng)沙
    時(shí)間: 2022-03-17 17:08:46

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

    QQ截圖20220317170735

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

    讓我們開始吧!

    架構(gòu)搭建

    項(xiàng)目技術(shù)選型為 vue3、vite、less、pnpm、ts,按照vue3 官網(wǎng)文檔來新建項(xiàng)目,注意:雖然我用了 vue3 實(shí)際上只是強(qiáng)行嘗鮮,主體內(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è)構(gòu)造函數(shù)

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

    畫布繪制

    canvas 圖層按照一般的游戲設(shè)計(jì)優(yōu)化理念,需要為靜態(tài)背景和動(dòng)態(tài)元素單獨(dú)用不同的 canvas 圖層表示,每次更新時(shí)只需要重新繪制動(dòng)態(tài)元素就好了,我抽象出一個(gè)渲染函數(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),可以定義一個(gè)函數(shù),當(dāng)鼠標(biāo)滑動(dòng)時(shí)候執(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

    每次重繪前需要先擦掉整個(gè)畫布

    this.clear = function clear() {

    ctx.clearRect(0, 0, dimension.x, dimension.y)

    }

    核心:繪制函數(shù)

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

    通過 rotate 旋轉(zhuǎn)元素來實(shí)現(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) // 改變起始點(diǎn)坐標(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

    }

    實(shí)現(xiàn)坦克的移動(dòng)

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

    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

    }

    然后再定義一個(gè) fire 開發(fā)函數(shù),當(dāng)開火后,會(huì)使用 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 來實(shí)現(xiàn)循環(huán)刷新畫布,通過修改各元素位置坐標(biāo)值,在下一次畫布重繪時(shí)更新視圖,這是階段交互的基本邏輯

    聲明:本站稿件版權(quán)均屬千鋒教育所有,未經(jīng)許可不得擅自轉(zhuǎn)載。

    猜你喜歡LIKE

    最新文章NEW

    相關(guān)推薦HOT

    更多>>

    快速通道 更多>>

    最新開班信息 更多>>

    網(wǎng)友熱搜 更多>>