网友真实露脸自拍10p,成人国产精品秘?久久久按摩,国产精品久久久久久无码不卡,成人免费区一区二区三区

小程序模板網(wǎng)

微信小游戲初體驗(yàn)

發(fā)布時(shí)間:2018-05-02 15:46 所屬欄目:小程序開(kāi)發(fā)教程

本文旨在通過(guò)分析官方給出的一個(gè)飛機(jī)大戰(zhàn)小游戲的源代碼來(lái)說(shuō)明如何進(jìn)行小游戲的開(kāi)發(fā)。

1.前言

前天一個(gè) 跳一跳 小游戲刷遍了朋友圈,也代表了微信小程序擁有了搭載游戲的功能(早該往這方面發(fā)展了,這才是應(yīng)該有的形態(tài)嘛)。作為一個(gè)前端er,我的大刀早已經(jīng)饑渴難耐了,趕緊去下一波最新的微信官方開(kāi)發(fā)工具,體驗(yàn)一波小游戲要如何開(kāi)發(fā)。

 

我們欣喜地看到可以直接點(diǎn)擊小游戲體驗(yàn)一下,而且官方也有一個(gè)示例源代碼,是一個(gè)簡(jiǎn)易版的飛機(jī)大戰(zhàn)的源碼,直接點(diǎn)開(kāi)模擬器就可以看效果。

 

2.源碼分析

(還是原汁原味的打飛機(jī)游戲呀!)通過(guò)閱讀這個(gè)源代碼我們便可以知道如何進(jìn)行小游戲的開(kāi)發(fā)了。廢話少說(shuō)直接進(jìn)入主題,先來(lái)分析一波源碼的整體結(jié)構(gòu)。


./js下面是官方示例中的js文件具體的作用


├── base                                   // 定義游戲開(kāi)發(fā)基礎(chǔ)類
│   ├── animatoin.js                       // 幀動(dòng)畫(huà)的簡(jiǎn)易實(shí)現(xiàn)
│   ├── pool.js                            // 對(duì)象池的簡(jiǎn)易實(shí)現(xiàn)
│   └── sprite.js                          // 游戲基本元素精靈類
├── libs
│   ├── symbol.js                          // ES6 Symbol簡(jiǎn)易兼容
│   └── weapp-adapter.js                   // 小游戲適配器
├── npc
│   └── enemy.js                           // 敵機(jī)類
├── player
│   ├── bullet.js                          // 子彈類
│   └── index.js                           // 玩家類
├── runtime
│   ├── background.js                      // 背景類
│   ├── gameinfo.js                        // 用于展示分?jǐn)?shù)和結(jié)算界面
│   └── music.js                           // 全局音效管理器
├── databus.js                             // 管控游戲狀態(tài)
└── main.js                                // 游戲入口主函數(shù)

官方文檔中提到, game.js 和 game.json 是小游戲必須要有的兩個(gè)文件

下面我會(huì)分析我認(rèn)為主要的文件與結(jié)構(gòu),不會(huì)對(duì)每一行代碼進(jìn)行解析,大家有興趣可以自行閱讀官方的源碼。每個(gè)文件后會(huì)跟隨我認(rèn)為重要的幾個(gè)小點(diǎn)。

game.js


import './js/libs/weapp-adapter'
import './js/libs/symbol'

import Main from './js/main'

new Main()
  1. 小程序啟動(dòng)會(huì)調(diào)用 game.js ,在其中導(dǎo)入了小游戲官方提供的適配器,用于注入canvas以及模擬DOM以及BOM(后續(xù)會(huì)具體說(shuō)明這個(gè)文件),可以在 https://mp.weixin.qq.com/debu...下載源代碼,修改適合自己的版本并通過(guò)webpack打包自用。當(dāng)然目前已經(jīng)足夠我們使用。
  2. 導(dǎo)入symbol的polyfill,主要用于模擬ES6類的私有變量。
  3. 導(dǎo)入Main類并實(shí)例化Main,于是順藤摸瓜我們將目光移至Main.js

