Cocos2d游戏开发学习记录——3.实现僵尸跳舞demo

文章目录

  • 1.案例介绍
  • 2.地图制作
  • 3.实践一
  • 4.粒子系统
  • 5.实践二
  • 6.游戏暂停
  • 7.实践三
  • 8.总结

1.案例介绍

让我们结合之前学到的东西,实现一个小demo。

该demo的描述如下:

在一个九曲十八弯的小路上,一个僵尸冒着大雪前行。最后雪停了,僵尸高兴的跳起了骑马舞。

分析需求

  • 在一个九曲十八弯的小路上

    地图制作(使用Tiled制作)

  • 一个僵尸冒着大雪前行

    粒子系统

  • 最后雪停了

    粒子系统的控制

  • 僵尸高兴的跳起了骑马舞

    声音引擎

2.地图制作

地图素材如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5YuY24Qy-1577980144404)(G:\cocos2.x视频\植物大战僵尸资料\植物大战僵尸\地图制作\bk1.jpg)]

下载Tiled软件,教程可上网搜索,这里将地图制作成如下图所示即可,注意:

  • 注意修改相关属性,尽量使用英文名
  • 将地图文件和图片一起放到项目中的assets目录下,修改地图文件的标签中的source属性,改成与地图图片名相同即可

Cocos2d游戏开发学习记录——3.实现僵尸跳舞demo_第1张图片

3.实践一

  1. 新建DemoLayer,继承CCLayer,并编写构造方法,用于实现该demo,代码如下:
package com.example.cocos2ddemo;

import org.cocos2d.layers.CCLayer;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    public DemoLayer() {
    }
}
  1. 修改MainActivity,使用DemoLayer来作为新的图层,代码如下:
package com.example.cocos2ddemo;

import android.app.Activity;
import android.os.Bundle;

import org.cocos2d.layers.CCScene;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.opengl.CCGLSurfaceView;

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,类似导演眼前的小屏幕
        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(); // 游戏继续
    }

    @Override
    protected void onPause() {
        super.onPause();
        director.pause(); // 游戏暂停
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        director.end(); // 游戏结束
    }
}
  1. 修改DemoLayer,首先获取地图,并且获取地图上那些节点的坐标,并且添加行走动画播放方法walk()(可在之前的博客中copy过来)和僵尸移动方法moveNext(暂时只行走到下一个点),注意一些细节信息的配置,代码如下:
package com.example.cocos2ddemo;

import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.types.CGPoint;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        CCTMXTiledMap map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        this.addChild(sprite);
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
    }

    private void moveNext(){
        CCMoveTo move = CCMoveTo.action(2,mPoints.get(1));
        sprite.runAction(move);
    }

    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }
}
  1. 修改DemoLayer,完善僵尸行走的方法,使其可以走到每一个获取到的节点上,注意三个地方:
    • 为了递归调用moveNext方法,首先使用了index来计量所有节点的数量,然后再调用CCSequence来封装了CCCallFunc方法,这个方法是一个反射方法,可以以递归的形式来反复调用moveNext方法
    • 为了顺利使用CCCallFunc方法来进行反射调用,需要将moveNext()方法的访问修饰符改为public
    • 为了使僵尸行走在每一段路上时都是均衡的速度,调用了CGPointUtil.distance来计算两个节点的距离,再设定僵尸的速度,使用 距离/速度 就可以得到僵尸走每段路所平均需要的时间
    • 代码如下:
package com.example.cocos2ddemo;

import org.cocos2d.actions.base.CCRepeatForever;
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.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    /**
     * 僵尸当前的位置
     */
    private int index;

    /**
     * 僵尸移动的速度
     */
    private int speed = 50;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        CCTMXTiledMap map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        this.addChild(sprite);
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
    }

    /**
     * 如果要通过反射(CCCallFunc.action)调用此方法,必须将访问修改符设置为public
     */
    public void moveNext(){
        index++;
        if (index < mPoints.size()){
            CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(mPoints.get(index - 1),mPoints.get(index)) / speed,mPoints.get(index)); // 通过CGPointUtil.distance计算两个坐标点的距离,再除以速度,就可以得到每一段路所花费的时间,即匀速前进
            CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"moveNext"));// 通过反射调用方法,实现类似于递归的循环
            sprite.runAction(sequence);
        }
        else {
            System.out.println("僵尸走到终点了......");
        }
    }

    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }
}
  1. 修改DemoLayer,因为地图在手机屏幕上显示不全,所以需要修改相应信息,并且重写ccTouchesMoved()方法,实现拖拽屏幕时可以查看整张地图。注意:
    • 需要打开CCLayer的可触控事件开关,因为默认是关闭的
    • 让僵尸与图层进行绑定,不然拖拽地图时僵尸不跟着地图移动(将this.addChild(sprite)修改成map.addChild(sprite))
    • 代码如下:
package com.example.cocos2ddemo;

import android.view.MotionEvent;

import org.cocos2d.actions.base.CCRepeatForever;
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.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 地图
     */
    private CCTMXTiledMap map;

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    /**
     * 僵尸当前的位置
     */
    private int index;

    /**
     * 僵尸移动的速度
     */
    private int speed = 50;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();

        // 打开触摸事件开关
        setIsTouchEnabled(true);
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        map.setAnchorPoint(0.5f,0.5f); // 为了保证地图可以正常触摸,将地图锚点设置为中心位置,默认为0,0
        map.setPosition(ccp(map.getContentSize().width/2,map.getContentSize().height/2)); // 设置位置为地图的中心点

        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        map.addChild(sprite);
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
    }

    /**
     * 如果要通过反射(CCCallFunc.action)调用此方法,必须将访问修改符设置为public
     */
    public void moveNext(){
        index++;
        if (index < mPoints.size()){
            CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(mPoints.get(index - 1),mPoints.get(index)) / speed,mPoints.get(index)); // 通过CGPointUtil.distance计算两个坐标点的距离,再除以速度,就可以得到每一段路所花费的时间,即匀速前进
            CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"moveNext"));// 通过反射调用方法,实现类似于递归的循环
            sprite.runAction(sequence);
        }
        else {
            System.out.println("僵尸走到终点了......");
        }
    }

    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }

    /**
     * 响应触摸移动事件
     * @param event
     * @return
     */
    @Override
    public boolean ccTouchesMoved(MotionEvent event) {
        map.touchMove(event,map); // 地图移动
        return super.ccTouchesMoved(event);
    }
}

4.粒子系统

粒子系统表示三维计算机图形学中模拟一些特定的模糊现象的技术,而这些现象用其它传统的渲染技术难以实现的真实感的 game physics。经常使用粒子系统模拟的现象有火、爆炸、烟、水流、火花、落叶、云、雾、雪、尘、流星尾迹或者象发光轨迹这样的抽象视觉效果等等。

为了实现下雪的效果,需要使用到CCParticleSystem这个类来实现。

在进行实践前,导入以下资源到assets目录下:

在这里插入图片描述

5.实践二

  1. 修改DemoLayer,添加particleSystem()方法,用于让场景中出现下雪效果,代码如下:
package com.example.cocos2ddemo;

import android.view.MotionEvent;

