Box2d

一、概述

1、关于

Box2D是个二维刚体仿真库, 用于编写游戏。程序员可以使用它, 让游戏中的物体运动起来更真实,  游戏世界更具交互性。以游戏的角度来看,物理引擎只是个程序性动画系统。(procedural animation)

做动画常有两种方法, 一种是预先准备好动画所需的数据,比如图片,再一帧一帧地播放。另 一种是以一定方法,动态计算出动画所需的数据,根据数据再进行绘图。 
从这种角度看,预先准备的,可称为数据性动画,动态计算的可称为程序性动画。

这个区别,就类似以前我们做历史题和数学题,做历史题,记忆很重要,也就是答案需要预先准备
好的。做数学题,方法就很重要,答案是需要用方法推导出来的。 

Box2D就是用物理学的方法,推导出那游戏世界物体的位置,角度等数据。而Box2D也仅仅推导出数 ,至于得到数据之后怎么处理就是程序员自己的事情了。)
Box2D用可移植的C++来写成,它定义的大部分类型都有b2前缀, 希望这能有效消除Box2D和你自己 的游戏引擎之间的名字冲突。

2、概念

形状(shape):
    2D几何对象, 比如圆形(circle)或多边(polygon)。

刚体(rigid body):
    十分坚硬的物质, 坚硬得像钻石,它上面任意两点之间的距离都保持不变。在后面的讨论中,我们用 物体(body)来代替刚体。

夹具(fixture):
    fixture将形状绑定到物体之上, 并有一定的材质属性, 比如密度(density), 摩擦(friction)和恢复 (restitution)。

约束(constraint):
    约束是个物理连接, 用于消除物体的自由度。在2D中, 物体有3个自由度(水平,垂直,旋转)。如果我 们把一个物体钉在墙上(像钟摆那样), 那就把它约束到了墙上。这个时候,此物体就只能绕着钉子旋转 , 所以这个约束消除了它2个自由度。
(注:简单的说, 需要用几个参数来确定物体的空间状态, 这个物体就有几个自由度。在二维中,完全 没有约束的条件下, 我们要确定物体的状态, 要有x坐标, y坐标, 旋转角这三个参数, 所以自由度为3。 如果物体被钉在墙上, 只要有旋转角,就可以完全确定物体的状态,有了钉子这个约束,物体自由度 就变成了1。)

接触约束(contact constraint):
    一种特殊的约束, 设计的目的是为了防止刚体被穿透, 也用于模拟摩擦和恢复。接触约束不用你来创 建, 它们会自动被Box2D生成。

关节(joint):
    关节就是种约束, 用于将两个或多个body固定到一起。Box2D支持不同的关节类型:转动(revolute),棱 柱(prismatic),距离(distance)等。一些关节可以有限制(limits)和马达(motors)。

关节限制(joint limit) :
    关节限制限定了一个关节的运动范围。例如人类的胳膊肘只能在某一角度范围内运动。

关节马达(joint motor):
    根据关节的自由度, 关节马达可以驱动关节所连接的物体。例如, 你可以使用一个马达来驱动一个 肘的旋转。

世界(world):
    一个物理世界就是各种, 刚体(bodies), 夹具(fixtures), 约束(constraints)相互作用的集合。 Box2D支持创 建多个世界, 但这通常没有必要。

3、单位

   Box2D使用浮点数, 所以必须使用一些公差来保证它正常工作。这些公差已经被调谐得适合米-千克-秒(MKS)单位。
   尤其是, Box2D被调谐得能良好地处理0.1到10米之间的移动物体。这意味着从罐头盒 到公共汽车大小的对象都能良好地工作。静态的物体就算到50米都没有大问题。

二、应用

1、创建物理世界

1、创建世界:
    每个Box2D程序开始时都会创建一个b2World对象。b2World是物理枢纽(physics hub), 用于管理内存 、对象和模拟。根据自己的实际情况, 你可以在栈, 堆或数据区中创建出world。
    创建Box2D的world很简单。首先, 我们要定义重力矢量,另外还要告诉world是否允许body在静止时 休眠。休眠中的body不需要任何模拟。

    b2Vec2 gravity;  //重力
    gravity.Set(0.f, -20.f);//竖直向下

    现在可以创建world对象了。注意,在这里我们是在栈中创建world, 所以world不能离开它的作用域。

    world = new b2World(gravity);//创建一个物理世界
    world->SetAllowSleeping(true);//允许睡眠
    world->SetContinuousPhysics(true);//允许碰撞

