刚接触android不久,自己根据网上的教程模仿了一个2014年的热门游戏“围住神经猫”,游戏方法非常简单,大家都玩过应该知道,只要将人物围住就可以获胜,若人物跑到地图边缘,则判定失败。
先上游戏界面:
代码中有三个类:MainActivity、Playground、Dot
Dot是格子的对象类,地图是由格子组成的,里面是基本的set、get方法。附代码:
public class Dot { int x,y; int status; public static final int STATUS_ON = 1;//设置障碍 public static final int STATUS_OFF = 0;//设置为空 public static final int STATUS_IN = 9;//神经猫的位置 public Dot(int x, int y) { super(); this.x = x; this.y = y; status = STATUS_OFF; } public int getX() { return x; } public int getY() { return y; } public int getStatus() { return status; } public void setX(int x) { this.x = x; } public void setY(int y) { this.y = y; } public void setStatus(int status) { this.status = status; } public void setXY(int x,int y){ this.x = x; this.y = y; } }
这个游戏的主要实现方法在PlayGround类中,这个类继承了SurfaceView类。
下面我们先来了解一下surfaceView这个类:
在Android系统中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行绘制。又由于不会占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。
普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则的话,系统就会认为应用程序没有响应了,因此就会弹出一个ANR对话框出来。对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,因此,它们的UI就不适合在应用程序的主线程中进行绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。
SurfaceView及其宿主Activity窗口的绘图表面示意图
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来控制。
下面把Playground完整代码附上(已打注释):
public class Playground extends SurfaceView implements View.OnTouchListener { private static int WIDTH = 40;//定义格子的大小 private static final int ROW = 9;//定义行数 private static final int COL = 9;//定义列数 private static final int BLOCK = 10;//定义路障的数量 private int size = 0;//定义size 为格子与gif图片之间的间隔,又来调整gif的位置 private Canvas c;//定义画布 private long movieStart;//动图的开始 private Dot matrix[][];//声明地图对象 private Dot cat;//声明猫的对象 //构造函数 public Playground(Context context) { super(context); getHolder().addCallback(callback);//回调函数 matrix = new Dot[ROW][COL];//初始化地图 for (int i = 0;i < ROW; i++) for (int j = 0;j < COL; j++) matrix[i][j] = new Dot(j,i); setOnTouchListener(this);//添加监听器 initGame();//初始化游戏数据 } /* 得到格子对象 */ private Dot getDot(int x,int y){ return matrix[y][x]; } //判断点是否在边界 private boolean isAtEdge(Dot d){ if (d.getX()*d.getY()==0||d.getX()+1==COL||d.getY()+1==ROW) return true; return false; } //得到对象one附近的对象 private Dot getNeighbour(Dot one,int dir){ /* dir为周围对象的下标,1为one点的左上点,2为one点的右上点,3为one点的正右点,4为one点的右下点,5为one点的左下点,6为one点的正左点 */ switch (dir) { case 1: return getDot(one.getX()-1, one.getY()); case 2: if (one.getY()%2 == 0) { return getDot(one.getX()-1, one.getY()-1); }else { return getDot(one.getX(), one.getY()-1); } case 3: if (one.getY()%2 == 0) { return getDot(one.getX(), one.getY()-1); }else { return getDot(one.getX()+1, one.getY()-1); } case 4: return getDot(one.getX()+1, one.getY()); case 5: if (one.getY()%2 == 0) { return getDot(one.getX(), one.getY()+1); }else { return getDot(one.getX()+1, one.getY()+1); } case 6: if (one.getY()%2 == 0) { return getDot(one.getX()-1, one.getY()+1); }else { return getDot(one.getX(), one.getY()+1); } default: break; } return null; } private void MoveTo(Dot one){ one.setStatus(Dot.STATUS_IN);//设置猫的位置 getDot(cat.getX(),cat.getY()).setStatus(Dot.STATUS_OFF);//将猫原来的位置设为空状态 cat.setXY(one.getX(),one.getY());//设置猫的x,y坐标 } //点击一个点后的移动判断事件 private void move(){ if(isAtEdge(cat)){//如果神经猫逃离到边界,则判断游戏失败 lose(); return; } Vector<Dot> avaliable=new Vector<>();//用vector记录某个点的各个方向路线 Vector<Dot> positive=new Vector<>();//用vector记录某个点各个方向离边缘的距离 HashMap<Dot,Integer> al=new HashMap<Dot,Integer>(); for (int i=1;i<7;i++){//得到周围的所有点 Dot n = getNeighbour(cat,i); if (n.getStatus()==Dot.STATUS_OFF) { avaliable.add(n);//如果n点为空,添加到avaliable中 al.put(n, i);//将n和i记录到al中 if (getDistance(n,i)>0){ positive.add(n);//将存在到边缘的路线添加到positive中 } } } if(avaliable.size()==0)//one点周围没有空点 win(); else if (avaliable.size() == 1){ MoveTo(avaliable.get(0));//猫的移动方法 } else{ Dot best=null; if (positive.size()!=0){//存在可以直接到达屏幕边缘的走向 int min=999; for (int i=0;i<positive.size();i++){//遍历所有路线 int a = getDistance(positive.get(i),al.get(positive.get(i))); if (a<min) { min = a; best = positive.get(i);//得到最优路线 } } } else{//所有方向都有路障 int max = 1; for (int i=0;i<avaliable.size();i++){//遍历所有路线 int k = getDistance(avaliable.get(i),al.get(avaliable.get(i))); if (k<max){ max=k;//选择离障碍最远的路线,这样才会有更多的路线选择打到边缘 best=avaliable.get(i);//最优路线 } } } MoveTo(best);//选择最优路线进行移动 } } private int getDistance(Dot one, int dir) { int distance = 0; if (isAtEdge(one)) {//判断one点是否在格子边缘 return 1; } Dot ori = one,next; while (true) {//在一条直线上 // 如果下一个点遇到障碍点,distance返回值为负 // 如果下一个点为空值,则distance+1,继续进行循环 // 如果下一个点到了格子边缘,distance+1后返 next = getNeighbour(ori,dir); if (next.getStatus() == Dot.STATUS_ON) { return distance*-1; } if (isAtEdge(next)) {//判断下一个点是否在格子边缘 distance++;//距离增加 return distance; } distance++; ori = next; } } /* 失败方法 */ private void lose(){ GameDialog gameDialog = new GameDialog(getContext(),this);//生成一个游戏结束时的自定义Dialog对象 gameDialog.show(); // Toast.makeText(getContext(),"Lose",Toast.LENGTH_SHORT).show();//弹出"Lose"消息 } /* 获胜方法 */ private void win(){ Toast.makeText(getContext(),"Win",Toast.LENGTH_SHORT).show();//弹出"Win"消息 } /* 调整Bitmap的大小 */ public static Bitmap resizeBitmap(Bitmap bitmap, int w, int h) { if (bitmap != null) { int width = bitmap.getWidth();//得到图片的宽度 int height = bitmap.getHeight();//得到图片的高度 int newWidth = w;//你想调整的宽度 int newHeight = h;//你想调整的高度 float scaleWidth = ((float) newWidth) / width;//调整的宽度的比例 float scaleHeight = ((float) newHeight) / height;//调整的高度的比例 Matrix matrix = new Matrix();//实例化一个矩阵对象 matrix.postScale(scaleWidth, scaleHeight);//缩放变换 Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);//得到调整大小后的新Bitmap对象 return resizedBitmap;//返回Bitmap对象 } else { return null;//如果Bitmap不存在,返回空值 } } public void gifDraw(Canvas canvas,float x, float y) { Movie movie = Movie.decodeStream(getResources().openRawResource(R.raw.p));//将p.gif资源读入Movie中 long now = SystemClock.uptimeMillis();//从开机到现在的毫秒数(手机睡眠的时间不包括在内) if (movieStart == 0) { // first time movieStart = now; } if (movie != null) { int dur = movie.duration();//动画的持续时间 if (dur == 0) { dur = 1000; } int relTime = (int) ((now - movieStart) % dur);//得到帧数 movie.setTime(relTime);//设置movie的帧数 movie.draw(canvas, x, y);//显示在canvas上 invalidate();//界面刷新 } } public void redraw(){ Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.bg); Bitmap xrd = BitmapFactory.decodeResource(getResources(), R.raw.p); c = getHolder().lockCanvas();//获取canvas对象 // c.drawColor(Color.LTGRAY); c.drawBitmap(resizeBitmap(bitmap,getWidth(),getHeight()),0,0,null);//设置背景图片 size = (ROW / 3) * (getWidth() / 9 - WIDTH);//将格子向下平移 Paint paint = new Paint();//实例化一个画笔对象 paint.setFlags(Paint.ANTI_ALIAS_FLAG);//消除锯齿 for (int i = 0;i < ROW; i++) { int offset = 0; if(i%2 != 0){//偶数行向右平移半个格子的距离 offset = WIDTH/2; } for (int j = 0; j < COL; j++) { Dot one = getDot(j, i); switch (one.getStatus()) { case Dot.STATUS_OFF://格子为空状态 paint.setColor(0x7fC0C0C0); // paint.setColor(0x7f040000); c.drawOval(new RectF(one.getX() * WIDTH + offset + size, one.getY() * WIDTH + 7 * getHeight()/20, ((one.getX() + 1) * WIDTH) + offset + size, (one.getY() + 1) * WIDTH + 7 * getHeight()/20), paint); break; case Dot.STATUS_IN://猫所在的格子 paint.setColor(0xFFFFAA00); c.drawOval(new RectF(one.getX() * WIDTH + offset + size, one.getY() * WIDTH + 7 * getHeight() / 20, ((one.getX() + 1) * WIDTH) + offset + size, (one.getY() + 1) * WIDTH + 7 * getHeight() / 20), paint); gifDraw(c, one.getX() * WIDTH + offset + size - 40, one.getY() * WIDTH + 7 * getHeight() / 20 - 140); // c.drawBitmap(resizeBitmap(xrd,WIDTH * 3 / 2,WIDTH * 2),one.getX() * WIDTH + offset + size - WIDTH / 4, one.getY() * WIDTH + 7 * getHeight() / 20 - 3 * WIDTH / 2 , null); break; case Dot.STATUS_ON: paint.setColor(0x7fFF0000);//障碍所在的格子 c.drawOval(new RectF(one.getX() * WIDTH + offset + size, one.getY() * WIDTH + 7 * getHeight()/20, ((one.getX() + 1) * WIDTH) + offset + size, (one.getY() + 1) * WIDTH + 7 * getHeight()/20), paint); break; default: break; } // c.drawOval(new RectF(one.getX() * WIDTH + offset + size, one.getY() * WIDTH + 7 * getHeight()/20, ((one.getX() + 1) * WIDTH) + offset + size, (one.getY() + 1) * WIDTH + 7 * getHeight()/20), paint); } } getHolder().unlockCanvasAndPost(c);//结束锁定画图,并提交改变,将图形显示 } Callback callback = new Callback() { @Override public void surfaceCreated(SurfaceHolder holder) { redraw(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { WIDTH=width/(COL+1); redraw(); } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }; //初始化游戏 public void initGame(){ for (int i = 0;i < ROW; i++) for (int j = 0;j < COL; j++) matrix[i][j].setStatus(Dot.STATUS_OFF); cat=new Dot(4,4);//初始神经猫的位置 getDot(4,4).setStatus(Dot.STATUS_IN); // cat.setStatus(Dot.STATUS_IN); for (int i = 0;i<BLOCK;){//随机设置障碍的位置 int x = (int) ((Math.random()*1000) % COL);//随机生成X坐标 int y = (int) ((Math.random()*1000) % ROW);//随机生成Y坐标 if (getDot(x,y).getStatus()==Dot.STATUS_OFF){//设置状态 getDot(x,y).setStatus(Dot.STATUS_ON); i++; } } } /* 屏幕的点击方法 */ public boolean onTouch(View v, MotionEvent e) { if(e.getAction()==MotionEvent.ACTION_UP){ int x,y=0; if(e.getY()>(7 * getHeight()/20))//判断点击范围是否在格子范围高度内 y= (int) ((e.getY()-7 * getHeight()/20)/WIDTH); else {//如若不是 initGame();//重新开始游戏 redraw();//重绘 return true; } if (y%2==0){//判断是否为偶数行 x= (int) ((e.getX() -size)/WIDTH);//得到x坐标 } else{ x= (int) ((e.getX()-WIDTH/2-size)/WIDTH);//同上 } if (x+1 > COL ||y+1>ROW) {//判断坐标是否大于边界 initGame(); } else if(getDot(x, y).getStatus() == Dot.STATUS_OFF){ getDot(x, y).setStatus(Dot.STATUS_ON);//设置障碍 move();//进行移动方法 } redraw();//重绘 } return true; } }
整个代码中最核心的部分是move(),人物移动的算法主要有两个:
1、人物的6个方向的直线上,只要有一条可以直通地图边缘的路线,则选择离边缘最短的那条走。
2、人物的6个方向的直线上,没有一条可以通往边缘的路线,即每个方向都有障碍物挡着,则选择离障碍物最远距离的那条路线走。
网上下载了一个图片想设为背景图片,可是没有一个函数可以调整图片的大小,找了一个自定义函数来实现:
public static Bitmap resizeBitmap(Bitmap bitmap, int w, int h) { if (bitmap != null) { int width = bitmap.getWidth();//得到图片的宽度 int height = bitmap.getHeight();//得到图片的高度 int newWidth = w;//你想调整的宽度 int newHeight = h;//你想调整的高度 float scaleWidth = ((float) newWidth) / width;//调整的宽度的比例 float scaleHeight = ((float) newHeight) / height;//调整的高度的比例 Matrix matrix = new Matrix();//实例化一个矩阵对象 matrix.postScale(scaleWidth, scaleHeight);//缩放变换 Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);//得到调整大小后的新Bitmap对象 return resizedBitmap;//返回Bitmap对象 } else { return null;//如果Bitmap不存在,返回空值 } }
本来还想实现的是通过一个gif来实现人物的动画,android内部类没有实现gif的功能,搜索了一下资料,android实现gif有三种方式:
第一 :GifView支持android播放gif,效果是先加载第一帧,然后慢慢加载完其他的针,这样效果视觉很不好,是从模糊到清晰的过程;
第二:是流行的把gif图片通过工具分拆成n帧,然后使用逐帧动画播放,很麻烦;
第三 :使用Movie提供的Movie.decodeStream()方法解析gif,然后通过文件流的方式播放,效果特别好 ,和原图片没差。
我试了一下第三种的方法,依旧没有实现,每次点击屏幕一次,动画才会跳一帧,等询问大神后再做修改。
public void gifDraw(Canvas canvas,float x, float y) { Movie movie = Movie.decodeStream(getResources().openRawResource(R.raw.p));//将p.gif资源读入Movie中 long now = SystemClock.uptimeMillis();//从开机到现在的毫秒数(手机睡眠的时间不包括在内) if (movieStart == 0) { // first time movieStart = now; } if (movie != null) { int dur = movie.duration();//动画的持续时间 if (dur == 0) { dur = 1000; } int relTime = (int) ((now - movieStart) % dur);//得到帧数 movie.setTime(relTime);//设置movie的帧数 movie.draw(canvas, x, y);//显示在canvas上 invalidate();//界面刷新 } }
暂时实现了这个游戏的核心功能,开始界面和结束界面过两天实现。
相关推荐
《基于Android的解谜游戏开发》是一篇关于本科毕业设计的论文,主要探讨了在Android平台上开发解谜游戏的过程和技术。作者首先介绍了课题的背景和意义,指出随着科技文化的快速发展和智能手机的普及,人们对游戏的...
总之,“Android游戏开发之音乐与音效的处理太鼓达人游戏原理”涵盖了从基础音频框架到高级音效实现的诸多方面。通过学习这个主题,开发者可以掌握创建类似太鼓达人这样的音乐节奏游戏的关键技术,为玩家带来富有...
总之,"Android 游戏开发之主角的移动与地图的平滑滚动源码"这个主题涵盖了Android游戏开发的基础和核心技巧,包括用户交互、动画制作、地图滚动、碰撞检测以及性能优化等多个方面。通过深入学习和实践,开发者可以...
在Android平台上,游戏开发是一个非常活跃的领域,各种各样的游戏层出不穷。《Abduction》是一款深受玩家喜爱的经典游戏,它的开场动画源代码是许多开发者学习和借鉴的对象。本篇文章将详细解析`android游戏开场动画...
在Android游戏开发中,Tween动画是一种非常常见的动画形式,它主要用于实现对象的平滑移动、缩放、旋转等效果。Tween动画,源自于图形设计领域,意为“时间插值”,在Android中,它是通过`android.animation....
在Android平台上进行游戏开发是一项富有挑战性和创新性的任务,它涉及到多个技术和工具的融合。"Android游戏开发大全源代码"这个资源提供了丰富的实践材料,帮助开发者深入理解和掌握Android游戏开发的关键技术。...
本资源"Android游戏开发之飞行射击类游戏原理实现源码"提供了一个深入学习和实践此类游戏开发的宝贵机会。下面我们将探讨飞行射击类游戏的关键技术和实现步骤。 首先,游戏的核心是游戏循环(Game Loop)。游戏循环...
本文将深入探讨“Android游戏开发之地图编辑器”的相关知识点,包括地图编辑器的功能、工作原理以及如何在Android环境中进行实际操作。 地图编辑器的主要功能: 1. **图形化界面**:一个直观的用户界面是地图编辑...
5.1 Android游戏开发框架 5.1.1 View类开发框架 5.1.2 SurfaceView类开发框架 5.2 Graphics类开发 5.5.1 Paint和Color类介绍 5.2.2 Canvas类介绍 5.2.3 几何图形绘制 5.2.4 字符串绘制 5.2.5 图像绘制 5.2.6 图像...
《Android 2.0游戏开发实战宝典》是一本针对Android游戏开发的全面教程,适合初学者和有经验的开发者。这本书深入浅出地讲解了Android游戏开发的各个环节,从基础知识到高级技术,旨在帮助读者掌握游戏开发的核心...
本项目“Android游戏开发之小球重力感应源码”提供了实现这一功能的详细代码,帮助开发者了解如何将手机的重力感应数据应用到游戏逻辑中。 首先,我们需要了解Android系统是如何获取重力感应数据的。Android系统...
第2篇为应用开发篇,通过实例介绍了Android UI布局、Android人机界面、手机硬件设备的使用、Android本地存储系统、Android中的数据库、多线程设计、Android传感器、Android游戏开发基础、Android与Internet,以及...
第二篇则对7个真实案例的开发步骤进行了详细的介绍,逐步向读者讲解Android 3D游戏的真实开发过程,同时源代码中还包含了详细的注释,以尽量帮助读者掌握代码中的每一个细节,尽快掌握Android 3D游戏开发。...
"Android游戏开发之游戏主菜单与进度条加载源码"这个主题涵盖了这两个关键领域,为开发者提供了一手的学习资料。下面将详细介绍这两个方面的知识点。 1. 游戏主菜单: - 主菜单界面设计:游戏的主菜单通常是玩家...
第二篇则对7个真实案例的开发步骤进行了详细的介绍,逐步向读者讲解Android 3D游戏的真实开发过程,同时源代码中还包含了详细的注释,以尽量帮助读者掌握代码中的每一个细节,尽快掌握Android 3D游戏开发。...
本文将深入探讨主角与物理层碰撞的实现,以"Android游戏开发之主角与物理层的碰撞源码"为例,揭示游戏开发中的核心技术和实践方法。 首先,我们需要了解的是Android游戏开发的基本框架,通常开发者会选择使用Unity...
在Android游戏开发中,摄像头的使用是一个非常关键的特性,特别是在增强现实(AR)游戏中,摄像头是连接虚拟世界与现实世界的桥梁。本文将深入探讨Android摄像头的原理,并讲解如何更新和使用源码来实现更高效的游戏...
本项目"Android扫雷游戏"就是利用Android Studio进行开发的一个实例,旨在帮助初学者理解Android编程的基本概念和技术。扫雷游戏,作为一款经典的小游戏,它的实现过程涵盖了Android应用开发的多个核心知识点。 1. ...
在Android平台上进行3D游戏开发是一项复杂而富有挑战性的任务,涉及到多个技术层面。这份"Android3D游戏开发技术详解源代码"集合提供了一个全面的学习资源,包含了多种游戏项目的源码,非常适合对3D游戏开发感兴趣...