看看这本书 -《Android游戏编程之从零开始》
Day_1 2019/01/08
开发之前需要熟悉
· View(视图)
· Canvas(画布)
· Paint(画笔)
· 刷屏的概念:对画布刷新来实现动态效果
视图的含义
· View:显示视图,内置画布,提供图形绘制函数、触屏事件、按键事件函数等
· SurfaceView: 基于View视图进行拓展的试图类,更适用于2D游戏开发
· GLSurfaceView:基于SurfaceView视图再次进行拓展的试图类,专用于3D游戏开发的视图
游戏框架:
1.View游戏框架
2.SurfaceView游戏框架
手机XY坐标
动态效果的实现方式
- 不断的绘制新的画布
- 使用一张画布,通过刷屏来让这张画布恢复到初始空白画布的状态,然后再向画布上进行绘制
View框架
public class MyView extends View
{
private int textX = 20;
private int textY = 20;
/**
* 重新父类构造函数
*/
public MyView( Context context )
{
super( context );
setFocusable( true );
}
/**
* 重写父类绘图函数
* 只执行一遍
*/
@Override
protected void onDraw( Canvas canvas )
{
//画笔
Paint paint = new Paint();
//设置颜色
paint.setColor( Color.BLACK );
//绘制文本
canvas.drawText( "Game" , textX , textY , paint );
super.onDraw( canvas );
}
/**
* 重新父类按键按下事件函数
* keyCode:用户点击的按键
* event:按键的动作事件队列
*/
@Override
public boolean onKeyDown(
int keyCode ,
KeyEvent event )
{
if( keyCode == KeyEvent.KEYCODE_DPAD_UP )
{
//"上"按键被点击
textY -= 2;
}
else if( keyCode == KeyEvent.KEYCODE_DPAD_DOWN )
{
//"下"按键被点击
textY += 2;
}
else if( keyCode == KeyEvent.KEYCODE_DPAD_LEFT )
{
//"左"按键被点击
textX -= 2;
}
else if( keyCode == KeyEvent.KEYCODE_DPAD_RIGHT )
{
//"右"按键被点击
textX += 2;
}
//不能在当前线程中循环调用(子线程)
invalidate();
//可以在子线程中循环调用使用
//postInvalidate();
return super.onKeyDown( keyCode , event );
}
/**
* 重新父类按键抬起事件函数
*/
@Override
public boolean onKeyUp(
int keyCode ,
KeyEvent event )
{
return super.onKeyUp( keyCode , event );
}
/**
* 重新触屏事件函数
*/
@Override
public boolean onTouchEvent( MotionEvent event )
{
//int x = ( int )event.getX();
//int y = ( int )event.getY();
//if( event.getAction() == MotionEvent.ACTION_DOWN )
//{
// //按下手指
// textX = x;
// textY = y;
//}
//else if( event.getAction() == MotionEvent.ACTION_MOVE )
//{
// //屏幕移动
// textX = x;
// textY = y;
//}
//else if( event.getAction() == MotionEvent.ACTION_UP )
//{
// //抬起手指
// textX = x;
// textY = y;
//}
//invalidate();
//postInvalidate();
textX = ( int )event.getX();
textY = ( int )event.getY();
invalidate();
//postInvalidate();
return true;
}
}
SurfaceView框架
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback
{
private int textX = 10;
private int textY = 10;
//用于控制SurfaceView
private SurfaceHolder holder;
private Paint paint;
public MySurfaceView( Context context )
{
super( context );
holder = this.getHolder();
//添加监听状态
holder.addCallback( this );
paint = new Paint();
paint.setColor( Color.WHITE );
}
//SurfaceHolder.Callback接口回调===================================
/**
* 当SurfaceView被创建完成后响应的函数
*/
@Override
public void surfaceCreated( SurfaceHolder surfaceHolder )
{
myDraw();
}
/**
* 当SurfaceView状态发生改变时想要的函数
*/
@Override
public void surfaceChanged(
SurfaceHolder surfaceHolder ,
int i ,
int i1 ,
int i2 )
{
}
/**
* 当SurfaceView状态被摧毁时响应的函数
*/
@Override
public void surfaceDestroyed( SurfaceHolder surfaceHolder )
{
}
//SurfaceHolder.Callback接口回调===================================
/**
* 自定义绘图函数
* 不能使用onDraw来绘图
*/
public void myDraw()
{
//加锁,防止绘制过程中被修改、摧毁
//获取的都是同一张画布,多次调用时会出现绘之过的图形
Canvas canvas = holder.lockCanvas();
//刷屏方式1 绘制一个等同屏幕大小的图形覆盖在画布上
canvas.drawRect( 0 , 0 , this.getWidth() , this.getHeight() , paint );
//刷屏方式2 在画布上填充一种颜色
canvas.drawColor( Color.BLACK );
//刷屏方式3 指定RGB来填充
canvas.drawRGB( 0 , 0 , 0 );
//刷屏方式4 绘制一张等同于屏幕大小的图片覆盖在画布上-绘制背景图
canvas.drawText( "Game" , textX , textY , paint );
holder.unlockCanvasAndPost( canvas );
}
/**
* 触屏监听
*/
@Override
public boolean onTouchEvent( MotionEvent event )
{
textX = ( int )event.getX();
textY = ( int )event.getY();
myDraw();
//使用return super.onTouchEvent( event );会监听不到MOVE事件
return true;
}
}
在游戏中,基本不会等用户每次触发了按键事件、触屏事件才重新绘制画布,而是会固定一个时间去刷新画布。所以在游戏中,都会有一个线程不停的去重绘画布,实时的更新游戏元素的状态
· 点击“BACK”按钮使当前程序切入后台,然后单击项目回到程序中,SurfaceView的状态变化为:
surfaceDestroy->构造函数->surfaceCreated->surfaceChanged
· 然后单击“HOME”按钮使当前程序切入后台,单击项目重新回到程序中,SurfaceView的状态变化为:
surfaceDestroy->surfaceCreated->surfaceChanged
注意:千万不要把线程的初始化放在surfaceCreated视图创建函数之前,而线程的启动却放在surfaceCreated视图创建的函数中,否则程序一旦被玩家点击“HOME”按钮后再返回到游戏时,会抛出异常。
public class MySurfaceView extends SurfaceView
implements SurfaceHolder.Callback, Runnable
{
//用于控制SurfaceView
private SurfaceHolder holder;
private Paint paint;
private int textX = 10;
private int textY = 10;
private Thread thread;
//线程消亡的标识位
private boolean flag;
private Canvas canvas;
//屏幕宽高
private int screenW;
private int screenH;
public MySurfaceView( Context context )
{
super( context );
holder = this.getHolder();
//添加监听状态
holder.addCallback( this );
paint = new Paint();
paint.setColor( Color.WHITE );
setFocusable( true );
}
//SurfaceHolder.Callback接口回调===================================
/**
* 当SurfaceView被创建完成后响应的函数
*/
@Override
public void surfaceCreated( SurfaceHolder surfaceHolder )
{
//获取宽高一定要在视图创建之后才能获取到,在此之前获取到的都是0
screenW = this.getWidth();
screenH = this.getHeight();
flag = true;
thread = new Thread( this );
thread.start();
}
/**
* 当SurfaceView状态发生改变时想要的函数
*/
@Override
public void surfaceChanged(
SurfaceHolder surfaceHolder ,
int i ,
int i1 ,
int i2 )
{
}
/**
* 当SurfaceView状态被摧毁时响应的函数
*/
@Override
public void surfaceDestroyed( SurfaceHolder surfaceHolder )
{
flag = false;
}
//SurfaceHolder.Callback接口回调===================================
/**
* 自定义绘图函数
* 不能使用onDraw来绘图
*/
public void myDraw()
{
//加锁,防止绘制过程中被修改、摧毁
//获取的都是同一张画布,多次调用时会出现绘之过的图形
Canvas canvas = holder.lockCanvas();
//刷屏方式1 绘制一个等同屏幕大小的图形覆盖在画布上
canvas.drawRect( 0 , 0 , this.getWidth() , this.getHeight() , paint );
//刷屏方式2 在画布上填充一种颜色
canvas.drawColor( Color.BLACK );
//刷屏方式3 指定RGB来填充
canvas.drawRGB( 0 , 0 , 0 );
//刷屏方式4 绘制一张等同于屏幕大小的图片覆盖在画布上-绘制背景图
canvas.drawText( "Game" , textX , textY , paint );
holder.unlockCanvasAndPost( canvas );
}
/**
* 自定义绘图函数2
* 不能使用onDraw来绘图
*/
public void myDraw2()
{
/**
* 使用try的原因:
* 当SurfaceView不可编辑或尚未创建时
* holder.lockCanvas();有可能会返回null
*/
try
{
canvas = holder.lockCanvas();
if( canvas != null )
{
//刷屏
canvas.drawRGB( 0 , 0 , 0 );
canvas.drawText( "Game" , textX , textY , paint );
}
}
catch( Exception e )
{
//TODO:handle exception
}
finally
{
//保证在出错的情况下也能执行解锁画布
if( canvas != null )
{
holder.unlockCanvasAndPost( canvas );
}
}
}
/**
* 触屏监听
*/
@Override
public boolean onTouchEvent( MotionEvent event )
{
textX = ( int )event.getX();
textY = ( int )event.getY();
//myDraw();
//使用return super.onTouchEvent( event );会监听不到MOVE事件return true;
}
/**
* 按键事件监听
*/
@Override
public boolean onKeyDown(
int keyCode ,
KeyEvent event )
{
return super.onKeyDown( keyCode , event );
}
/**
* 游戏逻辑
*/
private void logic()
{
}
@Override
public void run()
{
while( flag )
{
long start = System.currentTimeMillis();
myDraw2();
logic();
long end = System.currentTimeMillis();
//刷帧时间保持一致
//一般游戏中刷新时间在50-100毫秒之间,也就是10-20帧左右
try
{
if( end - start < 50 )
{
Thread.sleep( 50 - ( end - start ) );
}
}
catch( InterruptedException e )
{
e.printStackTrace();
}
}
}
}
View和SurfaceView的区别
- 更新画布
View:
是由系统主UI进行更新
postInvalidate()
invalidate()
有可能会出现UI线程被绘制函数阻塞而带来的一系列无法响应的问题
SurfaceView
单独线程去执行,不会出现因为UI线程阻塞而导致的无法响应按键、触屏等问题
2.视图机制
View:
没有双缓冲机制
SurfaceView:
有双缓冲机制,更适合的游戏开发的试图类
View和SurfaceView的选择
游戏画面的更新属于被动更新:依赖按键与触屏事件 - 使用View视图开发
例子:棋牌类游戏
很多元素都是动态的,需要不断的重绘元素状态 - 使用SurfaceView视图开发
例子:RPG、飞行射击类游戏
GitHub项目
156/406