20190108

看看这本书 -《Android游戏编程之从零开始》

Day_1 2019/01/08


开发之前需要熟悉

· View(视图)
· Canvas(画布)
· Paint(画笔)
· 刷屏的概念:对画布刷新来实现动态效果

视图的含义

· View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等
· SurfaceView: 基于View视图进行拓展的试图类,更适用于2D游戏开发
· GLSurfaceView:基于SurfaceView视图再次进行拓展的试图类,专用于3D游戏开发的视图

游戏框架:

1.View游戏框架
2.SurfaceView游戏框架


手机XY坐标

XY坐标.png

动态效果的实现方式

  1. 不断的绘制新的画布
  2. 使用一张画布,通过刷屏来让这张画布恢复到初始空白画布的状态,然后再向画布上进行绘制

View框架

public class MyView extends View
{
    
    private int textX = 20;
    private int textY = 20;
    
    /**
     * 重新父类构造函数
     */
    public MyView( Context context )
    {
        super( context );
        setFocusable( true );
    }
    
    /**
     * 重写父类绘图函数
     * 只执行一遍
     */
    @Override
    protected void onDraw( Canvas canvas )
    {
        //画笔
        Paint paint = new Paint();
        //设置颜色
        paint.setColor( Color.BLACK );
        //绘制文本
        canvas.drawText( "Game" , textX , textY , paint );
        super.onDraw( canvas );
    }
    
    /**
     * 重新父类按键按下事件函数
     * keyCode:用户点击的按键
     * event:按键的动作事件队列
     */
    @Override
    public boolean onKeyDown(
            int keyCode ,
            KeyEvent event )
    {
        if( keyCode == KeyEvent.KEYCODE_DPAD_UP )
        {
            //"上"按键被点击
            textY -= 2;
        }
        else if( keyCode == KeyEvent.KEYCODE_DPAD_DOWN )
        {
            //"下"按键被点击
            textY += 2;
        }
        else if( keyCode == KeyEvent.KEYCODE_DPAD_LEFT )
        {
            //"左"按键被点击
            textX -= 2;
        }
        else if( keyCode == KeyEvent.KEYCODE_DPAD_RIGHT )
        {
            //"右"按键被点击
            textX += 2;
        }
        
        //不能在当前线程中循环调用(子线程)
        invalidate();
        //可以在子线程中循环调用使用
        //postInvalidate();
        return super.onKeyDown( keyCode , event );
    }
    
    /**
     * 重新父类按键抬起事件函数
     */
    @Override
    public boolean onKeyUp(
            int keyCode ,
            KeyEvent event )
    {
        return super.onKeyUp( keyCode , event );
    }
    
    /**
     * 重新触屏事件函数
     */
    @Override
    public boolean onTouchEvent( MotionEvent event )
    {
        //int x = ( int )event.getX();
        //int y = ( int )event.getY();
        //if( event.getAction() == MotionEvent.ACTION_DOWN )
        //{
        //  //按下手指
        //  textX = x;
        //  textY = y;
        //}
        //else if( event.getAction() == MotionEvent.ACTION_MOVE )
        //{
        //  //屏幕移动
        //  textX = x;
        //  textY = y;
        //}
        //else if( event.getAction() == MotionEvent.ACTION_UP )
        //{
        //  //抬起手指
        //  textX = x;
        //  textY = y;
        //}
        //invalidate();
        //postInvalidate();
        
        textX = ( int )event.getX();
        textY = ( int )event.getY();
        invalidate();
        //postInvalidate();
        return true;
    }
}

SurfaceView框架

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback
{
    private int textX = 10;
    private int textY = 10;
    
    //用于控制SurfaceView
    private SurfaceHolder holder;
    private Paint paint;
    
    public MySurfaceView( Context context )
    {
        super( context );
        holder = this.getHolder();
        //添加监听状态
        holder.addCallback( this );
        paint = new Paint();
        paint.setColor( Color.WHITE );
    }
    
    //SurfaceHolder.Callback接口回调===================================
    
    /**
     * 当SurfaceView被创建完成后响应的函数
     */
    @Override
    public void surfaceCreated( SurfaceHolder surfaceHolder )
    {
        myDraw();
    }
    
    /**
     * 当SurfaceView状态发生改变时想要的函数
     */
    @Override
    public void surfaceChanged(
            SurfaceHolder surfaceHolder ,
            int i ,
            int i1 ,
            int i2 )
    {
        
    }
    
    /**
     * 当SurfaceView状态被摧毁时响应的函数
     */
    @Override
    public void surfaceDestroyed( SurfaceHolder surfaceHolder )
    {
        
    }
    //SurfaceHolder.Callback接口回调===================================
    
    /**
     * 自定义绘图函数
     * 不能使用onDraw来绘图
     */
    public void myDraw()
    {
        //加锁,防止绘制过程中被修改、摧毁
        //获取的都是同一张画布,多次调用时会出现绘之过的图形
        Canvas canvas = holder.lockCanvas();
        //刷屏方式1 绘制一个等同屏幕大小的图形覆盖在画布上
        canvas.drawRect( 0 , 0 , this.getWidth() , this.getHeight() , paint );
        //刷屏方式2 在画布上填充一种颜色
        canvas.drawColor( Color.BLACK );
        //刷屏方式3 指定RGB来填充
        canvas.drawRGB( 0 , 0 , 0 );
        //刷屏方式4 绘制一张等同于屏幕大小的图片覆盖在画布上-绘制背景图
        canvas.drawText( "Game" , textX , textY , paint );
        holder.unlockCanvasAndPost( canvas );
    }
    
    /**
     * 触屏监听
     */
    @Override
    public boolean onTouchEvent( MotionEvent event )
    {
        textX = ( int )event.getX();
        textY = ( int )event.getY();
        myDraw();
        //使用return super.onTouchEvent( event );会监听不到MOVE事件
        return true;
    }
}

在游戏中,基本不会等用户每次触发了按键事件、触屏事件才重新绘制画布,而是会固定一个时间去刷新画布。所以在游戏中,都会有一个线程不停的去重绘画布,实时的更新游戏元素的状态

· 点击“BACK”按钮使当前程序切入后台,然后单击项目回到程序中,SurfaceView的状态变化为:
surfaceDestroy->构造函数->surfaceCreated->surfaceChanged

· 然后单击“HOME”按钮使当前程序切入后台,单击项目重新回到程序中,SurfaceView的状态变化为:
surfaceDestroy->surfaceCreated->surfaceChanged

注意:千万不要把线程的初始化放在surfaceCreated视图创建函数之前,而线程的启动却放在surfaceCreated视图创建的函数中,否则程序一旦被玩家点击“HOME”按钮后再返回到游戏时,会抛出异常。

