Oh!Coder

Coding Life

Box2D C++ 教程-开发环境设置(iPhone)

| Comments

声明:本文翻译自Box2D C++ tutorial-Setting up (iPhone),仅供学习参考。

简介

这篇文章针对在iOS上使用Box2D和OpenGL进行开发的环境设置问题。既然这跟环境设置话题中使用testbed框架不一样,那么我么也就让这个教程页单独分离出来,但是如果你现在的情况依然是我六个月前提到的情况,那么这篇文章依然对你有用:

  • 想用Box2D基于iOS开发物理类游戏
  • 比较熟悉C++,并且对于学习Obj-C没有顾虑
  • 想在你的游戏中使用OpenGL
  • 比起使用cocos2d来说,你更愿意使用自己的代码
  • 你从来都没有用过XCode(甚至是Mac)

本篇文章可以作为’绝对的新手’导读,引导大家在XCode工具上使用Box2D创建一个基本的iPhone项目,假设用户对于XCode和Obj-C一无所知。本次将会创建一个正在掉落盒子的场景作为教程的简单例子。

项目仅使用OpenGL ES 1.1,基于XCode 3.2.4以及Box2D 2.1.2.

让项目支持Box2D

pic
pic

本项目创建了OpenGL基本的运行场景,动画方面使用了timer。

pic

这里下载Box2D源代码,解压到项目文件夹下面或者其它任何你认为方面的地方。

pic

在项目的源代码目录下添加Box2D引擎。

pic

明确一点,引擎的源代码应该包括带有Box2D.h文件的文件夹。

pic

可以方便的创建包含子目录的目录。

pic

添加完之后可以在项目文件中很容易的进行浏览。(cmake文件不是必须的,你可以移除这些。)

pic

试着编译项目。你可能会得到一个找不到有效文件的错误提示,可以从苹果(Apple)的iOS开发者中心获取此文件,这里我们不打算帮你解决这个问题,你需要把编译类型改为模拟器(Simulator)然后再次编译。

pic
pic

呃…试着编译一下,结果可能会显示几千个Box2D的头文件找不到的错误。

pic

对于项目的设置…

pic

…双击’Header Search Paths’(设置search域的’header’为的是更容找到头文件)。

pic

指定Box2D.h头文件的目录。这么做的原因是为了能够像下面这样把Box2D文件包含进来:
#include <Box2D/Box2d,h>

pic

现在应该能够编译成功了。

为OpenGL视图添加Box2D世界

查看一下项目目录树中目录下的ViewContorller.m文件:

  • -(void) awakeFromNib
  • -(void) drawFrame
  • -(void) viewDidUnload

awakeFromNib

当视图加载的时候调用一次。任何只需要加载一次就可以完成的任务都可以放在这里。当前这里创建OpenGL上下文(context)和动画timer。

drawFrame

当程序运行的时候此方法会不停的重复调用以更新视图。帧速可以在startAnimation方法中进行调整。drawFrame方法作为运行循环的一部分,每次循环都会调用,所以不需要另起线程,你不用担心并行问题。

viewDidUnload

当视图关闭的时候此方法被调用,默认情况下项目永远都不会关闭。如果有一个需要加载和卸载场景的更复杂的应用,可以在这个方法中释放任何资源。目前,这个方法只是可怜巴巴的对OpenGL上下文(context)进行了释放。

现在看下目录下的ViewController.h文件

这个类包含OpenGL上下文和一些其它成员变量。具体包括了Box2D头文件并添加了Box2D 中的world指针作为类的成员变量:

1
2
3
4
#include <Box2D/Box2d.h>

//inside the class declaration (the @interface part)
b2World* m_world;

这将再次引起大量的错误,因为我们试着把C++代码放到了Obj-C的文件中所以不会顺利的运行。不管怎么说吧,这些错误非常神奇的在乎起把文件的扩展名从.m文件改为.mm-如果你想在XCode完成这个操作,可以在Group&Files(??)面板上方便的更改硬盘上的文件名称。你还必须更改目录下的AppDelegate.m文件,因为它包含了viewController头文件。.mm扩展名意味着可以让C/C++/Obj-C很容易的进行混合编译。

现在我们可以在awakeFromNib方法中添加Box2D世界:

1
m_world = new b2World( b2Vec2(0,-10), true );

在viewDidUnload进行删除:

1
 delete m_world;

使用debug draw 类渲染Box2D世界

