文章目录

  • 1.植物大战僵尸
  • 2.开发前导
  • 3.载入页面
  • 4.菜单页面 & 预备战斗页面
  • 5.植物准备页面
  • 6.正式战斗页面
  • 7.战斗逻辑
  • 8.向日葵逻辑
  • 9.进度条逻辑
  • 10.音乐逻辑
  • 11.结果展示

1.植物大战僵尸

要想制作一款游戏,首先要对它进行分析。

首先分析它的功能

  • logo:运营公司的logo、开发公司的logo、游戏中的角色
  • “加载中”字样
  • 菜单页面
  • 地图移动
  • 展现僵尸
  • 选择植物框
  • 已选植物框
  • 选择/取消选择
  • 开始战斗
  • 地图移动回去
  • 准备…好…开始
  • 加载僵尸
  • 安放植物
  • 僵尸攻击植物
  • 植物攻击僵尸
  • 游戏的进度展示
  • 太阳花产生太阳
  • 声音播放
  • 收集太阳

分析完需求之后,让我们新建一个项目,然后开始着手制作这款游戏吧!

2.开发前导

当然,第一步仍然需要导入cocos2d-android.jar包,这样才能让我们以后更好开发,导入jar包后如图所示:

第二步,为了更好的游戏显示效果,推荐修改屏幕的样式为全屏,修改AndroidManifest.xml,如图所示:

第三步,修改MainActivity,直接套用上一个项目里的相关代码,代码如下:

package com.example.zombievsplantdemo;import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.opengl.CCGLSurfaceView;
import org.cocos2d.sound.SoundEngine;public class MainActivity extends AppCompatActivity {/*** 导演*/CCDirector director = CCDirector.sharedDirector();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//调用顺序:视图(CCGLSurfaceView) -》 导演(CCDirector) -》 场景(CCScene) -》 图层(CCLayer) -》 精灵(CCSprite) -》 动作(CCMove)// 获取视图CCGLSurfaceView view = new CCGLSurfaceView(this); // 创建一个SurfaceView,类似导演眼前的小屏幕,必须传this,底层要强转成ActvitysetContentView(view);// 获取导演的单例对象director.attachInView(view); // 开启绘制线程的方法director.setDisplayFPS(true); // 显示帧率,表示每秒刷新页面的次数。一般当帧率大于30帧时,基本上人眼看起来比较流畅,帧率和手机性能与程序性能有关director.setAnimationInterval(1/60f); // 设置最高帧率位60director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft); // 设置屏幕方式为横屏显示director.setScreenSize(480,320); // 设置分辨率,用于屏幕适配,会基于不同大小的屏幕等比例缩放,设置我们开发时候的分辨率// 获取场景对象CCScene scene = CCScene.node();// 获取图层对象//FirstLayer layer = new FirstLayer();//ActionLayer layer = new ActionLayer();DemoLayer layer = new DemoLayer();// 配置环境scene.addChild(layer); // 给场景添加图层director.runWithScene(scene); // 导演运行场景}@Overrideprotected void onResume() {super.onResume();director.resume(); // 游戏继续SoundEngine.sharedEngine().resumeSound(); // 音乐继续}@Overrideprotected void onPause() {super.onPause();director.pause(); // 游戏暂停SoundEngine.sharedEngine().pauseSound(); // 音乐暂停}@Overrideprotected void onDestroy() {super.onDestroy();director.end(); // 游戏结束}
}

3.载入页面

  1. 由于是新建的项目,自然就没有了图层。所以这里在新建一个名为layer的包,然后在该包里新建一个BaseLayer,作为所有图层的基类,在该类中通过CCDirector.sharedDirector().winSize()来获取屏幕的宽高,代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.layers.CCLayer;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.types.CGSize;/*** 基类图层*/
public class BaseLayer extends CCLayer {/*** 屏幕的宽高*/public CGSize size  = CCDirector.sharedDirector().winSize();public BaseLayer() {}
}
  1. 在Layer包下新建一个WelcomeLayer,用于作为欢迎界面的图层,继承BaseLayer。然后编写intitLogo()方法、showLogo()方法、showWelcome()方法,分别实现相应的逻辑。注意:在添加WelcomeLayer后,记得在MainActivity中同步调用WelcomeLayer,作为该应用程序第一个显示的图层,代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCSprite;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logoCCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);}
}
  1. 在assets导入以下图片(fps_image.png),不然应用程序会崩溃,如图所示:

除此之外,还需要导入相关的资源文件(image文件夹),将整个文件夹放入assets目录中,资源文件的获取地址如下:

http://pan.baidu.com/s/1kVScGXT 密码:zwet

  1. 修改WelcomeLayer,增加进度条动画方法showLoading(),代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;import java.util.ArrayList;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logo// 初始化欢迎页面CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);// 初始化加载中的页面CCSprite load = CCSprite.sprite("image/loading/loading_01.png"); // 初始化欢迎界面load.setPosition(size.width / 2 , 30);this.addChild(load);// 获取加载动画CCAction animate = showLoading();load.runAction(animate);}/*** 4.进度条读取页面*/private CCAction showLoading(){ArrayList<CCSpriteFrame> frames = new ArrayList<>();String format = "image/loading/loading_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)// 初始化7帧图片for (int i = 1; i <= 9 ; i++) {frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());}CCAnimation animation = CCAnimation.animation("loading",.2f,frames); // 第二个参数表示每一帧显示时间CCAnimate animate = CCAnimate.action(animation,false); // 第二个参数为false表示只执行一次,默认是truereturn animate;}
}
  1. 为了简化开发过程,需要新建一些工具类。新增名字为util的包,然后在该包下新建工具类CommonUtil,增加动画方法animate(),代码如下:
package com.example.zombievsplantdemo.util;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;import java.util.ArrayList;/*** 工具基类*/
public class CommonUtil {/*** 动画加载方法* @param format 文件名* @param num 动画帧数* @param repeat 是否循环播放* @return 返回一个CCAction对象*/public static CCAction animate(String format,int num,boolean repeat){ArrayList<CCSpriteFrame> frames = new ArrayList<>();for (int i = 1; i <= num ; i++) {frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());}CCAnimation animation = CCAnimation.animation("loading",.2f,frames); // 第二个参数表示每一帧显示时间if (!repeat){CCAnimate animate = CCAnimate.action(animation,false); // 第二个参数为false表示只执行一次,默认是truereturn animate;}else {CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是trueCCRepeatForever r = CCRepeatForever.action(animate);return r;}}
}
  1. 修改WelcomeLayer,删除showLoading()方法,简化代码,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCSprite;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logo// 初始化欢迎页面CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);// 初始化加载中的页面CCSprite load = CCSprite.sprite("image/loading/loading_01.png"); // 初始化欢迎界面load.setPosition(size.width / 2 , 30);this.addChild(load);// 获取加载动画,使用工具类封装CCAction animate = CommonUtil.animate("image/loading/loading_%02d.png", 9, false);load.runAction(animate);}
}
  1. 修改WelcomeLayer,在“加载中”动画加载完成后转变成“游戏开始”的字样,注意这里建立了一个AsyncTask,用于模拟耗时操作,并且在操作结束后将“游戏开始”显示出来,代码如下:
package com.example.zombievsplantdemo.layer;import android.os.AsyncTask;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCSprite;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;/*** “开始游戏”按钮对象*/private CCSprite start;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);// 异步在后台初始化数据new AsyncTask<Void,Void,Void>(){@Overrideprotected Void doInBackground(Void... voids) {try {// 模拟耗时操作Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}return null;}@Overrideprotected void onPostExecute(Void result) {start.setVisible(true); // 显示点击开始按钮}}.execute();}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logo// 初始化欢迎页面CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);// 初始化加载中的页面CCSprite load = CCSprite.sprite("image/loading/loading_01.png"); // 初始化欢迎界面load.setPosition(size.width / 2 , 30);this.addChild(load);// 初始化加载完后的动画(点击开始)start = CCSprite.sprite("image/loading/loading_start.png"); // 初始化欢迎界面start.setPosition(size.width / 2 , 30);start.setVisible(false); // 隐藏按钮this.addChild(start);// 获取加载动画,使用工具类封装CCAction animate = CommonUtil.animate("image/loading/loading_%02d.png", 9, false);load.runAction(animate);}
}
  1. 在Layer包下新建游戏主菜单与层MenuLayer,添加showMainMenu()方法,代码如下:

    package com.example.zombievsplantdemo.layer;import org.cocos2d.nodes.CCSprite;/*** 菜单图层*/
    public class MenuLayer extends BaseLayer{public MenuLayer() {showMainMenu();}/*** 1.初始化主菜单页面*/private void showMainMenu(){CCSprite background = CCSprite.sprite("image/menu/main_menu_bg.jpg");background.setAnchorPoint(0,0);this.addChild(background);}
    }
    
  2. 修改WelcomeLayer,为“游戏开始”按钮注册点击事件,添加切换图层方法changeLayer()。注意:这里要在“游戏按钮”出现时允许图层可以触摸,代码如下:

package com.example.zombievsplantdemo.layer;import android.os.AsyncTask;
import android.view.MotionEvent;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;/*** “开始游戏”按钮对象*/private CCSprite start;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);// 异步在后台初始化数据new AsyncTask<Void,Void,Void>(){@Overrideprotected Void doInBackground(Void... voids) {try {// 模拟耗时操作Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}return null;}@Overrideprotected void onPostExecute(Void result) {start.setVisible(true); // 显示点击开始按钮setIsTouchEnabled(true); // 开启触摸事件}}.execute();}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logo// 初始化欢迎页面CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);// 初始化加载中的页面CCSprite load = CCSprite.sprite("image/loading/loading_01.png"); // 初始化欢迎界面load.setPosition(size.width / 2 , 30);this.addChild(load);// 初始化加载完后的动画(点击开始)start = CCSprite.sprite("image/loading/loading_start.png"); // 初始化欢迎界面start.setPosition(size.width / 2 , 30);start.setVisible(false); // 隐藏按钮this.addChild(start);// 获取加载动画,使用工具类封装的方法CCAction animate = CommonUtil.animate("image/loading/loading_%02d.png", 9, false);load.runAction(animate);}/*** 4.切换图层*/private void changeLayer(){CCScene scene = CCScene.node();scene.addChild(new MenuLayer());CCJumpZoomTransition transition = CCJumpZoomTransition.transition(2, scene); // 添加转场效果CCDirector.sharedDirector().replaceScene(transitions); // 表示导演要切换场景}/*** 5.为“开始游戏”按钮注册点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);if (CGRect.containsPoint(start.getBoundingBox(),point)){changeLayer();}return super.ccTouchesBegan(event);}
}
  1. 为了简化开发过程,修改工具类CommonUtil,将图层切换方法changeLayer()封装进去,代码如下:
package com.example.zombievsplantdemo.util;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.transitions.CCFadeTransition;import java.util.ArrayList;/*** 工具类*/
public class CommonUtil {/*** 动画加载方法* @param format 文件名* @param num 动画帧数* @param repeat 是否循环播放* @return 返回一个CCAction对象*/public static CCAction animate(String format,int num,boolean repeat){ArrayList<CCSpriteFrame> frames = new ArrayList<>();for (int i = 1; i <= num ; i++) {frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());}CCAnimation animation = CCAnimation.animation("loading",.2f,frames); // 第二个参数表示每一帧显示时间if (!repeat){CCAnimate animate = CCAnimate.action(animation,false); // 第二个参数为false表示只执行一次,默认是truereturn animate;}else {CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是trueCCRepeatForever r = CCRepeatForever.action(animate);return r;}}/*** 用淡入淡出效果实现转场* @param layer*/public static void changeLayer(CCLayer layer){CCScene scene = CCScene.node();scene.addChild(layer);CCFadeTransition transition = CCFadeTransition.transition(1,scene); // 淡入淡出效果CCDirector.sharedDirector().replaceScene(transition); // 表示导演要切换场景}
}
  1. 修改WelcomeLayer,删除changeLayer()方法,简化代码结构,代码如下:
package com.example.zombievsplantdemo.layer;import android.os.AsyncTask;
import android.view.MotionEvent;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.instant.CCHide;
import org.cocos2d.actions.instant.CCShow;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;/*** 欢迎页面图层*/
public class WelcomeLayer extends BaseLayer {/*** logo对象*/private CCSprite logo;/*** “开始游戏”按钮对象*/private CCSprite start;public WelcomeLayer() {initLogo();// 初始化logoshowLogo(); // 展示logo}/*** 1.logo初始化*/private void initLogo(){logo = CCSprite.sprite("image/popcap_logo.png");logo.setPosition(size.width / 2 , size.height / 2); // 屏幕居中this.addChild(logo);}/*** 2.展示logo*/private void showLogo(){CCHide hide = CCHide.action(); // 表示隐藏动作CCDelayTime delay = CCDelayTime.action(1); // 延时一秒钟CCShow show = CCShow.action(); // 表示显示动作CCSequence sequence = CCSequence.actions(hide,delay,show,delay,hide,delay,CCCallFunc.action(this,"showWelcome")); // 一上来先隐藏logo,过一秒后显示logo,再过一秒钟后继续隐藏logologo.runAction(sequence);// 异步在后台初始化数据new AsyncTask<Void,Void,Void>(){@Overrideprotected Void doInBackground(Void... voids) {try {// 模拟耗时操作Thread.sleep(6000);} catch (InterruptedException e) {e.printStackTrace();}return null;}@Overrideprotected void onPostExecute(Void result) {start.setVisible(true); // 显示点击开始按钮setIsTouchEnabled(true); // 开启触摸事件}}.execute();}/*** 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!*/public void showWelcome(){logo.removeSelf(); // 删除logo// 初始化欢迎页面CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面welcome.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(welcome);// 初始化加载中的页面CCSprite load = CCSprite.sprite("image/loading/loading_01.png"); // 初始化欢迎界面load.setPosition(size.width / 2 , 30);this.addChild(load);// 初始化加载完后的动画(点击开始)start = CCSprite.sprite("image/loading/loading_start.png"); // 初始化欢迎界面start.setPosition(size.width / 2 , 30);start.setVisible(false); // 隐藏按钮this.addChild(start);// 获取加载动画,使用工具类封装的方法CCAction animate = CommonUtil.animate("image/loading/loading_%02d.png", 9, false);load.runAction(animate);}/*** 4.为“开始游戏”按钮注册点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);if (CGRect.containsPoint(start.getBoundingBox(),point)){CommonUtil.changeLayer(new MenuLayer());}return super.ccTouchesBegan(event);}
}

4.菜单页面 & 预备战斗页面

  1. 修改MenuLayer,使用CCMenu注册一个按钮“开始冒险吧”,并添加它的点击事件逻辑,代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.menus.CCMenu;
import org.cocos2d.menus.CCMenuItemSprite;
import org.cocos2d.nodes.CCSprite;/*** 菜单图层*/
public class MenuLayer extends BaseLayer{public MenuLayer() {showMainMenu();showStart();}/*** 1.初始化主菜单页面*/private void showMainMenu(){CCSprite background = CCSprite.sprite("image/menu/main_menu_bg.jpg");background.setAnchorPoint(0,0);this.addChild(background);}/*** 2.主菜单的逻辑*/private void showStart(){// 默认的按钮CCSprite normalStart = CCSprite.sprite("image/menu/start_adventure_default.png");// 被选中的按钮CCSprite selectStart = CCSprite.sprite("image/menu/start_adventure_press.png");CCMenu menu = CCMenu.menu();CCMenuItemSprite items = CCMenuItemSprite.item(normalStart,selectStart,this,"clickStart");menu.addChild(items);menu.setScale(0.5f);menu.setPosition(size.width / 2 - 25,size.height / 2 - 110);menu.setRotation(4.5f);this.addChild(menu);}/*** 3.“开始冒险吧”的点击事件* @param obj 必须携带的参数,不然无法被反射到*/public void clickStart(Object obj){System.out.println("点击!");}
}
  1. 在layer包下新建FightLayer,用于表示战斗场景的图层,并在loadMap()方法中加载地图,代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.layers.CCTMXTiledMap;/*** 战斗图层*/
public class FightLayer extends BaseLayer{public FightLayer() {loadMap();}/*** 1.加载地图*/private void loadMap(){CCTMXTiledMap map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);}
}
  1. 修改MenuLayer,在跳转方法中调用工具类的图层切换changeLayer方法,跳转到战斗图层FightLayer中,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.menus.CCMenu;
import org.cocos2d.menus.CCMenuItemSprite;
import org.cocos2d.nodes.CCSprite;/*** 菜单图层*/
public class MenuLayer extends BaseLayer{public MenuLayer() {showMainMenu();showStart();}/*** 1.初始化主菜单页面*/private void showMainMenu(){CCSprite background = CCSprite.sprite("image/menu/main_menu_bg.jpg");background.setAnchorPoint(0,0);this.addChild(background);}/*** 2.主菜单的逻辑*/private void showStart(){// 默认的按钮CCSprite normalStart = CCSprite.sprite("image/menu/start_adventure_default.png");// 被选中的按钮CCSprite selectStart = CCSprite.sprite("image/menu/start_adventure_press.png");CCMenu menu = CCMenu.menu();CCMenuItemSprite items = CCMenuItemSprite.item(normalStart,selectStart,this,"clickStart");menu.addChild(items);menu.setScale(0.5f);menu.setPosition(size.width / 2 - 25,size.height / 2 - 110);menu.setRotation(4.5f);this.addChild(menu);}/*** 3.“开始冒险吧”的点击事件* @param obj 必须携带的参数,不然无法被反射到*/public void clickStart(Object obj){CommonUtil.changeLayer(new FightLayer());}
}
  1. 修改FightLayer,加载僵尸的坐标点,代码如下:
package com.example.zombievsplantdemo.layer;import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;
import java.util.HashMap;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;public FightLayer() {loadMap();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);loadZombiePoint();}/*** 2.加载僵尸的坐标点集合* @return*/private ArrayList<CGPoint> loadZombiePoint(){ArrayList<CGPoint> tempPoints = new ArrayList<>();CCTMXObjectGroup zombies = map.objectGroupNamed("zombies");ArrayList<HashMap<String, String>> zombiesPoint = zombies.objects;for (HashMap<String, String> map : zombiesPoint) {Integer x  = Integer.parseInt(map.get("x"));Integer y  = Integer.parseInt(map.get("y"));tempPoints.add(ccp(x,y));}return tempPoints;}
}
  1. 为了简化开发过程,修改工具类CommonUtil,将加载坐标方法loadPoint()封装进去,代码如下:
package com.example.zombievsplantdemo.util;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCScene;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCNode;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.transitions.CCFadeTransition;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;
import java.util.HashMap;import static org.cocos2d.types.CGPoint.ccp;/*** 工具类*/
public class CommonUtil {/*** 动画加载方法* @param format 文件名* @param num 动画帧数* @param repeat 是否循环播放* @return 返回一个CCAction对象*/public static CCAction animate(String format,int num,boolean repeat){ArrayList<CCSpriteFrame> frames = new ArrayList<>();for (int i = 1; i <= num ; i++) {frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());}CCAnimation animation = CCAnimation.animation("loading",.2f,frames); // 第二个参数表示每一帧显示时间if (!repeat){CCAnimate animate = CCAnimate.action(animation,false); // 第二个参数为false表示只执行一次,默认是truereturn animate;}else {CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是trueCCRepeatForever r = CCRepeatForever.action(animate);return r;}}/*** 用淡入淡出效果实现转场* @param layer*/public static void changeLayer(CCLayer layer){CCScene scene = CCScene.node();scene.addChild(layer);CCFadeTransition transition = CCFadeTransition.transition(1,scene); // 淡入淡出效果CCDirector.sharedDirector().replaceScene(transition); // 表示导演要切换场景}/*** 加载坐标蒂娜* @param map 地图* @param groupName 组名* @return*/public static ArrayList<CGPoint> loadPoint(CCTMXTiledMap map,String groupName){ArrayList<CGPoint> tempPoints = new ArrayList<>();CCTMXObjectGroup tempPointGruops = map.objectGroupNamed(groupName);ArrayList<HashMap<String, String>> zombiesPoint = tempPointGruops.objects;for (HashMap<String, String> tempmap : zombiesPoint) {Integer x  = Integer.parseInt(tempmap.get("x"));Integer y  = Integer.parseInt(tempmap.get("y"));tempPoints.add(CCNode.ccp(x,y));}return tempPoints;}
}
  1. 修改FightLayer,优化代码结构,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;public FightLayer() {loadMap();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");}
}
  1. 修改FightLayer,为了模拟《植物大战僵尸》的效果,添加地图移动的方法,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;public FightLayer() {loadMap();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move);map.runAction(sequence);}
}
  1. 为了让僵尸显示到地图上,新建一个domain包,在该包下新建Zombie类,用于表示僵尸的实体类,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.nodes.CCSprite;/*** 僵尸的实体类*/
public class Zombie extends CCSprite {public Zombie(){super("image/zombies/zombies_1/shake/z_1_01.png"); // 初始化僵尸图片setScale(0.5); // 设置僵尸大小setAnchorPoint(0.5f,0); // 设置锚点为两脚之间CCAction animate = CommonUtil.animate("image/zombies/zombies_1/shake/z_1_%02d.png", 2, true);runAction(animate); // 让僵尸去颤抖}
}
  1. 修改FightLayer,编写loadZombie()方法,用于让僵尸显示到场景中,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move);map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}
}
  1. 修改FightLayer,编写showPlantBox()、showSelectedBox()和showChooseBox()方法,用于显示可选的植物框到页面上,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);}
}
  1. 修改FightLayer,在植物盒子中添加植物图标,坐标计算规则如下图:

代码如下:

package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);String format = "image/fight/chose/choose_default%02d.png";for (int i = 1; i <= 9 ; i++) {CCSprite plant = CCSprite.sprite(String.format(format,i));float x = (i - 1) % 4 * 54 + 16; // 计算x坐标float y = 175 - (i - 1) / 4 * 59; // 计算y坐标plant.setAnchorPoint(0,0); // 设置锚点为左下角plant.setPosition(x,y);mChooseBox.addChild(plant);}}
}

5.植物准备页面

  1. 在domain包下新建实体类Plant,用于表示植物。注意这里要设置两个spirit:一个是半透明的,一个是被选中的,具体效果可在游戏里参考,代码如下:
