android 遊戲導引(3. 圖形引擎之模型管理)
上一節中,我們構建了一個自己的情境世界。可以在內部繪製一些基本圖元了。本來這一節要說說貼圖的,想想還是休息下,放個小插曲,思考下模型的管理,遊戲引擎相關的東西。這些東西跟 cocos2d 很像,可能是 iphone 下常用 cocos2d 的緣故吧, 反正成熟且成功的東西,我們拿來用就行了。
源碼下載: 點我吧
Table of Contents
- 1 物件導向吧
- 2 模型父子鏈:樹
- 3 層式螢幕管理
- 4 遊戲引擎
- 5 代碼實現
- 6 繪製一個 android 機器人
1 物件導向吧
基於教程到這裡的進度,我們要會議幾個圖元模型的話,就要顯式的在 onDrawFrame 中繪製。基於物件導向的思想,我們知道我們繪製的模型僅僅是一個渲染單位而已,並且我們在 onDrawFrame 的操作只是一個opengl 訪問。 因此基於介面,我們的一個渲染模型類就誕生了。
2 模型父子鏈:樹
你也許會說,上面要渲染的模型太多的時候太散亂了。想到這裡,說明你還是一個合格的程式員,有著對完美的執著。與其手動的管理,不如讓程式自己管理。大家想想,如果是一個複雜的模型,比如一個人,它是有眾多細小的模型構成的,比如腦袋,胳膊,腿等, 而腦袋又有鼻子,耳朵,嘴巴等構成。哈哈,這就是一個樹形的結構了,看下結構特徵:
- 一個父節點有0個或多個子節點
- 一個位元組點最多有一個父節點
一個對象的父子關係如下:
現在遊戲多採用樹形模型管理,而且在 GUI 系統中也大放異彩。有了樹形管理,我們可以輕易的將一種類型的鼻子安裝到不同人的腦袋上,不用重複發明輪子了。
好了,現在我們的 onDrawFrame 中的形式是這樣的了,右邊的模型對象是一個封裝好的單位,可能內部許多子節點。
3 層式螢幕管理
我們最終是要把模型繪製到螢幕上,我們思考下面問題:
- 螢幕上有眾多模型需要渲染。比如一個人在草原中散步。概括講人模型,還有草原,太陽,河流等等。
- 螢幕上的模型又可以歸為不同的類別。如草原,太陽,河流這些是背景,我們控制的人是前景。
於是,層式管理來了,我們的渲染根結點稱為情境 Scene, 分為不同的層 Layer, 在每一層上掛接著具體的模型。看就明白了,(注:來自cocos2d-python 文檔)
為了向渲染 onDrawFrame 突出介面,Scene 和 Layer 也是一個渲染模型單位。
看看我們添加層式螢幕管理後的效果, 我們將所有要繪製的東西添加到一個情境中,就直接對這個情境節點 glVisit() 就可以了:
每個模型突出一個 glVisit(),這個函數的主要功能是調用自身繪製函數 draw() 然後對各個位元組點逐個調用 glVisit().
4 遊戲引擎
一個遊戲引擎的設計遵循的原則是: 邏輯要和渲染分離,盡量將opengl 最小程度封裝在底層,上層使用盡量不要觸及底層api,盡量讓核心代碼原理android系統的api。組件最小集合應該包含下面幾種:
- 圖形引擎,我們上述討論的就是
- 事件指派系統,手機應用是基於觸摸事件的,所以獨立出來(事實上,也是一個調度器)
- 時間調度器,遊戲除了觸摸時間還有自身的時間事件,如動畫
其他的可能用到的組件有碰撞檢測系統,輸入系統,尋路演算法等等。
遊戲引擎的豐富會在以後的教程中逐步增設,今天這一節算是個開頭,弄了個簡單的圖形引擎,也會在接下來隨著需求的需要豐富其功能和介面。為了便於從 android 相關代碼中解脫出來,添加一個 GameSystem 單件類:
public class GameSystem { private static GameSystem instance_ = new GameSystem(); public static GameSystem getInstance() { return instance_; } // 情境 private GlObject runningScene_= null; private int width = 0; // opengl的情境尺寸 private int height = 0; public void setWindowSize(int width, int height) { this.width = width; this.height = height; } public int getWindowWidth() { return this.width; } public int getWindowHeight() { return this.height; } // 設定情境 public void setScene(GlObject scene){ runningScene_ = scene; } // public void glVisit(GL10 gl) { if (runningScene_ != null) runningScene_.glVisit(gl); } }
我們將其插入到 android 代碼的三個地方:
- Activity 的 onCreate 方法中,設定WindowSize: setWindowSize. 設定初始情境 setScene
- Renderer 的 onDrawFrame 方法中,來訪問GameSystem 的遊戲情境 glVisit
5 代碼實現
在螢幕中除了上面的介面以外,增加的是座標點,儲存的是相對座標,相對於父節點而言,用於描述模型的位置。這個座標點不要理解為頂點數組中的頂點座標。 可以這樣理解,你將畫筆移動到座標點處,然後在此處依據頂點數組來繪製。代碼較長,省略了一些,可以下載源碼。
public class GlObject { protected GlObject parent = null; // 父節點 protected LinkedList<GlObject> children = new LinkedList<GlObject>();// 位元組點 private boolean visible = true; // 可訪問(對 gl) float x = 0; float y = 0; // 父子鏈管理 public void addChild(GlObject obj) { children.add(obj); obj.parent = this; } public void removeChild(GlObject obj) { obj.parent = null; children.remove(obj); } public void setParent(GlObject p) { if (parent != null) parent.removeChild(this); p.addChild(p); } public GlObject getParent() { return parent; } // 座標 float getX() { return x; } float getY() { return y; } void setXY(float x, float y) { this.x = x; this.y = y; } // visible public void setVisible(boolean v) { visible = true; } public boolean getVisible() { return visible; } // 外部gl提供者 public void glVisit(GL10 gl) { if (!visible) return; gl.glPushMatrix(); gl.glTranslatef(x, y, 0); this.draw(gl); for (GlObject child : children) child.glVisit(gl); gl.glPopMatrix(); } // 繪製自身:子類重寫此方法 public void draw(GL10 gl) {} //////////////////////////////////////////////////////////////////////////////////////// //// 一些常用的圖元繪製 // 繪製正方形 public static void drawQuater(GL10 gl, float left, float top, float right,float bottom) { // 略 } // 繪製三角形 public static void drawTriangle(GL10 gl, float oneX, float oneY,float twoX, float twoY, float threeX, float threeY) { // 略 } // 繪製扇形 public static void drawArc(GL10 gl, float length, float startAngle,float sweepAngle) { // 略 } // 繪製直線 public static void drawLine(GL10 gl, float oneX, float oneY, float twoX,float twoY) { // 略 }}
6 繪製一個 android 機器人
代碼還是以前的,繪製圖元,只不過經過本章的洗禮,代碼封裝性和擴充性上有了進步。一個機器人有下面的結構組成:
- 設定 GameSystem 的初始遊戲情境:
public class GlGame extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { // 。。。。略 // 初始化遊戲系統: // ... 螢幕大小 { DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); GameSystem.getInstance().setWindowSize(dm.widthPixels, dm.heightPixels); } // ...設定初始情境 GameSystem.getInstance().setScene(new AndroidScene()); //..... setContentView 代碼 }}
2. 添加一個情境類 AndroidScene 作為我們的畫布,我們將會在其上繪製機器人
public class AndroidScene extends GlObject{ AndroidRobot robot = new AndroidRobot(); public AndroidScene(){ super(); // 在螢幕中心繪製 robot.setXY(GameSystem.getInstance().getWindowWidth()/2, GameSystem.getInstance().getWindowHeight()/2); addChild(robot); }}
3. 添加一個機器人渲染模型 AndroidRobot, 代碼太長, 摺疊之:
public class AndroidRobot extends GlObject{ GlColor color = new GlColor(); //一個封裝著 rgba 四元素的簡單類 public AndroidRobot() { super(); // 顏色為綠色 color.set(0,1,0,0); //body // 2 arms // 2 leg // head: 2 eyes, 2 line, face Arm arms[] = new Arm[2]; Leg legs[] = new Leg[2]; Head head; Body body; // 構建身體部位 arms[0] = new Arm(); arms[1] = new Arm(); legs[0] = new Leg(); legs[1] = new Leg(); head = new Head(); body = new Body(); // 調整身體組件位置 body.setXY(0, 0); // 身體為中心 head.setXY(0, -65); arms[0].setXY(-55,0); arms[1].setXY(55, 0); legs[0].setXY(-20, 60); legs[1].setXY(20, 60); // 安裝身體部位 addChild(body); addChild(head); addChild(arms[0]); addChild(arms[1]); addChild(legs[0]); addChild(legs[1]); } public void draw(GL10 gl) { // 設定顏色為 gl.glColor4f(color.red, color.green, color.blue, color.alpha); } // 胳膊 class Arm extends GlObject{ public void draw(GL10 gl) { drawQuater(gl, -10, -68, 10, 50); } } // 腿 class Leg extends GlObject{ public void draw(GL10 gl) { drawQuater(gl, -15,0,15, 40); } } // 身體 class Body extends GlObject{ public void draw(GL10 gl) { drawQuater(gl, -40, -60, 40, 60); } } //頭 class Head extends GlObject{ public Head() { Eye eyes[] = new Eye[2]; eyes[0] = new Eye(); eyes[1] = new Eye(); eyes[0].setXY(-20, -10); eyes[1].setXY(20, -10); addChild(new Face()); addChild(new Antenna()); addChild(eyes[0]); addChild(eyes[1]); } // 眼睛 class Eye extends GlObject{ public void draw(GL10 gl){ //眼睛扣洞 gl.glColor4f(0, 0, 0, 0); drawQuater(gl, -4, -4, 4,4); //還原顏色 gl.glColor4f(color.red, color.green, color.blue, color.alpha); } } // 臉 class Face extends GlObject{ public void draw(GL10 gl){ drawArc(gl, 40, 0, 180); } } // 天線 class Antenna extends GlObject{ public void draw(GL10 gl){ drawLine(gl,0,0, -50,-50); drawLine(gl, 0, 0, 50, -50); } } }}
好了,本次就嘮叨到這裡,學習愉快。