打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Android60分钟搞定《FlappyBird》游戏

  自定义控件系列,前前后后分享了三四篇了,其实,在自定义控件的道路中,我们只仅仅迈出了一小步,未来的时间我们仍需走在路上,去探索那些未知的自定义领域。今天,是个周末的时刻,少些烦躁的代码,来一起实现一个有趣的类似FlappyBird小游戏吧;对于FlappyBird这个小游戏,相信在看这篇文章的你并不陌生,其富有挑战又有一丝乐趣,让玩者玩的不亦乐乎,曾经风靡一时,虽已经离发行过去了三年多的时间,但仍能激起我们心中那份玩游戏时的跌宕起伏。

  先看一下我们最终要实现的效果:

  

  其实就是一个特别简单的动作,点击屏幕时,使小球做上下跳动,进行绕过障碍物,碰到障碍物或者碰到底部,则游戏结束,这个小游戏,总体代码不足400行,比较简单,整个Demo地址为:

  download.csdn/detail/ming_147/9731585,来,开始一点点的实现吧。

  对于这个GIF动图,初显示时其实就是打开应用所启动的Activity,无非就是设置了几个TextView,这个过于简单,就不再叙述,小球所在的页面就是第二页面时,也是一个Activity,这个Activity是游戏的控制页,其实里面就是自己定义的一个继承于SurfaceView的一个View类。

  可能有同志要问来,为什么要继承SurfaceView而不是继承View?相信做过小游戏的同志们都会或多或少的知晓,其实SurfaceView是View的子类,内嵌了用于专门绘制的Surface,可以控制其格式和尺寸,及Surface的绘制位置,并且,SurfaceView提供了一个可见的区域,只有在可见区域内Surface才可见,还有SurfaceView默认是使用双缓冲技术,它支持在子线程中绘制图像,不会阻塞主线程,对于游戏的开发它是再适合不过的。

  首先呢自定义一个类继承于SurfaceView,在构造方法中,我们可以做一些初始化工作:

  private void init() {

  holder=this.getHolder();

  holder.addCallback(this);

  mPaint=new Paint();

  mPaint.setColor(Color.WHITE);

  mPaint.setAntiAlias(true);

  mPaint.setTextSize(50);

  mPaint.setStyle(Paint.Style.STROKE);

  setFocusable(true);

  setFocusableInTouchMode(true);

  this.setKeepScreenOn(true);

  }

  初始化工作需要获取SurfaceHolder, SurfaceHolder提供了访问和控制SurfaceView背后的Surface的相关方法,所以这个一定进行获取,获取SurfaceHolder之后,调取addCallBack()方法,让自定义的类实现SurfaceHolder.Callback接口,其实现的三个方法这里做一个简单描述:

  surfaceCreated(SurfaceHolder holder):surface创建的时候调用,一般在该方法中启动绘图的线程。

  surfaceChanged(SurfaceHolder holder, int format, int width,int height):surface尺寸发生改变的时候调用,如横竖屏切换。

  surfaceDestroyed(SurfaceHolder holder) :surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。

  对于初始化中其它方法也做一个简单说明,其实源码中也有相关说明:

  mPaint.setAntiAlias(true)方法主要是为画笔设置抗锯齿,这样做的作用就是让图像边缘可以相对清晰一点。

  mPaint.setStyle(Paint.Style.STROKE);设置画笔为空心。

  setFocusable(true);设置可以获取焦点。

  setFocusableInTouchMode(true);设置此视图是否可以在触底模式下接受焦点。

  this.setKeepScreenOn(true);控制其屏幕是否应继续。

  对于小球,虚线,障碍物,关数,我们尽量在surfaceCreated进行初始化,上面也有说明,我们可以在这个方法里获取屏幕的宽高,另外我们再另起一个线程,因为接下来我们用到很多处理数据,及数据很多的地方,isStartGame这个布尔类型的值用于标示游戏是否结束。

  /**

  * surface创建的时候调用,一般在该方法中启动绘图的线程

  */

  @Override

  public void surfaceCreated(SurfaceHolder holder) {

  isStartGame=true;

  windowWidth=this.getWidth();

  windowHeight=this.getHeight();

  initGame();

  thread=new Thread(this);

  thread.start();

  }

  刚进入游戏页面,小球是不动的,虚线也是静止的,我们可以看下下面的初始化代码;

  /**

  * 游戏开始前进行初始化

  */

  private void initGame() {

  if (gameState==GAME_START) {

  floorLine[0]=0;

  floorLine[1]=windowHeight - windowHeight / 5;

  levelText[0]=windowWidth / 2;

  levelText[1]=windowHeight - 100;

  level_value=0;

  circle[0]=windowWidth / 3;

  circle[1]=windowHeight / 2;

  rectangleList.clear();

  floorLine_width=dp2px(15);

  speed=dp2px(3);

  circle_width=dp2px(10);

  circle_a=dp2px(2);

  circle_vUp=-dp2px(16);

  wall_w=dp2px(45);

  wall_h=dp2px(100);

  wall_step=wall_w * 4;

  floorLine就是我定义的一个存储底部虚线xy坐标的一个数组:

  /**

  * 底部虚线xy坐标

  */

  private int[] floorLine=new int[2];

  levelText是存储关数的xy坐标的一个数组,level_value是默认第0关。

  /**

  * 关数坐标

  */

  private int[] levelText=new int[2];

  cirle是存储小球的xy坐标的一个数组:

  /**

  * 球的xy坐标

  */

  private int[] circle=new int[2];

  rectangleList这个集合是用于存储障碍物的坐标集合:

  private ArrayList removeRectangleList=new ArrayList();

  一切初始化工作完成之后,我们就是绘制小球,虚线,障碍物了,前边我们起了一个QQ账号卖号线程,这些工作我们就可以放到子线程去实现:

  @Override

  public void run() {

  while (isStartGame) {

  long start=System.currentTimeMillis();

  drawGame();

  startGame();

  long end=System.currentTimeMillis();

  try {

  if (end - start < 50) {

  Thread.sleep(50 - (end - start));

  }

  } catch (InterruptedException e) {

  e.printStackTrace();

  }

  }

  }

  isStartGame这个值前边说过,用于标示游戏是否结束,如果一直没结束,那么run方法就一直执行,记得在surface销毁时,改为false。

  /**

  * surface被销毁的时候调用,如退出游戏画面,一般在该方法中停止绘图线程。

  */

  @Override

  public void surfaceDestroyed(SurfaceHolder holder) {

  isStartGame=false;

  }

  关于绘制,前边几篇自定义控件中对这些绘制方法已经做了很多的例子,主要就使确定位置,这是非常重要的,获取画笔,我们可以new一个,也可以从SurfaceHolder里面获取,关于绘制虚线,其y轴是保持不变的,宽度一定,只是x轴逐渐递增;绘制圆其半径也是一定,x轴坐标固定,只有y轴上下移动,关数和障碍物的绘制,只要位置确定,其绘制也类似,当一切画完之后,记得一定要去调用unlockCanvasAndPost来改变内容。

  /**

  * 绘制小球,虚线,障碍物,关数

  * */

  private void drawGame() {

  try {

  canvas=holder.lockCanvas();//获取画笔

  mPaint.setColor(Color.WHITE);

  if (canvas !=null) {

  canvas.drawColor(Color.BLACK);

  int floor_start=floorLine[0];

  mPaint.setStrokeWidth(20);

  //绘制虚线

  while (floor_start < windowWidth) {

  canvas.drawLine(floor_start, floorLine[1], floor_start + floorLine_width, floorLine[1], mPaint);

  floor_start +=floorLine_width * 2;

  }

  mPaint.setStrokeWidth(1);

  //绘制圆

  mPaint.setStyle(Paint.Style.FILL);

  canvas.drawCircle(circle[0], circle[1], circle_width, mPaint);

  mPaint.setColor(Color.BLACK);

  canvas.drawText("明", circle[0] - 25, circle[1] + 20, mPaint);

  mPaint.setColor(Color.WHITE);

  //绘制关数

  canvas.drawText("第" + String.valueOf(level_value) + "关", levelText[0] - 50, levelText[1], mPaint);

  //绘制阻碍物

  for (int i=0; i < rectangleList.size(); i++) {

  int[] wall=rectangleList.get(i);

  float[] pts={

  wall[0], 0, wall[0], wall[1],

  wall[0], wall[1] + wall_h, wall[0], floorLine[1],

  wall[0] + wall_w, 0, wall[0] + wall_w, wall[1],

  wall[0] + wall_w, wall[1] + wall_h, wall[0] + wall_w, floorLine[1],

  wall[0], wall[1], wall[0] + wall_w, wall[1],

  wall[0], wall[1] + wall_h, wall[0] + wall_w, wall[1] + wall_h

  };

  canvas.drawLines(pts, mPaint);

  }

  }

  } catch (Exception e) {

  } finally {

  if (canvas !=null)

  holder.unlockCanvasAndPost(canvas);

  }

  }

  这里我把游戏分为来三个状态,分别是,游戏前,游戏中,游戏结束三个状态:

  /**

  * 游戏前

  */

  private static final int GAME_START=0;

  /**

  * 游戏中

  */

  private static final int GAME_ING=1;

  /**

  * 游戏结束

  */

  private static final int GAME_OVER=-1;

  当手点击屏幕之后,当小球落至虚线位置,或者碰到障碍物,则游戏结束,返回上一个activity,否则继续。

  @Override

  public boolean onTouchEvent(MotionEvent event) {

  if (event.getAction()==MotionEvent.ACTION_DOWN) {

  switch (gameState) {

  case GAME_START:

  gameState=GAME_ING;

  case GAME_ING:

  circle_v=circle_vUp;

  break;

  case GAME_OVER:

  if (circle[1] >=floorLine[1] - circle_width) {

  gameState=GAME_START;

  initGame();

  }

  break;

  }

  }

  return true;

  }

  /**

  * 开始游戏

  */

  private void startGame() {

  switch (gameState) {

  case GAME_START://游戏开始前

  break;

  case GAME_ING://游戏进行时

  circle_v +=circle_a;

  circle[1] +=circle_v;

  if (circle[1] > floorLine[1] - circle_width) {

  circle[1]=floorLine[1] - circle_width;

  gameState=GAME_OVER;//圆掉到了虚线以下

  }

  //虚线的滚动

  if (floorLine[0] < -floorLine_width) {

  floorLine[0] +=floorLine_width * 2;

  }

  floorLine[0] -=speed;

  removeRectangleList.clear();

  for (int i=0; i < rectangleList.size(); i++) {

  int[] wall=rectangleList.get(i);

  wall[0] -=speed;

  if (wall[0] < -wall_w) {

  removeRectangleList.add(wall);

  } else if (wall[0] - circle_width <=circle[0] && wall[0] + wall_w + circle_width >=circle[0]

  && (circle[1] <=wall[1] + circle_width || circle[1] >=wall[1] + wall_h - circle_width)) {

  gameState=GAME_OVER;

  }

  int pass=wall[0] + wall_w + circle_width - circle[0];

  if (pass < 0 && -pass <=speed) {

  level_value++;

  }

  }

  if (removeRectangleList.size() > 0) {

  rectangleList.removeAll(removeRectangleList);

  }

  move_step +=speed;

  if (move_step > wall_step) {

  //坐标随机产生

  int[] wall=new int[]{windowWidth, (int) (Math.random() * (floorLine[1] - 2 * wall_h) + 0.5 * wall_h)};

  rectangleList.add(wall);

  move_step=0;

  }

  break;

  case GAME_OVER:

  if (circle[1] < floorLine[1] - circle_width) {

  circle_v +=circle_a;

  circle[1] +=circle_v;

  if (circle[1] >=floorLine[1] - circle_width) {

  circle[1]=floorLine[1] - circle_width;

  }

  } else {

  //游戏结束返回上一个activity

  AbnerGameCircleActivity.mAbnerGameCircleActivityAchievement(level_value);

  gameState=GAME_START;

  initGame();

  }

  break;

  }

  }

  60分钟,我想,是完不成的,因为这个Demo花费了一下午的时间,能够顺利的完成这个小游戏Demo,感谢百度翻译,因为有许多SurfaceView类中的方法,都是通过翻译才大概的弄懂它,也特别感谢许多默默无闻中一直奉献的同志们,更多Android文章请扫描评论第一条二维码,关注我的公众账号吧。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Android自定义view
Android 放大镜效果实现原理
Android应用中仿今日头条App制作ViewPager指示器
Android特效专辑(六)——仿QQ聊天撒花特效,无形装逼,最为致命
自定义饼形图
[canvas]通过动态生成像素点做绚丽效果
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服