3.5 游戏框架

大多数知名企业的游戏都是基于Apache Struts、Spring和Hibernate等开发框架的。这些框架都是基于MVC设计模式的,实现了业务和逻辑的分离,这已经成为当前游戏开发的主流模式。本节将简单分析Android平台提供的View和Surfaceview类的基本知识。

3.5.1 View类

View视图类是Android系统中的一个超类,在此类中几乎包含了所有的屏幕类型。每一个View都有一个用于绘图的画布,这个画布可以进行任意扩展。在游戏开发中也可以自定义View视图,这个画布的功能更能满足我们在游戏开发中的需要。

在Android系统中,任何一个View类都只需重写onDraw()方法来实现界面显示,自定义的视图可以是复杂的3D实现,也可以是非常简单的文本形式。

在游戏中最重要的就是与玩家的交互,例如,键盘输入、触笔点击事件如何来处理呢?在Android中提供了onKeyUp、onKeyDown、onKeyMultiple、onKeyPreIme、onTouchEvent和onTrackballEvent等方法,通过这些方法可以轻松处理游戏中的事件信息。在继承View时需要重载这几个方法,当有按键按下或弹起事件发生时,按键代码自动会传输给这些相应的方法来处理。

游戏的核心功能是不断绘图和刷新界面,视图可以通过onDraw()方法绘制,接下来重点分析如何刷新界面。在Android中提供了invalidate()方法来刷新界面。不能直接在线程中调用方法invalidate(),特别是不能在子线程中调用,因为这违背了Android单线程模型。Android UI操作并不是线程安全的,并且这些操作必须在UI线程中执行,因此Android中最常用的方法就是利用Handler来实现UI线程的更新。

接下来通过一个具体实例,介绍在Android中使用View类实现屏幕更新显示的方法。

实例3-13 使用View类(daima\3\ViewC)。

在本实例中,要在屏幕上绘制了一个不停变换颜色的矩形,并且定义一些事件来通过模拟器的上下键调节矩形的位置(比如把这个矩形向上移动或者把这个矩形向下移动)。本实例的实现文件是ViewC.java,主要代码如下所示。

public class ViewC extends View
{
  int  miCount = 0;
  int  y = 0;
  public ViewC(Context context)
  {
      super(context);
  }
  //
  //绘图处理
  public void onDraw(Canvas canvas)
  {
      if (miCount < 100)
      {
          miCount++;
      }
      else
      {
          miCount = 0;
      }
      //绘图
      Paint mPaint = new Paint();
      switch (miCount%4)
      {
      case 0:
          mPaint.setColor(Color.BLUE);
          break;
      case 1:
          mPaint.setColor(Color.GREEN);
          break;
      case 2:
          mPaint.setColor(Color.RED);
          break;
      case 3:
          mPaint.setColor(Color.YELLOW);
          break;
    default:
      mPaint.setColor(Color.WHITE);
      break;
    }
    //绘制矩形--后面将详细讲解
    canvas.drawRect((320-80)/2, y, (320-80)/2+80, y+40, mPaint);
  }
}

执行后将在屏幕内绘制一个矩形,并随着线程的变化矩形的填充颜色随之变化,从而实现闪烁效果,如图3-35所示。通过键盘上的上下键来移动矩形,如图3-36所示。

图3-35 闪烁效果

图3-36 上下移动矩形

3.5.2 SurfaceView类

类SurfaceView在游戏开发中有着举足轻重的作用,它对画面的控制有更大的自由度。SurfaceView类有个双缓冲机制,在开发游戏时经常用到,提高整个效率。

1. SurfaceView类基础

在Android中开发游戏时,一般来说,要想写一个复杂一点的游戏,必须使用SurfaceView来实现。SurfaceView可直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。Android图形系统中的一个重要的概念和线索是Surface(界面)、View(视图)及其子类,如TextView和Button。

要想在Surface上绘图。则必须为每个Surface创建一个Canvas对象(但属性时常改变),用来管理View在Surface上的绘图操作,如画点与画线。还要注意的是,在使用SurfaceView的时候,一般都是出现在最顶层。

在使用SurfaceView的时候,一般情况下还要对创建、销毁、改变时的情况进行监视,格式如下所示。

//在 surface的大小发生改变时激发
public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}
//在创建时激发,一般在这里调用画图的线程
public void surfaceCreated(SurfaceHolder holder){}
//销毁时激发,一般在这里将画图的线程停止、释放
public void surfaceDestroyed(SurfaceHolder holder) {}

surfaceCreated会首先被调用,然后是surfaceChanged,当程序结束时会调用surfaceDestroyed。

由于SurfaceHolder是一个共享资源,所以在对其操作时应该实行“互斥操作”,即需要使用synchronized的“封锁”机制。

渲染文字的工作实际上是主线程(也就是LunarView类)的父类View的工作,而并不属于工作线程LunarThread,在工作线程中是无法控制的,所以改为向主线程发送一个Message来代替,让主线程通过Handler对接收到的消息进行处理,从而更新界面文字信息。

接下来将通过一个具体的实例,介绍在Android中使用SurfaceView类实现屏幕更新显示的流程。

实例3-14 使用SurfaceView类实现屏幕内容的闪烁显示(daima\3\SurfaceC)。

本实例的实现文件是SurfaceC.java,具体代码如下所示。

