2d小游戏 cocos

2018-07-26 10:48:39 LANGZI7758521 阅读数 979

Cocos2d-JS 适配小游戏版本来了!

 

Cocos 老友们无须一直担心 「Cocos2d-JS 开发的游戏能否适配到小游戏上面」,

答案是:当然可以!第一波上线的游戏里面,既有用 Cocos Creator,也有老游戏用 Cocos2d-JS。但是相对于 Cocos Creator 的一键导出和深度优化,Cocos2d-JS 需要用户手动去合并已经支持的小游戏引擎版本。

 

节前答应大家, 1 月 5 日结束前,在官方论坛上提供经过测试验证的 Cocos2d-JS 适配小游戏的版本目前已经发布,赶紧撸起袖子上社区大干一场吧!别问我为什么拖了一周才出来,我不先测稳定了,匆忙发出来你敢用?

 

 

再次提醒,Cocos Creator 不论工具链和引擎框架的优化,都比 Cocos2d-JS 要好非常多,不仅性能提高不少,而且开发效率也大幅提升了,资源充裕的情况下,还是强烈建议升级到 Cocos Creator 方案。

 

 

准备工作

 

 

1、从官方社区论坛下载已经包含适配微信小游戏的 Cocos2d-html5 引擎 (基于Cocos2d-html5 v3.16版本) 和 WeChatGame 依赖文件

 

【下载链接】

http://forum.cocos.com/t/cocos2d-html5-3-16/55119

 

2、下载微信小程序工具

 

【下载链接】

https://mp.weixin.qq.com/debug/wxagame/dev/devtools/devtools.html?t=201814

 

 

 

发布步骤

 

 

 

1、将发布包的 frameworks/cocos2d-html5 替换为官方社区下载下来的微信小游戏定制版 Cocos2d-html5 引擎后,通过命令行 `cocos compile -p web -m release` 重新发布 web 版本。

 

2、把 `WeChatGame` 内的文件拷贝到发布后 `publish/html5` 的目录下。

 

3、由于小游戏上传资源限制为 4mb,所以当整体包体超过 4mb 的时候,需要将资源移到远程服务器中,如果包体没有超过限制,跳过步骤 4 和 5。

 

4、把 `publish/html5` 目录下的 `project.json` 和 `res` 资源文件夹移到服务器目录下。注意:远程资源不应该超过 50mb,这是微信小游戏缓存空间的上限。

 

5、开启服务器后,需要修改 `game.js` 中 `window.REMOTE_SERVER_ROOT` 为当前开启服务器的路径,这样才能加载到远程资源。

 

6、打开微信工具点击创建小程序项目,项目目录选择打包后 `publish/html5` 的目录,添加 appid 和项目名称后进行创建项项目,就可以在微信工具中调试,发布微信小游戏了。(目前用户尚无法申请 appid,请使用微信开发者工具的 “体验小游戏” 功能)

 

 

 

注意事项

 

 

 

1、该解决方案暂时不包含远程资源加载的缓存和更新机制;

 

2、该版本小游戏的适配方案为社区版本,该版本主要让 CP 的现有游戏适配小游戏的进度加快,更好的使用体验请选择 Cocos Creator;

 

3、部分功能由于适配问题目前暂时无法使用,开发者可以参考微信小游戏 API 研究解决方案,目前暂时不支持的功能包括:ccui.WebView、ccui.VideoPlayer、ccui.TextField(可用 cc.EditBox 替代);

 

4、Cocos2d-JS(Cocos2d-html5)适配小游戏的后续技术支持问题,还请各位开发者直接上论坛交流、讨论。

 

来自:http://home.51.com/t349342778/diary/wxitem/164677.html

2016-11-15 10:50:36 axi295309066 阅读数 5502

什么是游戏引擎

游戏引擎是指一些已编写好的可编辑游戏系统或者一些交互式实时图像应用程序的核心组件。这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速地做出游戏程式而不用由零开始。

Cocos2d家族

cocos2d是一个开源的游戏开发框架,利用它可以非常容易的开发2D游戏。 包括以下成员

  • Cocos2d-x
  • Cocos2d-iphone
  • Cocos2d-android

本质区别:开发的语言不一样:Cocos2d-x使用c++、Cocos2d-iphone使用Object-C;共同点:api名字基本一致

我们为什么要使用cocos2d

  • 一款开源的,跨平台,轻量级的2D游戏引擎。
  • 性能良好
  • 广泛的社区支持
  • 拥护很多成功案例。比如捕鱼达人,三国塔防等
  • 使用MIT授权协议。可以自由使用,修改源码,不需要像GPL一样公开源码,可以商用

架构

cocos

  • 图形引擎Cocos2D Graphic
  • 声音引擎CocosDenshion
  • 物理引擎:Box2d、Chipmunk

cocos

架构说明

  • Cocos2D Graphic图形引擎
  • CocosDenshion Audio声音引擎
  • 物理引擎
    • Box2d
    • Chipmunk
  • Lua脚本库

Box2D是一个用于模拟2D刚体物体的C++引擎。作者为Erin Catto。
chipmunk本是一个独立的开源项目,用纯c编写

Lua 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能。Lua由标准C编写而成,几乎在所有操作系统和平台上都可以编译,运行。Lua并没有提供强大的库,这是由它的定位决定的。所以Lua不适合作为开发独立应用程序的语言。Lua 有一个同时进行的JIT项目,提供在特定平台上的即时编译功能。

图形引擎Cocos2D Graphic

CCDirector界面的统一管理者

cocos

图形引擎说明

cocos

  • CCDirector(导演):引擎的控制者,控制场景的切换,游戏引擎属性的设置
  • CCScene (场景):场景类,例如游戏的闪屏,主菜单,游戏主界面等。
  • CCLayer(布景):图层类,每个图层都有自己的触发事件,该事件只能对其拥有的元素有效,而图层之上的元素所包含的元素,是不受其事件管理的
  • CCSprite(人物):精灵类,界面上显示的最小单元
  • CCNode:引擎中最重要的元素,所有可以被绘制的东西都是派生于此。它可以包含其它CCNode,可以执行定时器操作,可以执行CCAction。
  • CCAction(动作):动作类,如平移、缩放、旋转等动作

CCDirector

CCGLSurfaceView

引擎中SurfaceView的子类,实现了SurfaceHolder.Callback接口,但并未实现Runnable接口。
存在问题:
1、绘制界面的线程是谁?
2、该线程是如何启动的?

CCDirector的作用

界面的绘制工作是由GLThread来完成,而GLThread的开启是在CCDirector的attachInView方法中完成的。
管理场景CCScene

CCDirector,导演类,负责管理和切换场景。还负责初始化openGL各项参数。
CCDirector引擎的控制者,控制场景的切换,游戏引擎属性的设置

单例模式,sharedDirector()取得对象

方法 功能描述
sharedDirector() 获取CCDirector对象,单例模式
attachInView() 连接OpenGL的SurfaceView
runWithScene() 运行场景
replaceScene() 替换场景
setDeviceOrientation() 设置横屏竖屏
setDisplayFPS(true) 显示fps帧率
setAnimationInterval(1.0f / 60) 设置帧率,每秒刷新60次
setScreenSize(480, 320) 设置屏幕分辨率,高于设置的分辨率时,引擎会自动适配
resume() 进入运行状态
pause() 暂停
end() 结束


public class MainActivity extends Activity {

    private CCDirector director;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        CCGLSurfaceView surfaceView=new CCGLSurfaceView(this);

        setContentView(surfaceView);
        //  程序只能有一个导演
        director = CCDirector.sharedDirector();
        director.attachInView(surfaceView);// 开启线程

        director.setDeviceOrientation(CCDirector.kCCDeviceOrientationLandscapeLeft);// 设置游戏方向 水平
        director.setDisplayFPS(true);//是否展示帧率
    //  director.setAnimationInterval(1.0f/30);// 锁定帧率  指定一个帧率  向下锁定 
        director.setScreenSize(480, 320);//设置屏幕的大小   可以自动屏幕适配 

        CCScene ccScene=CCScene.node();// 为了api 和cocos-iphone 一致 
        ccScene.addChild(new ActionLayer());//场景添加了图层 
        director.runWithScene(ccScene);//  运行场景

    }
    @Override
    protected void onResume() {
        super.onResume();
        director.resume();
    }
    @Override
    protected void onPause() {
        super.onPause();
        director.onPause();
        //director.pause();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        director.end();// 游戏结束了
    }

}

FirstLayer

public class FirstLayer extends CCLayer {
    private CCSprite ccSprite;


