1. 程式人生 > >原生JS實現的h5小遊戲-植物大戰僵屍

原生JS實現的h5小遊戲-植物大戰僵屍

完成後 資源 life css lan posit 獲得 抽象 dom

代碼地址如下:
http://www.demodashi.com/demo/12755.html

項目介紹

本項目是利用原生js實現的h5小遊戲-植物大戰僵屍,主要結合了一下自己對於h5小遊戲的理解,結合面向對象的編程思想進行開發,在實現時使用了部分es6語法,對於es6語法不太熟悉的小夥伴可以先查閱相關資料了解一下。

如有需要,可根據自己的需求修改源碼樣式、源碼配置屬性代碼,實現個性化定制。
以下為文件目錄結構示意圖,核心代碼在js文件夾下的四個common.jsmain.jsgame.jsscene.jsjs文件中

技術分享圖片

源碼:https://github.com/yangyunhe369/h5-game-plantsVSzombies

演示截圖

技術分享圖片

demo:https://yangyunhe369.github.io/jQuery-Yys-Slider

實現功能

  • 繪制遊戲場景:背景、陽光計分板、植物卡片(用於放置植物)、植物(6種)、僵屍(1種)
  • 植物和僵屍的攻擊判定、死亡判定
  • 角色動畫由一幀一幀圖片不停切換繪制實現,可繪制流暢動畫效果
  • 角色動畫根據角色狀態自動切換,植物動畫包括(普通形態、攻擊形態),僵屍動畫包括(普通形態、移動形態、攻擊形態、瀕死形態、死亡形態)
  • 陽光自動生成、植物放置需消耗陽光,僵屍隨機生成
  • 遊戲包含僵屍、植物獨立勝利條件判定
  • 遊戲狀態:Loading、遊戲運行、遊戲暫停、遊戲結束(玩家勝利)、遊戲結束(僵屍勝利)

目錄結構

.
├─ index.html                   // 首頁html
│  
├─ css                          // css樣式資源文件
├─ images                       // 圖片資源文件  
└─ js
   ├─ common.js                 // 公共方法
   ├─ scene.js                  // 遊戲場景相關類
   ├─ game.js                   // 遊戲主要運行邏輯
   └─ main.js                   // 遊戲運行主函數

核心代碼

遊戲引擎

遊戲核心代碼是基於ES6的class的方式抽象了的遊戲相關函數

class Game {
  constructor () {
      ...
      state: 0,                                                     // 遊戲狀態值,初始默認為 0
      state_LOADING: 0,                                             // 準備階段
      state_START: 1,                                               // 遊戲開始
      state_RUNNING: 2,                                             // 遊戲運行
      state_STOP: 3,                                                // 遊戲暫停
      state_PLANTWON: 4,                                            // 遊戲結束,玩家勝利
      state_ZOMBIEWON: 5,                                           // 遊戲結束,僵屍勝利
      canvas: document.getElementById("canvas"),                    // canvas元素
      context: document.getElementById("canvas").getContext("2d"),  // canvas畫布
      timer: null,                                                  // 輪詢定時器
      fps: window._main.fps,                                        // 動畫幀數
  }
  init () { // 初始化函數
    let g = this
    ...
    // 設置輪詢定時器
    g.timer = setInterval(function () {
      // 根據遊戲狀態,在canvas中繪制不同遊戲場景
    }, 1000/g.fps)
    ...
  }
}

其實核心邏輯很簡單,就是定義一個遊戲引擎主函數,生成一個定時器以每秒60幀的頻率不停在canvas畫布上繪制遊戲場景相關元素,然後在定時器函數中根據當前遊戲狀態(遊戲準備遊戲開始遊戲運行遊戲暫停遊戲結束)來繪制對應遊戲場景。

loading遊戲狀態:遊戲引擎繪制了頁面載入圖片,並添加了一個開始遊戲按鈕

start遊戲狀態:遊戲開始讀條倒計時,提醒用戶遊戲即將開始

running遊戲狀態:繪制遊戲運行時所需所有遊戲場景素材

stop遊戲狀態:遊戲進入暫停階段,遊戲中如生成陽關、僵屍的定時器都將清除,角色動畫處於靜止狀態

