• android 游戏导引(3. 图形引擎之模型管理)


    android 游戏导引(3. 图形引擎之模型管理)

    上一节中,我们构建了一个自己的场景世界。可以在内部绘制一些基本图元了。本来这一节要说说贴图的,想想还是休息下,放个小插曲,思考下模型的管理,游戏引擎相关的东西。这些东西跟 cocos2d 很像,可能是 iphone 下常用 cocos2d 的缘故吧, 反正成熟且成功的东西,我们拿来用就行了。

    源码下载: 点我吧

    1 面向对象吧

    基于教程到这里的进度,我们要会议几个图元模型的话,就要显式的在 onDrawFrame 中绘制。基于面向对象的思想,我们知道我们绘制的模型仅仅是一个渲染单位而已,并且我们在 onDrawFrame 的操作只是一个opengl 访问。 因此基于接口,我们的一个渲染模型类就诞生了。

    2 模型父子链:树

    你也许会说,上面要渲染的模型太多的时候太散乱了。想到这里,说明你还是一个合格的程序员,有着对完美的执着。与其手动的管理,不如让程序自己管理。大家想想,如果是一个复杂的模型,比如一个人,它是有众多细小的模型构成的,比如脑袋,胳膊,腿等, 而脑袋又有鼻子,耳朵,嘴巴等构成。哈哈,这就是一个树形的结构了,看下结构特征:

    • 一个父节点有0个或多个子节点
    • 一个字节点最多有一个父节点

    一个对象的父子关系如下:

    现在游戏多采用树形模型管理,而且在 GUI 系统中也大放异彩。有了树形管理,我们可以轻易的将一种类型的鼻子安装到不同人的脑袋上,不用重复发明轮子了。

    好了,现在我们的 onDrawFrame 中的形式是这样的了,右边的模型对象是一个封装好的单位,可能内部许多子节点。

    3 层式屏幕管理

    我们最终是要把模型绘制到屏幕上,我们思考下面问题:

    1. 屏幕上有众多模型需要渲染。比如一个人在草原中散步。概括讲人模型,还有草原,太阳,河流等等。
    2. 屏幕上的模型又可以归为不同的类别。如草原,太阳,河流这些是背景,我们控制的人是前景。

    于是,层式管理来了,我们的渲染根结点称为场景 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 代码的三个地方:

    1. Activity 的 onCreate 方法中,设定WindowSize: setWindowSize. 设定初始场景 setScene
    2. 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
    1. 设定 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);
                }
            }
        }
    }
    
    

    好了,本次就唠叨到这里,学习愉快。

  • 相关阅读:
    IIS主机托管的FSO设置用户权限问题
    关于使用UTF8开发ASP网站
    构建Android开发环境
    iOS如何取得APP的版本信息跟服务器对比进行升级提示?
    经典讲解VB.NET线程方法之访问数据库
    IIS7.0下ASP+Access(MDB)应用环境设置要点
    一文明白数据库事务隔离级别
    EA鼻祖,Zachman,6 行(视点)+ 6 列(W5H)+ 6 条规则
    C语言位运算详解
    关于while 和if
  • 原文地址:https://www.cnblogs.com/shengdoushi/p/1924322.html
Copyright © 2020-2023  润新知