    public  FirstLayer() {
        setIsTouchEnabled(true);// 打开触摸事件开关
        init();
    }
    // 按下的事件 
    @Override
    public boolean ccTouchesBegan(MotionEvent event) {
        //  先把android坐标系中的点 转换成 cocos2d坐标系中的点
        CGPoint convertTouchToNodeSpace = this.convertTouchToNodeSpace(event);
        System.out.println("我被按下了");
        CGRect boundingBox = ccSprite.getBoundingBox(); // 获取精灵的矩形
        // 判断点是否在矩形之中 
        //  参数1  矩形  参数2 点 
        boolean containsPoint = CGRect.containsPoint(boundingBox, convertTouchToNodeSpace);
        if(containsPoint){
            ccSprite.setScale(ccSprite.getScale()+0.2);
        }else{
            ccSprite.setScale(ccSprite.getScale()-0.2);
        }
        //this.getChildByTag(10); 根据Tag标签 找对应的孩子
        return super.ccTouchesBegan(event);
    }


    private void init() {
        CCSprite bg = CCSprite.sprite("bbg_arena.jpg");
        bg.setAnchorPoint(0, 0);
        this.addChild(bg,0);  // 如果第二个参数越大  默认显示的越靠上面, 如果一样大  谁先添加谁显示在下面


        ccSprite = CCSprite.sprite("z_1_attack_01.png");
        ccSprite.setAnchorPoint(0,0);// 设置锚点
        ccSprite.setPosition(100, 100);//设置坐标
        ccSprite.setScale(1); // 设置缩放
        //ccSprite.setFlipY(true);// X水平翻转  Y垂直翻转
        //ccSprite.setOpacity(0);//设置不透明度 值越大 越不透明  0-255
        ccSprite.setVisible(true);// 设置不可显示
        //ccSprite.
        // 把精灵添加到图层上 
        this.addChild(ccSprite);
    //  this.addChild(ccSprite, z);  // 优先级 
        this.addChild(ccSprite, 1, 10);//  参数3 标签 
    }
}

ActionLayer

public class ActionLayer extends CCLayer {
    public ActionLayer(){
        init();
    }

    private void init() {
    //  moveTo();
    //  moveBy();
    JumpBy();
        //scaleBy();
        //rotateBy();
        //rotateTo();
        //bezierBy();
        //fadeIn();
        //ease();  //和加速度有关系的动作
        //tint();
        //blink();
    }

    private void blink() {
        // 三秒钟闪烁3次
        CCBlink blink=CCBlink.action(3, 3);
        getSprite().runAction(blink);
    }

    private void tint() {
        // 专门显示文字的精灵 
        //  参数1  显示的内容 参数2 字体的样式  3 字体的大小
        CCLabel label=CCLabel.labelWithString("那些年,我们在黑马的苦逼日子", "hkbd.ttf", 24);
        label.setColor(ccc3(50, 0, 255));
        label.setPosition(200,200);
        this.addChild(label);

        ccColor3B c=ccc3(100, 255, -100);
        // 参数1 时间 参数2 变化后的颜色 
        CCTintBy by=CCTintBy.action(1, c);
        CCTintBy reverse = by.reverse();
        CCSequence actions = CCSequence.actions(by, reverse);
        CCRepeatForever forever=CCRepeatForever.action(actions);
        label.runAction(forever);
    }

    private void ease() {
        CCMoveTo ccMoveTo=CCMoveTo.action(10, CCNode.ccp(200, 0));
        CCEaseIn easeOut=CCEaseIn.action(ccMoveTo,9); // 让移动按照有一定加速度去移动
        getSprite().runAction(easeOut);

    }

    private void fadeIn() {
        CCFadeIn fadeIn=CCFadeIn.action(10);
        getSprite().runAction(fadeIn);


    }

    private void bezierBy() {
        CCBezierConfig c=new CCBezierConfig();
        c.controlPoint_1=ccp(0, 0);
        c.controlPoint_2=ccp(100, 100);
        c.endPosition=ccp(200, 0);

        CCBezierBy bezierBy=CCBezierBy.action(2, c);
        getSprite().runAction(bezierBy);
    }

    private void rotateTo() {
        CCSprite heart = getHeart();
        heart.setPosition(200, 100);
        CCRotateTo rotateTo=CCRotateTo.action(3, 240);  //偷懒的做法
        heart.runAction(rotateTo);
    }

    private void rotateBy() {
        // 参数2  旋转的角度
        CCRotateBy by=CCRotateBy.action(3, 240);
        getHeart().runAction(by);
    }

    private void scaleBy() {
        // 参数1 时间 参数2 缩放的比例
        CCScaleBy ccScaleBy=CCScaleBy.action(1f, 0.65f);   //基于锚点进行缩放
        CCScaleBy reverse = ccScaleBy.reverse();
        CCSequence sequence=CCSequence.actions(ccScaleBy, reverse);
        CCRepeatForever forever=CCRepeatForever.action(sequence);
        getHeart().runAction(forever);
    }

    private void JumpBy() {
        // 1 时间  单位秒 2 目的地   3 高出的高度  4 跳跃的次数
        CCJumpBy ccJumpBy=CCJumpBy.action(4, ccp(200, 100), 100, 2);
        CCRotateBy ccRotateBy=CCRotateBy.action(2, 360);
        // 并行动作
        CCSpawn ccSpawn=CCSpawn.actions(ccJumpBy, ccRotateBy);//并行起来了   跳跃的过程中伴随着旋转

        //CCJumpBy reverse = ccJumpBy.reverse();
        CCSequence sequence=CCSequence.actions(ccSpawn, ccSpawn.reverse());// 跳上去 跳回来(伴随着旋转) 
        CCRepeatForever forever=CCRepeatForever.action(sequence);// 让串行动作 永不停止循环了
        CCSprite sprite = getSprite();
        sprite.setAnchorPoint(0.5f, 0.5f);  
        sprite.setPosition(50, 50);
        sprite.runAction(forever);
    }

    private void moveBy() {
        CCSprite sprite = getSprite();
        sprite.setPosition(0, 100);
        // 参数1  移动的时间 单位秒  参数2  坐标的改变  
        CCMoveBy ccMoveBy=CCMoveBy.action(2, CCNode.ccp(200, 0)); // 
        CCMoveBy reverse = ccMoveBy.reverse();
        CCSequence sequence=CCSequence.actions(ccMoveBy, reverse);//CCSequence 串行动作  

        sprite.runAction(sequence);
    }

    public void moveTo() {
        CCSprite sprite = getSprite();

        // 参数1  移动的时间 单位秒  参数2  移动的目的地
        CCMoveTo ccMoveTo=CCMoveTo.action(2, CCNode.ccp(200, 0));
        CCIntervalAction reverse = ccMoveTo.reverse();
        CCSequence sequence=CCSequence.actions(ccMoveTo, reverse);//CCSequence 串行动作  
        sprite.runAction(sequence);
    }

    public CCSprite getSprite() {
        CCSprite sprite=CCSprite.sprite("z_1_attack_01.png");
        sprite.setAnchorPoint(0,0);

        this.addChild(sprite);
        return sprite;
    }

    public CCSprite getHeart() {
        CCSprite sprite=CCSprite.sprite("heart.png");
        sprite.setPosition(100, 100);
        this.addChild(sprite);
        return sprite;
    }
}

CCAction

对于一个游戏来说,动作是非常重要的,好的动作可以使游戏的可玩性大大提高,在cocos2d引擎中有一个动作类,CCAction是动作类的基类,动作是定义在节点(CCNode)上的,以节点为目标,主要分为两类瞬时动作和延时动作

CCAction

基本动作
Cocos2d提供的基本动作:瞬时动作、延时动作、运作速度。

1、瞬时动作:就是不需要时间,马上就完成的动作。瞬时动作的共同基类是 InstantAction。
Cocos2d提供以下瞬时动作:

放置 – Place
效果类似于 node.Position = ccp(x, y)。之所以作为一个动作来实现是为了可以与其他动作形成一个连续动作。

隐藏 – Hide
效果类似于 [node setVisible:NO]. 之所以作为一个劢作来实现是为了可以与其他动作形成一个连续动作。

显示 – Show
效果类似于 [node setVisible:YES]. 之所以作为一个动作来实现是为了可以与其他动作形成一个连续动作。

可见切换 – ToggleVisibility

2.延时动作

延时动作就是指动作的完成需要一定时间。因此actionWithDuration 是延时动作执行时的第一个参数,延时动作的共同基类是 CCIntervalAction(包含了组合动作类)。

Cocos2d提供以下瞬时动作(函数命名规则是:XxxxTo: 意味着运动到指定的位置, XxxxBy:意味着运动到按照指定的 x、y 增量的位置。[x、y 可以是负值]):