既然我不打算使用OpenGL ES 2.0,在app中我也会尽量避免使用,这也不是本篇文章的目的。我经常在awakeFromNib方法的开始的地方进行修改,避免第一次就创建2.0上下文(context),创建1.1上下文很容易:

1
 EAGLContext aContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];

使用硬编码(bare-bones)来实现,代替drawFrame方法中原来的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[(EAGLView *)self.view setFramebuffer];

glClearColor(0, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

m_world->Step(1/60.0f, 8, 3);
m_world->DrawDebugData();

[(EAGLView *)self.view presentFramebuffer];

既然我们没有在世界中设置debug draw句柄(handler),那我们在这里调用DrawDebugData方法是无用的。如果你只要一个黑屏那正合适,但我想要再多做一点。为了让Box2D世界再OpenGL的视图进行展示,我们需要创建一个b2DebugDraw类的子类,然后用OpenGL ES的方式对其中的方法进行填充。

对于细节的部分可以看debug draw话题,不过,按说对于b2DebugDraw中的每个方法都应该像下面这样进行实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void DrawSolidPolygon(const b2Vec2* vertices, int32 vertexCount, const b2Color& color)
{
    //set up vertex array
    GLfloat glverts[16]; //allow for polygons up to 8 vertices
    glVertexPointer(2, GL_FLOAT, 0, glverts); //tell OpenGL where to find vertices
    glEnableClientState(GL_VERTEX_ARRAY); //use vertices in subsequent calls to glDrawArrays

    //fill in vertex positions as directed by Box2D
    for (int i = 0; i < vertexCount; i++) {
        glverts[i*2]   = vertices[i].x;
        glverts[i*2+1] = vertices[i].y;
    }

    glEnable(GL_BLEND);
    glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    //draw solid area
    glColor4f( color.r, color.g, color.b, 0.5f );
    glDrawArrays(GL_TRIANGLE_FAN, 0, vertexCount);

    //draw lines
    glColor4f( color.r, color.g, color.b, 1 );
    glDrawArrays(GL_LINE_LOOP, 0, vertexCount);
}

作为一个简单的例子而言,类中其它方法的实现就留给读者了。对于本话题中接下来的内容,请假设我们有了一个称为MyDebugDraw的类实现和头文件(既然类实现是用纯C++实现的,所以这里的扩展名可以为.cpp)。

扫尾

现在我们有了一个debug draw类使用,在viewController头文件中我们可以添加:

1
2
3
4
#include "MyDebugDraw.h"

//inside the class declaration (the @interface part)
MyDebugDraw m_debugDraw;

…在viewController实现文件(.mm)中随后我们可以创建b2World:

1
2
m_debugDraw.SetFlags( b2DebugDraw::e_shapeBit );
m_world->SetDebugDraw( &m_debugDraw );

我擦依然是黑屏!呃,世界现在还是空的?让我们往里添加一些物体(这一步没什么特别的,添加任何你希望看到的东西进去):

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
27
28
29
30
//in awakeFromNib after creating world
{
    //body definition
    b2BodyDef myBodyDef;
    myBodyDef.type = b2_dynamicBody;

    //shape definition
    b2PolygonShape polygonShape;
    polygonShape.SetAsBox(1, 1); //a 2x2 rectangle

    //fixture definition
    b2FixtureDef myFixtureDef;
    myFixtureDef.shape = &polygonShape;
    myFixtureDef.density = 1;

    //create dynamic bodies
    for (int i = 0; i < 10; i++) {
        myBodyDef.position.Set(0, 10);
        m_world->CreateBody(&myBodyDef)->CreateFixture(&myFixtureDef);
    }

    //a static body
    myBodyDef.type = b2_staticBody;
    myBodyDef.position.Set(0, 0);
    b2Body* staticBody = m_world->CreateBody(&myBodyDef);

    //add a fixture to the static body
    polygonShape.SetAsBox( 10, 1, b2Vec2(0, 0), 0);
    staticBody->CreateFixture(&myFixtureDef);
}

我擦现在出现了一个绿屏!呃,这显示了世界的哪部分呢?你可以在调用DrawDebugData之前添加如下两行代码:

1
2
glViewport(0, 0, 320, 480);
glOrthof(-16, 16, -8, 40, -1, 1);

添加完之后你可以看到类似的屏幕…

pic

源代码

你可以从这里下载针对于XCode项目的源代码。

Comments