【quick-cocos2d-lua】 构建物理世界

本文介绍了如何在Cocos2d-x 3.x中使用Physics接口构建物理世界,包括创建带有物理特性的游戏角色,设置重力、调试模式,以及详细解释了掩码属性在碰撞检测中的作用,如CategoryBitmask、ContactTestBitmask和CollisionBitmask,最后讲解了物理引擎的碰撞事件监听与处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Cocos2d-x 3.x(Quick 3.x同样)版本中封装了一个全新的 Physics 接口,物理世界被融入到 Scene 中,即当创建一个场景时,可以指定这个场景是否使用物理引擎。

如:创建一个带物理引擎的游戏场景

display.newPhysicsScene("GameScene")
self.world = self:getPhysicsWorld()

    self.world:setGravity(cc.p(0, -98.0))

    self.world:setDebugDrawMask(cc.PhysicsWorld.DEBUGDRAW_ALL)

self:getPhysicsWorld()用来获取场景绑定的物理世界对象。
获取的 self.world 默认是有带重力的,大小为(0.0f, -98.0f), 但我们也可以通过的setGravity()方法来改变重力大小。
setDebugDrawMask 方法是在调试物理世界时使用的,该方法可以开启调试模,能把物理世界中不可见的body,shape,joint等元素可视化。当调试结束时,需要把该功能关闭。

 

然后创建一个带物理特性的游戏角色。

引擎封装了 Physics 接口后,Node 就自带了 body 属性,也就是每个 Sprite 都自带了 body 属性。所以我们要创建一个可受重力作用的 Sprite 是非常容易的,下面我们在游戏角色中加入如下的一段代码就可以为它绑定一个 body:

local body = cc.PhysicsBody:createBox(self:getContentSize(), cc.PHYSICSBODY_MATERIAL_DEFAULT, cc.p(0,0))

    self:setPhysicsBody(body)

这里调用cc.PhysicsBody:createBox()方法创建了一个矩形的 body,createBox 方法有三个参数,分别是:

  • 参数1为 cc.size 类型,它表示矩形 body 的尺寸大小。
  • 参数2为 cc.PhysicsMaterial 类型,表示物理材质的属性,默认情况下为 cc.PHYSICSBODY_MATERIAL_DEFAULT。 该参数也可自定义,方法如下:
    cc.PhysicsMaterial(density, restitution, friction)
  • density:表示密度
  • restitution:表示反弹力
  • friction:表示摩擦力
  • 例:(密度,反弹力、摩擦力都设为0是为了在碰撞的时候不发生任何物理形变)
  • local MATERIAL_DEFAULT = cc.PhysicsMaterial(0.0, 0.0, 0.0)
  •     local airshipBody = cc.PhysicsBody:createCircle(airshipSize.width / 2,
    
            MATERIAL_DEFAULT)
  • 参数3为 cc.p 类型,它也是一个可选参数,表示 body 与中心点的偏移量,默认下为cc.p(0,0)
  • 与 createBox 方法类似的还有 createCircle(radius, material, offset),该方法可以创建一个圆形的 body,除第一个参数为半径外,其余两参数与 createBox 方法一样。

掩码属性:

每个 cc.PhysicsBody 都具有三个掩码属性,两个刚体能不能碰撞,能不能发送接触事件信息,都依靠于这三个参数mask 的值。

  • CategoryBitmask:32位整型,刚体的类别掩码。它定义了一个物体所属类别,每一个物体在场景中都能被分配到多达32位不同的类别。默认值为0xFFFFFFFF。

  • ContactTestBitmask:32位整型,刚体的接触测试掩码。当两个物体接触时,用一个物体的类别掩码与另一个物体的接触测试掩码做“逻辑与”运行,如果结果为非零值,引擎才会新建 PhysicsContact 对象,发送碰撞事件。那么才发送碰撞事件消息。
    ContactTestBitmask 的设计是为了优化性能,并不是所有物体之间的碰撞我们都关心,所有这个 ContactTestBitmask 的默认值为0x00000000。

  • CollisionBitmask:32位整型,刚体的碰撞掩码。当两个物体接触后,用一个物体的碰撞掩码与另一个物体的类别掩码执行“逻辑与”运算,如果结果为非零值,那么该物体能够对另一个物体的碰撞发生反应。这里的“碰撞反应”会表现为一个物体受到另外物体的碰撞,而改变运动方向。默认值为0xFFFFFFFF。

