Android游戏快速入门(二):绘制动态文字和图片

来源:互联网 发布:机械公差配合软件 编辑:程序博客网 时间:2024/06/05 22:46

 

Android游戏快速入门(二):绘制动态文字和图片

http://blog.csdn.net/jackome/article/details/12618105

一、概述

这一讲我将带着大家来实现文字和图片的绘制,然后试着让文字和图片在屏幕里动起来。虽然,离真正的游戏还有一段距离,但是,这些都是游戏的基础,所以,大家都是需要掌握的。好的,不多说了,一起进入正题吧!

 

完成这一讲的任务,我们需要掌握如下一些概念,然后我会分别进行讲解。

层的概念
    文字层
    贴图层
View对象:自定义显示控件
    onDraw()方法:执行一系列绘制
Canvas对象:画布,呈现数据
    Paint:画笔对象
    drawText:绘制文字
    drawBitmap:绘制贴图
SurfaceView对象
    SurfaceHolder.Callback
Thread:线程让画面动起来

二、层的概念

学习photoshop的朋友肯定都知道层的概念,用通俗的话来说,层就是一个透明的玻璃纸。在android游戏中层的概念跟photoshop中也很相似,它可以用来呈现文字、图片等元素。游戏中一般都会有很多层组成,每个层中会有不同的元素,而且每个层中的元素是独立可控的。比如:在打飞机游戏中,背景是一层,玩家飞机是一层,敌机也是一层。大家要注意:层是有层次关系的,上面层会覆盖下面的层。那么,在打飞机游戏中,背景肯定是最里面一层,其他任何游戏元素都呈现在背景的上面。

 

另外有一点要跟大家特别讲一下,就是关于图片素材问题,我们都知道图片都是正规的矩形,而且有背景,所以在场景中肯定会有颜色块,看起来很不逼真。但是PNG格式的图片是可以做成透明背景,这样就解决这个问题,这也就是为什么android的图片素材基本上都是PNG格式的原因。

 

文字层:显示文字内容的层

贴图层:显示图片元素的层

但是,常常文字层和贴图层分的不是很清楚,文字层也可以绘制贴图,贴图层也可以绘制文字。

 

三、View对象

在普通的应用开发中似乎很难直接接触到View类,但实际上几乎所有的Android显示组件都是继承View类,TextView, EidtView, ImageView等等都是继承View类。开发中我们常常在XML文件中使用这些组件,但是如果要让组件具有更多独特的功能就需要自定义View类来扩展我们的需求了。

 

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

 

首先创建一个游戏主战场:GameView 类,并继承View类,结构如下:

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import android.content.Context;  
  4. import android.view.View;  
  5.   
  6. public class GameView extends View {  
  7.   
  8.     public GameView(Context context) {  
  9.         super(context);  
  10.     }  
  11.   
  12. }  

接下来我们要将上面创建的GameView类显示到手机屏幕上。需要在入口Activity中进行调用。项目创建的时候我就已经设定了一个主Activity,名称为:GameStartActivity,代码结构如下:


[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2. import android.app.Activity;  
  3. import android.os.Bundle;  
  4. public class GameStartActivity extends Activity {  
  5.     @Override  
  6.     public void onCreate(Bundle savedInstanceState) {  
  7.         super.onCreate(savedInstanceState);  
  8.           
  9.         // 默认是加载XML配置文件,显示的也是XML文件中的视图组件  
  10.         //setContentView(R.layout.main);  
  11.         //显示自定义的View,只需要将XML文件换成自定义的View对象就即可,如下:  
  12.         GameView gameView = new GameView(this);  
  13.         setContentView(gameView);   
  14.     }  
  15. }  

运行Application,效果如下:


很遗憾,页面中除了title什么也看不到。实际上,我只是测试自定义View是否能够正确显示,只要程序没有bug,就算是成功。(请看代码中的注释)

 

好的,接下来我们就在View里面展现一些内容,这个时候就要用到View对象中的onDraw方法,在自定义的GameView对象中必须覆盖父类View中的onDraw方法。接下来,你想展现任何内容都可以在此方法中进行了。假如,我想在屏幕的(100,100)处绘制蓝色文字:“飞机大战”,在屏幕的(100,200)处绘制一个半径10像素的红色圆。

[java] view plaincopy
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.         super.onDraw(canvas);  
  4. // 画笔对象,可以控制颜色和文字大小  
  5.         Paint paint = new Paint();  
  6.         // 给画笔设置系统内置的颜色:蓝色  
  7.         paint.setColor(Color.BLUE);   
  8.         // 文字左上角的坐标为(100,100)  
  9.         canvas.drawText("飞机大战",100100, paint);  
  10.           
  11.         //将画笔颜色调成红色  
  12.         paint.setColor(Color.RED);   
  13.         // 圆心点坐标为(100,200)  
  14.         canvas.drawCircle(10020010, paint);   
  15. }  

