Cocos2d-x3.0游戏实例之《别救我》第七篇——物理世界的碰撞检测

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

 

 

其实我也很惊讶…竟然写到第七篇了,我预计也就是四篇的内容,感觉很神奇,我也不会很唠叨什么吖(小若:32个喷!),怎么都到第七篇了。

 

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

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

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

 

碰撞监听

首先,确保我们创建物理对象的时候,给对象设置了碰撞条件(如果你是一步步按着教程来写的代码,那就是设置好了):


1
2
3
4
5
    body->setCategoryBitmask(1);    // 0001

    body->setCollisionBitmask(1);   // 0001

    body->setContactTestBitmask(1); // 0001

这样我们才能监听到它们的碰撞事件,至于原理,就不说了,以我的唠叨程度,不是一两篇内容能说完的。

 

然后,我们给TollgateScene添加一个函数声明:


1
2
3
    /* 碰撞检测 */

    bool onContactBegin(PhysicsContact& contact);

这是碰撞事件开始时的回调函数,监听碰撞事件很简单,我们修改一下TollgateScene的init函数:


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
bool TollgateScene::init()

{

    if (!Layer::init())

    {

        return false;

    }

    /* 创建主角 */

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

 

    m_player = Player::create();

    m_player->setPosition(Point(visibleSize.width * 0.5f, visibleSize.height * 0.85f));

    this->addChild(m_player, 5);

 

    /* 创建操作UI */

    createOprUI();

 

    /* 碰撞监听 */

    auto contactListener = EventListenerPhysicsContact::create();

    contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBeginthis);

    _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);

 

 

    this->schedule(schedule_selector(TollgateScene::logic));

    return true;

}

 

流程就是这样:

1. 创建EventListenerPhysicsContact对象,可以看做是碰撞监听回调接口

2. 绑定onContactBegin事件的回调函数

3. 将监听接口添加到统一的事件派发器里(addEventListenerWithSceneGraphPriority)

 

Cocos2d-x3.0的监听事件改动很大(但很好用),这里就不多解释了,网上很多文章有介绍。

 

如果大家有去看看EventListenerPhysicsContact的源码的话,会发现,碰撞事件不仅仅只有onContactBegin一个,其他的事件我就不说了。这里只是要onContactBegin,作用是在两个物理对象开始发生碰撞的时候调用。

 

好,最后看看onContactBegin函数实现:


1
2
3
4
5
6
7
8
9
10
11
12
13
bool TollgateScene::onContactBegin(PhysicsContact& contact)

{

    auto nodeA = (Sprite*)contact.getShapeA()->getBody()->getNode();

    auto nodeB = (Sprite*)contact.getShapeB()->getBody()->getNode();

 

    return true;

}

其中获取到的nodeA和nodeB就是发生碰撞的两个节点对象。

现在,看看碰撞检测是否正常吧,用调试模式运行游戏,然后在onContactBegin函数里打个断点,看看主角碰到墙的时候有没有进入这个断点吧~

如果有,那就代表成功了。

 

然后,还有一点别忘了,在TollgateScene的onExit函数里,把监听事件给取消了:


1
2
3
4
5
6
7
8
9
10
11
void TollgateScene::onExit()

{

    Layer::onExit();

 

    _eventDispatcher->removeEventListenersForTarget(this);

}

 

怎么知道发生碰撞的两个节点分别是谁?

现在我们只知道有两个节点碰撞了,也能取到两个节点对象,但是,它们都是谁啊?根本不认识啊,那怎么进行下一步操作呢?我想给Player对象加血,那怎么办呢?

 

没关系,节点有一个很万能的函数:setTag。

给节点设置Tag可不是仅仅用来从Layer里获取子节点对象,我们还可以用来区分这些节点是谁,应该是,区分这些节点是属于哪一类东西。

 

创建一个生物分类表

好,是时候新建一个头文件了,为了迎合这份高大上的教程,我们就称之为生物分类表吧~

创建一个头文件,命名为ObjectTag.h,内容如下:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#ifndef ObjectTag_H

#define ObjectTag_H

 

#define ObjectTag_Player 1

#define ObjectTag_Border 2

#define ObjectTag_Monster 3

 

#endif

这大有用处,别着急~

 

给各种物体设置Tag吧

好了,现在我们要给主角和墙(或者称之为锯齿)设定生物类别了,在Player的init函数的最后加上一句代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
bool Player::init()

{

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

 

    this->setTag(ObjectTag_Player);

    return true;

}

当然,ObjectTag.h头文件也别忘了加上。

 

然后,给BackgroundLayer的createBorder函数最后也加上一句代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Sprite* BackgroundLayer::createBorder(Point pos)

{

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

 

    border->setTag(ObjectTag_Border);

    return border;

}

 

开始区分谁是谁

OK了,主角和墙都有了各自的生物类型了~现在我们可以区分碰撞的两个对象分别是谁了。

 

我们要在TollgateScene的onContactBegin函数里做处理:


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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
bool TollgateScene::onContactBegin(PhysicsContact& contact)