移动到 – CCMoveTo
移动– CCMoveBy
跳跃到 – CCJumpTo 设置终点位置和跳跃的高度和次数。
跳跃 – CCJumpBy 设置终点位置和跳跃的高度和次数。
贝塞尔 – CCBezierBy 支持 3 次贝塞尔曲线:P0-起点,P1-起点切线方向,P2-终点切线方向,P3-终点。
放大到 – CCScaleTo 设置放大倍数,是浮点型。
放大 – CCScaleBy
旋转到 – CCRotateTo
旋转 – CCRotateBy
闪烁 – CCBlink 设定闪烁次数
色调变化到 – CCTintTo
色调变换 – CCTintBy
变暗到 – CCFadeTo
由无变亮 – CCFadeIn
由亮变无 – CCFadeOut

3.组合动作

按照一定的次序将上述基本动作组合起来,形成连贯的一套组合动作。组合动作包括以下几类:
序列 – CCSequence
Sequence 的使用非常简单,该类也从 CCIntervalAction 派生,本身就可以被 CocosNode 对象执行。该类的作用就是线序排列若干个动作,然后按先后次序逐个执行。
同步 – Spawn
Spawn 的使用非常简单,该类也从 IntervalAction 派生,本身就可以被CocosNode 对象执行。该类的作用就是同时并列执行若干个动作,但要求动作都必须是可以同时执行的。比如:移动式翻转、变色、变大小等。
需要特别注意的是,同步执行最后的完成时间由基本动作中用时最大者决定。
重复有线次数 – Repeate
重复有限次数的动作,该类也从 IntervalAction 派生,可以被 CocosNode 对象执行。
反动作 – Reverse
反动作就是反向(逆向)执行某个动作,支持针对动作序列的反动作序列。反动作不是一个专门的类,而是 CCFiniteAction 引入的一个接口。不是所有的类都支持反动作,XxxxTo 类通常不支持反动作,XxxxBy 类通常支持。
动画 – Animation
动画就是让精灵自身连续执行一段影像,形成模拟运动的效果:行走时的精灵状态、打斗时的状态等。
无限重复 – RepeatForever
RepeatForever 是从 Action 类直接派生的,因此无法参与序列和同步;自身也无法反向执行。该类的作用就是无限期执行某个动作或动作序列,直到被停止。

4.速度变化

基本动作和组合动作实现了针对精灵的各种运动、动画效果的改变,但这样的改变的速度是不变的,通过 CCEaseAction 为基类的类系和 CCSpped 类我们可以很方便的修改精灵执行劢作的速度:由快至慢还是由慢至快。
EaseIn 由慢至快。
EaseOut 由快至慢
EaseInOut 由慢至快再由快至慢。
EaseSineIn
由慢至快。
EaseSineOut 由快至慢
EaseSineInOut 由慢至快再由快至慢。
EaseExponentialIn 由慢至极快。
EaseExponentialOut 由极快至慢。
EaseExponentialInOut 由慢至极快再由极快至慢。
Speed 人工设定速度,还可通过 SetSpeed 不断调整。
5.扩展动作
延时动作 – Delay ,比如在动作序列中增加一个时间间歇
函数调用
函数在动作序列中间或者结束调用某个函数,执行任何需要执行的任务:动作、状态修改等。id acf = [CCCallFunc actionWithTarget:self selector:@selector(CallBack1)];对应的函数为:- (void) CallBack1 {
[sprite runAction:[CCTintBy actionWithDuration:0.5 red:255 green:0 blue:255]]; }
带对象参数–调用自定义函数时,传递当前对象。id acf = [CallFuncN actionWithTarget:self selector:@selector(CallBack2:)];对应的自定义函数:(这里,我们直接使用了该对象)

  • (void) CallBack2:(id)sender {
    [sender runAction:[CCTintBy actionWithDuration:1 red:255 green:0 blue:255]]; }
    带 对象、数据参数–用自定义函数时,传递当前对象和一个常量(也可以是指针)。id acf = [CCCallFuncND actionWithTarget:self selector:@selector(CallBack3:data:) data:(void*)2];对应的自定义函数,我们使用了传递的对象和数据:
    -(void) CallBack3:(id)sender data:(void*)data {
    [sender runAction:[CCTintBy actionWithDuration:(NSInteger)data red:255 green:0 blue:255]]; }

游戏地图制作

工具: Tiled绘图工具
图层:展示整体效果
块层:依据块划分图片后,整张图片会变成一个块层
块:块层中的一个单元
对象层:单独一层,放置一些指定的对象

地图制作,使用Tiled 制作地图。地图大小的宽度表示地图的一行有14 块组成,地图大小的高度表示地图一列有6 块组成,块大小的宽度表示每块的宽度为46px,块大小的高度表示每块的高度为53px。

新建地图文件,如下图1,生成的地图如下图2

游戏地图

游戏地图

接着将下图3中标红的图片拖入Tiled 的图层中。在下图4中直接点击确定

这里写图片描述

这里写图片描述

在图2.5 中选择一块草地点击,将鼠标移动到新建的地图中点击鼠标左键安放草块,如图2.6。

这里写图片描述

这里写图片描述

在图层中选择一块道路点击,如图2.7,将鼠标移动到新建的地图中点击鼠标左键安放道路,做出一条九曲十八弯的小路,如图2.8。

这里写图片描述

这里写图片描述

设置拐点。先添加一个对象层,如图2.9。然后在工具栏点击添加对象,如图2.10,最后在地图上的拐点处单击鼠标左键,如图2.11。

这里写图片描述

这里写图片描述

这里写图片描述

步骤:
1、新建地图,设置地图大小(以块为单位),设置块大小(以像素为单位)
2、显示网格需要设置视图中的显示网格选项
3、通过地图—新图块,来导入地图资源,从而利用工具进行地图的编辑
4、对象层,通过在图层里右键添加。

<?xml version="1.0" encoding="UTF-8"?>
<map version="1.0" orientation="orthogonal" width="14" height="6" tilewidth="46" tileheight="54">
 <tileset firstgid="1" name="bk1" tilewidth="46" tileheight="54">
  <image source="bk1.jpg" width="678" height="320"/>
 </tileset>
 <layer name="block" width="14" height="6">
  <data encoding="base64" compression="zlib">
   eJwTY2BgECMDW6BhQuK4zCDEprY+YtxMyBx8GADLYgpH
  </data>
 </layer>
 <objectgroup name="road" width="14" height="6">
  <object x="22" y="80"/>
  <object x="160" y="82"/>
  <object x="161" y="244"/>
  <object x="299" y="243"/>
  <object x="298" y="82"/>
  <object x="436" y="83"/>
  <object x="436" y="246"/>
  <object x="620" y="246"/>
 </objectgroup>
</map>

生成的tmx文件说明:
1、Image source属性中填写相对路径
2、层和对象命名尽量使用英文
3、关于对象层中信息,封装在objectgroup中
4、objectgroup中信息按照操作顺序进行排列object

游戏地图加载

加载:CCTMXTiledMap.tiledMap(name);
添加到当前显示层,存在问题:
无法进行图片的移动
用户的touch事件处理:
setIsTouchEnabled(true);
CCTMXTiledMap.touchMove(MotionEvent event, CCTMXTiledMap tiledMap);
存在问题:地图移动
获取图片大小,设置图片位置为宽高的一半
设置锚点为0.5,0.5

public class DemoLayer extends CCLayer {
    private CCTMXTiledMap map;
    private List<CGPoint> roadPoits;

    public DemoLayer() {
        this.setIsTouchEnabled(true);//打开触摸事件的开关
        init();
    }

    private void init() {
        // 加载地图
        loadMap();
        parserMap();
        loadParticle();
        loadZombies();
    }

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

    int position = 0;
    private CCSprite sprite;// 僵尸
    private CCParticleSystem system;