gameover遊戲狀態:分為玩家獲得勝利以及僵屍獲得勝利兩種情況,並分別繪制不同遊戲結算畫面

遊戲場景

在這裏我將遊戲中所有可控制的元素都歸於遊戲場景中,並且將這些元素都抽象為類,方便管理,這裏包括:植物類僵屍類陽光計分板類植物卡片類動畫類子彈類

遊戲場景中最核心的兩個類為植物類、僵屍類,不過在這兩個核心類中都會用到動畫類,這裏我先介紹一下。

動畫類(Animation)

class Animation{
  constructor (role, action, fps) {
    let a = {
      type: role.type,                                   // 動畫類型(植物、僵屍等等)
      section: role.section,                             // 植物或者僵屍類別
      action: action,                                    // 根據傳入動作生成不同動畫對象數組
      images: [],                                        // 當前引入動畫圖片對象數組
      img: null,                                         // 當前顯示動畫圖片
      imgIdx: 0,                                         // 當前角色圖片序列號
      count: 0,                                          // 計數器,控制動畫運行
      fps: fps,                                          // 角色動畫運行速度系數,值越小,速度越快
    }
    Object.assign(this, a)
  }
}

這裏用到的images,就是通過new Image()的方式生成並添加到images中組成的動畫序列:
技術分享圖片

其中type和section用於判斷當前需要加載植物或僵屍、哪一個動作所對應動畫序列,count和fps用於控制當前動畫的播放速度,而img用於表示當前所展示的圖片對象,即images[imgIdx],其關系類似於以下代碼:

// 在全局定時器中每1/60秒計算一次
// 獲取動畫序列長度
let animateLen = images.length
// 計數器自增
count++
// 設置當前顯示動畫序列號
imgIdx = Math.floor(count / fps)
// 當一整套動畫完成後重置動畫計數器
imgIdx === animateLen - 1 ? count = 0 : count = count
// 設置當前顯示動畫圖片
img = images[imgIdx]

角色類(Role)

class Role{
  constructor (obj) {
    let r = {
      id: Math.random().toFixed(6) * Math.pow(10, 6),      // 隨機生成 id 值,用於設置當前角色 ID,區分不同角色
      type: obj.type,                                      // 角色類型(植物或僵屍)
      section: obj.section,                                // 角色類別(豌豆射手、雙發射手...)
      x: obj.x,                                            // x軸坐標
      y: obj.y,                                            // y軸坐標
      w: 0,                                                // 角色圖片寬度
      h: 0,                                                // 角色圖片高度
      row: obj.row,                                        // 角色初始化行坐標
      col: obj.col,                                        // 角色初始化列坐標
      isAnimeLenMax: false,                                // 是否處於動畫最後一幀,用於判斷動畫是否執行完一輪
      isDel: false,                                        // 判斷是否死亡並移除當前角色
      isHurt: false,                                       // 判斷是否受傷
    }
    Object.assign(this, r)
  }
}

這裏的角色類主要用於抽象植物類和僵屍類的公共屬性,基本屬性包括typesectionxywhrowcol,其中rowcol屬性用於控制角色在草坪上繪制的橫縱坐標(即x軸和y軸方向位於第幾個方格),section屬性用於區分當前角色到底是哪一種,如豌豆射手、雙發射手、加特林射手、普通僵屍。

植物類(Plant)