{

    auto nodeA = (Sprite*)contact.getShapeA()->getBody()->getNode();

    auto nodeB = (Sprite*)contact.getShapeB()->getBody()->getNode();

 

    if (nodeA == NULL || nodeB == NULL)

    {

        return true;

    }

 

    Node* playerNode = NULL;    /* 玩家对象 */

    Node* other = NULL;         /* 怪物或墙等其他对象 */

 

    if (nodeA->getTag() == ObjectTag_Player)

    {

        playerNode = nodeA;

        other = nodeB;

    }

    else if (nodeB->getTag() == ObjectTag_Player)

    {

        playerNode = nodeB;

        other = nodeA;

    }

    else

    {

        /* 如果两个碰撞的物体中,不存在玩家对象,就忽略,不做处理 */

        return true;

    }

 

    Player* player = (Player*)playerNode;

 

    /* 碰撞到边缘锯齿(墙),+1血 */

    if (other->getTag() == ObjectTag_Border)

    {

        /* 扣-1血,就相当于加1血 */

        player->beAtked(-1);

 

        log("player cur HP:%d", player->getiHP());

    }

    return true;

}

(小若:这么长的代码,打死我我也不看~!)

 

这段代码要做的事情其实很简单,萝莉一下,萝莉、罗莉,罗列,嗯(这输入法坏了,一定是):

1.判断nodeA的Tag是不是ObjectTag_Player,如果是,那么nodeA就是Player对象了,同时,nodeB只能是墙或者是怪物对象了(因为游戏里只有一个Player对象)

2.如果nodeA不是Player,那就继续判断nodeB

3.如果nodeA和nodeB都不是Player对象,那我们就不做处理,因为怪物和怪物之间的碰撞不需要做处理

4.如果找到Player对象,那就判断other对象是不是墙,是的话,那就让Player加1滴血

5.由于这个实例缺少很多功能,比如UI、数据绑定、碰撞时产生的动画效果之类的,所以没法直观地看到Player加血的动作,只好用打印日志的方式来查看了。

 

有朋友提醒我漏了讲解Player的beAtked函数,这里补充一下,如代码:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void Player::beAtked(int iValue)
{
    if (iValue < 0)
    {
        cure(-iValue);
    }
    else
    {
        hurt(std::abs(iValue));
    }
}

void Player::hurt(int iValue)
{
    setiHP(getiHP() - iValue);
}

void Player::cure(int iValue)
{
    setiHP(getiHP() + iValue);
}

 

好了,现在用调试模式运行游戏(键盘F5),使劲让主角撞墙吧,然后看看日志输出:

player cur HP:101

player cur HP:102

player cur HP:103

player cur HP:104

player cur HP:105

player cur HP:106

player cur HP:107

player cur HP:108

player cur HP:109

 

如果有类似以上的日志输出,那就证明我们成功了~

 

 

 

 

 

24 评论

  1. Pingback: skype free download
  2. Pingback: java version
  3. Pingback: download firefox
  4. Pingback: Vanessa Smith
  5. 能运行 但是就是不会打印出日志。 if (nodeA->getTag() == objectTag_Player) { playerNode = nodeA; other = nodeB; CCLOG(“OK”); }这个函数是会被调用的 但是下面的就不再调用了、求解答。

      1. 只能进入这个判断 下面的加血 不会运行, 还有个问题是 做完第十章以后。运行 : /* 创建怪物对象,并保存起来 */ Monster* monster = Monster::create(id); 会在这里 monster->setID(id); 运行到这里崩溃掉。0x0019A1A0 处有未经处理的异常(在 JY.exe 中): 0xC0000005: 读取位置 0x00000000 时发生访问冲突。

  6. 最后的加血判断,我断点调试发现,里面的代码没有执行,但是主角确实碰到墙壁了,难道是判断条件不成立?

  7. player->beAtked(-1); 看到这里突然发现没有一丝印象,然后往前番看6篇文章,确实是没有提这个函数,还好我有完整的,然后又打开之前学习COCOSTUDIO时为了拿素材,下的第五课源码,打开出现不能加载项目,这是什么情况?虽然我完整的没问题,但简化版的有问题还是想问问。⊙﹏⊙‖

  8. 恩我找到了,是通过注册的方式local onContactBegin = function(contact) local nodeA = contact:getShapeA():getBody():getNode() local nodeB = contact:getShapeB():getBody():getNode() ptint(“zhuang”) return true end local contactListener = cc.EventListenerPhysicsContact:create() contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN);谢谢

  9. auto contactListener = EventListenerPhysicsContact::create(); contactListener->onContactBegin = CC_CALLBACK_1(TollgateScene::onContactBegin, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(contactListener, this);这部分如果使用lua改写,应该怎么改呢?CC_CALLBACK_1 应该怎么使用,如何给contactListener的onContactBegin赋值?

    1. 你可以看看官方的实例工程lua-tests,里面都有示例的~在lua里不需要用到类似CC_CALLBACK_1这种形式,直接丢function(){} 到参数里就可以了类似这样(正确的语法还是看看demo吧~):contactListener.onContactBegin = function(param1, param2) { — do something};

发表评论

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