用JS完成植物大战僵尸(前端作业)
1. 先搭架子
整体效果:
点击开始后进入主场景
左侧是植物卡片
右上角是游戏的开始和暂停键
- <!DOCTYPEhtml>
- <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函数, 用来生成图片路径
- constimageFromPath =function(src){
- let img = new Image()
- img.src = './images/' + src
- return img
- }
- // 原生动画参数
- // const keyframesOptions = {
- // iterations: 1,
- // iterationStart: 0,
- // delay: 0,
- // endDelay: 0,
- // direction: 'alternate',
- // duration: 3000,
- // fill: 'forwards',
- // easing: 'ease-out',
- // }
- // 图片素材路径
- constallImg = {
- 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>
- letcanvas=document.getElementById("canvas")
- letcxt=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=this
- cxt.drawImage(self.img,self.x+120,self.y) //用于在Canvas上绘制图像
- cxt.fillStyle='black'
- cxt.font='24px Microsoft YaHei'
- cxt.fontWeight=700
- cxt.fillText(self.sun_num,self.x+175,self.y+27)
- }
- //修改阳光 !!!!!
- changeSunNum(num=25){
- let self=this
- window._main.allSunVal+=num
- self.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) {
- letself =this, marginLeft =120
- if(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=this
- self.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=this
- if(!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){
- letself =this
- self.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 () {
- letself =this
- for (letzombieof 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 = 0
- zombie.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 + 30
- b.y= plant.y
- break
- case 'repeater':
- b.x= plant.x + 30
- b.y= plant.y
- break
- case 'gatlingpea':
- b.x= plant.x + 30
- b.y= plant.y + 10
- break
- }
- return b
- }
- draw(game,cxt){
- let self=this
- self.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.width
- role.h = this.height
- }
- }
- return a
- }
- /**
- * 为角色不同动作创造动画序列
- */
- create () {
- letself =this,
- section = self.section // 植物种类
- switch(self.type) {
- case 'plant':
- for(let i = 0; i < allImg.plants[section][self.action].len; i++){
- letidx = i <10 ? '0'+ i : i,
- path = allImg.plants[section][self.action].path
- // 依次添加动画序列
- self.images.push(imageFromPath(path.replace(/\*/, idx)))
- }
- break
- case 'zombie':
- // 濒死动画、死亡动画对象,包含头部动画以及身体动画
- if(self.action === 'dying'|| self.action === 'die') {
- for(let i = 0; i < allImg.zombies[self.action].head.len; i++){
- letidx = 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++){
- letidx = 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++){
- letidx = i <10 ? '0'+ i : i,
- path = allImg.zombies[self.action].path
- // 依次添加动画序列
- self.images.push(imageFromPath(path.replace(/\*/, idx)))
- }
- }
- break
- case 'loading': // loading动画
- for(let i = 0; i < allImg.loading[self.action].len; i++){
- letidx = 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 值,用于设置当前角色 ID
- type: 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 () {
- letself =this
- self.sunTimer = setInterval(function () {
- // 创建阳光元素
- let img = document.createElement('img'), // 创建元素
- container =document.getElementsByTagName('body')[0], // 父级元素容器
- id = self.id, // 当前角色 ID
- top = 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' + id
- img.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 () {
- letself =this
- clearInterval(self.sunTimer)
- }
- // 初始化
- init () {
- letself =this,
- setPlantFn =null
- // 初始化植物动画对象方法集
- setPlantFn = {
- sunflower () {// 向日葵
- self.idle = Animation.new(self,'idle', 12)
- // 定时生成阳光
- self.canSetTimer&& self.setSunTimer()
- },
- peashooter () {// 豌豆射手
- self.canShoot = true
- self.idle = Animation.new(self,'idle', 12)
- self.attack = Animation.new(self,'attack', 12)
- },
- repeater () {// 双发射手
- self.canShoot = true
- self.idle = Animation.new(self,'idle', 12)
- self.attack = Animation.new(self,'attack', 8)
- },
- gatlingpea () {// 加特林射手
- // 改变加特林渲染 y 轴距离
- self.y -= 12
- self.canShoot = true
- self.idle = Animation.new(self,'idle', 8)
- self.attack = Animation.new(self,'attack', 4)
- },
- cherrybomb () {// 樱桃炸弹
- self.x -= 15
- self.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 = 5
- self.y -= 45
- self.idle = Animation.new(self,'idle', 10)
- self.attack = Animation.new(self,'attack', 12)
- self.digest = Animation.new(self,'digest', 12)
- },
- }
- // 执行对应植物初始化技巧
- for (let key insetPlantFn) {
- if(self.section=== key) {
- setPlantFn[key]()
- }
- }
- }
- // 绘制途径
- draw (cxt) {
- letself =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)
- }
- break
- case true:
- // 受伤或移动植物时,绘制半透明图片
- cxt.globalAlpha = 0.5
- cxt.beginPath()
- cxt.drawImage(self[stateName].img, self.x, self.y)
- cxt.closePath()
- cxt.save()
- cxt.globalAlpha = 1
- break
- }
- }
- // 更新状态
- update (game) {
- letself =this,
- section = self.section,
- stateName = self.switchState()
- // 修改当前动画序列长度
- letanimateLen = 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 () {
- letself =this
- // 植物类别为向日葵和坚果墙时,不需判定
- if(self.section === 'sunflower'|| self.section === 'wallnut') return false
- // 循环僵尸对象数组
- for (letzombieof 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 = true
- setTimeout(()=> {
- zombie.isHurt = false
- }, 200)
- }
- if(zombie.life === 2) {
- zombie.changeAnimation('dying')
- } else if(zombie.life === 0) {
- zombie.changeAnimation('die')
- }
- }
- })
- }
- }
- }
- }
- // 射击方法
- shoot () {
- letself =this
- self.bullets[self.bullets.length] = Bullet.new(self)
- }
- /**
- * 判断角色状态并返回对应动画对象名称方法
- */
- switchState () {
- letself =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 indictionary) {
- if(state === dictionary[key]) {
- return key
- }
- }
- }
- /**
- * 切换角色动画
- * game => 游戏引擎对象
- * action => 动作类型
- * -idle: 站立动画
- * -idleH: 角色高血量动画(坚果墙)
- * -idleM: 角色中等血量动画(坚果墙)
- * -idleL: 角色低血量动画(坚果墙)
- * -attack: 攻击动画
- * -digest: 消化动画(食人花)
- */
- changeAnimation (action) {
- letself =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)return
- self.state= dictionary[action]
- }
- }
- // 僵尸类
- class Zombie extends Role{
- constructor(obj) {
- super(obj)
- // 僵尸类私有属性
- let z = {
- life: 10, // 角色血量
- canMove: true, // 判断当前角色是否可移动
- attackPlantID: 0, // 当前攻击植物对象 ID
- idle: null, // 站立动画对象
- run: null, // 奔跑动画对象
- attack: null, // 攻击动画对象
- dieboom: null, // 被炸死亡动画对象
- dying: null, // 濒临死亡动画对象
- die: null, // 死亡动画对象
- state: 1, // 保存当前状态值,默认为1
- state_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 () {
- letself =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) {
- letself =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.5
- cxt.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.5
- cxt.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) {
- letself =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') { // 普通动画(站立,移动,攻击)
- // 修改当前动画序列长度
- letanimateLen = 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 = 0
- self[stateName].imgIdx = 0
- if(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') { // 濒死动画,包含两个动画对象
- // 获取当前动画序列长度
- letheadAnimateLen = 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.x
- self.head_y= self.y
- self[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 = 0
- self[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') { // 死亡动画,囊括两个动画对象
- // 获取当前动画序列长度
- letheadAnimateLen = 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.x
- self.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 = true
- self[stateName].imgBody= self[stateName].images.body[bodyAnimateLen -1]
- } else {
- self[stateName].imgBody= self[stateName].images.body[self[stateName].imgIdxBody]
- }
- }
- }
- // 检测僵尸是否可攻击植物
- canAttack () {
- letself =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.attackPlantID
- self.changeAnimation('attack')
- } else {
- self.canMove = false
- }
- if(self.isAnimeLenMax&& self.life > 2) { // 僵尸动画每执行完一轮次
- // 扣除植物血量
- if(plant.life !== 0) {
- plant.life--
- plant.isHurt = true
- setTimeout(()=> {
- 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 () {
- letself =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 indictionary) {
- if(state === dictionary[key]) {
- return key
- }
- }
- }
- /**
- * 切换角色动画
- * game => 游戏引擎对象
- * action => 动作类型
- * -idle: 站立不动
- * -attack: 攻击
- * -die: 死亡
- * -dying: 濒死
- * -dieboom: 爆炸
- * -digest: 被消化
- */
- changeAnimation (action) {
- letself =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)return
- self.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, // 游戏状态值,初始默认为 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, // 动画帧数
- }
- 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.cars
- cars.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.cards
- for(let card ofcards){
- 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].len
- if(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 - 440
- for(let item ofplants){
- 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.y
- cxt.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.context
- plants.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 ofdelPlantsArr) {
- if(zombie.attackPlantID=== plant.id) {
- zombie.canMove = true
- if(zombie.life > 2){
- zombie.changeAnimation('run')
- }
- }
- }
- })
- }
- getMousePos(){
- let g = this,_main=window._main,cxt=g.context,cards=_main.cards,x=g.mouseX,y=g.mouseY
- if(g.canDrawMousePlant){
- g.mousePlantCallback(x,y)
- }
- }
- drawMousePlant(plant_info){
- let g = this,cxt = g.context,plant =null
- letmousePlant_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=true
- plant.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) + 1
- letplant_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 = row
- g.mouseCol = col
- if(row>=1&&row<=5&&col>=1&&col<=9){
- g.canLayUp=true
- for(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
- letactions =Object.keys(g.actions)
- for (let i = 0; i < actions.length; i++) {
- letkey = 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) {
- lete = 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+ scrollY
- g.mouseX = x
- g.mouseY = y
- }
- document.getElementById('js-startGame-btn').onclick = function () {
- g.state = g.state_START
- setTimeout(function () {
- g.state = g.state_RUNNING
- document.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 () {
- letplant =null,cards = _main.cards
- if(cards[idx].canClick) {
- g.cardSection = this.dataset.section
- g.canDrawMousePlant = true
- g.cardSunVal = {
- idx: idx,
- val: cards[idx].sun_val,
- }
- }
- }
- })
- document.getElementById('canvas').onclick = function (event) {
- letplant =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 = false
- g.mousePlant = null
- }
- }
- if (g.canLayUp&& g.canDrawMousePlant) {
- letcardSunVal = g.cardSunVal
- if(cardSunVal.val<= _main.allSunVal) {
- cards[cardSunVal.idx].canClick = false
- cards[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 = false
- g.mousePlant = null
- }
- } else {
- g.canDrawMousePlant = false
- g.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_RUNNING
- for (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 () {
- letself =this,
- iMax = self.zombies_iMax
- for(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=this
- clearInterval(self.sunTimer)
- clearInterval(self.zombieTimer)
- for(let plant of self.plants){
- if(plant.section=='sunflower'){
- plant.clearSunTimer()
- }
- }
- }
- // 设置全局阳光、僵尸生成定时器
- setTimer(){
- let self=this,zombies=self.zombies
- self.sunTimer = setInterval(function () {
- letleft =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 () {
- letidx = self.zombies_iMax- self.zombies_idx - 1
- if(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=this
- for(let car ofcars_info.position){
- letinfo={
- 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=this
- for (let card ofcards_info.position) {
- letinfo={
- 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 zombie
- setRoles(roles_info){
- let self=this,type = roles_info.type
- for (let role ofroles_info.position){
- letinfo = {
- 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=this
- self.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代码和相关图片资源也会上传
感谢大家的点赞和关注,你们的支持是我创作的动力!
浙公网安备 33010602011771号