項目介紹
本項目是利用原生js實現的h5小游戲-植物大戰僵屍,主要結合了一下自己對於h5小游戲的理解,結合面向對象的編程思想進行開發,在實現時使用了部分es6語法,對於es6語法不太熟悉的小伙伴可以先查閱相關資料了解一下。
如有需要,可根據自己的需求修改源碼樣式、源碼配置屬性代碼,實現個性化定制。
以下為文件目錄結構示意圖,核心代碼在js
文件夾下的四個common.js
、main.js
、game.js
、scene.js
js文件中
源碼: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)
}
}
這里的角色類主要用於抽象植物類和僵屍類的公共屬性,基本屬性包括type
、section
、x
、y
、w
、h
、row
、col
,其中row
和col
屬性用於控制角色在草坪上繪制的橫縱坐標(即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)
}
}
植物類的私有屬性包括idel
、attack
、bullets
、state
,其中idel
和attack
為動畫對象,相信看過上面關於動畫類介紹的小伙伴應該能理解其作用,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
屬性是固定值。
關於僵屍的動畫對象可能會比植物復雜一點,包含idle
、run
、attack
、dying
、die
五種形態的動畫序列,其中dying
和die
對應僵屍較低血量(瀕死狀態
)和血量為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()
這里就簡單介紹下plants
、zombies
對象數組;當游戲運行時,所有種植的植物以及生成的僵屍都會配合其相關初始化參數plants_info
、zombies_info
進行實例化再分別保存在plants
、zombies
對象數組中。
原生JS實現的h5小游戲-植物大戰僵屍
注:本文著作權歸作者,由demo大師代發,拒絕轉載,轉載需要作者授權