飞道的博客

基于JavaScript Canvas的植物大战僵尸,周末爆肝之作,请点个赞再走!

340人阅读  评论(0)

引言:

前两天看到小朋友在玩植物大战僵尸,想起来多年以前自己也经常玩这个游戏,是比较经典的一款休闲游戏,然后就心血来潮就周末写了一个,花了不少时间去找素材和编写代码,感觉上基本的功能是做好了(要上班,没那么多时间搞),写出来大家看看,确实有点爆肝!

效果图:

实现思路

  1. 用两张画布来实现,第一个画布绘制不用更新的东西,比如背景图、按钮、积分图,卡牌图等;
  2. 第二个画布,绘制经常更新的东西,比如僵尸的走动、僵尸吃植物、僵尸死亡、植物的摇摆、豌豆苗发射豌豆、子弹的运动、阳光的产生、阳光的收集等等;
  3. 动画的实现,通过图片的不停切换来实现的,开启一个总定时任务100毫秒重新绘制画布2,当然其他的每个动画都会重新开启定时任务(我称他们为子任务),它们不负责绘制,只负责改变对应的参数,绘制都是由总任务来完成的, 比如僵尸走动动画:开启子任务100毫秒执行一次图片切换,切换到最后一张的时候,返回到第一张,如果要走动的话同时改变图片的位置就好,子任务修改完成后,总任务自然会绘制出来;
  4. 卡牌的实现,目前就写了2张卡牌(向日葵、豌豆苗),给卡牌绘制了相同大小的方形来控制鼠标点击事件,当点击卡牌的时候,会创建对应的植物并且跟随鼠标移动,移动鼠标到合适的位置后点击(田 里面对应的方块),会在对应的位置种植;
  5. 田位置的控制,以方形来划分,每一块可以种植物的区域都用一个小方块来控制,植物就种在对应的方块内,当选择一个卡牌后,鼠标移动到田里面就会标示出来一个方形的区域,标示植物种植在这块区域里面。
  6. 豌豆苗被种植后,会定时的发射子弹,当子弹的位置和僵尸的位置交汇的时候,就判断为击中(处理子弹击中动画、子弹消失、僵尸扣除相应血量、击中的音效等),僵尸血量归零后会停止走动的动画,开启新的倒地动画,倒地完成后删除僵尸,同时累计得到的分数;
  7. 当僵尸的位置和植物的位置交汇的时候,僵尸会停止行走的动画,开启吃的动画(植物被扣除血量、僵尸吃的音效),植物血量归零后,植物对象会被清理;
  8. 阳光有两种产生方式,定时产生和向日葵产生,产生后会开启往下飘的动画,飘到一定范围后停止动画、开启计数器(目前设定为10秒),计数归零没有此阳光依然未被点击收集的话就会消失,在指定时间内点击了该阳光(音效),则会开启往左上角飞行的动画,到终点后阳光消失,阳光分增加(音效);
  9. 结束条件:1)僵尸触及田的最左边--判定为失败。2)得分300--判定为胜利!

实现

绘制背景


  
  1. //绘制背景
  2. Plants.prototype.drawBG=function(){
  3. var image,img,sx= 150,sy= 0,sWidth= 900,sHeight= 600,dx= 0,dy= 0,dWidth= 900,dHeight= 600;
  4. //背景
  5. image = this.imgObj[ 1];
  6. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  7. this.renderArr.push(img);
  8. }

绘制上方的卡牌区域、积分区域,相关按钮


  
  1. //绘制游戏上方的相关图片(卡片等)
  2. Plants.prototype.drawCard=function(){
  3. var image,img,sx= 0,sy= 0,sWidth= 446,sHeight= 87,dx= 0,dy= 0,dWidth= 446,dHeight= 80;
  4. //方形卡片盘
  5. image = this.imgObj[ 2];
  6. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  7. this.renderArr.push(img);
  8. sWidth= 128,sHeight= 31,dx= 450,dy= 0,dWidth= 128,dHeight= 40;
  9. //积分
  10. image = this.imgObj[ 12];
  11. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  12. this.renderArr.push(img);
  13. sWidth= 50,sHeight= 70,dx= 76,dy= 5,dWidth= 50,dHeight= 68;
  14. //方形卡片 太阳花
  15. image = this.imgObj[ 3];
  16. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  17. img.sunCost= 50; //生产一个苗需要50阳光
  18. img.type= 'sun'; //植物类型
  19. this.renderArr.push(img);
  20. this.cardArr.push(img);
  21. sWidth= 50,sHeight= 70,dx= 130,dy= 4,dWidth= 50,dHeight= 70;
  22. //方形卡片 豌豆
  23. image = this.imgObj[ 4];
  24. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  25. img.sunCost= 100; //生产一个苗需要100阳光
  26. img.type= 'wandou'; //植物类型
  27. this.renderArr.push(img);
  28. this.cardArr.push(img);
  29. sWidth= 97,sHeight= 33,dx= 780,dy= 8,dWidth= 97,dHeight= 33;
  30. //开始按钮图片
  31. image = this.imgObj[ 5];
  32. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  33. this.startImage=img;
  34. this.renderArr.push(img);
  35. sWidth= 97,sHeight= 33,dx= 650,dy= 8,dWidth= 97,dHeight= 33;
  36. //创建僵尸图片
  37. image = this.imgObj[ 8];
  38. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  39. this.createZombiesImage=img;
  40. this.renderArr.push(img);
  41. }

