Cocos2d-x3.0游戏实例之《别救我》第三篇——循环滚动背景

笨木头  2014-05-2 20:14   3.0游戏实例《别救我》,Cocos2d-x,Cocos2d-x3.0   阅读(14,407)   45条评论

 

好,这篇我们来讲解无限循环滚动背景,这个知识已经被讲到烂了,我以前的文章也介绍过,所以就不那么详细地说明了。

 

笨木头花心贡献,啥?花心?不呢,是用心~

转载请注明,原文地址:http://www.benmutou.com/archives/823

文章来源:笨木头与游戏开发

 

 

为什么是循环滚动背景?

用循环滚动背景,其实是因为我想偷懒,因为这样我只需要准备一张图片就可以了。

 

我们最终要创建这样的背景,如图:

 

Cocos2d-x3.0游戏实例之《别救我》第三篇-滚动背景

Cocos2d-x3.0游戏实例之《别救我》第三篇-滚动背景

 

背景是在滚动的,大家有没有看到?(小若:看你妹,这是jpg,不是gif)

 

大家是不是很在意下面的那多出来的一条锯齿?它可不是坐标错位了,这是一个伏笔(还伏笔,你以为写小说啊!)。

 

本篇教程会用到的图片资源到这里下载:http://download.csdn.net/detail/musicvs/7392931

 

创建2张连续的背景图片

要实现循环滚动的背景,需要2张相同的图片实现,图片首尾相接。

 

我们要创建一个新的类,叫做BackgroundLayer,用来实现滚动背景。

 

创建2张相同的背景图片,很简单,代码如下:


1
2
3
4
5
6
7
8
9
10
Size visibleSize = Director::getInstance()->getVisibleSize();

 /* 背景图片 */
 m_bg1 = Sprite::create("background.jpg");
 m_bg1->setPosition(Point(visibleSize.width * 0.5f, visibleSize.height * 0.5f));
 this->addChild(m_bg1);

 m_bg2 = Sprite::create("background.jpg");
 m_bg2->setPosition(Point(visibleSize.width * 0.5f, -visibleSize.height * 0.5f));
 this->addChild(m_bg2);
m_bg1和m_bg2都是Sprite对象,因为后面要用到,所以直接作为类的成员属性,方便调用。

m_bg1是屏幕居中,m_bg2要紧接着m_bg1的下面,大家感受一下。

 

创建边缘锯齿

先跑一下题,我们把边缘锯齿也给添加好:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 创建边缘锯齿 */
 auto border = Sprite::create("border.png");
 Size borderSize = border->getContentSize();

 auto border1 = createBorder(Point(borderSize.width * 0.5f, borderSize.height * 0.5f));
 this->addChild(border1);

 auto border2 = createBorder(Point(visibleSize.width - borderSize.width * 0.5f, borderSize.height * 0.5f));
 border2->setFlippedX(true);
 this->addChild(border2);

 auto border3 = createBorder(Point(visibleSize.width * 0.5f, visibleSize.height * 0.15f));
 borderSize = border3->getContentSize();
 border3->setRotation(90.0f);
 this->addChild(border3);
一共三个锯齿,左右各一个,下方一个。

createBorder是自定义函数,代码如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Sprite* BackgroundLayer::createBorder(Point pos)
{
    auto border = Sprite::create("border.png");

    Size borderSize = border->getContentSize();

    auto body = PhysicsBody::createBox(borderSize);
    body->setDynamic(false);
    body->setCategoryBitmask(1);    // 0001
    body->setCollisionBitmask(1);   // 0001
    body->setContactTestBitmask(1); // 0001
    border->setPhysicsBody(body);
    border->setPosition(pos);

    return border;
}
好,这个函数要稍微解释一下,这里使用PhysicsBody的createBox函数创建实体盒子刚体,因为边缘不是空心的。

然后调用了setDynamic函数,让刚体成为静态物体,也就是说,物理世界不会对它起产生影响了,它不会被撞飞,随你怎么撞,它都纹丝不动~

