备注:由于本人最近在学习box2d引擎,而中文资料中好的文章比较少,我就在google上找了一些英文资料。于是,发现了网上的Box2Dtutorial系列文章,觉得写得挺好的,于是做了一些翻译和大家分享分享。由于这是我第一次翻译技术型文章,翻译不当的地方还请各位多理解。

下面我给出原文网址:http://www.iforce2d.net/b2dtut/drawing-objects,本系列翻译文章仅用于学习交流之用,请勿用于商业用途。欢迎各位转载!

自己绘制物体

从之前的话题可以看出,仅仅使用debug draw是不能做出一款非常吸引人的游戏。通常,我们会使用自己的方法来在场景中绘制物体,并从Box2D中获取物理信息,以便我们知道从哪里开始绘制它们。在这一次的话题中,我们将创建一个类来作为一个游戏实体来使用,到时再看看如何保持实体正确位置。我们将通过在游戏实体中存储一个指向Box2D物体的指针来实现这一效果。

目前所有的实体类都是自己渲染,但之后我们将扩展它来演示其他的话题。这些课程的重点不是在渲染,因此我们将只用一个圆形来画一个很简单的笑脸,一次证明它可以正确地移动和旋转。

在这节话题中,我们从设置空心的围墙开始:

FooTest(){

//a static body

b2BodyDef myBodyDef;

myBodyDef.type=b2_staticBody;

myBodyDef.position.Set(0,0);

b2Body* staticBody= m_world->CreateBody(&myBodyDef);

 

//shape definition

b2PolygonShape polygonShape;

 

//fixture definition

b2FixtureDef myFixtureDef;

myFixtureDef.shape=&polygonShape;

 

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

}

 

让我们调用游戏实体类球,它是圆的,并且可以跳跃:

//outside and before the FooTest class
  class Ball {
  public:
    //class member variables
    b2Body* m_body;
    float m_radius;
  
  public:
    Ball(b2World* world, float radius) {
      m_body = NULL;
      m_radius = radius;
    }
    ~Ball() {}
  
  };
  
  //FooTest class member variable
  std::vector<Ball*> balls;
(为了保证最后部分的编译可以通过,你需要在文件开始出添加#include <vector>)。注意到,我们不是直接保存引用Box2D实体,而是让每个游戏实体之后,存储到游戏实体中。添加一个渲染函数到Ball类中以边画出一个漂亮的笑脸。这里要注意的重点是,我们是画着一张脸,它是集中在(0,0),那么它就不会旋转。半径为1是一个默认的呈现。
//Ball::render
    void render() {
      glColor3f(1,1,1);//white
  
      //nose and eyes
      glPointSize(4);
      glBegin(GL_POINTS);
      glVertex2f( 0, 0 );
      glVertex2f(-0.5, 0.5 );
      glVertex2f( 0.5, 0.5 );
      glEnd();
      
      //mouth
      glBegin(GL_LINES);
      glVertex2f(-0.5,  -0.5 );
      glVertex2f(-0.16, -0.6 );
      glVertex2f( 0.16, -0.6 );
      glVertex2f( 0.5,  -0.5 );
      glEnd();
  
      //circle outline
      glBegin(GL_LINE_LOOP);
      for (float a = 0; a < 360 * DEGTORAD; a += 30 * DEGTORAD) 
        glVertex2f( sinf(a), cosf(a) );
      glEnd();
    }

还有更多的事要做。在FooTest构造函数中,在创建了围墙,添加了Ball实体到场景中(如果你是想添加Ball实体,那么你应该在析构函数中删除它)

//add ball entity to scene in constructor
  Ball* ball = new Ball(m_world, 1);
  balls.push_back( ball );

最后,要实际画出ball实体,我们需要把它们添加到Step()函数中。如果你把这放到调用Test::Step()之后,ball实体将会被绘制在现存的debug draw data之上。

//in Step() function, after Test::Step()
  for (int i = 0; i < balls.size(); i++)
    balls[i]->render();

现在我们有一个ball实体,但它被绘制在默认的位置(0,0)上,并且我们不需要一个物理引擎那样做吧?让我们在Ball类中的构造函数创建为ball创建一个圆形。现在,就很明显为甚么构造函数需要一个b2World指针:

// Ball class constructor
    Ball(b2World* world, float radius) {
      m_body = NULL;
      m_radius = radius;
  
      //set up dynamic body, store in class variable
      b2BodyDef myBodyDef;
      myBodyDef.type = b2_dynamicBody;
      myBodyDef.position.Set(0, 20);    
      m_body = world->CreateBody(&myBodyDef);
  
      //add circle fixture
      b2CircleShape circleShape;
      circleShape.m_p.Set(0, 0); 
      circleShape.m_radius = m_radius; //use class variable
      b2FixtureDef myFixtureDef;
      myFixtureDef.shape = &circleShape;
      myFixtureDef.density = 1; 
      m_body->CreateFixture(&myFixtureDef); 
    }

好的,现在在场景中已经有一个物理物体,但我们的笑脸没有画在物理物体的位置上。为了做到这点,让我们向Ball类中添加另一个函数来在渲染前创建OpenGL转换。取决于你使用的渲染API,可能有更好的办法来做这个:

//in Ball class
    void renderAtBodyPosition() {
          
      //get current position from Box2D
      b2Vec2 pos = m_body->GetPosition();
      float angle = m_body->GetAngle();
  
      //call normal render at different position/rotation
      glPushMatrix();
      glTranslatef( pos.x, pos.y, 0 );
      glRotatef( angle * RADTODEG, 0, 0, 1 );//OpenGL uses degrees here
      render();//normal render at (0,0)
      glPopMatrix();
    }

不要忘记在Step()函数中改变调用渲染的函数使用新的renderAtBodyPosition()函数。现在,即使你关了debug draw显示功能(在控制面板上取消’Shapes’复选框),你的渲染代码仍然可以再正确的位置和旋转角度上画出ball。

为了提高娱乐性,我们可以使用从物理引擎反馈回来的信息用不同的速度来改变ball的颜色。例如,这样设置颜色:

  //in Ball::render
  b2Vec2 vel = m_body->GetLinearVelocity();
  float red = vel.Length() / 20.0;
  red = min( 1, red );
  glColor3f(red,0.5,0.5);

如何向场景中添加大量的ball----只需在FooTest构造函数中添加一个for循环即可:

//in FooTestconstructor

for(int i = 0; i < 20; i++){

Ball* ball = new Ball(m_world, 1);

balls.push_back( ball);

由于ball的大小是我们在创建Ball类对象时决定的,我们可以随机决定ball的大小:

for (int i = 0; i < 20; i++) {
    float radius = 1 + 2 * (rand()/(float)RAND_MAX);//random between 1 - 3
    Ball* ball = new Ball(m_world, radius);
    balls.push_back( ball );
  }

Uh-oh…有一些错误发生,balls没有再正确地碰撞。如果这是一个更完整的场景,那至少这是你要考虑的事,并且你有自信扔掉使用debug draw代码,而去支持使用令人炫目的图片。由于我们有debug draw随时可以用,它只需花费几秒钟就可以看到屋里引擎在做什么:

好吧,这里确实是有一点认为的错误,但我只是想介绍一些在你整个开发过程中有用的debug draw实现。最后,要想修复大小问题,我们只需在rendering code中使用scale:

//inside Ball::renderAtBodyPosition
  glPushMatrix();
  glTranslatef( pos.x, pos.y, 0 );
  glRotatef( angle * RADTODEG, 0, 0, 1 );
  glScalef( m_radius, m_radius, 1 ); //add this to correct size
  render();
  glPopMatrix();

打赏

发表评论

您的电子邮箱地址不会被公开。