点击开始的逻辑

点击开始就是游戏的入口,游戏的大部分功能都是在这个逻辑里面实现,包含

展示开始图片、开启背景音乐、阳光计分显示、积分显示、创建田的背景方形、创建卡牌的背景方形、开启总任务、定时创建太阳光、定时创建僵尸。

展示开始图片


  
  1. //展示开始图片
  2. Plants.prototype.startShow=function(){
  3. var image,img,sx= 0,sy= 0,sWidth= 225,sHeight= 108,dx= this.w /2-110,dy=this.h/ 2 -100,dWidth= 225,dHeight= 108;
  4. image = this.imgObj[ 10];
  5. img = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  6. this.renderArr2.push(img);
  7. var that= this;
  8. setTimeout(function(){
  9. that.clear(img);
  10. }, 2000);
  11. }

这里设置一个延时2秒后自动清除掉这个图片

阳光计分显示


  
  1. //创建阳光分
  2. Plants.prototype.createSunText= function(){
  3. x= 40,y= 74,content= this.sunTotalCount;
  4. var text = new _.Text({
  5. x:x,
  6. y:y,
  7. text:content,
  8. font: '20px ans-serif',
  9. textAlign: 'center',
  10. fill: true,
  11. fillStyle: 'green'
  12. });
  13. this.renderArr2.push(text);
  14. this.sunCountObj=text;
  15. }

积分显示


  
  1. //创建积分
  2. Plants.prototype.createCountText= function(){
  3. x= 530,y= 34,content= this.curCount;
  4. var text = new _.Text({
  5. x:x,
  6. y:y,
  7. text:content,
  8. font: '30px ans-serif',
  9. textAlign: 'center',
  10. fill: true,
  11. fillStyle: 'pink'
  12. });
  13. this.renderArr2.push(text);
  14. this.countObj=text;
  15. }

创建卡牌的背景方形(用于监听鼠标点击卡牌)

根据卡牌数组来创建,循环这个数组,方形的坐标和宽高与卡牌数组元素相对应,并且方形的fillStyle采用rgba来处理,如:rgba(192,192,192,0)  rgba(192,192,192,0.6),当最后一个数字是0的时候卡牌可用,当为0.6的时候,卡牌会被遮罩起来不可用(不可用是在鼠标点击的时候控制,这里只是一个遮罩的效果),当然这里会设置一个参数alive,当它为true表示可用,false则点击无效,鼠标点击的时候就是根据这个参数来控制的。


  
  1. //创建卡片背景方形
  2. Plants.prototype.createCardBGRect=function(){
  3. var x= 0,y= 0,rect,fillStyle,alive;
  4. for( var i= 0;i< this.cardArr.length;i++){
  5. var item= this.cardArr[i];
  6. fillStyle = this.sunTotalCount>=item.sunCost? 'rgba(192,192,192,0)': 'rgba(192,192,192,0.5)';
  7. alive = this.sunTotalCount>=item.sunCost? true: false;
  8. rect = new _.Rect({
  9. x:item.dx,
  10. y:item.dy,
  11. width:item.dWidth,
  12. height:item.dHeight,
  13. fill: true,
  14. fillStyle:fillStyle
  15. })
  16. rect.sunCost=item.sunCost; //设定需要花费的阳光数值
  17. rect.alive=alive;
  18. rect.type=item.type;
  19. this.renderArr2.push(rect);
  20. this.cardRectArr.push(rect);
  21. }
  22. }

创建田的背景方形(用于监听植物的种植)

根据背景上田的规格,来设置好X、Y坐标以及宽高,这样创建的方形就会和背景相对应,种植物的时候就比较好控制了,解释如下:

上图是我自己随便画的,也没有画好、没画全,实际上每个里面都有,并且比较整齐,我把代码稍微修改一下截图,最终的代码肯定不是这样的哦

这样,就把田的区域一块块的覆盖起来,但我们这里也是要用rgba的方式来,种植物的时候才会突出显示


  
  1. //创建植物田背景方形
  2. Plants.prototype.createBGRect= function(){
  3. var x= 0,y= 0,rect;
  4. for( var i= 1;i<= 5;i++){ //5行
  5. y = 75+(i -1)* 100;
  6. for( var j= 1;j<= 9;j++){ //9列
  7. x = 105+(j -1)* 80;
  8. rect = new _.Rect({
  9. x:x,
  10. y:y,
  11. width: 80,
  12. height: 100,
  13. fill: true,
  14. //fillStyle:_.getRandomColor()
  15. fillStyle: 'rgba(0,250,154, 0)'
  16. })
  17. rect.index=i; //标记行数
  18. this.renderArr2.push(rect);
  19. this.bgRectArr.push(rect);
  20. }
  21. }
  22. }

创建阳光

1、向日葵植物创建阳光和定时创建阳光都放到这里了,他们的区别是:定时创建的X坐标随机产生,而向日葵创建的阳光X、Y坐标是根据向日葵的位置来的。

2、设定阳光的分值、阳光的血量、阳光默认运动的终点位置(这个位置可以自己定,我定义Y坐标的是400),阳光为什么有血量呢?这个血量是用来控制消失时间的,比如我设定血量为100,当阳光运动到底部停止运动后,就会开启计算血量的任务,每100毫秒执行一次,让血量 -1,因100毫秒执行10次是1秒,1秒后血量就变成90了,当血量归零后如若依然没有去收集这个阳光,需要让阳光消失,同时关闭此定时器。

3、当点击阳光后,就把计算血量的定时器关闭,开启向左上角运动的动画,当然这里要用到  Math.atan2 根据阳光点击的位置和做上角的位置,计算出角度,然后根据角度利用Math.cos、Math.sin 计算出运动的X、Y的数值,定时器将根据这个数值来运动。

4、运动指定的位置后,要做以下动作:清除运动定时器、阳光积分累加与显示、收集音效开启、阳光消失、卡牌可用状态的更新。


  
  1. //创建阳光
  2. Plants.prototype.createSun= function(plant){
  3. var image,sun,sx= 0,sy= 0,sWidth= 77,sHeight= 74,dx= 0,dy= 70,dWidth= 45,dHeight= 44;
  4. if(plant){ //这种是植物创建的太阳
  5. dx = plant.dx;
  6. dy = plant.dy;
  7. } else{
  8. dx = _.getRandom( 200, 800); //x方形随机200-800
  9. }
  10. //绘制时的图片下标
  11. var startKey= this.count+ this.zombiesRunCount+ this.wandousRunCount+ this.zombiesDeadCount+ 1;
  12. //方形卡片盘
  13. image = this.imgObj[startKey];
  14. sun = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  15. sun.imageKey=startKey; //执行动画更新的下标
  16. sun.key=startKey; //原始下标
  17. sun.value= 20; //收集一个20分
  18. sun.blood= 100; //默认10点血量(10秒消失,因为我给太阳设置的是100毫秒执行一次,所以这个blood设置为100,每次-1 ,10秒就刚好100)
  19. sun.floor= 400; //到底的位置
  20. this.renderArr2.push(sun);
  21. this.suns.push(sun);
  22. sun.timmer = setInterval(animate.bind( this,sun), 100);
  23. function animate(z){
  24. var that= this;
  25. z.imageKey ++;
  26. //一个循环了,重新回到初始位置
  27. if(z.imageKey>=(that.sunRunCount+z.key)){
  28. z.imageKey=z.key;
  29. }
  30. z.image = that.imgObj[z.imageKey];
  31. z.dy+= 2;
  32. if(z.dy>=z.floor){
  33. //console.log('太阳到位置了');
  34. clearInterval(z.timmer);
  35. //开启定时任务,多少秒以后消失
  36. sun.timmer = setInterval(time.bind( this,sun), 100);
  37. }
  38. }
  39. //太阳计时
  40. function time(z){
  41. var that= this;
  42. z.imageKey ++;
  43. //一个循环了,重新回到初始位置
  44. if(z.imageKey>=(that.sunRunCount+z.key)){
  45. z.imageKey=z.key;
  46. }
  47. z.image = that.imgObj[z.imageKey];
  48. //计算消失时间
  49. z.blood--;
  50. if(z.blood<= 0){
  51. clearInterval(z.timmer);
  52. //console.log('太阳到时间了');
  53. fade.call( this,z); //执行消失
  54. }
  55. }
  56. //太阳消失
  57. function fade(z){
  58. this.clear(z);
  59. //console.log('太阳消失了');
  60. this.clearAssign( this.suns,z); //清楚指定对象
  61. z= null;
  62. }
  63. //太阳被点击
  64. function sunClick(z){
  65. //console.log('太阳被点击了')
  66. clearInterval(z.timmer); //清楚之前的定时器
  67. this.pointsMusic.play();
  68. var cx=cy= 20; //收集点的X\Y坐标
  69. var angle = Math.atan2((z.dy-cy), (z.dx-cx)) //弧度
  70. //计算出X\Y每一帧移动的距离
  71. var mx = my= 0;
  72. mx = Math.cos(angle)* 20;
  73. my = Math.sin(angle)* 20;
  74. z.mx=mx,z.my=my;
  75. //开启移动定时器
  76. z.timmer = setInterval(sunCollect.bind( this,z), 100);
  77. }
  78. //收集太阳动画
  79. function sunCollect(z){
  80. var that= this;
  81. z.imageKey ++;
  82. //一个循环了,重新回到初始位置
  83. if(z.imageKey>=(that.sunRunCount+z.key)){
  84. z.imageKey=z.key;
  85. }
  86. z.image = that.imgObj[z.imageKey];
  87. z.dx-=z.mx;
  88. z.dy-=z.my;
  89. if(z.dy<= 20||z.dx<= 20){
  90. //console.log('太阳收集完成');
  91. clearInterval(z.timmer);
  92. this.moneyfallsMusic.play();
  93. fade.call( this,z); //执行消失
  94. //计数累加
  95. that.sunTotalCount+=z.value;
  96. //更新卡片是否可用情况
  97. that.updateCardUse();
  98. //更新阳光数值
  99. that.sunCountObj.text=that.sunTotalCount;
  100. }
  101. }
  102. sun.click=sunClick.bind( this,sun); //给这个sun对象绑定点击函数
  103. }