但是,它会对其他物理对象产生影响,比如有人撞了它,那个人就可能会反弹~

 

接着,有三个很特别的函数:setCategoryBitmask、setCollisionBitmask、setContactTestBitmask。

这三个函数是用于物体间的碰撞检测的,用来作为判断条件,要解释它们需要不小的篇幅,所以我就不解释了(小若:有没有墙?我想撞一下)

本游戏在碰撞检测方面极其简单,所以不理解这三个函数都完全没有影响,因为游戏里的所有对象都能产生碰撞,没有什么特别的地方。

如果以后有机会,我再单独写一篇文章来介绍吧(或者大家百度一下)

 

目前的代码

好,来看看目前为止BackgroundLayer的代码,头文件如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef BackgroundLayer_H
#define BackgroundLayer_H

#include "cocos2d.h"
USING_NS_CC;

class BackgroundLayer : public Layer
{
public:
    BackgroundLayer();
    ~BackgroundLayer();
    CREATE_FUNC(BackgroundLayer);
    virtual bool init();
private:
    Sprite* m_bg1;
    Sprite* m_bg2;

    Sprite* createBorder(Point pos);
};

#endif
Cpp文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include "BackgroundLayer.h"

BackgroundLayer::BackgroundLayer(){}

BackgroundLayer::~BackgroundLayer(){}

bool BackgroundLayer::init()
{
    if (!Layer::init())
    {
        return false;
    }

    Size visibleSize = Director::getInstance()->getVisibleSize();

    /* 背景图片 */
    m_bg1 = Sprite::create("background.jpg");
    m_bg1->setPosition(Point(visibleSize.width * 0.5f, visibleSize.height * 0.5f));
    this->addChild(m_bg1);

    m_bg2 = Sprite::create("background.jpg");
    m_bg2->setPosition(Point(visibleSize.width * 0.5f, -visibleSize.height * 0.5f));
    this->addChild(m_bg2);

    /* 创建边缘锯齿 */
    auto border = Sprite::create("border.png");
    Size borderSize = border->getContentSize();

    auto border1 = createBorder(Point(borderSize.width * 0.5f, borderSize.height * 0.5f));
    this->addChild(border1);

    auto border2 = createBorder(Point(visibleSize.width - borderSize.width * 0.5f, borderSize.height * 0.5f));
    border2->setFlippedX(true);
    this->addChild(border2);

    auto border3 = createBorder(Point(visibleSize.width * 0.5f, visibleSize.height * 0.15f));
    borderSize = border3->getContentSize();
    border3->setRotation(90.0f);
    this->addChild(border3);
    return true;
}

Sprite* BackgroundLayer::createBorder(Point pos)
{
    auto border = Sprite::create("border.png");

    Size borderSize = border->getContentSize();

    auto body = PhysicsBody::createBox(borderSize);
    body->setDynamic(false);
    body->setCategoryBitmask(1);    // 0001
    body->setCollisionBitmask(1);   // 0001
    body->setContactTestBitmask(1); // 0001
    border->setPhysicsBody(body);
    border->setPosition(pos);

    return border;
}
先测试一下

我们来先测试一下代码的运行情况吧,我们给TollgateScene添加BackgroundLayer层,修改一下TollgateScene的scene函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Scene* TollgateScene::scene()
{
    auto scene = Scene::createWithPhysics();

    /* 这里省略了很多代码 */

    /* 背景层 */
    auto backgroundLayer = BackgroundLayer::create();
    scene->addChild(backgroundLayer, 0);

    auto layer = TollgateScene::create();
    scene->addChild(layer, 10);

    return scene;
}
 

OK,这样就可以了,再次运行代码,正常情况下,如图所示:

 

Cocos2d-x3.0游戏实例之《别救我》第三篇-滚动背景

Cocos2d-x3.0游戏实例之《别救我》第三篇-滚动背景

(小若:这就是一开始的那张图吧?连图片地址都一样好吧)

 