    // 展示僵尸
    private void loadZombies() {
        sprite = CCSprite.sprite("z_1_01.png");
        sprite.setPosition(roadPoits.get(0));
        sprite.setAnchorPoint(0.5f, 0); // 设置锚点在两腿之间
        sprite.setScale(0.65f);
        sprite.setFlipX(true); // 水平翻转

        map.addChild(sprite);// 通过地图去添加僵尸 // 地图随着手指移动,僵尸也会随着手指移动

        // 序列帧的播放
        ArrayList<CCSpriteFrame> frames = new ArrayList<CCSpriteFrame>();
        String format = "z_1_%02d.png";// 02d 占位符 可以表示两位的整数 如果不足两位前面用0补足
        for (int i = 1; i <= 7; i++) {
            CCSpriteFrame displayedFrame = CCSprite.sprite(
                    String.format(format, i)).displayedFrame();
            frames.add(displayedFrame);
        }

        // 配置序列帧的信息 参数1 动作的名字(给程序员看的) 参数2 每一帧播放的时间 单位秒 参数3 所有用到的帧
        CCAnimation anim = CCAnimation.animation("走路", 0.2f, frames);
        CCAnimate animate = CCAnimate.action(anim);
        // 序列帧动作默认是永不停止的循环
        CCRepeatForever forever = CCRepeatForever.action(animate);
        sprite.runAction(forever);

        moveToNext();
    }
    int speed=40;// 僵尸的速度
    public void moveToNext() {
        position++;
        if (position < roadPoits.size()) {
            CGPoint cgPoint = roadPoits.get(position);
            float t=CGPointUtil.distance(roadPoits.get(position-1), cgPoint)/speed;
            CCMoveTo moveTo = CCMoveTo.action(t, cgPoint);
            // 调用一个对象的某一个方法
            CCSequence ccSequence = CCSequence.actions(moveTo,
                    CCCallFunc.action(this, "moveToNext"));
            sprite.runAction(ccSequence);

        } else {
            //雪停下来
            system.stopSystem();// 停止粒子系统
            sprite.stopAllActions();//停止所有动作    
            //  跳舞 
            dance();
            SoundEngine engine=SoundEngine.sharedEngine();
            // 1 上下文 2. 音乐资源的id  3 是否循环播放
            engine.playSound(CCDirector.theApp, R.raw.psy, true);
        }
    }



    private void dance() {
        sprite.setAnchorPoint(0.5f, 0.5f);
        CCJumpBy ccJumpBy=CCJumpBy.action(2, ccp(-20,10), 10, 2);
        CCRotateBy by=CCRotateBy.action(1, 360);
        CCSpawn ccSpawn=CCSpawn.actions(ccJumpBy, by);
        CCSequence sequence=CCSequence.actions(ccSpawn, ccSpawn.reverse());
        CCRepeatForever forever=CCRepeatForever.action(sequence);
        sprite.runAction(forever);
    }

    // 解析地图
    private void parserMap() {
        roadPoits = 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);
            roadPoits.add(cgPoint);
        }
    }
    @Override
    public boolean ccTouchesMoved(MotionEvent event) {
        map.touchMove(event, map);// 地图会随着手指的移动而移动  如果该方法生效 必须保证地图的锚点在中间位置
        return super.ccTouchesMoved(event);
    }

    // 加载地图
    private void loadMap() {
        map = CCTMXTiledMap.tiledMap("map.tmx");
        map.setAnchorPoint(0.5f,0.5f);
        // 因为修改了锚点 ,所以坐标也是需要修改的
        map.setPosition(map.getContentSize().width/2,map.getContentSize().height/2);
        this.addChild(map);
    }

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

粒子系统

什么是粒子系统

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

粒子系统

实现方式:

使用代码设置参数
导入属性列表文件的方式来配置所要添加的粒子系统。

实现步骤:

CCParticleSystem system= CCParticleSnow.node();
system.setTexture(CCTextureCache.sharedTextureCache().addImage("f.png"));
this.addChild(system, 1);

自定义效果:

制作工具ParticleDesigner,使用工具将效果导出成.plist文件

CCParticleSystem ps=CCParticleSystemPoint.particleWithFile("f.plist");
ps.setPosition(0,winSize.height);
addChild(ps);

CocosDenshion Audio声音引擎

SoundEngine处理声音,内部封装的是MediaPlayer和SoundPool
声音类型:音乐和音效
可以从长短上来做区分,音乐一般用于游戏的背景声音,音效主要是短小的声音,如植物发射子弹,僵尸吃植物时发出的声音
声音控制,如播放、暂停、声音大小调节、静音
通过缓存音乐和音效文件来提高声音处理的效率

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

游戏暂停处理

处理游戏暂停思路
当前的主CCLayer处于暂停状态,处于暂停状态的CCLayer无法接收到用户的触摸事件
在主CCLayer暂停之前需要启动另外一个CCLayer显示,除提示玩家游戏已经暂停外,如果玩家需要切换到游戏界面需要点击当前的CCLayer中的按钮完成继续游戏的处理。
注意:
1、该暂停用CCLayer必须添加到与主CCLayer相同的parent下,不能再主CCLayer继续添加
2、点击完成时需要清除当前显示的CCLayer
核心方法:onEnter()——游戏继续
onExit——游戏暂停

//处理游戏暂停
//使用到的方法:CCLayer中的onEnter和onExit

@Override
public boolean ccTouchesEnded(MotionEvent event) {
PauseLayer pauseLayer=new PauseLayer();
this.getParent().addChild(pauseLayer);
onExit();
return super.ccTouchesEnded(event);
}

private class PauseLayer extends CCLayer
{
CCSprite sprite ;
public PauseLayer() {
setIsTouchEnabled(true);
sprite = CCSprite.sprite("ZombiesWon.jpg");
sprite.setScale(0.3);
sprite.setPosition(240, 160);
this.addChild(sprite);
}
@Override
public boolean ccTouchesEnded(MotionEvent event) {
if (isClick(event, sprite)) {
MyDemoLayer.this.onEnter();
this.removeSelf();
}
return true;
}
}

protected boolean isClick(MotionEvent event,CCNode sprite)
{
CGPoint clickPoint = this.convertTouchToNodeSpace(event);
return CGRect.containsPoint(sprite.getBoundingBox(), clickPoint);
}

2017-11-30 17:52:12 Jailman 阅读数 92846

(1)用cocos2d-x开发的中国象棋游戏源码
使用Cocos2d-X2.2.3开发的一款中国象棋游戏,游戏中可以实现、新局面、悔棋、游戏音乐、胜利后会显示游戏结果。


源码下载:http://www.okbase.net/file/item/27881

 

(2)Cocos2dx开发的一个横版过关游戏Demo源码
来自游戏开发基友群听雨分享的例子,一个横版过关游戏,有带简单ai的敌人,有横版的地图,有操控摇杆,主角的攻击效果,有很多新鲜的东 
西。


源码下载:http://www.okbase.net/file/item/27882

 

(3)Cocos2d-x游戏《忍者飞镖打幽灵》源码
初次接触Cocos2d-x的学习demo,发射飞镖击中飞过来的幽灵,可以作为初学参考。
环境: cocos2dx2.2+vs2012


源码下载:http://www.okbase.net/file/item/27890

 

(4)Cocos2dx 跑酷类游戏源代码《萝莉快跑》
跑酷类游戏,画面精美,如果Cocos2dx使用2.2版本,可能需要修改加载声音资源类。十分简单。


源码下载:http://www.okbase.net/file/item/27892

 

(5)MoonWarriors雷电战神传说Cocos2d-X游戏源代码
因为最近在研究cocos2d-x,前几日看到cocos2d-html5中有款飞机游戏MoonWarriors,想着能不能用cocos2d-x来做重构,一方面可以练习一下 
这种飞行游戏的编程思路,另一方面也可以加深对cocos2d-x的了解。


源码下载:http://www.okbase.net/file/item/27071

 

(6)Cocos2d-x 3.x编写的3D跑酷游戏源码
这是一个Cocos2d-x 3.3制作的3d跑酷demo,大家可以学习下Cocos2d-x 3.3 提供对Sprite3D的支持,包含教程。
教程主要是通过分析一个跑酷的Demo,来向大家简单的介绍一下Cocos2d-x 3.3 里对3D游戏开发提供的一些功能,并介绍一下他们的使用方法。


源码下载:http://www.okbase.net/file/item/27893

 

(7)开心泡泡猫Cocos2d-x源码
开心泡泡猫是一款一群喵星人闯入了风靡全球的泡泡龙游戏,游戏将将弹珠台的概念结合至传统泡泡类游戏之中,Cocos2D-X开发。

源码下载:http://www.okbase.net/file/item/27900

 

(8)Cocos2dx仿《王者之剑》游戏源码
《王者之剑》作为国内首款横版即时格斗类手机网游,以欧洲圆桌骑士、亚瑟王的故事为背景,以精美细腻的美漫暗黑画风,再现了这段传奇史 
诗。本代码是仿《王者之剑》,仅供学习参考。


源码下载:http://www.okbase.net/file/item/27936

 

(9)Cocos2dx手绘搞怪类游戏《奇怪大冒险》源码
Cocos2dx 2.2游戏源码:奇怪大冒险,(原作熊同学) 质量很好,使用vs2013和cocos2dx 2.2打开即可编译运行。


源码下载:http://www.okbase.net/file/item/27942

 

(10)Cocos2dx游戏《喵汪大战》源码
类似打飞机的猫狗大战游戏。
开发环境:vs2012,cocos2dx2.2