public class MySurfaceView extends SurfaceView
        implements SurfaceHolder.Callback, Runnable
{
    //用于控制SurfaceView
    private SurfaceHolder holder;
    private Paint paint;
    
    private int textX = 10;
    private int textY = 10;
    
    private Thread thread;
    //线程消亡的标识位
    private boolean flag;
    private Canvas canvas;
    //屏幕宽高
    private int screenW;
    private int screenH;
    
    public MySurfaceView( Context context )
    {
        super( context );
        holder = this.getHolder();
        //添加监听状态
        holder.addCallback( this );
        paint = new Paint();
        paint.setColor( Color.WHITE );
        setFocusable( true );
    }
    
    //SurfaceHolder.Callback接口回调===================================
    
    /**
     * 当SurfaceView被创建完成后响应的函数
     */
    @Override
    public void surfaceCreated( SurfaceHolder surfaceHolder )
    {
        //获取宽高一定要在视图创建之后才能获取到,在此之前获取到的都是0
        screenW = this.getWidth();
        screenH = this.getHeight();
        
        flag = true;
        thread = new Thread( this );
        thread.start();
    }
    
    /**
     * 当SurfaceView状态发生改变时想要的函数
     */
    @Override
    public void surfaceChanged(
            SurfaceHolder surfaceHolder ,
            int i ,
            int i1 ,
            int i2 )
    {
        
    }
    
    /**
     * 当SurfaceView状态被摧毁时响应的函数
     */
    @Override
    public void surfaceDestroyed( SurfaceHolder surfaceHolder )
    {
        flag = false;
    }
    //SurfaceHolder.Callback接口回调===================================
    
    /**
     * 自定义绘图函数
     * 不能使用onDraw来绘图
     */
    public void myDraw()
    {
        //加锁,防止绘制过程中被修改、摧毁
        //获取的都是同一张画布,多次调用时会出现绘之过的图形
        Canvas canvas = holder.lockCanvas();
        //刷屏方式1 绘制一个等同屏幕大小的图形覆盖在画布上
        canvas.drawRect( 0 , 0 , this.getWidth() , this.getHeight() , paint );
        //刷屏方式2 在画布上填充一种颜色
        canvas.drawColor( Color.BLACK );
        //刷屏方式3 指定RGB来填充
        canvas.drawRGB( 0 , 0 , 0 );
        //刷屏方式4 绘制一张等同于屏幕大小的图片覆盖在画布上-绘制背景图
        canvas.drawText( "Game" , textX , textY , paint );
        holder.unlockCanvasAndPost( canvas );
    }
    
    /**
     * 自定义绘图函数2
     * 不能使用onDraw来绘图
     */
    public void myDraw2()
    {
        /**
         * 使用try的原因:
         * 当SurfaceView不可编辑或尚未创建时
         * holder.lockCanvas();有可能会返回null
         */
        
        try
        {
            canvas = holder.lockCanvas();
            if( canvas != null )
            {
                //刷屏
                canvas.drawRGB( 0 , 0 , 0 );
                canvas.drawText( "Game" , textX , textY , paint );
            }
        }
        catch( Exception e )
        {
            //TODO:handle exception
        }
        finally
        {
            //保证在出错的情况下也能执行解锁画布
            if( canvas != null )
            {
                holder.unlockCanvasAndPost( canvas );
            }
        }
    }
    
    /**
     * 触屏监听
     */
    @Override
    public boolean onTouchEvent( MotionEvent event )
    {
        textX = ( int )event.getX();
        textY = ( int )event.getY();
        //myDraw();
        //使用return super.onTouchEvent( event );会监听不到MOVE事件return true;
    }
    
    /**
     * 按键事件监听
     */
    @Override
    public boolean onKeyDown(
            int keyCode ,
            KeyEvent event )
    {
        return super.onKeyDown( keyCode , event );
    }
    
    /**
     * 游戏逻辑
     */
    private void logic()
    {
        
    }
    
    @Override
    public void run()
    {
        while( flag )
        {
            long start = System.currentTimeMillis();
            myDraw2();
            logic();
            long end = System.currentTimeMillis();
            //刷帧时间保持一致
            //一般游戏中刷新时间在50-100毫秒之间,也就是10-20帧左右
            try
            {
                if( end - start < 50 )
                {
                    Thread.sleep( 50 - ( end - start ) );
                }
            }
            catch( InterruptedException e )
            {
                e.printStackTrace();
            }
        }
    }
}

View和SurfaceView的区别

  1. 更新画布
View:

是由系统主UI进行更新

postInvalidate()
invalidate()

有可能会出现UI线程被绘制函数阻塞而带来的一系列无法响应的问题

SurfaceView

单独线程去执行,不会出现因为UI线程阻塞而导致的无法响应按键、触屏等问题

2.视图机制

View:

没有双缓冲机制

SurfaceView:

有双缓冲机制,更适合的游戏开发的试图类

View和SurfaceView的选择

游戏画面的更新属于被动更新:依赖按键与触屏事件 - 使用View视图开发
例子:棋牌类游戏

很多元素都是动态的,需要不断的重绘元素状态 - 使用SurfaceView视图开发
例子:RPG、飞行射击类游戏

GitHub项目
156/406

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容