轉載文章:原貼地址: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);
}
|
讓我們調用我們的遊戲實體類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();
|
現在我們有了一個小球實體,但是它顯示在默認的(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);
}
|
好的,現在場景中有了一個物理實體,但是我們的笑臉並沒有被畫到物理實體的位置。爲了達到這個目的,讓我們爲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’勾選框),你自定義的渲染代碼仍然會渲染出一個笑臉小球,並顯示在正確的位置以及進行正確的旋轉。
爲了更加有趣,我們可以在小球以不同速度移動的時候改變其顏色。例如,像這樣設置顏色:
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 );
}
|
既然我們在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 );
}
|
啊哦…有不對勁的地方,小球之間的碰撞貌似不再正確。如果你想構造一個複雜一點場景,你需要考慮這一問題,然而你已經自信滿滿的讓debug draw在你花哨的圖形面前消失。但是既然我們手中有debug draw,而且它可以作爲第二方案來查看物理引擎到底發生了什麼:
好吧,雖然再次使用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();
|