package com.example.zombievsplantdemo.domain;import org.cocos2d.nodes.CCSprite;/*** 植物的实体类*/
public class Plant {/*** 植物图标*/private String format = "image/fight/chose/choose_default%02d.png";/*** 背景图片(半透明)*/private CCSprite bgPlant;/*** 背景图片(展现)*/private CCSprite showPlant;public Plant(int i) {initBgPlant(i);initShowPlant(i);}/*** 初始化植物背景图标(半透明)* @param i 植物图标的序号*/private void initBgPlant(int i){bgPlant = CCSprite.sprite(String.format(format,i));float x = (i - 1) % 4 * 54 + 16; // 计算x坐标float y = 175 - (i - 1) / 4 * 59; // 计算y坐标bgPlant.setAnchorPoint(0,0); // 设置锚点为左下角bgPlant.setPosition(x,y);bgPlant.setOpacity(100); // 设置为半透明}/*** 初始化植物背景图标(展现)* @param i 植物图标的序号*/private void initShowPlant(int i){showPlant = CCSprite.sprite(String.format(format,i));float x = (i - 1) % 4 * 54 + 16; // 计算x坐标float y = 175 - (i - 1) / 4 * 59; // 计算y坐标showPlant.setAnchorPoint(0,0); // 设置锚点为左下角showPlant.setPosition(x,y);}public CCSprite getBgPlant() {return bgPlant;}public CCSprite getShowPlant() {return showPlant;}
}
  1. 修改FightLayer,使用封装好的Plant类代替原来的逻辑,代码如下:
package com.example.zombievsplantdemo.layer;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());}}
}
  1. 修改FightLayer,添加showChooseBox()方法,用于处理植物在选择框和被选框之间切换的逻辑,代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5){mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内mPlant.getShowPlant().runAction(move);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左便宜;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}
}
  1. 修改FightLayer,首先修改ccTouchesBegan()方法,添加植物被移除时的偏移量,并且添加unLock()方法,用于处理植物能被反复选中的问题,代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();/*** 标记植物是否正在被移动*/private boolean isMoving = false;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5 && !isMoving){isMoving = true;mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"unLock"));mPlant.getShowPlant().runAction(sequence);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左偏移;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}/*** 8.重置植物移动的标记位*/public void unLock(){isMoving = false;}
}
  1. 修改FightLayer,修改showChooseBox()方法,加入一个开始战斗的按钮,并且注册它的点击事件gamePrepare(),代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();/*** 标记植物是否正在被移动*/private boolean isMoving = false;/*** 开始战斗的按钮*/private CCSprite start;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}// 添加开始战斗的按钮start = CCSprite.sprite("image/fight/chose/fight_start.png");start.setPosition(mChooseBox.getContentSize().width / 2 , 30);mChooseBox.addChild(start);}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){if (CGRect.containsPoint(start.getBoundingBox(),point)){ // 开始战斗被点击gamePrepare();return true;}for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5 && !isMoving){isMoving = true;mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"unLock"));mPlant.getShowPlant().runAction(sequence);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左偏移;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}/*** 8.重置植物移动的标记位*/public void unLock(){isMoving = false;}/*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){System.out.println("开始战斗");}
}

6.正式战斗页面

  1. 修改FightLayer,添加gamePrepare()方法,用于处理点击“开始战斗”按钮后的逻辑,同时添加moveMapBack()方法,让地图可以回到原来的位置,代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();/*** 标记植物是否正在被移动*/private boolean isMoving = false;/*** 开始战斗的按钮*/private CCSprite start;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}// 添加开始战斗的按钮start = CCSprite.sprite("image/fight/chose/fight_start.png");start.setPosition(mChooseBox.getContentSize().width / 2 , 30);mChooseBox.addChild(start);}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){if (CGRect.containsPoint(start.getBoundingBox(),point)){ // 开始战斗被点击gamePrepare();return true;}for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5 && !isMoving){isMoving = true;mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"unLock"));mPlant.getShowPlant().runAction(sequence);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左偏移;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}/*** 8.重置植物移动的标记位*/public void unLock(){isMoving = false;}/*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){//隐藏植物框mChooseBox.removeSelf();//地图移动回去moveMapBack();//缩放已选框mSelectedBox.setScale(0.65);//回收僵尸}/*** 10.地图反向移动*/private void moveMapBack(){float offset = map.getContentSize().width - size.width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move);map.runAction(sequence);}
}
  1. 修改CommonUtil类,添加animate的重载方法,多传入一个时间参数,便于之后调用,代码如下:
package com.example.zombievsplantdemo.util;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCScene;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCNode;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.transitions.CCFadeTransition;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;
import java.util.HashMap;/*** 工具类*/
public class CommonUtil {/*** 动画加载方法(1)* @param format 文件名* @param num 动画帧数* @param repeat 是否循环播放* @param t 动画播放时间* @return 返回一个CCAction对象*/public static CCAction animate(String format,int num,boolean repeat,float t){ArrayList<CCSpriteFrame> frames = new ArrayList<>();for (int i = 1; i <= num ; i++) {frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());}CCAnimation animation = CCAnimation.animation("loading",t,frames); // 第二个参数表示每一帧显示时间if (!repeat){CCAnimate animate = CCAnimate.action(animation,false); // 第二个参数为false表示只执行一次,默认是truereturn animate;}else {CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是trueCCRepeatForever r = CCRepeatForever.action(animate);return r;}}/*** 动画加载方法(2)* @param format 文件名* @param num 动画帧数* @param repeat 是否循环播放* @return 返回一个CCAction对象*/public static CCAction animate(String format,int num,boolean repeat){return animate(format,num,repeat,0.2f);}/*** 用淡入淡出效果实现转场* @param layer*/public static void changeLayer(CCLayer layer){CCScene scene = CCScene.node();scene.addChild(layer);CCFadeTransition transition = CCFadeTransition.transition(1,scene); // 淡入淡出效果CCDirector.sharedDirector().replaceScene(transition); // 表示导演要切换场景}/*** 加载坐标蒂娜* @param map 地图* @param groupName 组名* @return*/public static ArrayList<CGPoint> loadPoint(CCTMXTiledMap map,String groupName){ArrayList<CGPoint> tempPoints = new ArrayList<>();CCTMXObjectGroup tempPointGruops = map.objectGroupNamed(groupName);ArrayList<HashMap<String, String>> zombiesPoint = tempPointGruops.objects;for (HashMap<String, String> tempmap : zombiesPoint) {Integer x  = Integer.parseInt(tempmap.get("x"));Integer y  = Integer.parseInt(tempmap.get("y"));tempPoints.add(CCNode.ccp(x,y));}return tempPoints;}
}
  1. 修改FightLayer,添加showLabel()方法用于展示游戏开始前的文字,再添加gameStart()方法用于处理游戏正式开始的逻辑,代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();/*** 标记植物是否正在被移动*/private boolean isMoving = false;/*** 开始战斗的按钮*/private CCSprite start;/*** 展示僵尸的集合*/private ArrayList<Zombie> mShowZombies;/*** 展示“准备——安放——植物”的文本框*/private CCSprite startLabel;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){mShowZombies = new ArrayList<>();for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);mShowZombies.add(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}// 添加开始战斗的按钮start = CCSprite.sprite("image/fight/chose/fight_start.png");start.setPosition(mChooseBox.getContentSize().width / 2 , 30);mChooseBox.addChild(start);}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){if (CGRect.containsPoint(start.getBoundingBox(),point)){ // 开始战斗被点击if (!mSelectedPlants.isEmpty()){gamePrepare();}return true;}for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5 && !isMoving){isMoving = true;mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"unLock"));mPlant.getShowPlant().runAction(sequence);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左偏移;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}/*** 8.重置植物移动的标记位*/public void unLock(){isMoving = false;}/*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){//隐藏植物框mChooseBox.removeSelf();//地图移动回去moveMapBack();//缩放已选框mSelectedBox.setScale(0.65);for (Plant plant : mSelectedPlants) {plant.getShowPlant().setScale(0.65f); // 跟父容器同步缩小plant.getShowPlant().setPosition(plant.getShowPlant().getPosition().x * 0.65f,plant.getShowPlant().getPosition().y + (size.height - plant.getShowPlant().getPosition().y) * 0.35f);this.addChild(plant.getShowPlant());}}/*** 10.地图反向移动*/private void moveMapBack(){float offset = map.getContentSize().width - size.width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showLabel"));map.runAction(sequence);}/*** 11.游戏开始前的文字展示*/public void showLabel(){//回收僵尸,节省内存for (Zombie zombie : mShowZombies) {zombie.removeSelf();}mShowZombies.clear();//显示准备开始战斗的文字startLabel = CCSprite.sprite("image/fight/startready_01.png");startLabel.setPosition(size.width / 2 , size.height / 2);this.addChild(startLabel);CCAnimate animate = (CCAnimate) CommonUtil.animate("image/fight/startready_%02d.png", 3, false,0.5f);CCSequence sequence = CCSequence.actions(animate,CCCallFunc.action(this,"gameStart"));startLabel.runAction(sequence);}/*** 12.游戏正式开始的处理*/public void gameStart(){startLabel.removeSelf();System.out.println("游戏正式开始");}
}

7.战斗逻辑

  1. 因为FightLayer承载了太多逻辑,这里想把战斗逻辑分离出来。新建一个engine包,在包下创建GameEngine类,并且将该类设置为单例模式,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;import org.cocos2d.layers.CCTMXTiledMap;import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){}
}
  1. 修改FightLayer,在ccTouchesBegan()方法中添加逻辑,判断游戏是否正式开始。另外修改gameStart()方法,用于调用GameEngine类的gameStart()方法,传入的参数是地图和已选植物,代码如下:
package com.example.zombievsplantdemo.layer;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.Zombie;
import com.example.zombievsplantdemo.engine.GameEngine;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCMoveBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.concurrent.CopyOnWriteArrayList;/*** 战斗图层*/
public class FightLayer extends BaseLayer{/*** 地图对象*/private CCTMXTiledMap map;/*** 僵尸的坐标点集合*/private ArrayList<CGPoint> mZombiesPoints;/*** 已选植物框*/private CCSprite mSelectedBox;/*** 未选植物框*/private CCSprite mChooseBox;/*** 选择框内植物的集合*/private CopyOnWriteArrayList<Plant> mPlants;/*** 已选植物的集合*/private CopyOnWriteArrayList<Plant> mSelectedPlants = new CopyOnWriteArrayList<>();/*** 标记植物是否正在被移动*/private boolean isMoving = false;/*** 开始战斗的按钮*/private CCSprite start;/*** 展示僵尸的集合*/private ArrayList<Zombie> mShowZombies;/*** 展示“准备——安放——植物”的文本框*/private CCSprite startLabel;public FightLayer() {loadMap();loadZombie();}/*** 1.加载地图*/private void loadMap(){map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");this.addChild(map);mZombiesPoints = CommonUtil.loadPoint(map, "zombies");moveMap();}/*** 2.移动地图*/private void moveMap(){float offset = size.width - map.getContentSize().width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showPlantBox"));map.runAction(sequence);}/*** 3.加载僵尸*/private void loadZombie(){mShowZombies = new ArrayList<>();for (CGPoint mZombiesPoint : mZombiesPoints) {Zombie zombie = new Zombie();zombie.setPosition(mZombiesPoint);map.addChild(zombie);mShowZombies.add(zombie);}}/*** 4.展示植物框*/public void showPlantBox(){setIsTouchEnabled(true); // 打开点击事件showSelectedBox();showChooseBox();}/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox);}/*** 6.展示植物选择框(未选)*/private void showChooseBox(){mChooseBox = CCSprite.sprite("image/fight/chose/fight_choose.png");mChooseBox.setAnchorPoint(0,0); // 设置锚点为左下角this.addChild(mChooseBox);mPlants = new CopyOnWriteArrayList<>();for (int i = 1; i <= 9 ; i++) {Plant plant = new Plant(i);// 添加背景植物和展示植物,位置一样mChooseBox.addChild(plant.getBgPlant());mChooseBox.addChild(plant.getShowPlant());mPlants.add(plant);}// 添加开始战斗的按钮start = CCSprite.sprite("image/fight/chose/fight_start.png");start.setPosition(mChooseBox.getContentSize().width / 2 , 30);mChooseBox.addChild(start);}/*** 7.为植物图标注册点击事件,注意要打开点击事件* @param event* @return*/@Overridepublic boolean ccTouchesBegan(MotionEvent event) {// 判断游戏是否已经开始if(GameEngine.isStart){GameEngine.getInstance().handleTouch(event);return true;}CGPoint point = convertTouchToNodeSpace(event);// 是否落在植物选择框内if (CGRect.containsPoint(mChooseBox.getBoundingBox(),point)){if (CGRect.containsPoint(start.getBoundingBox(),point)){ // 开始战斗被点击if (!mSelectedPlants.isEmpty()){gamePrepare();}return true;}for (Plant mPlant : mPlants) {if (CGRect.containsPoint(mPlant.getShowPlant().getBoundingBox(),point)){if (mSelectedPlants.size() < 5 && !isMoving){isMoving = true;mSelectedPlants.add(mPlant);CCMoveTo move = CCMoveTo.action(0.5f,ccp(75 + ( mSelectedPlants.size() - 1 ) * 53,size.height - 65)); // 植物移动到已选框内CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"unLock"));mPlant.getShowPlant().runAction(sequence);}break;}}}else if (CGRect.containsPoint(mSelectedBox.getBoundingBox(),point)){boolean isSelect = false;for (Plant mSelectedPlant : mSelectedPlants) {if (CGRect.containsPoint(mSelectedPlant.getShowPlant().getBoundingBox(),point)){CCMoveTo move = CCMoveTo.action(0.5f,mSelectedPlant.getBgPlant().getPosition());  // 移动到背景植物的位置mSelectedPlant.getShowPlant().runAction(move);mSelectedPlants.remove(mSelectedPlant); // 移除取消的植物isSelect = true;continue;}if (isSelect){ // 说明有植物被点击了CCMoveBy move = CCMoveBy.action(0.5f,ccp(-53,0)); // 向左偏移;mSelectedPlant.getShowPlant().runAction(move);}}}return super.ccTouchesBegan(event);}/*** 8.重置植物移动的标记位*/public void unLock(){isMoving = false;}/*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){//隐藏植物框mChooseBox.removeSelf();//地图移动回去moveMapBack();//缩放已选框mSelectedBox.setScale(0.65);for (Plant plant : mSelectedPlants) {plant.getShowPlant().setScale(0.65f); // 跟父容器同步缩小plant.getShowPlant().setPosition(plant.getShowPlant().getPosition().x * 0.65f,plant.getShowPlant().getPosition().y + (size.height - plant.getShowPlant().getPosition().y) * 0.35f);this.addChild(plant.getShowPlant());}}/*** 10.地图反向移动*/private void moveMapBack(){float offset = map.getContentSize().width - size.width; // 地图移动的偏移量CCDelayTime delay = CCDelayTime.action(1); // 延时1秒CCMoveBy move = CCMoveBy.action(2,ccp(offset,0));CCSequence sequence = CCSequence.actions(delay,move,delay,CCCallFunc.action(this,"showLabel"));map.runAction(sequence);}/*** 11.游戏开始前的文字展示*/public void showLabel(){//回收僵尸,节省内存for (Zombie zombie : mShowZombies) {zombie.removeSelf();}mShowZombies.clear();//显示准备开始战斗的文字startLabel = CCSprite.sprite("image/fight/startready_01.png");startLabel.setPosition(size.width / 2 , size.height / 2);this.addChild(startLabel);CCAnimate animate = (CCAnimate) CommonUtil.animate("image/fight/startready_%02d.png", 3, false,0.5f);CCSequence sequence = CCSequence.actions(animate,CCCallFunc.action(this,"gameStart"));startLabel.runAction(sequence);}/*** 12.游戏正式开始的处理*/public void gameStart(){startLabel.removeSelf();System.out.println("游戏正式开始");GameEngine.getInstance().gameStart(map,mSelectedPlants);}
}
  1. 将资料文件中的(植物大战僵尸资料\植物大战僵尸\资源文件\植物大战僵尸\code)下的base文件夹拷贝到domain包下,游戏相关的对象如图所示:

​ base目录下的文件如图所示:

  1. 在domain包下新建PrimaryZombie,继承base包下的Zombie类,并实现一些方法,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;/*** 普通僵尸的实体类*/
public class PrimaryZombie extends Zombie {public PrimaryZombie(CGPoint startPoint,CGPoint endPoint) {super("image/zombies/zombies_1/walk/z_1_01.png");this.startPoint = startPoint;this.endPoint = endPoint;this.setPosition(startPoint); // 设置僵尸起点坐标move();}@Overridepublic void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(startPoint,endPoint) / speed,endPoint);this.runAction(move);baseAction();}@Overridepublic void attack(BaseElement element) {}@Overridepublic void attacked(int attack) {}@Overridepublic void baseAction() {// 僵尸行走CCAction move = CommonUtil.animate("image/zombies/zombies_1/walk/z_1_%02d.png", 7, true);this.runAction(move);}
}
  1. 修改GameEngine,在loadZombie()方法中完善逻辑,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");loadZombie();}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){}/*** 3.加载僵尸*/private void loadZombie() {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}
}
  1. 修改MainActivity,在onDestroy()中添加杀死进程的方法,用于解决静态变量未初始化的bug,代码如下:
package com.example.zombievsplantdemo;import android.app.Activity;
import android.os.Bundle;import com.example.zombievsplantdemo.layer.FightLayer;import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.opengl.CCGLSurfaceView;
import org.cocos2d.sound.SoundEngine;public class MainActivity extends Activity {/*** 导演*/CCDirector director = CCDirector.sharedDirector();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//调用顺序:视图(CCGLSurfaceView) -》 导演(CCDirector) -》 场景(CCScene) -》 图层(CCLayer) -》 精灵(CCSprite) -》 动作(CCMove)// 获取视图CCGLSurfaceView view = new CCGLSurfaceView(this); // 创建一个SurfaceView,类似导演眼前的小屏幕,必须传this,底层要强转成ActvitysetContentView(view);// 获取导演的单例对象director.attachInView(view); // 开启绘制线程的方法director.setDisplayFPS(true); // 显示帧率,表示每秒刷新页面的次数。一般当帧率大于30帧时,基本上人眼看起来比较流畅,帧率和手机性能与程序性能有关//director.setAnimationInterval(1/60f); // 设置最高帧率位60director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft); // 设置屏幕方式为横屏显示director.setScreenSize(480,320); // 设置分辨率,用于屏幕适配,会基于不同大小的屏幕等比例缩放,设置我们开发时候的分辨率// 获取场景对象CCScene scene = CCScene.node();// 获取图层对象//FirstLayer layer = new FirstLayer();//ActionLayer layer = new ActionLayer();FightLayer layer = new FightLayer();// 配置环境scene.addChild(layer); // 给场景添加图层director.runWithScene(scene); // 导演运行场景}@Overrideprotected void onResume() {super.onResume();director.resume(); // 游戏继续SoundEngine.sharedEngine().resumeSound(); // 音乐继续}@Overrideprotected void onPause() {super.onPause();director.pause(); // 游戏暂停SoundEngine.sharedEngine().pauseSound(); // 音乐暂停}@Overrideprotected void onDestroy() {super.onDestroy();director.end(); // 游戏结束System.exit(0); // 退出游戏时杀死程序的进程,清空所有静态变量}
}
  1. 修改GameEngine,修改gameStart()方法,用定时器来处理每隔两秒僵尸出现的逻辑,注意:在通过反射调用loadZombie()时需要传入一个float类型的参数,不然会报错,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.types.CGPoint;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}
}
  1. 修改PrimaryZombie,修改move()方法,添加让僵尸走到尽头时消失的逻辑,以节约CPU内存。这里的destroy()方法为父类的destroy()方法,表示销毁自身,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;/*** 普通僵尸的实体类*/
public class PrimaryZombie extends Zombie {public PrimaryZombie(CGPoint startPoint,CGPoint endPoint) {super("image/zombies/zombies_1/walk/z_1_01.png");this.startPoint = startPoint;this.endPoint = endPoint;this.setPosition(startPoint); // 设置僵尸起点坐标move();}@Overridepublic void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(startPoint,endPoint) / speed,endPoint);CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 僵尸走到头要销毁this.runAction(sequence);baseAction();}@Overridepublic void attack(BaseElement element) {}@Overridepublic void attacked(int attack) {}@Overridepublic void baseAction() {// 僵尸行走CCAction move = CommonUtil.animate("image/zombies/zombies_1/walk/z_1_%02d.png", 7, true);this.runAction(move);}
}
  1. 修改FightLayer,修改showSelectedBox()方法,添加标志位,用于让GameEngine获取到FIghtLayer的已选植物框,代码如下:
     /*** 已选植物的标志位*/public static final int TAG_SELECTED_BOX = 1;/*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox,0,TAG_SELECTED_BOX);}

另外,修改gamePrepare()方法,在其中关闭图层触摸开关,防止出现Bug,代码如下:

    /*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){setIsTouchEnabled(false); // 禁用点击事件//隐藏植物框mChooseBox.removeSelf();//地图移动回去moveMapBack();//缩放已选框mSelectedBox.setScale(0.65);for (Plant plant : mSelectedPlants) {plant.getShowPlant().setScale(0.65f); // 跟父容器同步缩小plant.getShowPlant().setPosition(plant.getShowPlant().getPosition().x * 0.65f,plant.getShowPlant().getPosition().y + (size.height - plant.getShowPlant().getPosition().y) * 0.35f);this.addChild(plant.getShowPlant());}}

最后,修改gameStart()方法,在地图移动完毕之后重新启用图层触摸开关,代码如下:

    /*** 12.游戏正式开始的处理*/public void gameStart(){startLabel.removeSelf();System.out.println("游戏正式开始");setIsTouchEnabled(true); // 打开点击事件GameEngine.getInstance().gameStart(map,mSelectedPlants);}
  1. 修改GameEngine,完善handleTouch()方法的逻辑,用于判断点击已选植物框后的事件,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.layer.FightLayer;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;/*** 当前被选择的植物*/private Plant mShowPlant;public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明break;}}}}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}
}
  1. 为了新建一个植物到界面中,这里先定义“防御性植物”——“土豆”。在domain包下新建Nut类,继承DefancePlant类,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.DefancePlant;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;/*** 土豆的实体类*/
