程序员人生 网站导航

Android SurfaceView实战 带你玩转flabby bird (上)

栏目:综合技术时间:2015-03-04 08:13:05
Ÿএ加௧拫௭5300:Oracle中su切换进去sqlplus登录失败的问题处理ing膘Ÿএ䚈௮霋ೊ51738说说阿里增量计算框架Galaxy :增量计算模型 (二)逬

背景

在前1篇文章中,介绍到了Galaxy的增量计算性质,其state是框架内部管理的,和与Storm的简单对照。这篇文章将讲述更多Galaxy增量模型的事情,并介绍这套增量模型之上实现的Galaxy SQL和Galaxy Operator,同时会从增量角度对照Spark Streaming。


Galaxy MRM增量与Spark Streaming

MRM模型全称为MapReduceMerge,比MapReduce做了1个Merge操作。merge阶段可与state交互,读写某个key的oldValue,并且这个merge接口还具有rollback语义。在流计算场景下,数据按时间或条数切成不同的批,批内可以做普遍意义下的MapReduce操作,批之间需要merge阶段做跨批聚合的计算。大家可以对照Spark Streaming的UpdateStateByKey操作,在1个DStream内,各个时间段内的RDD(即各批)可以通过这个接口更新1次任务内的state。而galaxy的merge本质上是1次add的进程,对应的rollback是1次delete的进程,从数据库的语义看,两个进程合起来相当因而update操作,而这俩进程都是根据1个primary key来做的,所以这件事情与spark streaming的updateStateByKey做的事情是1样的,但是细看的话,二者还是存在很大的差异。



问题描写:

生产环境的oracle数据库突然登录不上去了,rlwrap生产环境的oracle数据库突然登录不上去了,rlwrap sqlplus "/ as sysdba"报错以下:rlwrap
[oracle@localhost root]$ rlwrap sqlplus "/ as sysdba"
rlwrap: warning: your $TERM is 'xterm' but rlwrap couldn't find it in the terminfo database. Expect some problems.
rlwrap: Cannot execute sqlplus: Permission denied
“p” command prints the buffer (remember to use -n option with “p”) 
“d” command is just opposite, its for deletion. ‘d’ will delete the pattern space buffer and immediately starts the next cycle. 

Syntax: 
# sed 'ADDRESS'd filename 
# sed /PATTERN/d filename 

Let us first creates thegeekstuff.txt file that will be used in all the examples mentioned below. 
# cat thegeekstuff.txt 
1. Linux - Sysadmin, Scripting etc. 
2. Databases - Oracle, mySQL etc. 
3. Hardware 
4. Security (Firewall, Network, Online Security etc) 
5. Storage 
6. Cool gadgets and websites 
7. Productivity (Too many technologies to explore, not much time available) 
8. Website Design 
9. Software Development 
10.Windows- Sysadmin, reboot etc. 

例子1:删除第n行 
sed ‘Nd’ filename 

As per sed methodology, 
It reads the first line and places in its pattern buffer. 
Check whether supplied command is true for this line, if true, deletes pattern space buffer and starts next cycle. i.e Read next line. 
If supplied command doesnt true, as its normal behaviour it prints the content of the pattern space buffer. 


再来张动态的:


由于上传图片最大限制为2M,所以做了紧缩处理,凑合看吧 ~~~


2、分析

仔细视察游戏,需要绘制的有:背景、地板、鸟、管道、分数;

游戏开始时:

地板给人1种想左移动的感觉;

管道与地板一样的速度向左移动;

鸟默许着落;

当用户touch屏幕时,鸟上升1段距离后,着落;

运动进程中需要判断管道和鸟之间的位置关系,是不是触碰,是不是穿过等,需要计算分数。

好了,大概就这么多,那我们首先开始斟酌绘制~~~


3、SurfaceView的1般写法

接下来,我们首先编写下SurfaceView的1般写法:

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); } } }

这个基础的类,在Android SurfaceView实战 打造抽奖转盘已出现过,就不多说了,大家以后写SurfaceView的相干程序,可以直接拷贝,在此类基础上编写。


4、绘制


1、绘制背景