源码下载:http://www.okbase.net/file/item/27943

 

(11)山寨版FlappyBird游戏源代码(cocos2dx)
这是仿制的FlappyBird游戏,作者为xiaohexuehe。
包括两个版本的代码:Unity版和cocos2dx版。
FlappyBird不用多说了,一款极其简单,但是又很火的游戏。我在得知这款疯狂的游戏后,就有一种把它重现的冲动,然后花了我4个多小时, 
产生出了一个可以玩的版本,分享给大家。

源码下载:http://www.okbase.net/file/item/25707

 

(12)三国策略战争游戏cocos2d-x源码
利用 cocos2d-x 实现一个三国战争游戏。 

作者说:搜到一个开源的半成品游戏代码(游戏主体部分没有),但最高兴的是有不错的资源(没有美工引发头疼问题)。游戏虽小,地图只有 
一张,可玩,还会完善,希望做成个dota风格,外加塔防玩法的游戏。

源码下载:http://www.okbase.net/file/item/27070

 

(13)Cocos2d-x手机单机斗地主源码
利用cocos2d-x实现手机单机斗地主游戏。 
完成了斗地主的基本游戏规则,洗牌-发牌-出牌-压牌-判断胜负(计分)。


源码下载:http://www.okbase.net/file/item/26919

 

(14)cocos2d-x 多平台高仿微信打飞机源代码
利用 cocos2d-x 实现多平台高仿微信打飞机游戏。目的是学习 Cocos2d-x 如何支持多平台开发。


源码下载:http://www.okbase.net/file/item/24078

 

(15)cocos2d-x俄罗斯方块游戏源代码
基于cocos2d-x开发的俄罗斯方块游戏,是本人在学习cocos2d-x引擎时,在win7下使用vs2010的C++做的。
附上完整的源代码。请根据自己的cocos2d-x安装路径更改vs工程项目的属性设置,即可调试运行。该代码仅供cocos2d-x游戏开发爱好者研究学习。

源码下载:http://www.okbase.net/file/item/27944

2014-10-16 22:06:12 u010105970 阅读数 3354

学了这么久Cocos2d-X,今天终于可以做出一个简单的小游戏了,游戏非常简单,通过菜单项控制精灵运动

在做游戏前,先学一个新概念

调度器(scheduler):

Cocos2d-x调度器为游戏提供定时事件和定时调用服务。所有Node对象都知道如何调度和取消调度事件,使用调度器有几个好处:

  1. 每当Node不再可见或已从场景中移除时,调度器会停止。
  2. Cocos2d-x暂停时,调度器也会停止。当Cocos2d-x重新开始时,调度器也会自动继续启动。
  3. Cocos2d-x封装了一个供各种不同平台使用的调度器,使用此调度器你不用关心和跟踪你所设定的定时对象的销毁和停止,以及崩溃的风险。


使用调度器时用到的一些函数:

// 让帧循环调用this->update(float dt)函数
 // scheduleUpdate(); 

// 让帧循环去调用制定的函数,时间还是1/60秒
 // schedule(schedule_selector(T19Update::MyScheduleFunc));

 // 定时器,每隔2秒调用T19Update::MyScheduleFunc函数
 // schedule(schedule_selector(T19Update::MyScheduleFunc), 2.0f);

 // 有限次数的定时器,当次数定义得很大时,也认为是无限的,实际次数是制定参数的+1
 schedule(schedule_selector(T19Update::MyScheduleFunc), 1.0f, 3, 2.0f);

 // 只调度一次的定时器
 // scheduleOnce(schedule_selector(T19Update::MyScheduleFunc), 5.0f);

// 停止一个以参数为回调函数的定时器
 unschedule(schedule_selector(T19Update::MyScheduleFunc));

 // 停止update调度
 unscheduleUpdate();

 // 停止所有的调度
 unscheduleAllSelectors();


熟悉了调度器的概念后就开始我们今天的重头戏,一个简单的游戏

首先在工程目录下的Resource文件中添加一张小球的图片


然后定义一个Update类

在Update.h类中添加下面的代码

#ifndef _Update_H_
#define _Update_H_

#include "cocos2d.h"
USING_NS_CC;

class Update : public CCLayer
{
public:
    static CCScene* scene();

    CREATE_FUNC(Update);

    bool init();

    void update(float dt);

    CCSprite* _sprite;

    void Handle(CCObject* sender);

    //表示方向
    int _direction;

    //窗口的大小
    CCSize winSize;
};

#endif


在Update.cpp中添加下面的代码

#include "Update.h"

CCScene* Update::scene()
{
    CCScene* s = CCScene::create();

    Update* layer = Update::create();

    s->addChild(layer);

    return s;
}

bool Update::init()
{
    //初始化父类
    CCLayer::init();

    //得到窗口的大小
    winSize = CCDirector::sharedDirector()->getWinSize();

    //设置坐标
    CCPoint center = ccp(winSize.width / 2, winSize.height / 2);

    //让帧循环调用this->update(float dt)函数
	scheduleUpdate();

    //创建精灵
    _sprite = CCSprite::create("green_edit.png");
    addChild(_sprite);

    //设置精灵的位置
    _sprite->setPosition(center);

    //创建菜单项
    CCMenuItemFont* up = CCMenuItemFont::create("up", this, menu_selector(Update::Handle));
    CCMenuItemFont* down = CCMenuItemFont::create("down", this, menu_selector(Update::Handle));
    CCMenuItemFont* left = CCMenuItemFont::create("left", this, menu_selector(Update::Handle));
    CCMenuItemFont* right = CCMenuItemFont::create("right", this, menu_selector(Update::Handle));
    CCMenuItemFont* Stop = CCMenuItemFont::create("Stop", this,  menu_selector(Update::Handle));

    //创建菜单
    CCMenu* menu = CCMenu::create(up, down, left, right, Stop, NULL);
    addChild(menu);

    //对齐菜单项
    menu->alignItemsVertically();

    //设置菜单项的ID
    up->setTag(1);
    down->setTag(2);
    left->setTag(3);
    right->setTag(4);
    Stop->setTag(0);

    //标记方向
    _direction = -1;

    return true;
}

void Update::Handle(CCObject* sender)
{
    CCNode* node = (CCNode*)sender;

    //得到菜单项的ID,ID对应的是精灵移动的方向
    _direction  = node->getTag();
}

void Update::update(float dt)
{
   // _direction == 1表示精灵向上移动
    if(1 == _direction)
    {
        //精灵向上移动
        //50表示一秒钟移动50个像素
        _sprite->setPositionY(_sprite->getPositionY() + dt * 100);
    }

     // _direction == 2表示精灵向下移动
    if(2 == _direction)
    {
        //精灵向下移动
        //50表示一秒钟移动50个像素
        _sprite->setPositionY(_sprite->getPositionY() - dt * 100);
    }

     // _direction == 3表示精灵向左移动
    if(3 == _direction)
    {
        //精灵向左移动
        //50表示一秒钟移动50个像素
        _sprite->setPositionX(_sprite->getPositionX() - dt * 100);
    }

     // _direction == 4表示精灵向右移动
    if(4 == _direction)
    {
        //精灵向右移动
        //50表示一秒钟移动50个像素
        _sprite->setPositionX(_sprite->getPositionX() + dt * 100);
    }

     // _direction == 4表示精灵停止移动
    if(0 == _direction)
    {
    }
}

执行结果:




2015-06-15 16:38:08 u013271921 阅读数 3844

上周,我做了一个基于 cocos2d-x 的飞机大战的游戏,因为我刚学cocos2d-x没多久,所以这个飞机大战很多都是看着别人的教程,再加上自己的一些想法,来做的。

下面我想说一说我的思路。

飞机大战有三个场景:

  1. LayerGameStart(游戏开始)
  2. LayerGameMain(游戏进行中)
  3. LayerGameOver(游戏结束)

一、游戏开始场景(LayerGameStart)