运行效果如下图:



到目前为止,你已经可以在自定义的GameView中绘制文字和各种图形了,但游戏中都是大量的图片素材,对于图片如何绘制呢?也很简单,Canvas类也提供了相应的drawBitmap方法。现在,我来绘制屏幕的(100,300)处绘制一个飞机图片。只需要在ondraw方法中添加如下代码即可:

[java] view plaincopy
  1. //加载资源图片图片  
  2. Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.hero1);  
  3. // 在(100,300)处绘制图片  
  4. canvas.drawBitmap(heroBitmap, 100300, paint);  

运行效果如下图:


对于代码中还有Canvas和Paint两个类没有细讲,我们可以这样打个比方吧,假如Canvas是一个画家,那么Paint就是画家手中的笔。画家能够画出各种景象(文字,形状,贴图等等),就要用到不同的画笔和不同的颜色。通过画笔类Paint就可以调整颜色,字体样式,字体大小等等。

我们发现绘制贴图canvas.drawBitmap(heroBitmap, 100, 300, paint);也会用到paint对象,但实际上paint起到的作用不大,我们完全可以忽略。

这种写法也是对的:canvas.drawBitmap(heroBitmap, 100, 300, null);

具体的用法代码里面已经有了,我就不再多说了,大家可以亲自查询下Android SDK API。


四、Thread:让画面动起来

 

上面我们已经实现了自定义的View中绘制了文字、形状和贴图,但是一切都是静止的,跟游戏还差的很远,意义不是很大。那么,接下来我就带着大家一起来让画面动起来。

 

实现这个目标,我们需要用到一个在游戏开发中非常重要的机制,就是多线程机制。具体多线程实现方式,不是我们现在讨论的问题,如果还不是很明白就需要自己补补线程方面的知识了。

 

在这里我们采用GameView类直接实现Runnable接口的方式:

[java] view plaincopy
  1. public class GameView extends View implements Runnable { }  

默认必须实现run方法:

[java] view plaincopy
  1. public void run() {  
  2.     while(true){  
  3.         // 不断的调用View中的postInvalidate方法,让界面重新绘制  
  4.         this.postInvalidate();  
  5.         try {  
  6.             // 暂停0.5秒继续  
  7.             Thread.sleep(500);  
  8.         } catch (InterruptedException e) {  
  9.             e.printStackTrace();  
  10.         }  
  11.     }  
  12. }  

postInvalidate() :此方法是View类中的方法,功能是触发调用onDraw方法实现界面重绘。

只要在每次重绘之前对层中对象的位置、形状、颜色或者透明度进行修改, 而且在一秒钟之内完成几十次的重绘,人的眼睛根本无法分辨,所以流畅的动画效果就产生了。动画片和电影也是这个原理。

现在我想让上面场景中的小球每隔0.5秒钟改变一次透明度和颜色,飞机垂直向上飞行10dp,效果如下:


完整的代码如下:

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import java.util.Random;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Paint;  
  11. import android.view.View;  
  12.   
  13. public class GameView extends View implements Runnable{  
  14.   
  15.     Random rand = new Random();   
  16.     private int dx = 0 ;   // x轴移动像素  
  17.     private int dy = 0 ;   // y轴移动像素  
  18.     /** 
  19.      * 必须要覆盖View中一个构造方法 
  20.      * @param context 
  21.      */  
  22.     public GameView(Context context) {  
  23.         super(context);  
  24.           
  25.         //启动线程  
  26.         new Thread(this).start();  
  27.     }  
  28.   
  29.     @Override  
  30.     protected void onDraw(Canvas canvas) {  
  31.         super.onDraw(canvas);  
  32.         // 画笔对象,可以控制颜色和文字大小  
  33.         Paint paint = new Paint();  
  34.           
  35.         // 给画笔设置系统内置的颜色:蓝色  
  36.         paint.setColor(Color.BLUE);   
  37.         // 文字左上角的坐标为(100,100)  
  38.         canvas.drawText("飞机大战",100100, paint);  
  39.           
  40.         //将画笔颜色调成红色  
  41.         //paint.setColor(Color.RED);   
  42.         //ARGB : A:透明度 、R:红色、G:绿色、B:蓝色。取值范围都在:0 ~ 255  
  43.         paint.setARGB(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255), rand.nextInt(255));  
  44.         // 圆心点坐标为(100,200)  
  45.         canvas.drawCircle(10020010, paint);   
  46.           
  47.         //加载资源图片图片  
  48.         Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.hero1);  
  49.         // 在(100,300)处绘制图片,dy值控制在y轴上的位置  
  50.         canvas.drawBitmap(heroBitmap, 100300+dy, null);  
  51.     }  
  52.   
  53.     @Override  
  54.     public void run() {  
  55.         while(true){  
  56.             //修改下dy的坐标,方向向上,所以dy不断减,每次上移10dp  
  57.             dy -= 10;   
  58.             // 不断的调用View中的postInvalidate方法,让界面重新绘制  
  59.             this.postInvalidate();  
  60.             //this.invalidate(); 此方法要求在UI主线程调用  
  61.             try {  
  62.                 // 暂停0.5秒继续  
  63.                 Thread.sleep(500);  
  64.             } catch (InterruptedException e) {  
  65.                 // TODO Auto-generated catch block  
  66.                 e.printStackTrace();  
  67.             }  
  68.         }  
  69.     }  
  70. }  

到此,实际上我们已经完成了我们的任务,但是并没有考虑到系统运行效率等问题。下面我将带着大家学习一下高效且更适合做游戏开发的SurfaceView类。

 

五、SurfaceView对象

 

Surfaceview类是View类的一个子类,我们来看看API的层级关系:



1、SurfaceView的特点

可以在主线程之外的线程中向屏幕绘图上。这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度

 

2、实现方式

定义一个游戏场景类继承SurefaceView ,同事实现SurfaceHolder.Callback接口。因为使用SurfaceView有一个原则,所有的绘图工作必须在Surface 被创建之后才能开始(Surface这个概念在 图形编程中常常被提到,基本上我们可以把它当作显存的一个映射,写入到Surface 的内容可以被直接复制到显存从而显示出来,这使得显示速度会非常快),而在Surface 被销毁之前必须结束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了绘图处理代码的边界。

 

3、需要重写的几个方法:

//在surface的大小发生改变时激发

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

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

2)  public void surfaceCreated(SurfaceHolder holder){} 

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

3)  public void surfaceDestroyed(SurfaceHolder holder) {}

 

4、整个代码过程逻辑:

-->继承SurfaceView并实现SurfaceHolder.Callback接口 

--> SurfaceView.getHolder()获得SurfaceHolder对象 

-->SurfaceHolder.addCallback(callback) 添加回调函数

-->SurfaceHolder.lockCanvas()获得Canvas对象并锁定画布

--> Canvas绘画 

-->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)结束锁定,并提交改变,将图形显示。

 

5、SurfaceHolder

这里用到了一个类SurfaceHolder,可以把它当成surface的控制器,用来操纵surface。处理Canvas上的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:

    // 给SurfaceView当前的持有者一个回调对象。
1)  abstract void addCallback(SurfaceHolder.Callback callback);

// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
2)  abstract Canvas lockCanvas();
// 锁定画布的某个区域进行画图等,因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。

3)  abstract Canvas lockCanvas(Rect dirty);

// 结束锁定画图,并提交改变。
4)  abstract void unlockCanvasAndPost(Canvas canvas);

6、我们把上面View的实现功能改为SurfaceView来重新实现

