【读书笔记《Android游戏编程的从零开始》】11.游戏开发基础(SurfaceView 游戏框架、View 和 SurfaceView 的区别)

图片 1

在代码中“boolean flag;”语句声明一个布尔值,它主要用于以下两点:

①便于消亡线程

一个线程一旦启动,就会执行run() 函数,run() 函数执行结束后,线程也伴随着消亡。由于游戏开发中使用的线程一般都会在run() 函数中使用一个while 死循环,在这个循环中会调用绘图和逻辑函数,使得不断的刷新画布和更新逻辑;那么如果游戏暂停或者游戏结束时,为了便于销毁线程在此设置一个标识位来控制。

②防止重复创建线程及程序异常

为什么会重复创建线程,首先从Android 系统的手机说起。熟悉或者接触过 Android 系统的人都知道,Android 手机上一般都有“Back(返回)”与“Home(小房子)”按键。不管当前手机运行了什么程序,只要单击“Back”或者“Home”按键的时候,默认会将当前的程序切入到系统后台运行(程序中没有截获这两个按钮的前提下);也正因为如此,会造成MySurfaceView 视图的状态发生改变。

首先单击“Back” 按钮使当前程序切入后台,然后单击项目重新回到程序中,SurfaceView 的状态变化为:surfaceDestroyed -> 构造函数 -> surfaceCreated -> surfaceChanged 。
然后单击“Home” 按钮使当前程序切入后台,然后单击项目重新回到程序中,SurfaceView 的状态变化为:surfaceDestroyed -> surfaceCreated -> surfaceChanged 。
通过 SurfaceView 的状态变化可以明显看到,当点击“Back” 按钮并重新进入程序的过程要比点击“Home” 按钮多执行了一个构造函数。也就是说当点击“Back” 返回按键时,SurfaceView 视图会被重新加载 。
正因为这个原因,如果线程的初始化是在构造函数或者在构造函数之前,那么线程也要放在视图构造函数中进行。
千万不要把线程的初始化放在 surfaceCreated 视图创建函数之前,而线程的启动却放在 surfaceCreated 视图创建的函数中,否则程序一旦被玩家点击“Home”按键后再重新回到游戏时,程序会抛出异常。

异常是因为线程已经启动造成的,原因很简单,因为程序被“Home” 键切入后台再从后台恢复时,会直接进入 surfaceCreated 视图中创建函数,又执行了一遍线程启动!

能够想到的解决方法是,可以将线程的初始化和启动都放在视图的构造函数中,或者都放在视图创建的函数中。但是这里又出现新的问题,如果将线程的初始化和启动都放在视图的构造函数中,那么当程序被“Back”键切入后台再从后台恢复时,线程的数量会增多,反复多次,就会反复多出对应的线程。

那么,如果将flag这个线程标识位在视图摧毁时让其值改为false ,从而使当前这个线程的run 方法执行完毕,以达到摧毁线程的目的,但是如果点击“Home”键呢?当程序恢复的时候,程序就不执行线程了,也就是说重绘和逻辑函数都不再执行!
所以最完美的做法是,线程的初始化与线程的启动都写在视图的surfaceCreated 创建函数中,并且将线程标识位在视图摧毁时将其值改变为 false 。这样既可避免“线程已启动”的异常,还可以避免点击Back 按键无限增加线程数的问题。

男胎喜欢有动手的事务,感谢学校来了手工坊,锯、刀、挫,这是男孩子的世外桃源。创意和专注俱以。

倘继续SurfaceView类并实现SurfaceHolder.Callback接口就可兑现一个自定义的SurfaceView了,SurfaceHolder.Callback以脚的Surface状态发生变化的时刻通知View,SurfaceHolder.Callback具有如下的接口:

教室里虽安排各种棋牌游戏,阴雨天,下棋时。还有折飞机的交战游戏,他们说明的,以及绘制各种迷宫。

绘图的时候可能会出现不可预知的 Bug, 虽然使用 try 语句包起来了,不会导致程序崩溃;但是一旦在提交画布之前出错,那么解锁提交画布函数则无法被执行到,这样会导致下次通过 lockCanvas() 来获取 Canvas 时程序抛出异常,原因是因为画布上次没有解锁提交!所以画布将解锁提交的函数应放入 finally 语句块中。
还要注意,虽然这样保证了每次能正常提交解锁画布,但是提交解锁之前要保证画布不为空的前提,所以还需判断 Canvas 是否为空,这样一来就完美了。

教室不足够充分,那便多夺以外,组建球队,踢球;足球班呼啦啦全齐;天气晴好时,爬山。爬山他们最为欢喜难爬的,手脚并因此,危险重重,小呼小叫——小心骗局,但可乐在其中。这是单独发男胎的趣,没有哭,喊什么害怕,而是充满探险,乐在其中。

  android:theme="@android:style/Theme.NoTitleBar.Fullscreen" 

3.书的多元化

Introductions

初始不久,果不其然,发现有些男孩们,翻看最多之是自己带的这些科普书,不绝爱读书的一贯(他是听觉型孩子,喜欢放故事)“什么是呀”却是均等据属一随地圈。“可怕的没错”系列也是她们翻译看尽多的,《博物》杂志那是甚让欢迎。“手斧男孩”不用推荐,他们本就见面寻找了去。这些开以永无岛教室的当儿几乎无人问津,在她们这里确实是产生了用武之地。

图片 2图片 3

搏斗?上课坐不停歇?物品摆放乱七八赖?没有细腻之契作品?

2.视图体制
Android 中之View 视图是从未双缓冲机制的,而 SurfaceView
视图却来!也得大概了解为, SurfaceView 视图就是一个是因为 View
拓展下的更加符合游戏开发之视图类。
View 与 SurfaceView 都各出该亮点:
比如同款款棋牌类游戏,此类型游戏画面的翻新属于被动更新;因为画布的重绘主要是依赖以及按键与触屏事件(当玩家有了操作下画布才用开展翻新),所以此类游戏选
View 视图进行开发比较适宜,而且为减少了为采用 SurfaceView
需单独从一个初的线程来不断更新画布所带的运转开销。
不过如若是知难而进创新画布的娱乐项目,比如RPG、飞行射击等档的打中,很多因素都是动态的,需要不停重绘元素状态,这时还采取
View 显然就是不相宜了。
故而究竟出娱乐使用啊种视图更加的宜,这了取决于游戏项目、风格及需要。
完整来说, SurfaceView 更加吻合游戏开发,因为她会适应再多之一日游类。

阳胎生轻为不停止,若是过分强调纪律坐姿,对她们而言往往是独损害。男孩听的注目大多不够,课堂上还要用他们留意,除了课程设计之外,重要之地方,提前提醒,让子女辈的眼睛都扣留正在导师,然后再说效果会哼过多。

现实说明可查代码。

图片 4

图片 5图片 6

4.创立一个再盛又耐心的环境

修改MainActivity 类,让那出示自定义的SurfaceView 视图

比方发生规范,乐高编程引入教室,更会是好叫男性胎等的接,这为是养她们专注力的顶好的方法。

图片 7

源于网友所拍(黄山冬日)

代码说明:

这就是说文学书籍怎么惩罚?引导。三只月,从桥梁书到了《明朝那些从事》、“遗产”三部曲,“哈利波特”更是受宠。阅读量大了,文字吗尽管慢慢细腻起来了。

SurfaceHolder 类:

阳胎喜探险,喜欢漫画,喜欢大。养育儿子之时段故意的大都买大类图书,因为私心里不爱异学文,想将他逗到理工的途中,所以购买了大气周边类图书。这样的做法也也是合了男孩的本性。

(3)绘图函数 try 一下

就类似的篇章读了众多,所以才有人特意写《养育男孩》。男孩和女孩不同,生理构造所控制,荷尔蒙决定,那么还是男孩子的趟怎么带。

上面的MySurfaceView
类继承surfaceview类,并且使回调callback接口及线程runnable接口。那么这里大概的说生Callback接口和SurfaceHolder
类的企图;

而出场道,那么下一致步,我们如果筹建教室的实验角,试管烧杯酒精灯配共——一万一自己下的门实验室。

图片 8图片 9

对器乐以及部分特学科的攻,进展迟缓的男孩子更需要再多耐心。比如,T巴乌学习最好慢,因为模仿得放缓越发恐惧,就易得无比排斥。当您无是批评,而是简单示范,足够耐心等待,一方方面面所有练习,鼓励,他即使从未了毛骨悚然。虽然放缓,花之时日大丰富,但是慢慢会吹下来了,孩子就不再排斥,现在啊克主动以出来练习了。

改后,MySurfaceView 类代码如下:

阳胎的特质,容易生出攻击性,写作就是123,动手能力大,被动之放的能力弱……

 

小学等的男孩,当我们会玩她们的特质,给予重多耐心与救助,创设一个又多鼓励和更宽松的环境,你晤面意识他们身上更多创造性的火苗,是那可爱。他们呢会更担当,阳刚的气弥漫,教室里便始终会生勃勃生机。

MySurfaceView

2.多动手

图片 10图片 11

图片 12

实例效果如下:

无处不在的读书

图片 13图片 14

图片 15

Introductions

对此再次易于互相指责以及抨击的男孩子的话,爱攻击是个性,互相指责更多的凡后天条件遭到习得的平种植应针对模式,怕给收拾,所以管责任推到别人身上。一旦孩子辈懂得这环境遭到,不是罚,学会从解决问题之角度出发考虑问题,那么互相推诿指责之场景即见面大大减少。谁还见面举行不是的,做错不可怕,承担相应自然后果,然后改正。大家互相帮助提醒,班级氛围形成,那么孩子为就会见改自己之答疑模式。

(5)刷帧时间尽可能保持一致

男胎或自然的灵巧,他们喜欢动物,喜欢山野,那就算引导着啊关注植物,多些追问和探讨。做当然笔记,做植物标本,将来又届动物标本。

布文件被装置应用程序为全屏

好有或。

surfaceChanged(SurfaceHolder holder, int format, int width,int
height):当Surface的状态(大小及格式)发生变化的时段会调用该函数,在surfaceCreated调用后该函数至少会受调用一蹩脚。

X是个发接触攻击性的子女,因为安全感不够,他欣赏打起厚厚的墙。四年级同学来说X跟别人打外号,当教员了解的上,X就哭着喝,他们先说自己的,我从不说他俩,之后嗓门越来越老。这样的哭丧掩饰之凡提心吊胆,如果自己承认了,那么会不见面面临严厉的惩治。这时候老师的批评指责只会加深他的这种模式,老师扮个心理医师,等客平静下来,表示了解,然后帮他同分析,不同的回后果是啊,以及怎样是再次好地解决方法。男胎等自然就是偏于理性思维,几不善下就是见面习得重新理智的化解问题的办法。

Introductions

一经一个班级才发男胎会咋样?

下为方实例中之SurfaceView 视图添加线程,用于不停歇的重绘画布以及无歇地履打逻辑。

1.资足够的运动空间

2.SurfaceView 视图添加线程

图片 16

(2)获取视图的宽和大

还记儿子小时候见面蹲到地上看蚂蚁,一看少独钟头。我耶曾爱这样,所以就是带在他俩同台去寻找。同时配备相关书籍,《常见植物》、《常见昆虫》,某一样上《化石》肯定吗会见化她们爱的。

虽然在线程循环中,设置了休眠时间,但是这样并不完善!比如在当前项目中, run 的 while 循环中除了调用绘图函数还一直调用处理游戏逻辑的 logic() 函数,虽然在当前项目的逻辑函数中并没有写任何的代码,但是假设这个逻辑函数 logic()中写了几千行的逻辑,那么系统在处理逻辑时,时间的开销是否和上次的相同,这是无法预料的,但是可以尽可能地让其时间差值趋于相同。假设游戏线程的休眠时间为X毫秒,一般线程的休眠写法为:
Thread.sleep(X);

优化写法步骤如下:

步骤1 首先通过系统函数获取到一个时间戳:
long start = System.currentTimeMillis();
//在线程中的绘图、逻辑等函数

步骤2 处理以上所有函数之后,再次通过系统函数获取到一个时间戳:
long end = System.currentTimeMillis();

步骤3 通过这两个时间戳的差值,就可以知道这些函数所消耗的时间:如果(end - start) > X, 那线程就完全没有必要去休眠;如果(end - start) < X, 那线程的休眠时间应该为 X - (end - start) 。
线程休眠应更改为以下写法:
 if ((end - start) < X) {
 Thread.sleep(X - (end - start));
 }
 一般游戏中刷新时间在50~100毫秒之间,也就是每秒10~20帧左右;当然还要视具体情况和项目而定。

图片 17图片 18

实例效果:就是屏幕及之文本跟着点击的地方倒,效果图如下:

 

步骤:

package com.example.ex4_5;

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

//Callback接口用于SurfaceHolder 对SurfaceView 的状态进行监听
public class MySurfaceView extends SurfaceView implements Callback, Runnable {
    // 用于控制SurfaceView 的大小、格式等,并且主要用于监听SurfaceView 的状态
    private SurfaceHolder sfh;
    // 声明一个画笔
    private Paint paint;
    // 文本坐标
    private int textX = 30, textY = 30;
    // 声明一个线程
    private Thread th;
    // 线程消亡的标识符
    private boolean flag;
    // 声明一个画布
    private Canvas canvas;
    // 声明屏幕的宽高
    private int screenW, screenH;

    /**
     * SurfaceView 初始化函数
     * 
     * @param context
     */
    public MySurfaceView(Context context) {
        super(context);
        // 实例SurfaceView
        sfh = this.getHolder();
        // 为SurfaceView添加状态监听
        sfh.addCallback(this);
        // 实例一个画笔
        paint = new Paint();
        // 设置字体大小
        paint.setTextSize(20);
        // 设置画笔的颜色
        paint.setColor(Color.WHITE);
        // 设置焦点
        setFocusable(true);
    }

    /**
     * SurfaceView 视图创建,响应此函数
     */
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        screenW = this.getWidth();
        screenH = this.getHeight();
        flag = true;
        // 实例线程
        th = new Thread(this);
        // 启动线程
        th.start();
    }

    /**
     * SurfaceView 视图状态发生改变时,响应此函数
     */
    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    /**
     * SurfaceView 视图消亡时,响应此函数
     */
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        flag = false;
    }

    /**
     * 游戏绘图
     */
    public void myDraw() {
        try {
            canvas = sfh.lockCanvas();
            if (canvas != null) {
                // ————利用绘制矩形的方式刷屏
                // canvas.drawRect(0, 0, this.getWidth(), this.getHeight(),
                // paint);
                // ————利用填充画布,刷屏
                // canvas.drawColor(Color.BLACK);
                // ————利用填充画布指定的颜色分量,刷屏
                canvas.drawRGB(0, 0, 0);
                canvas.drawText("啦啦啦,德玛西亚!", textX, textY, paint);
            }
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (canvas != null) {
                sfh.unlockCanvasAndPost(canvas);
            }
        }
    }

    /**
     * 触屏事件监听
     */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        textX = (int) event.getX();
        textY = (int) event.getY();
        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();
            myDraw();
            logic();
            long end = System.currentTimeMillis();
            try {
                if (end - start < 50) {
                    Thread.sleep(50 - (end - start));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

}

本例没有当该surfaceview的初始化函数中拿那个 ScreenW 与 ScreenH
进行赋值,这里而特别注意,如果您当初始化调用ScreenW =
this.getWidth();和ScreenH = this.getHeight();那么您以获取特别失望之值
全部也0;原因是同接口Callback接口机制有关,当继承callback接口会还写她的surfaceChanged()、surfaceCreated()、surfaceDestroyed(),这几个函数当surfaceCreated()被执行的早晚,真正的view才被创造,也就是说之前获得的值吗0
,是盖初始化会在surfaceCreated()方法执行以前执行,view没有的时刻我们去获取屏幕宽高一定是0,所以这里而留意就或多或少;
 
此处将draw的代码都try起来,主要是为当画的始末被设丢来特别了,那么为能于finally中施行该操作。这样当代码抛来老的时不见面招Surface出去不平等的状态。  

Introductions

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //显示自定义的SurfaceView 视图
    setContentView(new MySurfaceView(this));
}
}

图片 19图片 20

 

在SurfaceView 视图中获取视图的宽和高的方法:
this.getWidth(); 获取视图宽度
this.getHeight(); 获取视图高度

在 SurfaceView 视图中获取视图的宽高,一定要在视图创建之后才可以获取到,也就是在 surfaceCreated 函数之后获取,在此函数执行之前获取到的永远是零,因为当前视图还没有创建,是没有宽高值的。

它是一个用以控制surface的接口,它提供了决定surface
的轻重,格式,上面的像素,即监视其转之。

1.翻新画布
在 View 视图中对此画布的更绘制,是经调用 View 提供的
postInvalidate() 与 invalidate()
这点儿独函数来实行之,也就是说画布是出于网主 UI 进行更新。那么当系统主 UI
线程更新画布时可能会见引发部分题材;比如更新画面的时刻一旦过长,就见面促成主
UI
线程被绘制函数阻塞,这样一来虽然会抓住无法响应按键、触屏等消息的题目。
SurfaceView
视图中对画布的重绘是由一个新的独门线程去实践拍卖,所以不见面现出因主 UI
线程阻塞而造成无法响应按键、触屏信息等问题

package com.example.ex4_5;

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

//Callback接口用于SurfaceHolder 对SurfaceView 的状态进行监听
public class MySurfaceView extends SurfaceView implements Callback{
    //用于控制SurfaceView 的大小、格式等,并且主要用于监听SurfaceView 的状态
    private SurfaceHolder sfh;
    private Paint paint;
    private int textX=30,textY=30;
    public MySurfaceView(Context context) {
        super(context);
        //实例SurfaceView
        sfh = this.getHolder();
        //为SurfaceView添加状态监听
        sfh.addCallback(this);
        //实例一个画笔
        paint = new Paint();
        //设置字体大小
        paint.setTextSize(30);
        //设置画笔的颜色
        paint.setColor(Color.GREEN);
    }

    @Override
    //当SurfaceView 被创建完成后响应
    public void surfaceCreated(SurfaceHolder holder) {
        myDraw();
    }

    @Override
    //当SurfaceView 状态发生改变时响应
    public void surfaceChanged(SurfaceHolder holder, int format, int width,
            int height) {

    }

    @Override
    //当SurfaceView 状态被摧毁时响应
    public void surfaceDestroyed(SurfaceHolder holder) {

    }

    //SurfaceView 是通过SurfaceHolder 来修改其数据,所以即时重写View 的onDraw(Canvas canvas)函数,在SurfaceView 启动时也不会执行到,因此这里自定义绘图函数
    public void myDraw()
    {
        //获取SurfaceView 的Canvas 对象,
        //同时对获取的Canvas 画布进行加锁,防止SurfaceView 在绘制过程中被修改、摧毁等发生的状态改变
        //另外一个lockCanvas(Rect rect)函数,其中传入一个Rect矩形类的实例,用于得到一个自定义大小的画布
        Canvas canvas = sfh.lockCanvas();
        //填充背景色,即刷屏,每次在画布绘图前都对画布进行一次整体的覆盖
        canvas.drawColor(Color.BLACK);
        //绘制内容
        canvas.drawText("This is a Text !", textX, textY, paint);
        //解锁画布和提交
        sfh.unlockCanvasAndPost(canvas);
    }

    //重写触屏监听事件
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        textX = (int)event.getX();
        textY = (int)event.getY();
        myDraw();
        return super.onTouchEvent(event);
    }

}
因为当 SurfaceView 不可编辑或尚未创建时,调用 lockCanvas() 函数会返回null; Canvas 进行绘图时也会出现不可预知的问题,所以要对绘制函数中进行 try...catch 处理;既然 lockCanvas() 函数有可能获取为 null, 那么为了避免其他使用 canvas 实例进行绘制的函数报错,在使用 Canvas 开始绘制时,需要对其进行判定是否为 null 。

callback接口:

每当耍中,基本上不会见当及用户每次触发了按键事件、触屏事件才去重绘画布,而是会固定一个工夫错开刷新画布:比如玩中之倒计时、动态的花卉、流水等等,这些游戏元素并无见面跟玩家互动,但是这些因素还是动态的。所以戏开发中见面生一个线程不停歇的去重绘画布,实时的创新游戏元素的状态。
本来游戏受除了画布给玩家最为直白的动态展现外,也会发出许多逻辑需要不停的失创新,比如怪物的AI(人工智能)、游戏被钱的翻新等等。

(1)线程标识位

MySurfaceView

3.View 和 SurfaceView 的区别

surfaceCreated(SurfaceHolder
holder):当Surface第一次等创后会及时调用该函数。程序可以在该函数吃召开来与制图界面相关的初始化工作,一般景象下还是在另外的线程来绘制界面,所以不要以斯函数中绘制Surface。

(4)提交画布必须放在 finally 中

1. SurfaceView 游戏框架实例

Introductions

新建项目“GameSurfaceView”,首先由定义一个近乎”MySurfaceView”,此类继承SurfaceView,并促成android.view.SurfaceHolder.Callback 接口,代码如下

图片 21

SurfaceView的getHolder()函数可以落SurfaceHolder对象,Surface
就于SurfaceHolder对象内。虽然Surface保存了现阶段窗口的像素数量,但是在运过程中是未直跟Surface打交道的,由SurfaceHolder的Canvas
lockCanvas()或则Canvas
lockCanvas()函数来获得Canvas对象,通过在Canvas上绘制内容来修改Surface中的数码。如果Surface不可编辑或虽然没创建调用该函数会返回null,在
unlockCanvas() 和
lockCanvas()中Surface的始末是免缓存的,所以需要全重绘Surface的情节,为了提高效率只重绘变化的局部则可调用lockCanvas(Rect
rect)函数来指定一个rect区域,这样该区域客的情会缓存起来。在调用lockCanvas函数获取Canvas后,SurfaceView会获取Surface的一个合办锁直到调用unlockCanvasAndPost(Canvas
canvas)函数才刑满释放该锁,这里的同台机制确保在Surface绘制过程被未见面受改动(被损毁、修改)。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图