package com.SurfaceC;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
public class SurfaceC extends SurfaceView
implements SurfaceHolder.Callback,Runnable
{
  //控制循环
  boolean mbLoop = false;
  //定义SurfaceHolder对象
  SurfaceHolder   mSurfaceHolder  = null;
  int             miCount         = 0;
  int             y               = 50;
  public SurfaceC(Context context)
  {
      super(context);
      //实例化SurfaceHolder
      mSurfaceHolder = this.getHolder();
      //添加回调
      mSurfaceHolder.addCallback(this);
      this.setFocusable(true);
      mbLoop = true;
  }
  //在surface的大小发生改变时激发
  public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
  {
  }
  //在surface创建时激发
  public void surfaceCreated(SurfaceHolder holder)
  {
      //开启绘图线程
      new Thread(this).start();
  }
  //在surface销毁时激发
  public void surfaceDestroyed(SurfaceHolder holder)
  {
      //停止循环
      mbLoop = false;
  }
  //绘图循环
  public void run()
  {
      while (mbLoop)
  {
    try
    {
        Thread.sleep(200);
    }
    catch (Exception e)
    {
    }
    synchronized( mSurfaceHolder )
    {
        Draw();
    }
  }
}
//绘图方法
public void Draw()
{
  //锁定画布,得到canvas
  Canvas canvas= mSurfaceHolder.lockCanvas();
  if (mSurfaceHolder==null || canvas == null )
  {
    return;
  }
  if (miCount < 100)
  {
    miCount++;
  }
  else
  {
    miCount = 0;
  }
  //绘图
  Paint mPaint = new Paint();
  mPaint.setAntiAlias(true);
  mPaint.setColor(Color.BLACK);
  //绘制矩形--清屏作用
  canvas.drawRect(0, 0, 320, 480, mPaint);
  switch (miCount % 4)
  {
  case 0:
    mPaint.setColor(Color.BLUE);
    break;
  case 1:
    mPaint.setColor(Color.GREEN);
    break;
  case 2:
    mPaint.setColor(Color.RED);
    break;
  case 3:
    mPaint.setColor(Color.YELLOW);
    break;
  default:
    mPaint.setColor(Color.WHITE);
    break;
  }
    // 绘制矩形--后面将详细讲解
    canvas.drawCircle((320 - 25) / 2, y, 50, mPaint);
    // 绘制后解锁,绘制后必须解锁才能显示
    mSurfaceHolder.unlockCanvasAndPost(canvas);
  }
}

执行后将在屏幕内绘制一个圆形,并随着线程的变化,圆形的填充颜色也随之变化,从而实现闪烁效果,如图3-37所示;可以通过键盘上的上下键来移动圆形,如图3-38所示。

图3-37 闪烁效果

图3-38 上下移动圆形

2. 双缓冲

双缓冲的是一种防止动画闪烁的多线程应用,基于SurfaceView的双缓冲实现很简单,只需开一条线程并在其中绘图即可。Android中的SurfaceView类就基于双缓冲机制,所以在开发游戏时应尽量使用SurfaceView而不要使用View,这样效率会较高,而且SurfaceView的功能也更加完善。

双缓冲的核心是先通过方法setBitmap()将要绘制的所有图形绘制到一个Bitmap(图像对象)上,然后调用方法drawBitmap()绘制这个Bitmap,并在屏幕上显示出来。下面将通过一个具体的实例,讲解在Android中使用SurfaceView类实现双缓冲。

实例3-15 使用双缓冲技术在屏幕中显示一幅图片(daima\3\shuanghuan)。

本实例的实现文件是shuanghuan.java,具体代码如下所示。

package com.shuanghuan;
import com.shuanghuan.R;
import Android.content.Context;
import Android.graphics.Bitmap;
import Android.graphics.Canvas;
import Android.graphics.Paint;
import Android.graphics.Bitmap.Config;
import Android.graphics.drawable.BitmapDrawable;
import Android.view.KeyEvent;
import Android.view.MotionEvent;
import Android.view.View;
public class shuanghuan extends View implements Runnable
{
  /* 声明Bitmap对象 */
  Bitmap  mBitQQ  = null;
  Paint   mPaint = null;
  /* 创建一个缓冲区 */
Bitmap  mSCBitmap = null;
/* 创建Canvas对象 */
Canvas mCanvas = null;
public shuanghuan(Context context)
{
    super(context);
    /* 装载资源 */
    mBitQQ = ((BitmapDrawable) getResources().getDrawable(R.drawable.qq)).getBitmap();
    /* 创建屏幕大小的缓冲区 */
    mSCBitmap=Bitmap.createBitmap(320, 480, Config.ARGB_8888);
    /* 创建Canvas */
    mCanvas = new Canvas();
    /* 设置将内容绘制在mSCBitmap上 */
    mCanvas.setBitmap(mSCBitmap);
    mPaint = new Paint();
    /* 将mBitQQ绘制到mSCBitmap上 */
    mCanvas.drawBitmap(mBitQQ, 0, 0, mPaint);
    /* 开启线程 */
    new Thread(this).start();
}
public void onDraw(Canvas canvas)
{
    super.onDraw(canvas);
    /* 将mSCBitmap显示到屏幕上 */
    canvas.drawBitmap(mSCBitmap, 0, 0, mPaint);
}
//触笔事件
public boolean onTouchEvent(MotionEvent event)
{
    return true;
}
//按键按下事件
public boolean onKeyDown(int keyCode, KeyEvent event)
{
    return true;
}
//按键弹起事件
public boolean onKeyUp(int keyCode, KeyEvent event)
{
    return false;
}
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event)
{
    return true;
}
/*线程处理*/
public void run()
{
    while (!Thread.currentThread().isInterrupted())
    {
        try
        {
            Thread.sleep(100);
        }
      catch (InterruptedException e)
      {
        Thread.currentThread().interrupt();
      }
      //使用postInvalidate可以直接在线程中更新界面
      postInvalidate();
    }
  }
}

执行后将用双缓冲技术在屏幕中显示图片,效果如图3-39所示。

图3-39 执行效果