欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

程序员文章站 2023-12-23 19:23:10
...

1.做游戏就像放电影

  • 荧幕——SurfaceView——视图层
    • SurfaceView具有双缓冲机制,一个用来缓冲数据,另一个用来展现数据
  • 放映机——SufaceHolder——控制层
  • 胶片——Surface——数据层
  • 工作人员——Thread——绘制线程

面试题:Andoird中能否在子线程中渲染屏幕

回答:不能,但是SurfaceView可以!

很显然,这样的设计概念运用到了常用的设计模式:MVC,包含了model(数据层)、view(展示层)、control(控制层)

面试题:讲一讲Android里的MVC设计模式

回答:M(Sqlite、SharedPreferences、文件、网络),V(View、Layout),C(Activity、Fragment)

2.SurfaceView

通过阅读官方文档,可以获知SurfaceView具有以下特性:

  1. 嵌入屏幕的View, 可以控制格式, 大小, 位置等

Provides a dedicated drawing surface embedded inside of a view hierarchy. You can control the format of this surface and, if you like, its size; the SurfaceView takes care of placing the surface at the correct location on the screen

  1. SurfaceView可以在子线程中绘制屏幕!!!

One of the purposes of this class is to provide a surface in which a secondary thread can render into the screen. If you are going to use it this way, you need to be aware of some threading semantics:

  1. 绘制线程必须在主线程中调用

All SurfaceView and SurfaceHolder.Callback methods will be called from the thread running the SurfaceView’s window (typically the main thread of the application). They thus need to correctly synchronize with any state that is also touched by the drawing thread.

  1. 绘制线程必须在SurfaceHolder.Callback.surfaceCreated() 和 SurfaceHolder.Callback.surfaceDestroyed()之间调用

You must ensure that the drawing thread only touches the underlying Surface while it is valid – between SurfaceHolder.Callback.surfaceCreated() and SurfaceHolder.Callback.surfaceDestroyed() .

另外,在SurfaceView底层实现了Parcelable接口,这是一个类似于Serializable,实现对象序列化,较为轻量级

3.Surface

Surface和SurfaceView经常容易混淆,它们之间的区别如下图所示:

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

4.实践一

这次,我们通过SurafaceView 在移动端上来制造一款简单的小demo,通过此篇博客的所有实践,可以使该demo具有以下功能:

  • 显示背景
  • 显示人物图像
  • 显示炸弹图像
  • 显示按钮图像
  • 点击任意处,炸弹会从人物处产生,向任意处进行移动
  • 点击按钮,会使人物向下移动

在操作之前,贴出所有需要的资源文件,可自行修改文件名,放入drawable文件夹即可:

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo
Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

  1. 定义一个GameUI类,继承SurfaceView,实现一个构造方法,并继承SurafaceHolder.callback,重写其中的三个方法,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}
  1. 修改构造方法,新建Holder示例,并将当前对象绑定到里面,代码如下:
    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holder示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }
  1. 修改MainActivity,修改视图,代码如下:
package com.example.zombiegame;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        GameUI gameUI = new GameUI(this);
        setContentView(gameUI);
    }
}
  1. 修改GameUI,增加绘制视图的内部类和方法,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (true){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){

    }
}
  1. 修改GameUI,优化绘制视图的内部类和方法,增加标志位,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;
        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){

    }
}
  1. 在View中尝试运用画布和画笔,注意给画笔进行非空判断,防止出现空指针异常,修改drawUI方法,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;
        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 画布
        Canvas canvas = getHolder().lockCanvas(); // 获取画布对象

        if (canvas != null){
            // 画笔
            Paint paint = new Paint();
            paint.setColor(Color.YELLOW);

            canvas.drawRect(0,0,100,100,paint);

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}

这样,一个简单的环境就搭建好了。

5.精灵

游戏中,所有的元素,包括人物,场景,道具,按钮,装备,都可以成为精灵(Sprite)

题外话:如何使程序没有bug,更加健壮?

回答:在方法中,永远不要相信外来的参数(不是自己创建的参数)

