【转】Android开发之游戏中的常用技巧

来源:互联网 发布:淘宝巴克110 编辑:程序博客网 时间:2024/06/09 15:39

一、设置全屏以及绘画简单的图形

    package cn.szl;  

    import android.app.Activity;  

    import android.os.Bundle;  

    import android.view.Window;  

    import android.view.WindowManager;  

    public class MainActivity extends Activity {  

        /** Called when the activity is first created. */  

         @Override  

         public void onCreate(Bundle savedInstanceState) {  

             super.onCreate(savedInstanceState);  

             this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);  

             //隐去电池等图标和一切修饰部分(状态栏部分)   

             this.requestWindowFeature(Window.FEATURE_NO_TITLE);  

             // 隐去标题栏(程序的名字)  

             setContentView(new MyView(this));   

         }  

     }  

 注意: 隐去标题(应用的名字) 此设定必须要写在setContentView之前,否则会有异常! 

对于设置全屏,主要就两点:

 一点是设置隐去状态栏部分,包括电池等图标,第二点无疑就是把我们应用的名字也隐去不显示,这样一来就全屏了。

    package cn.szl;  

    import android.content.Context;  

    import android.graphics.Canvas;  

    import android.graphics.Color;  

    import android.graphics.Paint;  

    import android.graphics.Rect;  

    import android.graphics.RectF;  

    import android.view.View;  

    public class MyView extends View {  

         private Paint paint ;  

         public MyView(Context context) {  

             super(context);  

             paint = new Paint();  

             paint.setAntiAlias(true);//设置画笔无锯齿(如果不设置可以看到效果很差)  

             this.setKeepScreenOn(true);//设置背景常亮  

             paint.setColor(Color.RED);  

        }  

         @Override  

         public void onDraw(Canvas canvas) {   

        canvas.drawColor(Color.WHITE);//设置刷屏颜色  

             Rect rect = new Rect(30,30,50,50); //这里最后两个参数不是宽高、而是矩形右下角的坐标  

             canvas.drawRect(rect, paint);  

             RectF rectF = new RectF(70f,30f,90f,90f);//RectF 只是矩形 float形式 只是跟Rect精确度不一样  

             canvas.drawArc(rectF, 0, 360, true, paint);  

             canvas.drawCircle(150, 30, 20, paint);//这也是画圆 第三个参数为半径  

             float[] points =new float[]{200f,10f,200f,40f,300f,30f,400f,70f};  

             canvas.drawLines(points, paint);  

     //      canvas.drawLines(points, 1, 4, paint);//选取特定点数组中两点来画出一条直线  

             canvas.drawText("Szl", 230, 30, paint);  

         }   

     }  

    设置横竖屏也可以在AndroidManifest.xml中定义:

android:theme="@android:style/Theme.NoTitleBar" 隐去标题栏android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 隐去状态栏

 

二、剖析游戏开发用view还是sarfaceView

 在Android游戏当中充当主要的除了控制类外就是显示类,在J2ME中我们用Display和Canvas来实现这些,而Google Android中涉及到显示的为view类,Android游戏开发中比较重要和复杂的就是显示和游戏逻辑的处理。 

这里我们说下android.view.View和android.view.SurfaceView。SurfaceView是从View基类中派生出来的显示类,直接子类有GLSurfaceView和VideoView,可以看出GL和视频播放以及Camera摄像头一般均使用SurfaceView,到底有哪些优势呢? SurfaceView可以控制表面的格式,比如大小,显示在屏幕中的位置,最关键是的提供了SurfaceHolder类,使用getHolder方法获取,相关的有Canvas lockCanvas() 

Canvas lockCanvas(Rect dirty) 、void removeCallback(SurfaceHolder.Callback callback)、void unlockCanvasAndPost(Canvas canvas) 控制图形以及绘制,而在SurfaceHolder.Callback 接口回调中可以通过重写下面方法实现。 

使用的SurfaceView的时候,一般情况下要对其进行创建,销毁,改变时的情况进行监视,这就要用到 SurfaceHolder.Callback. 

class XxxView extends SurfaceView implements SurfaceHolder.Callback { 

public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){} 

//看其名知其义,在surface的大小发生改变时激发 