最简单确当然是背景了,直接drawBitmap便可。

我们添加需要的成员变量,和初始化1些参数,然后添加drawBg方法,最后在draw中调用drawBg;

public class CopyOfGameFlabbyBird extends SurfaceView implements Callback, Runnable { /** * 当前View的尺寸 */ private int mWidth; private int mHeight; private RectF mGamePanelRect = new RectF(); /** * 背景 */ private Bitmap mBg; public CopyOfGameFlabbyBird(Context context, AttributeSet attrs) { //省略了很多代码 initBitmaps(); } /** * 初始化图片 */ private void initBitmaps() { mBg = loadImageByResId(R.drawable.bg1); } private void draw() { //省略了很多代码 drawBg(); //省略了很多代码 } /** * 绘制背景 */ private void drawBg() { mCanvas.drawBitmap(mBg, null, mGamePanelRect, null); } /** * 初始化尺寸相干 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; mGamePanelRect.set(0, 0, w, h); } /** * 根据resId加载图片 * * @param resId * @return */ private Bitmap loadImageByResId(int resId) { return BitmapFactory.decodeResource(getResources(), resId); } }

基本就是添加成员变量,然后初始化,然后绘制,上述代码经过删减,贴出的都是与前面基础代码不同的部份,大家可以将代码对号入坐进行填充。

好了,现在背景图绘制好了,接下来,我们绘制小鸟~~~


2、绘制bird

鸟在我们的屏幕中,初始化时需要1个位置,x上,肯定是居中,y上我们取2/3的高度;

关于bird,我们单独创建1个类:

package com.zhy.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RectF; public class Bird { /** * 鸟在屏幕高度的2/3位置 */ private static final float RADIO_POS_HEIGHT = 2 / 3F; /** * 鸟的宽度 30dp */ private static final int BIRD_SIZE = 30; /** * 鸟的横坐标 */ private int x; /** * 鸟的纵坐标 */ private int y; /** * 鸟的宽度 */ private int mWidth; /** * 鸟的高度 */ private int mHeight; /** * 鸟的bitmap */ private Bitmap bitmap; /** * 鸟绘制的范围 */ private RectF rect = new RectF(); public Bird(Context context, int gameWith, int gameHeight, Bitmap bitmap) { this.bitmap = bitmap; //鸟的位置 x = gameWith / 2 - bitmap.getWidth() / 2; y = (int) (gameHeight * RADIO_POS_HEIGHT); // 计算鸟的宽度和高度 mWidth = Util.dp2px(context, BIRD_SIZE); mHeight = (int) (mWidth * 1.0f / bitmap.getWidth() * bitmap.getHeight()); } /** * 绘制自己 * * @param canvas */ public void draw(Canvas canvas) { rect.set(x, y, x + mWidth, y + mHeight); canvas.drawBitmap(bitmap, null, rect, null); } public int getY() { return y; } public void setY(int y) { this.y = y; } public int getWidth() { return mWidth; } public int getHeight() { return mHeight; } }

定义了1个类,代表我们的鸟,和1堆成员变量,并且提供1个draw方法对外;

在GameFlabbyBird中,只需要,初始化我们的Bird,在draw里面调用bird.draw便可;

部份筛检后代码:

public class CopyOfGameFlabbyBird extends SurfaceView implements Callback, Runnable { /** * *********鸟相干********************** */ private Bird mBird; private Bitmap mBirdBitmap; /** * 初始化图片 */ private void initBitmaps() { mBg = loadImageByResId(R.drawable.bg1); mBirdBitmap = loadImageByResId(R.drawable.b1); } private void draw() { // drawSomething.. drawBg(); drawBird(); } private void drawBird() { mBird.draw(mCanvas); } /** * 初始化尺寸相干 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 初始化mBird mBird = new Bird(getContext(), mWidth, mHeight, mBirdBitmap); } }
是否是很简单,下面看下此时效果图:

Activity里面这么调用便可:

package com.zhy.surfaceViewDemo; import com.zhy.view.GameFlabbyBird; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { GameFlabbyBird mGame; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); requestWindowFeature(Window.FEATURE_NO_TITLE); mGame = new GameFlabbyBird(this); setContentView(mGame); } }

效果图:

不管咋样,我们的鸟已在指定的位置了~~~有木有1点小激动~~

下面开始添加地板;

3、绘制地板

绘制地板相比来讲会难1点,由于我们需要斟酌怎样让地板运动,起初我截取了两个大图,希望通过两张图不断变化,产生动画效果,but,动画的太卡,有跳跃感;

因而,我忽然想到了1个东西可以做,我就把基础图变成了这样:

很小的1块图,先不斟酌运动,如何填充成我们目标效果呢?

还记得有个类叫做BitmapShader么?我们可以利用它进行填充。

相干知识可以参考:Android BitmapShader 实战 实现圆形、圆角图片

首先我们照旧是定义1个地板类:Floor

package com.zhy.view; import java.util.concurrent.TimeUnit; import com.zhy.surfaceViewDemo.Config; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapShader; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.Shader.TileMode; public class Floor { /* * 地板位置游戏面板高度的4/5到底部 */ private static final float FLOOR_Y_POS_RADIO = 4 / 5F; // height of 4/5 /** * x坐标 */ private int x; /** * y坐标 */ private int y; /** * 填充物 */ private BitmapShader mFloorShader; private int mGameWidth; private int mGameHeight; public Floor(int gameWidth, int gameHeight, Bitmap floorBg) { mGameWidth = gameWidth; mGameHeight = gameHeight; y = (int) (gameHeight * FLOOR_Y_POS_RADIO); mFloorShader = new BitmapShader(floorBg, TileMode.REPEAT, TileMode.CLAMP); } /** * 绘制自己 * * @param mCanvas * @param mPaint */ public void draw(Canvas mCanvas, Paint mPaint) { if (-x > mGameWidth) { x = x % mGameWidth; } mCanvas.save(Canvas.MATRIX_SAVE_FLAG); //移动到指定的位置 mCanvas.translate(x, y); mPaint.setShader(mFloorShader); mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint); mCanvas.restore(); mPaint.setShader(null); } public int getX() { return x; } public void setX(int x) { this.x = x; } }

定义了1堆成员变量,核心就在于,我们传入地板背景的填充物,然后初始化我们的mFloorShader,横向重复,纵向拉伸(这里的拉伸是指,纵向的最后1个像素不断重复)。

我们对外公布了draw方法,传入Canvas,我们首先调用canvas.save(),然后将canvas移动到指定的位置,然后绘制我们的矩形,矩形的填充就是我们的地板了~~;

这里,注意1下,我们这里使用了1个变量x,而不是0;为何呢?由于我们的地板需要利用这个x运动。

那末现在我们如何才能动呢?

首先我们在GameFlabbyBird定义1个变量,表示移动速度mSpeed,然后在draw中不断更新mFloor的x坐标为:mFloor.setX(mFloor.getX() - mSpeed);

这样的画,每次绘制我们floor的出发点,会向左移动mSpeed个位置,就构成了运行的效果;但是呢?不能1直减下去,不然终究我们的x岂不是负无穷了,那得绘制多大?

所以我们:

if (-x > mGameWidth)
{
x = x % mGameWidth;
}

如果x的正值大于宽度了,我们取余1下~~~

终究我们的绘制范围是:

mCanvas.drawRect(x, 0, -x + mGameWidth, mGameHeight - y, mPaint);

ok,贴下筛检后GameFlabbyBird代码:

package com.zhy.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PixelFormat; import android.graphics.RectF; import android.util.AttributeSet; import android.util.Log; import android.view.SurfaceHolder; import android.view.SurfaceHolder.Callback; import android.view.SurfaceView; import com.zhy.surfaceViewDemo.R; public class CopyOfGameFlabbyBird extends SurfaceView implements Callback, Runnable { private Paint mPaint; /** * 地板 */ private Floor mFloor; private Bitmap mFloorBg; private int mSpeed; public CopyOfGameFlabbyBird(Context context, AttributeSet attrs) { super(context, attrs); mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setDither(true); initBitmaps(); // 初始化速度 mSpeed = Util.dp2px(getContext(), 2); } /** * 初始化图片 */ private void initBitmaps() { mFloorBg = loadImageByResId(R.drawable.floor_bg2); } private void draw() { // drawSomething.. drawBg(); drawBird(); drawFloor(); // 更新我们地板绘制的x坐标 mFloor.setX(mFloor.getX() - mSpeed); } private void drawFloor() { mFloor.draw(mCanvas, mPaint); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { // 初始化地板 mFloor = new Floor(mWidth, mHeight, mFloorBg); } }

其实很简单,就是声明几个变量,初始化1下;记得在draw中更新mFloor的x便可。

现在的效果:


好了,最后剩下个管道了~~~


4、绘制管道

然后是写弄1个管道类Pipe,注意我们的管道分为上下,每一个管道的高度可能不同,所以会多1些成员变量;

package com.zhy.view; import java.util.Random; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.RectF; /** * 管道分为上下 * * @author zhy * */ public class Pipe { /** * 上下管道间的距离 */ private static final float RADIO_BETWEEN_UP_DOWN = 1 / 5F; /** * 上管道的最大高度 */ private static final float RADIO_MAX_HEIGHT = 2 / 5F; /** * 上管道的最小高度 */ private static final float RADIO_MIN_HEIGHT = 1 / 5F; /** * 管道的横坐标 */ private int x; /** * 上管道的高度 */ private int height; /** * 上下管道间的距离 */ private int margin; /** * 上管道图片 */ private Bitmap mTop; /** * 下管道图片 */ private Bitmap mBottom; private static Random random = new Random(); public Pipe(Context context, int gameWidth, int gameHeight, Bitmap top, Bitmap bottom) { margin = (int) (gameHeight * RADIO_BETWEEN_UP_DOWN); // 默许从最左侧出现 x = gameWidth; mTop = top; mBottom = bottom; randomHeight(gameHeight); } /** * 随机生成1个高度 */ private void randomHeight(int gameHeight) { height = random .nextInt((int) (gameHeight * (RADIO_MAX_HEIGHT - RADIO_MIN_HEIGHT))); height = (int) (height + gameHeight * RADIO_MIN_HEIGHT); } public void draw(Canvas mCanvas, RectF rect) { mCanvas.save(Canvas.MATRIX_SAVE_FLAG); // rect为全部管道,假定完全管道为100,需要绘制20,则向上偏移80 mCanvas.translate(x, -(rect.bottom - height)); mCanvas.drawBitmap(mTop, null, rect, null); // 下管道,便宜量为,上管道高度+margin mCanvas.translate(0, (rect.bottom - height) + height + margin); mCanvas.drawBitmap(mBottom, null, rect, null); mCanvas.restore(); } public int getX() { return x; } public void setX(int x) { this.x = x; } }

我们直接看draw方法,我们的传入的rect是固定的1个矩形,我们的上下管道都是完全的绘制在这个rect中;

然后根据height,去偏移canvas的y,让rect显示出height部份,主要是由于,这样可以保证每一个管道模样是1样的(如果根据height,使用不同的rect,会产生缩放);

Pipe写好了~~我们需要在GameFlabbyBird中去使用;但是斟酌1下,游戏中的管道不像鸟和地面,有很多个,且是在运行中不断生成新的~~~

所以我们保存Pipe最最少是个List<Pipe>

筛检后的代码:

public class GameFlabbyBird extends SurfaceView implements Callback, Runnable { /** * *********管道相干********************** */ /** * 管道 */ private Bitmap mPipeTop; private Bitmap mPipeBottom; private RectF mPipeRect; private int mPipeWidth; /** * 管道的宽度 60dp */ private static final int PIPE_WIDTH = 60; private List<Pipe> mPipes = new ArrayList<Pipe>(); public GameFlabbyBird(Context context, AttributeSet attrs) { super(context, attrs); mPipeWidth = Util.dp2px(getContext(), PIPE_WIDTH); } /** * 初始化图片 */ private void initBitmaps() { ; mPipeTop = loadImageByResId(R.drawable.g2); mPipeBottom = loadImageByResId(R.drawable.g1); } private void draw() { drawBg(); drawBird(); drawPipes(); drawFloor(); } /** * 绘制管道 */ private void drawPipes() { for (Pipe pipe : mPipes) { pipe.setX(pipe.getX() - mSpeed); pipe.draw(mCanvas, mPipeRect); } } /** * 初始化尺寸相干 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 初始化管道范围 mPipeRect = new RectF(0, 0, mPipeWidth, mHeight); Pipe pipe = new Pipe(getContext(), w, h, mPipeTop, mPipeBottom); mPipes.add(pipe); } }

我们在onSizeChanged中初始化了1个Pipe,添加到了mPipes中,然后在draw里面,动态改变Pipe的x为pipe.setX(pipe.getX() - mSpeed);

下面来看下效果:


我们的管道从右边进入界面,然后消失在左边~

固然了,关于管道还有很多需要编写,比如管道每隔多远生成1个,也不能让无穷生成,当管道从界面移除应当从mPipes中移出;

和判断管道和鸟的碰撞,这些都放置到下1篇博客叙述~~


5、绘制分数

分数的绘制比较简单,我准备了10个图,对应于0⑼

没有单独定义类了,直接写了~~

筛检后的代码:

public class GameFlabbyBird extends SurfaceView implements Callback, Runnable { /** * 分数 */ private final int[] mNums = new int[] { R.drawable.n0, R.drawable.n1, R.drawable.n2, R.drawable.n3, R.drawable.n4, R.drawable.n5, R.drawable.n6, R.drawable.n7, R.drawable.n8, R.drawable.n9 }; private Bitmap[] mNumBitmap; private int mGrade = 100; /** * 单个数字的高度的1/15 */ private static final float RADIO_SINGLE_NUM_HEIGHT = 1 / 15f; /** * 单个数字的宽度 */ private int mSingleGradeWidth; /** * 单个数字的高度 */ private int mSingleGradeHeight; /** * 单个数字的范围 */ private RectF mSingleNumRectF; /** * 初始化图片 */ private void initBitmaps() { mNumBitmap = new Bitmap[mNums.length]; for (int i = 0; i < mNumBitmap.length; i++) { mNumBitmap[i] = loadImageByResId(mNums[i]); } } private void draw() { // drawSomething.. drawBg(); drawBird(); drawPipes(); drawFloor(); drawGrades(); } /** * 绘制分数 */ private void drawGrades() { String grade = mGrade + ""; mCanvas.save(Canvas.MATRIX_SAVE_FLAG); mCanvas.translate(mWidth / 2 - grade.length() * mSingleGradeWidth / 2, 1f / 8 * mHeight); // draw single num one by one for (int i = 0; i < grade.length(); i++) { String numStr = grade.substring(i, i + 1); int num = Integer.valueOf(numStr); mCanvas.drawBitmap(mNumBitmap[num], null, mSingleNumRectF, null); mCanvas.translate(mSingleGradeWidth, 0); } mCanvas.restore(); } /** * 初始化尺寸相干 */ @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 初始化分数 mSingleGradeHeight = (int) (h * RADIO_SINGLE_NUM_HEIGHT); mSingleGradeWidth = (int) (mSingleGradeHeight * 1.0f / mNumBitmap[0].getHeight() * mNumBitmap[0].getWidth()); mSingleNumRectF = new RectF(0, 0, mSingleGradeWidth, mSingleGradeHeight); } }

我们定义了单个数字的范围,然后假定现在为100分,注意在绘制的时候,直接提取数字,把数字作为下标,找到对的图片进行绘制;

绘制前,根据数字的位数,对画布进行偏移到中心位置,然后绘制;绘制进程中,每绘制完成1个数字则偏移1个数字的宽度;

现在的效果:


ok,到此为止,我们完成了所有需要绘制的东西~~由于篇幅缘由,下1篇,将在此基础上完善剩下的所有内容~~~

有兴趣的,可以在此基础上直接尝试写了~~~



源码点击下载



建了1个QQ群,方便大家交换。群号:423372824

----------------------------------------------------------------------------------------------------------

博主部份视频已上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):

视频目录地址:本人录制的视频教程














------分隔线----------------------------
------分隔线----------------------------

最新技术推荐