class Plant{
  constructor (obj) {
    let p = {
      life: 3,                                             // 角色血量
      idle: null,                                          // 站立動畫對象
      attack: null,                                        // 攻擊動畫對象
      bullets: [],                                         // 子彈對象數組
      state: 1,                                            // 保存當前狀態值,默認為1      
      state_IDLE: 1,                                       // 站立不動狀態
      state_ATTACK: 2,                                     // 攻擊狀態
    }
    Object.assign(this, p)
  }
  // 繪制方法
  draw(cxt) {
      // 根據當前植物的狀態,分別繪制正常狀態動畫,以及受傷時的半透明狀態動畫
      let self = this
      cxt.drawImage(self[stateName].img, self.x, self.y)
  }
  // 更新當前植物狀態
  update() {
      // 通過動畫計數器計算出當前植物顯示動畫序列的圖片
  }
  // 判斷當前植物是否可進入攻擊狀態
  canAttack() {
      // 通過輪詢僵屍對象數組,判斷處於當前植物同行的僵屍,且進入草坪內時,即開始攻擊僵屍
      // 目前僅有三種射手類植物可使用子彈攻擊,櫻桃炸彈屬於範圍傷害類植物(判斷範圍為其周圍八個格子內)
      // 攻擊成功時,減少對應僵屍血量,並在僵屍血量到達特殊值時,切換其動畫(如瀕死狀態,死亡狀態),在血量為 0 時,從僵屍對象數組中移除當前僵屍
  }
  // 射擊方法
  shoot() {
      // 當前植物攻擊時, bullets 數組添加子彈對象
      let self = this
      self.bullets[self.bullets.length] = Bullet.new(self)
  }
}

植物類的私有屬性包括idelattackbulletsstate,其中idelattack為動畫對象,相信看過上面關於動畫類介紹的小夥伴應該能理解其作用,bullets即用於保存當前植物的所有子彈對象(同動畫類,子彈類也有屬性、方法的配置,這裏就不詳細敘述了)。

關於植物的狀態控制屬性,如isHurt屬性會在植物受傷時,切換為true,並由此給動畫添加一個透明度,模擬受傷效果;isDel屬性會在植物血量降為0時,將植物從植物對象數組中移除,即不再繪制當前植物;state屬性用於植物在兩種形態中進行切換,即普通形態攻擊形態,當前狀態值為哪種形態,即播放對應形態動畫,對應關系如下:

state === state_IDLE =>      // 播放植物普通形態動畫 idle
state === state_ATTACK =>    // 播放植物攻擊形態動畫 attack

攻擊形態的切換,這裏就涉及需要循環當前植物對象與所有的僵屍對象所組成的數組,判斷是否有僵屍處於當前植物對象的射程內(即處於同一行草坪,且進行屏幕顯示範圍)。

這裏主要介紹了植物類的相關屬性,其方法包括初始化植物對象、植物繪制、植物射擊、更新植物狀態、檢測植物是否可攻擊僵屍...

僵屍類(Zombie)

class Zombie{
  constructor (obj) {
    let z = {
      life: 10,                                            // 角色血量
      idle: null,                                          // 站立動畫對象
      run: null,                                           // 奔跑動畫對象
      attack: null,                                        // 攻擊動畫對象
      dying: null,                                         // 瀕臨死亡動畫對象
      die: null,                                           // 死亡動畫對象
      state: 1,                                            // 保存當前狀態值,默認為1
      state_IDLE: 1,                                       // 站立不動狀態
      state_RUN: 2,                                        // 奔跑狀態
      state_ATTACK: 3,                                     // 攻擊狀態
      state_DYING: 4,                                      // 瀕臨死亡狀態
      state_DIE: 5,                                        // 死亡狀態
      canMove: true,                                       // 判斷當前角色是否可移動
      attackPlantID: 0,                                    // 當前攻擊植物對象 ID
      speed: 3,                                            // 移動速度
    }
    Object.assign(this, z)
  }
  // 繪制方法
  draw() {
      // 根據當前僵屍的狀態,分別繪制正常狀態動畫,以及受傷時的半透明狀態動畫
      let self = this
      cxt.drawImage(self[stateName].img, self.x, self.y)
  }
  // 更新當前僵屍狀態
  update() {
      // 動畫計數器計算出當前植物顯示動畫序列的圖片
  }
  // 判斷當前僵屍是否可進入攻擊狀態
  canAttack() {
      // 通過輪詢植物對象數組,判斷處於當前僵屍同行的植物,且進入其攻擊範圍內時,即開始攻擊植物
      // 攻擊成功時,當前僵屍 canMove 屬性將為 false ,記錄其 attackPlantID ,即所攻擊植物 id 值,並減少對應植物血量;
      // 在植物血量為 0 時,切換其動畫(進入死亡狀態),並從植物對象數組中移除該植物,同時
      // 將所有攻擊該植物的僵屍的狀態切換為移動狀態, canMove 屬性值改為 true
  }
}

