cocos2dx -lua 物理引擎碰撞检测

物理引擎

前言

  在游戏中模拟真实的物理世界是比较麻烦的,通常都会交给物理引擎来做,比较知名的物理引擎有Box2D和Chipmunk。
  在Cocos2d-x 2.x中,游戏直接使用物理引擎,引擎提供一个简单的CCPhysicsSprite,处理了物理引擎的body与CCSprite的关系,而物理引擎的其他元素并没有和引擎对应起来,游戏需要选择直接调用Box2D或Chipmunk的API来处理逻辑。然而直接使用物理引擎是比较复杂的,物理引擎的接口参数繁多、复杂、需要开发人员对物理引擎和Coco2d-x都很了解,才能把两者融合得很好。
  这种情况在Cocos2d-x 3.x中有了改变,全新的Physics Integration,把Chipmunk和Box2D封装到引擎内部,开发者不必关心底层具体用的是哪个物理引擎,也不用直接调用物理引擎的接口。
  Cocos2d-x 3.x默认使用Chipmunk作为内部物理引擎。
  Physics Integration 做了以下深度融合:
  (1)物理世界被融入到Scene中,即当创建一个场景时,可以指定这个场景是否使用物理引擎。
  (2)Node自带body属性,也就是Sprite自带body属性。
  (3)对物理引擎的Body(cc.PhysicsBody),Shape(cc.PhysicsShape),Contact(cc.PhysicsContact),Joint(cc.PhysicsJoint),World(cc.PhysicsWold)进行封装抽象,使用更加简单。
  (4)更简单的碰撞检测监听:EventListenerPhysicsContact.

创建带物理世界的scene

  使用下面方法创建带物理世界的scene:

local MainScene = class ("MainScene",function()
    return display.newScene("MainScene",{physics = 2})
end)

  创建后,self:getPhysicsWorld()用来获取场景绑定的物理世界对象。
  PhysicsWorld()默认是带重力的,大小为Vect(0.0f,-98.0f),也可以通过setGravity()方法来改变重力值,如下:

self:getPhysicsWorld():setGravity(cc.p(0,-100)) 

  在调试物理世界中物体运动模拟时,可以使用PyhsicsWorldsetDebugDrawMask()来开启调试模式。它能把物理世界中不可见的shape, joint和contact都可视化。当调试结束需要发布游戏的时候,需要把该debug开关关闭
  关闭DEBUG,传入参数cc.PhysicsWorld.DEBUGDRAW_NONE

创建物理边界

  在物理世界中,所以物体均受重力的影响。物理引擎提供StaticShape创建一个不受重力影响的形状,在Cocos2d-x 2.x中,我们需要了解物理引擎的StaticShape相关的各种参数来完成边界设置。而在Cocos2d-x 3.x中,由cc.PhysicsBody创建边界,然后由Node添加到场景,addChild内部自动碗好吃呢个边界添加到物理世界,Node在这里起中介作用。
代码如下:

    local size = display.size
    local body = cc.PhysicsBody:createEdgeBox(size,cc.PHYSICSBODY_MATERIAL_DEFAULT,2)
    local edgeNode = display.newNode()
    edgeNode:setPosition(size.width/2,size.height/2)
    edgeNode:setPhysicsBody(body)
    self:addChild(edgeNode)

  cc.PhysicsBody包含很多工程方法,createEdgeBox创建一个不受重力影响的矩形边界,参数含义依次是:
   (1)矩形区域大小,这里设置为屏幕大小。
   (2)设置材质,可选参数,默认为PHYSICSBODY_ MATERIAL DEFAULT
   (3)边线宽度,可选参数,默认为1
   然后我们创建一个Node,把刚刚创建的body附加到Node上,并设置好Node的position为屏幕中心点。最后把Node添加到scene。
  Node的addChild方法,在Cocos2d-x 3. x中有对物理body做处理,它会自动把Node的body设置到scene的PhysicsWorld上去。
   PhysicsBody中的工程方法,针对参数设置的body大小,会自动创建对应的PhysicsBody和一个PhysicsShape, 这也是通常情况下,直接使用物理引擎创建一个body需要做的事情。Cocos2d-x 3. x的Physics Integration 极大地简化了使用物理引擎的代码量。