总结:

  1. CategoryBitmask 是其它两个掩码比较的基础。
  2. CategoryBitmask & ContactTestBitmask 决定是否发送事件消息。
  3. CategoryBitmask & CollisionBitmask 决定是否产生刚体反弹效果。
  4. ContactTestBitmask 和 CollisionBitmask 互相之间没有联系。

注:每个 mask 都有对应的 get 和 set 接口来获取或者修改mask。
另外,发生碰撞和发送事件消息是不同的概念,前者是直观地一种表现-碰撞反弹,后者是一种消息机制,就是说是否调用碰撞事件的回调函数。

例:

节点类别掩码接触测试掩码碰撞掩码
玩家:Player011111111001
心心:Heart000101000001
鸟:Bird001000101000
飞艇:Airship010001001000
地面100000010011
天界100000000001

首先,以玩家和心心为例,我们希望它们发生碰撞,并希望发送事件消息。所以,玩家的类别掩码0111 逻辑&与 心心的碰撞掩码0001结果为0001(不为0),发生碰撞;且玩家的类别掩码0111 逻辑&与 心心的接触测试掩码0100结果为0100(不为0),发送事件消息。反之,心心的类别掩码0001 & 玩家的碰撞掩码1001结果为0001(不为0),发生碰撞;且心心的类别掩码0001 逻辑&与 玩家的接触测试掩码1111结果为0001(不为0),发送事件消息。所以,玩家和心心两个物体相互接触时,它们会发生碰撞反弹,同时会发出事件消息。

我们再举一例,这次以玩家和鸟为例,我们希望它们不发生碰撞,但希望发送事件消息。所以,玩家的类别掩码0111 逻辑&与 鸟的碰撞掩码1000结果为0000(为0),不发生碰撞;且玩家的类别掩码0111 逻辑&与 鸟的接触测试掩码0010结果为0010(不为0),发送事件消息。反之,鸟的类别掩码0010 & 玩家的碰撞掩码1001结果为0000(为0),不发生碰撞;且鸟的类别掩码0010 逻辑&与 玩家的接触测试掩码1111结果为0010(不为0),发送事件消息。所以,玩家和鸟两个物体相互接触时,它们不发生碰撞反弹,但会发出事件消息。

为了更方便碰撞检测,我们给各个节点设置一个标签。标签的定义我们可以放在 config.lua 文件中。

例:

GROUND_TAG   = 1

HEART_TAG    = 2

BIRD_TAG     = 3

AIRSHIP_TAG  = 4

PLAYER_TAG   = 5

根据以上的掩码属性和标签属性,我们在游戏项目中要设置好这些属性,如对于 Player,我们要在绑定刚体的地方加上如下的一段代码,即 Player:ctor()方法中:

body:setCategoryBitmask(0x0111)

body:setContactTestBitmask(0x1111)

body:setCollisionBitmask(0x1001)



self:setTag(PLAYER_TAG)

 

在物理场景中添加一些能阻止刚体下落或飞出屏幕的边界。

local width = self.map:getContentSize().width

local height1 = self.map:getContentSize().height * 9 / 10

local height2 = self.map:getContentSize().height * 3 / 16



local sky = display.newNode()

local bodyTop = cc.PhysicsBody:createEdgeSegment(cc.p(0, height1), cc.p(width, height1))

sky:setPhysicsBody(bodyTop)

self:addChild(sky)



local ground = display.newNode()

local bodyBottom = cc.PhysicsBody:createEdgeSegment(cc.p(0, height2), cc.p(width, height2))

ground:setPhysicsBody(bodyBottom)

self:addChild(ground)

这里我们创建了两根和 TiledMap 背景一样长的边界线。
createEdgeSegment 方法能创建一个不受重力约束的自由线条,它有四个参数,分别表示:

  • 参数1为 cc.p 类型,表示线条的起点;
  • 参数2也为 cc.p 类型,表示线条的终点;
  • 参数3为 cc.PhysicsMaterial 类型,表示物理材质的属性,默认情况下为 cc.PHYSICSBODY_MATERIAL_DEFAULT;
  • 参数4为 number 类型,表示线条粗细。