這裏可以看到僵屍類的很多屬性與植物類類似,就不過多敘述了,由於目前只開發了一種僵屍,所以section屬性是固定值。

關於僵屍的動畫對象可能會比植物復雜一點,包含idlerunattackdyingdie五種形態的動畫序列,其中dyingdie對應僵屍較低血量(瀕死狀態)和血量為0死亡狀態)時所播放的動畫。

在僵屍的控制屬性上,與植物同理,這裏僵屍的五種動畫對象也對應五種狀態值,並隨狀態值的切換而切換。

這裏主要介紹了僵屍類的相關屬性,其方法包括初始化實例化僵屍對象繪制僵屍僵屍攻擊更新僵屍狀態檢測僵屍是否可攻擊植物...

遊戲主函數

在遊戲主函數中,將會把之前所有用到的遊戲相關類,進行實例化,並保存在Main類中,在這裏調用start遊戲啟動函數,將會開啟遊戲引擎,開始繪制遊戲場景,所以遊戲啟動函數會在頁面加載完成後立即調用。

class Main {
  constructor () {
    let m = {
      allSunVal: 200,                           // 陽光總數量
      loading: null,                            // loading 動畫對象
      sunnum: null,                             // 陽光實例對象
      cars: [],                                 // 實例化除草車對象數組
      cars_info: {                              // 初始化參數
        x: 170,                                 // x 軸坐標
        y: 102,                                 // y 軸坐標
        position: [
          {row: 1},
          {row: 2},
          {row: 3},
          {row: 4},
          {row: 5},
        ],
      },
      cards: [],                                // 實例化植物卡片對象數組
      cards_info: {                             // 初始化參數
        x: 0,
        y: 0,
        position: [
          {name: ‘peashooter‘, row: 1, sun_val: 100},
          {name: ‘repeater‘, row: 2, sun_val: 150},
          {name: ‘gatlingpea‘, row: 3, sun_val: 200},
        ]
      },
      plants: [],                               // 實例化植物對象數組
      zombies: [],                              // 實例化僵屍對象數組
      plants_info: {                            // 初始化參數
        type: ‘plant‘,                          // 角色類型
        x: 250,                                 // 初始 x 軸坐標,遞增量 80
        y: 92,                                  // 初始 y 軸坐標,遞增量 100
        len: 0,
        position: []                            // section:植物類別,row:橫行坐標(最小值為 5),col:豎列坐標(最大值為 9)
      },
      zombies_info: {                           // 初始化參數
        type: ‘zombie‘,                         // 角色類型
        x: 250,                                 // x軸坐標
        y: 15,                                  // y軸坐標
        position: []                            // section:僵屍類別,row:橫行坐標(最小值為 9),col:豎列坐標(最大值為 13)
      },
      zombies_idx: 0,                            // 隨機生成僵屍 idx
      zombies_row: 0,                            // 隨機生成僵屍的行坐標
      zombies_iMax: 50,                          // 隨機生成僵屍數量上限
      sunTimer: null,                            // 全局定時器,用於控制全局定時生成陽光
      sunTimer_difference: 20,                   // 定時生成陽光時間差值(單位:秒)
      zombieTimer: null,                         // 全局定時器,用於控制全局定時生成僵屍
      zombieTimer_difference: 12,                // 定時生成僵屍時間差值(單位:秒)
      game: null,                                // 遊戲引擎對象
      fps: 60,
    }
    Object.assign(this, m)
  }
  // 此處省略部分函數介紹
  ...
  // 遊戲啟動函數
  start() {
     // 實例化遊戲場景篇中的所有類
  }
}
window._main = new Main()
window._main.start()

這裏就簡單介紹下plantszombies對象數組;當遊戲運行時,所有種植的植物以及生成的僵屍都會配合其相關初始化參數plants_infozombies_info進行實例化再分別保存在plantszombies對象數組中。

原生JS實現的h5小遊戲-植物大戰僵屍

代碼地址如下:
http://www.demodashi.com/demo/12755.html

註:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權

原生JS實現的h5小遊戲-植物大戰僵屍