~~~~我的生活,我的点点滴滴!!
游戏中一种常见的需求是让物体做匀速运动。例如横屏游戏中的玩家角色,太空飞船或者汽车,等等。根据游戏的不同,有时候物
体应该逐渐改变速度,其他情况又希望能够瞬间开始和停止运动。使用SetLinearVelocity方法精确的设置物体速度,这种方法看起
来非常诱人,而且这么做也可以达到目标,
但是这种方法有其自身的缺点。虽然这么做在屏幕上看起来很好,但是直接设置物体的速度不是参与模拟物理世界的正确方法。让
我们看看如何使用实际的力和冲量来使物体达到特定速度。
我们将会看到两种情况,一种是物体立即开始移动到特定速度,另一种是物体缓慢移动直到达到特定速度。一开始,我们需要一个
有动态物体的场景,然后再放一些静态物体的篱笆墙来防止这个动态物体到处跑。篱笆墙的场景在后面的话题中还会经常遇到。
为了持续跟踪用户所做的操作,我们会创建一个类成员变量来记录上一次用户所做的输入状态。
//.h 中添加 enum class moveState { MS_STOP, MS_LEFT, MS_RIGHT, }; moveState m_moveState; //.cpp 中添加一个物体和四面墙 b2Vec2 gravity(0, 0); /*********************start*************************/ //定义一个物体的基本属性,他基本包含了我们所知现实世界物体的所有属性 b2BodyDef bodyDef; //设置一个动态物体 bodyDef.type = b2_dynamicBody; bodyDef.angle = 0; //形状 b2PolygonShape boxShape; //创建一个2x2的正方形盒子 boxShape.SetAsBox(1,1); //定制器 b2FixtureDef fixtureDef; fixtureDef.shape = &boxShape; fixtureDef.density = 10; for(int i = 0 ;i < NUM; ++ i) { bodyDef.position.Set( -10 + i * 10, 20 ); m_body[i] = m_world->CreateBody(&bodyDef); m_body[i]->CreateFixture(&fixtureDef); } bodyDef.type = b2_staticBody; bodyDef.position.Set(0,0); b2Body *staticBody = m_world->CreateBody(&bodyDef); boxShape.SetAsBox(20, 1, b2Vec2(0,0), 0); staticBody->CreateFixture(&fixtureDef); boxShape.SetAsBox(20,1, b2Vec2(0,40), 0); staticBody->CreateFixture(&fixtureDef); boxShape.SetAsBox(1,20, b2Vec2(-20,20), 0); staticBody->CreateFixture(&fixtureDef); boxShape.SetAsBox(1,20, b2Vec2(20,20), 0); staticBody->CreateFixture(&fixtureDef); m_moveState = moveState::MS_STOP;
这样测试环境构建好了,我们需要在Step()里面去设置力/冲量来达到改变速度的效果,让我们看看如何使用SetLinearVelocity方
法来直接指定物体的速度。对于许多应用来说这么做已经足够好了。在Step()方法内部,完成一些每帧都需要更新的操作。
b2Vec2 vel = m_body[0]->GetLinearVelocity(); switch (m_moveState) { case moveState::MS_LEFT: { vel.x = -5; } break; case moveState::MS_STOP: { vel.x = 0; } break; case moveState::MS_RIGHT: { vel.x = 5; } break; default: break; } m_body[0]->SetLinearVelocity(vel); Test::Step(settings);
这里,我们获取当前速度并且保持垂直方向的速度不变,相反只改变横向速度,因为我们只想影响物体水平运动的速度。但是上面
这个有点问题,每次都是瞬间改变到5,感觉没有那种自然界正常加速的过程,为了让物体速度缓慢的变化至最大特定速度,使用下
面代码替换上述代码:
b2Vec2 vel = m_body[0]->GetLinearVelocity(); switch (m_moveState) { case moveState::MS_LEFT: { vel.x = b2Max( vel.x - 0.1f, -5.0f ); } break; case moveState::MS_STOP: { vel.x *= 0.98; } break; case moveState::MS_RIGHT: { vel.x = b2Min( vel.x + 0.1f, 5.0f ); } break; default: break; } m_body[0]->SetLinearVelocity(vel); Test::Step(settings);
这会在每帧计算的时候线性增加0.1,直到在该方向上增加到最大速度5为止,在testbed框架中,默认为每秒60帧,只要50帧或者一
秒钟就达到最大速度了。当按下按下stop按键,速度就会减小到前一帧速度的98%,一秒钟算下来就是0.98 ^ 60=每秒大概0.3。这
个方法的一个优点是可以很容易的针对加速特性进行调整。
使用力更适合使物体缓慢加速到指定速度,我们看下面的代码:
b2Vec2 vel = m_body[0]->GetLinearVelocity(); float force = 0; switch (m_moveState) { case moveState::MS_LEFT: { if ( vel.x > -5 ) force = -50; } break; case moveState::MS_STOP: { force = vel.x * -9.8f; } break; case moveState::MS_RIGHT: { if ( vel.x < 5 ) force = 50; } break; default: break; } m_body[0]->ApplyForce( b2Vec2(force,0), m_body[0]->GetWorldCenter() );
其缓慢加速的简单的基本逻辑。你多半会希望把当前正在做的例子做一些调整,例如一辆汽车以低速为起点进行快速加速,但是随
着速度越来越接近最大速度,加速度也会随之减小。因此,你要查看当前速度和最大速度之间的差距并且适当的减小力。力和加速
度之间的关系是f = ma,其中m是我们需要移动的物体的质量,a是‘每秒钟单位变化率’物体移动的速度也就是加速度,f是我们想
要进行计算的力。加速度也可以称为“每秒钟的速率”,既然这里的加速度和“每秒钟”是一回事。那么我们就可以写成f=mv/t,
其中t是力作用的时间长度。我们可以通过使用GetMass()方法获取物体的质量m。v是我们想要在最大速度和当前速度之间进行改变的
变量。为了达到瞬间改变速度的效果,如果在默认的testbed框架内,我们需要在每一帧或者说1/60秒的时间内不断的施加力。现在
我们知道了除f以外的所有条件,具体实现可以像下面这样:
b2Vec2 vel = m_body[0]->GetLinearVelocity(); float desiredVel = 0; switch (m_moveState) { case moveState::MS_LEFT: { desiredVel = -5; } break; case moveState::MS_STOP: { desiredVel = 0; } break; case moveState::MS_RIGHT: { desiredVel = 5; } break; default: break; } float velChange = desiredVel - vel.x; float force = m_body[0]->GetMass() * velChange / (1/60.0); //f = mv/t m_body[0]->ApplyForce( b2Vec2(force,0), m_body[0]->GetWorldCenter() ); Test::Step(settings);
或许已经发现上面的代码中可以简单的使用冲量来代替。既然冲量本身已经模拟了每帧中力的累加计算,我们只要去除时间部分然后
使用ApplyLinearImpulse方法就可以达到相同的效果:
b2Vec2 vel = m_body[0]->GetLinearVelocity(); float desiredVel = 0; switch (m_moveState) { case moveState::MS_LEFT: { desiredVel = b2Max( vel.x - 0.1f, -5.0f ); } break; case moveState::MS_STOP: { desiredVel = vel.x * 0.98f; } break; case moveState::MS_RIGHT: { desiredVel = b2Min( vel.x + 0.1f, 5.0f ); } break; default: break; } float velChange = desiredVel - vel.x; float impulse = m_body[0]->GetMass() * velChange; //disregard time factor m_body[0]->ApplyLinearImpulse( b2Vec2(impulse,0), m_body[0]->GetWorldCenter() ); Test::Step(settings);