android 游戏导引(3. 图形引擎之模型管理)
上一节中,我们构建了一个自己的场景世界。可以在内部绘制一些基本图元了。本来这一节要说说贴图的,想想还是休息下,放个小插曲,思考下模型的管理,游戏引擎相关的东西。这些东西跟 cocos2d 很像,可能是 iphone 下常用 cocos2d 的缘故吧, 反正成熟且成功的东西,我们拿来用就行了。
源码下载: 点我吧
Table of Contents
- 1 面向对象吧
- 2 模型父子链:树
- 3 层式屏幕管理
- 4 游戏引擎
- 5 代码实现
- 6 绘制一个 android 机器人
1 面向对象吧
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, 代码太长, 折叠之:
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);
}
}
}
}
好了,本次就唠叨到这里,学习愉快。
