原文连接 app
http://www.iforce2d.net/b2dtut/top-down-car 函数
最近讨论'top down' car physics如何很实现的话题很愈来愈多,因此我想我能够试一试而且展开一个话题。一个 td(top-down) car 被设计成在一个0重力的世界中,被一个为底盘的身体和四个分离的轮子身体。它取决于仅仅用一个带有底盘的身体 测试
和没必要担忧的分离的轮子。 问题的关键的另外一种状况是阻止身体沿一条轴移动,然而仍然容许他在另一条轴上自由移动(轮子应该能够来回移动。)这自己并非一个艰难的壮举,诀窍是使得那些用户来控制车有很好的感觉。若是水平速度被简单的彻底种植,车子就会感受像是在铁路上跑同样,而且咱们想容许车子能够在必定的状况下大话,而且在不一样的表面有不一样的表现等等。在咱们开始之前你能够看一下这个完美实现的top-down赛车游戏.这种事情是咱们的目标。 基础的步骤是去找到最近的身体的水平速度而且给他一个推理是的咱们能够取消那个水平速度。咱们开始仅仅用一个身体来表明一个轮子,一会咱们用四个它来固定到另一个身体来实现更复杂的模拟。由于全部的轮子作一样的事情,咱们能够为他作一个类。这是起始点,一个带有b2Body指针自觉得成员变量而且带着一个简单的盒子形状来初始化。 class TDTire { public: b2Body* m_body; TDTire(b2World* world) { b2BodyDef bodyDef; bodyDef.type = b2_dynamicBody; m_body = world->CreateBody(&bodyDef); b2PolygonShape polygonShape; polygonShape.SetAsBox( 0.5f, 1.25f ); m_body->CreateFixture(&polygonShape, 1);//shape, density m_body->SetUserData( this ); } ~TDTire() { m_body->GetWorld()->DestroyBody(m_body); } }; 这里我是用了一个CreateFixture不须要fixture定义的简单版本,注意到咱们也设置了数据来建立b2body,因此这个物理身体和游戏逻辑会彼此涉及。 ---------------------------------------------------------------------------------- 消除水平速度。 为了取消水平速度,咱们须要知道它是什么。咱们能够找到在最近时间内身体的水平速度在赛道方向的法线上的投影假。设轮子的当前坐标是(0,1)将会向前走1,0将会向右走咱们可使用获取世界向量来获取最近的身体在世界坐标中的朝向。 假设咱们的轮胎旋转了一点点,向上移动以下图所示。咱们想要使得蓝色向量向红色向量投影来看看他有多长若是他仅仅是以红色方向移动。 咱们能够增长下面这样的方法到TDTire里面: b2Vec2 getLateralVelocity() { b2Vec2 currentRightNormal = m_body->GetWorldVector( b2Vec2(1,0) ); return b2Dot( currentRightNormal, m_body->GetLinearVelocity() ) * currentRightNormal; } 而后给他一个推力,使得摆脱他的水平速度。这和最后一部分“一恒定速度前进”很类似,那里咱们有一个渴望的速度。咱们应用一个推力乘以身体的质量,使其达到这一速度在一个时间步。咱们增长一个函数使得它在一个时间步让他作这些。 void updateFriction() { b2Vec2 impulse = m_body->GetMass() * -getLateralVelocity(); m_body->ApplyLinearImpulse( impulse, m_body->GetWorldCenter() ); } 在舞台上,若是你建立了一个轮子身体在世界中,若是使用鼠标推进他,将会注意到赛道方向的速度确实被推力取消了,若是朝前方推进轮子你会发现它更倾向于转圆圈。几乎他就像一个真的轮胎同样当你滚动轮子而且他会慢慢停下来。 你也会注意到他能够自由旋转,使得它看起来有点不真实,一个真实的车轮子不是那样的,因此让咱们给用 类似的方式来消除侧面速度。。旋转更容易一些,由于咱们不须要作这个向量投影的东西——添加这个到updateFriction: m_body->ApplyAngularImpulse( 0.1f * m_body->GetInertia() * -m_body->GetAngularVelocity() ); 0.1是我决定的值,他有点像我上一次旋转的轮胎。若是咱们彻底停掉轮子的旋转,他将会像在铁路同样,哪里都不能去,除了走直线。没有彻底阻止轮胎的旋转的另外一个缘由是咱们想让玩家来使用这个身体来驱动它。 最后你可能注意到轮子会永远朝前走,因此咱们给一个强力使得它最终中止。 b2Vec2 currentForwardNormal = getForwardVelocity(); float currentForwardSpeed = currentForwardNormal.Normalize(); float dragForceMagnitude = -2 * currentForwardSpeed; m_body->ApplyForce( dragForceMagnitude * currentForwardNormal, m_body->GetWorldCenter() ); 再次进入updateFriction和值2来自一些诈骗和调整。固然你聪明的人自动知道getForwardVelocity getLateralVelocity()是同样的(),但与当地的一个向量(0,1)代替(1,0)对吗? 控制一个轮子。 在咱们将要制做四轮车以前,咱们须要更多的考虑下一个轮子应该作的事情。咱们至少能让他往前日后走。我也要使得它可以打滑,而且处理不一样的表面。咱们先集中精力作好一个,剩下四个就会很容易。 要测试轮胎是受多种运动的状况,咱们能够先假设这个单一的轮胎自己就是一辆汽车,让用户直接旋转它。这是一个基本的方式跟踪的关键(W/A/S/D)的用户是当前按下的按键: //global scope enum { TDC_LEFT = 0x1, TDC_RIGHT = 0x2, TDC_UP = 0x4, TDC_DOWN = 0x8 }; //testbed Test class variable int m_controlState; //testbed Test class constructor m_controlState = 0; //testbed Test class functions void Keyboard(unsigned char key) { switch (key) { case 'a' : m_controlState |= TDC_LEFT; break; case 'd' : m_controlState |= TDC_RIGHT; break; case 'w' : m_controlState |= TDC_UP; break; case 's' : m_controlState |= TDC_DOWN; break; default: Test::Keyboard(key); } } void KeyboardUp(unsigned char key) { switch (key) { case 'a' : m_controlState &= ~TDC_LEFT; break; case 'd' : m_controlState &= ~TDC_RIGHT; break; case 'w' : m_controlState &= ~TDC_UP; break; case 's' : m_controlState &= ~TDC_DOWN; break; default: Test::Keyboard(key); } } 注意: KeyboardUp 能够在最新的box2d testbed中使用,可是若是你正在使用v2.1.2 从其余的教程中找不到。你能够获取最新的box2d版本,或者在testbed中增长实现,他是很简单的仅仅使用键盘函数。 让咱们增长一个函数到轮子的clas中,使得他在输入的状态中作更聪明的事情。 float m_maxForwardSpeed; // 100; float m_maxBackwardSpeed; // -20; float m_maxDriveForce; // 150; //tire class function void updateDrive(int controlState) { //find desired speed float desiredSpeed = 0; switch ( controlState & (TDC_UP|TDC_DOWN) ) { case TDC_UP: desiredSpeed = m_maxForwardSpeed; break; case TDC_DOWN: desiredSpeed = m_maxBackwardSpeed; break; default: return;//do nothing } //find current speed in forward direction b2Vec2 currentForwardNormal = m_body->GetWorldVector( b2Vec2(0,1) ); float currentSpeed = b2Dot( getForwardVelocity(), currentForwardNormal ); //apply necessary force float force = 0; if ( desiredSpeed > currentSpeed ) force = m_maxDriveForce; else if ( desiredSpeed < currentSpeed ) force = -m_maxDriveForce; else return; m_body->ApplyForce( force * currentForwardNormal, m_body->GetWorldCenter() ); } 使用速度和力量来到使他变成你想要的那样,话题的开始咱们有太多考虑过尺寸,而且个人轮子不是个不真实的一宽,因此这些速度也不是真实世界的值。 如今轮子能够向前移动和向后移动,咱们也使得它运转经过事假一些扭转力,当a/d 按键被按下。由于咱们最后的目标是绑定这些轮子到一个车子的身上,程序的这部分将会被丢掉,因此他只是一个粗略的方式使得拐弯发生。因此咱们能够测试下一部分--打滑和表面。另外一方面,若是你真的倾向于设计汽车做为单体,你可能须要冲个这些使得它够合乎情理的。e.g.不要让车子拐弯除非他在移动。 void updateTurn(int controlState) { float desiredTorque = 0; switch ( controlState & (TDC_LEFT|TDC_RIGHT) ) { case TDC_LEFT: desiredTorque = 15; break; case TDC_RIGHT: desiredTorque = -15; break; default: ;//nothing } m_body->ApplyTorque( desiredTorque ); } Allowing skidding 这一点咱们有一个能够控制的汽车身体,根据咱们最初的计划来消除横向速度,他表现的良好。这是很是好的,若是你想模拟的槽车,坚持本身的轨道像胶水。可是他感受更天然若是车子能够再打滑一点。记得当咱们彻底横向速度的时候,咱们怎样彻底消除他的?咱们简单的计算一下推力并给他加上去。因此全部咱们须要作的事情就是约束这个推力到最大值,而且轮子将会打滑当环境要求一个比容许值更大的修改。在updateFriction函数中这仅仅是一个另外的状态。 b2Vec2 impulse = m_body->GetMass() * -getLateralVelocity(); //existing code if ( impulse.Length() > maxLateralImpulse ) impulse *= maxLateralImpulse / impulse.Length(); m_body->ApplyLinearImpulse( impulse, m_body->GetWorldCenter() ); //existing code 我发现maxLateralImpulse为3的时候容许不多的数量的打滑 当以告诉度拐弯的时候,值为2的时候就像一个湿撸,值为1的时候是我想起一个行驶的小船在水上拐弯。这些值不管如何将会被适应当轮子链接到车子底盘。因此如今不要大惊小怪。