刚接触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操作系统进行设计。这个游戏的开发涉及到了多个核心知识点,包括但不限于以下几个方面: 1. **Android Studio**: Android游戏开发通常...
在本篇博文中,我们将探讨如何进行Android游戏开发,以"抓住疯狂熊"为例,这是一个初学者友好的项目,旨在帮助开发者了解基本的游戏编程概念。本文将深入剖析游戏的架构、设计过程以及实现细节,同时也会提及一些...
总之,Android游戏开发之旅不仅涵盖了游戏开发的基础知识,还深入探讨了高级技术和游戏引擎构建,为有意进入这一领域的开发者提供了全面的指导。通过学习并实践这些知识,开发者将能够创建出吸引人的、高质量的...
本文基于Android平台开发了一款解谜游戏,旨在探讨开发过程中的关键技术和设计理念,并分析了游戏的市场潜力和用户需求。通过对Android系统特征的深入研究,结合游戏策划与需求分析,本文详尽地描述了游戏从概念到...
总之,“Android游戏开发之音乐与音效的处理太鼓达人游戏原理”涵盖了从基础音频框架到高级音效实现的诸多方面。通过学习这个主题,开发者可以掌握创建类似太鼓达人这样的音乐节奏游戏的关键技术,为玩家带来富有...
《Android游戏开发实战宝典与3D游戏开发教程》是一份综合性的学习资源,涵盖了Android平台上游戏开发的多个重要方面。这份压缩包包含了两部分核心内容:《ANDROID_2游戏开发实战宝典》和《Android_3D游戏开发教程》...
在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 图像...
第2章 游戏开发相关android基础知识 23 2.1 游戏中的音效 23 2.1.1 游戏中的即时音效 23 2.1.2 即时音效的一个案例 24 2.1.3 背景音乐播放技术 27 2.1.4 简易音乐播放器的实现 29 2.2 简单数据的...
《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...