现在地图是不会滚动的,没意思,我们来开始滚床单…不,不好意思,习惯了(邪恶),是滚动背景才对。

 

统一控制游戏逻辑

地图滚动,其实就是不断改变2张地图的坐标,要不断改变坐标,就要用schedule来实现,schedule可以在游戏每一帧或者每隔一段时间的时候执行一次逻辑,这个如果不了解的,可以看看我的另外几篇关于schedule的文章:

【木头Cocos2d-x 032】我是定时器(第01章)—我爱单线程之schedule介绍

【木头Cocos2d-x 033】我是定时器第02章—HelloWorld之scheduleUpdate

【木头Cocos2d-x 034】我是定时器(第03章)—真正的定时器之schedule

【木头Cocos2d-x 035】我是定时器(第04章)—停止update和触发器

 

木头我有一个坏习惯,那就是把游戏中所有的逻辑都用一个schedule来完成,这么说有点模糊,直接看代码吧。

 

首先给TollgateScene添加一个logic函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TollgateScene : public Layer
{
public:
    ~TollgateScene();
    static Scene* scene();
    CREATE_FUNC(TollgateScene);
    virtual bool init();

    virtual void onExit() override;
private:
    void logic(float dt);

    BackgroundLayer* m_backgroundLayer;
};

#endif
 

以及我偷偷添加了一个BackgroundLayer成员变量,大有用处,不要着急~

然后修改一下TollgateScene的scene函数:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Scene* TollgateScene::scene()
{
    auto scene = Scene::createWithPhysics();

    /* 这里省略了很多代码 */

    /* 背景层 */
    auto backgroundLayer = BackgroundLayer::create();
    scene->addChild(backgroundLayer, ,0);

    auto layer = TollgateScene::create();
    scene->addChild(layer, 10);

    layer->m_backgroundLayer = backgroundLayer;
    return scene;
}
 

留意最后一句代码(小若:是倒数第二句!),好吧,倒数第二句,我们保留了BackgroundLayer的引用。也许这样保持引用是比较糟糕的做法,或许用getChildByTag的方式来获取BackgroundLayer会好一些,但因为这对象要使用很多次,我选择了直接保存引用。大家根据个人喜好来决定吧~

 

OK,最重要的,看看TollgateScene的logic函数实现,有点复杂,大家要有心理准备:


1
2
3
4
void TollgateScene::logic(float dt)
{
    m_backgroundLayer->logic(dt);
}
 

(小若:= =哇,好复杂啊,完全看不懂….(蛇精病))

 

我想,大家已经理解我之前说的,统一控制逻辑的意思了吧?由TollgateScene场景来调用各个层的logic函数,这样很方便,要停止逻辑的时候,只要由TollgateScene控制一下就可以了,不需要对各个层单独地进行控制。

 

OK,别忘了,在TollgateScene的init函数加上schedule的调用:


1
2
3
4
5
6
7
8
9
bool TollgateScene::init()
{
    if (!Layer::init())
    {
        return false;
    }
    this->schedule(schedule_selector(TollgateScene::logic));
    return true;
}
 

BackgroundLayer背景层的逻辑

好了,我们还得为BackgroundLayer添加一个logic逻辑处理函数,头文件添加函数声明:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class BackgroundLayer : public Layer
{
public:
    BackgroundLayer();
    ~BackgroundLayer();
    CREATE_FUNC(BackgroundLayer);
    virtual bool init();

    void logic(float dt);
private:
    Sprite* m_bg1;
    Sprite* m_bg2;

    Sprite* createBorder(Point pos);
};
 

