surfaceView的使用实例之虐心小游戏

145 篇文章 12 订阅
28 篇文章 0 订阅
1、SurfaceView
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();
	}
}

相关素材:


以上整理自教程




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值