public class Nut extends DefancePlant {public Nut() {super("image/plant/nut/p_3_01.png");baseAction();}@Overridepublic void baseAction() {CCAction animate = CommonUtil.animate("image/plant/nut/p_3_%02d.png", 11, true);this.runAction(animate);}
}
  1. 修改domain包下的plant类,为了知道现在选择的是哪个植物,新建id标识,代码如下:
package com.example.zombievsplantdemo.domain;import org.cocos2d.nodes.CCSprite;/*** 植物的实体类*/
public class Plant {/*** 植物图标*/private String format = "image/fight/chose/choose_default%02d.png";/*** 背景图片(半透明)*/private CCSprite bgPlant;/*** 背景图片(展现)*/private CCSprite showPlant;/*** 标识植物的种类*/private int id;public Plant(int i) {this.id = i;initBgPlant(i);initShowPlant(i);}/*** 初始化植物背景图标(半透明)* @param i 植物图标的序号*/private void initBgPlant(int i){bgPlant = CCSprite.sprite(String.format(format,i));float x = (i - 1) % 4 * 54 + 16; // 计算x坐标float y = 175 - (i - 1) / 4 * 59; // 计算y坐标bgPlant.setAnchorPoint(0,0); // 设置锚点为左下角bgPlant.setPosition(x,y);bgPlant.setOpacity(100); // 设置为半透明}/*** 初始化植物背景图标(展现)* @param i 植物图标的序号*/private void initShowPlant(int i){showPlant = CCSprite.sprite(String.format(format,i));float x = (i - 1) % 4 * 54 + 16; // 计算x坐标float y = 175 - (i - 1) / 4 * 59; // 计算y坐标showPlant.setAnchorPoint(0,0); // 设置锚点为左下角showPlant.setPosition(x,y);}public CCSprite getBgPlant() {return bgPlant;}public CCSprite getShowPlant() {return showPlant;}public int getId() {return id;}
}
  1. 修改GameEngine,修改handleTouch()方法,用于将已选框的植物放到页面上,同时添加isInGrass()方法用于判断鼠标是否点击到了草坪内部。代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Nut;
import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.layer.FightLayer;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;/*** 当前被点击的植物*/private Plant mShowPlant;/*** 当前被安放的植物*/private com.example.zombievsplantdemo.domain.base.Plant mPlant;public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明switch (mShowPlant.getId()){case 4:mPlant = new Nut(); // 安放土豆break;default:break;}break;}}}else { //鼠标落在草坪上if (isInGrass(point)){ // 判断是否落在草坪的格子里System.out.println("在格子里");}}}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}/*** 4.判断是否在草坪的格子上* @return*/private boolean isInGrass(CGPoint point){int column = (int) (point.x / 46); // 计算第几列int line = (int) ((CCDirector.sharedDirector().winSize().height - point.y) / 54); // 计算第几行if (column >= 1 && column <= 9 && line >= 1 && line <= 5){return true;}return false;}
}
  1. 修改GameEngine,完善植物放在草坪上的逻辑(handleTouch()方法、isInGrass()方法),并添加loadPlant()方法用于加载植物,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Nut;
import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.layer.FightLayer;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;/*** 当前被点击的植物*/private Plant mShowPlant;/*** 当前被安放的植物*/private com.example.zombievsplantdemo.domain.base.Plant mPlant;/*** 存放植物坐标的二维数组*/private CGPoint[][] mPlantPoints = new CGPoint[5][9];public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法loadPlant();}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){if (mShowPlant != null){mShowPlant.getShowPlant().setOpacity(255); // 将上一个植物设置为不透明}mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明switch (mShowPlant.getId()){case 4:mPlant = new Nut(); // 安放土豆break;default:break;}break;}}}else { //鼠标落在草坪上if (isInGrass(point)){ // 判断是否落在草坪的格子里System.out.println("在格子里");if (mPlant != null && mShowPlant != null){map.addChild(mPlant); // 植物已经安放好了mShowPlant.getShowPlant().setOpacity(255); // 安放完毕后,透明度编程完全不透明mPlant = null;mShowPlant = null;}}}}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}/*** 4.判断是否在草坪的格子上* @return*/private boolean isInGrass(CGPoint point){int column = (int) (point.x / 46); // 计算第几列int line = (int) ((CCDirector.sharedDirector().winSize().height - point.y) / 54); // 计算第几行if (column >= 1 && column <= 9 && line >= 1 && line <= 5){if (mPlant != null){mPlant.setLine(line - 1); // 设置行号mPlant.setColumn(column - 1); // 设置列号mPlant.setPosition(mPlantPoints[line-1][column-1]); // 设置植物的位置return true;}}return false;}/*** 5.加载植物*/private void loadPlant() {String format = "tower%02d";for (int i = 1; i <= 5; i++) {ArrayList<CGPoint> loadPoints = CommonUtil.loadPoint(map, String.format(format, i));for (int j = 0; j < loadPoints.size() ; j++) {mPlantPoints[i-1][j] = loadPoints.get(j);}}}
}
  1. 为了解决点击同一个格子可以产生多个植物的bug,在engine下新建一个实体类FightLineEngine,代码如下:
package com.example.zombievsplantdemo.engine;import com.example.zombievsplantdemo.domain.base.Plant;import java.util.HashMap;/*** 封装战线的引擎类*/
public class FightLineEngine {public FightLineEngine(int i) {}/*** 保存植物对象的Map*/private HashMap<Integer,Plant> mPlants = new HashMap<>(); // key表示植物在第几列/*** 添加植物* @param plant 植物对象*/public void addPlant(Plant plant){mPlants.put(plant.getColumn(),plant);}/*** 判断战线上是否已经有植物,有的话就不能再安放了* @return*/public boolean contaionsPlant(Plant plant){return mPlants.keySet().contains(plant.getColumn());}
}
  1. 修改GameEngine,用static代码块来初始化FightLineEngine类,完成植物可以安放在格子的逻辑,代码如下:
package com.example.zombievsplantdemo.engine;import android.view.MotionEvent;import com.example.zombievsplantdemo.domain.Nut;
import com.example.zombievsplantdemo.domain.Plant;
import com.example.zombievsplantdemo.domain.PrimaryZombie;
import com.example.zombievsplantdemo.layer.FightLayer;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.CopyOnWriteArrayList;/*** 处理战斗逻辑的引擎* 单例类*/
public class GameEngine {/*** 单例对象*/private static GameEngine mInstance = new GameEngine();/*** 游戏地图*/private CCTMXTiledMap map;/*** 已选植物*/private CopyOnWriteArrayList<Plant> mSelectedPlants;/*** 标记游戏是否正式开始*/public static boolean isStart;/*** 僵尸的移动路径*/private ArrayList<CGPoint> mZombiePoints;/*** 当前被点击的植物*/private Plant mShowPlant;/*** 当前被安放的植物*/private com.example.zombievsplantdemo.domain.base.Plant mPlant;/*** 存放植物坐标的二维数组*/private CGPoint[][] mPlantPoints = new CGPoint[5][9];/*** 存储战线集合*/private static ArrayList<FightLineEngine> mFightLines;/*** 初始化5条战线*/static {mFightLines = new ArrayList<>();for (int i = 0; i < 5; i++) {FightLineEngine line = new FightLineEngine(i);mFightLines.add(line);}}public GameEngine() {}public static GameEngine getInstance(){return mInstance;}/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法loadPlant();}/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){if (mShowPlant != null){mShowPlant.getShowPlant().setOpacity(255); // 将上一个植物设置为不透明}mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明switch (mShowPlant.getId()){case 4:mPlant = new Nut(); // 安放土豆break;default:break;}break;}}}else { //鼠标落在草坪上if (isInGrass(point)){ // 判断是否落在草坪的格子里System.out.println("在格子里");if (mPlant != null && mShowPlant != null){map.addChild(mPlant); // 植物已经安放好了mShowPlant.getShowPlant().setOpacity(255); // 安放完毕后,透明度编程完全不透明//给战线添加植物mFightLines.get(mPlant.getLine()).addPlant(mPlant);mPlant = null;mShowPlant = null;}}}}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);}/*** 4.判断是否在草坪的格子上* @return*/private boolean isInGrass(CGPoint point){int column = (int) (point.x / 46); // 计算第几列int line = (int) ((CCDirector.sharedDirector().winSize().height - point.y) / 54); // 计算第几行if (column >= 1 && column <= 9 && line >= 1 && line <= 5){if (mPlant != null){mPlant.setLine(line - 1); // 设置行号mPlant.setColumn(column - 1); // 设置列号mPlant.setPosition(mPlantPoints[line-1][column-1]); // 设置植物的位置if (mFightLines.get(line - 1).contaionsPlant(mPlant)){ // 判断战线是否包含植物return false;}return true;}}return false;}/*** 5.加载植物*/private void loadPlant() {String format = "tower%02d";for (int i = 1; i <= 5; i++) {ArrayList<CGPoint> loadPoints = CommonUtil.loadPoint(map, String.format(format, i));for (int j = 0; j < loadPoints.size() ; j++) {mPlantPoints[i-1][j] = loadPoints.get(j);}}}
}
  1. 修改FightLineEngine,添加addZombie()方法,用于将僵尸添加到集合中,代码如下:
package com.example.zombievsplantdemo.engine;import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;import org.cocos2d.actions.CCScheduler;import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;/*** 封装战线的引擎类*/
public class FightLineEngine {/*** 保存植物对象的Map*/private HashMap<Integer,Plant> mPlants = new HashMap<>(); // key表示植物在第几列/*** 保存僵尸对象的集合*/private CopyOnWriteArrayList<Zombie> mZombies = new CopyOnWriteArrayList<>();public FightLineEngine(int i) {CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,0.2f,false);}/*** 1.添加植物* @param plant 植物对象*/public void addPlant(Plant plant){mPlants.put(plant.getColumn(),plant);}/*** 2.判断战线上是否已经有植物,有的话就不能再安放了* @return*/public boolean contaionsPlant(Plant plant){return mPlants.keySet().contains(plant.getColumn());}/*** 3.添加僵尸* @param zombie 僵尸对象*/public void addZombie(final Zombie zombie){// 僵尸的死亡回调zombie.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mZombies.remove(zombie); // 僵尸死亡后从集合中移除}});mZombies.add(zombie);}/*** 4.僵尸攻击植物* @param f*/public void attackPlant(float f){}
}
  1. 修改GameEngine,修改loadZombie()方法,调用FightLineEngine类中的addZombie()方法,代码如下:
    /*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie);mFightLines.get(line).addZombie(zombie); // 把僵尸添加到战线中}
  1. 修改FightLineEngine,添加attackPlant()方法表示僵尸和植物处在同一个x轴上时僵尸开始攻击植物,代码如下:
package com.example.zombievsplantdemo.engine;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;import org.cocos2d.actions.CCScheduler;import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;/*** 封装战线的引擎类*/
public class FightLineEngine {/*** 保存植物对象的Map*/private HashMap<Integer,Plant> mPlants = new HashMap<>(); // key表示植物在第几列/*** 保存僵尸对象的集合*/private CopyOnWriteArrayList<Zombie> mZombies = new CopyOnWriteArrayList<>();public FightLineEngine(int i) {CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,0.2f,false); // 每隔0.2秒检测僵尸是否可以攻击植物}/*** 1.添加植物* @param plant 植物对象*/public void addPlant(Plant plant){mPlants.put(plant.getColumn(),plant);}/*** 2.判断战线上是否已经有植物,有的话就不能再安放了* @return*/public boolean contaionsPlant(Plant plant){return mPlants.keySet().contains(plant.getColumn());}/*** 3.添加僵尸* @param zombie 僵尸对象*/public void addZombie(final Zombie zombie){// 僵尸的死亡回调zombie.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mZombies.remove(zombie); // 僵尸死亡后从集合中移除}});mZombies.add(zombie);}/*** 4.僵尸攻击植物* @param f*/public void attackPlant(float f){if (!mPlants.isEmpty() && !mZombies.isEmpty()){for (Zombie zombie : mZombies) {int column = (int) (zombie.getPosition().x / 46 - 1) ;if(mPlants.keySet().contains(column)){ // 僵尸当前所在的列上有植物存在if (!zombie.isAttacking()){zombie.attack(mPlants.get(column));// 表示僵尸开始攻击该列的植物zombie.setAttacking(true);  // 标记正在攻击}}}}}
}
  1. 修改PrimaryZombie,完善attack()方法,描述僵尸攻击植物的逻辑,并且添加attackPlant()方法,表示植物被僵尸攻击后的逻辑,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;/*** 普通僵尸的实体类*/
public class PrimaryZombie extends Zombie {/*** 正在被攻击的植物对象*/private Plant mPlant;public PrimaryZombie(CGPoint startPoint,CGPoint endPoint) {super("image/zombies/zombies_1/walk/z_1_01.png");this.startPoint = startPoint;this.endPoint = endPoint;this.setPosition(startPoint); // 设置僵尸起点坐标move();}@Overridepublic void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(startPoint,endPoint) / speed,endPoint);CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 僵尸走到头要销毁this.runAction(sequence);baseAction();}@Overridepublic void attack(BaseElement element) {if (element instanceof Plant){ // 判断元素是否是植物mPlant = (Plant) element;this.stopAllActions(); // 停止僵尸所有的动作CCAction animate = CommonUtil.animate("image/zombies/zombies_1/attack/z_1_attack_%02d.png", 10, true);this.runAction(animate); // 僵尸开始攻击植物的动画CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,1,false); // 每隔一秒钟咬一口植物}}@Overridepublic void attacked(int attack) {}@Overridepublic void baseAction() {// 僵尸行走CCAction move = CommonUtil.animate("image/zombies/zombies_1/walk/z_1_%02d.png", 7, true);this.runAction(move);}/*** 1.僵尸攻击植物,植物掉血* @param f 必须参数*/public void attackPlant(float f){if (mPlant != null){mPlant.attacked(attack); // 植物掉血if (mPlant.getLife() <= 0){ // 植物死亡CCScheduler.sharedScheduler().unschedule("attackPlant",this); // 停止定时器this.stopAllActions();move(); // 僵尸继续前进isAttacking = false; // 表示僵尸已经攻击结束}}}
}
  1. 修改FightLineEngine,修改addPlant()方法,添加死亡监听,以便让植物死亡后移除到列表外,代码如下:
    /*** 1.添加植物* @param plant 植物对象*/public void addPlant(final Plant plant){plant.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mPlants.remove(plant.getColumn()); // 移除植物}});mPlants.put(plant.getColumn(),plant);}
  1. 修改PrimaryZombie,为了解决僵尸在攻击完植物后速度不一致的问题,需要修改move()方法中的起始点,从startPoint更改为getPosition()即可,代码如下:

    @Override
    public void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(getPosition(),endPoint) / speed,endPoint);CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 僵尸走到头要销毁this.runAction(sequence);baseAction();
    }
    
  2. 在domain包下新建PeaPlant,代表豌豆射手的实体类,继承AttackPlant,代码如下:

package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.AttackPlant;
import com.example.zombievsplantdemo.domain.base.Bullet;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.base.CCAction;/*** 豌豆射手的实体类*/
public class PeaPlant extends AttackPlant {public PeaPlant() {super("image/plant/peas/p_2_01.png");baseAction();}@Overridepublic Bullet createBullet() {return null;}@Overridepublic void baseAction() {CCAction animate = CommonUtil.animate("image/plant/peas/p_2_%02d.png", 8, true);this.runAction(animate);}
}
  1. 修改GameEngine,修改handleTouch()方法,添加豌豆射手可以加到页面上的逻辑,代码如下:
/*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){if (mShowPlant != null){mShowPlant.getShowPlant().setOpacity(255); // 将上一个植物设置为不透明}mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明switch (mShowPlant.getId()){case 1:mPlant = new PeaPlant(); // 安放豌豆射手break;case 4:mPlant = new Nut(); // 安放土豆break;default:break;}break;}}}else { //鼠标落在草坪上if (isInGrass(point)){ // 判断是否落在草坪的格子里System.out.println("在格子里");if (mPlant != null && mShowPlant != null){map.addChild(mPlant); // 植物已经安放好了mShowPlant.getShowPlant().setOpacity(255); // 安放完毕后,透明度编程完全不透明//给战线添加植物mFightLines.get(mPlant.getLine()).addPlant(mPlant);mPlant = null;mShowPlant = null;}}}}
  1. 在domain包下新建Pea,代表豌豆射手打出的子弹的实体类,继承Bullet,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.Bullet;import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCDirector;/*** 豌豆射手的子弹的实体类*/
public class Pea extends Bullet {public Pea() {super("image/fight/bullet.png");setScale(0.65f);}@Overridepublic void move() {float t = (CCDirector.sharedDirector().winSize().width - getPosition().x) / speed; // 计算子弹移动时间CCMoveTo move = CCMoveTo.action(t,ccp(CCDirector.sharedDirector().winSize().width,getPosition().y)); // 子弹移动到屏幕右侧边缘CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 子弹销毁runAction(sequence);}
}
  1. 修改PeaPlant,修改createBullet()方法,添加创建子弹的逻辑,代码如下:
    @Overridepublic Bullet createBullet() {if (bullets.size() < 1){ // 每次只能生产一个子弹final Pea pea = new Pea();pea.setPosition(ccp(getPosition().x + 30,getPosition().y + 45)); // 设置子弹的位置pea.move(); // 子弹移动pea.setDieListener(new DieListener() {@Overridepublic void die() {bullets.remove(pea); // 从集合中移除子弹}});bullets.add(pea);this.getParent().addChild(pea); // 子弹显示到屏幕上return pea;}return null;}
  1. 修改FightLineEngine,添加计时器逻辑,添加创建子弹的方法createBullet()。这里新创建了一个集合,用于存储植物是否是攻击性的,还要在相应的添加植物方法里进行对应的修改,代码如下:
package com.example.zombievsplantdemo.engine;import com.example.zombievsplantdemo.domain.base.AttackPlant;
import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;import org.cocos2d.actions.CCScheduler;import java.util.HashMap;
import java.util.concurrent.CopyOnWriteArrayList;/*** 封装战线的引擎类*/
public class FightLineEngine {/*** 保存植物对象的Map*/private HashMap<Integer,Plant> mPlants = new HashMap<>(); // key表示植物在第几列/*** 保存僵尸对象的集合*/private CopyOnWriteArrayList<Zombie> mZombies = new CopyOnWriteArrayList<>();/*** 保存攻击性植物的集合*/private CopyOnWriteArrayList<AttackPlant> mAttackPlants = new CopyOnWriteArrayList<>();public FightLineEngine(int i) {CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,0.2f,false); // 每隔0.2秒检测僵尸是否可以攻击植物scheduler.schedule("createBullet",this,0.2f,false); // 每隔0.2秒检测是否要产生子弹}/*** 1.添加植物* @param plant 植物对象*/public void addPlant(final Plant plant){plant.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mPlants.remove(plant.getColumn()); // 移除植物mAttackPlants.remove(plant);}});mPlants.put(plant.getColumn(),plant);if (plant instanceof AttackPlant){ // 判断是否是可攻击性的植物mAttackPlants.add((AttackPlant)plant);}}/*** 2.判断战线上是否已经有植物,有的话就不能再安放了* @return*/public boolean contaionsPlant(Plant plant){return mPlants.keySet().contains(plant.getColumn());}/*** 3.添加僵尸* @param zombie 僵尸对象*/public void addZombie(final Zombie zombie){// 僵尸的死亡回调zombie.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mZombies.remove(zombie); // 僵尸死亡后从集合中移除}});mZombies.add(zombie);}/*** 4.僵尸攻击植物* @param f*/public void attackPlant(float f){if (!mPlants.isEmpty() && !mZombies.isEmpty()){for (Zombie zombie : mZombies) {int column = (int) (zombie.getPosition().x / 46 - 1) ;if(mPlants.keySet().contains(column)){ // 僵尸当前所在的列上有植物存在if (!zombie.isAttacking()){zombie.attack(mPlants.get(column));// 表示僵尸开始攻击该列的植物zombie.setAttacking(true);  // 标记正在攻击}}}}}/*** 5.产生子弹* @param f*/public void createBullet(float f){if (!mZombies.isEmpty() && !mAttackPlants.isEmpty()){ // 有僵尸和攻击性植物for (AttackPlant plant : mAttackPlants) {plant.createBullet(); // 产生子弹}}}
}
  1. 修改FightLineEngine,添加计时器逻辑,添加子弹打中僵尸的方法attackZombie()。代码如下:
package com.example.zombievsplantdemo.engine;import com.example.zombievsplantdemo.domain.base.AttackPlant;
import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Bullet;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;import org.cocos2d.actions.CCScheduler;import java.util.HashMap;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;/*** 封装战线的引擎类*/
public class FightLineEngine {/*** 保存植物对象的Map*/private HashMap<Integer,Plant> mPlants = new HashMap<>(); // key表示植物在第几列/*** 保存僵尸对象的集合*/private CopyOnWriteArrayList<Zombie> mZombies = new CopyOnWriteArrayList<>();/*** 保存攻击性植物的集合*/private CopyOnWriteArrayList<AttackPlant> mAttackPlants = new CopyOnWriteArrayList<>();public FightLineEngine(int i) {CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,0.2f,false); // 每隔0.2秒检测僵尸是否可以攻击植物scheduler.schedule("createBullet",this,0.2f,false); // 每隔0.2秒检测是否要产生子弹scheduler.schedule("attackZombie",this,0.2f,false); // 每隔0.2秒检测子弹是否可以攻击僵尸}/*** 1.添加植物* @param plant 植物对象*/public void addPlant(final Plant plant){plant.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mPlants.remove(plant.getColumn()); // 移除植物mAttackPlants.remove(plant);}});mPlants.put(plant.getColumn(),plant);if (plant instanceof AttackPlant){ // 判断是否是可攻击性的植物mAttackPlants.add((AttackPlant)plant);}}/*** 2.判断战线上是否已经有植物,有的话就不能再安放了* @return*/public boolean contaionsPlant(Plant plant){return mPlants.keySet().contains(plant.getColumn());}/*** 3.添加僵尸* @param zombie 僵尸对象*/public void addZombie(final Zombie zombie){// 僵尸的死亡回调zombie.setDieListener(new BaseElement.DieListener() {@Overridepublic void die() {mZombies.remove(zombie); // 僵尸死亡后从集合中移除}});mZombies.add(zombie);}/*** 4.僵尸攻击植物* @param f*/public void attackPlant(float f){if (!mPlants.isEmpty() && !mZombies.isEmpty()){for (Zombie zombie : mZombies) {int column = (int) (zombie.getPosition().x / 46 - 1) ;if(mPlants.keySet().contains(column)){ // 僵尸当前所在的列上有植物存在if (!zombie.isAttacking()){zombie.attack(mPlants.get(column));// 表示僵尸开始攻击该列的植物zombie.setAttacking(true);  // 标记正在攻击}}}}}/*** 5.产生子弹* @param f*/public void createBullet(float f){if (!mZombies.isEmpty() && !mAttackPlants.isEmpty()){ // 有僵尸和攻击性植物for (AttackPlant plant : mAttackPlants) {plant.createBullet(); // 产生子弹}}}/*** 6.子弹攻击僵尸* @param f*/public void attackZombie(float f){if (!mZombies.isEmpty() && !mAttackPlants.isEmpty()){ // 有僵尸和攻击性植物for (Zombie zombie : mZombies) {int x = (int) zombie.getPosition().x;int left = x - 10;int right = x + 10;for (AttackPlant plant : mAttackPlants) {List<Bullet> bullets = plant.getBullets(); // 获取植物的子弹for (Bullet bullet : bullets) {int bx = (int) bullet.getPosition().x;if (bx >= left && bx <= right){ // 子弹处于可攻击的范围内zombie.attacked(bullet.getAttack()); // 僵尸掉血了}}}}}}
}
  1. 修改PrimaryZombie,完善attacked()方法,代表僵尸被攻击之后掉血的逻辑,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;/*** 普通僵尸的实体类*/
public class PrimaryZombie extends Zombie {/*** 正在被攻击的植物对象*/private Plant mPlant;public PrimaryZombie(CGPoint startPoint,CGPoint endPoint) {super("image/zombies/zombies_1/walk/z_1_01.png");this.startPoint = startPoint;this.endPoint = endPoint;this.setPosition(startPoint); // 设置僵尸起点坐标move();}@Overridepublic void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(getPosition(),endPoint) / speed,endPoint);CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 僵尸走到头要销毁this.runAction(sequence);baseAction();}@Overridepublic void attack(BaseElement element) {if (element instanceof Plant){ // 判断元素是否是植物mPlant = (Plant) element;this.stopAllActions(); // 停止僵尸所有的动作CCAction animate = CommonUtil.animate("image/zombies/zombies_1/attack/z_1_attack_%02d.png", 10, true);this.runAction(animate); // 僵尸开始攻击植物的动画CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,1,false); // 每隔一秒钟咬一口植物}}@Overridepublic void attacked(int attack) {life -= attack; // 僵尸掉血if (life <= 0){destroy(); // 僵尸死亡}}@Overridepublic void baseAction() {// 僵尸行走CCAction move = CommonUtil.animate("image/zombies/zombies_1/walk/z_1_%02d.png", 7, true);this.runAction(move);}/*** 1.僵尸攻击植物,植物掉血* @param f 必须参数*/public void attackPlant(float f){if (mPlant != null){mPlant.attacked(attack); // 植物掉血if (mPlant.getLife() <= 0){ // 植物死亡CCScheduler.sharedScheduler().unschedule("attackPlant",this); // 停止定时器this.stopAllActions();move(); // 僵尸继续前进isAttacking = false; // 表示僵尸已经攻击结束}}}
}
  1. 修改FightLineEngine类,修改attackZombie()方法,让子弹在打中僵尸时消失,并且不再穿透,代码如下:
 /*** 6.子弹攻击僵尸* @param f*/public void attackZombie(float f){if (!mZombies.isEmpty() && !mAttackPlants.isEmpty()){ // 有僵尸和攻击性植物for (Zombie zombie : mZombies) {int x = (int) zombie.getPosition().x;int left = x - 10;int right = x + 10;for (AttackPlant plant : mAttackPlants) {List<Bullet> bullets = plant.getBullets(); // 获取植物的子弹for (Bullet bullet : bullets) {int bx = (int) bullet.getPosition().x;if (bx >= left && bx <= right){ // 子弹处于可攻击的范围内zombie.attacked(bullet.getAttack()); // 僵尸掉血了bullet.setVisible(false); // 隐藏子弹bullet.setAttack(0); // 让子弹攻击力为0}}}}}}
  1. 修改PrimaryZombie,增加标志位isDieding,用于判断僵尸是否处于死亡状态。同时增加died()方法,表示僵尸已经死亡。修改attack()、attacked()、attackPlant()三个方法,添加相应的判断逻辑,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.BaseElement;
import com.example.zombievsplantdemo.domain.base.Plant;
import com.example.zombievsplantdemo.domain.base.Zombie;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.actions.base.CCAction;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;/*** 普通僵尸的实体类*/
public class PrimaryZombie extends Zombie {/*** 正在被攻击的植物对象*/private Plant mPlant;/*** 表示僵尸是否正在死亡*/private boolean isDieding = false;public PrimaryZombie(CGPoint startPoint,CGPoint endPoint) {super("image/zombies/zombies_1/walk/z_1_01.png");this.startPoint = startPoint;this.endPoint = endPoint;this.setPosition(startPoint); // 设置僵尸起点坐标move();}@Overridepublic void move() {CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(getPosition(),endPoint) / speed,endPoint);CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"destroy")); // 僵尸走到头要销毁this.runAction(sequence);baseAction();}@Overridepublic void attack(BaseElement element) {if (element instanceof Plant && !isDieding){ // 判断元素是否是植物mPlant = (Plant) element;this.stopAllActions(); // 停止僵尸所有的动作CCAction animate = CommonUtil.animate("image/zombies/zombies_1/attack/z_1_attack_%02d.png", 10, true);this.runAction(animate); // 僵尸开始攻击植物的动画CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("attackPlant",this,1,false); // 每隔一秒钟咬一口植物}}@Overridepublic void attacked(int attack) {life -= attack; // 僵尸掉血if (life <= 0 && !isDieding){ // 没有攻击的动画isDieding = true;this.stopAllActions(); // 停止所有动画if (!isAttacking){CCAnimate animate1 = (CCAnimate) CommonUtil.animate("image/zombies/zombies_1/head/z_1_head_%02d.png", 6, false);CCAnimate animate2 = (CCAnimate) CommonUtil.animate("image/zombies/zombies_1/die/z_1_die_%02d.png", 6, false);CCSequence sequence = CCSequence.actions(animate1,animate2,CCCallFunc.action(this,"died"));this.runAction(sequence);}else { // 正在攻击的动画CCAnimate animate3 = (CCAnimate) CommonUtil.animate("image/zombies/zombies_1/attack_losthead/z_1_attack_losthead_%02d.png", 8, false);CCAnimate animate4 = (CCAnimate) CommonUtil.animate("image/zombies/zombies_1/die/z_1_die_%02d.png", 6, false);CCSequence sequence = CCSequence.actions(animate3,animate4,CCCallFunc.action(this,"died"));this.runAction(sequence);}}}@Overridepublic void baseAction() {// 僵尸行走CCAction move = CommonUtil.animate("image/zombies/zombies_1/walk/z_1_%02d.png", 7, true);this.runAction(move);}/*** 1.僵尸攻击植物,植物掉血* @param f 必须参数*/public void attackPlant(float f){if (mPlant != null && !isDieding){mPlant.attacked(attack); // 植物掉血if (mPlant.getLife() <= 0){ // 植物死亡CCScheduler.sharedScheduler().unschedule("attackPlant",this); // 停止定时器this.stopAllActions();move(); // 僵尸继续前进isAttacking = false; // 表示僵尸已经攻击结束}}else {CCScheduler.sharedScheduler().unschedule("attackPlant",this); // 僵尸停止攻击植物}}/*** 2.僵尸死亡后的逻辑*/public void died(){destroy();isDieding = false;}
}

8.向日葵逻辑

  1. 为了创建能生产阳光的向日葵植物,在domian包下新建实体类SunPlant,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.ProductPlant;
import com.example.zombievsplantdemo.util.CommonUtil;import org.cocos2d.actions.CCScheduler;
import org.cocos2d.actions.base.CCAction;/*** 向日葵的实体类*/
public class SunPlant extends ProductPlant {public SunPlant() {super("image/plant/sunflower/p_1_01.png");life = 100;baseAction();create();}@Overridepublic void create() {CCScheduler.sharedScheduler().schedule("create",this,10,false);}public void create(float f){new Sun(this.getParent(),ccp(getPosition().x,getPosition().y + 40),ccp(getPosition().x + 25,getPosition().y));}@Overridepublic void baseAction() {CCAction animate = CommonUtil.animate("image/plant/sunflower/p_1_%02d.png",8,true);runAction(animate);}
}
  1. 为了创建向日葵生产出来的阳光,在domian包下新建实体类Sun,代码如下:
package com.example.zombievsplantdemo.domain;import com.example.zombievsplantdemo.domain.base.Product;import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCDelayTime;
import org.cocos2d.actions.interval.CCJumpTo;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCRotateBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCNode;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;import java.util.concurrent.CopyOnWriteArrayList;public class Sun extends Product {/*** 放置阳光的集合*/public static final CopyOnWriteArrayList<Sun> suns = new CopyOnWriteArrayList<>();/*** 起始坐标*/private CGPoint start;/*** 终点坐标*/private CGPoint end;/*** 速度*/private int speed = 20;/*** 太阳的图标*/private static final String resPath = "image/product/sun.png";/*** 初始金额*/public static int totalMoney = 25;/*** 地图对线*/private CCNode parent;public Sun(CCNode parent,CGPoint start,CGPoint end) {super(resPath);this.start = start;this.end = end;this.parent = parent;setScale(0.3);setPosition(start);parent.getParent().addChild(this); // 往图层上添加添加,否则会被挡住suns.add(this);baseAction();}@Overridepublic void baseAction() {// 弧线动作float t = (start.y - end.y) / speed;CCJumpTo jump = CCJumpTo.action(t,end,20,1);CCCallFunc callFunc = CCCallFunc.action(this,"destroy");CCSequence sequence = CCSequence.actions(jump,CCDelayTime.action(5),callFunc);runAction(sequence);// 旋转动作runAction(CCRepeatForever.action(CCRotateBy.action(1,180)));}/*** 1.阳光的点击事件*/public void  collectAction(){stopAllActions();float t = CGPointUtil.distance(CCNode.ccp(0,CCDirector.sharedDirector().winSize().height),end) / 200;CCMoveTo moveTo = CCMoveTo.action(t,CCNode.ccp(10,CCDirector.sharedDirector().winSize().height - 10));CCSequence sequence = CCSequence.actions(moveTo,CCCallFunc.action(this,"collect"));runAction(sequence);}/*** 2.阳光的收集事件*/public void collect(){totalMoney += 25;}/*** 3.销毁阳光*/public void destroy(){suns.remove(this);super.destroy();}
}
  1. 修改GameEngine,修改handleTouch(),在switch-case中添加点击到向日葵的逻辑,同时添加点击阳关后可以采集的逻辑,代码如下:
    /*** 2.专门处理点击事件* @param event*/public void handleTouch(MotionEvent event){CGPoint point = map.convertTouchToNodeSpace(event);CCSprite selectedBox = (CCSprite) map.getParent().getChildByTag(FightLayer.TAG_SELECTED_BOX);if (CGRect.containsPoint(selectedBox.getBoundingBox(),point)){ // 已选框被点击for (Plant plant : mSelectedPlants) {if (CGRect.containsPoint(plant.getShowPlant().getBoundingBox(),point)){if (mShowPlant != null){mShowPlant.getShowPlant().setOpacity(255); // 将上一个植物设置为不透明}mShowPlant = plant;plant.getShowPlant().setOpacity(100);//变为半透明switch (mShowPlant.getId()){case 1:mPlant = new PeaPlant(); // 安放豌豆射手break;case 2:mPlant = new SunPlant(); // 安放向日葵break;case 4:mPlant = new Nut(); // 安放土豆break;default:break;}break;}}}else { //鼠标落在草坪上if (isInGrass(point)){ // 判断是否落在草坪的格子里System.out.println("在格子里");if (mPlant != null && mShowPlant != null){map.addChild(mPlant); // 植物已经安放好了mShowPlant.getShowPlant().setOpacity(255); // 安放完毕后,透明度编程完全不透明//给战线添加植物mFightLines.get(mPlant.getLine()).addPlant(mPlant);mPlant = null;mShowPlant = null;}}}// 判断阳关是否被点击CopyOnWriteArrayList<Sun> suns = Sun.suns;for (Sun sun : suns) {if (CGRect.containsPoint(sun.getBoundingBox(),point)){// 阳关被点击// 收集阳关sun.collectAction();break;}}}
  1. 修改FightLayer,修改showSelectedBox(),gamePrepare(),添加文字能显示在盒子上的逻辑,代码如下:
    /*** 阳光*/public static final int TAG_TOTAL_MONEY = 2;    /*** 5.展示植物选择框(已选)*/private void showSelectedBox(){mSelectedBox = CCSprite.sprite("image/fight/chose/fight_chose.png");mSelectedBox.setAnchorPoint(0,1); // 设置锚点为左上角mSelectedBox.setPosition(0,size.height);this.addChild(mSelectedBox,0,TAG_SELECTED_BOX);//显示阳光数label = CCLabel.labelWithString(String.valueOf(Sun.totalMoney),"hkbd.ttf",15);label.setColor(ccc3(0,0,0));label.setPosition(33,CCDirector.sharedDirector().winSize().height - 62);this.addChild(label,1,TAG_TOTAL_MONEY);}/*** 9.点击“开始战斗”后,游戏资源的准备*/private void gamePrepare(){setIsTouchEnabled(false); // 禁用点击事件//隐藏植物框mChooseBox.removeSelf();//地图移动回去moveMapBack();//缩放已选框mSelectedBox.setScale(0.65);for (Plant plant : mSelectedPlants) {plant.getShowPlant().setScale(0.65f); // 跟父容器同步缩小plant.getShowPlant().setPosition(plant.getShowPlant().getPosition().x * 0.65f,plant.getShowPlant().getPosition().y + (size.height - plant.getShowPlant().getPosition().y) * 0.35f);this.addChild(plant.getShowPlant());}// 缩放添加阳光数的文字label.setPosition(22,CCDirector.sharedDirector().winSize().height - 42);label.setScale(0.65f);}
  1. 修改Sun,修改collect(),增加获取阳光文字的逻辑,代码如下:
    /*** 2.阳光的收集事件*/public void collect(){totalMoney += 25; //每个阳关增加25CCLabel label = (CCLabel) parent.getParent().getChildByTag(FightLayer.TAG_TOTAL_MONEY);label.setString(String.valueOf(totalMoney));destroy();}

9.进度条逻辑

  1. 修改GameEngine,添加progress()方法,代表进度器的逻辑,并修改gameStart()方法,在游戏开始时显示进度条,再修改loadZombies()方法,让每加载一只僵尸就让进度+5,代码如下:
    /*** 定义进度器*/private CCProgressTimer progressTimer;/*** 僵尸加载的进度*/private int progress;/*** 1.游戏开始的一些处理* @param map 游戏地图* @param selectedPlants 已选植物*/public void gameStart(CCTMXTiledMap map,CopyOnWriteArrayList<Plant> selectedPlants){isStart = true;this.map = map;this.mSelectedPlants = selectedPlants;mZombiePoints = CommonUtil.loadPoint(map, "road");// 定时器CCScheduler scheduler = CCScheduler.sharedScheduler();scheduler.schedule("loadZombie",this,2,false); //每隔两秒执行一次loadZombie()方法loadPlant();progress(); // 在游戏开始时显示进度条}/*** 3.加载僵尸* @param f 必须有的参数,不然CCScheduler无法通过反射调用*/public void loadZombie(float f) {Random random = new Random();int line = random.nextInt(5); // 随机数为0,1,2,3,4CGPoint startPoint = mZombiePoints.get(line * 2); // 起点坐标CGPoint endPoint = mZombiePoints.get(line * 2 + 1); // 终点坐标PrimaryZombie zombie = new PrimaryZombie(startPoint,endPoint);map.addChild(zombie,1);mFightLines.get(line).addZombie(zombie); // 把僵尸添加到战线中progress += 5; // 每加载一个僵尸,让加载进度+5progressTimer.setPercentage(progress); // 更新进度条进度}/*** 6.进度条*/private void progress() {progressTimer = CCProgressTimer.progressWithFile("image/fight/progress.png");progressTimer.setPosition(CCDirector.sharedDirector().getWinSize().width - 70, 13);map.getParent().addChild(progressTimer);progressTimer.setScale(0.6f);// 0-100progressTimer.setPercentage(0);// 每增加一个僵尸需要调整进度,增加5 0-100// 设置样式progressTimer.setType(CCProgressTimer.kCCProgressTimerTypeHorizontalBarLR);CCSprite sprite = CCSprite.sprite("image/fight/flagmeter.png");sprite.setPosition(CCDirector.sharedDirector().getWinSize().width - 70, 13);map.getParent().addChild(sprite);sprite.setScale(0.6f);CCSprite name = CCSprite.sprite("image/fight/FlagMeterLevelProgress.png");name.setPosition(CCDirector.sharedDirector().getWinSize().width - 70, 5);map.getParent().addChild(name);name.setScale(0.6f);}

10.音乐逻辑

将音乐文件添加到res/raw目录下,在需要添加音乐的地方添加以下语句:

SoundEngine engine = SoundEngine.sharedEngine();
engine.playSound(CCDirector.theApp,R.raw.music,true); // 第三个参数表示是否循环播放

想要停止播放音乐时,添加以下语句:

SoundEngine.sharedEngine().realesAllSounds();

这里就不再详述。

11.结果展示

  1. 展示加载界面
  2. 展示主界面
  3. 植物选择界面
  4. 战斗界面

Cocos2d游戏开发学习记录——4.开发《植物大战僵尸》相关推荐

  1. 微信小游戏开发学习记录2

    接上一篇:微信小游戏开发学习记录_寂静流年韶华舞的博客-CSDN博客_微信小游戏开发学习 目录 一.UI系统 1.基础渲染组件-精灵组件 (1)操作: (2)Sprite 属性 (3)渲染模式 2.L ...

  2. Android 开发学习记录(4)---- httpclient使用(三)

    之前在Android 开发学习记录(3)---- httpclient使用(二)中介绍了如何使用httpclient访问需要账户登录的网址,当然首先是要有一个合法的登录账户. 但是现在好多网站在登录时 ...

  3. 恩施软件开发人员每月多少钱_恩施软件开发学习,恩施软件开发学习哪家好,恩施软件开发学习一般能拿多少工资...

    恩施软件开发学习,恩施软件开发学习哪家好,恩施软件开发学习一般能拿多少工资 首页 > 软件 > 恩施软件开发学习 作者:镀金池   发布时间:2017-11-22 18:54 因为cent ...

  4. Cocos2d游戏开发学习记录——2.使用Cocos2D Graphic实现僵尸的移动

    文章目录 1.游戏引擎Cocos2D 2.图形引擎Cocos2D Graphic 3.实践一 4.Cocos2D的坐标体系 5.CCNode 6.实践二 7.CCAction 8.实践三 9.CCAn ...

  5. 微信小程序开发学习记录(一):直播功能

    微信小程序直播是最近上线的一个新功能,用了将近一天半的时间实现了用直播API去创建直播间,中间踩了很多坑(有的到现在也不是很明白为啥这样就可以 那样就不行),网上有关直播功能的参考文章也比较少,所以这 ...

  6. 微信小程序+java后台+云服务器开发学习记录

    仅作为个人学习记录

  7. Android开发学习——2.Android开发环境准备

    文章目录 一.引言 二.开发前准备 1. JDK 2. Android SDK 3. Android Studio 三.结尾 四.参考 一.引言 前一篇文中提到了Android Studio(简称AS ...

  8. 微信开发学习二 -- 微信开发入门(简单demo)

    一.微信公众平台的基本原理 在开始做之前,先简单介绍了微信公众平台的基本原理. 微信服务器就相当于一个转发服务器,终端(手机.Pad等)发起请求至微信服务器,微信服务器然后将请求转发给我们的应用服务器 ...

  9. 植物大战僵尸游戏道具或参数的属性-植物大战僵尸免费版提供下载

    植物大战僵尸中文版修改器是一款专用于修改植物大战僵尸游戏道具或参数的属性修改工具.使用植物大战僵尸中文版修改器你可以修改不同版本的植物大战僵尸游戏,还支持修改金钱.阳光.肥料(还有智慧树的肥料).杀虫 ...

最新文章

  1. 高翔Slambook第七讲代码解读(2d-2d位姿估计)
  2. Mangofile.PersonalPlus5(x86) Crack
  3. 2416开发板上网卡芯片lan9220的时序配置问题
  4. 很抱歉,这场大会我们没法卖票给你了
  5. Python第三章-字符串
  6. c++ 将输入存储到数组,然后反转数组,最后输出
  7. linux dd devzero,makefile中ifeq与ifneq dev/null和dev/zero简介 dd命令
  8. Python pip使用国内镜像
  9. easyui树形菜单生成算法,及在关系型数据库中的存储方式(非递归,高效算法)
  10. idea搭建maven项目关于数据库连接jar包版本问题解决方案
  11. 苹果推出雷雳 3 Pro连接线:黑色编织设计 售价949元
  12. Android版日语学习应用的逆向分析
  13. JAVA实现网页版斗地主_使用Java实现简单的斗地主案例
  14. Spring源码解析一 (IOC容器初始化深度解析)
  15. MySQL 权限操作
  16. Android进阶之路——Flurry的使用
  17. 卸载windows 自带 内置软件应用 2022,windows垃圾清理技巧
  18. ElasticSearch学习笔记(八)Java AP实现增删改查
  19. 微信账户如何解除对第三方应用的授权
  20. 网页上显示word和Excel

热门文章

  1. 网页代码扒ppt_怎么在网页中在线浏览ppt文档
  2. Pwnable.kr
  3. 数学之旅 上海交通大学
  4. WPCSYS ansys命令
  5. 算法设计与分析之回溯法
  6. asp.net实现视频在线播放
  7. 一文带你了解Java8之Stream
  8. css margin缩写简写
  9. unity shader全局雾效
  10. Windows10 CodeWarrior安装