创建僵尸

1、创建僵尸:定时(10秒)、随机数量(1-5只)、随机行(1-5行,这里表示出现在地图的哪一行)。

2、设定僵尸的血量、僵尸所处的行数、僵尸的状态(run、eat、dead),状态是用来控制切换图片的下标的,不然动画会出错。

3、行走动画依赖于图片的切换和X坐标的改变(每一帧x坐标 减少2即可)。

4、每一帧都要判断x坐标与植物的坐标是否交汇,如果是先关闭行走的动画,更新状态为 eat ,开启吃的动画,切换图片的下标。

5、每一次吃的时候递减植物的血量,判断植物的血量,如果血量归零则表示吃完了,此时要清理掉植物,僵尸回归行走的动画,状态改为run;若没吃完则继续吃。

6、每一帧也要判断僵尸的x坐标是否到了最左边,如果是游戏结束。

创建


  
  1. //创建僵尸
  2. Plants.prototype.createZombie= function(){
  3. var image,zomble,sx= 0,sy= 0,sWidth= 75,sHeight= 119,dx= 900 -75,dy= 270,dWidth= 75,dHeight= 119;
  4. var index = _.getRandom( 1, 6); //随机获取1\2\3\4\5 行数
  5. if(index== 1){
  6. dy= 60;
  7. } else if(index== 2){
  8. dy= 160;
  9. } else if(index== 3){
  10. dy= 260;
  11. } else if(index== 4){
  12. dy= 355;
  13. } else if(index== 5){
  14. dy= 460;
  15. }
  16. //绘制时的图片下标
  17. var startKey= this.count+ 1;
  18. //方形卡片盘
  19. image = this.imgObj[startKey];
  20. zomble = new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  21. zomble.imageKey=startKey; //执行动画更新的下标
  22. zomble.key=startKey; //原始下标
  23. zomble.blood= 10; //默认10点血量
  24. zomble.index=index; //设定僵尸的行数
  25. zomble.state= 'run';
  26. this.renderArr2.push(zomble);
  27. this.zombies.push(zomble);
  28. zomble.run=run.bind( this);
  29. zomble.run();
  30. function run(){
  31. zomble.timmer = setInterval(animate.bind( this,zomble), 100);
  32. }
  33. function animate(z){
  34. var that= this;
  35. z.imageKey ++;
  36. //一个循环了,重新回到初始位置
  37. if(z.imageKey>=(that.zombiesRunCount+z.key)){
  38. z.imageKey=z.key;
  39. }
  40. z.image = that.imgObj[z.imageKey];
  41. z.dx-= 2;
  42. //判断有没有接触到植物,如果有开始吃植物
  43. that.eat(zomble)
  44. if(z.dx<= 100){
  45. console.log( '结束了');
  46. that.end();
  47. }
  48. }
  49. }