6.实践二

  1. 在实践一的基础上添加一个domain包,并在其中添加一个Sprite类,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;

/**
 * 精灵的实体基类
 */
public class Sprite {

    /**
     * 默认图片
     */
    public Bitmap mDefaultBitmap;

    /**
     * 显示位置
     */
    public Point mPoint;

    public Sprite(Bitmap mDefaultBitmap, Point mPoint) {
        this.mDefaultBitmap = mDefaultBitmap;
        this.mPoint = mPoint;
    }

    /**
     * 绘制自身
     */
    public void drawSelf(Canvas canvas){
        if (mDefaultBitmap != null){
            canvas.drawBitmap(mDefaultBitmap,mPoint.x,mPoint.y,null); // 绘制图片
        }
    }
}
  1. 在domain包下添加Boy类,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * 人类的实体类
 */
public class Boy extends Sprite{

    public Boy(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }
}
  1. 在domain包下添加MyButton类,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * 一个向下箭头的实体类
 */
public class MyButton extends Sprite {
    public MyButton(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }
}
  1. 在domain包下添加Bomb类,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * 炸弹的实体类
 */
public class Bomb extends Sprite {

    public Bomb(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }
}
  1. 修改GameUI,在屏幕上绘制人类和炸弹,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Point;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;
        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){

        Canvas canvas = getHolder().lockCanvas(); // 获取画布对象

        if (canvas != null){
            Boy boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));
            boy.drawSelf(canvas); // 绘制人类对象

            Bomb bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0));
            bomb.drawSelf(canvas); // 绘制炸弹对象
            getHolder().unlockCanvasAndPost(canvas); // 解锁画布并提交
        }
    }
}

7.绘制炸弹移动

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

根据该图,可以进行炸弹移动轨迹的绘制

8.实践三

  1. 修改Bomb实体类,增加炸弹移动轨迹,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * 炸弹的实体类
 */
public class Bomb extends Sprite {

    /**
     * 炸弹的移动速度
     */
    private int speed = 10;

    /**
     * 炸弹在x轴的偏移量
     */
    private int dx;

    /**
     * 炸弹在y轴的偏移量
     */
    private int dy;

    /**
     *
     * @param mDefaultBitmap 炸弹的视图
     * @param mPoint 起始位置
     * @param targetPoint 目标位置
     */
    public Bomb(Bitmap mDefaultBitmap, Point mPoint,Point targetPoint) {
        super(mDefaultBitmap, mPoint);

        // 获取炸弹在x轴和y轴的距离
        int x = targetPoint.x - mPoint.x;
        int y = targetPoint.y - mPoint.y;

        // 获取炸弹距离
        int d = (int) Math.sqrt(x * x + y * y);

        // 计算x和y的偏移量
        dx = x * speed / d;
        dy = y * speed / d;
    }

    /**
     * 炸弹移动
     */
    public void move(){
        mPoint.x += dx;
        mPoint.y += dy;
    }
}
  1. 修改GameUI,将人类和炸弹对象提取成公共变量,并修改相应方法,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Point;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹对象
     */
    private Bomb bomb;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取炸弹对象的实例
        bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0),new Point(200,200));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制人类对象
            boy.drawSelf(canvas);

            // 绘制炸弹对象
            bomb.drawSelf(canvas);
            bomb.move();

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 运行,会发现炸弹一直在重叠移动(因为绘制新视图的时候会保存老视图),需要进行相应的修改,如图所示:

Cocos2d游戏开发学习记录——1.Surface、SurfaceView、SurfaceHolder实现简单的游戏demo

  1. 修改GameUI,增加背景绘制,以此来清洗屏幕,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹对象
     */
    private Bomb bomb;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取炸弹对象的实例
        bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0),new Point(200,200));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 绘制炸弹对象
            bomb.drawSelf(canvas);
            bomb.move();

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}

9.实现点击事件产生炸弹

在游戏中,我们希望点击屏幕的任意位置,炸弹就会在那个位置出现,所以需要修改相应的代码。

10.实践四

  1. 修改Boy实体类,新建createBomb方法,表示"炸弹是由人类丢出来的",代码如下:
package com.example.zombiegame.domain;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

import com.example.zombiegame.R;

/**
 * 人类的实体类
 */
public class Boy extends Sprite{

    public Boy(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }


    public Bomb createBomb(Context context,Point targetPoint){
        // 获取炸弹对象的实例
        Bomb bomb = new Bomb(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_bomb),new Point(0,0),targetPoint);
        return bomb;
    }
}
  1. 修改GameUI,增加handleTouch方法,用于创建“产生炸弹”的触发器,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹对象
     */
    private Bomb bomb;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取炸弹对象的实例
        // bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0),new Point(200,200));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                bomb = boy.createBomb(getContext(),new Point(x,y));
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 绘制炸弹对象
            if (bomb != null){
                bomb.drawSelf(canvas);
                bomb.move();
            }

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 修改MainActivity,重写onTouchEvent方法,来获取到GameUI中的handleTouch方法,代码如下:
package com.example.zombiegame;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;

public class MainActivity extends AppCompatActivity {

    private GameUI gameUI;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        gameUI = new GameUI(this);
        setContentView(gameUI);
    }

    /**
     * 相应的触摸事件:发射炸弹
     * @param event
     * @return
     */
    @Override
    public boolean onTouchEvent(MotionEvent event){
        gameUI.handleTouch(event);
        return true;
    }
}
  1. 修改GameUI,使用炸弹集合来替换炸弹对象,并且修改相应代码,以此来实现多次点击具有多次炸弹的效果,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

import java.util.ArrayList;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹对象
     */
    // private Bomb bomb;

    /**
     * 定义炸弹集合
     */
    private ArrayList<Bomb> bombList = new ArrayList<>();

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取炸弹对象的实例
        // bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0),new Point(200,200));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Bomb bomb = boy.createBomb(getContext(),new Point(x,y));
                bombList.add(bomb);
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            /*
            if (bomb != null){
                bomb.drawSelf(canvas);
                bomb.move();
            }
            */
            
            // 遍历炸弹集合,绘制出所有炸弹
            for (int i = 0 ; i < bombList.size() ; i++){
                Bomb bomb = bombList.get(i);
                bomb.drawSelf(canvas);
                bomb.move();
            }

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 注意:在炸弹离开画面后应该让炸弹退出集合,防止内存溢出。修改Bomb实体类,添加getPoint方法,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;

/**
 * 炸弹的实体类
 */
public class Bomb extends Sprite {

    /**
     * 炸弹的移动速度
     */
    private int speed = 10;

    /**
     * 炸弹在x轴的偏移量
     */
    private int dx;

    /**
     * 炸弹在y轴的偏移量
     */
    private int dy;

    /**
     *
     * @param mDefaultBitmap 炸弹的视图
     * @param mPoint 起始位置
     * @param targetPoint 目标位置
     */
    public Bomb(Bitmap mDefaultBitmap, Point mPoint,Point targetPoint) {
        super(mDefaultBitmap, mPoint);

        // 获取炸弹在x轴和y轴的距离
        int x = targetPoint.x - mPoint.x;
        int y = targetPoint.y - mPoint.y;

        // 获取炸弹距离
        int d = (int) Math.sqrt(x * x + y * y);

        // 计算x和y的偏移量
        dx = x * speed / d;
        dy = y * speed / d;
    }

    /**
     * 炸弹移动
     */
    public void move(){
        mPoint.x += dx;
        mPoint.y += dy;
    }

    /**
     * 获取炸弹当前显示位置
     * @return
     */
    public Point getPoint(){
        return mPoint;
    }
}
  1. 修改GameUI,添加相应逻辑,注意:因为是在foreach循环下边遍历集合边修改元素,会报并发修改异常(ConcurrentModificationException,即快速失败),此时需要作出相应的修改,即拷贝一份原list到新的list中,此刻foreach循环遍历的集合长度会动态改变,即变成安全失败(使用CopyOnWriteArrayList集合替换ArrayList集合亦可),代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;

