Box2D C++ 匀速运动

~~~~我的生活,我的点点滴滴!!


游戏中一种常见的需求是让物体做匀速运动。例如横屏游戏中的玩家角色,太空飞船或者汽车,等等。根据游戏的不同,有时候物

体应该逐渐改变速度,其他情况又希望能够瞬间开始和停止运动。使用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()方法内部,完成一些每帧都需要更新的操作。


1、直接设置速度

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。这

个方法的一个优点是可以很容易的针对加速特性进行调整。


2、使用力

使用力更适合使物体缓慢加速到指定速度,我们看下面的代码:

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);

这和使用SetLinearVelocity方法具有同样的效果,而且还符合真实的物理场景应用。


3、使用冲量

或许已经发现上面的代码中可以简单的使用冲量来代替。既然冲量本身已经模拟了每帧中力的累加计算,我们只要去除时间部分然后

使用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);

达到逐步加速与缓慢减速的效果。



你可能感兴趣的:(Box2D)