僵尸吃


  
  1. //僵尸吃
  2. Plants.prototype.eat=function(zomble){
  3. //先判断当前僵尸有没有到达吃的位置,有的话就开始吃,关闭掉之前的僵尸动画,开始吃的动画
  4. var plants= this.plants;
  5. var plant; //被捕获的植物
  6. for( var i= 0;i<plants.length;i++){
  7. var item=plants[i];
  8. if(item.index==zomble.index){
  9. if(item.dx+item.dWidth- 20>=zomble.dx){ //判断为吃
  10. plant=item;
  11. break;
  12. }
  13. }
  14. }
  15. if(plant){
  16. clearInterval(zomble.timmer); //清除移动动画
  17. zomble.imageKey=zomble.key= this.count+ this.zombiesRunCount+ this.wandousRunCount+ this.zombiesDeadCount+ this.sunRunCount+ 1; //设定key
  18. zomble.state= 'eat';
  19. zomble.timmer = setInterval(animate.bind( this,zomble,plant), 100);
  20. }
  21. function animate(z,p){
  22. this.eatMusic.play();
  23. var that= this;
  24. z.imageKey ++;
  25. //一个循环了,重新回到初始位置
  26. if(z.imageKey>=(that.zombiesEatCount+z.key)){
  27. z.imageKey=z.key;
  28. }
  29. z.image = that.imgObj[z.imageKey];
  30. p.blood--; //植物血量的处理
  31. if(p.blood<= 0){
  32. //console.log('植物被吃了');
  33. clearInterval(z.timmer);
  34. //清除植物
  35. this.delPlant(p);
  36. zomble.state= 'run';
  37. zomble.imageKey=zomble.key= this.count+ 1; //设定key
  38. //继续移动
  39. z.run();
  40. }
  41. }
  42. }

删除植物


  
  1. //删除掉植物
  2. Plants.prototype.delPlant= function(plant,type){
  3. if(! type){ //还没有创建的植物不需要清除这两个任务
  4. //停止植物自身的动画
  5. clearInterval(plant.timmerSelf);
  6. //停止植物发射子弹的动画
  7. clearInterval(plant.timmer);
  8. }
  9. //渲染中删除
  10. this.clear(plant);
  11. //plants数组中删除
  12. this.clearAssign( this.plants,plant);
  13. //植物对应的背景处理
  14. if(plant.bgRect){
  15. plant.bgRect.alive= false;
  16. plant.bgRect.plant= false;
  17. }
  18. plant= null;
  19. }

创建向日葵

1、点击卡牌后的创建,所以肯定是要传入鼠标的位置,创建的跟随鼠标移动(此时向日葵已经创建)

2、设定 alive 函数,当在田里面选中位置后,执行此函数,进行种植操作。

3、扣除阳光花费、更新卡牌是否可用、开启定时产生阳光的任务。


  
  1. //创建太阳植物
  2. Plants.prototype.createSunPlant=function(pos,item){
  3. var image,plant,sx= 0,sy= 0,sWidth= 63,sHeight= 73,dx= 110,dy= 300,dWidth= 63,dHeight= 73;
  4. dx = pos.x,dy=pos.y; //设定初始位置为鼠标的位置
  5. //绘制时的图片下标
  6. var startKey= this.count+ this.zombiesRunCount+ this.wandousRunCount+ this.zombiesDeadCount+ this.sunRunCount+ this.zombiesEatCount+ 1;
  7. //方形卡片盘
  8. image = this.imgObj[startKey];
  9. plant = new _.ImageDraw({image:image,sx:sx,sy:sy,sWidth:sWidth,sHeight:sHeight, dx:dx, dy:dy ,dWidth:dWidth,dHeight:dHeight});
  10. plant.imageKey=startKey; //执行动画更新的下标
  11. plant.key=startKey; //原始下标
  12. plant.sunCost=item.sunCost; //阳光花费值
  13. plant.blood= 50; //设定为50血量,实际是5秒吃完,因为100毫秒计算一次吃
  14. plant.id= 'SunPlant';
  15. this.renderArr2.push(plant);
  16. this.plants.push(plant);
  17. this.currPlant=plant; //标记正在创建的植物
  18. plant.alive=alive.bind( this);
  19. function alive(bgRect){
  20. //设置背景对象
  21. plant.bgRect=bgRect;
  22. //扣除阳光花费
  23. this.sunTotalCount-=plant.sunCost;
  24. //更新卡片是否可用情况
  25. this.updateCardUse();
  26. //更新阳光数值
  27. this.sunCountObj.text= this.sunTotalCount;
  28. //每6秒发射一个阳光
  29. plant.timmer = setInterval(shoot.bind( this), 6000);
  30. this.plantMusic.play(); //音乐
  31. }
  32. function shoot(){
  33. this.createSun(plant);
  34. }
  35. //植物本身的动画
  36. plant.timmerSelf = setInterval(animate.bind( this,plant), 100);
  37. function animate(p){
  38. var that= this;
  39. p.imageKey ++;
  40. //一个循环了,重新回到初始位置
  41. if(p.imageKey>=(that.sunPlantRunCount+p.key)){
  42. p.imageKey=p.key;
  43. }
  44. p.image = that.imgObj[p.imageKey];
  45. }
  46. }

