Android基于box2d开发弹弓类游戏[二]-------------游戏界面的搭建&移动游戏场景

来源:互联网 发布:怎么报复淘宝差评 编辑:程序博客网 时间:2024/05/06 03:24

前面一讲中,我们介绍了,游戏开发的前期准备与如何创建项目。 

Android基于box2d开发弹弓类游戏[一]-------------前期准备&创建项目

在这一讲中,我们介绍如何搭建游戏界面,在游戏界面中加入静态如片,如何移动游戏场景。

呼呼呼!!那么,我们开始吧!

三.创建游戏界面

Android中用于显示游戏界面的视图,常用的有View和SurfaceView。SurfaceView是从View基类中派生出来的显示类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来控制。

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

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

 下面创建 基于SurfaceView的游戏界面类MainView.java:

复制代码
package com.catapultdemo; import android.content.Context;import android.view.SurfaceHolder;import android.view.SurfaceHolder.Callback;import android.view.SurfaceView; public class MainView extends SurfaceView implements Callback,Runnable {     public MainView(Context context) {       super(context);    }    @Override    public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {       // TODO Auto-generated method stub          }     @Override    public void surfaceCreated(SurfaceHolder arg0) {       // TODO Auto-generated method stub          }     @Override    public void surfaceDestroyed(SurfaceHolder arg0) {       // TODO Auto-generated method stub          }     @Override    public void run() {       // TODO Auto-generated method stub          } }
复制代码

只要继承SurfaceView类并实现SurfaceHolder.Callback接口和runnable接口就可以实现一个自定义的SurfaceView了。

SurfaceHolder.Callback在底层的Surface状态发生变化的时候通知View。

Runnable用来实现多线程

 游戏界面已经搭建完成,下面要做的就是让项目启动之后,显示我们的游戏界面,也就是MainView。

 自定义draw方法,用后之后话界面使用。为什么要实现这个方法,会在第四节进行说明。

public void draw(){}

现在可以运行一下项目,看一下运行结果。

可能与想象的有些差别。因为android项目创建完成之后,默认创建一个layout布局文件,用于初始布局。所以我们可以删除这个布局文件。

删除布局文件之后项目主文件MainActivity.java会报错。因为,默认情况下,向界面输出刚刚删除的布局文件,然后布局文件已经被我们删除了。

复制代码
package com.catapultdemo; import android.os.Bundle;import android.app.Activity; public class MainActivity extends Activity {     @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }}
复制代码

此时将setContentView(R.layout.activity_main);改成        setContentView(new MainView(this));

此行代码的意思是,让程序运行,向界面输出我们的游戏场景界面。接下来再一次运行程序。

我们删除了布局文件后,显示出了我们的游戏场景,中间的“hello world”也已经消失的,但是与我们的想象的结果还是有些差距。我们接下来需要去除头部的状态栏和应用程序的名称栏。还要试游戏屏幕变成横屏。

复制代码
// 隐去电池等图标和一切修饰部分(状态栏部分)this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);// 隐去标题栏(程序的名字)this.requestWindowFeature(Window.FEATURE_NO_TITLE);// 游戏界面横屏setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
复制代码

此时屏幕显示界面已经全部变黑了。此时我们的游戏界面搭建完成了。

四.在游戏场景中加入静态图片

现在游戏界面还没有任何的东西。接下来,我们在游戏场景中加入背景图片,和一些静态的物体。由于这些背景和静态的物体不需要模拟物理场景,所以,之需要在游戏场景中画出图片即可。

此前已经创建了继承自SurfaceView的MainView.java游戏界面类。接下来对方法进行完善和介绍。

 

实现了CallBack接口,重写了一下方法。

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

实现了Runnable接口,重写了run方法。下面介绍一下为什么要实现Runnable接口并且要重写run方法:surfaceView有onDraw方法,但是surfaceView不会自己去调用这个方法,所以我们要自己实现 draw方法,并放在run方法内。Runnable实现线程,run方法就是在开辟的线程中无限的去执行。所以我们自己完成的draw方法也可以不断的执行。这个就是刷屏。

在类中定义一些必要的变量。

复制代码
private Resources res;private SurfaceHolder sfh;   private Thread th;   private Canvas canvas;   private Paint paint;  public MainView(Context context) {           super(context);        res = this.getResources();        sfh = this.getHolder();           sfh.addCallback(this);          paint = new Paint();           paint.setAntiAlias(true);           paint.setColor(Color.RED);this.setKeepScreenOn(true);// 保持屏幕常亮  } 
复制代码

Resources资源变量。可以通过this.getResources()获取项目中的资源。

 

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绘制过程中不会被改变(被摧毁、修改)。

Thread:定义线程

Canvas: 定义游戏展示的平台,也就是一个画布。所有的游戏界面将会在画布上展示。

Paint: 定义画笔。 拥有了画布,我们需要一个画笔在画布上进行图画。

1.     场景中加入背景

首先要在资源文件中提取图片文件。

复制代码
//背景图片background_top = BitmapFactory.decodeResource(res, R.drawable.bg);background_bottom = BitmapFactory.decodeResource(res, R.drawable.fg);//两个松鼠图片squirrel_1 = BitmapFactory.decodeResource(res, R.drawable.squirrel_1);squirrel_2 = BitmapFactory.decodeResource(res, R.drawable.squirrel_2);//发射器底座图片catapult_base_1 = BitmapFactory.decodeResource(res, R.drawable.catapult_base_1);catapult_base_2=BitmapFactory.decodeResource(res,R.drawable.catapult_base_2); 加载了图片文件之后,定义一个常量FLOOR_HEIGHT。这个是地面的高度,为了能够更精确的摆放物体。这个高度就是手机屏幕下边缘到游戏中模拟的地图的高度。private static final float FLOOR_HEIGHT =82f; 接下来还要定义屏幕的高和宽。这个高和宽指的是手机屏幕可见区域的高和宽,并不是游戏场景中的高和宽。请注意。ScreenW = this.getWidth(); ScreenH = this.getHeight();  接下来在canvas上画出这些图片。public void surfaceCreated(SurfaceHolder arg0) {       ScreenH = this.getHeight();       ScreenW = this.getWidth();       thread_flag = true;       th = new Thread(this); // 创建线程       th.start();   //开启线程    } 此时需要注意。一定要把th.start()开启线程这段代码放到surfaceCreated最后,否则会出现启动自动退出的bug. private void draw() {           try {           canvas = sfh.lockCanvas(); // 得到一个canvas实例           if (canvas != null) {                canvas.drawColor(Color.WHITE);// 刷屏                canvas.drawBitmap(background_top, 0-w/2, 0, paint);                               canvas.drawBitmap(catapult_base_2,260-w,ScreenH-FLOOR_HEIGHT-catapult_base_2.getHeight()-catapult_base_2.getHeight()/4,paint);                canvas.drawBitmap(catapult_base_1,265-w,ScreenH-FLOOR_HEIGHT-catapult_base_1.getHeight()-catapult_base_1.getHeight()/4,paint);                               canvas.drawBitmap(squirrel_1, 50-w, ScreenH-FLOOR_HEIGHT-squirrel_1.getHeight(), paint);                canvas.drawBitmap(squirrel_2, 350-w, ScreenH-FLOOR_HEIGHT-squirrel_2.getHeight(), paint);                               canvas.drawBitmap(background_bottom, 0-w, ScreenH-background_bottom.getHeight(), paint);         }        } catch (Exception ex) {           } finally {              if (canvas != null)                sfh.unlockCanvasAndPost(canvas);  // 将画好的画布提交           }   }  
复制代码

此时运行程序。就可以看到我们的游戏场景了。但是现实的都是非物理模拟部分。

此时,是不是有些小小的兴奋。。。。但是不要怪我泼凉水。现在我们只是把一些图片拼凑在了一起。其他的什么都没有呢。手指滑动屏幕也没有任何反应。

接下来我们使游戏场景进行移动。

五.移动场景

现在的游戏运行之后,只能显示一半的场景,接下来实现用手指滑动屏幕移动场景。我们要在MainView.java主类中,复写View中的onTouchEvent方法。此方法用于检测触摸屏事件。

复制代码
@Override    public boolean onTouchEvent(MotionEvent event) {return super.onTouchEvent(event);    }然后分别在onTouchEvent方法中,实现触屏 按下,抬起,移动事件。public boolean onTouchEvent(MotionEvent event) {       if(event.getAction() == MotionEvent.ACTION_DOWN)       {}else if(event.getAction() == MotionEvent.ACTION_UP)       {}else if(event.getAction() == MotionEvent.ACTION_MOVE)       {}       return super.onTouchEvent(event);    }
复制代码

还需要定义两个变量。position_X是当按下触摸屏时,触摸点在当前游戏场景中的x轴位置,move_X是移动触摸屏时,移动的偏移量。

private float position_X;private float move_X;

当按下触摸屏时计算当前的场景中的位置。event.getX()是获取触摸点的x轴坐标,这个是相对于屏幕的坐标,所以我们还需要加上move_X偏移量,这样就能获取当前触摸点在游戏场景中的位置。

复制代码
if(event.getAction() == MotionEvent.ACTION_DOWN)    {       position_X =move_X+ event.getX();    }接下来就完成当手指移动时,move_X偏移量的值。偏移量应该是按下手指时的位置与移动时当前位置的差。else if(event.getAction() == MotionEvent.ACTION_MOVE)    {       move_X = position_X-event.getX();    }
复制代码

得到了偏移量我之需要在draw方法中,画游戏界面时对每张图片的x轴位置进行改变。这样就能屏幕移动的效果。

例如话背景头部图片时,x轴的位置减去偏移量的位置就是移动之后的位置。其他图片方法一样。不明白的可以查看第二节,Android游戏坐标系一节。

canvas.drawBitmap(background_top, 0-move_X, 0, paint);

接下来我们可以运行程序查看一下效果。

此时的运行效果可能会很失望,移动触屏,游戏场景并没有进行移动。原因是我们只是实现了触屏方法,但是当前surfaceview不允许对触屏进行点击。所以我们需要在MainView构造方法中,添加以下代码。

setClickable(true);

此时再次运行程序。游戏界面可以进行移动了。

但是经过测试会发现,当移动场景的时候,可能会超出整个游戏场景。如下图。

这个bug是在有些尴尬啊!!

这是由于我们没有为偏移量进行限制的原因。

0<Move_X<游戏场景宽度 – 屏幕宽度

游戏场景的宽度也就是背景图片的宽度。定义一个变量 gameWidth,它的值为背景图片的宽度。

复制代码
private float gameWidth;gameWidth = background_top.getWidth(); 在触屏移动时对move_X进行限制。else if(event.getAction() == MotionEvent.ACTION_MOVE)    {       move_X = position_X-event.getX();       move_X = move_X<0?0:(move_X>gameWidth-ScreenW?gameWidth-ScreenW:move_X);    }
复制代码

这样就能保证屏幕不会超出游戏场景的范围。

再次运行程序。可以发现,运行正常。

下一章中,我们将要介绍整个游戏的核心部门---》创建游戏世界!