public void surfaceCreated(SurfaceHolder holder){} 

//同上,在创建时激发,一般在这里调用画图的线程。 

public void surfaceDestroyed(SurfaceHolder holder) {} 

//同上,销毁时激发,一般在这里将画图的线程停止、释放。 

 

对于Surface相关的,Android底层还提供了GPU加速功能,所以一般实时性很强的应用中主要使用SurfaceView而不是直接从View构建,同时后来做android 3d OpenGL中的GLSurfaceView也是从该类实现。 

SurfaceView和View最本质的区别在于,surfaceView是在一个新起的单独线程中可以重新绘制画面而View必须在UI的主线程中更新画面。 

那么在UI的主线程中更新画面 可能会引发问题,比如你更新画面的时间过长,那么你的主UI线程会被你正在画的函数阻塞。那么将无法响应按键,触屏等消息。 

当使用surfaceView 由于是在新的线程中更新画面所以不会阻塞你的UI主线程。但这也带来了另外一个问题,就是事件同步。比如你触屏了一下,你需要surfaceView中thread处理,一般就需要有一个event queue的设计来保存touch event,这会稍稍复杂一点,因为涉及到线程同步。

所以基于以上,根据游戏特点,一般分成两类。 

1 被动更新画面的。比如棋类,这种用view就好了。因为画面的更新是依赖于 onTouch 来更新,可以直接使用 invalidate。 因为这种情况下,这一次Touch和下一次的Touch需要的时间比较长些,不会产生影响。 

2 主动更新。比如一个人在一直跑动。这就需要一个单独的thread不停的重绘人的状态,避免阻塞main UI thread。所以显然view不合适,需要surfaceView来控制。

Android中的SurfaceView类就是双缓冲机制。因此,开发游戏时尽量使用SurfaceView而不要使用View,这样的话效率较高,而且SurfaceView的功能也更加完善。

 考虑以上几点,所以一般选用 SurfaceView 来进行游戏开发。

三、剖析Surface Callback以及SurfaceHolder

之前我们对view和surfaceview 做了比较和取舍,最后我们发现surfaceview更加的适合运作与游戏开发中,那么下面就让我们来看看这个surfaceview的结构吧; 

    先上一段代码:

     package cn.szl;    

     import android.content.Context;    

     import android.graphics.Canvas;    

     import android.graphics.Color;    

     import android.graphics.Paint;    

     import android.view.SurfaceHolder;    

     import android.view.SurfaceView;    

     import android.view.SurfaceHolder.Callback;    

     import android.view.animation.Animation;    

    

public class MySurfaceView extends SurfaceView implements Callback, Runnable {// 备1    

         private SurfaceHolder sfh;    

         private Thread th;    

         private Canvas canvas;    

         private Paint paint;    

         private int ScreenW, ScreenH;    

         public MySurfaceView(Context context) {    

             super(context);    

             th = new Thread(this);    

             sfh = this.getHolder();    

             sfh.addCallback(this); // 备注1    

             paint = new Paint();    

             paint.setAntiAlias(true);    

             paint.setColor(Color.RED);    

             this.setKeepScreenOn(true);// 保持屏幕常亮    

         }    

         @Override    

         public void startAnimation(Animation animation) {    

             super.startAnimation(animation);    

         }    

         public void surfaceCreated(SurfaceHolder holder) {    

             ScreenW = this.getWidth();// 备注2    

             ScreenH = this.getHeight();    

             th.start();    

         }    

         private void draw() {    

             try {    

                 canvas = sfh.lockCanvas(); // 得到一个canvas实例    

                 canvas.drawColor(Color.WHITE);// 刷屏    

                 canvas.drawText("Szl", 100, 100, paint);// 画文字文本    

                 canvas.drawText("这就是简单的一个游戏框架", 100, 130, paint);    

             } catch (Exception ex) {    

             } finally { // 备注3    

                 if (canvas != null)    

                     sfh.unlockCanvasAndPost(canvas);  // 将画好的画布提交    

             }    

         }     

         public void run() {    

             while (true) {    

                 draw();    

                 try {    

                     Thread.sleep(100);    

                 } catch (InterruptedException e) {    

                     // TODO Auto-generated catch block    

                     e.printStackTrace();    

                 }    

             }    

         }    

