Box2D C++教程10-匀速运动
本文出自https://shuwoom.com博客,欢迎访问!
转载自:http://www.ohcoder.com/post/2012-06-17/40027394030
以匀速移动物体
游戏中很多情况会让物体做匀速运动。例如横屏游戏中的玩家角色,太空飞船或者汽车,等等。根据游戏的不同,有时候物体应该逐渐改变速度,另外一些时候又希望能够立刻开始和停止运动。使用SetLinearVelocity方法明确的设置物体速度来完成这项任务,看起来非常的诱人,当然这么做也可以工作的很好,但是这种方法是有缺点的。通常情况下这么做屏幕上看起来很好,但是直接设置物体的速度不是参与模拟物理世界的正确方法。让我们看看如何使用实际的力和冲量来使物体达到特定速度的方法。
我们将会看到两种情况,一种是物体立即开始移动到所需速度,另一种是物体缓慢移动直到达到特定速度。作为开始我们将会在场景中放置一个动态物体,然后再放一些静态物体的篱笆墙来阻止这个动态物体的运动。这些篱笆墙的创建方法来自于之前所讨论的话题中。为了跟踪用户要做的动作,我们会创建一个类的成员变量来记录上一次用户所做的输入状态。
//enumeration of possible input states enum _moveState { MS_STOP, MS_LEFT, MS_RIGHT, };
//class member variables b2Body* body; _moveState moveState;
FooTest() { //body definition b2BodyDef myBodyDef; myBodyDef.type = b2_dynamicBody;
//shape definition b2PolygonShape polygonShape; polygonShape.SetAsBox(1, 1); //a 2x2 rectangle
//fixture definition b2FixtureDef myFixtureDef; myFixtureDef.shape = &polygonShape; myFixtureDef.density = 1;
//create dynamic body myBodyDef.position.Set(0, 10); body = m_world->CreateBody(&myBodyDef); body->CreateFixture(&myFixtureDef);
//a static body myBodyDef.type = b2_staticBody; myBodyDef.position.Set(0, 0); b2Body* staticBody = m_world->CreateBody(&myBodyDef);
//add four walls to the static body polygonShape.SetAsBox( 20, 1, b2Vec2(0, 0), 0);//ground staticBody->CreateFixture(&myFixtureDef); polygonShape.SetAsBox( 20, 1, b2Vec2(0, 40), 0);//ceiling staticBody->CreateFixture(&myFixtureDef); polygonShape.SetAsBox( 1, 20, b2Vec2(-20, 20), 0);//left wall staticBody->CreateFixture(&myFixtureDef); polygonShape.SetAsBox( 1, 20, b2Vec2(20, 20), 0);//right wall staticBody->CreateFixture(&myFixtureDef);
moveState = MS_STOP; } |
下面我们需要Keyboard()方法实现键盘输入:
void Keyboard(unsigned char key) { switch (key) { case 'q': //move left moveState = MS_LEFT; break; case 'w': //stop moveState = MS_STOP; break; case 'e': //move right moveState = MS_RIGHT; break; default: //run default behaviour Test::Keyboard(key); } } |
从这里开始,所有的需要改变的特性都会在Step()方法中实现,物体具体行为取决于用户的输入。
直接设置速度
之前我们是以力/冲量方法作为开始的,让我们看看如何使用SetLinearVelocity方法来直接指定物体的速度。对许多应用来说这或许是比较不错的。在Step()方法内部,我们会做一些每帧都需要更新的操作:
//inside Step() b2Vec2 vel = body->GetLinearVelocity(); switch ( moveState ) { case MS_LEFT: vel.x = -5; break; case MS_STOP: vel.x = 0; break; case MS_RIGHT: vel.x = 5; break; } body->SetLinearVelocity( vel ); |
这里,我们获取当前速度并且保持垂直方向的速度不变,相反只改变横向速度,因为我们只想影响物体的横向运动速度。
使用上述代码,你会在testbed场景中看到物体的速度会立刻改变。为了让物体速度缓慢的变化至最大指定速度,你应该使用下面代码替换上述代码:
switch ( moveState ) { case MS_LEFT: vel.x = b2Max( vel.x - 0.1f, -5.0f ); break; case MS_STOP: vel.x *= 0.98; break; case MS_RIGHT: vel.x = b2Min( vel.x + 0.1f, 5.0f ); break; } |
这样会在每帧计算的时候增加0.1,直到增加到指定方向上最大速度5为止,在testbed框架中,默认为每秒60帧,只要50帧就达到最大速度了。当按下按下stop按键,速度就会减小到前一帧的98%,一秒钟算下来就是0.98*60=每秒大概有0.3。这个方法的一个优点是可以简单的实现加速特性。
使用力矩
使用力矩更适合使物体缓慢加速到指定速度,首先让我们尝试一下:
b2Vec2 vel = body->GetLinearVelocity(); float force = 0; switch ( moveState ) { case MS_LEFT: if ( vel.x > -5 ) force = -50; break; case MS_STOP: force = vel.x * -10; break; case MS_RIGHT: if ( vel.x < 5 ) force = 50; break; } body->ApplyForce( b2Vec2(force,0), body->GetWorldCenter() ); |
和上面加速方式类似,也是线性的,但是制动方式是非线性的。通过这个例子我们得到了一个在每帧更新的时候对物体施加最大力矩并让其缓慢加速的简单逻辑。你会把你的应用替换成这种方式的。就像一辆汽车以低速为起点快速进行加速,但是随着速度越来越接近最大速度,加速度也会随之降低。因为这一点,你需要查看当前速度和最大速度之间的不同并且适当的调整力矩。
从之前的话题中我们知道力矩只能起到缓慢的加速效果,这不会使用它们让物体变化的速度得到迅速改变。尽管如此,如果我们让时间刻度足够短,力矩足够大,我们还是可以得到于冲量相同的效果的。首先我们需要做一点点数学运算...
力矩和加速度之间的关系是f=ma,其中m是我们需要移动的物体的质量,a是‘每秒钟每帧’物体移动的速度也就是加速度,f是我们想要进行加速的力矩。加速度也可以称为“每秒钟速度”,既然这里速度和“每秒钟”是一回事。那么我们就可以写成f=mv/t,中t是力矩作用的时间长度。
我们可以通过使用GetMass()方法获取物体的质量。v是我们想要在最大速度和当前速度之间进行改变的变量。为了达到瞬间改变速度的效果,如果在默认的testbed框架内,我们需要在每一帧或者说1/60秒的时间内不断的施加力矩。现在我们就可以得出想要的f,具体实现可以像下面这样:
b2Vec2 vel = body->GetLinearVelocity(); float desiredVel = 0; switch ( moveState ) { case MS_LEFT: desiredVel = -5; break; case MS_STOP: desiredVel = 0; break; case MS_RIGHT: desiredVel = 5; break; } float velChange = desiredVel - vel.x; float force = body->GetMass() * velChange / (1/60.0); //f = mv/t body->ApplyForce( b2Vec2(force,0), body->GetWorldCenter() ); |
这样就可以达到和使用SetLinearVelocity方法同样的效果,而且还符合真实的物理场景的应用。
使用冲量
聪明的读者或许已经发现上面的代码中可以简单的使用冲量来代替。因为冲量本身就是模拟了每帧中力的累加计算,我们只要使用ApplyLinearImpulse方法把时间部分替换成冲量就可以达到相同的效果:
b2Vec2 vel = body->GetLinearVelocity(); float desiredVel = 0; switch ( moveState ) { case MS_LEFT: desiredVel = -5; break; case MS_STOP: desiredVel = 0; break; case MS_RIGHT: desiredVel = 5; break; } float velChange = desiredVel - vel.x; float impulse = body->GetMass() * velChange; //disregard time factor body->ApplyLinearImpulse( b2Vec2(impulse,0), body->GetWorldCenter() ); |
为了达到缓慢加速的效果,我们只要在速度上做适当的调整就可以了:
caseMS_LEFT: desiredVel = b2Max( vel.x - 0.1f, -5.0f );break;
caseMS_STOP: desiredVel = vel.x * 0.98f; break;
caseMS_RIGHT: desiredVel = b2Min( vel.x + 0.1f, 5.0f );break;