openGL ES game development: the game grow like this
遊戲是這樣寫成的 (第一篇: 建立 OpenGL ES 項目)
看了些大家在論壇的討論, 似乎有不少同學有意用 OpenGL ES 寫遊戲, 但又好像不知道從那方面入手, 所以我決定為 cocoachina.com 寫幾篇教程, 和大家分享一下我在這方面的經驗, 希望對大家有點幫助, 也順便推廣一下 OpenGL ES 的應用!
不過大家要注意的是, 這個教程所想包含的, 只是OpenGL ES 的入門和怎麼利用它寫 2D 遊戲, 至於本教程標題, 只是嘩眾取巧之作, 不能期望有什麼高深的學問呢
.
讓我們轉入正題吧!在官方 SDK 建立一個 OpenGL ES 的項目非常簡單, 只要選擇”New Project”, 然後用” Cocoa Touch OpenGL Application” 的模版就可以了:

這時只要我們”Build and Go”, 就會看到一個旋轉的彩色四方形.

這時我們會發現螢幕上面有一個不大好看的Status Bar, 要把它弄掉,可以在info.plist 里加上下面這句:

有了基本的東西, 接下來怎麼做呢? 非常幸運的是SDK 提供了不少例子可供我們利用, 我們下載和打開 CrashLanding 看看, 項目里有幾個檔案我們可以拿來借用一下 :

我們把 Texture2D.h, Texture2D.m, OpenGL_Internal.h 拖拉到我們的 Classes 里 (記得選”Copy items…”) :

Texture2D非常好用,我們不只可以用它載入貼圖, 更可以用它把貼圖畫上螢幕當為sprite 用, 這個不正是我們寫遊戲所需要的基本功能嗎? 爽!
但在沒編譯前,我們還有一件事要做:把 CoreGraphics.framework 加到我們的 Frameworks !

跟著我們要把 OpenGL ES 的起始代碼改一下, 讓我們可以寫2D 遊戲, 這部份,我們可以在 layoutSubviews 里做:
Quote:
- (void)layoutSubviews
{
[EAGLContext setCurrentContext:context];
[self destroyFramebuffer];
[self createFramebuffer];glViewport(0, 0, backingWidth, backingHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrthof(0, (float)backingWidth, 0, (float)backingHeight, 0, 100);glMatrixMode(GL_MODELVIEW);
glLoadIdentity();glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);glEnableClientState (GL_VERTEX_ARRAY);
glEnableClientState (GL_TEXTURE_COORD_ARRAY);glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
[self drawView];
}
好了, 做了這麼多準備工作, 我們終於來到本篇最精彩的部份: 在螢幕上畫圖! 我們把想要用到的圖拖拉到 Resources 里,就可以在程序里把它們載入應用:
Quote:
bg = [[Texture2D alloc] initWithImagePath:@”bg.jpg”];
sprites = [[Texture2D alloc] initWithImagePath:@”planet.png”];
這樣,我們在 drawView里就可以把圖畫在螢幕上了!
Quote:
[bg drawAtPoint: CGPointMake(160.0f, 240.0f)];
[sprites drawAtPoint: CGPointMake(160.0f, 100.0f)];
看看我們的成果!很漂亮的畫面啊!呵呵。。。這篇就寫到這里,有時間再繼續寫下一篇。