BackgroundLayer的logic函数实现如下,这个就真的有点小复杂了:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void BackgroundLayer::logic(float dt) {
    int posY1 = m_bg1->getPositionY(); // 背景地图1的Y坐标
    int posY2 = m_bg2->getPositionY(); // 背景地图2的Y坐标

    int iSpeed = 3;    // 地图滚动速度

    /* 两张地图向上滚动(两张地图是相邻的,所以要一起滚动,否则会出现空隙) */
    posY1 += iSpeed;
    posY2 += iSpeed;

    /* 屏幕宽 */
    int iVisibleHeight = Director::getInstance()->getVisibleSize().height;

    /* 当第1个地图完全离开屏幕时,让第2个地图完全出现在屏幕上,同时让第1个地图紧贴在第2个地图后面 */
    if (posY1 > iVisibleHeight * 1.5f) {
        posY2 = iVisibleHeight * 0.5f;
        posY1 = -iVisibleHeight * 0.5f;
    }
    /* 同理,当第2个地图完全离开屏幕时,让第1个地图完全出现在屏幕上,同时让第2个地图紧贴在第1个地图后面 */
    if (posY2 > iVisibleHeight * 1.5f) {
        posY1 = iVisibleHeight * 0.5f;
        posY2 = -iVisibleHeight * 0.5f;
    }

    m_bg1->setPositionY(posY1);
    m_bg2->setPositionY(posY2);
}
 

原理我就不解释了,大家看看代码注释,然后自己在纸上画一画,很好理解的。反正目的就是让两张背景不断往上移动,一旦图片完全离开屏幕,就让它回到屏幕下方,然后又继续往上移动。

 

好了,现在运行游戏,就能看到背景在滚动了~

 

OK,下一篇我们就正式加入主角了。

 

 

