[转载]android 游戏导引(3. 图形引擎之模型管理) – 圣斗士 – 博客园.
Android 游戏导引(3. 图形引擎之模型管理)
上一节中,我们构建了一个自己的场景世界。可以在内部绘制一些基本图元了。本来这一节要说说贴图的,想想还是休息下,放个小插曲,思考下模型的管 理,游戏引擎相关的东西。这些东西跟 cocos2d 很像,可能是 iphone 下常用 cocos2d 的缘故吧, 反正成熟且成功的东西,我们拿来用就行了。
源码下载: 点我吧
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 机器人
代码还是以前的,绘制图元,只不过经过本章的洗礼,代码封装性和扩展性上有了进步。一个机器人有下面的结构组成:
- 身体
- 头
- 脸
- 眼睛 * 2
- 天线
- 腿 * 2
- 胳膊 * 2
- 设定 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 ); |
} |
} |
} |
} |
好了,本次就唠叨到这里,学习愉快。