20190108

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

Day_1 2019/01/08


开发之前需要熟悉

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

视图的含义

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

游戏框架:

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


手机XY坐标

20190108_第1张图片
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

你可能感兴趣的:(20190108)