用JS實現植物大戰僵尸(前端作業)

1. 先搭架子

整體效果:

點擊開始后進入主場景

左側是植物卡片

右上角是游戲的開始和暫停鍵

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="css/common.css"><link rel="stylesheet" href="css/style.css">
</head>
<body><div id="js-startGame-btn" class="startGame-btn">點擊開始游戲</div><!--主場景--><div class="content-box"><canvas id="canvas" width="1400" height="600"></canvas></div><!--左側植物--><ul class="cards-list"><li class="cards-item" data-section="sunflower"><div class="card-intro"><span>向日葵</span><span>冷卻時間:5秒</span></div></li><li class="cards-item" data-section="wallnut"><div class="card-intro"><span>堅果墻</span><span>冷卻時間:12秒</span></div></li><li class="cards-item" data-section="peashooter"><div class="card-intro"><span>豌豆射手</span><span>冷卻時間:7秒</span></div></li><li class="cards-item" data-section="repeater"><div class="card-intro"><span>雙發豌豆射手</span><span>冷卻時間:10秒</span></div></li><li class="cards-item" data-section="gatlingpea"><div class="card-intro"><span>加特林射手</span><span>冷卻時間:15秒</span></div></li><li class="cards-item" data-section="chomper"><div class="card-intro"><span>食人花</span><span>冷卻時間:15秒</span></div></li><li class="cards-item" data-section="cherrybomb"><div class="card-intro"><span>櫻桃炸彈</span><span>冷卻時間:25秒</span></div></li></ul><!--Start and Pause--><div class="menu-box"><div id="pauseGame" class="contro-btn">暫停</div><div id="restartGame" class="contro-btn">開始游戲</div></div><!--自動生成陽光--><!-- <img class="sum-img systemSun"  src="images/sun.gif" alt=""> --><script src="js/common.js"></script><script src="js/scene.js"></script><script src="js/game.js"></script><script src="js/main.js"></script>
</body>
</html>

2. 導入植物/僵尸/陽光...的圖片?

圖片包含:植物cd好的狀態和冷卻期的狀態,植物空閑狀態/攻擊狀態,僵尸包含移動狀態/攻擊狀態/櫻桃炸彈炸的效果, 同時我們提供對外的imageFromPath函數, 用來生成圖片路徑

const imageFromPath = function(src){let img = new Image()img.src = './images/' + srcreturn img
}
// 原生動畫參數
// const keyframesOptions = {
//     iterations: 1,
//     iterationStart: 0,
//     delay: 0,
//     endDelay: 0,
//     direction: 'alternate',
//     duration: 3000,
//     fill: 'forwards',
//     easing: 'ease-out',
// }
// 圖片素材路徑
const allImg = {startBg: 'coverBg.jpg',                         // 首屏背景圖bg: 'background1.jpg',                          // 游戲背景bullet: 'bullet.png',                           // 子彈普通狀態bulletHit: 'bullet_hit.png',                    // 子彈擊中敵人狀態sunback: 'sunback.png',                         // 陽光背景框zombieWon: 'zombieWon.png',                     // 僵尸勝利畫面car: 'car.png',                                 // 小汽車圖片loading: {                                      // loading 畫面write: {path: 'loading/loading_*.png',len: 3,},},plantsCard: {                                               // 植物卡片sunflower: {  // 向日葵img: 'cards/plants/SunFlower.png',imgG: 'cards/plants/SunFlowerG.png',},peashooter: { // 豌豆射手img: 'cards/plants/Peashooter.png',imgG: 'cards/plants/PeashooterG.png',},repeater: { // 雙發射手img: 'cards/plants/Repeater.png',imgG: 'cards/plants/RepeaterG.png',},gatlingpea: { // 加特林射手img: 'cards/plants/GatlingPea.png',imgG: 'cards/plants/GatlingPeaG.png',},cherrybomb: { // 櫻桃炸彈img: 'cards/plants/CherryBomb.png',imgG: 'cards/plants/CherryBombG.png',      },wallnut: {  // 堅果墻img: 'cards/plants/WallNut.png',imgG: 'cards/plants/WallNutG.png',},chomper: {  // 食人花img: 'cards/plants/Chomper.png',imgG: 'cards/plants/ChomperG.png',},},plants: {                                                   // 植物 sunflower: {  // 向日葵idle: {path: 'plants/sunflower/idle/idle_*.png',len: 18,},},peashooter: { // 豌豆射手idle: {path: 'plants/peashooter/idle/idle_*.png',len: 8,},attack: {path: 'plants/peashooter/attack/attack_*.png',len: 8,},},repeater: { // 雙發射手idle: {path: 'plants/repeater/idle/idle_*.png',len: 15,},attack: {path: 'plants/repeater/attack/attack_*.png',len: 15,},},gatlingpea: { // 加特林射手idle: {path: 'plants/gatlingpea/idle/idle_*.png',len: 13,},attack: {path: 'plants/gatlingpea/attack/attack_*.png',len: 13,},},cherrybomb: { // 櫻桃炸彈idle: {path: 'plants/cherrybomb/idle/idle_*.png',len: 7,},attack: {path: 'plants/cherrybomb/attack/attack_*.png',len: 5,},},wallnut: { // 堅果墻idleH: { // 血量高時動畫path: 'plants/wallnut/idleH/idleH_*.png',len: 16,},idleM: { // 血量中等時動畫path: 'plants/wallnut/idleM/idleM_*.png',len: 11,},idleL: { // 血量低時動畫path: 'plants/wallnut/idleL/idleL_*.png',len: 15,},},chomper: { // 食人花idle: { // 站立動畫path: 'plants/chomper/idle/idle_*.png',len: 13,},attack: { // 攻擊動畫path: 'plants/chomper/attack/attack_*.png',len: 8,},digest: { // 消化階段動畫path: 'plants/chomper/digest/digest_*.png',len: 6,}},},zombies: {                                            // 僵尸idle: { // 站立動畫path: 'zombies/idle/idle_*.png',len: 31,},run: { // 移動動畫path: 'zombies/run/run_*.png',len: 31,},attack: { // 攻擊動畫path: 'zombies/attack/attack_*.png',len: 21,},dieboom: { // 被炸死亡動畫path: 'zombies/dieboom/dieboom_*.png',len: 20,},dying: { // 瀕死動畫head: {path: 'zombies/dying/head/head_*.png',len: 12,},body: {path: 'zombies/dying/body/body_*.png',len: 18,},},die: { // 死亡動畫head: {path: 'zombies/dying/head/head_*.png',len: 12,},body: {path: 'zombies/die/die_*.png',len: 10,},},}
}

3. 場景的塑造

例如:左上角的陽光顯示板, 右側的植物卡片, 小汽車和子彈等等...

先來了解一下Canvas這個標簽, 你可以把它想像成一個畫布,我們可以通過獲取上下文來繪制在畫布上進行繪畫(坐標系如下)?

    <canvas id="canvas" width="500" height="500"></canvas><script>let canvas=document.getElementById("canvas")let cxt=canvas.getContext("2d")     //畫筆//繪制一個矩形ctx.rect(0,0,100,200)//實心ctx.fill()    //描邊ctx.stroke()//為上下文填充顏色cxt.fillStyle="orange"//填充文本ctx.font="700 16px Arial"ctx.fillText("內容",x,y,[,maxWidth])//添加圖片let img=new Image()img.src='myImage.png'cxt.drawImage(img,x,y,width,height)//預加載let img=new Image()img.onload=function(){ctx.drawImage(img,0,0)}img.src='myImage.png'</script>