Main.js


import Player     from './player/index'
import Enemy      from './npc/enemy'
import BackGround from './runtime/background'
import GameInfo   from './runtime/gameinfo'
import Music      from './runtime/music'
import DataBus    from './databus'

let ctx   = canvas.getContext('2d')
let databus = new DataBus()

/**
 * 游戲主函數(shù)
 */
export default class Main {
  constructor() {
    this.restart()
  }

  restart() {
    databus.reset()

    canvas.removeEventListener(
      'touchstart',
      this.touchHandler
    )

    this.bg       = new BackGround(ctx)
    this.player   = new Player(ctx)
    this.gameinfo = new GameInfo()
    this.music    = new Music()

    window.requestAnimationFrame(
      this.loop.bind(this),
      canvas
    )
  }

  /**
   * 隨著幀數(shù)變化的敵機(jī)生成邏輯
   * 幀數(shù)取模定義成生成的頻率
   */
  enemyGenerate() {
    if ( databus.frame % 30 === 0 ) {
      let enemy = databus.pool.getItemByClass('enemy', Enemy)
      enemy.init(6)
      databus.enemys.push(enemy)
    }
  }

  // 全局碰撞檢測(cè)
  collisionDetection() {
    let that = this

    databus.bullets.forEach((bullet) => {
      for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
        let enemy = databus.enemys[i]

        if ( !enemy.isPlaying && enemy.isCollideWith(bullet) ) {
          enemy.playAnimation()
          that.music.playExplosion()

          bullet.visible = false
          databus.score  += 1

          break
        }
      }
    })

    for ( let i = 0, il = databus.enemys.length; i < il;i++ ) {
      let enemy = databus.enemys[i]

      if ( this.player.isCollideWith(enemy) ) {
        databus.gameOver = true

        break
      }
    }
  }

  //游戲結(jié)束后的觸摸事件處理邏輯
  touchEventHandler(e) {
    e.preventDefault()

    let x = e.touches[0].clientX
    let y = e.touches[0].clientY

    let area = this.gameinfo.btnArea

    if (   x >= area.startX
        && x <= area.endX
        && y >= area.startY
        && y <= area.endY  )
      this.restart()
    }

    /**
     * canvas重繪函數(shù)
     * 每一幀重新繪制所有的需要展示的元素
     */
    render() {
    ctx.clearRect(0, 0, canvas.width, canvas.height)

    this.bg.render(ctx)

    databus.bullets
           .concat(databus.enemys)
           .forEach((item) => {
              item.drawToCanvas(ctx)
            })

    this.player.drawToCanvas(ctx)

    databus.animations.forEach((ani) => {
      if ( ani.isPlaying ) {
        ani.aniRender(ctx)
      }
    })

    this.gameinfo.renderGameScore(ctx, databus.score)
  }

  // 游戲邏輯更新主函數(shù)
  update() {
    this.bg.update()

    databus.bullets
           .concat(databus.enemys)
           .forEach((item) => {
              item.update()
            })

    this.enemyGenerate()

    this.collisionDetection()
  }

  // 實(shí)現(xiàn)游戲幀循環(huán)
  loop() {
    databus.frame++

    this.update()
    this.render()

    if ( databus.frame % 20 === 0 ) {
      this.player.shoot()
      this.music.playShoot()
    }

    // 游戲結(jié)束停止幀循環(huán)
    if ( databus.gameOver ) {
      this.gameinfo.renderGameOver(ctx, databus.score)

      this.touchHandler = this.touchEventHandler.bind(this)
      canvas.addEventListener('touchstart', this.touchHandler)

      return
    }

    window.requestAnimationFrame(
      this.loop.bind(this),
      canvas
    )
  }
}
  1. 導(dǎo)入了創(chuàng)建游戲需要的我放飛機(jī),敵方飛機(jī),背景,游戲信息,音樂(lè),游戲全局?jǐn)?shù)據(jù)類,并獲取了canvas的上下文(看到這是不是有一個(gè)疑惑,canvas到底是從哪里定義?先帶著這個(gè)問(wèn)題最后再說(shuō)),創(chuàng)建了一個(gè)全局?jǐn)?shù)據(jù)實(shí)例(后面會(huì)提到)。
  2. 創(chuàng)建Main的實(shí)例自然會(huì)調(diào)用構(gòu)造方法,在構(gòu)造方法中調(diào)用restart函數(shù),進(jìn)行了游戲的初始化并進(jìn)行循環(huán)刷幀( requestAnimationFrame 看起來(lái)是不是很親切)。
  3. loop函數(shù)中我們可以看到主要調(diào)用了update, render方法,并設(shè)置了player發(fā)射子彈的時(shí)間,對(duì)游戲是否結(jié)束進(jìn)行判斷,最后接著刷幀。
  4. update方法會(huì)調(diào)用各個(gè)場(chǎng)景內(nèi)對(duì)象的update方法來(lái)更新他們的位置以及其他信息。
  5. render方法會(huì)調(diào)用各個(gè)場(chǎng)景內(nèi)對(duì)象的render方法來(lái)將他們繪制到canvas中。