注意:項目要用SDK Beta6編譯!
附件:
iDemo.zip (96 K)
遊戲是這樣寫成的 (第二篇: 混合 C/C++/Obj-C 的應用)
在第一篇我們弄好了一個 OpenGL ES 框架, 接下來我們可以再進一步為寫遊戲作準備了, 這時, 我們有一個問題要好好考慮一下: 到底我們想以 Obj-C 開發還是以 C/C++ 開發呢? 個人來說, 我還是比較偏向 C/C++, 一來比較熟, 二來要是借用別人遊戲方面的代碼, 也比較容易找到! 所以在本篇, 我會和大家介紹一下怎麼混合 C/C++ 和 Obj-C, 並編寫一個 C++ 的 sprite class, 方便以後在遊戲里應用!
我們首先要做的第一件事, 是打開第一篇的示範工程,並把檔案的點綴名由 .m 改為 .mm, 這樣, 我們就可以在代碼里, 隨意引用 C++ 的 class了.
好了, 我們現在可以開始弄我們的 Sprite class 了, 讓我們把它叫做 CCSprite 吧 (CC 就是代表cocoachina),class 的結構如下:
Copy code
class CCSprite
{
public:
CCSprite(GLuint texId, float width, float height, float texWidth, float texHeight);
~CCSprite();void render(float x, float y);
private:
GLuint mTextureId;
float mImageWidth;
float mImageHeight;
float mTextureWidth;
float mTextureHeight;};
加 好了 CCSprite.h 和 CCSprite.cpp, 基本上, 我們可以抄襲 Texture2D, 把它的功能真接搬到 CCSprite! 至於 Texture2D 的載入貼圖功能, 我可看不懂它里面那一大堆的代碼, 也不知怎麼搬到 C++, 我們直接用它好了! OpenGL ES 來說,我們只要拿到 texture id 就可以畫圖. 於是把試試把 Texture2D.h 放進 CCSprite.cpp:
Copy code
#include “Texture2D.h”
編 譯一下,天!!! 1995 個錯!有沒有搞錯!看來在 C++里引用 Obj-C的東西是有點問題,還好,反過來在 Obj-C 里引用 C++ 的東西,就沒有問題! 所以我們先要弄一些封裝的代碼,讓我們可以在 C++ 里間接的用 Obj-C 的東西. 於是我們有了 Wrapper.h 和 Wrapper.mm, 在 Wrapper.mm 里引用和生成 CCSprite 就可以了!
Copy code
CCSprite *CCSpriteCreate(const char *filename)
{NSString *name = [[NSString alloc] initWithUTF8String: filename];
Texture2D *tex = [[Texture2D alloc] initWithImagePath:name];CCSprite *sprite = new CCSprite([tex name], tex.contentSize.width, tex.contentSize.height, tex.pixelsWide, tex.pixelsHigh);
[tex release];
return sprite;
}
這里有一點注意的是,Texture2D 被釋放時會同時 texture 釋放掉,我們要把有關代碼拿走:
Copy code
- (void) dealloc
{
//if(_name)
//glDeleteTextures(1, &_name);[super dealloc];
}
接下來,我們就可以把畫圖部份, 抄到我們自己的class 里, 有了這些, 我們就弄好我們的CCSprite class 了:
Copy code
void CCSprite::render(float x, float y)
{
GLfloat _maxS = mImageWidth/mTextureWidth;
GLfloat _maxT = mImageHeight/mTextureHeight;GLfloat coordinates[] =
{
0, _maxT,
_maxS, _maxT,
0, 0,
_maxS, 0
};GLfloat width = mImageWidth;
GLfloat height = mImageHeight;GLfloat vertices[] =
{
-width / 2 + x, -height / 2 + y, 0,
width / 2 + x, -height / 2 + y, 0,
-width / 2 + x, height / 2 + y, 0,
width / 2 + x, height / 2 + y, 0
};glBindTexture(GL_TEXTURE_2D, mTextureId);
glVertexPointer(3, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
我們再改一下 EGALView 里截入貼圖和畫圖的部份,哈,大功告成!

附件:
iDemo_2.zip
遊戲是這樣寫成的 (第三篇: 簡單的遊戲框架)


通過上一篇, 我們已有個基本的畫圖功能, 這次讓我們弄一個簡單的遊戲框架吧!
其實一般的遊戲, 大至有兩個主要的函數就可以了: update 和 render!
我們在 update 里更新遊戲數據, 然後在render 里把遊戲的畫面畫出來。我們來定議一個叫 CCGameApp 的 class 吧:
Copy code
class CCGameApp
{
public:
CCGameApp();
~CCGameApp();void update(float dt);
void render();
大 家看到我們的 update 函數, 有一個 dt 參數, 在這里我約為解說一下: 遊戲的更新率, 最理想是我們把它設成固定的,比如每秒60幀,但有時可能某些原因,遊戲沒法達到這個更新率,那怎麼辦呢?總不能讓角色忽快忽慢的移動吧!而這個 dt 參數是從上一幀到現在這幀,共用去了多少時間(秒),我們可以利用它調整遊戲里的數據,比如說,主角一秒移動1個像素,現在過了dt 這麼多秒,那主角應該移動 (1*dt) 個像素了!
這次,我們也把 CCSprite 一分為二,弄多了一個 CCTexture,目的是讓不同的 CCSprite 可以共享一個貼圖,一般來說,為了節省空間和速度考慮,我們會把數個小圖,放在一個大的貼圖里,像上面第一個圖,里面就有兩個角色的 圖,那我們建立兩個不同的sprites時,可以用同一個貼圖!
而建立 CCSprite的參數,就是小圖在大貼圖里的起始位置和大小:
Copy code
CCSprite(CCTexture *texture, float x, float y, float width, float height);
CCSprite 的 render 也多加了一個角度的參數,我們可以用它把角色轉來轉去了!
Copy code
void render(float x, float y, float angle=0.0f);
因為時間關係不寫太多了,大家有興趣,可以看一下代碼作為參考。
附件:
iDemo_3.zip (106 K)
遊戲是這樣寫成的 (第四篇: 縮放和混色)

我也用了 SDK Final 的模塊,重新建立了一次項目。
這次的修改,主要是 CCSprite 的 render, 大家可以參考一下,怎麼用glScalef 來作縮放。
Copy code
void CCSprite::render(float x, float y, float angle, float xScale, float yScale)
{
y = SCREEN_HEIGHT-y; // for OpenGL ES, (0,0) is at lower left corner!GLfloat _minU = mX/mTexture->getTextureWidth();
GLfloat _maxU = (mX+mWidth)/mTexture->getTextureWidth();
GLfloat _minV = mY/mTexture->getTextureHeight();
GLfloat _maxV = (mY+mHeight)/mTexture->getTextureHeight();GLfloat coordinates[] =
{
_minU, _maxV,
_maxU, _maxV,
_minU, _minV,
_maxU, _minV
};GLfloat xx = – mWidth/2;
GLfloat yy = – mHeight/2;GLfloat vertices[] =
{
xx, yy,
xx+mWidth, yy,
xx, yy+mHeight,
xx+mWidth, yy+mHeight
};mTexture->bind();
glColor4f(mRed, mGreen, mBlue, mAlpha);
glPushMatrix();
glTranslatef(x, y, 0.0f);
glRotatef(angle, 0.0f, 0.0f, 1.0f);
glScalef(xScale, yScale, 1.0f);
glVertexPointer(2, GL_FLOAT, 0, vertices);
glTexCoordPointer(2, GL_FLOAT, 0, coordinates);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glPopMatrix();glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
附件:
iDemo_4.zip (100 K)
遊戲是這樣寫成的 (第五篇: MD2模型顯示)


Seven 同學在論譠討論怎樣顯示MD2, 正好我以前在PSP 上有個MD2的類,我把它移植了過來,給大家參考一下或拿來玩玩。
在Wrapper 里,我也加了兩個新功能,一個是 Enable2D(),一個是 Enable3D(),方便我們混合2D/3D。
Copy code
void Enable2D()
{
int width = SCREEN_WIDTH;
int height = SCREEN_HEIGHT;glViewport (0, 0, width, height);
glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
glOrthof(0, (float)width, 0, (float)height, 0, 100);
glMatrixMode (GL_MODELVIEW);
glLoadIdentity();glEnable (GL_BLEND);
glEnable (GL_TEXTURE_2D);
glDisable (GL_CULL_FACE);
glDisable (GL_DEPTH_TEST);
glDisable (GL_LIGHTING);
//glDisableClientState (GL_NORMAL_ARRAY);}
void Enable3D()
{
int width = SCREEN_WIDTH;
int height = SCREEN_HEIGHT;
float aspect = (float)width/(float)height;glViewport (0, 0, width, height);
glScissor (0, 0, width, height);//glMatrixMode (GL_MODELVIEW);
//glLoadIdentity ();glMatrixMode (GL_PROJECTION);
glLoadIdentity ();
InitPerspective (60.f, aspect, 0.1f, 1000.f);glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();glEnable (GL_CULL_FACE);
//glDisable (GL_BLEND);
glEnable (GL_DEPTH_TEST);
//glEnable (GL_LIGHTING);
//glEnableClientState (GL_NORMAL_ARRAY);//glEnable(GL_NORMALIZE);
}
因為我們沒有用lighting,我把Normal Array 暫時弄走了。
這個例子,每格8秒會換到另一個動作。人的模型是732 面,槍是102面。
附件:
iDemo_5.zip (497 K)
遊戲是這樣寫成的 (第六篇: 顯示文字)

iPhone 系統里已有不少字體, 我們理論上可以用NSString 來畫字:
复制代码
|
但因為速度關係和不想以系統的東西混合了OpenGL ES一起運用, 我們還得另外想想辦法.
如 果大家有詳細的看過Texture2D 的代碼, 會發現一個 initWithString 的生成貼圖的方法, 它的功能就是把一段文字, 畫在一張貼圖上, 我們跟著就可以應用這個貼圖在自己的遊戲中. 一般不用太多文字的遊戲, 這個方法基本就可以解決了我們的所需.
當然為了比較有彈性, 我們應該搞一個font 類, 讓我們可以任意的顯示每個英文字母! 有了這個想法, 於是就有了這次帶給大家的 CCFont!
在 Texture2D里新加了一個 makeASCIIBitmapFont 的功能, 它把由 ASCII 碼32 ~ 127 共95個字母, 都畫到一張貼圖上(見截圖下方), 而CCFont 就會根據這張貼圖, 生成95個 CCSprite 來對應每個字母, 接著我們就可以利用這個 CCSprite 陣矩來顯示字串了!
void CCFont::drawString(const char *text, float x, float y) { char ch; int idx; while ((ch = *text++) != 0) { idx = ch - 32; mCharacters[idx]->render(x, y); x += mCharWidthArray[idx]; } } |
CCSprite 也新加了setCenter, 大家現在可以任意設定CCSprite 的中心點!
附件:
iDemo_6.rar (441 K)
遊戲是這樣寫成的 (第七篇: 偷來的粒子系統)
這次為大家介紹的, 正是我從一個windows 的開源遊戲引擎 HGE 上借來的粒子系統. (HGE 網站: http://hge.relishgames.com/)
代碼要改動的地方其實不多, 主要載入粒子定義和渲染部份!

附件之一是windows粒子編輯器, 大家可以用它來制作不同的粒子效果!
iDemo7.rar (64 K)
hge_particleed.rar (258 K)遊戲是這樣寫成的 (第八篇: CCGameBox 0.1a)
安裝方法:
- 利用cnsoft 提供的下截和安裝腳本:http://www.cocoachina.com/bbs/read.php?tid-2889.html
- 下截本貼附件, 自行還原到XCODE 有關目錄.
—————————————————————————
大家先看一下這個截圖, 有什麼特別的地方嗎? 答案…

答案是: 沒有! 
表面上它的確跟最早幾篇教程的截圖沒什麼分別, 但看不到的地方: 程序底層, 郤有了非常大的改變!
寫 了幾篇基本的東西, 我想大家對於在 iPhone 上運用OpenGL ES 己有了一些認識, 現在也是時候來個小結了! 我把之前的東西整理了一下, 也把整個結構改變了, 為打造一個比較正規的2D 遊戲程序庫作好準備! 因為過於簡陋, 不敢叫它作引擎, 我就稱之為遊戲盒子吧!
正式向大家介紹: CCGameBox 0.1a 
(請大家記住, CC stands for CocoaChina !!!)
這是 CCGameBox 第一版, 現在只包括了圖形渲染部份.
類的列表:
- CCRenderBox : 包括所有和圖像方面有關的功能, 如截入貼圖, 畫圖等.
- CCTexture: 貼圖類.
- CCImage: 可以被渲染的基本圖像類.
這是一個 OpenGL ES 庫, 所以一般都會用在用 Cocoa Touch 模块建出來的項目, 利用 CCGameBox 編程, 首先我們要引進 CCGameBox.h :
复制代码
|
在初始化了OpenGL ES 之後, 我們要用 CCGameBoxInit() 來初始化一下我們的盒子:
复制代码
|
接著在render 循環里, 我們就可以很方便的以 CCGameBox 的 singleton 物件大展拳腳了!
复制代码
|
最後在我們的程序結束時, 用 CCGameBoxDestroy() 移除 CCGameBox !
复制代码
|
當然, 大家要是懶得理會這些亂七八糟的初始工作, 可以直接更改 GameApp.cpp 和 GameApp.h 來實現自己的東西, 比如寫個遊戲! (其實還要等一等, 還有不少功能沒加呢…)
早 期的CCSprite 沒有優化, 每渲染一次, 都會更換一次貼圖和調用一次 glDrawArrays, 要是有大量的 CCSprite 要渲染, 速度就會很慢! CCRenderBox 在這方面作出了優化, 貼圖只在有需要時才更換, 而另外也加了一個頂點緩存, 集合了一堆才一次性渲染至螢幕!
有時間我會慢慢把其他功能加到 CCGameBox , 有興趣的朋友請留意這個貼子的更新哦!
iDemo8.zip (128 K)
iDemo8b.zip (461 K)
iDemo8c.zip (693 K)
GameBoxDemo3.zip (562 K)
GameBoxDemo4.rar (488 K)
Sidewalk_with_example.rar (1687 K)
GameBoxDemo5.rar (515 K)