import org.cocos2d.actions.base.CCRepeatForever;
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.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.particlesystem.CCParticleSnow;
import org.cocos2d.particlesystem.CCParticleSystem;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 地图
     */
    private CCTMXTiledMap map;

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    /**
     * 僵尸当前的位置
     */
    private int index;

    /**
     * 僵尸移动的速度
     */
    private int speed = 50;

    /**
     * 粒子系统
     */
    private CCParticleSystem system;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();

        // 打开触摸事件开关
        setIsTouchEnabled(true);
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        map.setAnchorPoint(0.5f,0.5f); // 为了保证地图可以正常触摸,将地图锚点设置为中心位置,默认为0,0
        map.setPosition(ccp(map.getContentSize().width/2,map.getContentSize().height/2)); // 设置位置为地图的中心点

        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        map.addChild(sprite); // 把僵尸添加到地图
        //CCFollow follow = CCFollow.action(sprite); //参数表示跟随的对象
        //map.runAction(follow); // 地图跟随僵尸移动,如果实现地图跟随效果,需要把地点的锚点和位置改为默认值,不要手动设置
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
        //particleSystem(); // 粒子系统——下雪,报出空指针异常,不知道该怎么解决
    }

    /**
     * 粒子系统
     */
    private void particleSystem(){
        system= CCParticleSnow.node(); // 下雪
        //CCParticleSystem system = CCParticleExplosion.node();
        //system.setScale(2); // 设置雪花大小
        //system.setSpeed(10); // 设置雪花速度
        system.setTexture(CCTextureCache.sharedTextureCache().addImage("snow.png")); // 设置雪花图片
        this.addChild(system, 1);
    }

    /**
     * 如果要通过反射(CCCallFunc.action)调用此方法,必须将访问修改符设置为public
     */
    public void moveNext(){
        index++;
        if (index < mPoints.size()){
            CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(mPoints.get(index - 1),mPoints.get(index)) / speed,mPoints.get(index)); // 通过CGPointUtil.distance计算两个坐标点的距离,再除以速度,就可以得到每一段路所花费的时间,即匀速前进
            CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"moveNext"));// 通过反射调用方法,实现类似于递归的循环
            sprite.runAction(sequence);
        }
        else {
            System.out.println("僵尸走到终点了......");
            system.stopSystem(); // 停止粒子系统
        }
    }



    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }

    /**
     * 响应触摸移动事件
     * @param event
     * @return
     */
    @Override
    public boolean ccTouchesMoved(MotionEvent event) {
        map.touchMove(event,map); // 地图移动
        return super.ccTouchesMoved(event);
    }
}

PS:这里作者在试验粒子系统的时候system.setTexture(CCTextureCache.sharedTextureCache().addImage("snow.png"));这行代码报出了空指针异常,然后模拟器闪退。但作者检查了几遍后仍没有发现问题,在assets目录下确实有snow.png这张图片。若有读者对这块有了解的话希望读者可以帮忙修改一下此处的Bug,感激不尽!

  1. 修改DemoLayer,添加骑马舞方法dance()。注意:声音文件要放在res/raw目录下,而不是assets目录下,代码如下:
package com.example.cocos2ddemo;

import android.view.MotionEvent;