45 评论

  1. logic在BackgroundLayer定义了 然而在调用m_backgroundLayer->logic(dt);却提示BackgroundLayer没有成员logic

      1. 解决了 只是比较奇怪 除了在上包含cpp上包含头文件 还要在TollgateScene.h里面也要包含头文件

      2. 现在我碰到个问题就是不知道怎么再入TMXTiledMap的地图进去,我尝试做的是一个超级玛丽的游戏

        1. 载入地图时 0x770E017E (ntdll.dll) (blind.exe 中)处有未经处理的异常: 0x00000000: 操作成功完成。。 都是这种不知道怎么找的问题

          1. 这种问题可以使用调试模式(F5)运行游戏,出现错误时,点击中断按钮,它会跳到错误的地方。
            如果错误的地方是底层代码,则可以在【调用堆栈】窗口看到程序执行步骤,双击某一行,可以定位代码。一直往上一个步骤找,直到找到自己的代码为止。

      1. 真的不太懂啊,大神,我在错误列表中找不到错误,在输出列表中就看到了
        2>BackGroundLayer.obj : error LNK2005: “public: void __thiscall BackgroundLayer::logic(float)” (?logic@BackgroundLayer@@QAEXM@Z) 已经在 AppDelegate.obj 中定义
        2>Tollgatescene.obj : error LNK2005: “public: void __thiscall BackgroundLayer::logic(float)” (?logic@BackgroundLayer@@QAEXM@Z) 已经在 AppDelegate.obj 中定义
        2>Tollgatescene.obj : error LNK2005: “private: void __thiscall Tollgatescene::logic(float)” (?logic@Tollgatescene@@AAEXM@Z) 已经在 AppDelegate.obj 中定义
        2> 正在创建库 .Debug.win32do_not_save_me.lib 和对象 .Debug.win32do_not_save_me.exp
        2>.Debug.win32do_not_save_me.exe : fatal error LNK1169: 找到一个或多个多重定义的符号
        然后就生成失败了,我找了百度也找不到什么合适的方法。

  2. Pingback: skype free
  3. Pingback: java updates
  4. Pingback: firefox free download
  5. Pingback: google download
  6. Pingback: Vanessa Smith
  7. 笨木头大哥, 猪脚物理攻击力, 太高了,多踹几脚, 会踹破PhysicsBody::createEdgeBox 物理世界节点 怎么办 难道上面也要做个border

  8. 你好,一直想知道PhysicsBody的相关api在哪看?他们里面有好多函数,但是都不知道用来干嘛的。。。

      1. 0x0104601C 处有未经处理的异常(在 DSM.exe 中): 0xC0000005: 读取位置 0x00000000 时发生访问冲突。ntdll.dll!778c37eb()ntdll.dll!778c37be()缺失或不正确没有为kernel32.dll加载符号

          1. 在调试模式运行就会弹出0x0104601C 处有未经处理的异常(在 DSM.exe 中): 0xC0000005: 读取位置 0x00000000 时发生访问冲突。

  9. 我按照你的步骤走到这,在背景滚动这部分,出现了 m_bg1,m_bg2,都没有得到赋值,调试中显示 ??,提示无法读取内存….怎么回事呢

  10. 谢谢木头同学,我现在清楚了,原来我的思维一直按照锚点在左下角思考,多谢了。

  11. /* 屏幕宽 */ int iVisibleHeight = Director::getInstance()->getVisibleSize().height; /* 当第1个地图完全离开屏幕时,让第2个地图完全出现在屏幕上,同时让第1个地图紧贴在第2个地图后面 */ if (posY1 > iVisibleHeight * 1.5f) { posY2 = iVisibleHeight * 0.5f; posY1 = -iVisibleHeight * 0.5f; } /* 同理,当第2个地图完全离开屏幕时,让第1个地图完全出现在屏幕上,同时让第2个地图紧贴在第1个地图后面 */ if (posY2 > iVisibleHeight * 1.5f) { posY1 = iVisibleHeight * 0.5f;g posY2 = -iVisibleHeight * 0.5f; }我感觉这里写错了,虽然没运行游戏,呵呵,不要喷我哦我觉得应该修改成这样: /* 屏幕宽 */ int iVisibleHeight = Director::getInstance()->getVisibleSize().height; /* 当第1个地图完全离开屏幕时,让第2个地图完全出现在屏幕上,同时让第1个地图紧贴在第2个地图后面 */ if (posY1 > iVisibleHeight * 1.0f) { posY2 = iVisibleHeight * 0.0f; posY1 = -iVisibleHeight * 1.0 f; } /* 同理,当第2个地图完全离开屏幕时,让第1个地图完全出现在屏幕上,同时让第2个地图紧贴在第1个地图后面 */ if (posY2 > iVisibleHeight * 1.0f) { posY1 = iVisibleHeight * 0.0f; posY2 = -iVisibleHeight * 1.0f; }也不知对不对,感觉按照你的代码我无法理解,为什么是1.5f呢?

  12. 好奇怪,我添加完BackgroundLayer的功能,物理世界调试模式显示出来的边框就不见了。Scene *TollgateScene::Myscene(){ // 使场景具备物理世界的功能 auto scene = Scene::createWithPhysics(); /* 微重力世界 */ Vect gravity(0, -0.5f); // 设置重力方向,这个点是相对发射点 scene->getPhysicsWorld()->setGravity(gravity); /* 开启测试模式 */ scene->getPhysicsWorld()->setDebugDrawMask(PhysicsWorld::DEBUGDRAW_ALL); // 创建一个边界 Size visibleSize = Director::getInstance()->getVisibleSize(); /* 创建一个空心盒子刚体,作为我们游戏世界的边界(避免游戏内的物体跑出屏幕) 参数分别是刚体大小、材质(其实就是一些预设的配置数据)、边线厚度 */ auto body = PhysicsBody::createEdgeBox(Size(visibleSize.width, visibleSize.height), PHYSICSBODY_MATERIAL_DEFAULT, 3); /* 创建一个节点,用于承载刚体,这样刚体就能参与到游戏的物理世界 */ auto node = Node::create(); node->setPosition(Point(visibleSize.width / 2, visibleSize.height / 2)); node->setPhysicsBody(body); scene->addChild(node); // 背景 auto backgroundLayer = BackgroundLayer::create(); scene->addChild(backgroundLayer, 1); auto layer = TollgateScene::create(); scene->addChild(layer, 10); layer->m_backgroundLayer = backgroundLayer; return scene;}一开始是没这问题,当加了,schedule后边框就不见了,现在 我把schedule注释掉,边框还是不出来,好奇怪。

发表评论

电子邮件地址不会被公开。 必填项已用*标注