         public void surfaceChanged(SurfaceHolder holder, int format, int width,    

                 int height) {    

         }    

         public void surfaceDestroyed(SurfaceHolder holder) {    

             // TODO Auto-generated method stub    

         }    

     }    

 代码很简单,我们继承继承surfaceview类,并且使用回调callback接口以及线程runnable接口。那么这里我简单的说下Callback接口和SurfaceHolder 类的作用;

//备注1

  callback接口:

 只要继承SurfaceView类并实现SurfaceHolder.Callback接口就可以实现一个自定义的SurfaceView了,SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View,SurfaceHolder.Callback具有如下的接口:

surfaceCreated(SurfaceHolder holder):当Surface第一次创建后会立即调用该函数。程序可以在该函数中做些和绘制界面相关的初始化工作,一般情况下都是在另外的线程来绘制界面,所以不要在这个函数中绘制Surface。

surfaceChanged(SurfaceHolder holder, int format, int width,int height):当Surface的状态(大小和格式)发生变化的时候会调用该函数,在surfaceCreated调用后该函数至少会被调用一次。

SurfaceHolder 类:

它是一个用于控制surface的接口,它提供了控制surface 的大小,格式,上面的像素,即监视其改变的。 

SurfaceView的getHolder()函数可以获取SurfaceHolder对象,Surface 就在SurfaceHolder对象内。虽然Surface保存了当前窗口的像素数据,但是在使用过程中是不直接和Surface打交道的,由SurfaceHolder的Canvas lockCanvas()或则Canvas lockCanvas()函数来获取Canvas对象,通过在Canvas上绘制内容来修改Surface中的数据。如果Surface不可编辑或则尚未创建调用该函数会返回null,在 unlockCanvas() 和 lockCanvas()中Surface的内容是不缓存的,所以需要完全重绘Surface的内容,为了提高效率只重绘变化的部分则可以调用lockCanvas(Rect rect)函数来指定一个rect区域,这样该区域外的内容会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个同步锁直到调用unlockCanvasAndPost(Canvas canvas)函数才释放该锁,这里的同步机制保证在Surface绘制过程中不会被改变(被摧毁、修改)。

// 备注2

我没有在该surfaceview的初始化函数中将其 ScreenW 与 ScreenH 进行赋值,这里要特别注意,如果你在初始化调用ScreenW = this.getWidth();和ScreenH = this.getHeight();那么你将得到很失望的值 全部为0;原因是和接口Callback接口机制有关,当我们继承callback接口会重写它的surfaceChanged()、surfaceCreated()、surfaceDestroyed(),这几个函数当surfaceCreated()被执行的时候,真正的view才被创建,也就是说之前得到的值为0 ,是因为初始化会在surfaceCreated()方法执行以前执行,view没有的时候我们去取屏幕宽高肯定是0,所以这里要注意这一点;

//备注3

这里我把draw的代码都try起来,主要是为了当画的内容中一旦抛出异常了,那么我们也能 在finally中执行该操作。这样当代码抛出异常的时候不会导致Surface出去不一致的状态。   

