上期已经和大家讲解了做游戏的前提要求,今天要给大家讲述的是游戏界面如何绘制。
一:如何获取到surface:
(1)首先我们需要获取一个surface,我们先通过surfaceView获取一个surfaceHolder,然后在通过surfaceHolder拿到surface,但是由于surfaceHolder已经具备surface的一些功能,就可以直接通过surfaceHolder去实现了。
(2)surface中的方法:
①lockCanvas:锁定画布,获取一个画布,绘制界面
②绘制界面
③unlockCanvasAndPost:解锁画布并提交,
为何需要锁定和解锁:其实也是避免了一些安全隐患,比如说多个线程同时修改界面,就有可能导致界面有些错乱,所以就规定了必须先锁定画布,然后再解锁画布。
(3)surfaceHolder里面具备了和surface一样的方法,一样可以锁定画布,解锁画布,其实这个surfaceHolder的里面就是通过surface实现的,通过下面的代码,我们就绘制出来如下的一个矩形
二:绘制笑脸和小人:
(1)首先我们看这个小小的游戏,摁住按钮可以使得小人向下移动,然后随便点击屏幕哪里,这个小人就可以发射笑脸。然后我们再来图解一下按钮,小人和笑脸,这三个对象我们都可以看作是一个精灵(Sprite),在游戏中我们都有各种各样的精灵,这个精灵泛指在游戏中最小的单位,他们所拥有的共性的方法就是
①显示图片
②都有一个显示的位置
③画自己
(2)我们先新建一个Sprite类作为一个基类,然后将他们的三个共性的方法表示出来,
三:笑脸移动:
(1)我们在face类中根据触摸的位置增加一个笑脸移动的方法
(2)由于我们在不停的创建笑脸,就会出现如下这种情况,解决的办法就是新建一个矩形,将之前创建的屏幕覆盖掉,然后不同的创建不停的覆盖,这就是做游戏的原理
(4)要实现多个笑脸同时移动, 首先要定义一个Face的集合,将所有的笑脸都添加进这个集合,然后在绘制笑脸的时候遍历集合,让笑脸画出自身,而且移动,
四:按钮的显示和点击事件:
(1)要做到将按钮按下的时候修改图片,我们就可以在按钮按下的时候将图片改变,
我们可以在MyButton类中将精灵类中的修改自身的方法完善,这样就可以实现按下的时候改变图片的样式了。
(2)暴露出来一个方法,用来设置按钮的点击状态,
(3)新建一个方法,用于判断我们用户所按下的那个点是否包含在我们所创建的按钮矩形中,这个就可以判断我们的按钮是否被点击了。
(4)新建一个方法,用于移动小人
(5)模仿android源码,为MyButton设置一个点击事件,写起来也非常的简单,为回调方法设置一个点击事件,这个回调方法写起来也是非常的简单,首先写一个
接口,然后在这里把接口声明出来,再给接口去设置一个set方法, 然后再暴露一个方法,供接口不为空的时候再去调用这个方法,
五:详细代码:
(1)GameUI类:
/** * 这是游戏的UI类 * 如果想让SurfaceHolder.Callback生效,我们必须要去调用 * SurfaceHolder.addCallback(SurfaceHolder.Callback) * 这样的一个方法,这样就可以保证实现SurfaceHolder.Callback的这 * 三个方法能够生效 */ public class GameUI extends SurfaceView implements SurfaceHolder.Callback{ private RenderThread renderThread; private boolean flag;// 线程运行的一个标记 SurfaceHolder holder; private Man man;//小人 private List<Face> faceList;//笑脸 private MyButton button;//这个就是我们需要用到的按钮 public GameUI(Context context) { super(context); holder = getHolder(); holder.addCallback(this);//保证surfaceCreated,surfaceChanged,surfaceDestroyed能够生效 } /** * 这个方法用来处理屏幕的点击事件 * @param event */ public void handlerTouch(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN://当按下的时候创建笑脸 int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); if(button.isClick(new Point(rawX,rawY))){ //man.move(Man.DOWN); button.click(); }else{ Face face= man.createFace(getContext(),new Point(rawX,rawY)); faceList.add(face); } break; case MotionEvent.ACTION_UP: button.setIsClick(false);//当手指抬起的时候,将按钮点击状态改成false break; } } private class RenderThread extends Thread{ @Override public void run() { while(flag){ //由于我们的程序可能走到一半就退出了,但是线程还在执行, //所以有可能会出现异常,我们直接捕获一下就可以了 try { drawUI(); }catch (Exception e){ e.printStackTrace(); } } } } /** * 这个函数用来绘制界面 */ private void drawUI() { //第一步,锁定画布 Canvas lockCanvas = holder.lockCanvas(); //绘制界面 Paint paint=new Paint(); paint.setColor(Color.GRAY); //绘制了一个的矩形,开始位置在左上角 lockCanvas.drawRect(0, 0, getWidth(), getHeight(), paint);//绘制了整个屏幕大小的矩形,把之前绘制的 //全部盖在下面, man.drawSelf(lockCanvas);//将男孩绘制到屏幕上 button.drawSelf(lockCanvas);//绘制button for(Face face: faceList){ face.drawSelf(lockCanvas); face.move(); } //解锁画布并提交 holder.unlockCanvasAndPost(lockCanvas);//解锁画布并提交,如果不执行的话,上面的代码都没有效果 } //当surface创建的时候调用 @Override public void surfaceCreated(SurfaceHolder holder) { Log.d("surfaceCreated----->","surface创建的时候调用"); Bitmap manBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.avatar_boy); man = new Man(manBitmap,new Point(0,0));//创建了男孩 faceList=new ArrayList<>(); //在这个创建一个button Bitmap buttonBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bottom_default); Bitmap pressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bottom_press); button = new MyButton(buttonBitmap,new Point(100,getHeight()-100),pressBitmap); button.setOnClickListener(new MyButton.OnClickListener() { @Override public void click() { man.move(Man.DOWN); } }); renderThread = new RenderThread(); flag=true; renderThread.start();//开启线程..... } //当surface的大小改变的时候调用,一般的改变是当前显示的界面的改变 @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { Log.d("surfaceCreated----->","surface大小改变的时候调用"); } //当surface销毁的时候调用 @Override public void surfaceDestroyed(SurfaceHolder holder) { Log.d("surfaceCreated----->","surface销毁的时候调用"); //renderThread.stop();//停止线程,因为这个方法有一些安全隐患,所以java工程师 //已经不建议我们通过这个方法去停止线程了,所以我们停止线程都是通过线程的一个循环,让线程自动停止 flag=false;//停止一个线程只要直接将标记定义为false就行了 } }
(2)face类:
/** * 这个是笑脸 */ public class Face extends Sprite { int spend=6; private int tX; private int tY; public Face(Bitmap defaultBitmap, Point postion,Point touchPoint) { super(defaultBitmap, postion); int X=touchPoint.x - postion.x; int Y =touchPoint.y - postion.y;//算出来两个直角边 int D= (int) Math.sqrt(X*X+Y*Y); tX=spend*X/D;//计算每次移动的偏移量 tY=spend*Y/D; } /** * 笑脸移动的方法, */ public void move(){ this.postion.x+=tX; this.postion.y+=tY; } }
/** * 这个是小人 */ public class Man extends Sprite { public static final int DOWN=0; int speed=6; public Man(Bitmap defaultBitmap, Point postion) { super(defaultBitmap, postion); } /** * 因为笑脸是从小人的嘴里面发射出来的,所以我们为 * 小人添加一个创建笑脸的方法, * @return */ public Face createFace(Context context,Point touchPoint){ Bitmap faceBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.rating_small); Face face = new Face(faceBitmap,new Point(postion.x+60,postion.y+55),touchPoint);//创建一个笑脸 return face; } /** * 这里是小人移动的方法 * @param d 这个参数是小人移动的方向 */ public void move(int d){ if(d==DOWN){ this.postion.y+=speed; } } }
(4)Sprite(精灵)类:
/** *这是一个精灵,这是作为我们显示界面最小单位的一个基类, *为了防止直接使用,所以我们把这个类抽象 */ public abstract class Sprite { private Bitmap defaultBitmap;//默认显示的图片 public Point postion;//图片显示的位置 //这个是为了防止你不给我传入图片和位置,所以使用一个构造方法强制传入参数 protected Sprite(Bitmap defaultBitmap, Point postion) { super(); this.defaultBitmap = defaultBitmap; this.postion = postion; } /** * 绘制自身的方法,就相当于把图片绘制到这个指定的位置上 */ public void drawSelf(Canvas canvas){ canvas.drawBitmap(defaultBitmap,postion.x,postion.y,null); } }
/** * 这是一个BUtton类, */ public class MyButton extends Sprite { private boolean isClick; private Bitmap pressdBitmap; private OnClickListener onClickListener; /** * 设置点击事件 * @param onClickListener */ public void setOnClickListener(OnClickListener onClickListener) { this.onClickListener = onClickListener; } public interface OnClickListener{ void click(); } /** * 按钮真正被点击的时候我们应该执行这个方法 */ public void click(){ if(onClickListener!=null){ onClickListener.click(); } } protected MyButton(Bitmap defaultBitmap, Point postion,Bitmap pressdBitmap) { super(defaultBitmap, postion); this.pressdBitmap=pressdBitmap; } //修改自身这个方法被完善 @Override public void drawSelf(Canvas canvas){ if(isClick){ //绘制一个按下的图片 canvas.drawBitmap(pressdBitmap,postion.x,postion.y,null); }else{ //绘制一个默认的图片 super.drawSelf(canvas); } } /** * 这个函数用于判断用于按下的点是否在我们的按钮矩形之中 * @param touchPoint */ public boolean isClick(Point touchPoint){ //先拿到按钮所在的矩形 Rect rect =new Rect(postion.x,postion.y,postion.x+pressdBitmap.getWidth(), postion.y+pressdBitmap.getHeight()); //如果包含这个点了就可以认为它被点击了, isClick = rect.contains(touchPoint.x, touchPoint.y); return isClick; } /** *暴露的一个方法,用于设置按钮的点击状态 * @param isClick */ public void setIsClick(boolean isClick) { this.isClick = isClick; } }