一、概述
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)等来定义body2.2:通过world对象来创建body2.3:用形状(shape), 摩擦(friction), 密度(density)等来定义 fixture2.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);