四、Android 游戏框架

 其实上面分析surfaceview的内容就是一个简单的游戏框架了,当然这里再强调一下,简单的游戏框架,所以不要高手们不要乱喷。

 这个Demo是一个对图片操作以及按键处理,游戏简单框架的一个demo,这里放出给大家分享。

     package cn.szl;    

    import android.content.Context;    

    import android.content.res.Resources;    

    import android.graphics.Bitmap;    

    import android.graphics.BitmapFactory;    

    import android.graphics.Canvas;    

    import android.graphics.Color;    

    import android.graphics.Paint;    

    import android.util.Log;    

    import android.view.KeyEvent;    

    import android.view.SurfaceHolder;    

     import android.view.SurfaceView;    

     import android.view.SurfaceHolder.Callback;    

     public class MySurfaceView extends SurfaceView implements Callback, Runnable {    

         private Thread th = new Thread(this);    

         private SurfaceHolder sfh;    

         private int SH, SW;    

         private Canvas canvas;    

         private Paint p;    

         private Paint p2;    

        private Resources res;    

         private Bitmap bmp;    

         private int bmp_x = 100, bmp_y = 100;    

         private boolean UP, DOWN, LEFT, RIGHT;    

         private int animation_up[] = { 3, 4, 5 };    

         private int animation_down[] = { 0, 1, 2 };    

         private int animation_left[] = { 6, 7, 8 };    

         private int animation_right[] = { 9, 10, 11 };    

         private int animation_init[] = animation_down;    

         private int frame_count;    

       public MySurfaceView(Context context) {    

             super(context);    

             this.setKeepScreenOn(true);    

             res = this.getResources();    

             bmp = BitmapFactory.decodeResource(res, R.drawable.enemy1);    

             sfh = this.getHolder();    

             sfh.addCallback(this);    

             p = new Paint();    

             p.setColor(Color.YELLOW);    

             p2 = new Paint();    

           p2.setColor(Color.RED);    

             p.setAntiAlias(true);    

             setFocusable(true);  //备注1  

         }    

         public void surfaceCreated(SurfaceHolder holder) {    

             SH = this.getHeight();    

             SW = this.getWidth();    

             th.start();    

         }    

         public void draw() {    

           canvas = sfh.lockCanvas();    

             canvas.drawRect(0, 0, SW, SH, p);   //备注2  

             canvas.save();   //备注3  

             canvas.drawText("Szl", bmp_x-2, bmp_y-10, p2);    

             canvas.clipRect(bmp_x, bmp_y, bmp_x + bmp.getWidth() / 13, bmp_y+bmp.getHeight());    

             if (animation_init == animation_up) {    

                 canvas.drawBitmap(bmp, bmp_x - animation_up[frame_count] * (bmp.getWidth() / 13), bmp_y, p);    

             } else if (animation_init == animation_down) {    

                 canvas.drawBitmap(bmp, bmp_x - animation_down[frame_count] * (bmp.getWidth() / 13), bmp_y, p);    

             } else if (animation_init == animation_left) {    

               canvas.drawBitmap(bmp, bmp_x - animation_left[frame_count] * (bmp.getWidth() / 13), bmp_y, p);    

             } else if (animation_init == animation_right) {    

                 canvas.drawBitmap(bmp, bmp_x - animation_right[frame_count] * (bmp.getWidth() / 13), bmp_y, p);    

             }    

             canvas.restore();  //备注3  

             sfh.unlockCanvasAndPost(canvas);    

         }    

         public void cycle() {    

             if (DOWN) {    

                 bmp_y += 5;    

           } else if (UP) {    

                 bmp_y -= 5;    

             } else if (LEFT) {    

                 bmp_x -= 5;    

             } else if (RIGHT) {    

                 bmp_x += 5;    

             }    

             if (DOWN || UP || LEFT || RIGHT) {    

                 if (frame_count < 2) {    

                     frame_count++;    

               } else {    

                     frame_count = 0;    

                 }    

             }    

             if (DOWN == false && UP == false && LEFT == false && RIGHT == false) {    

                 frame_count = 0;    

             }    

         }    

        @Override    

        public boolean onKeyDown(int key, KeyEvent event) {    

         if (key == KeyEvent.KEYCODE_DPAD_UP) {    

                if (UP == false) {    

                    animation_init = animation_up;    

                }    

                UP = true;    

            } else if (key == KeyEvent.KEYCODE_DPAD_DOWN) {    

                if (DOWN == false) {    

                    animation_init = animation_down;    

                }    

                 DOWN = true;    

           } else if (key == KeyEvent.KEYCODE_DPAD_LEFT) {    

                 if (LEFT == false) {    

                     animation_init = animation_left;    

                 }    

                 LEFT = true;    

             } else if (key == KeyEvent.KEYCODE_DPAD_RIGHT) {    

                 if (RIGHT == false) {    

                     animation_init = animation_right;    

                 }    

                 RIGHT = true;    

           }    

             return super.onKeyDown(key, event);    

         }    

         /* (non-Javadoc)  

          * @see android.view.View#onKeyUp(int, android.view.KeyEvent)  

          */    

         @Override    

         public boolean onKeyUp(int keyCode, KeyEvent event) {    

             if (DOWN) {    

                 DOWN = false;    

           } else if (UP) {    

                 UP = false;    

             } else if (LEFT) {    

                 LEFT = false;    

             } else if (RIGHT) {    

                 RIGHT = false;    

             }    

             return super.onKeyUp(keyCode, event);    

         }    

         @Override    

       public void run() {    

             // TODO Auto-generated method stub    

             while (true) {    

                 draw();    

                 cycle();    

                 try {    

                     Thread.sleep(100);    

                 } catch (Exception ex) {    

                 }    

             }    

       }    

         @Override    

         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {    

             // TODO Auto-generated method stub    

         }    

         @Override    

         public void surfaceDestroyed(SurfaceHolder holder) {    

             // TODO Auto-generated method stub    

         }    

     }    

 备注1

   此方法是用来响应按键!如果是自己定义一个继承自View的类,重新实现onKeyDown方法后,只有当该View获得焦点时才会调用onKeyDown方法,Actvity中的onKeyDown方法是当所有控件均没有处理该按键事件时,才会调用.

 备注2

   这里也是对屏幕进行刷屏操作,其实这也只是一种,之前文章里我也用到drawRGB的方法同样实现,当然也可以用fillRect等来刷屏。

    那么这里我想说下,在继承view中,因为onDraw方法是系统自动调用的,不像在surfaceview这里这样去在run里面自己去不断调用,在view中我们可以抵用 invalidate()/postInvalidate() 这两种方法实现让系统调用onDraw方法,这里也是和surfaceview中的不同之一!

 备注3

   这里canvas.save();和canvas.restore();是两个相互匹配出现的,作用是用来保存画布的状态和取出保存的状态的。这里稍微解释一下,

   当我们对画布进行旋转,缩放,平移等操作的时候其实我们是想对特定的元素进行操作,比如图片,一个矩形等,但是当你用canvas的方法来进行这些操作的时候,其实是对整个画布进行了操作,那么之后在画布上的元素都会受到影响,所以我们在操作之前调用canvas.save()来保存画布当前的状态,当操作之后取出之前保存过的状态,这样就不会对其他的元素进行影响