2、创建地面盒

   2.1: 用位置(position), 阻尼(damping)等来定义body
   2.2:通过world对象来创建body
   2.3:用形状(shape), 摩擦(friction), 密度(density)等来定义 fixture
   2.4:在body上来创建fixture

    //创建刚体定义
    b2BodyDef groundBodyDef;//创建地面
    groundBodyDef.position.Set(0.0f, 0.0f);//原点  刚体定义中位置设置

    //创建刚体    world创建刚体
    b2Body* groundBody = world->CreateBody(&groundBodyDef);
    b2EdgeShape groundBox;
    //依次定义盒子的边界
    //下
    groundBox.Set(b2Vec2(-visabliSize.width / PTM_RATIO, 0.7), b2Vec2(2 * visabliSize.width / PTM_RATIO, 0.7));
    groundBody->CreateFixture(&groundBox, 0);

    //上
    groundBox.Set(b2Vec2(-visabliSize.width / PTM_RATIO, visabliSize.height / PTM_RATIO), b2Vec2(2 * visabliSize.width / PTM_RATIO, visabliSize.height / PTM_RATIO));
    groundBody->CreateFixture(&groundBox, 0);

    //左
    groundBox.Set(b2Vec2(0 / PTM_RATIO, 0), b2Vec2(0 / PTM_RATIO, visabliSize.height / PTM_RATIO));
    groundBody->CreateFixture(&groundBox, 0);

    //右
    groundBox.Set(b2Vec2(visabliSize.width / PTM_RATIO, 0), b2Vec2(visabliSize.width / PTM_RATIO, visabliSize.height / PTM_RATIO));
    groundBody->CreateFixture(&groundBox, 0);

2、创建动态刚体,并绑定精灵

   1、创建精灵

     auto sp = Sprite::create("1.png");
     sp->setPosition(240,160);
     this->addChild(sp);

   2、创建刚体,绑定精灵

     b2BodyDef ballBodydef;
     ballBodydef.type = b2_dynamicBody; //动态刚体
     ballBodydef.position.Set(sp->getPosition().x / PTM_RATIO, sp->getPosition().y / PTM_RATIO);   //设置刚体的位置

    ballBodydef.userData = sp;  //绑定精灵
    b2Body * ballBody = world-  >CreateBody(&ballBodydef);    //创建刚体

    //我们创建一个多边形shapde, 并将它附加到fixture定义上。我们先创建一个box shape:

    b2PolygonShape blockShape;
    blockShape.SetAsBox(0.3f, 0.3f);

    //接下来我们使用box创建一个fixture定义

    b2FixtureDef ballShapeDef;
    ballShapeDef.shape = &blockShape;

    ballShapeDef.density = 50.0f;  //设置密度
    ballShapeDef.friction = 0.5f;  //设置摩擦
    ballShapeDef.restitution = 0.2f; //设置恢复

    //刚体添加夹具
    ballBody->CreateFixture(&ballShapeDef);

