什么是物理世界,是对你游戏的物理模拟. 使用前确认你的引擎包含了下载的Physics Extension包.
创建物理世界的时候,有两类型供选择, 标准(Standard)和FixedStepPhysicsWorld. 区别?
后者多了个参数,指定了每秒更新固定次数. 下例指示了如何创建物理世界:
private PhysicsWorld physicsWorld;
private void initPhysics()
{
physicsWorld = new PhysicsWorld(new Vector2(0, SensorManager.GRAVITY_EARTH), false);
yourScene.registerUpdateHandler(physicsWorld);
}
1. 如何控制重力
创建物理世界的时候必须传递Vector2(注:向量),来指定重力. 大多数时候第一个参数是0. 但是有时候你需要模拟其它力,比如横向的风力,那X就不是0了.
2.使用FixedStepPhysicsWorld
FixedStepPhysicsWorld是PhysicsWorld的子类,所以用法上大体一致, 这个类使用固定的帧数, 这个由你的需要来定.但是过大的帧数设定在配置差的机器上就会有差的表现,所以你需要自己度量.
l 不同刚体间的区别:
在BOX2D中有以下三类刚体:
· 静态刚体(Static)
· 运动刚体(Kinematic)
· 动态刚体(Dynamic)
1.静态刚体
静态刚体在物理模拟中不会移动,就好像有无限重力一样(换言之,就是永远在同一地方,不会受任何力的影响). 比如,滚动的游戏中的地面.(注: 但是会和其它刚体发生作用的)
2.运动刚体
运动刚体在模拟中根据它自己的速度来运动, 但是不会对力的作用有反应. 通过设置它的速度来控制运动, 不会与其它刚体发生作用. 如,在跑酷游戏中滚动的背景.
3.动态刚体
动态刚体是全模拟的,可以被使用者移动,也可以被力移动. 动态刚体可以与其它刚体发生碰撞.
重要说明:
总是选择正确的刚体类型, 因为 模拟是非常消耗性能的,显然对性能最友好的静态类型.
值得一提的是,在创建后也能改变刚体的类型.
l 创建刚体
创建刚体在在这个引擎中小菜一碟. 你可以使用基本形状(也可以有复杂形状):
· BoxBody- Rectangles, Squares
· LineBody
· CircleBody
1.例:
private void createBody()
{
final FixtureDef objectFixtureDef = PhysicsFactory.createFixtureDef(1, 0.5f, 0.5f);
PhysicsFactory.createBoxBody(physicsWorld, sprite, BodyType.StaticBody, objectFixtureDef);
}
创建一个新的Body 时候,应当指定刚体的类型, 物理世界,和属性fixture. fixture有三个参数:
· 密度(Density)
· 张力(Elasticity)
· 摩擦(Friction)
这些值根据你的需要来.
2.创建移动的刚体
如果要创建一个移动的刚体,就要注册物理关联(Physics Connector),什么意思? 就是让你的形状/精灵跟随刚体运动, 不然精灵就不会移动.
physicsWorld.registerPhysicsConnector(new PhysicsConnector(sprite, body, true, true));
参数说明:被关联的精灵和刚体,是否更新位置,是否旋转。
3.设置用户数据:
通过设置用户数据为刚体提供“全局标识”,通过这个简单的方法,很容易区分刚体间的联接。
yourBody.setUserData("Player");
这里的参数类型为Object, 所以不一定为String, 甚至可以是Sprite,这样你就可以检查刚体是不是精灵的实例。
l 处理刚体间的碰撞和链接
基于物理模拟的游戏,都需要处理刚体间的碰撞和链接,使用ContactListener就好了。假如我们要在Body A 和 Body B产生接触后执行某种动作,使用ContactListener使工作变得简单。
首先,创建新的ContactListener:
private ContactListener createContactListener()
{
ContactListener contactListener = new ContactListener()
{
@Override
public void beginContact(Contact contact)
{
final Fixture x1 = contact.getFixtureA();
final Fixture x2 = contact.getFixtureB();
}
@Override
public void endContact(Contact contact)
{
}
@Override
public void preSolve(Contact contact, Manifold oldManifold)
{
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse)
{
}
};
return contactListener;
}
这里有两个重要的函数:beginContact 和endContact。在beginContact 中得到两个产生链接的刚体引用。
步骤二, 在物理系统中注册创建的Listener、
mPhysicsWorld.setContactListener(createContactListener());
通过设置的UserData, 可以在ContactListener中检查是否是特定的刚体:yourBody.setUserData("player");
if (x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("monster"))
{
Log.i("CONTACT", "BETWEEN PLAYER AND MONSTER!");
}
注意:这里只检查了一次,假设x2是玩家而x1是怪物,有时候你需要调换顺序再检查一次。
l 完全移除刚体
有时候你需要将刚体从系统中完全移动,一个常见的错误是没有在UpdateThread线程中做,这可以导致错误和崩溃。正确的做法是:
mEngine.runOnUpdateThread(new Runnable()
{
@Override
public void run()
{
// Detete objects safely here.
}
}
1.完全移除刚体和形状(shape):
如果已经关联了PhysicsConnector, 应当先反注册,再移除刚体,见代码:
final PhysicsConnector physicsConnector =
physicsWorld.getPhysicsConnectorManager().findPhysicsConnectorByShape(shape);
mEngine.runOnUpdateThread(new Runnable()
{
@Override
public void run()
{
if (physicsConnector != null)
{
physicsWorld.unregisterPhysicsConnector(physicsConnector);
body.setActive(false);
physicsWorld.destroyBody(bbody);
scene.detachChild(shape);
}
}
});
注:Shape是Entity的实例
l 物理系统的调试渲染器(Physics Debug Render)
这节介绍一个对每个开发者都非常有用的工具,特别是在创建不规则形状的刚体的时候。你知道,刚体默认是不可见的,为了更直观,使用nazgee分享的调试渲染器吧。用来做什么的?就是能让你看到刚体的边界!
1.如何使用:
前提是AE的GLES2或者GLES2 AC版本。
把源码连接到你的工程
与你的场景关联
DebugRenderer debug = new DebugRenderer(mPhysicsWorld, getVertexBufferObjectManager());
pScene.attachChild(debug);
使用这个东东后,你的游戏速度会有明显的下降,但是这仅仅作为调试用,所以问题不是太大。
l 不规则的刚体形状
有时候你发现有必要创建不规则的刚体形状(不是简单的矩形或圆),比如地面,有两个办法:
ü 在代码中手动创建
ü 使用一些工具从精灵中生成多边形刚体形状
1.手动方式:
对于不太复杂的形状(比如由几个基本元素(fixture),矩形和感应器)这种方式很简单,如果你想知道具体怎么做,参见AE的这个例子: CLICK.
2.使用工具
介绍两个工作,一个免费一个付费的,但是可以免费试用。
Physics Editor by Andreas Löw (收费) - CLICK.
Vertex Helper by ZAN (免费) - CLICK
推荐第一个,更准备更容易。(注:打广告?)
l 创建感应器或叫触发器
先解释到底什么是感应器(sensor), 引用BOX2D手册中的话:sensor 就是检测碰撞但是不产生反应的装置。你可以把任何装置标记为感应器,感应器可以为静态或者动态,记住一个刚体可以有多个装置,因此是多个感应器和装置的组合。
(A sensor is a fixture that detects collision but does not produce a response.You can flag any fixture as being a sensor. Sensors may be static or dynamic. Remember that you may have multiple fixtures per body and you can have any mix of sensors and solid fixtures)
1.用法:
假如游戏中有一些区域,当玩家走进这些区域时会触发一些事情(比如显示消息)
final Sprite area = new Sprite(x, y, 100, 300, textureRegion, vbo);
FixtureDef areaFixtureDef = PhysicsFactory.createFixtureDef(0, 0, 0);
areaFixtureDef.isSensor = true;
Body b = PhysicsFactory.createBoxBody(physicsWorld, area, BodyType.StaticBody, areaFixtureDef);
scene.attachChild(area);
在FixtureDef 对象中设置isSensor属性为true.
问题来了,当玩家走进上面创建的区域时如何检测呢?
处理刚体间的接触就是使用Contact Listener ,参见前面的文章。
· serUserData 设置标识。
· 在beginContact() 检查标识
· 声明一个新类域 boolean isInsideZoone = false;
· 如果在beginContact中产生接触将这个布尔值设为 true
· 在 endContact() 将值设为false
private boolean isInsideZoone = false;
private ContactListener createContactListener()
{
ContactListener contactListener = new ContactListener()
{
@Override
public void beginContact(Contact contact)
{
final Fixture x1 = contact.getFixtureA();
final Fixture x2 = contact.getFixtureB();
if(x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("area"))
{
isInsideZoone = true;
//action on enter
}
}
@Override
public void endContact(Contact contact)
{
final Fixture x1 = contact.getFixtureA();
final Fixture x2 = contact.getFixtureB();
if(x2.getBody().getUserData().equals("player")&&x1.getBody().getUserData().equals("area"))
{
isInsideZoone = false;
//进入指定区域,执行特定动作
}
}
@Override
public void preSolve(Contact contact, Manifold oldManifold)
{
}
@Override
public void postSolve(Contact contact, ContactImpulse impulse)
{
}
};
return contactListener;
}
l 如何克服重力
假如有一个动态刚体,不时地需要克服重力,很简单,只需将与重力相反的力加到刚体上。
private Body body;
private void createAntiGravityBody()
{
Rectangle yourEntity = new Rectangle(0, 0, 100, 100, vbo)
{
@Override
protected void onManagedUpdate(float pSecondsElapsed)
{
super.onManagedUpdate(pSecondsElapsed);
body.applyForce(-physicsWorld.getGravity().x * body.getMass(),
-physicsWorld.getGravity().y * body.getMass(),
body.getWorldCenter().x,
body.getWorldCenter().y);
}
};
yourEntity.setColor(Color.RED);
body = PhysicsFactory.createBoxBody(physicsWorld, yourEntity, BodyType.DynamicBody, fixtureDef);
scene.attachChild(yourEntity);
}
关键就是使用applyForce 方法。
注:看了评论,还有一个更简单的方法body.setGravityScale(0);
l 销毁物理系统和所有的对象
前面已经讲了如何销毁单个刚体对象,本节讲如何销毁整个物理系统。
记住,必须在update thread线程中做
public void destroyPhysicsWorld()
{
engine.runOnUpdateThread(new Runnable()
{
public void run()
{
Iterator<Body> localIterator = physicsWorld.getBodies();
while (true)
{
if (!localIterator.hasNext())
{
physicsWorld.clearForces();
physicsWorld.clearPhysicsConnectors();
physicsWorld.reset();
physicsWorld.dispose();
System.gc();
return;
}
try
{
final Body localBody = (Body) localIterator.next();
GameScene.this.physicsWorld.destroyBody(localBody);
}
catch (Exception localException)
{
Debug.e(localException);
}
}
}
});
}
通过getBodies 获取对象,释放完全,再将整个physicsWorld清除!
l BOX2D的连接体
什么是连接体,你可能也猜得到是用来做什么的,我们可以把不同的刚体通过接头连起来,请先阅读下边的重要提示:
IMPORTANT: 大意就是要使用最新版本的AE和BOX2D
环境配置如下:
1.最新的AE, GLES2 Anchor Center CLICK HERE
2.最新的Box2D,CLICK HERE
3.Box2D debug draw CLICK HERE
连接类型:
· 旋转接头(Revolute joint) - 旋转接头强迫两个刚体共享一个锚点通常叫做铰点,旋转接头有一个旋转自由度,即两个刚体的相对旋转角度。
· Distance joint - it maintains distance between two bodies, by specifying anchor points for those joints, some advantages and possibilities: you can manipulate distance on the runtime, you can also adjust damping ratio and mass-spring-damper frequency in Hertz to achieve different behaviour (you will often need to "play" with its values before achieving result you were thinking of)
· Prismatic joint - Prismatic joint allows for relative translation of two bodies along a specified axis. A prismatic joint prevents relative rotation. Therefore, a prismatic joint has a single degree of freedom. Still do not understand how does it work? Lets imagine slider in your pants.
· Weeld joint - It attempts to constrain all relative motion between two bodies, in another words, it bounds two bodies together
· Rope joint - Restricts the maximum distance between two points. This can be useful to prevent chains of bodies from stretching, even under high load, usually we use this joint to connect multiple small bodies to each other, to simulate proper rope, thought it usually its not performance friendly, because requires many dynamic bodies.
· Wheel joint - The wheel joint restricts a point on bodyB to a line on bodyA. The wheel joint also provides a suspension spring, as you can conclude from joint name, we can use it to simulate wheel of the car for instance - with realistic suspension effect.
B2里还有更多类型的装置,比如gear, 更详细地请参见B2官方手册 :box2d manual.
l 旋转接头
下图展示了一个RJ:
如何实现下图的效果呢?
简明起见,有两个刚体,动态的红色矩形和静态的绿色矩形。锚点在绿色矩形的中心:
// Create green rectangle
final Rectangle greenRectangle = new Rectangle(400, 240, 40, 40, getVertexBufferObjectManager());
greenRectangle.setColor(Color.GREEN);
scene.attachChild(greenRectangle);
// Create red rectangle
final Rectangle redRectangle = new Rectangle(460, 240, 120, 10, getVertexBufferObjectManager());
redRectangle.setColor(Color.RED);
scene.attachChild(redRectangle);
// Create body for green rectangle (Static)
final Body greenBody = PhysicsFactory.createBoxBody(physicsWorld, greenRectangle, BodyType.StaticBody, PhysicsFactory.createFixtureDef(0, 0, 0));
// Create body for red rectangle (Dynamic, for our arm)
final Body redBody = PhysicsFactory.createBoxBody(physicsWorld, redRectangle, BodyType.DynamicBody, PhysicsFactory.createFixtureDef(5, 0.5f, 0.5f));
physicsWorld.registerPhysicsConnector(new PhysicsConnector(redRectangle, redBody, true, true));
// Create revolute joint, connecting those two bodies
final RevoluteJointDef revoluteJointDef = new RevoluteJointDef();
revoluteJointDef.initialize(greenBody, redBody, greenBody.getWorldCenter());
revoluteJointDef.enableMotor = true;
revoluteJointDef.motorSpeed = -1;
revoluteJointDef.maxMotorTorque = 100;
physicsWorld.createJoint(revoluteJointDef);
// Attach box2d debug renderer if you want to see debug.
scene.attachChild(new DebugRenderer(physicsWorld, getVertexBufferObjectManager()));
1.创建绿色的矩形
2.创建红色矩形,设置正确的位置。
3.为两个矩形创建正确类型刚体。
4.为红色刚体注册物理连接器,让红色矩形随着刚体运动。
5.定义一个连接头,使用initialize 初始化连接头:第一个刚体,第二个刚体,锚点
6.关联box2d debug renderer。
设定刚体位置时,注意使用的单位是米(Meters)而不是像素(Pixels)
float pixelsInMeters = pixelsValue / PhysicsConstants.PIXEL_TO_METER_RATIO_DEFAULT;
常见问题:
1.如何改变转动方向? 将MotorSpeed值乘以-1
2.最大的扭转力(Motor Torque)是多少? 设为你想达到的效果即可
3.运行时如何调整连接体的属性?
一个很愚蠢的错误是对RevoluteJointDef 的引用修改属性还期望会得到正确的结果,其实什么都不会发生!因为DEF只是用来创建Joint用的。正确的做法是直接修改对RevoluteJoin的t引用:
// Our reference
private RevoluteJoint revoluteJoint;
// While creating joint
revoluteJoint = (RevoluteJoint) physicsWorld.createJoint(revoluteJointDef);
然后对RevoluteJOint进行修改。