Main內(nèi)結(jié)構(gòu)清晰,主要理解整個(gè)流程就是調(diào)用 requestAnimationFrame 來(lái)不停地刷幀更新位置信息推動(dòng)所有對(duì)象運(yùn)動(dòng),每個(gè)對(duì)象在每一幀都有新的位置,連起來(lái)就是動(dòng)畫(huà)了。分清位置的更新與對(duì)象的繪制是關(guān)鍵。

databus.js


import Pool from './base/pool'

let instance

/**
 * 全局狀態(tài)管理器
 */
export default class DataBus {
  constructor() {
    if ( instance )
      return instance

    instance = this

    this.pool = new Pool()

    this.reset()
  }

  reset() {
    this.frame      = 0
    this.score      = 0
    this.bullets    = []
    this.enemys     = []
    this.animations = []
    this.gameOver   = false
  }

  /**
   * 回收敵人,進(jìn)入對(duì)象池
   * 此后不進(jìn)入幀循環(huán)
   */
  removeEnemey(enemy) {
    let temp = this.enemys.shift()

    temp.visible = false

    this.pool.recover('enemy', enemy)
  }

  /**
   * 回收子彈,進(jìn)入對(duì)象池
   * 此后不進(jìn)入幀循環(huán)
   */
  removeBullets(bullet) {
    let temp = this.bullets.shift()

    temp.visible = false

    this.pool.recover('bullet', bullet)
  }
}
  1. 我們可以看出,databus是一個(gè)單例對(duì)象,不論在其他代碼中new多少次,都是返回的同一個(gè)實(shí)例,符合我們的期望。
  2. reset定義了所需要的數(shù)據(jù)源并初始化
  3. 通過(guò)一個(gè)對(duì)象池的概念,控制當(dāng)前頁(yè)面對(duì)象的數(shù)量,避免使用js原有的垃圾處理機(jī)制,而是通過(guò)對(duì)象池來(lái)復(fù)用已經(jīng)創(chuàng)建的對(duì)象,算是一個(gè)性能優(yōu)化。
  4. frame屬性主要是用來(lái)刷幀的時(shí)候用來(lái)控制子彈的發(fā)射與敵機(jī)的出現(xiàn)時(shí)間。

sprite.js


/**
 * 游戲基礎(chǔ)的精靈類
 */
export default class Sprite {
  constructor(imgSrc = '', width=  0, height = 0, x = 0, y = 0) {
    this.img     = new Image()
    this.img.src = imgSrc

    this.width  = width
    this.height = height

    this.x = x
    this.y = y

    this.visible = true
  }