创建受重力作用的sprite

   在Cocos2d-x 3.0中创建一个受重力作用的Sprite也很简单。首先来看代码:

    local oneSprite = display.newSprite("game/basketball/image/basketball.png")
    -- local oneBody = cc.PhysicsBody:createBox(oneSprite:getContentSize(),cc.PHYSICSBODY_MATERIAL_DEFAULT,cc.p(0,0))  --矩形刚体
    local oneBody = cc.PhysicsBody:createCircle(25,cc.PHYSICSBODY_MATERIAL_DEFAULT,cc.p(0,0))      --圆形刚体   
    oneBody:setContactTestBitmask(0xFFFFFFFF)
    oneBody:applyImpulse(cc.p(0,10000))
    oneSprite:setPhysicsBody(oneBody)
    oneSprite:setPosition(x,y)
    self:addChild(oneSprite)

   首先创建-一个sprite,然后用cc.PhysicsBody:createBox()创建一个矩形的body附加在sprite上。createBox 接受三个参数如下:
   (1)参数1,cc. size类型,表示矩形的长宽。
   (2)参数2,cc.PhysicsMaterial类型,表示物理材质属性,可选参数,默认为cc. PHYSICSBODY MATERIAL DEFAULT。手动创建材质方法如下:

      cc. PhysicsMaterial(density, restitution, friction)

   其中第一个参数表示密度,第二个参数表示反弹力,第三个参数表示摩擦力。
  (3)参数3,cc.p类型,表示body与中心点的偏移量,可选参数,默认为cc. p(0,0)。类似地,可以用下面的方法创建圆形body:

      cc. PhysicsBody:createCircle(radius,material,offset)

  不同于矩形的创建,第一个参数是园的半径,第二、三个参数的作用同createBox一样。

碰撞检测

  在Cocos2d-x 中,事件派发机制做了重构,所有事件均由事件派发器统一管理。物理引擎的碰撞事件也不例外,下面的代码注册碰撞begin回调函数。

--监听碰撞
    local function onContactBegin(contact)
        local tag = contact:getShapeA():getBody():getNode():getTag()
        -- print(tag)     --碰撞后的回调事件
        return true
    end

    local contactListener = cc.EventListenerPhysicsContact:create()
    contactListener:registerScriptHandler(onContactBegin,cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN)
    local eventDispatcher = cc.Director:getInstance():getEventDispatcher()
    eventDispatcher:addEventListenerWithFixedPriority(contactListener,1)

  碰撞检测的所有事件由cc.EventListenerPhysicsContact的实例来监听,这些事件有如下几类。
  (1) cc. Handler. EVENT_ PHYSICS_ _CONTACT_ BEGIN,在碰撞刚发生时,触发这个事件,并且在此次碰撞中只会被调用一次。可以通过返回true 或者false 来决定物体是否发生碰撞。需要注意的是,当这个事件的回调函数返回flase 时, EVENT _ PHYSICS_CONTACT_ PRESOLVEEVENT_ PHYSICS_ CONTACT_ POSTSOLVE 将不会被触发,但EVENT_ PHYSICS_ CONTACT_ SEPERATE必定会触发。
  (2) cc. Handler. EVENT_ PHYSICS_ CONTACT_ PRESOLVE, 发生在碰撞的每个step,可以通过调用cc.PhysicsContactPreSolve的成员函数来改变碰撞处理的一些参数设定,比如弹力和阻力等。同样可以通过返回true或者false来决定物体是否发生碰撞。
  (3)cc. Handler. EVENT_ PHYSICS_ CONTACT_ POSTSOLVE,发生在碰撞计算完毕的每个step,可以在此做一些碰撞的后续处理,比如安全的移除某个物体等。
   (4) cc. Handler. EVENT_ PHYSICS_ CONTACT_ SEPERATE,发生在碰撞结束两物体分离时,同样只会被调用一次。它与onContactBegin必定是成对出现的。
   监听器设置完毕,需要加入到引擎导演的事件分发器中。
   默认情况下,物理引擎中的物体都不发出碰撞事件,也就是上面的代码中的onContactBegin永远不会调用到。为了解决这个问题,首先需要了解cc. PhysicsBody的三个mask。
   (1) CategoryBitmask body,类别掩码,32位整型,也就是可以有32 个不同的类别。默认值为0xFFFFFFFF
   (2) ContactTestBitmask,当两个物体接触时,用一个物体的CategoryBitmask与另一.个物体的ContactTestBitmask做逻辑运算,不为0时引擎才会新建PhysicsContact对象,发送碰撞事件。ContactTestBitmask的设计是为了优化性能,并不是所有物体之间的碰撞我们都关心,所以这个ContactTestBitmask的默认值为0x00000000
   (3)CollisionBitmask刚体碰撞掩码,当两个物体接触后,用–个物体的CollisionBitmask与另一个物体的CategoryBitmask做逻辑运算,不为0时才能发生刚体碰撞,默认值为0xFFFFFFFF
   上面的解释说明了每个掩码的作用,而掩码之间的相互作用可归纳如下:
   (1)CategoryBitmask,是其他两个掩码比较的基础。
   (2) CategoryBitmask & ContactTestBitmask,决定是否发送事件消息。
   (3) CategoryBitmask & CollisionBitmask,决定是否产生刚体反弹效果。
   (4) ContactTestBitmaskCollisionBitmask,互相之间没有联系。

完整代码

下面是main.lua文件的完整代码,单击屏幕任意一点会创建一个精灵,精灵之间相互碰撞后产生回调事件。

local MainScene = class ("MainScene",function()
    return display.newScene("MainScene",{physics = 2})
end)