import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCJumpBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCRotateBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.actions.interval.CCSpawn;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.particlesystem.CCParticleSnow;
import org.cocos2d.particlesystem.CCParticleSystem;
import org.cocos2d.sound.SoundEngine;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.util.CGPointUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 地图
     */
    private CCTMXTiledMap map;

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    /**
     * 僵尸当前的位置
     */
    private int index;

    /**
     * 僵尸移动的速度
     */
    private int speed = 50;

    /**
     * 粒子系统
     */
    private CCParticleSystem system;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();

        // 打开触摸事件开关
        setIsTouchEnabled(true);
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        map.setAnchorPoint(0.5f,0.5f); // 为了保证地图可以正常触摸,将地图锚点设置为中心位置,默认为0,0
        map.setPosition(ccp(map.getContentSize().width/2,map.getContentSize().height/2)); // 设置位置为地图的中心点

        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        map.addChild(sprite); // 把僵尸添加到地图
        //CCFollow follow = CCFollow.action(sprite); //参数表示跟随的对象
        //map.runAction(follow); // 地图跟随僵尸移动,如果实现地图跟随效果,需要把地点的锚点和位置改为默认值,不要手动设置
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
        //particleSystem(); // 粒子系统——下雪,报出空指针异常,不知道该怎么解决
    }

    /**
     * 粒子系统
     */
    private void particleSystem(){
        system= CCParticleSnow.node(); // 下雪
        //CCParticleSystem system = CCParticleExplosion.node();
        //system.setScale(2); // 设置雪花大小
        //system.setSpeed(10); // 设置雪花速度
        system.setTexture(CCTextureCache.sharedTextureCache().addImage("snow.png")); // 设置雪花图片
        this.addChild(system, 1);
    }

    /**
     * 如果要通过反射(CCCallFunc.action)调用此方法,必须将访问修改符设置为public
     */
    public void moveNext(){
        index++;
        if (index < mPoints.size()){
            CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(mPoints.get(index - 1),mPoints.get(index)) / speed,mPoints.get(index)); // 通过CGPointUtil.distance计算两个坐标点的距离,再除以速度,就可以得到每一段路所花费的时间,即匀速前进
            CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"moveNext"));// 通过反射调用方法,实现类似于递归的循环
            sprite.runAction(sequence);
        }
        else {
            System.out.println("僵尸走到终点了......");
            system.stopSystem(); // 停止粒子系统
            dance();
        }
    }

    /**
     * 图层(僵尸)跳舞
     */
    private void dance(){
        sprite.setAnchorPoint(0.5f,0.5f);
        CCJumpBy jump = CCJumpBy.action(1,ccp(-20,10),20,3);
        CCRotateBy rotate = CCRotateBy.action(1,360);
        CCSpawn spawn = CCSpawn.actions(jump,rotate);
        CCSequence sequence = CCSequence.actions(spawn,spawn.reverse());
        CCRepeatForever repeat = CCRepeatForever.action(sequence);
        sprite.runAction(repeat);

        SoundEngine engine = SoundEngine.sharedEngine();
        engine.playSound(CCDirector.theApp,R.raw.psy,true);
    }

    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }

    /**
     * 响应触摸移动事件
     * @param event
     * @return
     */
    @Override
    public boolean ccTouchesMoved(MotionEvent event) {
        map.touchMove(event,map); // 地图移动
        return super.ccTouchesMoved(event);
    }
}

6.游戏暂停

一般来说,游戏都会提供一个按钮,用于让玩家在游玩时随时随地地暂停。接下来的实践就是要完成这一点。为了方便起见,这里要达到的效果是点击屏幕的任意位置都会停下。

暂停时,会出现以下标识:

在这里插入图片描述

7.实践三

  1. 修改MainActivity,在onResume()方法和onPause()方法中分别添加让音乐恢复的逻辑和让音乐停止的逻辑,代码如下:
package com.example.cocos2ddemo;

import android.app.Activity;
import android.os.Bundle;

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();
        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(); // 游戏结束
    }
}
  1. 修改DemoLayer,重写ccTouchesBegan()方法,添加游戏暂停的逻辑,并且新增内部类PauseLayer,用于在点击屏幕的任意处时,出现一个心脏图标,来提示游戏暂停,然后再次点击图标时,游戏继续。代码如下:
package com.example.cocos2ddemo;

import android.view.MotionEvent;

import org.cocos2d.actions.base.CCRepeatForever;
import org.cocos2d.actions.instant.CCCallFunc;
import org.cocos2d.actions.interval.CCAnimate;
import org.cocos2d.actions.interval.CCJumpBy;
import org.cocos2d.actions.interval.CCMoveTo;
import org.cocos2d.actions.interval.CCRotateBy;
import org.cocos2d.actions.interval.CCSequence;
import org.cocos2d.actions.interval.CCSpawn;
import org.cocos2d.layers.CCLayer;
import org.cocos2d.layers.CCTMXObjectGroup;
import org.cocos2d.layers.CCTMXTiledMap;
import org.cocos2d.nodes.CCAnimation;
import org.cocos2d.nodes.CCDirector;
import org.cocos2d.nodes.CCSprite;
import org.cocos2d.nodes.CCSpriteFrame;
import org.cocos2d.nodes.CCTextureCache;
import org.cocos2d.particlesystem.CCParticleSnow;
import org.cocos2d.particlesystem.CCParticleSystem;
import org.cocos2d.sound.SoundEngine;
import org.cocos2d.types.CGPoint;
import org.cocos2d.types.CGRect;
import org.cocos2d.types.CGSize;
import org.cocos2d.types.util.CGPointUtil;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * 实现demo的图层
 */