?

?

陽光顯示板:1. 背景img? 2. 所顯示的陽光總數量 3. 字體大小和顏色

class SunNum{constructor(){let s={img:null,sun_num:window._main.allSunVal,  //陽光總數量x:105,y:0,}Object.assign(this,s)}static new(){let s=new this()s.img=imageFromPath(allImg.sunback)return s}draw(cxt){let self=thiscxt.drawImage(self.img,self.x+120,self.y)  //用于在Canvas上繪制圖像cxt.fillStyle='black'cxt.font='24px Microsoft YaHei'cxt.fontWeight=700cxt.fillText(self.sun_num,self.x+175,self.y+27)}//修改陽光 !!!!!changeSunNum(num=25){let self=thiswindow._main.allSunVal+=numself.sun_num+=num}
}

左側卡片:當我們使用了一個植物后,它的狀態就會改變, 類似于進入到冷卻時間

class Card{constructor(obj){let c={name:obj.name,canGrow:true,canClick:true,img:null,images:[],timer:null,timer_spacing:obj.timer_spacing,timer_num:1,sun_val:obj.sun_val,row:obj.row,x:0,y:obj.y,}Object.assign(this,c)}static new(obj){let b=new this(obj)b.images.push(imageFromPath(allImg.plantsCard[b.name].img))       b.images.push(imageFromPath(allImg.plantsCard[b.name].imgG)) if(b.canClick){b.img=b.images[0]}else{b.img=b.images[1]}b.timer_num = b.timer_spacing / 1000  //1000ms                           return b}draw(cxt) {let self = this, marginLeft = 120if(self.sun_val > window._main.allSunVal){self.canGrow = false}else{self.canGrow = true}if(self.canGrow && self.canClick){self.img = self.images[0]}else{self.img = self.images[1]}cxt.drawImage(self.img, self.x + marginLeft, self.y)cxt.fillStyle = 'black'cxt.font = '16px Microsoft YaHei'cxt.fillText(self.sun_val, self.x + marginLeft + 60, self.y + 55)if (!self.canClick && self.canGrow) {cxt.fillStyle = 'rgb(255, 255, 0)'cxt.font = '20px Microsoft YaHei'cxt.fillText(self.timer_num, self.x + marginLeft + 30, self.y + 35)}}drawCountDown(){let self=thisself.timer=setInterval(()=>{        //定時器if(self.timer_num>0){self.timer_num--}else{clearInterval(self.timer)self.timer_num=self.timer_spacing/1000}},1000)}changeState(){let self=thisif(!self.canClick){self.timer=setTimeout(()=> {    //延時器self.canClick=true},self.timer_spacing)}}
}

?除草車:當僵尸靠近坐標x(在一定范圍內)的時候,? 就會清除整行僵尸

class Car{constructor(obj){let c={img: imageFromPath(allImg.car),state:1,state_NORMALE:1,state_ATTACK:2,w:71,h:57,x:obj.x,y:obj.y,row:obj.row,}Object.assign(this,c)}static new(obj){let c=new this(obj)return c}draw(game,cxt){let self = thisself.canMove()self.state === self.state_ATTACK && self.step(game)cxt.drawImage(self.img, self.x, self.y)}step(game) {game.state === game.state_RUNNING ? this.x += 15 : this.x = this.x}// 判斷是否移動小車 (zombie.x < 150時)canMove () {let self = thisfor (let zombie of window._main.zombies) {if (zombie.row === self.row) {if (zombie.x < 150) { self.state = self.state_ATTACK}if (self.state === self.state_ATTACK) { if (zombie.x - self.x < self.w && zombie.x < 950) {zombie.life = 0zombie.changeAnimation('die')}}}}}
}

?子彈:例如像豌豆射手就會發射子彈,但是只有在state_RUNNING狀態下, 才會進行觸發

class Bullet{constructor(plant){let b={img: imageFromPath(allImg.bullet),w:56,h:34,x:0,y:0,}Object.assign(this,b)}static new(plant){let b=new this(plant)switch (plant.section) {case 'peashooter':b.x = plant.x + 30b.y = plant.ybreakcase 'repeater':b.x = plant.x + 30b.y = plant.ybreakcase 'gatlingpea':b.x = plant.x + 30b.y = plant.y + 10break}return b}draw(game,cxt){let self=thisself.step(game)cxt.drawImage(self.img,self.x,self.y)}step(game){if(game.state === game.state_RUNNING){this.x+=4}else{this.x=this.x}}
}

?為角色設置動畫

class Animation{constructor (role, action, fps) {let a = {type: role.type,                                   // 動畫類型(植物、僵尸等等)section: role.section,                             // 植物或者僵尸類別(向日葵、豌豆射手)action: action,                                    // 根據傳入動作生成不同動畫對象數組images: [],                                        // 當前引入角色圖片對象數組img: null,                                         // 當前顯示角色圖片imgIdx: 0,                                         // 當前角色圖片序列號count: 0,                                          // 計數器,控制動畫運行imgHead: null,                                     // 當前顯示角色頭部圖片imgBody: null,                                     // 當前顯示角色身體圖片imgIdxHead: 0,                                     // 當前角色頭部圖片序列號imgIdxBody: 0,                                     // 當前角色身體圖片序列號countHead: 0,                                      // 當前角色頭部計數器,控制動畫運行countBody: 0,                                      // 當前角色身體計數器,控制動畫運行fps: fps,                                          // 角色動畫運行速度系數,值越小,速度越快}Object.assign(this, a)}// 創建,并初始化當前對象static new (role, action, fps) {let a = new this(role, action, fps)// 瀕死動畫、死亡動畫對象(僵尸)if (action === 'dying' || action === 'die') {a.images = {head: [],body: [],}a.create()} else {a.create()a.images[0].onload = function () {role.w = this.widthrole.h = this.height}}return a}/*** 為角色不同動作創造動畫序列*/create () {let self = this,section = self.section    // 植物種類switch (self.type) {case 'plant':for(let i = 0; i < allImg.plants[section][self.action].len; i++){let idx = i < 10 ? '0' + i : i,path = allImg.plants[section][self.action].path// 依次添加動畫序列self.images.push(imageFromPath(path.replace(/\*/, idx)))}breakcase 'zombie':// 瀕死動畫、死亡動畫對象,包含頭部動畫以及身體動畫if (self.action === 'dying' || self.action === 'die') {for(let i = 0; i < allImg.zombies[self.action].head.len; i++){let idx = i < 10 ? '0' + i : i,path = allImg.zombies[self.action].head.path// 依次添加動畫序列self.images.head.push(imageFromPath(path.replace(/\*/, idx)))}for(let i = 0; i < allImg.zombies[self.action].body.len; i++){let idx = i < 10 ? '0' + i : i,path = allImg.zombies[self.action].body.path// 依次添加動畫序列self.images.body.push(imageFromPath(path.replace(/\*/, idx)))}} else { // 普通動畫對象for(let i = 0; i < allImg.zombies[self.action].len; i++){let idx = i < 10 ? '0' + i : i,path = allImg.zombies[self.action].path// 依次添加動畫序列self.images.push(imageFromPath(path.replace(/\*/, idx)))}}breakcase 'loading': // loading動畫for(let i = 0; i < allImg.loading[self.action].len; i++){let idx = i < 10 ? '0' + i : i,path = allImg.loading[self.action].path// 依次添加動畫序列self.images.push(imageFromPath(path.replace(/\*/, idx)))}break}}
}

?為植物和僵尸設置不同狀態下的動畫效果

/*** 角色類* 植物、僵尸類繼承的基礎屬性*/
class Role{constructor (obj) {let r = {id: Math.random().toFixed(6) * Math.pow(10, 6),      // 隨機生成 id 值,用于設置當前角色 IDtype: obj.type,                                      // 角色類型(植物或僵尸)section: obj.section,                                // 角色類別(豌豆射手、雙發射手...)x: obj.x,                                            // x軸坐標y: obj.y,                                            // y軸坐標row: obj.row,                                        // 角色初始化行坐標col: obj.col,                                        // 角色初始化列坐標w: 0,                                                // 角色圖片寬度h: 0,                                                // 角色圖片高度isAnimeLenMax: false,                                // 是否處于動畫最后一幀,用于判斷動畫是否執行完一輪isDel: false,                                        // 判斷是否死亡并移除當前角色isHurt: false,                                       // 判斷是否受傷}Object.assign(this, r)}
}
// 植物類
class Plant extends Role{constructor (obj) {super(obj)// 植物類私有屬性let p = {life: 3,                                             // 角色血量idle: null,                                          // 站立動畫對象idleH: null,                                         // 堅果高血量動畫對象idleM: null,                                         // 堅果中等血量動畫對象idleL: null,                                         // 堅果低血量動畫對象attack: null,                                        // 角色攻擊動畫對象digest: null,                                        // 角色消化動畫對象bullets: [],                                         // 子彈數組對象state: obj.section === 'wallnut' ? 2 : 1,            // 保存當前狀態值state_IDLE: 1,                                       // 站立不動狀態state_IDLE_H: 2,                                     // 站立不動高血量狀態(堅果墻相關動畫)state_IDLE_M: 3,                                     // 站立不動中等血量狀態(堅果墻相關動畫)state_IDLE_L: 4,                                     // 站立不動低血量狀態(堅果墻相關動畫)state_ATTACK: 5,                                     // 攻擊狀態state_DIGEST: 6,                                     // 待攻擊狀態(食人花消化僵尸狀態)canShoot: false,                                     // 植物是否具有發射子彈功能canSetTimer: obj.canSetTimer,                        // 能否設置生成陽光定時器sunTimer: null,                                      // 生成陽光定時器sunTimer_spacing: 10,                                // 生成陽光時間間隔(秒)}Object.assign(this, p)}// 創建,并初始化當前對象static new (obj) {let p = new this(obj)p.init()return p}// 設置陽光生成定時器setSunTimer () {let self = thisself.sunTimer = setInterval(function () {// 創建陽光元素let img = document.createElement('img'),                  // 創建元素container = document.getElementsByTagName('body')[0], // 父級元素容器id = self.id,                                         // 當前角色 IDtop = self.y + 30,left = self.x - 130,keyframes1 = [                                        // 陽光移動動畫 keyframes{ transform: 'translate(0,0)', opacity: 0 },{ offset: .3,transform: 'translate(0,0)', opacity: 1 },{ offset: .5,transform: 'translate(0,0)', opacity: 1 },{ offset: 1,transform: 'translate(-'+ (left - 110) +'px,-'+ (top + 50) +'px)',opacity: 0 }]// 添加陽關元素img.src = 'images/sun.gif'img.className += 'sun-img plantSun' + idimg.style.top = top + 'px'img.style.left = left + 'px'container.appendChild(img)// 添加陽光移動動畫let sun = document.getElementsByClassName('plantSun' + id)[0]sun.animate(keyframes1,keyframesOptions)// 動畫完成,清除陽光元素setTimeout(()=> {sun.parentNode.removeChild(sun)// 增加陽光數量window._main.sunnum.changeSunNum()}, 2700)}, self.sunTimer_spacing * 1000)}// 清除陽光生成定時器clearSunTimer () {let self = thisclearInterval(self.sunTimer)}// 初始化init () {let self = this,setPlantFn = null// 初始化植物動畫對象方法集setPlantFn = {sunflower () {  // 向日葵self.idle = Animation.new(self, 'idle', 12)// 定時生成陽光self.canSetTimer && self.setSunTimer()},peashooter () { // 豌豆射手self.canShoot = trueself.idle = Animation.new(self, 'idle', 12)self.attack = Animation.new(self, 'attack', 12)},repeater () { // 雙發射手self.canShoot = trueself.idle = Animation.new(self, 'idle', 12)self.attack = Animation.new(self, 'attack', 8)},gatlingpea () { // 加特林射手// 改變加特林渲染 y 軸距離self.y -= 12self.canShoot = trueself.idle = Animation.new(self, 'idle', 8)self.attack = Animation.new(self, 'attack', 4)},cherrybomb () { // 櫻桃炸彈self.x -= 15self.idle = Animation.new(self, 'idle', 15)self.attack = Animation.new(self, 'attack', 15)setTimeout(()=> {self.state = self.state_ATTACK}, 2000)},wallnut () { // 堅果墻self.x += 15// 設置堅果血量self.life = 12// 創建堅果三種不同血量下的動畫對象self.idleH = Animation.new(self, 'idleH', 10)self.idleM = Animation.new(self, 'idleM', 8)self.idleL = Animation.new(self, 'idleL', 10)},chomper () { // 食人花self.life = 5self.y -= 45self.idle = Animation.new(self, 'idle', 10)self.attack = Animation.new(self, 'attack', 12)self.digest = Animation.new(self, 'digest', 12)},}// 執行對應植物初始化方法for (let key in setPlantFn) {if (self.section === key) {setPlantFn[key]()}}}// 繪制方法draw (cxt) {let self = this,stateName = self.switchState()switch (self.isHurt) {case false:if (self.section === 'cherrybomb' && self.state === self.state_ATTACK) {// 正常狀態,繪制櫻桃炸彈爆炸圖片cxt.drawImage(self[stateName].img, self.x - 60, self.y - 50)} else {// 正常狀態,繪制普通植物圖片cxt.drawImage(self[stateName].img, self.x, self.y)}breakcase true:// 受傷或移動植物時,繪制半透明圖片cxt.globalAlpha = 0.5cxt.beginPath()cxt.drawImage(self[stateName].img, self.x, self.y)cxt.closePath()cxt.save()cxt.globalAlpha = 1break}}// 更新狀態update (game) {let self = this,section = self.section,stateName = self.switchState()// 修改當前動畫序列長度let animateLen = allImg.plants[section][stateName].len// 累加動畫計數器self[stateName].count += 1// 設置角色動畫運行速度self[stateName].imgIdx = Math.floor(self[stateName].count / self[stateName].fps)// 一整套動畫完成后重置動畫計數器self[stateName].imgIdx === animateLen - 1 ? self[stateName].count = 0 : self[stateName].count = self[stateName].count// 繪制發射子彈動畫if (game.state === game.state_RUNNING) {// 設置當前幀動畫對象self[stateName].img = self[stateName].images[self[stateName].imgIdx]if (self[stateName].imgIdx === animateLen - 1) {if (stateName === 'attack' && !self.isDel) {// 未死亡,且為可發射子彈植物時if (self.canShoot) {// 發射子彈self.shoot()// 雙發射手額外發射子彈self.section === 'repeater' && setTimeout(()=> {self.shoot()}, 250)}// 當為櫻桃炸彈時,執行完一輪動畫,自動消失self.section === 'cherrybomb' ? self.isDel = true : self.isDel = false// 當為食人花時,執行完攻擊動畫,切換為消化動畫if (self.section === 'chomper') {// 立即切換動畫會出現圖片未加載完成報錯setTimeout(()=> {self.changeAnimation('digest')}, 0)}} else if (self.section === 'chomper' && stateName === 'digest') {// 消化動畫完畢后,間隔一段時間切換為正常狀態setTimeout(()=> {self.changeAnimation('idle')}, 30000)}self.isAnimeLenMax = true} else {self.isAnimeLenMax = false}}}// 檢測植物是否可攻擊僵尸方法canAttack () {let self = this// 植物類別為向日葵和堅果墻時,不需判定if (self.section === 'sunflower' || self.section === 'wallnut') return false// 循環僵尸對象數組for (let zombie of window._main.zombies) {if (self.section === 'cherrybomb') { // 當為櫻桃炸彈時// 僵尸在以櫻桃炸彈為圓心的 9 個格子內時if (Math.abs(self.row - zombie.row) <= 1 && Math.abs(self.col - zombie.col) <= 1 && zombie.col < 10) {// 執行爆炸動畫self.changeAnimation('attack')zombie.life = 0// 僵尸炸死動畫zombie.changeAnimation('dieboom')}} else if (self.section === 'chomper' && self.state === self.state_IDLE) { // 當為食人花時// 僵尸在食人花正前方時if (self.row === zombie.row && (zombie.col - self.col) <= 1 && zombie.col < 10) {self.changeAnimation('attack')setTimeout(()=> {zombie.isDel = true}, 1300)}} else if (self.canShoot && self.row === zombie.row) { // 當植物可發射子彈,且僵尸和植物處于同行時// 僵尸進入植物射程范圍zombie.x < 940 && self.x < zombie.x + 10 && zombie.life > 0 ? self.changeAnimation('attack') : self.changeAnimation('idle')// 植物未被移除時,可發射子彈if (!self.isDel) {self.bullets.forEach(function (bullet, j) {// 當子彈打中僵尸,且僵尸未死亡時if (Math.abs(zombie.x + bullet.w - bullet.x) < 10 && zombie.life > 0) { // 子彈和僵尸距離小于 10 且僵尸未死亡// 移除子彈self.bullets.splice(j, 1)// 根據血量判斷執行不同階段動畫if (zombie.life !== 0) {zombie.life--zombie.isHurt = truesetTimeout(()=> {zombie.isHurt = false}, 200)}if (zombie.life === 2) {zombie.changeAnimation('dying')} else if (zombie.life === 0) {zombie.changeAnimation('die')}}})}}}}// 射擊方法shoot () {let self = thisself.bullets[self.bullets.length] = Bullet.new(self)}/*** 判斷角色狀態并返回對應動畫對象名稱方法*/switchState () {let self = this,state = self.state,dictionary = {idle: self.state_IDLE,idleH: self.state_IDLE_H,idleM: self.state_IDLE_M,idleL: self.state_IDLE_L,attack: self.state_ATTACK,digest: self.state_DIGEST,}for (let key in dictionary) {if (state === dictionary[key]) {return key}}}/*** 切換角色動畫* game => 游戲引擎對象* action => 動作類型*  -idle: 站立動畫*  -idleH: 角色高血量動畫(堅果墻)*  -idleM: 角色中等血量動畫(堅果墻)*  -idleL: 角色低血量動畫(堅果墻)*  -attack: 攻擊動畫*  -digest: 消化動畫(食人花)*/changeAnimation (action) {let self = this,stateName = self.switchState(),dictionary = {idle: self.state_IDLE,idleH: self.state_IDLE_H,idleM: self.state_IDLE_M,idleL: self.state_IDLE_L,attack: self.state_ATTACK,digest: self.state_DIGEST,}if (action === stateName) returnself.state = dictionary[action]}
}
// 僵尸類
class Zombie extends Role{constructor (obj) {super(obj)// 僵尸類私有屬性let z = {life: 10,                                            // 角色血量canMove: true,                                       // 判斷當前角色是否可移動attackPlantID: 0,                                    // 當前攻擊植物對象 IDidle: null,                                          // 站立動畫對象run: null,                                           // 奔跑動畫對象attack: null,                                        // 攻擊動畫對象dieboom: null,                                       // 被炸死亡動畫對象dying: null,                                         // 瀕臨死亡動畫對象die: null,                                           // 死亡動畫對象state: 1,                                            // 保存當前狀態值,默認為1state_IDLE: 1,                                       // 站立不動狀態state_RUN: 2,                                        // 奔跑狀態state_ATTACK: 3,                                     // 攻擊狀態state_DIEBOOM: 4,                                    // 死亡狀態state_DYING: 5,                                      // 瀕臨死亡狀態state_DIE: 6,                                        // 死亡狀態state_DIGEST: 7,                                     // 消化死亡狀態speed: 3,                                            // 移動速度head_x: 0,                                           // 頭部動畫 x 軸坐標head_y: 0,                                           // 頭部動畫 y 軸坐標}Object.assign(this, z)}// 創建,并初始化當前對象static new (obj) {let p = new this(obj)p.init()return p}// 初始化init () {let self = this// 站立self.idle = Animation.new(self, 'idle', 12)// 移動self.run = Animation.new(self, 'run', 12)// 攻擊self.attack = Animation.new(self, 'attack', 8)// 炸死self.dieboom = Animation.new(self, 'dieboom', 8)// 瀕死self.dying = Animation.new(self, 'dying', 8)// 死亡self.die = Animation.new(self, 'die', 12)}// 繪制方法draw (cxt) {let self = this,stateName = self.switchState()if (stateName !== 'dying' && stateName !== 'die') { // 繪制普通動畫if (!self.isHurt) { // 未受傷時,繪制正常動畫cxt.drawImage(self[stateName].img, self.x, self.y)} else { // 受傷時,繪制帶透明度動畫// 繪制帶透明度動畫cxt.globalAlpha = 0.5cxt.beginPath()cxt.drawImage(self[stateName].img, self.x, self.y)cxt.closePath()cxt.save()cxt.globalAlpha = 1}} else { // 繪制瀕死、死亡動畫if (!self.isHurt) { // 未受傷時,繪制正常動畫cxt.drawImage(self[stateName].imgHead, self.head_x + 70, self.head_y - 10)cxt.drawImage(self[stateName].imgBody, self.x, self.y)} else { // 受傷時,繪制帶透明度動畫// 繪制帶透明度身體cxt.globalAlpha = 0.5cxt.beginPath()cxt.drawImage(self[stateName].imgBody, self.x, self.y)cxt.closePath()cxt.save()cxt.globalAlpha = 1// 頭部不帶透明度cxt.drawImage(self[stateName].imgHead, self.head_x + 70, self.head_y - 10)}}}// 更新狀態update (game) {let self = this,stateName = self.switchState()// 更新能否移動狀態值self.canMove ? self.speed = 3 : self.speed = 0// 更新僵尸列坐標值self.col = Math.floor((self.x - window._main.zombies_info.x) / 80 + 1)if (stateName !== 'dying' && stateName !== 'die') { // 普通動畫(站立,移動,攻擊)// 修改當前動畫序列長度let animateLen = allImg.zombies[stateName].len// 累加動畫計數器self[stateName].count += 1// 設置角色動畫運行速度self[stateName].imgIdx = Math.floor(self[stateName].count / self[stateName].fps)// 一整套動畫完成后重置動畫計數器if (self[stateName].imgIdx === animateLen) {self[stateName].count = 0self[stateName].imgIdx = 0if (stateName === 'dieboom') { // 被炸死亡狀態// 當死亡動畫執行完一輪后,移除當前角色self.isDel = true}// 當前動畫幀數達到最大值self.isAnimeLenMax = true} else {self.isAnimeLenMax = false}// 游戲運行狀態if (game.state === game.state_RUNNING) {// 設置當前幀動畫對象self[stateName].img = self[stateName].images[self[stateName].imgIdx]if (stateName === 'run') { // 當僵尸移動時,控制移動速度self.x -= self.speed / 17}}} else if (stateName === 'dying') { // 瀕死動畫,包含兩個動畫對象// 獲取當前動畫序列長度let headAnimateLen = allImg.zombies[stateName].head.len,bodyAnimateLen = allImg.zombies[stateName].body.len// 累加動畫計數器if (self[stateName].imgIdxHead !== headAnimateLen - 1) {self[stateName].countHead += 1}self[stateName].countBody += 1// 設置角色動畫運行速度self[stateName].imgIdxHead = Math.floor(self[stateName].countHead / self[stateName].fps)self[stateName].imgIdxBody = Math.floor(self[stateName].countBody / self[stateName].fps)// 設置當前幀動畫對象,頭部動畫if (self[stateName].imgIdxHead === 0) {self.head_x = self.xself.head_y = self.yself[stateName].imgHead = self[stateName].images.head[self[stateName].imgIdxHead]} else if (self[stateName].imgIdxHead === headAnimateLen) {self[stateName].imgHead = self[stateName].images.head[headAnimateLen - 1]} else {self[stateName].imgHead = self[stateName].images.head[self[stateName].imgIdxHead]}// 設置當前幀動畫對象,身體動畫if (self[stateName].imgIdxBody === bodyAnimateLen) {self[stateName].countBody = 0self[stateName].imgIdxBody = 0// 當前動畫幀數達到最大值self.isAnimeLenMax = true} else {self.isAnimeLenMax = false}// 游戲運行狀態if (game.state === game.state_RUNNING) {// 設置當前幀動畫對象self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]if (stateName === 'dying') { // 瀕死狀態,可以移動self.x -= self.speed / 17}}} else if (stateName === 'die') { // 死亡動畫,包含兩個動畫對象// 獲取當前動畫序列長度let headAnimateLen = allImg.zombies[stateName].head.len,bodyAnimateLen = allImg.zombies[stateName].body.len// 累加動畫計數器if (self[stateName].imgIdxBody !== bodyAnimateLen - 1) {self[stateName].countBody += 1}// 設置角色動畫運行速度self[stateName].imgIdxBody = Math.floor(self[stateName].countBody / self[stateName].fps)// 設置當前幀動畫對象,死亡狀態,定格頭部動畫if (self[stateName].imgIdxHead === 0) {if (self.head_x == 0 && self.head_y == 0) {self.head_x = self.xself.head_y = self.y}self[stateName].imgHead = self[stateName].images.head[headAnimateLen - 1]}// 設置當前幀動畫對象,身體動畫if (self[stateName].imgIdxBody === 0) {self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]} else if (self[stateName].imgIdxBody === bodyAnimateLen - 1) {// 當死亡動畫執行完一輪后,移除當前角色self.isDel = trueself[stateName].imgBody = self[stateName].images.body[bodyAnimateLen - 1]} else {self[stateName].imgBody = self[stateName].images.body[self[stateName].imgIdxBody]}}}// 檢測僵尸是否可攻擊植物canAttack () {let self = this// 循環植物對象數組for (let plant of window._main.plants) {if (plant.row === self.row && !plant.isDel) { // 當僵尸和植物處于同行時if (self.x - plant.x < -20 && self.x - plant.x > -60) {if (self.life > 2) {// 保存當前攻擊植物 hash 值,在該植物被刪除時,再控制當前僵尸移動self.attackPlantID !== plant.id ? self.attackPlantID = plant.id : self.attackPlantID = self.attackPlantIDself.changeAnimation('attack')} else {self.canMove = false}if (self.isAnimeLenMax && self.life > 2) {  // 僵尸動畫每執行完一輪次// 扣除植物血量if (plant.life !== 0) {plant.life--plant.isHurt = truesetTimeout(()=> {plant.isHurt = false// 堅果墻判斷切換動畫狀態if (plant.life <= 8 && plant.section === 'wallnut') {plant.life <= 4 ? plant.changeAnimation('idleL') : plant.changeAnimation('idleM')}// 判斷植物是否可移除if (plant.life <= 0) {// 設置植物死亡狀態plant.isDel = true// 清除死亡向日葵的陽光生成定時器plant.section === 'sunflower' && plant.clearSunTimer()}}, 200)}} }}}}/*** 判斷角色狀態并返回對應動畫對象名稱方法*/switchState () {let self = this,state = self.state,dictionary = {idle: self.state_IDLE,run: self.state_RUN,attack: self.state_ATTACK,dieboom: self.state_DIEBOOM,dying: self.state_DYING,die: self.state_DIE,digest: self.state_DIGEST,}for (let key in dictionary) {if (state === dictionary[key]) {return key}}}/*** 切換角色動畫* game => 游戲引擎對象* action => 動作類型*  -idle: 站立不動*  -attack: 攻擊*  -die: 死亡*  -dying: 瀕死*  -dieboom: 爆炸*  -digest: 被消化*/changeAnimation (action) {let self = this,stateName = self.switchState(),dictionary = {idle: self.state_IDLE,run: self.state_RUN,attack: self.state_ATTACK,dieboom: self.state_DIEBOOM,dying: self.state_DYING,die: self.state_DIE,digest: self.state_DIGEST,}if (action === stateName) returnself.state = dictionary[action]}
}

?游戲引擎

class Game {constructor (){let g = {actions: {},                                                  // 注冊按鍵操作keydowns: {},                                                 // 按鍵事件對象cardSunVal: null,                                             // 當前選中植物卡片index以及需消耗陽光值cardSection: '',                                              // 繪制隨鼠標移動植物類別canDrawMousePlant: false,                                     // 能否繪制隨鼠標移動植物canLayUp: false,                                              // 能否放置植物mousePlant: null,                                             // 鼠標繪制植物對象mouseX: 0,                                                    // 鼠標 x 軸坐標mouseY: 0,                                                    // 鼠標 y 軸坐標mouseRow: 0,                                                  // 鼠標移動至可種植植物區域的行坐標mouseCol: 0,                                                  // 鼠標移動至可種植植物區域的列坐標state: 0,                                                     // 游戲狀態值,初始默認為 0state_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,                                        // 動畫幀數}Object.assign(this,g)}static new(){let g=new this()g.init()return g}// clearGameTimer(){//     let g=this//     clearInterval(g.timer)// }drawBg(){let g=this,cxt=g.context,sunnum=window._main.sunnum,cards=window._main.cards,img=imageFromPath(allImg.bg)cxt.drawImage(img,0,0)sunnum.draw(cxt)}drawCars(){let g=this,cxt=g.context,cars=window._main.carscars.forEach((car,idx)=>{if(car.x>950){cars.splice(idx,1)}car.draw(g,cxt)})}drawCards(){let g=this,cxt=g.context,cards=window._main.cardsfor(let card of cards){card.draw(cxt)}}drawPlantWon(){let g=this,cxt=g.context,text='恭喜玩家獲得勝利!'cxt.fillStyle='red'cxt.font='48px Microsoft YaHei'cxt.fillText(text,354,300)}drawZombieWon(){let g=this,cxt=g.context,img=imageFromPath(allImg.zombieWon)cxt.drawImage(img,293,66)}drawLoading(){let g=this,cxt=g.context,img=imageFromPath(allImg.startBg)cxt.drawImage(img,119,0)}drawStartAnime(){let g=this,stateName='write',loading=window._main.loading,cxt=g.context,canvas_w=g.canvas.width,canvas_h=g.canvas.height,animateLen=allImg.loading[stateName].lenif(loading.imgIdx!=animateLen){loading.count+=1} loading.imgIdx=Math.floor(loading.count/loading.fps)if(loading.imgIdx==animateLen){loading.img=loading.images[loading.imgIdx-1]}else{loading.img=loading.images[loading.imgIdx]}cxt.drawImage(loading.img,437,246)}drawBullets(plants){let g=this,context = g.context, canvas_w = g.canvas.width - 440for(let item of plants){item.bullets.forEach((bullet,idx,arr)=>{bullet.draw(g,context)if(bullet.x>=canvas_w){arr.splice(idx,1)}})}}drawBlood (role) {let g = this,cxt = g.context,x = role.x,y = role.ycxt.fillStyle = 'red'cxt.font = '18px Microsoft YaHei'if(role.type === 'plant'){cxt.fillText(role.life, x + 30, y - 10)}else if(role.type === 'zombie') {cxt.fillText(role.life, x + 85, y + 10)}}updateImage(plants,zombies){let g = this,cxt = g.contextplants.forEach((plant, idx)=>{ plant.canAttack() plant.update(g)})zombies.forEach((zombie, idx)=>{if (zombie.x < 50){ g.state = g.state_ZOMBIEWON}zombie.canAttack()zombie.update(g)})}drawImage (plants, zombies){let g = this,cxt = g.context, delPlantsArr = []plants.forEach((plant, idx, arr)=>{if(plant.isDel){delPlantsArr.push(plant)arr.splice(idx,1)}else{plant.draw(cxt)// g.drawBlood(plant)}})zombies.forEach(function (zombie, idx) {if(zombie.isDel){ zombies.splice(idx, 1)if(zombies.length === 0) {g.state = g.state_PLANTWON}}else{zombie.draw(cxt)// g.drawBlood(zombie)}for(let plant of delPlantsArr) {if(zombie.attackPlantID === plant.id) {zombie.canMove = trueif(zombie.life > 2){zombie.changeAnimation('run')}}}})
}getMousePos(){let g = this,_main=window._main,cxt=g.context,cards=_main.cards,x=g.mouseX,y=g.mouseYif(g.canDrawMousePlant){g.mousePlantCallback(x,y)}}drawMousePlant(plant_info){let g = this,cxt = g.context,plant = nulllet mousePlant_info={type:'plant',section:g.cardSection,x: g.mouseX + 82,y: g.mouseY - 40,row: g.mouseRow,col: g.mouseCol,}if(g.canLayUp){plant=Plant.new(plant_info)plant.isHurt=trueplant.update(g)plant.draw(cxt)}g.mousePlant = Plant.new(mousePlant_info)g.mousePlant.update(g)g.mousePlant.draw(cxt)}mousePlantCallback(x,y){let g = this,_main = window._main,cxt = g.context, row = Math.floor((y - 75) / 100) + 1, col = Math.floor((x - 175) / 80) + 1let plant_info={type:'plant'    ,section: g.cardSection,x: _main.plants_info.x + 80 * (col - 1),y: _main.plants_info.y + 100 * (row - 1),row: row,col: col,}g.mouseRow = rowg.mouseCol = colif(row>=1&&row<=5&&col>=1&&col<=9){g.canLayUp=truefor(let plant of _main.plants){if(row==plant.row&&col==plant.col){g.canLayUp=false}}}else{g.canLayUp=false}if(g.canDrawMousePlant){g.drawMousePlant(plant_info)}}registerAction (key, callback) {this.actions[key] = callback}setTimer(_main) {let g = this,plants = _main.plants,zombies = _main.zombies           let actions = Object.keys(g.actions)for (let i = 0; i < actions.length; i++) {let key = actions[i]if (g.keydowns[key]) {g.actions[key]()}}g.context.clearRect(0, 0, g.canvas.width, g.canvas.height)if (g.state === g.state_LOADING) {g.drawLoading()} else if (g.state === g.state_START) {g.drawBg()g.drawCars()g.drawCards()g.drawStartAnime()} else if (g.state === g.state_RUNNING) {g.drawBg()g.updateImage(plants, zombies)g.drawImage(plants, zombies)g.drawCars()g.drawCards()g.drawBullets(plants)g.getMousePos()} else if (g.state === g.state_STOP) {g.drawBg()g.updateImage(plants, zombies)g.drawImage(plants, zombies)g.drawCars()g.drawCards()g.drawBullets(plants)_main.clearTiemr()} else if (g.state === g.state_PLANTWON) {g.drawBg()g.drawCars()g.drawCards()g.drawPlantWon()_main.clearTiemr()} else if (g.state === g.state_ZOMBIEWON) { g.drawBg()g.drawCars()g.drawCards()g.drawZombieWon()_main.clearTiemr()}}//========================================================================init(){let g=this,_main=window._main//     window.addEventListener('keydown', function (event) {//     g.keydowns[event.keyCode] = 'down'// })//     window.addEventListener('keyup', function (event) {//     g.keydowns[event.keyCode] = 'up'// })g.registerAction = function (key, callback) {g.actions[key] = callback}g.timer = setInterval(function () {g.setTimer(_main)}, 1000/g.fps)document.getElementById('canvas').onmousemove = function (event) {let e = event || window.event,scrollX = document.documentElement.scrollLeft || document.body.scrollLeft,scrollY = document.documentElement.scrollTop || document.body.scrollTop,x = e.pageX || e.clientX + scrollX,y = e.pageY || e.clientY + scrollYg.mouseX = xg.mouseY = y}document.getElementById('js-startGame-btn').onclick = function () {g.state = g.state_STARTsetTimeout(function () {g.state = g.state_RUNNINGdocument.getElementById('pauseGame').className += ' show'document.getElementById('restartGame').className += ' show'_main.clearTiemr()_main.setTimer()}, 2500)document.getElementsByClassName('cards-list')[0].className += ' show'document.getElementsByClassName('menu-box')[0].className += ' show'document.getElementById('js-startGame-btn').style.display = 'none'document.getElementById('js-intro-game').style.display = 'none'document.getElementById('js-log-btn').style.display = 'none'}document.querySelectorAll('.cards-item').forEach(function (card, idx) {card.onclick = function () {let plant = null,cards = _main.cardsif (cards[idx].canClick) {g.cardSection = this.dataset.sectiong.canDrawMousePlant = trueg.cardSunVal = {idx: idx,val: cards[idx].sun_val,}}}})document.getElementById('canvas').onclick = function (event) {let plant = null,cards = _main.cards,x = g.mouseX,y = g.mouseY,plant_info = {                           type: 'plant',section: g.cardSection,x: _main.plants_info.x + 80 * (g.mouseCol - 1),y: _main.plants_info.y + 100 * (g.mouseRow - 1),row: g.mouseRow,col: g.mouseCol,canSetTimer: g.cardSection === 'sunflower' ? true : false, }for (let item of _main.plants){if(g.mouseRow === item.row && g.mouseCol === item.col) {g.canLayUp = falseg.mousePlant = null}}if (g.canLayUp && g.canDrawMousePlant) {let cardSunVal = g.cardSunValif (cardSunVal.val <= _main.allSunVal) { cards[cardSunVal.idx].canClick = falsecards[cardSunVal.idx].changeState()cards[cardSunVal.idx].drawCountDown()plant = Plant.new(plant_info)_main.plants.push(plant)_main.sunnum.changeSunNum(-cardSunVal.val)g.canDrawMousePlant = false} else { g.canDrawMousePlant = falseg.mousePlant = null}} else {g.canDrawMousePlant = falseg.mousePlant = null}}document.getElementById('pauseGame').onclick = function (event) {g.state = g.state_STOP}document.getElementById('restartGame').onclick = function (event) {if (g.state === g.state_LOADING) { g.state = g.state_START}else{g.state = g.state_RUNNINGfor (let plant of _main.plants) {if (plant.section === 'sunflower') {plant.setSunTimer()}}}_main.setTimer()}}}

?主程序入口

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: 'sunflower', row: 1, sun_val: 50, timer_spacing: 5 * 1000},{name: 'wallnut', row: 2, sun_val: 50, timer_spacing: 12 * 1000},{name: 'peashooter', row: 3, sun_val: 100, timer_spacing: 7 * 1000},{name: 'repeater', row: 4, sun_val: 150, timer_spacing: 10 * 1000},{name: 'gatlingpea', row: 5, sun_val: 200, timer_spacing: 15 * 1000},{name: 'chomper', row: 6, sun_val: 200, timer_spacing: 15 * 1000},{name: 'cherrybomb', row: 7, sun_val: 250, timer_spacing: 25 * 1000},]},plants:[],zombies:[],plants_info:{type:'plant',x:250,y:92,position:[]},zombies_info:{type:'zombie',x:170,y:15,position:[]},zombies_idx: 0,                           zombies_row: 0,                            zombies_iMax: 50,                          sunTimer: null,                            sunTimer_difference: 20,                   zombieTimer: null,                         zombieTimer_difference: 12,                game: null,                            fps: 60,}Object.assign(this,m)}setZombiesInfo () {let self = this,iMax = self.zombies_iMaxfor(let i = 0; i < iMax; i++) {let row = Math.ceil(Math.random() * 4 + 1)self.zombies_info.position.push({section: 'zombie',row: row,col: 11 + Number(Math.random().toFixed(1))})}}clearTiemr(){let self=thisclearInterval(self.sunTimer)clearInterval(self.zombieTimer)for(let plant of self.plants){if(plant.section=='sunflower'){plant.clearSunTimer()}}}// 設置全局陽光、僵尸生成定時器setTimer(){let self=this,zombies=self.zombiesself.sunTimer = setInterval(function () {let left = parseInt(window.getComputedStyle(document.getElementsByClassName('systemSun')[0],null).left), // 獲取當前元素left值top = '-100px',keyframes1 = [{ transform: 'translate(0,0)', opacity: 0 },{ offset: .5,transform: 'translate(0,300px)', opacity: 1 },{ offset: .75,transform: 'translate(0,300px)', opacity: 1 },{ offset: 1,transform: 'translate(-'+ (left - 110) +'px,50px)',opacity: 0 }] document.getElementsByClassName('systemSun')[0].animate(keyframes1,keyframesOptions)setTimeout(function () {self.sunnum.changeSunNum()document.getElementsByClassName('systemSun')[0].style.left = Math.floor(Math.random() * 200 + 300) + 'px'document.getElementsByClassName('systemSun')[0].style.top = '-100px'}, 2700)}, 1000 * self.sunTimer_difference)self.zombieTimer = setInterval(function () {let idx = self.zombies_iMax - self.zombies_idx - 1if(self.zombies_idx === self.zombies_iMax) { // 僵尸生成數量達到最大值,清除定時器return clearInterval(self.zombieTimer)}if(self.zombies[idx]) {self.zombies[idx].state = self.zombies[idx].state_RUN}self.zombies_idx++},1000 * self.zombieTimer_difference)}setCars(cars_info){let self=thisfor(let car of cars_info.position){let info={x: cars_info.x,y: cars_info.y + 100 * (car.row - 1),row: car.row,}self.cars.push(Car.new(info))}}setCards(cards_info){let self=thisfor (let card of cards_info.position) {let info={name:card.name,row:card.row,sun_val:card.sun_val,timer_spacing: card.timer_spacing,y: cards_info.y + 60 * (card.row - 1),}self.cards.push(Card.new(info))}}//palnt or zombiesetRoles(roles_info){let self=this,type = roles_info.typefor (let role of roles_info.position){let info = {type: roles_info.type,section: role.section,x: roles_info.x + 80 * (role.col - 1),y: roles_info.y + 100 * (role.row - 1),col: role.col,row: role.row,}if(type==='plant'){self.plants.push(Plant.new(info))}else if(type==='zombie'){self.zombies.push(Zombie.new(info))}}}//===========================================start(){let self=thisself.loading = Animation.new({type: 'loading'}, 'write', 55)self.sunnum = SunNum.new()self.setZombiesInfo()self.setCars(self.cars_info)self.setCards(self.cards_info)self.setRoles(self.plants_info)self.setRoles(self.zombies_info)self.game = Game.new()}
}window._main=new Main()
window._main.start()

只對JS中常見的DOM/BOM和基礎語法進行鞏固,后續的CSS代碼和相關圖片資源也會上傳

感謝大家的點贊和關注,你們的支持是我創作的動力!?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85363.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85363.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85363.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

深入理解設計模式之代理模式

深入理解設計模式之&#xff1a;代理模式 一、什么是代理模式&#xff1f; 代理模式&#xff08;Proxy Pattern&#xff09;是一種結構型設計模式。它為其他對象提供一種代理以控制對這個對象的訪問。代理對象在客戶端和目標對象之間起到中介作用&#xff0c;可以在不改變目標…

Ubuntu設置之初始化

安裝SSH服務 # 安裝 OpenSSH Server sudo apt update sudo apt install -y openssh-server# 檢查 SSH 服務狀態 sudo systemctl status ssh # Active: active (running) since Sat 2025-05-31 17:13:07 CST; 6s ago# 重啟服務 sudo systemctl restart ssh自定義分辨率 新…

【仿生機器人】極具前瞻性的架構——認知-情感-記憶“三位一體的仿生機器人系統架構

基于您的深度需求分析&#xff0c;我將為您設計一個全新的"認知-情感-記憶"三位一體的仿生機器人系統架構。以下是經過深度優化的解決方案&#xff1a; 一、核心架構升級&#xff08;三體認知架構&#xff09; 采用量子糾纏式架構設計&#xff1a; 認知三角&#xf…

Python量化交易12——Tushare全面獲取各種經濟金融數據

兩年前寫過Tushare的簡單使用&#xff1a; Python量化交易08——利用Tushare獲取日K數據_skshare- 現在更新一下吧&#xff0c;這兩年用過不少的金融數據庫&#xff0c;akshare&#xff0c;baostock&#xff0c;雅虎的&#xff0c;pd自帶的......發現還是Tushare最穩定最好用&…

python打卡day39@浙大疏錦行

知識點回顧 圖像數據的格式&#xff1a;灰度和彩色數據模型的定義顯存占用的4種地方 模型參數梯度參數優化器參數數據批量所占顯存神經元輸出中間狀態 batchisize和訓練的關系 1. 圖像數據格式 - 灰度圖像 &#xff1a;單通道&#xff0c;像素值范圍通常0-255&#xff0c;形狀為…

源碼解析(二):nnUNet

原文 &#x1f600; nnU-Net 是一個用于生物醫學圖像分割的自配置深度學習框架&#xff0c;可自動適應不同的數據集。可用于處理和訓練可能規模龐大的二維和三維醫學圖像。該系統分析數據集屬性并配置優化的基于 U-Net 的分割流程&#xff0c;無需手動參數調整或深度學習專業知…

clickhouse如何查看操作記錄,從日志來查看寫入是否成功

背景 插入表數據后&#xff0c;因為原本表中就有數據&#xff0c;一時間沒想到怎么查看插入是否成功&#xff0c;因為對數據源沒有很多的了解&#xff0c;這時候就想怎么查看下插入是否成功呢&#xff0c;于是就有了以下方法 具體方法 根據操作類型查找&#xff0c;比如inse…

udp 傳輸實時性測量

UDP&#xff08;用戶數據報協議&#xff09;是一種無連接的傳輸協議&#xff0c;適用于實時性要求較高的應用&#xff0c;如視頻流、音頻傳輸和游戲等。測量UDP傳輸的實時性可以通過多種工具和方法實現&#xff0c;以下是一些常見的方法和工具&#xff1a; 1. 使用 iperf 測試…

pikachu通關教程- over permission

如果使用A用戶的權限去操作B用戶的數據&#xff0c;A的權限小于B的權限&#xff0c;如果能夠成功操作&#xff0c;則稱之為越權操作。 越權漏洞形成的原因是后臺使用了 不合理的權限校驗規則導致的。 水平越權 當我們以Lucy賬號登錄&#xff0c;查詢個人信息時&#xff0c;會有…

nc 命令示例

nc -zv 實用示例 示例 1&#xff1a;測試單個 TCP 端口&#xff08;最常見&#xff09; 目標&#xff1a; 檢查主機 webserver.example.com 上的 80 端口 (HTTP) 是否開放。 nc -zv webserver.example.com 80成功輸出&#xff1a; Connection to webserver.example.com (19…

Redis是什么

注&#xff1a;本人不懂Redis是什么&#xff0c;問的大模型&#xff0c;讓它用生動淺顯的語言向我解釋。為了防止忘記&#xff0c;我把它說的記錄下來。接下來的解釋都是大模型生成的&#xff0c;如果有錯誤的地方歡迎指正 。 Redis 是什么&#xff1f;&#xff08;一句話解釋&…

CVE-2021-28164源碼分析與漏洞復現

漏洞概述 漏洞名稱&#xff1a;Jetty 路徑解析邏輯漏洞導致 WEB-INF 敏感信息泄露 漏洞編號&#xff1a;CVE-2021-28164 CVSS 評分&#xff1a;7.5 影響版本&#xff1a;Jetty 9.4.37 - 9.4.38 修復版本&#xff1a;Jetty ≥ 9.4.39 漏洞類型&#xff1a;路徑遍歷/信息泄露 C…

顛覆傳統!單樣本熵最小化如何重塑大語言模型訓練范式?

顛覆傳統&#xff01;單樣本熵最小化如何重塑大語言模型訓練范式&#xff1f; 大語言模型&#xff08;LLM&#xff09;的訓練往往依賴大量標注數據與復雜獎勵設計&#xff0c;但最新研究發現&#xff0c;僅用1條無標注數據和10步優化的熵最小化&#xff08;EM&#xff09;方法…

自動駕駛系統研發系列—激光雷達感知延遲:自動駕駛安全的隱形隱患?

???? 歡迎來到我的技術小筑,一個專為技術探索者打造的交流空間。在這里,我們不僅分享代碼的智慧,還探討技術的深度與廣度。無論您是資深開發者還是技術新手,這里都有一片屬于您的天空。讓我們在知識的海洋中一起航行,共同成長,探索技術的無限可能。 ?? 探索專欄:學…

【MySQL】事務及隔離性

目錄 一、什么是事務 &#xff08;一&#xff09;概念 &#xff08;二&#xff09;事務的四大屬性 &#xff08;三&#xff09;事務的作用 &#xff08;四&#xff09;事務的提交方式 二、事務的啟動、回滾與提交 &#xff08;一&#xff09;事務的啟動、回滾與提交 &am…

視覺分析明火檢測助力山東化工廠火情防控

視覺分析技術賦能化工廠火情防控&#xff1a;從山東事故看明火與煙霧檢測的應用價值 一、背景&#xff1a;山東化工事故中的火情防控痛點 近期&#xff0c;山東高密友道化學有限公司、淄博潤興化工科技有限公司等企業接連發生爆炸事故&#xff0c;暴露出傳統火情防控手段的局…

【小程序】微信小程序備案失敗,有請DeepSeek閃亮出場,看TA如何快速給出解決方案

&#x1f339;歡迎來到《小5講堂》&#x1f339; &#x1f339;這是《小程序》系列文章&#xff0c;每篇文章將以博主理解的角度展開講解。&#x1f339; &#x1f339;溫馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不對之處望指正&#xff01;&a…

Oracle正則表達式學習

目錄 一、正則表達簡介 二、REGEXP_LIKE(x,匹配項) 三、REGEXP_INSTR 四、REGEXP_SUBSTR 五、REGEXP_REPLACE 一、正則表達簡介 相關網址&#xff1a; https://cloud.tencent.com/developer/article/1456428 https://www.cnblogs.com/lxl57610/p/8227599.html https://…

vscode 代理模式(agent mode),簡單嘗試一下。

1. 起因&#xff0c; 目的: agent mode&#xff0c; 很流行&#xff0c;名氣很大。簡單試試效果&#xff0c;確實很強。agent mode&#xff0c; 取代人工&#xff0c;確實是前進了一大步。 2. 先看效果 效果對比&#xff0c;左邊是 普通的AI 生成的&#xff0c; 右邊是 代理…

貝銳蒲公英工業路由器R300A海外版:支持多國4G頻段,全球組網

為更好地滿足全球部署和企業出海項目的多樣化需求&#xff0c;貝銳蒲公英異地組網工業路由器R300A海外版全新上市&#xff0c;并已正式上架速賣通&#xff01;無論是跨國分支機構協同辦公&#xff0c;還是海外工廠設備遠程運維&#xff0c;R300A海外版都能為企業提供靈活、高性…