对于 canvas.save();和canvas.restore();  还有不少人不懂,OK、我再补充点:

代码段1:

     public void draw() {   

      Canvas canvas = sfh.lockCanvas();    

      canvas.drawColor(Color.BLACK);  

      canvas.drawBitmap(bmp1, 0,0,paint);  

      canvas.save();   

      canvas.scale(5f, 5f);  

      canvas.restore();   

      canvas.drawBitmap(bmp2, 0,0,paint);  

      sfh.unlockCanvasAndPost(canvas);    

     }  

 

代码段2:

     public void draw() {   

       Canvas canvas = sfh.lockCanvas();    

       canvas.drawColor(Color.BLACK);  

       canvas.drawBitmap(bmp1, 0,0,paint);  

       canvas.scale(5f, 5f);  

       canvas.drawBitmap(bmp2, 0,0,paint);  

       sfh.unlockCanvasAndPost(canvas);    

     }  

上面这两个代码片段中我们都假设有两张图片 bmp1和bmp2,并且都画在画布上!

那么代码段1和代码段2的不同:

代码段1中我们进行画布缩放的之前保存了画布状态,做了缩放操作之后又取出之前保存的状态,这样做是为了保证bmp2正常画出来不受到缩放的影响!

代码段2里,画了bmp1后就执行了缩放操作,并且没有保存状态!紧接着画了bmp2,那么bmp2也会一样受到缩放的影响!!

所以我们如果单独处理一张图片的时候,而且不想影响其他部分的绘制,那么应该如下来做:

view plaincopy to clipboardprint?

     public void draw() {   

        Canvas canvas = sfh.lockCanvas();    

        canvas.drawColor(Color.BLACK);  

        canvas.drawBitmap(bmp1, 0,0,paint);  

        canvas.save();   

        canvas.scale(5f, 5f);  

        canvas.drawBitmap(bmp2, 0,0,paint);  

        canvas.restore();   

        sfh.unlockCanvasAndPost(canvas);    

       }  

 

原文见:http://www.cnblogs.com/wshark/archive/2011/05/12/2044201.html