[java] view plaincopy
  1. package cn.zkyc.android.game;  
  2.   
  3. import java.util.Random;  
  4.   
  5. import android.content.Context;  
  6. import android.graphics.Bitmap;  
  7. import android.graphics.BitmapFactory;  
  8. import android.graphics.Canvas;  
  9. import android.graphics.Color;  
  10. import android.graphics.Paint;  
  11. import android.view.SurfaceHolder;  
  12. import android.view.SurfaceHolder.Callback;  
  13. import android.view.SurfaceView;  
  14.   
  15. public class GameSFView extends SurfaceView implements Callback, Runnable {  
  16.   
  17.     private SurfaceHolder surfaceHolder;  
  18.     private Random rand = new Random();  
  19.     private int dx = 0, dy = 0;  
  20.   
  21.       
  22.     public GameSFView(Context context) {  
  23.         super(context);  
  24.         surfaceHolder = this.getHolder(); // 获取SurfaceHolder对象  
  25.         surfaceHolder.addCallback(this); // 添加回调  
  26.     }  
  27.   
  28.     @Override  
  29.     public void run() {  
  30.           
  31.         while(true){  
  32.             dy -= 10//修改下dy的坐标,方向向上,所以dy不断减,每次上移10dp  
  33.             draw(); //调用重绘  
  34.             try {  
  35.                 Thread.sleep(500);  
  36.             } catch (InterruptedException e) {  
  37.                 // TODO Auto-generated catch block  
  38.                 e.printStackTrace();  
  39.             }  
  40.         }  
  41.     }  
  42.   
  43.     /** 
  44.      * 自定义绘制方法 
  45.      */  
  46.     public void draw() {  
  47.         synchronized(surfaceHolder){  
  48.             // 获取Canvas对象  
  49.             Canvas canvas = surfaceHolder.lockCanvas(); // 锁住Canvas  
  50.               
  51.             //清理背景,游戏中将换成具体的背景贴图  
  52.             canvas.drawColor(Color.BLACK);  
  53.               
  54.             // 画笔对象,可以控制颜色和文字大小  
  55.             Paint paint = new Paint();  
  56.   
  57.             // 给画笔设置系统内置的颜色:蓝色  
  58.             paint.setColor(Color.BLUE);  
  59.             // 文字左上角的坐标为(100,100)  
  60.             canvas.drawText("飞机大战"100100, paint);  
  61.   
  62.             // 将画笔颜色调成红色  
  63.             // paint.setColor(Color.RED);  
  64.             // ARGB : A:透明度 、R:红色、G:绿色、B:蓝色。取值范围都在:0 ~ 255  
  65.             paint.setARGB(rand.nextInt(255), rand.nextInt(255), rand.nextInt(255),rand.nextInt(255));  
  66.             // 圆心点坐标为(100,200)  
  67.             canvas.drawCircle(10020010, paint);  
  68.   
  69.             // 加载资源图片图片  
  70.             Bitmap heroBitmap = BitmapFactory.decodeResource(getResources(),R.drawable.hero1);  
  71.             // 在(100,300)处绘制图片,dy值控制在y轴上的位置  
  72.             canvas.drawBitmap(heroBitmap, 100300 + dy, null);  
  73.               
  74.             surfaceHolder.unlockCanvasAndPost(canvas);  // 解锁Canvas,更新  
  75.         }  
  76.           
  77.     }  
  78.   
  79.     @Override  
  80.     public void surfaceChanged(SurfaceHolder holder, int arg1, int arg2,int arg3) {  
  81.           
  82.     }  
  83.   
  84.     @Override  
  85.     public void surfaceCreated(SurfaceHolder holder) {  
  86.         // Surface创建成功启动线程  
  87.         new Thread(this).start();  
  88.     }  
  89.   
  90.     @Override  
  91.     public void surfaceDestroyed(SurfaceHolder holder) {  
  92.           
  93.     }  
  94.   
  95. }  

大家发现,飞机移动到顶部之后就不见了,请大家思考,如何让飞机飞过顶部之后还能从底部出来呢?

 

下一讲,我将带着大家实现更炫的游戏效果,敬请期待吧?

 

源码下载:

CSDNhttp://download.csdn.net/detail/jackome/6383395

百度云盘:http://pan.baidu.com/s/1BW2qK


0 0
原创粉丝点击