点击卡牌后移动鼠标,向日葵会跟随鼠标移动,移动到田的方形位置,则此块方形的颜色会突出,点击它则会种植下去。

创建豌豆植物

1、与向日葵的创建很相似,就不同的时候向日葵创建的是阳光,豌豆植物创建的是小豌豆,可以攻击僵尸的。

2、子弹在运动的时候、判断是否与僵尸接触,如果接触则执行击中的动画、删除这个子弹、减去僵尸的血量、如果僵尸死亡,则开启僵尸死亡动画,增加积分。

创建


  
  1. //创建豌豆
  2. Plants.prototype.createWandou= function(plant){
  3. var image,img,sx= 0,sy= 0,sWidth= 28,sHeight= 28,dx=plant.dx+ 50,dy=plant.dy,dWidth= 28,dHeight= 28;
  4. //绘制时的图片下标
  5. var startKey= 6;
  6. //方形卡片盘
  7. image = this.imgObj[startKey];
  8. var wandou= new _.ImageDraw({ image:image, sx:sx, sy:sy, sWidth:sWidth, sHeight:sHeight, dx:dx, dy:dy , dWidth:dWidth, dHeight:dHeight});
  9. this.renderArr2.push(wandou);
  10. wandou.index=plant.index; //给子弹设定行数标示
  11. this.shootMusic.play(); //射击音乐
  12. wandou.timmer = setInterval(wandouMove.bind( this,wandou), 100);
  13. function wandouMove(wandou){
  14. wandou.dx+= 10;
  15. //判断击中僵尸
  16. var flag = this.hit(wandou);
  17. if(flag || wandou.dx> 850){ //击中目标 或者 超出边界
  18. clearInterval(wandou.timmer);
  19. if(flag){ //击中目标
  20. //创建子弹击中的图片
  21. this.hitAnimate(wandou);
  22. this.hitMusic.play(); //击中音乐
  23. }
  24. //清除这个子弹
  25. this.clear(wandou);
  26. wandou= null;
  27. }
  28. }
  29. }

击中目标


  
  1. //击中
  2. Plants.prototype.hit=function(obj){
  3. var arr = this.zombies; //僵尸对象
  4. for( var i= 0;i<arr.length;i++){
  5. var item = arr[i];
  6. if(item.dx<=obj.dx+obj.dWidth && obj.index==item.index ){ //子弹的函数标示与僵尸的行数标示也要相等
  7. item.blood--;
  8. if(item.blood== 0){ //消灭这个僵尸
  9. clearInterval(item.timmer);
  10. if(item.state== 'run'){
  11. item.imageKey=item.key=item.key+ this.zombiesRunCount+ this.wandousRunCount; //设置死亡图片下标
  12. } else if(item.state== 'eat'){
  13. item.imageKey=item.key=item.key- this.sunRunCount- this.zombiesDeadCount; //设置死亡图片下标
  14. }
  15. item.state= 'dead';
  16. this.addCount(); //增加积分
  17. this.dead(item); //死亡动画
  18. arr.splice(i, 1);
  19. }
  20. return true;
  21. }
  22. }
  23. }