3、模拟(Box2d的)世界

   Box2D使用了一个叫积分器(integrator)的数值算法。 积分器在离散的时间点上模拟连续的物理方程。 它与传统的游戏动画循环一同运行。我们需要为Box2D选取一个时间步。通常来说用于游戏的物理 引擎需要至少 60Hz 的速度,也就是 1/60 的时间步。你可以使用更大的时间步,但是你必须更加小心地 为你的世界调整定义。我们也不喜欢时间步变化得太大,所以不要把时间步关联到帧频(除非你真的 必须这样做)。直截了当地,这个就是时间步:
   float32 timeStep = 1.0f / 60.0f;

   除积分器外,Box2D代码还使用了约束求解器(constraint solver)。约束求解器用于解决模拟中的所有约束,一次一个。单个的约束会被完美的求解,然而当我们求解一个约束的时候,我们就会稍微耽误另一 个。要得到良好的解,我们需要多次迭代所有约束。
   约束求解有两个阶段:速度、位置。在速度阶段,求解器会计算必要的冲量,使得物体正确运动。而在位置阶段,求解器会调整物体的位置,减少物体之间的重叠。每个阶段都有自己的迭代计数。此外,如果误差已足够小的话,位置阶段的迭代可能会提前退出。
   对于速度和位置,建议的Box2D迭代次数都是10次。你可以按自己的喜好去调整这个数字,但要记 得它是性能与精度之间的折中。更少的迭代会增加性能但降低精度,同样地,更多的迭代会降低性 能但能提高模拟质量。对于这个简单示例,我们不需要多次迭代。这是我们选择的迭代次数。
   int32 velocityIterations = 10;
   int32 positionIterations = 10;

   完整代码如下:

   void Box2DTest::update(float dt)
{
    float timeStep = 1.0f / 60.0f;//更新时间(物理世界刷新次数)
    int32 velocityIterations = 10;//速度迭代次数
    int32 positionIterations = 10;//位置迭代次数
    //刷新
    world->Step(timeStep, velocityIterations, positionIterations);
    //遍历物理世界中的刚体
    for (b2Body *b = world->GetBodyList(); b; b = b->GetNext())
    {
        if (b->GetUserData()!=nullptr)
        {
            //获取刚体绑定的精灵
            auto sprite = (Sprite*)b->GetUserData();

            //更新刚体绑定的精灵的位置
            sprite->setPosition( Vec2( b->GetPosition().x *PTM_RATIO, b->GetPosition().y * PTM_RATIO) );
            sprite->setRotation( -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()) );
        }
    }
}

4、刚体碰撞的监听

    代码如下:
    .h文件:
#include <iostream>
#include "Box2D/Box2D.h"
#include "cocos-ext.h"
#include "cocos2d.h"
using namespace std;
USING_NS_CC_EXT;
USING_NS_CC;

class MyContactListener:public b2ContactListener
{
public:
    b2World* _world;
    Layer* _layer;
    MyContactListener();
    MyContactListener(b2World* w,Layer* c);
    ~MyContactListener();

    virtual void BeginContact(b2Contact* contact);//碰撞开始
    virtual void EndContact(b2Contact* contact);//碰撞结束
    virtual void PreSolve(b2Contact* contact, const b2Manifold* oldManifold);//持续接触时响应
    //b2Manifold结构含有一个法向量和最多两个的接触点。向量和接触点都是相对于局部坐标系。为方便接触求解器处理,每个接触点都存储了法向冲量和切向(摩擦)冲量。
    virtual void PostSolve(b2Contact* contact, const b2ContactImpulse* impulse);//持续接触时响应,调用完preSolve后调用
};

    .cpp文件:

#include "MyContactListener.hpp"

MyContactListener::MyContactListener()
{

}

MyContactListener::MyContactListener(b2World* w,Layer* c)
{
    _world = w;
    _layer = c;
}

MyContactListener::~MyContactListener()
{

}

void MyContactListener::BeginContact(b2Contact *contact)
{
    log("BegainContact");
}

void MyContactListener::EndContact(b2Contact* contact)
{
    log("EndContact");
}
void MyContactListener::PreSolve(b2Contact* contact, const b2Manifold* oldManifold)
{
    log("PreSolve");
}
void MyContactListener::PostSolve(b2Contact* contact, const b2ContactImpulse* impulse)
{
    log("PostSolve");
    //Solve计算完成后调用的函数
    float force = impulse->normalImpulses[0];//受到的力

    if (force>2) {

        b2Body* bodyA = contact->GetFixtureA()->GetBody();
        b2Body* bodyB = contact->GetFixtureB()->GetBody();
        auto spriteA = (Sprite*)bodyA->GetUserData();
        auto spriteB = (Sprite*)bodyB->GetUserData();

        if (spriteA != nullptr && spriteB != nullptr)
        {
            spriteA->setTag(4);
            spriteB->setTag(4);
        }
    }
}

最后给物理世界设置碰撞监听:
listener=new MyContactListener(world,this);
world->SetContactListener(listener);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值