import java.util.ArrayList;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹对象
     */
    // private Bomb bomb;

    /**
     * 定义炸弹集合
     */
    private ArrayList<Bomb> bombList = new ArrayList<>();

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取炸弹对象的实例
        // bomb = new Bomb(BitmapFactory.decodeResource(getResources(),R.drawable.ic_bomb),new Point(0,0),new Point(200,200));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                Bomb bomb = boy.createBomb(getContext(),new Point(x,y));
                bombList.add(bomb);
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 遍历炸弹集合,绘制出所有炸弹
            ArrayList<Bomb> list = new ArrayList<>();
            list.addAll(bombList);
            for (Bomb bomb: list) {
                bomb.drawSelf(canvas); // 绘制炸弹对象
                bomb.move(); // 炸弹移动

                //判断炸弹是否移出屏幕
                Point point = bomb.getPoint();
                if (point.x > getWidth() || point.x < 0 ||
                        point.y > getHeight() || point.y < 0){
                    bombList.remove(bomb);
                }
            }

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 至此,该应用已经可以达成以下效果:
    1. 有人物图像
    2. 有炸弹图像
    3. 点击任意处,可以在任意处设定炸弹的终点(起点固定)

11.实现按钮

现在需要在应用里添加一个向下的按钮,用于控制人物移动

12.实践五

  1. 修改MyButton实体类,添加isClick方法,用于判断该按钮是否被点击,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.Rect;

/**
 * 一个向下箭头的实体类
 */
public class MyButton extends Sprite {

    /**
     * 标记按钮是否被点击
     */
    private Boolean isClick;

    public MyButton(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }

    /**
     * 判断按钮是否被点击
     * @return
     */
    public boolean isClick(Point point){
        // 获取按钮所在的矩形区域
        Rect rect = new Rect(mPoint.x,mPoint.y,mPoint.x + mDefaultBitmap.getWidth(),mPoint.y + mDefaultBitmap.getHeight());
        isClick = rect.contains(point.x, point.y);// 判断点击的点是否落在矩形区域内
        return isClick;
    }
}
  1. 继续修改Mybutton,添加按钮是否被点击的方法,以此来添加按钮被按下的图片(坐标根据使用的安卓模拟器进行调整),代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;

/**
 * 一个向下箭头的实体类
 */
public class MyButton extends Sprite {

    /**
     * 标记按钮是否被点击
     */
    private Boolean isClick;

    /**
     * 被点击的按钮图片
     */
    private Bitmap mPressBitmap;

    public MyButton(Bitmap mDefaultBitmap,Bitmap mPressBitmap,Point mPoint) {
        super(mDefaultBitmap, mPoint);
        this.mPressBitmap = mPressBitmap;
    }

    /**
     * 判断按钮是否被点击
     * @return
     */
    public boolean isClick(Point point){
        // 获取按钮所在的矩形区域
        Rect rect = new Rect(mPoint.x,mPoint.y,mPoint.x + mDefaultBitmap.getWidth(),mPoint.y + mDefaultBitmap.getHeight());
        isClick = rect.contains(point.x, point.y);// 判断点击的点是否落在矩形区域内
        return isClick;
    }

    public void setClick(boolean isClick){
        this.isClick = isClick;
    }

    /**
     * 重写父类的绘制自身的方法
     * @param canvas
     */
    @Override
    public void drawSelf(Canvas canvas) {
        if (!isClick){
            super.drawSelf(canvas);
        }
        else {
            canvas.drawBitmap(mPressBitmap,mPoint.x,mPoint.y,null); // 绘制图片
        }
    }
}
  1. 修改GameUI,添加按钮被按下时的逻辑处理,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;
import com.example.zombiegame.domain.MyButton;

