Android SurfaceView仿“是男人就下一百层”
来源:互联网 发布:毕业生烧炭自杀知乎 编辑:程序博客网 时间:2024/04/29 21:28
本文感谢鸿洋先生的博客
http://blog.csdn.net/lmj623565791/article/details/42965779
本文基于以上博客,利用鸿洋先生的创意改编而成。
正题,本例子想做到的效果:
1.不断上升的地板条
2.小人,踩到地板会跟着上升,否则自然下坠
3.小人上升过屏幕顶或者下降到屏幕底部外则判定为死亡,地板停止,小人自然下坠
最终效果截图:
本例子所用素材来自游戏:涂鸦跳跃
好了,开工。
1.首先,回顾下SurfaceView的一般用法,直接上鸿洋的干货
package com.zhy.view; import android.content.Context; import android.graphics.Canvas; import android.graphics.PixelFormat; import android.util.AttributeSet; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; public class GameFlabbyBird extends SurfaceView implements Callback, Runnable { private SurfaceHolder mHolder; /** * 与SurfaceHolder绑定的Canvas */ private Canvas mCanvas; /** * 用于绘制的线程 */ private Thread t; /** * 线程的控制开关 */ private boolean isRunning; public GameFlabbyBird(Context context) { this(context, null); } public GameFlabbyBird(Context context, AttributeSet attrs) { super(context, attrs); mHolder = getHolder(); mHolder.addCallback(this); setZOrderOnTop(true);// 设置画布 背景透明 mHolder.setFormat(PixelFormat.TRANSLUCENT); // 设置可获得焦点 setFocusable(true); setFocusableInTouchMode(true); // 设置常亮 this.setKeepScreenOn(true); } @Override public void surfaceCreated(SurfaceHolder holder) { // 开启线程 isRunning = true; t = new Thread(this); t.start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { // TODO Auto-generated method stub } @Override public void surfaceDestroyed(SurfaceHolder holder) { // 通知关闭线程 isRunning = false; } @Override public void run() { while (isRunning) { long start = System.currentTimeMillis(); draw(); long end = System.currentTimeMillis(); try { if (end - start < 50) { Thread.sleep(50 - (end - start)); } } catch (InterruptedException e) { e.printStackTrace(); } } } private void draw() { try { // 获得canvas mCanvas = mHolder.lockCanvas(); if (mCanvas != null) { // drawSomething.. } } catch (Exception e) { } finally { if (mCanvas != null) mHolder.unlockCanvasAndPost(mCanvas); } } }
SurfaceView的优点就在此,给予开发者很大的便利,可以直接开普通线程更改UI,
上面的关键就在继承SurfaceView,并拿到holder,在主线程中开一个循环线程,循环绘制得到的画布;
接下来干啥?当然要有背景
2.背景
这里我封装成了一个类,便于以后停止游戏等操作:
public class Background { Bitmap bitmap; RectF rectf; int gameHeight; int gameWidth; public Background(Bitmap bitmap, RectF rectf) { this.bitmap = bitmap; this.rectf = rectf; } public void setGameHeight(int gameHeight) { this.gameHeight = gameHeight; } public void setGameWidth(int gameWidth) { this.gameWidth = gameWidth; } public void draw(Canvas canvas) { canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.drawBitmap(bitmap, null, rectf, null); canvas.restore(); }}
很简单,主要就是后面的draw,拿到目标画布后在上面绘制背景
3.地板
地板的横轴坐标是随机的,一开始出现了一个小麻烦,就是上一次绘制的地板没有去掉,导致屏幕上都是地板。。。后来动了点小手脚就搞定了
Paint mPaint = new Paint(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR)); mCanvas.drawPaint(mPaint);
在每次绘制之前执行这段,清除掉Canvas原来的内容
我还试过一个方法,就是自己新建一个Canvas,在这上面画,再还给holders,这就不用清楚了吗,结果就是报错:)要用人家原来的画布。。
public class Floor { int width; int height; int x; int y; Bitmap mBitmap; public Floor(Context context, int gameHeight, int gameWidth, Bitmap bitmap) { y=gameHeight; mBitmap=bitmap; isWith=false; } //省略众多赋值取值语句 public void draw(Canvas canvas,RectF rectf) { canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.translate(x, y); canvas.drawBitmap(mBitmap,null,rectf,null); canvas.restore(); }}
从代码上看也很简单
3.地板的增加、删除、移动
public void caculate() { for (Floor f : floors) { if (f.getY() < 0 - floor_hight) { //delete the floor out of window floors_delete.add(f); break; } } floors.removeAll(floors_delete); floors_delete.clear(); //caculate need add floor or not if (mTmpMove > mTargetMove) { int randx = (int) (Math.random() * (game_width - floor_width)); Floor floor = new Floor(getContext(), game_height, game_width, floor_bitmap); floor.setX(randx); floor.setY(game_height); floor.setHeight(floor_hight); floor.setWidth(floor_width); floors.add(floor); mTmpMove = 0; mGrade++; } }
采用的还是鸿洋先生那个方法,遍历,看看当前是否需要删除地板,利用链表储存,然后再看看当前移动距离是否需要再增加地板
地板移动,相对简单
/**floors move up*/ private void floor_move() { for (Floor f : floors) { f.setY(f.getY() - mSpeed); } mTmpMove += mSpeed; }
只是注意,这里我们是向上,因此坐标是要减的,(0,0)在左上角
4.小人man
必要的坐标啊,大小矩形啊是肯定的,我们做出来的小人肯定是要移动的,所以我希望向左的时候有一张向左的图,向右亦然
public class Man { private int width; private int height; private int x; private int y; private int gameWidth; private Bitmap mBitmap; private RectF rectf; private Bitmap bitmap_center; private Bitmap bitmap_left; private Bitmap bitmap_right; private int ori; private boolean boom_left; private boolean boom_right; private int boom_time; /** * set the ori,-1left 0center 1right */ public void setOri(int x) { ori = x; if (x == -1) { mBitmap = bitmap_left; } else if (x == 1) { mBitmap = bitmap_right; } else { mBitmap = bitmap_center; } } public Man(Context context, int gameHeight, int gameWidth, Bitmap bitmap) { y = gameHeight / 2; x = gameWidth/2; mBitmap = bitmap; this.gameWidth = gameWidth; ori = 0; boom_left = false; boom_right=false; boom_time=5; } public void trans_y(int x) { this.y+=x; } //省略一堆get\set public void draw(Canvas canvas, RectF rectf) { canvas.save(Canvas.MATRIX_SAVE_FLAG); if (this.x>0&&this.x<gameWidth-width) { }else if(this.x<=0) { this.x=0; }else{ this.x=gameWidth-width; } canvas.translate(x,y); canvas.drawBitmap(mBitmap, null, rectf, null); canvas.restore(); this.rectf = rectf; } }
可以看到,首先我要求要有三张图,代表正面、向左、向右
而且在这里有限制的绘制条件,不允许水平超出屏幕
我开放了一个函数,可以根据调用改变当前显示图像
在相应的地方调用
/*set the man ori,-1 left ;0 center;1 right/
public void setManOri(int x) {
if (man != null)
this.man.setOri(x);
}
3.碰撞
这个例子有一个很重要的点就是判断man是否站在了地板上,如果是则跟着地板上升
首先,判断,不难
/** * return is will click the floor */ private boolean pre_calcute_pomb(Floor f) { if (f.getX() <= man.getX() && f.getX() + floor_width >= man.getX() && (f.getY() - man_height <= man.getY() && f.getY() + floor_hight >= man.getY())) { return true; } return false; }
然后是绘制
一开始的想法是判断成功后先画地板,然后man的y坐标跟着移动
实践后发现这样卡顿效果明显,可以往上翻翻看,我们的绘制都涉及到画布的保存回收等
canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.translate(x, y); canvas.restore();
也就是这样才看上去才卡顿
然后就是第二种想法,可不可以给地板一个Man对象,如果落到这个地板上,则地板绘制的时候连着小人一起画?
for (Floor f : floors) { if (pre_calcute_pomb(f)) {//find the floor the man will on,if have f.setWith(man); f.draw(mCanvas, floor_rectf); floor_touch = f; isClick = true; } else { f.draw(mCanvas, floor_rectf); } } if (!isClick) { man.trans_y(man_down_speed); man.draw(mCanvas, man_Rectf); if (floor_touch != null) floor_touch.setUnWith(); man_down_speed++; } else { man_down_speed = 0; }
判断,如果落下了就把当前man绑定到这个floor中,然后还记得标记,否则等下原先的man也绘制,屏幕上老蹦出小人
public void setWith(Man m) { man=m; isWith=true; } public void setUnWith() { isWith=false; } public void draw(Canvas canvas,RectF rectf) { canvas.save(Canvas.MATRIX_SAVE_FLAG); canvas.translate(x, y); canvas.drawBitmap(mBitmap,null,rectf,null); canvas.restore(); if(isWith) { man.setY(this.y-man.getHeight()/2- this.height/2); man.draw(canvas); } }
这样相对静止的效果看上去还是很舒服的;
4.碰撞到顶部的时候地板静止,小人掉落
这个时候就要说到我们程序的状态了,分为三个
private enum Game_State { WAITTING, RUNNING, STOP; }
一开始是准备阶段WAITTING
点击后是RUNNING
死亡后是STOP
这个时候只要不执行地板移动以及碰撞检测,让小人死尸式掉落就行啦
private void draw_Stop() { //let the man flow down if (man.getY() <= game_height) { long start = System.currentTimeMillis(); mCanvas = surfaceHolder.lockCanvas(); if (mCanvas != null) { Paint mPaint = new Paint(); mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));//clear the canvas mCanvas.drawPaint(mPaint); background.draw(mCanvas); for (Floor f : floors) { f.draw(mCanvas, floor_rectf); } man.trans_y(man_down_speed); man.draw(mCanvas, man_Rectf); if (floor_touch != null) floor_touch.setUnWith(); man_down_speed++; drawGrades(mCanvas); if (mCanvas != null) { surfaceHolder.unlockCanvasAndPost(mCanvas); } long end = System.currentTimeMillis(); if (end - start < 50) { try { Thread.sleep(50 - end + start); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
一开始脑抽了下想保存当前画布,不清除的,直接画小人就行
结果。。。一排小人井喷= =
5.利用手机翻转控制小人左右移动
这个算是亮点了吧,记不记得上面那个提供出来的函数
/**set the man ori,-1 left ;0 center;1 right*/ public void setManOri(int x) { if (man != null) this.man.setOri(x); }
这里就要用到它啦~当前我们是在完成一个View,不好监控手机倾斜等,就暴露出这个函数,具体的监控程序放在Activity中,主要是请求两个传感器
public class BeAManAct extends Activity { GameBeAMan gameBeAMan; SensorManager sensorManager; /** * use to get asset */ Context mContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); gameBeAMan = new GameBeAMan(this); setContentView(gameBeAMan); /**Sensor*/ sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); Sensor magnetticsensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);//方向传感器 Sensor acclerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); sensorManager.registerListener(listener, magnetticsensor, SensorManager.SENSOR_DELAY_GAME); sensorManager.registerListener(listener, acclerSensor, SensorManager.SENSOR_DELAY_GAME); } @Override protected void onDestroy() { super.onDestroy(); if (sensorManager != null) { sensorManager.unregisterListener(listener); } } private SensorEventListener listener = new SensorEventListener() { float[] acceValues = new float[3]; float[] magnetValues = new float[3]; @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { acceValues = sensorEvent.values.clone(); } else if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { magnetValues = sensorEvent.values.clone(); } float[] R = new float[9]; float[] values = new float[3]; SensorManager.getRotationMatrix(R, null, acceValues, magnetValues); SensorManager.getOrientation(R, values); if (values[2] < -0.1) { gameBeAMan.man_runleft(); gameBeAMan.setManOri(-1); } else if (values[2] > 0.1) { gameBeAMan.man_runright(); gameBeAMan.setManOri(1); }else{ gameBeAMan.setManOri(0); } } @Override public void onAccuracyChanged(Sensorsensor, int i) { } };}
看上去有点长,没关系,我们慢慢看:
想要请求系统服务,下面这步必不可少
sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
然后从经理:)那里求两个传感器
Sensor magnetticsensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);//方向传感器 Sensor acclerSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); //加速度传感器 //然后注册监听者(好吧不会起名字= =) sensorManager.registerListener(listener, magnetticsensor, SensorManager.SENSOR_DELAY_GAME); sensorManager.registerListener(listener, acclerSensor, SensorManager.SENSOR_DELAY_GAME);
然后就是监听器内部的代码了,这个东西理解起来还是可以的,要死记也行。。
private SensorEventListener listener = new SensorEventListener() { float[] acceValues = new float[3]; float[] magnetValues = new float[3]; @Override public void onSensorChanged(SensorEvent sensorEvent) { if (sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { acceValues = sensorEvent.values.clone(); } else if (sensorEvent.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) { magnetValues = sensorEvent.values.clone(); } float[] R = new float[9]; float[] values = new float[3]; SensorManager.getRotationMatrix(R, null, acceValues, magnetValues); SensorManager.getOrientation(R, values); if (values[2] < -0.1) { gameBeAMan.man_runleft(); gameBeAMan.setManOri(-1); } else if (values[2] > 0.1) { gameBeAMan.man_runright(); gameBeAMan.setManOri(1); }else{ gameBeAMan.setManOri(0); } } @Override public void onAccuracyChanged(Sensor sensor, int i) { } };
我们直接跳步,前面的都是标配,没啥好讲
SensorManager.getOrientation(R, values);
跟经理要方向信息,储存在三位数组中
盗图的,来自
http://www.cnblogs.com/mengdd/archive/2013/05/19/3086781.html
临时找个,这位先生写的也不错,推荐看看
继续
其中value[0]是x轴,values[1]z轴,values[2]y轴
咱们手机都是正面朝上放在手里的嘛,这里我们监控倾斜就要用到y轴
一开始闹了小笑话,我是看参考书搞这个的:(,人家说变化范围是-180到180,我就信了,监控的时候也这么写,结果死活不出效果,后来自己用Log监听才乐呵了,人家的数值范围是-1到1= =
if (values[2] < -0.1) { gameBeAMan.man_runleft(); gameBeAMan.setManOri(-1); } else if (values[2] > 0.1) { gameBeAMan.man_runright(); gameBeAMan.setManOri(1); }else{ gameBeAMan.setManOri(0); }
这里我们持续监听,用那个开放的函数,实时改变状态以及坐标值
那个Run函数只是改变x值而已
/**change the man's x to left*/ public void man_runleft() { if (man != null) man.setX(man.getX() - x_moveSpeed); }
好了,来填坑了。。
说实话,写这个跟用github我都是第一次,多有疏漏希望读者们谅解并提出来,大家共同进步~
源代码:
最终效果:
https://github.com/SGZoom/GZoomDaily
谢谢观看~
- Android SurfaceView仿“是男人就下一百层”
- html5仿一flash游戏,是男人就下一百层
- 是男人就下100层(简仿)
- 是男人就下100层【第三层】——高仿交通银行手机客户端界面
- 是男人就下100层【第六层】——高仿豆瓣客户端
- 是男人就下100层
- 自己写的第一个android 游戏《是男人就下100层》
- 是男人就下100层 1.0 beta
- flash版小游戏:是男人就下100层
- flash版小游戏:是男人就下100层
- cocos2d-x 是男人就下100层 附源码
- cocos2d-x 是男人就下100层 附源码
- “奔跑吧,牛客“---是男人就下100层
- 【编程马拉松】【026-是男人就下100层】
- 是男人就下100层(小游戏)
- 是男人就下100层【第一层】——高仿微信界面(1)
- 是男人就下100层【第一层】——高仿微信界面(2)
- 是男人就下100层【第一层】——高仿微信界面(3)
- js正则表达式具体时间的验证,当前日期跟所填日期比较大小,时间的比较
- java I/O流 文件的生成及筛选
- 【一次国际会议经历】NDSS‘ 16以及出席国际会议种种
- 杭电2068RPG的错排
- 负载均衡中的四层负载和七层负载
- Android SurfaceView仿“是男人就下一百层”
- 对vim配置高亮,自动缩进等功能。
- Java位操作符使用总结
- Apache 修改端口号
- python list定义并初始化长度 以及range()
- 安卓adb.exe无法启动
- java文件的生成及筛选类型文件
- boost基础——any(二)
- Day02 CSS,JAVAScript