function MainScene:ctor()
    self:getPhysicsWorld():setGravity(cc.p(0,-100))    --重力
    self:getPhysicsWorld():setDebugDrawMask(cc.PhysicsWorld.DEBUGDRAW_ALL)
    local size = display.size
    local body = cc.PhysicsBody:createEdgeBox(size,cc.PHYSICSBODY_MATERIAL_DEFAULT,2)
    local edgeNode = display.newNode()
    edgeNode:setPosition(size.width/2,size.height/2)
    edgeNode:setPhysicsBody(body)
    self:addChild(edgeNode)
    --监听碰撞
    local function onContactBegin(contact)
        local tag = contact:getShapeA():getBody():getNode():getTag()
        -- print(tag)     --碰撞后的回调事件
        return true
    end

    local contactListener = cc.EventListenerPhysicsContact:create()
    contactListener:registerScriptHandler(onContactBegin,cc.Handler.EVENT_PHYSICS_CONTACT_BEGIN)
    local eventDispatcher = cc.Director:getInstance():getEventDispatcher()
    eventDispatcher:addEventListenerWithFixedPriority(contactListener,1)

self:getPhysicsWorld():setDebugDrawMask(cc.PhysicsWorld.DEBUGDRAW_ALL)
    local function onTouchBegan( touch, event )
        return true  
    end
    local function onTouchEnded( touch, event )
        local location = touch:getLocation()   --获取鼠标的位置
        local event_x = location["x"] or 0      
        local event_y = location["y"] or 0
        self:addSprite(event_x,event_y)
    end
    local function onTouchMoved(touch, event)
    end
    local listener1 = cc.EventListenerTouchOneByOne:create()  --创建一个单点事件监听
    listener1:setSwallowTouches(false)  --是否向下传递
    listener1:registerScriptHandler(onTouchBegan,cc.Handler.EVENT_TOUCH_BEGAN )
    listener1:registerScriptHandler(onTouchMoved,cc.Handler.EVENT_TOUCH_MOVED )
    listener1:registerScriptHandler(onTouchEnded,cc.Handler.EVENT_TOUCH_ENDED )
    local eventDispatcher = self:getEventDispatcher() 
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener1, self) --分发监听事件
end
function MainScene:addSprite(x,y)
    local oneSprite = display.newSprite("game/basketball/image/basketball.png")
    -- local oneBody = cc.PhysicsBody:createBox(oneSprite:getContentSize(),cc.PHYSICSBODY_MATERIAL_DEFAULT,cc.p(0,0))  --矩形刚体
    local oneBody = cc.PhysicsBody:createCircle(25,cc.PHYSICSBODY_MATERIAL_DEFAULT,cc.p(0,0))      --圆形刚体   
    oneBody:setContactTestBitmask(0xFFFFFFFF)
    oneBody:applyImpulse(cc.p(0,10000))
    oneSprite:setPhysicsBody(oneBody)
    oneSprite:setPosition(x,y)
    oneSprite:setTag(101)
    self:addChild(oneSprite)
end


function MainScene:showWithScene(transition, time, more)
    self:setVisible(true)
    local scene = self
    display.runScene(scene, transition, time, more)
    return self
end

return MainScene

  首先,使用 display.newPhysicsScene作为MainScene的父类,创建一个带物理世界的MainScene。
  在MainScene:ctor中依次做了下面的初始化工作:
  (1)修改物理世界的重力,重力是从cc. p(0,0)到setGravity()参数点之间的向量。
  (2)用cc. PhysicsBody: createEdgeBox在屏幕四周创建物理边界,然后通过节点添加到场景中,它不受重力的影响。
  (3)注册EVENT_ PHYSICS_ CONTACT_ BEGIN事件的回调函数。

注: onContactBegin需要return true,否则物体碰撞后不发生刚体反弹。

  (4)打开物理世界的调试模式,可以在屏幕上看到物理边界以及刚体的框架。
  (5)注册触摸事件,每次触摸事件到来都会在触摸点创建一个刚体精灵。
  MainScene: addSprite完成精灵的创建以及初始化:
  (1)xxxx.png是个圆形的图片,通过这种图片创建精灵。
  (2) cc. PhysicsBody: createCircle创建一个圆形的刚体,注意到它采用了radius作为第一个参数,这样刚体就能完全吻合图片的形状。
  (3)setContactTestBitmask修改精灵的接触检测掩码,这样精灵之间碰撞就能发出事件。
  (4)我们还用applyImpulse为刚体施加了一个向,上的瞬时冲力,这样精灵创建后会先向上飞,再掉落下来。applyImpulse是个很有用的接口,在物理世界中,用这个接口来改变物体的运动轨迹,而不是用传统的setPos,否则物理世界的运动将不可预期。
  (5)setPhysicsBody把物理刚体和精灵都绑定在一起。
  (6)设置精灵的初始坐标并添加到场景上。
  快速单机屏幕创建多个精灵,发现它们互相弹开了,这是由于刚体的弹力作用,然后由于受到重力的影响,最终他们都凋落到屏幕下方。
  设置刚体属性,参数分别是密度,碰撞系数,摩擦力

cc.PhysicsMaterial(1,1,0)

效果图

在这里插入图片描述

发布了65 篇原创文章 · 获赞 141 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/Silent_F/article/details/86657693