public class DemoLayer extends CCLayer {

    /**
     * 地图
     */
    private CCTMXTiledMap map;

    /**
     * 坐标点的集合
     */
    private ArrayList<CGPoint> mPoints;

    /**
     * 僵尸
     */
    private CCSprite sprite;

    /**
     * 僵尸当前的位置
     */
    private int index;

    /**
     * 僵尸移动的速度
     */
    private int speed = 50;

    /**
     * 粒子系统
     */
    private CCParticleSystem system;

    public DemoLayer() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setFlipX(true);
        sprite.setScale(0.5);
        sprite.setAnchorPoint(0.5f,0); // 设置锚点为僵尸的两脚之间
        loadMap();

        // 打开触摸事件开关
        setIsTouchEnabled(true);
    }

    /**
     * 加载地图
     */
    private void loadMap(){
        map = CCTMXTiledMap.tiledMap("map.tmx"); // 加载地图
        map.setAnchorPoint(0.5f,0.5f); // 为了保证地图可以正常触摸,将地图锚点设置为中心位置,默认为0,0
        map.setPosition(ccp(map.getContentSize().width/2,map.getContentSize().height/2)); // 设置位置为地图的中心点

        mPoints = new ArrayList<>(); // 初始化所有的坐标点
        CCTMXObjectGroup road = map.objectGroupNamed("road"); // 根据名字,找到objectGroup
        ArrayList<HashMap<String, String>> objects = road.objects; // 所有坐标点的集合
        for (HashMap<String, String> hashMap : objects) {
            Integer x = Integer.parseInt(hashMap.get("x"));
            Integer y = Integer.parseInt(hashMap.get("y"));
            mPoints.add(ccp(x,y));
        }
        this.addChild(map);
        sprite.setPosition(mPoints.get(0)); // 设置僵尸的初始位置
        map.addChild(sprite); // 把僵尸添加到地图
        //CCFollow follow = CCFollow.action(sprite); //参数表示跟随的对象
        //map.runAction(follow); // 地图跟随僵尸移动,如果实现地图跟随效果,需要把地点的锚点和位置改为默认值,不要手动设置
        walk(); // 播放僵尸行走的动画
        moveNext();// 开始行走
        //particleSystem(); // 粒子系统——下雪,报出空指针异常,不知道该怎么解决
    }

    /**
     * 粒子系统
     */
    private void particleSystem(){
        system= CCParticleSnow.node(); // 下雪
        //CCParticleSystem system = CCParticleExplosion.node();
        //system.setScale(2); // 设置雪花大小
        //system.setSpeed(10); // 设置雪花速度
        system.setTexture(CCTextureCache.sharedTextureCache().addImage("snow.png")); // 设置雪花图片
        this.addChild(system, 1);
    }

    /**
     * 如果要通过反射(CCCallFunc.action)调用此方法,必须将访问修改符设置为public
     */
    public void moveNext(){
        index++;
        if (index < mPoints.size()){
            CCMoveTo move = CCMoveTo.action(CGPointUtil.distance(mPoints.get(index - 1),mPoints.get(index)) / speed,mPoints.get(index)); // 通过CGPointUtil.distance计算两个坐标点的距离,再除以速度,就可以得到每一段路所花费的时间,即匀速前进
            CCSequence sequence = CCSequence.actions(move,CCCallFunc.action(this,"moveNext"));// 通过反射调用方法,实现类似于递归的循环
            sprite.runAction(sequence);
        }
        else {
            System.out.println("僵尸走到终点了......");
            dance();
            system.stopSystem(); // 停止粒子系统
        }
    }

    /**
     * 图层(僵尸)跳舞
     */
    private void dance(){
        sprite.setAnchorPoint(0.5f,0.5f);
        CCJumpBy jump = CCJumpBy.action(1,ccp(-20,10),20,3);
        CCRotateBy rotate = CCRotateBy.action(1,360);
        CCSpawn spawn = CCSpawn.actions(jump,rotate);
        CCSequence sequence = CCSequence.actions(spawn,spawn.reverse());
        CCRepeatForever repeat = CCRepeatForever.action(sequence);
        sprite.runAction(repeat);

        SoundEngine engine = SoundEngine.sharedEngine();
        engine.playSound(CCDirector.theApp,R.raw.psy,true);
    }

    /**
     * 图层(僵尸)行走
     */
    private void walk(){
        // 初始化7帧图片
        ArrayList<CCSpriteFrame> frames = new ArrayList<>();
        String format = "z_1_%02d.png"; // %02d表示两位数字,如果是个位,用0去补位(01,02);如果是十位,则不用补位(10,11)
        // 初始化7帧图片
        for (int i = 1; i <= 7 ; i++) {
            frames.add(CCSprite.sprite(String.format(format,i)).displayedFrame());
        }

        CCAnimation animation = CCAnimation.animation("walk",.2f,frames); // 第二个参数表示每一帧显示时间
        CCAnimate animate = CCAnimate.action(animation);

        CCRepeatForever repeat = CCRepeatForever.action(animate); // 表示动画永远循环,若不循环则会报出空指针异常

        sprite.runAction(repeat);
    }

    /**
     * 响应触摸移动事件
     * @param event
     * @return
     */
    @Override
    public boolean ccTouchesMoved(MotionEvent event) {
        map.touchMove(event,map); // 地图移动
        return super.ccTouchesMoved(event);
    }

    /**
     * 点击屏幕,游戏暂停
     * @param event
     * @return
     */
    @Override
    public boolean ccTouchesBegan(MotionEvent event) {
        this.onExit(); // 游戏暂停
        this.getParent().addChild(new PauseLayer()); // 给父控件添加一个暂停的图层
        return super.ccTouchesBegan(event);
    }

    /**
     * 暂停图层
     */
    class PauseLayer extends CCLayer{

        /**
         * 心脏图标
         */
        private CCSprite heart;

        public PauseLayer() {
            heart = CCSprite.sprite("heart.png");
            CGSize winSize = CCDirector.sharedDirector().winSize();
            heart.setPosition(winSize.width / 2,winSize.height / 2);
            this.addChild(heart);

            setIsTouchEnabled(true); // 打开点击事件
        }

        /**
         * 监听心脏图标的方法
         * @param event
         * @return
         */
        @Override
        public boolean ccTouchesBegan(MotionEvent event) {
            CGPoint point = convertTouchToNodeSpace(event);

            // 判断心脏图标是否被点击
            if (CGRect.containsPoint(heart.getBoundingBox(),point)){
                DemoLayer.this.onEnter(); // 游戏继续
                this.removeSelf(); // 删除暂停的图层
            }
            return super.ccTouchesBegan(event);
        }
    }
}

8.总结

到这里,这个小demo就基本完成了,下篇博客将正式介绍移动端《植物大战僵尸》的制作。
Cocos2d游戏开发学习记录——3.实现僵尸跳舞demo_第2张图片
Cocos2d游戏开发学习记录——3.实现僵尸跳舞demo_第3张图片

你可能感兴趣的:(Cocos2d,cocos2d,游戏开发)