  /**
   * 將精靈圖繪制在canvas上
   */
  drawToCanvas(ctx) {
    if ( !this.visible )
      return

    ctx.drawImage(
      this.img,
      this.x,
      this.y,
      this.width,
      this.height
    )
  }

  /**
   * 簡(jiǎn)單的碰撞檢測(cè)定義:
   * 另一個(gè)精靈的中心點(diǎn)處于本精靈所在的矩形內(nèi)即可
   * @param{Sprite} sp: Sptite的實(shí)例
   */
  isCollideWith(sp) {
    let spX = sp.x + sp.width / 2
    let spY = sp.y + sp.height / 2

    if ( !this.visible || !sp.visible )
      return false

    return !!(   spX >= this.x
              && spX <= this.x + this.width
              && spY >= this.y
              && spY <= this.y + this.height  )
  }
}
  1. 作為所有場(chǎng)景對(duì)象的基類,定義了所有精靈對(duì)象基本有的信息(位置,圖片,是否可見(jiàn))
  2. 定義了兩種能力,檢測(cè)碰撞與將自己繪制在canvas上

可以看出畫(huà)圖主要是用的canvas里的drawImage方法,也是我們自行開(kāi)發(fā)小游戲以后會(huì)用到的方法。包括background,player等類都會(huì)繼承自精靈類,并且會(huì)添加自己的update方法來(lái)暴露更新自己位置信息的接口。enermy還會(huì)包裝一層爆炸動(dòng)畫(huà)的封裝,思路大同小異,就不在多贅述了。

3.結(jié)論

  1. 我們發(fā)現(xiàn)小游戲的開(kāi)發(fā)與我們使用canvas進(jìn)行h5小游戲的開(kāi)發(fā)并沒(méi)有什么太大的區(qū)別,無(wú)論從繪圖的api還是事件的api都十分相似,還可以用window對(duì)象,這主要?dú)w功于官方提供的 webapp-adapter.js ,該js會(huì)注入window對(duì)象并提供相應(yīng)的canvas全局變量,也是文章中提到為什么在main.js里找不到canvas變量在哪里定義的原因了。所以我們可以開(kāi)開(kāi)心心地使用canvas來(lái)開(kāi)發(fā)小游戲了!!!
  2. 官方還說(shuō)了一句,可以不引入 webapp-adapter.js 來(lái)開(kāi)發(fā)小游戲,( https://mp.weixin.qq.com/debu... )這是小游戲的api文檔(當(dāng)時(shí)找了很久)適配器的源碼寫(xiě)得也很清晰,可以一讀來(lái)了解一些,其中也有很多官方寫(xiě)的TODO的事情,還并不十分完善,如果想要快速移植已有的h5游戲代碼使用適配器是很有效的。如果想直接開(kāi)發(fā)小游戲根據(jù)api文檔直接來(lái)開(kāi)發(fā)也是很有效的方法,畢竟引入一層適配器還是會(huì)有一定的開(kāi)銷。

tips: 讀一讀適配器源碼也有利于了解如何開(kāi)發(fā)小程序(例如事件綁定之類的操作)

4.結(jié)語(yǔ)

小程序終于可以來(lái)做小游戲了,感覺(jué)還是休閑類的游戲會(huì)占主導(dǎo)地位,前端大大可以迎接新的戰(zhàn)場(chǎng)啦哈哈哈~~~(接下來(lái)會(huì)去掉適配器用原生api改寫(xiě)官方demo)



易優(yōu)小程序(企業(yè)版)+靈活api+前后代碼開(kāi)源 碼云倉(cāng)庫(kù):starfork
本文地址:http://www.xiuhaier.com/wxmini/doc/course/24176.html 復(fù)制鏈接 如需定制請(qǐng)聯(lián)系易優(yōu)客服咨詢:800182392 點(diǎn)擊咨詢
QQ在線咨詢
AI智能客服 ×