zoukankan      html  css  js  c++  java
  • 植物大战僵尸经典开发步骤

    植物大战僵尸一直是一个很受欢迎的经典的小游戏,我主要用cocos2d-android做了一个类似的小demo,在这里主要介绍一下我做给这个小demo。

    开发前各种准备工作

    做一个小游戏我们首先要有一个地图吧,所以我用tiled这个软件来制作地图,安装和使用都挺简单了,画好后用notepad++打开看一下图片路径对不对,然后把图片、字体文件、地图文件.ttf放到工程的assets目录下,然后我们就可以在后面使用这些资源了。
    当然我,我们先来了解一下一些其他相关知识点

    加载地图

    CCTMXTiledMap map=CCTMXTiledMap.tiledMap("map.tmx");
        this.addChild(map);
    

    解析地图
    //解析地图

    private void parseMap() {
          roadPoints=new ArrayList<CGPoint>();
    
         CCTMXObjectGroup objectGroupNamed=map.objectGroupNamed("road");
        ArrayList<HashMap<String,String>> objects=objectGroupNamed.objects;
        for(HashMap<String,String> hashMap:objects){
    
            int x=Integer.parseInt(hashMap.get("x"));
            int y=Integer.parseInt(hashMap.get("y"));
            CGPoint cgPoint=ccp(x,y);
            roadPoints.add(cgPoint);
        }
    
    }
    

    //展示僵尸

    int position=3;
    private void loadZombies() {
        CCSprite sprite=CCSprite.sprite("z_1_01.png");
        sprite.setPosition(roadPoints.get(position));
        sprite.setAnchorPoint(0.5f,0);
    
        sprite.setScale(0.65f);
    
        this.addChild(sprite);
    
    }
    

    粒子系统

    eg:飘雪:CCParticleSnow

    private void loadParticle() {
        system = CCParticleSnow.node();
        // 设置雪花的样式
        system.setTexture(CCTextureCache.sharedTextureCache().addImage("f.png"));
        this.addChild(system, 1);       
    }
    
    system.stopSystem();// 停止粒子系统
    

    自定义效果,后缀.plis


    声音引擎

            SoundEngine engine=SoundEngine.sharedEngine();
            // 1 上下文 2. 音乐资源的id  3 是否循环播放
            engine.playSound(CCDirector.theApp, R.raw.psy, true);
    

    暂停和继续

    1.onExit();
    2.onEnter();

    @Override
    public boolean ccTouchesBegan(MotionEvent event) {
        this.onExit(); // 暂停
        this.getParent().addChild(new PauseLayer());// 让场景添加新的图层 
        return super.ccTouchesBegan(event);
    }
    
    // 专门用来暂停的图层
    private class PauseLayer extends CCLayer{
        private CCSprite heart;
        public PauseLayer(){
    
        this.setIsTouchEnabled(true);// 打开触摸事件的开关
            heart = CCSprite.sprite("heart.png");
            // 获取屏幕的尺寸
            CGSize winSize = CCDirector.sharedDirector().getWinSize();
            heart.setPosition(winSize.width/2, winSize.height/2);// 让图片再屏幕的中间
    
            this.addChild(heart);
        }
        // 当点击PauseLayer的时候 
        @Override
        public boolean ccTouchesBegan(MotionEvent event) {
            CGRect boundingBox = heart.getBoundingBox();
            //  把Android坐标系中的点 转换成Cocos2d坐标系中的点 
            CGPoint convertTouchToNodeSpace = this.convertTouchToNodeSpace(event);
            if(CGRect.containsPoint(boundingBox, convertTouchToNodeSpace)){// 确实点击了心
    
    
                this.removeSelf();// 回收当前图层
                DemoLayer.this.onEnter();//游戏继续
            }
    
            return super.ccTouchesBegan(event);
        }
    }
    

    项目正式开始

    首先要有一个logo,logo下面有一个背景图片,需要加载一个进度条,一步步跳转就可以了,说到这个进度条,其实就是一个帧动画
    用下面这段代码来看一下,当然这里之后我抽取了一个CommonUtils工具类,
    这里写图片描述

    private void loading() {
            CCSprite loading=CCSprite.sprite("image/loading/loading_01.png");
            loading.setPosition(winSize.width/2, 30);
            this.addChild(loading);
    
            CCAction animate = CommonUtils.getAnimate("image/loading/loading_%02d.png", 9, false);
            loading.runAction(animate);
    
            start = CCSprite.sprite("image/loading/loading_start.png");
            start.setPosition(winSize.width/2, 30);
            start.setVisible(false);// 暂时不可见
            this.addChild(start);
        }

    当然我们可以来看一下这个工具类,这在之后的开发中有很多的实用价值:

    `public class CommonUtils {
        /**
         * 切换图层 
         * @param newLayer  新进入的图层
         */
        public static void changeLayer(CCLayer newLayer){
            CCScene scene=CCScene.node();
            scene.addChild(newLayer);
            CCFlipXTransition transition=CCFlipXTransition.transition(2, scene, 0);
            CCDirector.sharedDirector().replaceScene(transition);//切换场景 ,参数 新的场景
        }
        /**
         * 解析地图上 对象的所有点
         * @param map  地图
         * @param name  对象的名字
         * @return
         */
        public static List<CGPoint> getMapPoints(CCTMXTiledMap map,String name){
            List<CGPoint> points = new ArrayList<CGPoint>();
            // 解析地图
            CCTMXObjectGroup objectGroupNamed = map.objectGroupNamed(name);
            ArrayList<HashMap<String, String>> objects = objectGroupNamed.objects;
            for (HashMap<String, String> hashMap : objects) {
                int x = Integer.parseInt(hashMap.get("x"));
                int y = Integer.parseInt(hashMap.get("y"));
                CGPoint cgPoint = CCNode.ccp(x, y);
                points.add(cgPoint);
            }
            return points;
        }
        /**
         * 创建了序列帧的动作
         * @param format  格式化的路径
         * @param num   帧数
         * @param isForerver  是否永不停止的循环
         * @return
         */
        public  static CCAction getAnimate(String format,int num,boolean isForerver){
            ArrayList<CCSpriteFrame> frames=new ArrayList<CCSpriteFrame>();
            //String format="image/loading/loading_%02d.png";
            for(int i=1;i<=num;i++){
                CCSpriteFrame spriteFrame = CCSprite.sprite(String.format(format, i)).displayedFrame();
                frames.add(spriteFrame);
            }
            CCAnimation anim=CCAnimation.animation("", 0.2f, frames);
            // 序列帧一般必须永不停止的播放  不需要永不停止播放,需要指定第二个参数 false
            if(isForerver){
                CCAnimate animate=CCAnimate.action(anim);
                CCRepeatForever forever=CCRepeatForever.action(animate);
                return forever;
            }else{
                CCAnimate animate=CCAnimate.action(anim,false);
                return animate;
            }
    
        }
    }`

    至于其他小的细节我就不一一啰嗦了,只说一下僵尸和植物对战需要的几个关键代码:

    /**
     * 处理游戏开始后的操作
     * 
     * 
     */
    public class GameCotroller {
        private GameCotroller() {
        }
    
        private static GameCotroller cotroller = new GameCotroller();
    
        public static GameCotroller getInstance() {
    
            return cotroller;
        }
    
        public static boolean isStart; // 游戏是否开始
    
        private CCTMXTiledMap map;
        private List<ShowPlant> selectPlants;
    
        private static List<FightLine> lines; // 管理了五行
    
        private List<CGPoint> roadPoints;
        static {
            lines = new ArrayList<FightLine>();
            for (int i = 0; i < 5; i++) {
                FightLine fightLine = new FightLine(i);
                lines.add(fightLine);
            }
        }
    
        /**
         * 开始游戏
         * 
         * @param map
         *            游戏的地图
         * @param selectPlants
         *            玩家已选植物的集合
         */
        public void startGame(CCTMXTiledMap map, List<ShowPlant> selectPlants) {
            isStart = true;
            this.map = map;
            this.selectPlants = selectPlants;
    
            loadMap();
            // 添加僵尸
    
            // 定时器
            // 参数1 方法名(方法带float类型的参数) 参数2 调用方法的对象 参数3 间隔时间 参数4 是否暂停
            CCScheduler.sharedScheduler().schedule("addZombies", this, 1,false);
            // CCCallFunc.action(target, selector)
            // addZombies();
    
            // 安放植物
            // 僵尸攻击植物
            // 植物攻击僵尸
    
             progress();
        }
    
        CGPoint[][] towers = new CGPoint[5][9];
    
        private void loadMap() {
            roadPoints = CommonUtils.getMapPoints(map, "road");
            for (int i = 1; i <= 5; i++) {
                List<CGPoint> mapPoints = CommonUtils.getMapPoints(map,
                        String.format("tower%02d", i));
                for (int j = 0; j < mapPoints.size(); j++) {
                    towers[i - 1][j] = mapPoints.get(j);
                }
            }
    
        }
    
        /***
         * 添加僵尸
         * 
         * @param t
         */
        public void addZombies(float t) {
            Random random = new Random();
            int lineNum = random.nextInt(5);// [0-5)
            PrimaryZombies primaryZombies = new PrimaryZombies(
                    roadPoints.get(lineNum * 2), roadPoints.get(lineNum * 2 + 1));
            map.addChild(primaryZombies,1);// 让僵尸一直在植物的上面
            lines.get(lineNum).addZombies(primaryZombies);// 把僵尸记录到行战场中
    
            progress+=5;
            progressTimer.setPercentage(progress);//设置新的进度
        }
    
        public void endGame() {
            isStart = false;
        }
    
        private ShowPlant selectPlant; // 玩家选择的植物
        private Plant installPlant;
    
        /**
         * 当游戏开始后处理点击事件的方法
         * 
         * @param point
         *            点击到的点
         */
        public void handleTouch(CGPoint point) {
            CCSprite chose = (CCSprite) map.getParent().getChildByTag(
                    FightLayer.TAG_CHOSE);
            if (CGRect.containsPoint(chose.getBoundingBox(), point)) {
                // 认为玩家有可能选择了植物
                if (selectPlant != null) {
                    selectPlant.getShowSprite().setOpacity(255);
                    selectPlant = null;
                }
                for (ShowPlant plant : selectPlants) {
                    CGRect boundingBox = plant.getShowSprite().getBoundingBox();
                    if (CGRect.containsPoint(boundingBox, point)) {
                        // 玩家选择了植物
                        selectPlant = plant;
                        selectPlant.getShowSprite().setOpacity(150);
                        int id = selectPlant.getId();
                        switch (id) {
                        case 1:
                            installPlant =new PeasePlant();
                            break;
                        case 4:
                            installPlant = new Nut();
                            break;
                        default:
                            break;
                        }
                    }
                }
            } else {
                // 玩家有可能安放植物
                if (selectPlant != null) {
                    int row = (int) (point.x / 46) - 1; // 1-9 0-8
                    int line = (int) ((CCDirector.sharedDirector().getWinSize().height - point.y) / 54) - 1;// 1-5
                                                                                                            // 0-4
                    // 限制安放的植物的范围
                    if (row >= 0 && row <= 8 && line >= 0 && line <= 4) {
    
                        // 安放植物
                        // selectPlant.getShowSprite().setPosition(point);
                        // installPlant.setPosition(point); // 坐标需要修改
                        installPlant.setLine(line);// 设置植物的行号
                        installPlant.setRow(row); // 设置植物的列号
    
                        installPlant.setPosition(towers[line][row]); // 修正了植物的坐标
                        FightLine fightLine = lines.get(line);
                        if (!fightLine.containsRow(row)) {  // 判断当前列是否已经添加了植物 如果添加了 就不能再添加了
                            fightLine.addPlant(installPlant);// 把植物记录到了行战场中
                            map.addChild(installPlant);
                        }
                    }
                    installPlant = null;
                    selectPlant.getShowSprite().setOpacity(255);
                    selectPlant = null;// 下次安装需要重新选择
                }
            }
        }
        CCProgressTimer progressTimer;
        int  progress=0;
        private void progress() {
            // 创建了进度条
            progressTimer = CCProgressTimer.progressWithFile("image/fight/progress.png");
            // 设置进度条的位置 
            progressTimer.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 13);
            map.getParent().addChild(progressTimer); //图层添加了进度条 
            progressTimer.setScale(0.6f);  //  设置了缩放 
    
            progressTimer.setPercentage(0);// 每增加一个僵尸需要调整进度,增加5
            progressTimer.setType(CCProgressTimer.kCCProgressTimerTypeHorizontalBarRL);  // 进度的样式
    
            CCSprite sprite = CCSprite.sprite("image/fight/flagmeter.png");
            sprite.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 13);
            map.getParent().addChild(sprite);
            sprite.setScale(0.6f);
            CCSprite name = CCSprite.sprite("image/fight/FlagMeterLevelProgress.png");
            name.setPosition(CCDirector.sharedDirector().getWinSize().width - 80, 5);
            map.getParent().addChild(name);
            name.setScale(0.6f);
        }
    
    }
    

    还有一个很关键的就是:

    /**
     * 对战界面的图层
     * 
     */
    public class FightLayer extends BaseLayer {
        public static final int TAG_CHOSE = 10;
        private CCTMXTiledMap map;
        private List<CGPoint> zombilesPoints;
        private CCSprite choose; // 玩家可选植物的容器
        private CCSprite chose; // 玩家已选植物的容器
    
        public FightLayer() {
            init();
        }
    
        private void init() {
            loadMap();
            parserMap();
            showZombies();
            moveMap();
        }
    
        // 加载展示用的僵尸
        private void showZombies() {
            for (int i = 0; i < zombilesPoints.size(); i++) {
                CGPoint cgPoint = zombilesPoints.get(i);
                ShowZombies showZombies = new ShowZombies();
                showZombies.setPosition(cgPoint);// 给展示用的僵尸设置了位置
                map.addChild(showZombies);// 注意: 把僵尸加载到地图上
            }
        }
    
        private void parserMap() {
            zombilesPoints = CommonUtils.getMapPoints(map, "zombies");
        }
    
        // 移动地图
        private void moveMap() {
            int x = (int) (winSize.width - map.getContentSize().width);
            CCMoveBy moveBy = CCMoveBy.action(3, ccp(x, 0));
            CCSequence sequence = CCSequence
                    .actions(CCDelayTime.action(4), moveBy, CCDelayTime.action(2),
                            CCCallFunc.action(this, "loadContainer"));
            map.runAction(sequence);
        }
    
        private void loadMap() {
            map = CCTMXTiledMap.tiledMap("image/fight/map_day.tmx");
            map.setAnchorPoint(0.5f, 0.5f);
            CGSize contentSize = map.getContentSize();
            map.setPosition(contentSize.width / 2, contentSize.height / 2);
            this.addChild(map);
        }
    
        // 加载两个容器
        public void loadContainer() {
            chose = CCSprite.sprite("image/fight/chose/fight_chose.png");
            chose.setAnchorPoint(0, 1);
            chose.setPosition(0, winSize.height);// 设置位置是屏幕的左上角
            this.addChild(chose,0,TAG_CHOSE);
    
            choose = CCSprite.sprite("image/fight/chose/fight_choose.png");
            choose.setAnchorPoint(0, 0);
            this.addChild(choose);
    
            loadShowPlant();
    
    
            start = CCSprite.sprite("image/fight/chose/fight_start.png");
            start.setPosition(choose.getContentSize().width/2, 30);
            choose.addChild(start);
        }
    
        private List<ShowPlant> showPlatns; // 展示用的植物的集合
    
        // 加载展示用的植物
        private void loadShowPlant() {
            showPlatns = new ArrayList<ShowPlant>();
            for (int i = 1; i <= 9; i++) {
                ShowPlant plant = new ShowPlant(i); // 创建了展示的植物
    
                CCSprite bgSprite = plant.getBgSprite();
                bgSprite.setPosition(16 + ((i - 1) % 4) * 54,
                        175 - ((i - 1) / 4) * 59);
                choose.addChild(bgSprite);
    
                CCSprite showSprite = plant.getShowSprite();// 获取到了展示的精灵
                // 设置坐标
                showSprite.setPosition(16 + ((i - 1) % 4) * 54,
                        175 - ((i - 1) / 4) * 59);
                choose.addChild(showSprite); // 添加到了容器上
    
                showPlatns.add(plant);
            }
            setIsTouchEnabled(true);
        }
        public void unlock(){
            isLock=false;
        }
        private List<ShowPlant> selectPlants = new CopyOnWriteArrayList<ShowPlant>();// 已经选中植物的集合
        boolean isLock;
        boolean isDel; // 是否删除了选中的植物
        private CCSprite start;
        @Override
        public boolean ccTouchesBegan(MotionEvent event) {
    
            // 需要把Android坐标系中的点 转换成Cocos2d坐标系中的点
            CGPoint point = this.convertTouchToNodeSpace(event);
            if(GameCotroller.isStart){// 如果游戏开始了 交给GameCtoller 处理
                GameCotroller.getInstance().handleTouch(point);
    
                return super.ccTouchesBegan(event);
            }
    
    
            CGRect boundingBox = choose.getBoundingBox();
            CGRect choseBox = chose.getBoundingBox();
    
            // 玩家有可能反选植物
            if(CGRect.containsPoint(choseBox, point)){
                isDel=false;
                for(ShowPlant plant:selectPlants){
                    CGRect selectPlantBox = plant.getShowSprite().getBoundingBox();
                    if(CGRect.containsPoint(selectPlantBox, point)){
                        CCMoveTo moveTo=CCMoveTo.action(0.5f, plant.getBgSprite().getPosition());
                        plant.getShowSprite().runAction(moveTo);
                        selectPlants.remove(plant);// 走到这一步 确实代表反选植物了
                        isDel=true;
                        continue;//  跳出本次循环,继续下次循环
                    }
                    if(isDel){
                        CCMoveBy ccMoveBy=CCMoveBy.action(0.5f, ccp(-53, 0));
                        plant.getShowSprite().runAction(ccMoveBy);
                    }
                }
    
            }else if (CGRect.containsPoint(boundingBox, point)) {
                if(CGRect.containsPoint(start.getBoundingBox(), point)){
                    // 点击了一起来摇滚
                    ready();
    
                }else if (selectPlants.size() < 5&&!isLock) {  //如果已经选择满了 就不能再选择了
                    // 有可能 选择植物
                    for (ShowPlant plant : showPlatns) {
                        CGRect boundingBox2 = plant.getShowSprite()
                                .getBoundingBox();
                        if (CGRect.containsPoint(boundingBox2, point)) {// 如果点恰好落在植物展示的精灵矩形之中
                            // 当前植物被选中了
                            isLock=true;
                    //      System.out.println("我被选中了...");
    
                            CCMoveTo moveTo = CCMoveTo.action(
                                    0.5f,
                                    ccp(75 + selectPlants.size() * 53,
                                            255));
                            CCSequence sequence=CCSequence.actions(moveTo, CCCallFunc.action(this, "unlock"));
                            plant.getShowSprite().runAction(sequence);
                            selectPlants.add(plant);
                        }
                    }
                }
    
            }
    
            return super.ccTouchesBegan(event);
        }
    
        /**
         * 点击了一起来摇滚 做的操作
         */
        private void ready() {
            // 缩小玩家已选植物容器
            chose.setScale(0.65f);
            // 把选中的植物重新添加到 存在的容器上
            for(ShowPlant plant:selectPlants){
    
                plant.getShowSprite().setScale(0.65f);// 因为父容器缩小了 孩子一起缩小
    
                plant.getShowSprite().setPosition(
                        plant.getShowSprite().getPosition().x * 0.65f,
                        plant.getShowSprite().getPosition().y
    
                        + (CCDirector.sharedDirector().getWinSize().height - plant
    
                        .getShowSprite().getPosition().y)
                        * 0.35f);// 设置坐标
                this.addChild(plant.getShowSprite());
            }
    
            choose.removeSelf();// 回收容器
            // 地图的平移
            int x = (int) (map.getContentSize().width-winSize.width);
            CCMoveBy moveBy = CCMoveBy.action(1, ccp(x, 0));
            CCSequence sequence=CCSequence.actions(moveBy, CCCallFunc.action(this, "preGame"));
            map.runAction(sequence);
        }
        private CCSprite ready;
        public void preGame(){
            ready=CCSprite.sprite("image/fight/startready_01.png");
            ready.setPosition(winSize.width/2, winSize.height/2);
            this.addChild(ready);
            String format="image/fight/startready_%02d.png";
            CCAction animate = CommonUtils.getAnimate(format, 3, false);
            CCSequence sequence=CCSequence.actions((CCAnimate)animate, CCCallFunc.action(this, "startGame"));
            ready.runAction(sequence);
        }
        public void startGame(){
            ready.removeSelf();// 移除中间的序列帧
            GameCotroller cotroller=GameCotroller.getInstance();
            cotroller.startGame(map,selectPlants);
    
        }
    }
    

    这里写图片描述

    在做这个的过程中总是遇到空指针的异常,例如这次:后来发现是因为我ShowPlant.java这个地方写错了
    这里写图片描述

    做媒一个项目都需要一些成长和一些经验,在之前的CCSequence,CGPoint,CCSprite,CCTMXTiledMap
    后面又学会了CCScheduler.sharedScheduler().schedule(“attackPlant”, this, 0.5f, false),ready.removeSelf();// 移除中间的序列帧等内容,让我对这整个架构有了初步了了解了实践,学习之路很长,我们一起加油!

  • 相关阅读:
    会议总结
    排球比赛积分规则
    我的计算机历程和认识
    排球积分程序
    《如何成为一个高手》观后感
    十八周总结
    十六周总结(流程)
    排球计分程序
    十四周学习总结
    十三周学习总结
  • 原文地址:https://www.cnblogs.com/xiaowangba/p/6314892.html
Copyright © 2011-2022 走看看