加入事件控制


  
  1. //给canvas2画布添加鼠标移动事件(因为画布2在上面)
  2. canvas2.addEventListener( 'mousemove', this.mouseMove.bind( this));
  3. //给canvas2画布添加鼠标点击事件(因为画布2在上面)
  4. canvas2.addEventListener( 'click', this.mouseClick.bind( this));
  5. //给canvas2画布添加鼠标右键事件(因为画布2在上面)
  6. canvas2.addEventListener( 'contextmenu', this.contextMenu.bind( this));

鼠标移动事件


  
  1. //鼠标移动事件
  2. Plants.prototype.mouseMove=function(e){
  3. var that= this;
  4. if(that.gameOver) return ; //目前设定的是结束后,要刷新页面才可以开始
  5. if(! this.startImage) return; //防止没加载完成报错
  6. var pos = _.getOffset(e); //获取鼠标位置
  7. var isCatch = this.startImage.isPoint(pos); //鼠标捕捉
  8. if(!isCatch && this.gameAlive){
  9. isCatch = this.createZombiesImage.isPoint(pos); //鼠标捕捉
  10. }
  11. if(!isCatch){
  12. if( this.gameAlive && ! this.currPlant) { //游戏开始,并且没有正在创建的植物的时候可以执行
  13. //循环卡片背景数组
  14. for( var i= 0;i< this.cardRectArr.length;i++){
  15. var item= this.cardRectArr[i];
  16. if(item.isPoint(pos) && item.alive){ //鼠标捕捉
  17. isCatch= true;
  18. break;
  19. }
  20. }
  21. }
  22. }
  23. if(!isCatch){
  24. //循环太阳数组
  25. for( var i= 0;i< this.suns.length;i++){
  26. var item= this.suns[i];
  27. if(item.isPoint(pos)){ //鼠标捕捉
  28. isCatch= true;
  29. break;
  30. }
  31. }
  32. }
  33. var plant = this.currPlant;
  34. if(!isCatch){
  35. if(plant){ //创建植物的时候,才出现填入背景框
  36. //循环田背景数组
  37. for( var i= 0;i< this.bgRectArr.length;i++){
  38. var item= this.bgRectArr[i];
  39. if(item.isPoint(pos) && !item.plant){ //鼠标捕捉,并且当前没有植物
  40. isCatch= true;
  41. item.fillStyle= "rgba(0,250,154, 0.5)";
  42. } else{
  43. item.fillStyle= "rgba(0,250,154, 0)";
  44. }
  45. }
  46. }
  47. }
  48. if(isCatch){
  49. this.el.style.cursor = 'pointer'; //改为手状形态
  50. } else{
  51. this.el.style.cursor = '';
  52. }
  53. //表示当前正在创建植物
  54. if(plant){
  55. plant.dx=pos.x;
  56. plant.dy=pos.y;
  57. }
  58. this.render2();
  59. }

鼠标点击事件


  
  1. //鼠标点击事件
  2. Plants.prototype.mouseClick=function(e){
  3. if( this.gameOver) return ; //目前设定的是结束后,要刷新页面才可以开始
  4. var that= this;
  5. var pos = _.getOffset(e); //获取鼠标位置
  6. var isCatch = that.startImage.isPoint(pos); //鼠标捕捉
  7. if(isCatch){
  8. that.start();
  9. }
  10. if(!isCatch && this.gameAlive){
  11. isCatch = this.createZombiesImage.isPoint(pos); //鼠标捕捉
  12. if(isCatch){
  13. that.createZombie();
  14. }
  15. }
  16. if(!isCatch){
  17. if( this.gameAlive && ! this.currPlant) { //游戏开始,并且没有正在创建的植物的时候可以执行
  18. //循环卡片数组
  19. for( var i= 0;i< this.cardRectArr.length;i++){
  20. var item= this.cardRectArr[i];
  21. if(item.isPoint(pos) && item.alive){ //鼠标捕捉
  22. isCatch= true;
  23. //生成一个新的豌豆苗,跟随鼠标移动
  24. this.createPlant(pos,item); //创建植物
  25. break;
  26. }
  27. }
  28. }
  29. }
  30. if(!isCatch){
  31. //循环太阳数组
  32. for( var i= 0;i< this.suns.length;i++){
  33. var item= this.suns[i];
  34. if(item.isPoint(pos)){ //鼠标捕捉
  35. item.click && item.click();
  36. break;
  37. }
  38. }
  39. }
  40. if(!isCatch){
  41. var plant = this.currPlant;
  42. if(plant){ //正在创建植物
  43. //循环田背景数组
  44. for( var i= 0;i< this.bgRectArr.length;i++){
  45. var item= this.bgRectArr[i];
  46. if(item.plant) continue; //如果当前有植物,则跳过
  47. if(item.isPoint(pos)){ //鼠标捕捉
  48. isCatch= true;
  49. plant.dx=item.x+ 10;
  50. plant.dy=item.y+ 20;
  51. plant.index=item.index; //给plant设置行数
  52. plant.alive(item); //植物生效
  53. item.plant= true; //表示有植物
  54. item.fillStyle= "rgba(0,250,154, 0)";
  55. this.currPlant= null;
  56. break;
  57. }
  58. }
  59. }
  60. }
  61. }

