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

1.植物大战僵尸

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

首先分析它的功能

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

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

2.开发前导

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPVDghQd-1578500738253)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200104152652357.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rU5sxv1U-1578500652255)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200104152845333.png)]

第三步,修改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();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //调用顺序:视图(CCGLSurfaceView) -》 导演(CCDirector) -》 场景(CCScene) -》 图层(CCLayer) -》 精灵(CCSprite) -》 动作(CCMove)

        // 获取视图
        CCGLSurfaceView view = new CCGLSurfaceView(this); // 创建一个SurfaceView,类似导演眼前的小屏幕,必须传this,底层要强转成Actvity
        setContentView(view);

        // 获取导演的单例对象
        director.attachInView(view); // 开启绘制线程的方法
        director.setDisplayFPS(true); // 显示帧率,表示每秒刷新页面的次数。一般当帧率大于30帧时,基本上人眼看起来比较流畅,帧率和手机性能与程序性能有关
        director.setAnimationInterval(1/60f); // 设置最高帧率位60
        director.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); // 导演运行场景
    }

    @Override
    protected void onResume() {
        super.onResume();
        director.resume(); // 游戏继续
        SoundEngine.sharedEngine().resumeSound(); // 音乐继续
    }

    @Override
    protected void onPause() {
        super.onPause();
        director.pause(); // 游戏暂停
        SoundEngine.sharedEngine().pauseSound(); // 音乐暂停
    }

    @Override
    protected 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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.runAction(sequence);
    }

    /**
     * 3.显示欢迎界面。切记因为用反射调用的该方法,所以访问修饰符一定要是public,这里踩过坑!!!
     */
    public void showWelcome(){
        logo.removeSelf(); // 删除logo
        CCSprite welcome = CCSprite.sprite("image/welcome.jpg"); // 初始化欢迎界面
        welcome.setAnchorPoint(0,0); // 设置锚点为左下角
        this.addChild(welcome);
    }
}
  1. 在assets导入以下图片(fps_image.png),不然应用程序会崩溃,如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lqhYOY7e-1578500652256)(G:\AndroidProject\ZombieVsPlantDemo\app\src\main\assets\fps_images.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DLmHpRpe-1578500652257)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200104154240207.png)]

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DH6ODcgM-1578500652257)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200104154412504.png)]

  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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.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表示只执行一次,默认是true
        return 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表示只执行一次,默认是true
            return animate;
        }
        else {
            CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是true
            CCRepeatForever 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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.runAction(sequence);

        // 异步在后台初始化数据
        new AsyncTask<Void,Void,Void>(){

            @Override
            protected Void doInBackground(Void... voids) {
                try {
                    // 模拟耗时操作
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected 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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.runAction(sequence);

        // 异步在后台初始化数据
        new AsyncTask<Void,Void,Void>(){

            @Override
            protected Void doInBackground(Void... voids) {
                try {
                    // 模拟耗时操作
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected 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
     */
    @Override
    public 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表示只执行一次,默认是true
            return animate;
        }
        else {
            CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是true
            CCRepeatForever 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();// 初始化logo
        showLogo(); // 展示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,再过一秒钟后继续隐藏logo
        logo.runAction(sequence);

        // 异步在后台初始化数据
        new AsyncTask<Void,Void,Void>(){

            @Override
            protected Void doInBackground(Void... voids) {
                try {
                    // 模拟耗时操作
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }

            @Override
            protected 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
     */
    @Override
    public 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表示只执行一次,默认是true
            return animate;
        }
        else {
            CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是true
            CCRepeatForever 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,在植物盒子中添加植物图标,坐标计算规则如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ole7E6mz-1578500652259)(G:\cocos2.x视频\植物大战僵尸笔记\植物选择框.png)]

代码如下:

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
     */
    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public 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
     */
    @Override
    public 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表示只执行一次,默认是true
            return animate;
        }
        else {
            CCAnimate animate = CCAnimate.action(animation); // 第二个参数为false表示只执行一次,默认是true
            CCRepeatForever 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
     */
    @Override
    public 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
     */
    @Override
    public 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包下,游戏相关的对象如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2PuPruHU-1578500652261)(G:\cocos2.x视频\植物大战僵尸笔记\植物大战僵尸的对象.png)]

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-arL9tE8V-1578500652267)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20200105142250805.png)]

  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();
    }

    @Override
    public void move() {
        CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(startPoint,endPoint) / speed,endPoint);
        this.runAction(move);
        baseAction();
    }

    @Override
    public void attack(BaseElement element) {

    }

    @Override
    public void attacked(int attack) {

    }

    @Override
    public 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,4
        CGPoint 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();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //调用顺序:视图(CCGLSurfaceView) -》 导演(CCDirector) -》 场景(CCScene) -》 图层(CCLayer) -》 精灵(CCSprite) -》 动作(CCMove)

        // 获取视图
        CCGLSurfaceView view = new CCGLSurfaceView(this); // 创建一个SurfaceView,类似导演眼前的小屏幕,必须传this,底层要强转成Actvity
        setContentView(view);

        // 获取导演的单例对象
        director.attachInView(view); // 开启绘制线程的方法
        director.setDisplayFPS(true); // 显示帧率,表示每秒刷新页面的次数。一般当帧率大于30帧时,基本上人眼看起来比较流畅,帧率和手机性能与程序性能有关
        //director.setAnimationInterval(1/60f); // 设置最高帧率位60
        director.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); // 导演运行场景
    }

    @Override
    protected void onResume() {
        super.onResume();
        director.resume(); // 游戏继续
        SoundEngine.sharedEngine().resumeSound(); // 音乐继续
    }

    @Override
    protected void onPause() {
        super.onPause();
        director.pause(); // 游戏暂停
        SoundEngine.sharedEngine().pauseSound(); // 音乐暂停
    }

    @Override
    protected 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,4
        CGPoint 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();
    }

    @Override
    public 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();
    }

    @Override
    public void attack(BaseElement element) {

    }

    @Override
    public void attacked(int attack) {

    }

    @Override
    public 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,4
        CGPoint 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();
    }

    @Override
    public 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,4
        CGPoint 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,4
        CGPoint 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,4
        CGPoint 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() {
            @Override
            public 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,4
        CGPoint 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() {
            @Override
            public 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();
    }

    @Override
    public 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();
    }

    @Override
    public 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); // 每隔一秒钟咬一口植物
        }
    }

    @Override
    public void attacked(int attack) {

    }

    @Override
    public 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() {
            @Override
            public 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();
    }

    @Override
    public Bullet createBullet() {
        return null;
    }

    @Override
    public 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);
    }

    @Override
    public 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()方法,添加创建子弹的逻辑,代码如下:
    @Override
    public 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() {
                @Override
                public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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() {
            @Override
            public 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();
    }

    @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();
    }

    @Override
    public 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); // 每隔一秒钟咬一口植物
        }
    }

    @Override
    public void attacked(int attack) {
        life -= attack; // 僵尸掉血
        if (life <= 0){
            destroy(); // 僵尸死亡
        }
    }

    @Override
    public 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();
    }

    @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();
    }

    @Override
    public 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); // 每隔一秒钟咬一口植物
        }
    }

    @Override
    public 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);
            }
        }
    }

    @Override
    public 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();
    }

    @Override
    public 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));
    }

    @Override
    public 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();
    }

    @Override
    public 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; //每个阳关增加25
        CCLabel 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,4
        CGPoint 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; // 每加载一个僵尸,让加载进度+5
        progressTimer.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-100
        progressTimer.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. 战斗界面
    在这里插入图片描述
发布了253 篇原创文章 · 获赞 52 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_41151659/article/details/103900277