public class SurfaceView extends View
SurfaceView是视图(View)的继承类,这个视图里内嵌了一个专门用于绘制的Surface。你可以控制这个Surface的格式和尺寸。Surfaceview控制这个Surface的绘制位置。
surfaceview的核心在于提供了两个线程:UI线程和渲染线程。
这里应注意:
1> 所有SurfaceView和SurfaceHolder.Callback的方法都应该在UI线程里调用,一般来说就是应用程序主线程。
渲染线程所要访问的各种变量应该作同步处理。
2> 由于surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之间有效,所以要确保渲染线程访问的是合法有效的surface。
可以在主线程之外的线程中向屏幕绘图,这样可以避免画图任务繁重的时候造成主线程阻塞,从而提高了程序的反应速度。
在游戏开发中多用到SurfaceView,游戏中的背景、人物、动画等等尽量在画布canvas中画出。
2、SurfaceHolder
public interface SurfaceHolder
显示一个surface的抽象接口,使你可以控制surface的大小和格式, 以及在surface上编辑像素,和监视surace的改变。
这个接口通常通过SurfaceView类实现。
surface的控制器,用来操纵surface,处理它的Canvas上画的效果和动画,控制表面,大小,像素等。
几个需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 给SurfaceView当前的持有者一个回调对象。
(2)、abstract Canvas lockCanvas();
// 锁定画布,一般在锁定后就可以通过其返回的画布对象Canvas,在其上面画图等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 锁定画布的某个区域进行画图等..因为画完图后,会调用下面的unlockCanvasAndPost来改变显示内容。
// 相对部分内存要求比较高的游戏来说,可以不用重画dirty外的其它区域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 结束锁定画图,并提交改变。
3、SurfaceHolder.Callback
用户可以实现此接口接收surface变化的消息。当用在一个SurfaceView 中时, 它只在SurfaceHolder.Callback.surfaceCreated()和SurfaceHolder.Callback.surfaceDestroyed()之间有效。设置Callback的方法是SurfaceHolder.addCallback.
实现上一般继承SurfaceView并实现SurfaceHolder.Callback接口
游戏:
分析场景
游戏的角色:小人 和 障碍物
跑起来的话,图片的轮播
障碍物,从右往左边移动
实现的第一步:
先让小人跑起来,让障碍物动起来(没有做碰撞检测)
图片轮播和界面的重画(surfview,线程)
第二步:
小人能够跳起来,并进行碰撞检测
完善:1.选择游戏难度???
2.游戏的时间
3.游戏死掉重开的模式的选择???
选择游戏的难度,决定了同时控制几个小人。
SurfaceView是一个视图提供两个线程:UI线程和渲染线程
注意:
1> SurfaceView和SurfaceHolder.Callback的方法都应该在我们的UI线程调用,一般就是我们应用程序的主线程
渲染线程所要访问的各种变量要做同步处理
2>由于Surface可能被销毁,它只在SurfaceHolder.Callback.surfaceCreated和SurfaceHolder.Callback.surfaceDestroyed
之间有效,所以要确保渲染线程访问的是合法的surface
SurfaceHolder显示一个Surface的抽象接口,是我们可以控制surface的大小和格式以及监听surface的改变
几个需要注意到额方法:
addCallBack
lockCanvas
unlockCanvasAndPOst
SurfaceHolder.Callback
用户可以实现此接口接收surface变化的消息
效果图:
角色类:
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
public class Role {
private Bitmap bitmap;//当前图片
private Bitmap[] bms;//完成一个动画的图片数组
float x,y;//自身的坐标
float speedX,speedY;//速度
float width,height;//自身的宽高
private long lastTime;
private int index;//记录当前图片在图片数组中的下标
private boolean isJump;//标记小人是否跳起来了
public Role(Bitmap[] bms){
this.bms = bms;
this.bitmap = bms[0];
this.width = bitmap.getWidth();
this.height = bitmap.getHeight();
}
public Bitmap getBitmap() {
return bitmap;
}
public void setBitmap(Bitmap bitmap) {
this.bitmap = bitmap;
}
public Bitmap[] getBms() {
return bms;
}
public void setBms(Bitmap[] bms) {
this.bms = bms;
}
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public float getSpeedX() {
return speedX;
}
public void setSpeedX(float speedX) {
this.speedX = speedX;
}
public float getSpeedY() {
return speedY;
}
public void setSpeedY(float speedY) {
this.speedY = speedY;
}
public float getWidth() {
return width;
}
public void setWidth(float width) {
this.width = width;
}
public float getHeight() {
return height;
}
public void setHeight(float height) {
this.height = height;
}
public boolean isJump() {
return isJump;
}
public void setJump(boolean isJump) {
this.isJump = isJump;
}
//把画板传进来,自己画自己
public void drawSelf(Canvas canvas){
if (System.currentTimeMillis() - lastTime >= 50){//间隔多久时间切换图片
index++;
if (index == bms.length){
index = 0;
}
bitmap = bms[index];
lastTime = System.currentTimeMillis();
}
//终于可以画自己了
canvas.drawBitmap(bitmap, this.getX(), this.getY(), null);
}
/**
* 获取任务的矩形
*/
public Rect getRectFromRole(){
Rect rect = new Rect();
rect.left = (int) this.getX();
rect.right = (int) (this.getX()+this.getWidth());
rect.top = (int) this.getY();
rect.bottom = (int) (this.getY()+this.getHeight());
return rect;
}
}
绘图线程类:
import java.util.Random;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import com.tz.tony.R;
import com.tz.tony.role.Role;
public class MyThread extends Thread {
private Context context;
private SurfaceHolder holder;
private int w,h;
Paint paint;
private int gameType;
private boolean isStart;//绘画任务是否开始
private Bitmap[] bms;//对应的小人的资源图片
private int[] paths;//对应的小人的资源图片的ID
//游戏状态
private int gameStatu = 0;
private int gameSpan;
//游戏的角色
public Role[] roles;
private Rect[] rects;
//记录游戏的时间
private long startTime,overTime;
public static final int RUNNING =0 ;
public static final int STANDOFF =1 ;
public static final int GAMEOVER =2 ;
public MyThread(Context context, SurfaceHolder holder, int w,int h, int gameType) {
this.context = context;
this.holder = holder;
this.w = w;
this.h = h;
this.gameType = gameType;
paint = new Paint();
isStart = true;//游戏启动
// 初始化资源图片
paths = new int[]{
R.drawable.role1_00,
R.drawable.role1_01,
R.drawable.role1_02,
R.drawable.role1_03,
R.drawable.role1_04,
R.drawable.role1_05,
};
bms = new Bitmap[paths.length];
for (int i = 0; i < paths.length; i++) {
//每循环一次根据paths加载对应的图片
Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), paths[i]);
bms[i] = bitmap;
}
//计算游戏的区间高度
gameSpan = h*4/(5*gameType);
roles = new Role[gameType];
rects = new Rect[gameType];
//初始化精灵
initSpirit();
}
/**
* 初始化精灵
*/
public void initSpirit() {
startTime = System.currentTimeMillis();
//创建人物
for (int i = 0; i < gameType; i++) {
// 第i根线的Y坐标
int lineY = h/10+(i+1)*gameSpan;
//创建小人,并初始化它的坐标
Role role = new Role(bms);
role.setX(w/8);
role.setY(lineY - role.getHeight());
roles[i] = role;
//创建障碍物
Rect rect = new Rect();
// 随机设置障碍物的宽高(假设小人的宽30 高60)
int random_w = (int) (roles[i].getWidth()*(Math.random()*5+1)/4);//(0.25--1.5)
int random_h = (int) (roles[i].getHeight()*(Math.random()*5+1)/4);//(0.25--1.5)
rect.left = 3*w/2;
rect.right = rect.left + random_w;
rect.bottom = lineY;
rect.top = rect.bottom - random_h;
//添加到数据
rects[i] = rect;
}
}
public void run() {
super.run();
//执行画画的任务
while(isStart){
//获取我们的画布
Canvas canvas = null;
try {
canvas = holder.lockCanvas();//讲获取的画布绑定到画板
if (canvas != null){
switch (gameStatu) {
//确定画板OK了就可以开始画
//根据游戏的几种进行话(1.正常的跑 2.僵持3.结束)
case RUNNING:
//正常的跑动
drawRuningView(canvas);
break;
case STANDOFF:
//僵持状态
drawStandOffView(canvas);
break;
case GAMEOVER:
//游戏结束
drawGameOverView(canvas);
break;
default:
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if (canvas != null){
holder.unlockCanvasAndPost(canvas);
}
}
}
}
/**
* 游戏结束状态
* @param canvas
*/
private void drawGameOverView(Canvas canvas) {
//游戏结束的状态
canvas.drawColor(Color.RED);
String[] modes = new String[]{"普通","噩梦","地狱","炼狱"};//对应控制的人数:2 ,3 ,4,5
String mode = modes[gameType - 2];
String scoreType = mode + ":"+(overTime-startTime)/1000f+"\'";
String back = "返回";
String reStart = "重来";
// 绘制三个文本
drawText(canvas, scoreType,h/2, 28);
drawText(canvas, back, 4*h/6, 24);
drawText(canvas, reStart, 5*h/6, 24);
}
/**
* 僵持状态
* @param canvas
*/
private void drawStandOffView(Canvas canvas) {
//僵持状态也要绘制对应的精灵
if (System.currentTimeMillis() - overTime >=1000){
//僵持状态停留1秒进入结束状态
gameStatu = GAMEOVER;
}else{
for (int i = 0; i < gameType; i++) {
//第i根线的Y坐标
int lineY = h/10+(i+1)*gameSpan;
//画一根碰撞的界面的线
canvas.drawLine(0, lineY, w, lineY, paint);
//画一个人
roles[i].drawSelf(canvas);
//绘制一个障碍物
canvas.drawRect(rects[i], paint);
}
}
}
private void drawRuningView(Canvas canvas) {
//画背景
paint.setStrokeWidth(5);
canvas.drawColor(Color.WHITE);
//绘制右上角的游戏得分
paint.setTextSize(20);
String score = (System.currentTimeMillis() - startTime)/1000f+"\'";
float scroe_w = paint.measureText(score);
canvas.drawText(score, w-scroe_w, -paint.ascent(), paint);
//画底部的开发者logo
String logo = "一个小游戏";
float logo_y = 9*h/10+(h/10-paint.descent()+paint.ascent())/2-paint.ascent();
drawText(canvas,logo,logo_y,20);
//绘制
for (int i = 0; i < gameType; i++) {
//第i根线的Y坐标
int lineY = h/10 + (i+1)*gameSpan;
//模拟人向下的加速度
roles[i].setSpeedY(roles[i].getSpeedY()+h/800f);
roles[i].setY(roles[i].getY()+roles[i].getSpeedY());
//不能一直往下掉
if (roles[i].getY() + roles[i].getHeight() >=lineY){
//说明小人已经落地
roles[i].setY(lineY - roles[i].getHeight());
roles[i].setSpeedY(0);
roles[i].setJump(false);
}
//先让我的障碍物冲右向左移动
rects[i].left = rects[i].left - (h/150);
rects[i].right = rects[i].right - (h/150);
//当我的障碍物移动出去了
if(rects[i].right <= 0){
//障碍物出去了,让它回到右边
int random_w = (int) (roles[i].getWidth()*(Math.random()*5+1)/4);//(0.25--1.5)
int random_h = (int) (roles[i].getHeight()*(Math.random()*5+1)/4);//(0.25--1.5)
int random_start = (int) (w* new Random().nextFloat()/2);
rects[i].left = 3*w/2+random_start;
rects[i].right = rects[i].left + random_w;
rects[i].top = rects[i].bottom - random_h;
}
//碰撞检测
if (rects[i].intersect(roles[i].getRectFromRole())){
//碰到了
//进入僵持状态
gameStatu = STANDOFF;
overTime = System.currentTimeMillis();
}
//画线
canvas.drawLine(0, lineY, w, lineY, paint);
//画小人
roles[i].drawSelf(canvas);
//画障碍物
canvas.drawRect(rects[i], paint);
}
}
private void drawText(Canvas canvas, String text, float y, int textSize) {
paint.setTextSize(textSize);
float measureText = paint.measureText(text);//计算文字的宽度
canvas.drawText(text, (w-measureText)/2, y, paint);
}
public boolean isStart() {
return isStart;
}
public void setStart(boolean isStart) {
this.isStart = isStart;
}
public int getGameStatu() {
return gameStatu;
}
public void setGameStatu(int gameStatu) {
this.gameStatu = gameStatu;
}
}
activity界面布局文件:
main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<!-- 上面部分 -->
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="4"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="一个都不能死"
android:drawableLeft="@drawable/ic_launcher"
android:layout_centerInParent="true"
android:gravity="center_vertical"
android:textSize="20sp"
/>
</RelativeLayout>
<!-- 下面的部分 -->
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:orientation="vertical"
>
<!-- 第一个 -->
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tubiao00_cn"
android:layout_centerInParent="true"
android:id="@+id/normal"
/>
</RelativeLayout>
<!-- 第二个 -->
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tubiao01_cn"
android:layout_centerInParent="true"
android:id="@+id/nightmare"
/>
</RelativeLayout>
<!-- 第三个 -->
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tubiao02_cn"
android:layout_centerInParent="true"
android:id="@+id/hell"
/>
</RelativeLayout>
<!-- 第四个 -->
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/tubiao03_cn"
android:layout_centerInParent="true"
android:id="@+id/pur"
/>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
主activity:
import com.tz.tony.runnable.MyThread;
import android.app.Activity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceHolder.Callback;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.widget.ImageView;
public class MainActivity extends Activity implements OnClickListener, Callback,OnTouchListener{
private int gameType;//标记游戏的难易程度
private SurfaceView mSurfaceView ;//画板
private MyThread thread;
private int w,h;//画板的宽高
private int gameSpan;//游戏区域
ImageView normal,nightmare,hell,pur;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startMenuView();
}
/**
* 启动菜单视图
*/
private void startMenuView() {
setContentView(R.layout.main);
normal = (ImageView) findViewById(R.id.normal);
nightmare = (ImageView) findViewById(R.id.nightmare);
hell = (ImageView) findViewById(R.id.hell);
pur = (ImageView) findViewById(R.id.pur);
normal.setOnClickListener(this);
nightmare.setOnClickListener(this);
hell.setOnClickListener(this);
pur.setOnClickListener(this);
}
/**
* 初始化游戏界面
*/
private void startGameView() {
mSurfaceView = new SurfaceView(this);//创建画板
mSurfaceView.getHolder().addCallback(this);
mSurfaceView.setOnTouchListener(this);
setContentView(mSurfaceView);//将我们的画板加载到activity界面
}
public boolean onTouch(View v, MotionEvent event) {
switch (thread.getGameStatu()) {
case MyThread.RUNNING:
//游戏正在进行,找到对应的游戏区域的role跳起来
confirmRole(event);
break;
case MyThread.GAMEOVER:
//游戏结束,重新选择
backOrStart(event);
break;
default:
break;
}
return true;
}
/**
* 根据手指按下的Y坐标到底是返回还是重来
* @param event
*/
private void backOrStart(MotionEvent event) {
float y = event.getY();
if (y>h/2 && y<3*h/4){
//返回
startMenuView();
}else if (y > 3*h/4){
//重来
restart();
}
}
/**
* 重来
*/
private void restart() {
//设置游戏回到正常运行的状态
thread.setGameStatu(MyThread.RUNNING);
//初始化精灵
thread.initSpirit();
}
/**
*确认被点击的区域
* @param event
*/
private void confirmRole(MotionEvent event) {
int action = event.getAction();
//多点触摸
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
//第一根手指
float y = event.getY();
roleJump(y);
break;
//多点触摸响应的down
case MotionEvent.ACTION_POINTER_DOWN:
// 其他的手指陆续暗下来
float y2 = event.getY(event.getPointerCount()-1);
roleJump(y2);
break;
default:
break;
}
}
/**
* 根据手指Y的坐标获取对应的游戏区域的任务,然后跳起来
* @param y
*/
private void roleJump(float y) {
for (int i = 0; i < gameType; i++) {
int lineD = h/10 + (i+1)*gameSpan;
int lineU = h/10 + i*gameSpan;
if (y>lineU && y<lineD && !thread.roles[i].isJump()){
//让该区域的role跳起来
thread.roles[i].setSpeedY(-h/55);
thread.roles[i].setJump(true);
}
}
}
public void surfaceCreated(SurfaceHolder holder) {
w = mSurfaceView.getWidth();//画板的宽
h = mSurfaceView.getHeight();//画板的高
gameSpan = h * 4/(5*gameType);//得到游戏的区域
thread = new MyThread(this,holder,w,h,gameType);
thread.start();
}
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
// 画板改变
}
public void surfaceDestroyed(SurfaceHolder holder) {
//停止游戏不要画界面,销毁画板
thread.setStart(false);
}
public void onClick(View v) {
switch (v.getId()) {
case R.id.normal:
gameType = 2;
break;
case R.id.nightmare:
gameType = 3;
break;
case R.id.hell:
gameType = 4;
break;
case R.id.pur:
gameType = 5;
break;
default:
break;
}
//选择完了菜单后开启游戏的视图,开始玩游戏
startGameView();
}
}
相关素材:
以上整理自教程