与之类似的函数还有:createEdgeBox(矩形边界),createEdgePolygon,createEdgeChain。它们都能创建不受重力约束的边界。

 

碰撞检测:

物理引擎的碰撞事件由 cc.EventListenerPhysicsContact 的实例来监听。

碰撞监听事件有以下几种:

  • cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN
    它是碰撞刚发生时触发的事件,并且在此次碰撞中只会被调用一次。我们可以通过返回 true 或者 false 来决定物体是否发生碰撞。
    需要注意的是,当这个事件的回调函数返回 flase 时,EVENT_PHYSICS_CONTACT_PRESOLVEEVENT_PHYSICS_CONTACT_POSTSOLVE将不会被触发,但EVENT_PHYSICS_CONTACT_SEPERATE必定会触发。

  • cc.Handler.EVENT_PHYSICS_CONTACT_PRESOLVE
    它在碰撞接触后的每帧都会调用。在该事件的回调函数中我们可以计算碰撞处理的一些属性,比如弹力,速度等。同样它可以通过返回 true 或者 false 来决定物体是否发生碰撞。

  • cc.Handler.EVENT_PHYSICS_CONTACT_POSTSOLVE
    发生在碰撞计算完毕的每个步骤(帧),你可以在此做一些碰撞的后续处理,比如安全的移除某个物体等。

  • cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE
    发生在碰撞结束两物体分离时,同样只会被调用一次。

例:

local contactListener = cc.EventListenerPhysicsContact:create()   -- 1

contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN)     -- 2

contactListener:registerScriptHandler(onContactSeperate, cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE)   -- 3

local eventDispatcher = cc.Director:getInstance():getEventDispatcher()   -- 4

eventDispatcher:addEventListenerWithFixedPriority(contactListener, 1)    -- 5
  1. 创建物体碰撞检测事件监听器对象(contactListener);
  2. 设置监听器的碰撞开始函数 onContactBegin;
  3. 设置监听器的碰撞分离函数 onContactSeperate;
  4. 监听器设置完毕,需要把它加入到引擎导演的事件分发器中。所以这里获取游戏的事件分发器(eventDispatcher);
  5. 将检测事件监听器(contactListener)添加到事件分发器(eventDispatcher)中,这样就可以触发碰撞检测事件。addEventListenerWithFixedPriority 方法是指定固定的事件优先级注册监听器,事件优先级决定事件响应的优先级别,值越小优先级越高。

例:碰撞检测的整段函数

function GameScene:addCollision()



    local function contactLogic(node)  

        -- 4              

        if node:getTag() == HEART_TAG then

            -- 这里给玩家增加血量,并添加心心消除特效

            node:removeFromParent()

        -- 5

        elseif node:getTag() == GROUND_TAG then

            -- 这里减少玩家20点血量,并添加玩家受伤动画



        elseif node:getTag() == AIRSHIP_TAG then

            -- 这里减少玩家10点血量,并添加玩家受伤动画



        elseif node:getTag() == BIRD_TAG then

            -- 这里减少玩家5点血量,并添加玩家受伤动画

        end

    end



    local function onContactBegin(contact)   -- 1

        -- 2

        local a = contact:getShapeA():getBody():getNode()  

        local b = contact:getShapeB():getBody():getNode()

        -- 3

        contactLogic(a)

        contactLogic(b)



        return true

    end



    local function onContactSeperate(contact)   -- 6

        -- 在这里检测当玩家的血量减少是否为0,游戏是否结束。

    end



    local contactListener = cc.EventListenerPhysicsContact:create()

    contactListener:registerScriptHandler(onContactBegin, cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN)

    contactListener:registerScriptHandler(onContactSeperate, cc.Handler.EVENT_PHYSICS_CONTACT_SEPERATE)

    local eventDispatcher = cc.Director:getInstance():getEventDispatcher()

    eventDispatcher:addEventListenerWithFixedPriority(contactListener, 1)

end

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值