import java.util.concurrent.CopyOnWriteArrayList;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹集合
     */
    private CopyOnWriteArrayList<Bomb> bombList = new CopyOnWriteArrayList<>();

    /**
     * 定义按钮对象
     */
    private MyButton button;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取按钮的实例
        button = new MyButton(BitmapFactory.decodeResource(getResources(),R.drawable.bottom_default),BitmapFactory.decodeResource(getResources(),R.drawable.bottom_press),new Point(30,getHeight() - 250));

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        Point point = new Point(x,y);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (!button.isClick(point)){
                    Bomb bomb = boy.createBomb(getContext(),point);
                    bombList.add(bomb);
                }else {

                }
                break;
            case MotionEvent.ACTION_UP:
                button.setClick(false);
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 遍历炸弹集合,绘制出所有炸弹
            for (Bomb bomb: bombList) {
                bomb.drawSelf(canvas); // 绘制炸弹对象
                bomb.move(); // 炸弹移动

                //判断炸弹是否移出屏幕
                Point point = bomb.getPoint();
                if (point.x > getWidth() || point.x < 0 ||
                        point.y > getHeight() || point.y < 0){
                    bombList.remove(bomb);
                }
            }

            // 绘制按钮对象
            button.drawSelf(canvas);

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 进一步修改MyButton,为按钮注册点击时间。因为MyButton并非为Android原生组件,所以需要自行实现监听接口,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;

/**
 * 一个向下箭头的实体类
 */
public class MyButton extends Sprite {

    /**
     * 标记按钮是否被点击
     */
    private Boolean isClick = false;

    /**
     * 被点击的按钮图片
     */
    private Bitmap mPressBitmap;

    /**
     * 声明接口
     */
    private ClickListener listener;

    public MyButton(Bitmap mDefaultBitmap,Bitmap mPressBitmap,Point mPoint) {
        super(mDefaultBitmap, mPoint);
        this.mPressBitmap = mPressBitmap;
    }

    /**
     * 判断按钮是否被点击
     * @return
     */
    public boolean isClick(Point point){
        // 获取按钮所在的矩形区域
        Rect rect = new Rect(mPoint.x,mPoint.y,mPoint.x + mDefaultBitmap.getWidth(),mPoint.y + mDefaultBitmap.getHeight() + 200);
        isClick = rect.contains(point.x, point.y);// 判断点击的点是否落在矩形区域内

        if (isClick){
            if (listener != null){
                listener.click();
            }
        }
        return isClick;
    }

    public void setClick(boolean isClick){
        this.isClick = isClick;
    }

    /**
     * 设置按钮的点击监听
     * @param listener
     */
    public void setClickListener(ClickListener listener){
        this.listener = listener;
    }

    /**
     * 按钮点击的回调接口
     */
    public interface ClickListener {
        public void click();
    }

    /**
     * 重写父类的绘制自身的方法
     * @param canvas
     */
    @Override
    public void drawSelf(Canvas canvas) {
        if (!isClick){
            super.drawSelf(canvas);
        }
        else {
            canvas.drawBitmap(mPressBitmap,mPoint.x,mPoint.y,null); // 绘制图片
        }
    }
}
  1. 修改GameUI,让MyButton实现接口中的方法,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;
import com.example.zombiegame.domain.MyButton;

import java.util.concurrent.CopyOnWriteArrayList;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;


    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹集合
     */
    private CopyOnWriteArrayList<Bomb> bombList = new CopyOnWriteArrayList<>();

    /**
     * 定义按钮对象
     */
    private MyButton button;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取按钮的实例
        button = new MyButton(BitmapFactory.decodeResource(getResources(),R.drawable.bottom_default),BitmapFactory.decodeResource(getResources(),R.drawable.bottom_press),new Point(50,getHeight() - 300));
        button.setClickListener(new MyButton.ClickListener() {
            @Override
            public void click() {
                System.out.println("我被点击了");
            }
        });

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        Point point = new Point(x,y);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (!button.isClick(point)){
                    Bomb bomb = boy.createBomb(getContext(),point);
                    bombList.add(bomb);
                }else {

                }
                break;
            case MotionEvent.ACTION_UP:
                button.setClick(false);
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 遍历炸弹集合,绘制出所有炸弹
            for (Bomb bomb: bombList) {
                bomb.drawSelf(canvas); // 绘制炸弹对象
                bomb.move(); // 炸弹移动

                //判断炸弹是否移出屏幕
                Point point = bomb.getPoint();
                if (point.x > getWidth() || point.x < 0 ||
                        point.y > getHeight() || point.y < 0){
                    bombList.remove(bomb);
                }
            }

            // 绘制按钮对象
            button.drawSelf(canvas);

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 修改Boy实体类,添加移动标志位和相应的移动方法(这里只添加下移的逻辑),代码如下:
package com.example.zombiegame.domain;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;

