Box2D基本概念的学习记录
关于Box2D
Box2D 是一个用于游戏的 2D 刚体仿真库。程序员可以在游戏中使用它,让物体的运动更加可信,让世界看起来更具交互性。从游戏引擎的视角来看,物理引擎就是一个为程序化动画 (procedural animation)的系统。Box2D是用C++开发的一款物理引擎, 而本文用的是Box2dWeb,即Box2D的JS版本。
一些基本概念
Box2D使用了一些基本的对象来定义和模拟游戏世界。最重要的几个对象如下:* World(世界):最主要的Box2D对象, 包括了所有的世界对象并模拟着游戏的物理行为。* Body(刚体): 刚体可以由一个或多个通过设备附属在刚体上的形状组成。* Shapes(形状): 一个二维形状,如圆形或多边形, 这些是用于Box2D内的基础形状。* Fixture(设备): 将一个形状附属在一个刚体上,用于碰撞检测。设备持有额外的,非几何的数据,如摩擦,碰撞和滤器。* Joint(关节):用来将两个刚体以不同的方式限制在一起。比如,一个转动关节限制两个刚体共享一个共同的节点,同时它们可以通过这个节点自由的旋转。
计算单位系统
Box2D的所有计算使用米制系统, 而我们在绘图时使用的是像素, 因此需要建立一个米到像素的映射, 通常使用30像素代表1米。
创建世界的步骤
- 创建重力系数,重力系数通过b2Vec来创建, 接收两个参数,代表x和y的值。
- 设置是否允许睡眠,即当刚体处于空闲时,是否可以被排除计算。处于空闲的刚体,可以被碰撞唤醒。###一个简单的定义如下
var gravity = new Box2D.Common.Math.b2Vec2(0, 9.8),
allowSleep = true;
var world = new Box2D.Dynamics.b2World(gravity, allowSleep);
有了世界,则可以添加刚体在世界中。
创建刚体的步骤
- 创建刚体(Body)定义对象数据(b2BodyDef), 设置定义对象的各种属性,包括刚体的位置,以及类型(动态或静态).静态刚体不受重力和其他刚体的碰撞影响。
- 创建设备(Fixture)定义对象数据(b2FixtureDef), 设置定义对象的各种属性, 包括摩擦系数,密度以及附加形状的恢复系数。
- 设置设备定义对象数据的形状属性,在Box2D中用到的两种形状为圆形(b2CircleShape)和多边形(b2PolygonShape)
- 调用World对象的crateBody方法, 传入刚体定义数据, 将返回一个刚体(Body)对象。
- 调用刚体(Body)的createFixture方法,传入设备定义数据, 这会把设备上的形状对象(Shape)附加到刚体上,并返回一个设备对象。
一个简单的定义如下
- //单位映射值
- var scale = 30;
- //创建刚体定义数据对象
- var bodyDef = new b2BodyDef;
- //为静态刚体, 即不受碰撞影响
- bodyDef.type = b2Body.b2_staticBody;
- //位置
- bodyDef.position.x = 640 / 2 / scale;
- bodyDef.position.y = 450 / scale;
- //创建设备定义数据对象
- var fixtureDef = new b2FixtureDef;
- //密度
- fixtureDef.density = 1.0;
- //摩擦
- fixtureDef.friction = 0.5;
- //恢复系数
- fixtureDef.restitution = 0.2;
- //设备形状为多边形
- fixtureDef.shape = new b2PolygonShape;
- //设置为矩形
- fixtureDef.shape.SetAsBox(320 / scale, 10 / scale);
- //世界创建刚体, 刚体创建设备, 设备拥有形状
- var body = world.CreateBody(bodyDef);
- var fixture = body.CreateFixture(fixtureDef);
绘制世界
Box2D对我们来说主要是用来处理物理计算, 而我们自行处理绘制世界中的所有对象。尽管如此,Box2D还是给我们提供了一个简单的方法DrawDebugData(), 我们可以用它将世界绘制在Canvas上。在使用DrawDebugData()之前, 需要先通过定义b2DebugDraw对象,并将其传入world.setDeugDraw()方法来设置调试绘制环境。
可以通过如下方式设置调试绘制
- var context = document.getElementById('canvas').getContext('2d');
- var debugDraw = new b2DebugDraw();
- //设置绘图上下文
- debugDraw.SetSprite(context);
- //设置绘图比例
- debugDraw.SetDrawScale(scale);
- //设置填充透明度
- debugDraw.SetFillAlpha(0.3);
- //设置线条厚度
- debugDraw.SetLineThickness(1.0);
- // 绘制所有的形状和关节
- debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
- //使用调试绘图
- world.SetDebugDraw(debugDraw);
让世界动起来
让世界产生动画效果, 无非就是不断的重复擦除旧的数据, 绘制新的数据,在Box2D中,通过以下步骤产生动画效果:
- 告诉Box2D为一小段时间片(通常是1/60秒)进行模拟运算。我们通过调用world.Step()函数完成这一步骤。
- 清除旧的绘制数据, 这可以通过world.ClearForces()来完成。
- 绘制世界中的一切对象, 此时对象经过Box2D的运算,将会出现在新的位置, 可以使用world.DrawDebugData()或者我们自己的绘制函数来绘制。
- 重复1-3步骤。###以下是一段示范代码
- var timeStep = 1 / 60;
- //设置速度约束求解器的迭代次数, 位置约束求解器迭代次数. 根据Box2d的文档, 8和3是建议的数值.
- var velocityIterations = 8;
- var positionIterations = 3;
- function animate() {
- world.Step(timeStep, velocityIterations, positionIterations);
- world.ClearForces();
- world.DrawDebugData();
- setTimeout(animate, timeStep);
- }
world.Step接收3个参数: 每次计算的时间片, 速度约束求解器迭代次数, 位置约束求解器迭代次数。后面2个参数和数学的积分器相关, 看不懂也无关紧要, 我们根据Box2D的文档, 设置建议数值8和3就行了。Box2D内部使用了积分器算法进行计算。积分器在每个不同的时间点内模拟计算物理公式。 时间步长则是我们想要Box2D要模拟计算的总时间。在这里,我们设置了1/60秒, 则也是官方文档建议的数值。
总结
Box2D将一个游戏映射为一个世界, 当然可以有很多的世界,不过没有太多的必要。世界本身具有重力,一个世界拥有许多的刚体, 而刚体又拥有形状。形状则被设备持有,设备拥有各种系数,包括摩擦,密度,恢复等,刚体之间又可以通过关节连接。而这个世界中的一切运作都通过Box2D自动计算。Box2D帮助我们从复杂的数学和物理公式中脱离出来, 让我们将更多的精力放在了游戏逻辑上。游戏开发者就像是一个创始者,创造了一个世界,并设置了相关的参数,接着将Box2D丢入这个世界中, 这个世界就开始了生生不息的循环运作。