Box2D C++ 教程-畫自己的圖像

轉載文章:原貼地址:http://ohcoder.com/blog/2012/11/30/drawing-your-own-objects/

  • 畫自己的圖像

從之前的話題中可以看出,很顯然使用debug draw不能做出視覺上具有吸引力的遊戲。通常我們都會使用我們自己的方法在場景中畫圖像,然後訪問Box2d獲取物體的物理信息以此知道在哪裏畫圖像。在本次話題中,我們將會專門定義一個類用作渲染遊戲實體,然後我們來看看如何保證實體的物理狀態。

現在所有實體類將會進行自身渲染,但是之後我們在其它話題中使用。那些話題中的焦點就不再是渲染圖形,所以這裏我們在一個圓弧上,畫了一張帶有微笑的笑臉,並且保證其能夠正確移動和旋轉。

那麼在本話題中,讓我們創建一個四方形空的區域作爲開始。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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);
}

pic

讓我們調用我們的遊戲實體類Ball並然其滾動和彈跳:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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語句。)注意現在在實體內直接存儲了Box2D物體的引用,我們會讓每一個實體自身存儲其對應其相應的物理實體的引用。爲了渲染一個漂亮的笑臉,爲Ball類添加一個渲染方法。這裏有一個需要注意的地方,那就是讓笑臉的中心作爲(0,0)點,但是不讓其旋轉。默認的渲染半徑設置爲1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//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對象進行刪除)。

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

最後,爲了真正畫出小球實體我們需要添加Step()方法。如果你把這個方法放到Test::Step()之後,那麼小球會顯示在debug draw所畫的圖像之上。

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

pic

現在我們有了一個小球實體,但是它顯示在默認的(0,0)點並且我們不會希望我們的物理引擎這麼做的,是吧?讓我們在Ball類的構造函數中對球的圓弧進行設置。現在可以明顯的看出爲什麼Ball的構造函數需要傳遞b2World指針了吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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);
}

pic

好的,現在場景中有了一個物理實體,但是我們的笑臉並沒有被畫到物理實體的位置。爲了達到這個目的,讓我們爲Ball類在渲染之前添加另外一個方法來設置OpenGL的平移變換。取決於你所使用的API,可能有比這裏列舉的更好的方法來完成這個功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
//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顯示(在testbed右邊控制面板中取消所勾選的‘Shapes’勾選框),你自定義的渲染代碼仍然會渲染出一個笑臉小球,並顯示在正確的位置以及進行正確的旋轉。

pic

爲了更加有趣,我們可以在小球以不同速度移動的時候改變其顏色。例如,像這樣設置顏色:

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

那麼往場景中添加一堆小球如何呢?只要在FooTest構造函數中多循環幾次就行:

1
2
3
4
5
//in FooTest constructor
for (int i = 0; i < 20; i++) {
    Ball* ball = new Ball(m_world, 1);
    balls.push_back( ball );
}

pic

既然我們在Ball類中考慮了球的大小變量,或許我們可以讓小球的大小也變成隨機的:

1
2
3
4
5
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 );
}

pic

啊哦…有不對勁的地方,小球之間的碰撞貌似不再正確。如果你想構造一個複雜一點場景,你需要考慮這一問題,然而你已經自信滿滿的讓debug draw在你花哨的圖形面前消失。但是既然我們手中有debug draw,而且它可以作爲第二方案來查看物理引擎到底發生了什麼:

pic

好吧,雖然再次使用debug draw讓人有些糾結,但是我只是想說明用debug draw能夠更有效的保證開發進度。最後,問題出在物理實體的大小上,所以我們只要調整一下圖形的縮放代碼就ok了:

1
2
3
4
5
6
7
//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();

pic


發佈了36 篇原創文章 · 獲贊 5 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章