import com.example.zombiegame.R;

/**
 * 人类的实体类
 */
public class Boy extends Sprite{

    /**
     * 设置人物移动的速度
     */
    private int speed = 10;

    /**
     * 设置人物向下移动的标志位
     */
    public static final int MOVE_DOWN = 0;

    /**
     * 设置人物向上移动的标志位
     */
    public static final int MOVE_UP = 1;

    /**
     * 设置人物向左移动的标志位
     */
    public static final int MOVE_LEFT = 2;

    /**
     * 设置人物向右移动的标志位
     */
    public static final int MOVE_RIGHT = 3;

    public Boy(Bitmap mDefaultBitmap, Point mPoint) {
        super(mDefaultBitmap, mPoint);
    }

    /**
     * 创建炸弹
     * @param context
     * @param targetPoint
     * @return
     */
    public Bomb createBomb(Context context,Point targetPoint){
        // 获取炸弹对象的实例
        Bomb bomb = new Bomb(BitmapFactory.decodeResource(context.getResources(),R.drawable.ic_bomb),new Point(0,0),targetPoint);
        return bomb;
    }

    /**
     * 人物移动
     * @param direction
     */
    public void move(int direction){
        switch (direction){
            case MOVE_DOWN:
                mPoint.y += speed;
                break;
            default:
                break;
        }
    }
}
  1. 修改GameUI,在按钮点击事件中调用boy.move方法,使其可以在按钮被按下时向下移动,注意:为了防止不会使人物下降到屏幕之外,需要加入一个判断,代码如下:
package com.example.zombiegame;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.example.zombiegame.domain.Bomb;
import com.example.zombiegame.domain.Boy;
import com.example.zombiegame.domain.MyButton;

import java.util.concurrent.CopyOnWriteArrayList;

public class GameUI extends SurfaceView implements SurfaceHolder.Callback {

    /**
     * 标记是否要开启线程
     */
    private boolean isRender;

    /**
     * 定义人类对象
     */
    private Boy boy;

    /**
     * 定义炸弹集合
     */
    private CopyOnWriteArrayList<Bomb> bombList = new CopyOnWriteArrayList<>();

    /**
     * 定义按钮对象
     */
    private MyButton button;

    /**
     * 构造方法
     * @param context
     */
    public GameUI(Context context) {
        super(context);
        SurfaceHolder holder = getHolder(); // 获取Holader示例
        holder.addCallback(this); // 添加回调,这样才能在SurafaceView中回调生命周期的api
    }