其中,游戏开始和游戏结束是比较简单的,那我就先从简单的说起,
首先说下游戏开始场景。我们在这个场景里面只需要做一下工作:

  • 预加载一些资源(声音,图片缓存,这些资源都是全局的)
  • 注意要将同一类资源放在一起,便于管理
  • 检测游戏游戏的本地存数数据中是否有游戏最高分(UserDefault

在这个场景,我只想说下如果检测游戏的最高分
具体实现如下:

//判断分数是否已经被存储
bool LayerGameStart::isSaveFile()
{
    //用一个bool值作为标志,如果有则表示分数已经被存储
    if (!UserDefault::getInstance()->getBoolForKey("isSaveFileXml"))
    {
        //如果没有就设置标志并置为真
        UserDefault::getInstance()->setBoolForKey("isSaveFileXml",true);
        //设置最高分,默认值为0
        UserDefault::getInstance()->setIntegerForKey("HightestScore",0);
        ////flush的作用是将数据写入到xml文件中。flush()在windows下是空的。。。呵呵。。。
        UserDefault::getInstance()->flush();
        return false;
    }
    else
        return true;
}
void LayerGameStart::getHightestScore()
{
    if (isSaveFile())
    {
        //在这里设置历史最高得分
        LayerGameOver::_hightestScore = 
            UserDefault::getInstance()->getIntegerForKey("HightestScore",0);
    }
}

然后只需要将 getHightestScore( ) 函数放在 LayerGameStartinit( ) 函数中即可。


二、游戏结束场景(LayerGameOver)

游戏结束场景也很容易,主要实现下面的功能:

  • 显示本局游戏的得分
  • 显示历史最高分
  • 设置“返回游戏”按钮,和“退出”按钮

其中显示本局所得分数,也是比较好实现的,需要注意的是,如何将主场景(LayerGameMain)中的分数传递到游戏结束场景中去。做法如下:

    static LayerGameOver * create(int score);
    static cocos2d::Scene * scene(int score);
    bool init(int score);

在创建场景时将分数传入,即在 init( ) 函数中传入分数,因为 init 在 create 有被调用,而 create 函数又在 scene 函数中被调用,所以这三个函数都有参数了。在切换场景的时候直接将分数传递过来就行。

然后就是显示历史最高分,显示历史最高分需要在游戏结束场景的 class 中添加一个静态成员变量

    static int _hightestScore;//用于存到本地,记得要在class外进行初始化!
    cocos2d::Label * hightestScore;//用于显示

具体实现如下:

    //显示历史最高分
    Value strHightestScore(_hightestScore);
    hightestScore = Label::createWithBMFont("font/font.fnt",strHightestScore.asString());
    hightestScore->setColor(Color3B(30,50,240));
    hightestScore->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
    hightestScore->setPosition(Point(150,winSize.height-40));
    this->addChild(hightestScore);

返回和退出按钮就很简单了,就是设置两个图片作为按钮 (MenuItemSpritebackItemexitItem 然后将这两个精灵添加到 Menu 中去:Menu * menu = Menu::create(backItem, exitItem, nullptr); 这两个按钮点击的回调函数也很简单,返回就是切换到游戏开始场景,退出就是直接退出程序 exit(1);


三、游戏主场景

游戏主场景就是最重要也最难的,主要有一下功能:

  • 添加游戏背景并让游戏背景滚动起来

  • 添加玩家飞机
    1.飞机的动作行为(闪三次后播放自身帧动画,要方便控制,扩大其BoundingBox)
    2.飞机不能飞出屏幕
    3.由于其他层也需要飞机的位置,为了方便其他层获得飞机,将其设计为单例getInstance( )
    4.玩家飞机自身爆炸的动画(因为爆炸后要切换到游戏结束场景,所以要在这里将本局游戏得分传到结束场景)

  • 添加子弹
    1.首先得拿到玩家飞机的位置(因为子弹是从玩家飞机发出的)
    2.设置定时器来产生子弹
    3.让子弹飞~
    4.子弹的行为:与敌机碰撞或者什么也没碰撞到——飞出屏幕外
    5.将子弹放在一个容器里面,便于碰撞检测
    6.不只有单发子弹还有多发子弹(MultiBullets

  • 添加敌机
    1.首先有一个敌机类Enemy,用于产生所有敌机(小敌机,中敌机,打敌机)
    2.敌机产生的位置(屏幕上方,随机产生)
    3.敌机消失在屏幕下方
    4.与子弹和玩家飞机发生碰撞
    5.敌机爆炸动画,玩家得分。

  • 添加道具
    1.在玩游戏时,会用道具出现(从屏幕上方,随机出现)
    2.道具有两种:炸弹和双发子弹(bigBoom,multiBullets)
    3.道具行为:与玩家飞机碰撞或者什么也没碰到——掉到屏幕外
    4.炸弹可以让当前屏幕中所有敌机爆
    5.双发子弹增加玩家飞机的威力(第一次吃到会变成双发子弹,以后再吃到不会再加子弹而是直接给玩家加100分)

  • 添加控制层
    1.更新玩家得分
    2.实现游戏的暂停和继续的功能(添加屏蔽层)

怎么样,头晕了吗?要加这么多层,实现这么多功能。。。还有一些我没写(一时想不起来)
我也不准备,每个都详细说明了,我就说说实现这些功能需要注意的地方吧,也是我觉得比较难的地方。。。

1. 如何实现屏幕滚动:

我在这里其实只用了一个背景图,但是把它加载了两次,然后让两个图片一起向下移动,具体过程请看下图

这里写图片描述

代码实现如下:

//添加背景
void LayerGameMain::addBackground()
{
    SimpleAudioEngine::getInstance()->playBackgroundMusic("sound/game_music.wav",true);

    auto bg1 = Sprite::createWithSpriteFrameName("background.png");
    bg1->setTag(BG1);
    bg1->setAnchorPoint(Point::ZERO);
    bg1->setPosition(Point(0,0));
    this->addChild(bg1);

    auto bg2 = Sprite::createWithSpriteFrameName("background.png");
    bg2->setTag(BG2);
    bg2->setAnchorPoint(Point::ZERO);
    bg2->setPosition(0,bg1->getContentSize().height - 5);//为了不留空隙
    this->addChild(bg2);

    //利用帧循环来实现背景滚动
    this->schedule(schedule_selector(LayerGameMain::movingBackgroundCallback),0.01f);
}

//使得背景滚动起来
void LayerGameMain::movingBackgroundCallback(float dt)
{
    Sprite * bg1 = (Sprite *)this->getChildByTag(BG1);
    Sprite * bg2 = (Sprite *)this->getChildByTag(BG2);

    bg1->setPositionY(bg1->getPositionY() - 2);//每个循环下移2个像素
    bg2->setPositionY(bg1->getPositionY() + bg2->getContentSize().height - 2);

    if (bg2->getPositionY() < 0)
    {
        bg1->setPositionY(0);//重置背景
    }
}

2. 如何将玩家飞机设计为单例?
cocos2d中很多类都有获得单例的函数getInstance( ),这里我也写了这么一个函数来得到飞机单例

class MyPlane : public cocos2d::Sprite
{
        /*省略部分代码*/
        //将飞机设计成全局的
    static MyPlane * getInstance();
    static MyPlane * _splane;
}

//初始化
MyPlane * MyPlane::_splane = nullptr;

MyPlane * MyPlane::getInstance()
{
    if (!_splane)
    {
        _splane = new MyPlane();
        if (_splane && _splane->init())
        {
            //不将其挂到渲染树上,让飞机的生命周期跟场景一样
            //_splane->autorelease();
        }
    }
    return _splane;//return 在if语句外面
}

还有就是玩家飞机爆炸的函数,需要传入飞机爆炸之前得到的分数。好让游戏结束场景能得到分数。

3. 子弹层的设计
因为玩家的飞机是不断移动的,然后子弹的动作都是 MoveTo ,其 create 函数只有时间变量,我们如何使得子弹的速度是一样的呢?很简单,根据 v = s / t;若想要速度一样则在距离不同的情况下,就必须改变子弹运行的时间,所以只要给不同位置发出的子弹不同的时间,就可以使得子弹的速度一样。具体实现如下:

    //得到子弹到屏幕上边沿的距离
    float distance = 
        winSize.height - plane->getPositionY() - plane->getBoundingBox().size.height/2;
    //确定子弹的速度 一秒跨越800个像素。
    float velocity = 800/1;
    //根据距离和速率求得时间
    float movedt = distance / velocity;
    //子弹在movedt的时间内移动到屏幕上边沿之外的地方(加上的 bullet->getContentSize().height 就是超出屏幕的距离)
    MoveTo * to = MoveTo::create(movedt,
        Point(birthPlace.x,winSize.height + bullet->getContentSize().height));

4. 敌机类的设计
这个不说了直接看代码,代码都有注释的
Enemy.h:

#ifndef __Enemy_H_
#define __Enemy_H_
#include "cocos2d.h"

class Enemy : public cocos2d::Node
{
public:
    //构造器
    Enemy();
    //析构器
    ~Enemy();
    //创建敌机
    static Enemy * create();
    //将敌机与其对应的Sprite(图片)和生命值绑定(有三类敌机)
    void bindEnemySprite(cocos2d::Sprite * spr,int life);
    //得到敌机
    cocos2d::Sprite * getSprite();
    //得到生命值
    int getLife();
    //失去生命值
    void loseLife();
    //得到敌机在世界坐标内的的位置和尺寸大小boundingbox
    cocos2d::Rect Get_BoundingBox();

private:
    cocos2d::Sprite * _sprite;
    int _life;
};

#endif

Enemy.cpp

#include "Enemy.h"
USING_NS_CC;

Enemy::Enemy()
{
    //在构造函数中初始化,其实也可以在init函数中初始化,但这里没有init函数
    _sprite = nullptr;
    _life = 0;
}
Enemy::~Enemy()
{

}
Enemy * Enemy::create()
{
    Enemy * pRect = new Enemy();
    if (pRect != nullptr)
    {
        pRect->autorelease();
        return pRect;
    }
    else
        return nullptr;
}
//绑定敌机,不同的敌机有不同的图片,不同的生命值
void Enemy::bindEnemySprite(cocos2d::Sprite * spr,int life)
{
    _sprite = spr;
    _life = life;
    //将_sprite加到 pRect 上!!pRect 实质就是一个Node
    this->addChild(_sprite);
}
Sprite * Enemy::getSprite()
{
    return _sprite;
}
int Enemy::getLife()
{
    return _life;
}
void Enemy::loseLife()
{
    _life--;
}
//自定义的getBoundingBox函数,便于主场景中的碰撞检测
Rect Enemy::Get_BoundingBox()
{
    Rect rect = _sprite->getBoundingBox();
    //本来敌机是加到pRect上的它的坐标是相对于pRect的
    //这里将敌机的坐标转换为世界坐标
    Point position = this->convertToWorldSpace(rect.origin);
    //这里只需要知道敌机的起始坐标,因为敌机的宽度和长度是不会改变的
    Rect enemyRect = Rect(position.x, position.y, rect.size.width, rect.size.height);
    return enemyRect;
}

5.有了敌机类,就要将敌机添加到主场景中去(LayerEnemy)
因为要加3类敌机,其实每一类敌机的添加方法都一样,只不过他们的图片,生命值,出场概率,被击毁后玩家所得的分数不相同罢了。在这里就将添加小敌机的方法说一下,中敌机和大敌机都一样。

    //小敌机更新函数(在定时器里面调用)
    void addSmallEnemyCallback(float dt);
    //小敌机移动完成后(没有碰撞)
    void smallEnemyMoveFinished(cocos2d::Node * node);
    //小敌机爆炸
    void smallEnemyBlowup(Enemy * smallEnemy);
    //移除小敌机
    void removeSmallEnemy(cocos2d::Node * target, void * data);
    //移除所有小敌机
    void removeAllSmallEnemy();

    //容器,用来存放所有小敌机,便于碰撞检测
    cocos2d::Vector<Enemy *> _smallVec;

实现函数:

//添加敌机的回调函数(在帧循环里面调用)
void LayerEnemy::addSmallEnemyCallback(float dt)
{
    Enemy * smallEnemy = Enemy::create();
    //绑定
    smallEnemy->bindEnemySprite(Sprite::createWithSpriteFrameName("enemy1.png"),SMALL_MAXLIFE);
    //加到smallVec中
    _smallVec.pushBack(smallEnemy);
    //确定敌机的坐标:横坐标x是一个随机值
    //smallEnemy->Get_BoundingBox().size.width/2 < x < winSize.width - smallEnemy->Get_BoundingBox().size.width/2
    //注意:这里要使用 Enemy 类里面的Get_BoundingBox() 函数!
    float x = CCRANDOM_0_1()*(winSize.width - 2*smallEnemy->Get_BoundingBox().size.width) + 
         smallEnemy->Get_BoundingBox().size.width/2;
    float y = winSize.height + smallEnemy->Get_BoundingBox().size.height/2;
    Point smallBirth = Point(x,y);
    //设置坐标
    smallEnemy->setPosition(smallBirth);
    this->addChild(smallEnemy);

    MoveTo * to = MoveTo::create(3,Point(smallBirth.x,smallBirth.y - 
        winSize.height - smallEnemy->Get_BoundingBox().size.height));
    CallFuncN * actionDone = CallFuncN::create(this,
            callfuncN_selector(LayerEnemy::smallEnemyMoveFinished));
    Sequence * sequence = Sequence::create(to,actionDone,NULL);
    smallEnemy->runAction(sequence);
}

//敌机爆炸的函数
void LayerEnemy::smallEnemyBlowup(Enemy * smallEnemy)
{
    SimpleAudioEngine::getInstance()->playEffect("sound/enemy1_down.wav");
    Animate * smallAnimate = 
        Animate::create(AnimationCache::getInstance()->animationByName("smallBlowup"));


    /*利用 CallFuncN 来完成 CallFuncND 的功能 !!
    注意这里(我花了很长时间才解决请看http://blog.csdn.net/crayondeng/article/details/18767407)*/

    auto actionDone = 
        CallFuncN::create(CC_CALLBACK_1(LayerEnemy::removeSmallEnemy,this,smallEnemy));
    Sequence * sequence = Sequence::create(smallAnimate,actionDone,NULL);
    smallEnemy->getSprite()->runAction(sequence);//这么写可以吗? smallEnemy->runAction(sequence)不行!
}

//这是没有碰撞的remove
void LayerEnemy::smallEnemyMoveFinished(cocos2d::Node * node)
{
    Enemy * smallEnemy = (Enemy *)node;
    this->removeChild(smallEnemy,true);
    _smallVec.eraseObject(smallEnemy);
    //node->removeAllChildrenWithCleanup(true);
}

//这是碰撞之后的remove
void LayerEnemy::removeSmallEnemy(cocos2d::Node * target,void * data)
{
    Enemy * smallEnemy = (Enemy *)data;
    if (smallEnemy)
    {
        _smallVec.eraseObject(smallEnemy);
        smallEnemy->removeFromParentAndCleanup(true);//和这句等效:this->removeChild(smallEnemy,true);
    }

}

//去掉所有小敌机
void LayerEnemy::removeAllSmallEnemy()
{
    for (auto node : _smallVec)
    {
        Enemy * enemy = (Enemy *)node;
        if (enemy->getLife() > 0)
        {
            this->smallEnemyBlowup(enemy);
        }
    }
}

6. 然后就添加道具层

道具有两种,一个是大炸弹,一个是双发子弹。它们产生的位置都是在屏幕上方,随机产生。
这里主要说一说炸弹,因为炸弹是可以点击的,一点击后,当前屏幕的所有敌机都会爆炸。炸弹的数量减一。所以炸弹需要在主场景的帧循环中不断检测,用一个容器来存放炸弹,玩家飞机一吃到炸弹道具,就更新炸弹数

void LayerGameMain::updateBigBoomCount(int bigBoomCount)
{
    String strBoomCount;//用来显示炸弹的数量
    Sprite * norBoom = Sprite::createWithSpriteFrameName("bomb.png");//正常的图片
    Sprite * selBoom = Sprite::createWithSpriteFrameName("bomb.png");//选择的图片
    if (bigBoomCount < 0)//如果小于0
    {
        return;//则什么也不做
    }
    else if (bigBoomCount == 0)//如果炸弹数等于0
    {
        if (this->getChildByTag(TAG_BIGBOOM))//在主场景里检查是否有炸弹图标
        {
            this->removeChildByTag(TAG_BIGBOOM,true);//如果有,就将其删除
        }
        if (this->getChildByTag(TAG_BIGBOOMCOUNT))//在主场景里面检查是否有炸弹数字标签
        {
            this->removeChildByTag(TAG_BIGBOOMCOUNT,true);//如果有,则删除
        }
    }
    else if (bigBoomCount == 1)//如果炸弹数等于1
    {
        if ( !(this->getChildByTag(TAG_BIGBOOM)) )//检查是否有炸弹图标
        {
            //如果没有,就添加一个炸弹图标(其实是一个菜单项)
            MenuItemSprite * boomItem = MenuItemSprite::create(norBoom,
                                                               selBoom,
                   CC_CALLBACK_1(LayerGameMain::boomMenuCallback,this));
            boomItem->setPosition(norBoom->getContentSize().width/2,
                                  norBoom->getContentSize().height/2);
            Menu * boomMenu = Menu::create(boomItem,nullptr);
            boomMenu->setPosition(Point::ZERO);
            this->addChild(boomMenu,0,TAG_BIGBOOM);
        }
        if ( !(this->getChildByTag(TAG_BIGBOOMCOUNT)) )//检查是否有炸弹数字标签
        {
            //如果没有,就添加一个炸弹数字标签
            strBoomCount.initWithFormat("X %d",bigBoomCount);
            LabelBMFont * labelBoomCount = 
                LabelBMFont::create(strBoomCount.getCString(),"font/font.fnt");
            labelBoomCount->setAnchorPoint(Point::ANCHOR_MIDDLE_LEFT);
            labelBoomCount->setPosition(Point(norBoom->getContentSize().width,
                                        norBoom->getContentSize().height - 30));
            this->addChild(labelBoomCount,0,TAG_BIGBOOMCOUNT);
        }
    }
    else if (bigBoomCount > 1 )//如果炸弹数大于1
    {
        //则只更新炸弹数目
        strBoomCount.initWithFormat("X %d",bigBoomCount);
        LabelBMFont * labelCount = 
            (LabelBMFont *)this->getChildByTag(TAG_BIGBOOMCOUNT);
        labelCount->setString(strBoomCount.getCString());//设置炸弹数目
    }
}

7. 最后来添加控制层
控制层主要是两个作用:1,暂停和继续游戏(添加屏蔽层)2,更新玩家得分
暂停和继续游戏需要两个按钮来控制。刚开始游戏时,游戏是进行着的,没有暂停。当玩家按了暂停按钮后,游戏暂停,按钮变成继续状态(三角形)这个还是比较简单的。下面来看实现代码:

LayerControl.h

#ifndef __LayerControl_H_
#define __LayerControl_H_
#include "cocos2d.h"
#include "LayerNoTouch.h"
class LayerControl : public cocos2d::Layer
{
public:
    CREATE_FUNC(LayerControl);
    bool init();

    void menuCallback(cocos2d::Ref * ref);
    void updateScore(int score);


private:
    cocos2d::MenuItemSprite * pauseMenuItem;
    cocos2d::LabelBMFont * scoreItem;
    LayerNoTouch * _noTouchLayer;
};

#endif

LayerControl.cpp

#include "LayerControl.h"
#include "AppMacros.h"
#include "SimpleAudioEngine.h"
using namespace CocosDenshion;
USING_NS_CC;

bool LayerControl::init()
{
    if (!Layer::init())
    {
        return false;
    }
    _noTouchLayer = nullptr;//初始化
    //暂停按钮不同状态下的两个图片
    Sprite * nor = Sprite::createWithSpriteFrameName("game_pause_nor.png");
    Sprite * press = Sprite::createWithSpriteFrameName("game_pause_pressed.png");
    pauseMenuItem = 
        MenuItemSprite::create(nor,press,CC_CALLBACK_1(LayerControl::menuCallback,this));
    Point menuBrith = Point(pauseMenuItem->getContentSize().width/2 + 10, 
                            winSize.height - pauseMenuItem->getContentSize().height);
    pauseMenuItem->setPosition(menuBrith);
    Menu * pauseMenu = Menu::create(pauseMenuItem,nullptr);
    pauseMenu->setPosition(Point::ZERO);
    this->addChild(pauseMenu,101);//将暂停/继续 按钮放在最前面

    scoreItem = LabelBMFont::create("0","font/font.fnt");
    scoreItem->setColor(Color3B(255,255,0));
    scoreItem->setAnchorPoint(Point(0,0.5));
    scoreItem->setPosition(Point(pauseMenuItem->getPositionX() + nor->getContentSize().width/2 + 5, 
                                 pauseMenuItem->getPositionY()));
    this->addChild(scoreItem);

    return true;
}

//按钮回调函数
void LayerControl::menuCallback(cocos2d::Ref * ref)
{
    if (!Director::getInstance()->isPaused())//如果点击按钮之前游戏没有暂停
    {
        if (SimpleAudioEngine::getInstance()->isBackgroundMusicPlaying())
        {
            //如果背景音乐还在播放,则暂停其播放
            SimpleAudioEngine::getInstance()->pauseBackgroundMusic();
        }
        //则将 暂停/继续 按钮设置为继续状态的按钮
        pauseMenuItem->setNormalImage(Sprite::createWithSpriteFrameName("game_resume_nor.png"));
    pauseMenuItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_resume_pressed.png"));
        //并暂停游戏
        Director::getInstance()->pause();
        //添加屏蔽层,屏蔽层一定要加到其它所有层前面,暂停/继续 按钮的后面!!!
        _noTouchLayer = LayerNoTouch::create();
        this->addChild(_noTouchLayer);
    }
    else
    {
        SimpleAudioEngine::getInstance()->resumeBackgroundMusic();//恢复背景音乐
        pauseMenuItem->setNormalImage(Sprite::createWithSpriteFrameName("game_pause_nor.png"));
    pauseMenuItem->setSelectedImage(Sprite::createWithSpriteFrameName("game_pause_pressed.png"));
        Director::getInstance()->resume();
        this->removeChild(_noTouchLayer,true);
    }
}
//更新游戏得分,这个函数也可以放在主场景中
void LayerControl::updateScore(int score)
{
    /*2.0版本
    String * strScore = String::createWithFormat("%d",score);
    scoreItem->setString(strScore->getCString());*/

    //3.0版本
    Value strScore(score);
    scoreItem->setString(strScore.asString());//更新成绩转换为字符串
}

8. 最后,我们来看看主场景里面都做了什么

bool LayerGameMain::init()
{
    if (!Layer::init())
    {
        return false;
    }
    _bigBoomCount = 0;
    _score = 0;//不要将_score 设置为static ,否则在场景切换时,它不能清零

    this->addBackground();

    this->addMyPlane();

    this->addBulletLayer();//执行了startShoot()

    this->addMultiBulletsLayer();//没有执行startShoot()

    this->addEnemyLayer();

    this->addFoodLayer();

    this->addControlLayer();

    this->scheduleUpdate();//开启定时器便于碰撞检测

    auto listener = EventListenerTouchOneByOne::create();
    listener->setSwallowTouches(true);//触摸吞噬
    listener->onTouchBegan = CC_CALLBACK_2(LayerGameMain::onTouchBegan,this);
    listener->onTouchMoved = CC_CALLBACK_2(LayerGameMain::onTouchesMoved,this);
    this->_eventDispatcher->addEventListenerWithSceneGraphPriority(listener,this);

    return true;
}

这是主场景的初始化函数,大家都应该看到了,依次添加了背景、玩家飞机、子弹层、敌机层、食物层、控制层、最后开启帧循环定时器、设置触摸事件的监听。
怎么样,是不是很简单,很清晰? 哈哈,当做完之后再会过来看自己写的代码还真是挺好。。

最后的最后

来说一说主场景里面的 update 函数。这个函数里面做了整个游所有的碰撞检测。其原理都一样,这里我拿子弹与小敌机的碰撞检测为例说明一下:

//单发子弹与小敌机碰撞
    /*思路:
        两次遍历(即两个for循环),第一次遍历子弹容器(_bulletVector),取出其第一个子弹,
    第二次遍历小敌机容器(_smallVec)将这个取出的子弹与当前屏幕上所有的小敌机做碰撞检测,
    如果检测到碰撞,再判断当前碰撞到的小敌机的生命值_life 若等于1,则小敌机失去生命值
    再分别将当前的子弹和当前的小敌机加到容器 bulletToDel_Small 和 smallToDel 中去,
    当第一个子弹与屏幕上的敌机全部碰撞检测完以后,就把 bulletToDel_Small 和 smallToDel
    里面的对象全部删除,这样可以防止在遍历时发生错误!*/
    Vector<Sprite *> bulletToDel_Small;
    for (auto bt : _bulletLayer->_bulletVector)
    {
        Sprite * bullet = bt;
        Vector<Enemy *> smallToDel;
        for (auto et : _enemyLayer->_smallVec)
        {
            Enemy * enemy = et;
            if (bullet->getBoundingBox().intersectsRect(enemy->Get_BoundingBox()))
            {
                if (enemy->getLife() == 1)
                {
                    enemy->loseLife();
                    bulletToDel_Small.pushBack(bullet);
                    smallToDel.pushBack(enemy);
                    _score += SMALL_SCORE;//加上小敌机的分数
                    _controlLayer->updateScore(_score);
                }
            }

        }
        for(auto et : smallToDel)//注意for循环的位置,要与创建时的语句在同一层
        {
            Enemy * enemy = et;
            _enemyLayer->smallEnemyBlowup(enemy);//敌机爆炸(删除)
        }
    }
    for (auto bt : bulletToDel_Small)//注意for循环的位置,要与创建时的语句在同一层
    {
        Sprite * bullet = bt;
        _bulletLayer->removeBullet(bullet);//删除子弹
    }

好了,就写到这里了,写的很乱,不知道大家能不能看懂。
要是有什么写错了的地方,还望斧正。

最后附上本游戏的完整代码地址: PlaneFight

cocos2d与cocos2d x卸载

阅读数 3292

cocos-2d 游戏开发

阅读数 409