鼠标右键事件


  
  1. //右键事件
  2. Plants.prototype.contextMenu=function(e){
  3. var e = e||window.event;
  4. //取消右键默认事件
  5. e.preventDefault && e.preventDefault();
  6. if( this.gameOver) return ; //目前设定的是结束后,要刷新页面才可以开始
  7. if(! this.startImage) return; //防止没加载完成报错
  8. if(! this.gameAlive) return;
  9. console.log( 'oncontextmenu');
  10. //正在创建的植物删除
  11. this.delPlant( this.currPlant, 1);
  12. this.currPlant= null;
  13. //循环田背景数组
  14. for( var i= 0;i< this.bgRectArr.length;i++){
  15. var item= this.bgRectArr[i];
  16. item.fillStyle= "rgba(0,250,154, 0)";
  17. }
  18. }

总结

基本的功能实现了,但是有很多没有实现的功能这里交代一下,确实没有时间去搞了,要上班。。。。。,不能像在座赚了几个亿的大佬一样逍遥自在:

1、结束后必须刷新页面,才能重新开始(”点击开始“按钮)。

2、卡牌支持的作物也比较少,作物也不能用铲子替换。

3、没有过关的感觉、没有车子压僵尸的场景等等(不说了还有好多...)。

如果作为完整的游戏来说,肯定还有很多功能要做、很多地方要完善、其中相当的代码也可以重构;但是如果作为一个练手、学习、思路的分享,我觉得是足够了。

昨天做好的东西,今天晚上写出来也花了不少时间,能看到这里的都是大佬。

欢迎各位大佬 点赞+评论+关注,谢谢!

源码下载

方式1:少量积分,下载代码

方式2:关注下方公众号,回复 129 下载代码

 更多源码

♥ 基于canvas的九宫格抽奖特效(附源码)

♥ 基于canvas的手风琴特效(附源码)

♥ 抖音很火的华为太空人表盘(附源码)

♥ 基于JavaScript页面动态验证码(附源码)

♥ 基于JavaScript的拖动滑块拼图验证(附源码)

♥ 基于JavaScript的幸运大转盘(附源码)

♥ 抖音很火的罗盘时钟(附源码)

♥ 基于JavaScript的俄罗斯方块小游戏(附源码)

♥ 基于JavaScript的贪吃蛇游戏(附源码)

♥ 基于JavaScript的拼图游戏(附源码)

♥ 用JavaScript给女儿做的烟花特效(附源码)

♥ 老父亲给女儿做的下雪特效,满足女儿看雪的愿望(附源码)

♥ 雷达扫描特效(附源码)

♥ 香港黄金配角吴孟达去世,80后程序员以轮播图来悼念达叔,达叔一路走好!(附源码)

♥ 仿抖音刷新进度条(附源码)

♥ 仿头条方形刷新进度条(附源码)

♥ 仿360加速球、水波评分特效(附源码)

♥ 基于canvas的刮刮卡(附源码)

♥ 原生js写的左侧飞入拼图特效,你是喜欢美女单飞还是双飞(附源码)

♥ 用js写的旋转木马,在新年献给各位刚登基的皇帝,让你的后宫转起来!程序员就是可以为所欲为!(附源码)

♥ 用js写的轮播图,八位女明星,你翻谁的牌,程序员就是可以为所欲为!(附源码)

♥ 原生js实现美女拼图,把美女老婆抱回家,5个美女够不够!程序员就是可以为所欲为!(附源码)

♥ 用js仿探探拖拽卡片的效果、飞卡片的效果,感觉挺酷,最后有美女看哦!(附源码)

♥ 老婆说程序员不懂浪漫,程序员默默拿起了键盘,这就亲手带你去看流星雨,女人真的会影响男人拔刀的速度!(附源码)

♥ 学生成绩管理系统(jsp+jquery+java+mysql+tomcat)有源码,你的毕设我的心(附源码)


转载:https://blog.csdn.net/dkm123456/article/details/115871302
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场