    /**
     * SurfaceView创建
     * @param holder
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        isRender = true;

        // 获取人类对象的实例
        boy = new Boy(BitmapFactory.decodeResource(getResources(),R.drawable.avatar_boy),new Point(0,0));

        // 获取向下按钮的实例
        button = new MyButton(BitmapFactory.decodeResource(getResources(),R.drawable.bottom_default),BitmapFactory.decodeResource(getResources(),R.drawable.bottom_press),new Point(50,getHeight() - 300));
        button.setClickListener(new MyButton.ClickListener() {
            @Override
            public void click() {
                int y = getHeight() - boy.mDefaultBitmap.getHeight();
                if (boy.mPoint.y < y){
                    boy.move(Boy.MOVE_DOWN);
                }
            }
        });

        RenderThread renderThread = new RenderThread();
        renderThread.start(); // 开启绘制线程
    }

    /**
     * SurfaceView发生变化
     * @param holder
     * @param format
     * @param width
     * @param height
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    /**
     * SurfaceView销毁
     * @param holder
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        // 停止绘制
        isRender = false;
    }

    /**
     * 响应触摸事件:发射炸弹
     * @param event
     */
    public void handleTouch(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        Point point = new Point(x,y);
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if (!button.isClick(point)){
                    Bomb bomb = boy.createBomb(getContext(),point);
                    bombList.add(bomb);
                }else {

                }
                break;
            case MotionEvent.ACTION_UP:
                button.setClick(false);
                break;
            default:;
        }
    }

    /**
     * 绘制视图的线程类
     */
    class RenderThread extends Thread{
        @Override
        public void run() {
            // 不断绘制界面,保证界面能够一直发生变化,从而实现动画效果
            while (isRender){
                drawUI();
            }
        }
    }

    /**
     * 绘制屏幕
     */
    private void drawUI(){
        // 获取画布对象
        Canvas canvas = getHolder().lockCanvas();

        if (canvas != null){
            // 绘制背景,清洗屏幕
            Paint paint = new Paint();
            paint.setColor(Color.GRAY);
            canvas.drawRect(0,0,getWidth(),getHeight(),paint);

            // 绘制人类对象
            boy.drawSelf(canvas);

            // 遍历炸弹集合,绘制出所有炸弹
            for (Bomb bomb: bombList) {
                bomb.drawSelf(canvas); // 绘制炸弹对象
                bomb.move(); // 炸弹移动

                //判断炸弹是否移出屏幕
                Point point = bomb.getPoint();
                if (point.x > getWidth() || point.x < 0 ||
                        point.y > getHeight() || point.y < 0){
                    bombList.remove(bomb);
                }
            }

            // 绘制按钮对象
            button.drawSelf(canvas);

            // 解锁画布并提交
            getHolder().unlockCanvasAndPost(canvas);
        }
    }
}
  1. 修改MyButton,为了让人物在按住按钮时可以持续移动,添加计时器,注意要在按钮不被按下时取消定时器,代码如下:
package com.example.zombiegame.domain;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;

import java.util.Timer;
import java.util.TimerTask;

/**
 * 一个向下箭头的实体类
 */
public class MyButton extends Sprite {

    /**
     * 标记按钮是否被点击
     */
    private Boolean isClick = false;

    /**
     * 被点击的按钮图片
     */
    private Bitmap mPressBitmap;

    /**
     * 声明接口
     */
    private ClickListener listener;

    /**
     * 计时器
     */
    private Timer timer;

    public MyButton(Bitmap mDefaultBitmap,Bitmap mPressBitmap,Point mPoint) {
        super(mDefaultBitmap, mPoint);
        this.mPressBitmap = mPressBitmap;
    }

    /**
     * 判断按钮是否被点击
     * @return
     */
    public boolean isClick(Point point){
        // 获取按钮所在的矩形区域
        Rect rect = new Rect(mPoint.x,mPoint.y,mPoint.x + mDefaultBitmap.getWidth(),mPoint.y + mDefaultBitmap.getHeight() + 200);
        isClick = rect.contains(point.x, point.y);// 判断点击的点是否落在矩形区域内

        if (isClick){
            if (listener != null){
                timer = new Timer();
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        listener.click();
                    }
                },0,200);
            }
        }
        return isClick;
    }

    public void setClick(boolean isClick){
        this.isClick = isClick;
        if (!isClick){
            if (timer != null){
                timer.cancel();
            }
        }
    }

    /**
     * 设置按钮的点击监听
     * @param listener
     */
    public void setClickListener(ClickListener listener){
        this.listener = listener;
    }

    /**
     * 按钮点击的回调接口
     */
    public interface ClickListener {
        public void click();
    }

    /**
     * 重写父类的绘制自身的方法
     * @param canvas
     */
    @Override
    public void drawSelf(Canvas canvas) {
        if (!isClick){
            super.drawSelf(canvas);
        }
        else {
            canvas.drawBitmap(mPressBitmap,mPoint.x,mPoint.y,null); // 绘制图片
        }
    }
}

13.总结

至此,整个小demo就已经完成了。下篇博客将正式介绍Cocox2.x框架以及使用。

相关标签: Cocos2d

上一篇:

下一篇: