2048游戏回顾一:使用SurfaceView创建游戏启动动画

SurfaceView有个很大的好处,就是可以在子线程中绘制UI,其他的View只能在主线程中更新UI,这或多或少给编程增加了些不便。而SurfaceVIew在子线程中可以绘制UI的特性,再加上其可以直接从内存或者DMA等硬件接口取得图像数据,这使得它适合2d游戏的开发。

SurfaceView使用步骤

SurfaceView的使用比较简单,可以总结为如下几个步骤:

1.继承SurfaceView并实现 SurfaceHolder.Callback方法

譬如:

public class StartAniSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
  @Override
    public void surfaceCreated(SurfaceHolder holder) {
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
}

2.给SurfaceHolder对象注册回调方法

getHolder().addCallback(this);

SurfaceView中有个SurfaceHolder 的实例,这个实例是整个SurfaceView的核心,我们这个给这个SurfaceHolder实力添加回掉方法以后,就会导致surfaceCreated方法被回掉,用来通知你Surface已经准备好了,你可以绘图了。当你修改了SurfaceView的大小以后,surfaceChanged方法就会被会调,用来通知你Surface发生了改变,当你按下退出键,导致SurfaceView销毁的之前,surfaceDestroyed方法就会被调用,通知你Surface要销毁了,你赶紧手动释放需要释放的资源吧等等。

总之,addCallback这份方法一定要调用,不然回掉方法没有添加进去,就不可能有有毁掉方法调用的行为了。

3.在surfaceCreated方法中开始绘画

一般我们会在surfaceCreated方法中开启一个线程,让它来执行绘画的工作,当然不开启也没关系,直接绘画也可以。

准备好SurfaceView以后,SurfaceView的使用和其他的View就没有社么不同了,同门可以静态的在布局文件中使用,然后使用findViewById来获取到它的实例,也可以动态使用,直接new就可以了,相比也没什么好说的呢。

绘图

关于绘图,需要使用至少两个类:Canvas,和Paint,Paint可以理解成一个画笔,你在绘画前需要设置它的颜色,粗细等信息,Canvas可以理解成一个人,他手里拿着Paint,而且他有很多的技能,比如绘制矩形,椭圆等。我们说SurfaceView的核心是一个SurfaceHolder,所以,这个我们要绘图的话得有专业的画家,这个画家得向SurfaceHolder来要,它要是不给那就没办法了。因此,绘图的代码可以是这样:

        Canvas canvas = holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        paint.setColor(bgColor);
        canvas.drawRoundRect(0,0,getWidth(),getHeight(),cornerRadius,cornerRadius,paint);
        。。。
        holder.unlockCanvasAndPost(canvas);

可以看到我们首先向holder请求一个画家canvas,canvas会使用paint来绘画,画好以后化一定要交给画家的boss,也就是holder,使用holder.unlockCanvasAndPost(canvas);来实现,这样这幅画才能显示出来。

动画就是不断的这样话一幅又一幅的画,只不过这些画前后有联系,这样就能形成动画。

游戏的启动动画

下面以2048游戏的启动动画为例,具体介绍SurfaceView的使用。

在2048游戏的启动动画中,就是使用了SurfaceView来绘制动画的,这里再把效果图拿出来:

这个动画模仿了焰火的效果,实现过程如下:

FlyNumber

一个飞出去的数字用FlyNumber类来表示,她的定义如下:

public class FlyNumber {
    public Point start;
    public Point control;
    public Point end;
    public Point cur;
    public float t;
    public int number;
    public int clolor;
    public int textSize;
    public FlyNumber(){
        start = new Point();
        control = new Point();
        end = new Point();
        cur = new Point();
        t=0f;
    }
    public void setStart(int x,int y){
        start.x=x;
        start.y=y;
    }
    public void setControl(int x,int y){
        control.x=x;
        control.y=y;
    }
    public void setEnd(int x,int y){
        end.x=x;
        end.y=y;
    }
}

类中定义了一个要飞出来的数字的值,颜色,大小,起始点,结束点,控制点以及时间等信息,说到起始点,结束点,控制点以及时间,大家应该已经想到了贝塞尔曲线了吧,是的每一个飞出去的数字使用贝塞尔曲线生成路线。

贝塞尔曲线

二次方公式

二次方贝兹曲线的路径由给定点P0、P1、P2的函数B(t)追踪:

这里只用到二次贝塞尔曲线,所以只给出二次的公式,其他的就不深究了。根据这个公式,我们可以把它转换为java代码:

    public void getBesselFlayNumber(FlyNumber number,float gatT){
        number.t += gatT;
        double x = (1-number.t)*(1-number.t)*number.start.x +2*(1-number.t)*number.t*number.control.x + number.t*number.t*number.end.x;
        double y = (1-number.t)*(1-number.t)*number.start.y +2*(1-number.t)*number.t*number.control.y + number.t*number.t*number.end.y;
        number.cur.x = (int)x;
        number.cur.y = (int)y;
    }

我们会通过时间t,以及起始点,控制点,和结束点,生成当前时刻FlyNumber的值,也就是FlyNumber的cur的值。然后再cur.x,cur.y的地方绘制一个数字,绘制方法为:

    public void drawOneFlyNumber(Canvas canvas, Paint paint, FlyNumber number){
        paint.setTextSize(number.textSize);
        paint.setColor(number.clolor);
        canvas.drawText(String.valueOf(number.number),number.cur.x,number.cur.y,paint);
    }

调用可以想象我们需要不断的生成数字,然后绘制出所有生成的数字,当这个数字到达终点后,就把它销毁掉,怎么实现呢?用一个链表就可以实现,思路如下:

不断的构造FlyNumber对象,构造好它的起点,终点,控制点等信息以后,把它放到一个链表中,这样生成端什么都不考虑,只管生成。绘制端不断的从遍历链表,把通过贝塞尔曲线函数生成坐标,然后绘制它,当一个FlyNumber到达终点后,就把它从链表中移除。

根据这个思路,代码如下:

         public void run() {
                ArrayList<FlyNumber> arrayList=new ArrayList<>();
                drawFlayNumbersBessel(holder,paint,arrayList,aniCount);
               ...

在绘制线程的run方法中构建一个链表:arrayList,然后把它传给drawFlayNumbersBessel方法继续处理:


    final int MAX = 100;
    public void drawFlayNumbersBessel(SurfaceHolder holder,Paint paint,ArrayList<FlyNumber> arrayList,final int count){
        int countl = 0;
        final float gapT = 1.0f/MAX;
        for(int i=0;i<100;i++){
            arrayList.add(generateRandomNumber());
        }
        Canvas canvas;
        FlyNumber number;
        while (countl++<(count)){
            canvas = holder.lockCanvas();
            if(countl<count-MAX){
                arrayList.add(generateRandomNumber());
                arrayList.add(generateRandomNumber());
            }
            if(canvas != null){
                canvas.drawColor(Color.WHITE);
                paint.setColor(bgColor);
                canvas.drawRoundRect(0,0,getWidth(),getHeight(),cornerRadius,cornerRadius,paint);
                if(arrayList.size()>0){
                    Iterator<FlyNumber> iterator = arrayList.iterator();
                    while (iterator.hasNext()){
                        number = iterator.next();
                        if(number.t>=1.0f){
                            iterator.remove();
                        }
                        getBesselFlayNumber(number,gapT);
                        drawOneFlyNumber(canvas,paint,number);
                        int dif = count-countl;
                        if(dif<50 && dif>0){
                            drawWelComeState(canvas,paint,255-(count-countl)*3,300-(count-countl)*6);
                        }
                    }
                }
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }

这份方法如下事情:

1.初始化100个数字

        for(int i=0;i<100;i++){
            arrayList.add(generateRandomNumber());
        }

2.每次循环,如果循环次数countl

            if(countl<count-MAX){
                arrayList.add(generateRandomNumber());
                arrayList.add(generateRandomNumber());
            }

为什么是countl

getBesselFlayNumber(number,gapT);

4.绘制FlyNumber

drawOneFlyNumber(canvas,paint,number);

5.最后50次绘制不断放大的2048字样

drawWelComeState(canvas,paint,255-(count-countl)*3,300-(count-countl)*6);

drawWelComeState函数如下:

    private void drawWelComeState(Canvas canvas,Paint paint,int alpha,int textSieze){
        paint.setAlpha(alpha);
        paint.setTextSize(textSieze);
        paint.setColor(Color.WHITE);
        String string = "2048";
        float width = paint.measureText(string);
        canvas.drawText(string,getWidth()/2-width/2,getHeight()/2+textSieze/3,paint);
    }

大家在计算数字位置的时候,使用paint.measureText方法能准确的计算出字符串的宽度,这在绘制字符串的过程中十分常用。

这样2048游戏的开机动画就结束了。最后,把整个StartAniSurfaceView类贴出来,方便对比:

package com.jinwei.tvgame2048.ui;

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.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

import com.jinwei.tvgame2048.R;
import com.jinwei.tvgame2048.model.FlyNumber;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

/**
 * Created by Jinwei on 2016/10/27.
 */
public class StartAniSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
    private final int textSize = 60;
    private final int bgColor = Color.BLACK;
    private final int aniCount = 300;
    private final int forbidArea = 200;
    private final int gameNameSize = 300;
    private final int cornerRadius = 20;
    private Handler mHandler;
    public interface AniOverListener{
        public void onAniOver();
    }
    public void setHandler(Handler handler){
        mHandler = handler;
    }
    AniOverListener mListener;
    public StartAniSurfaceView(Context context) {
        super(context);
    }

    public StartAniSurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public void init(){
        getHolder().addCallback(this);
    }
    public void setAniOverListener(AniOverListener listener){
        mListener = listener;
    }
    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        Paint paint = new Paint();
        paint.setAntiAlias(true);
        doDraw(holder,paint);
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {

    }
    public void getBesselFlayNumber(FlyNumber number,float gatT){
        number.t += gatT;
        double x = (1-number.t)*(1-number.t)*number.start.x +2*(1-number.t)*number.t*number.control.x + number.t*number.t*number.end.x;
        double y = (1-number.t)*(1-number.t)*number.start.y +2*(1-number.t)*number.t*number.control.y + number.t*number.t*number.end.y;
        number.cur.x = (int)x;
        number.cur.y = (int)y;
    }

    public void drawOneFlyNumber(Canvas canvas, Paint paint, FlyNumber number){
        paint.setTextSize(number.textSize);
        paint.setColor(number.clolor);
        canvas.drawText(String.valueOf(number.number),number.cur.x,number.cur.y,paint);
    }
    public void doDraw(final SurfaceHolder holder,final Paint paint){
        new Thread(new Runnable() {
            Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.game_name);
            @Override
            public void run() {
                ArrayList<FlyNumber> arrayList=new ArrayList<>();
                drawFlayNumbersBessel(holder,paint,arrayList,aniCount);
                drawGameName(holder,paint);
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if(mListener!=null){
                            mListener.onAniOver();
                        }
                    }
                },2000);

            }
        }).start();
    }
    int numbers[] = {2,0,4,8};
    public FlyNumber generateRandomNumber(){
        FlyNumber number = new FlyNumber();
        Random random = new Random();
        number.setStart(getWidth()/2,getHeight());
        number.setControl(getWidth()/2,0);
        int endX = random.nextInt(getWidth());
        int endY = random.nextInt(getHeight());
        if(endY>getHeight()/2){
            if (endX>forbidArea&&endX<getWidth()/2){
                endX -= forbidArea;
            }else if(endX>getWidth()/2 && endX<getWidth()-forbidArea){
                endX+=forbidArea;
            }
        }
        number.setEnd(endX,endY);
        number.t = 0;
        number.number = numbers[random.nextInt(4)];
        number.clolor = Color.rgb(random.nextInt(256),random.nextInt(256),random.nextInt(256));
        number.textSize = random.nextInt(textSize);
        return number;
    }

    final int MAX = 100;
    public void drawFlayNumbersBessel(SurfaceHolder holder,Paint paint,ArrayList<FlyNumber> arrayList,final int count){
        int countl = 0;
        final float gapT = 1.0f/MAX;
        for(int i=0;i<100;i++){
            arrayList.add(generateRandomNumber());
        }
        Canvas canvas;
        FlyNumber number;
        while (countl++<(count)){
            canvas = holder.lockCanvas();
            if(countl<count-MAX){
                arrayList.add(generateRandomNumber());
                arrayList.add(generateRandomNumber());
            }
            if(canvas != null){
                canvas.drawColor(Color.WHITE);
                paint.setColor(bgColor);
                canvas.drawRoundRect(0,0,getWidth(),getHeight(),cornerRadius,cornerRadius,paint);
                if(arrayList.size()>0){
                    Iterator<FlyNumber> iterator = arrayList.iterator();
                    while (iterator.hasNext()){
                        number = iterator.next();
                        if(number.t>=1.0f){
                            iterator.remove();
                        }
                        getBesselFlayNumber(number,gapT);
                        drawOneFlyNumber(canvas,paint,number);
                        int dif = count-countl;
                        if(dif<50 && dif>0){
                            drawWelComeState(canvas,paint,255-(count-countl)*3,300-(count-countl)*6);
                        }
                    }
                }
                holder.unlockCanvasAndPost(canvas);
            }
        }
    }
    private void drawWelComeState(Canvas canvas,Paint paint,int alpha,int textSieze){
        paint.setAlpha(alpha);
        paint.setTextSize(textSieze);
        paint.setColor(Color.WHITE);
        String string = "2048";
        float width = paint.measureText(string);
        canvas.drawText(string,getWidth()/2-width/2,getHeight()/2+textSieze/3,paint);
    }
    private void drawGameName(SurfaceHolder holder,Paint paint){
        Canvas canvas = holder.lockCanvas();
        canvas.drawColor(Color.WHITE);
        paint.setColor(bgColor);
        canvas.drawRoundRect(0,0,getWidth(),getHeight(),cornerRadius,cornerRadius,paint);
        drawWelComeState(canvas,paint,255,gameNameSize);
        holder.unlockCanvasAndPost(canvas);
    }
}
时间: 2024-10-13 02:21:02

2048游戏回顾一:使用SurfaceView创建游戏启动动画的相关文章

JavaFX战旗类游戏开发 第三课 创建游戏角色

在上一节课程中,我们学习了在JavaFX中绘制游戏地图.这一节课,我们将会创建我们的游戏角色. 首先,同样的,我们创建一个简单的基类. import javafx.scene.canvas.GraphicsContext; /** * 游戏物体基类 * @author Wing Mei */ public abstract class BaseObject { protected double x, y; protected double width,height; protected bool

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

1. SurfaceView 游戏框架实例 实例效果:就是屏幕上的文本跟着点击的地方移动,效果图如下: 步骤: 新建项目“GameSurfaceView”,首先自定义一个类"MySurfaceView",此类继承SurfaceView,并实现android.view.SurfaceHolder.Callback 接口,代码如下 package com.example.ex4_5; import android.content.Context; import android.graphi

2.phaser创建游戏和场景

新建HTML文件 页面内引用phaser文件 phaser.Game创建游戏 phaser种基本的执行函数

Cocos2d-x游戏开发之lua工程创建

操作系统:OS X 10.85 Cocos2d-x 版本: 2.2.1 使用Cocos2d-x 可以创建lua工程,已经使用cpp创建的工程也可以继承lua进行开发,但是lua并不支持mac工程(因为一些框架的问题). 支持的工程文件如下: 所有使用创建工程create.py language 为cpp的工程,后集成lua及其工具的时候,要注意这一点. 撒 现在进入cocos2d-x 目录之下,通过cd 进入文件目录 进入之后,如果忘记了命令,可以直接运行 create_project.py 如

Swift游戏实战-跑酷熊猫 01 创建工程导入素材

在这节里,我们将建立一个游戏工程,并导入一些必要的素材,例如序列帧动画文件,声音素材文件.动画文件我们使用atlas形式.在打包发布或者模拟器测试的时候,它会将整个.atlas文件夹下的图片打包成一张png图片. 要点: texture atlas :它包含了一组相关的texture.使用atlas可以提高性能 项目文件地址 http://yun.baidu.com/share/link?shareid=3824235955&uk=541995622 Swift游戏实战-跑酷熊猫系列 00 游戏

创建游戏场(实战演习)

在创建游戏场之前,先要复习一个关于链接的知识: ln 命令即可创建硬链接,也可以创建符号链接.可以用其中一种方法来使用它:ln file link创建硬链接,和:ln -s item link创建符号链接,"item" 可以是一个文件或是一个目录. 硬链接硬链接和符号链接比起来,硬链接是最初 Unix 创建链接的方式,而符号链接更加现代.在默认情况下,每个文件有一个硬链接,这个硬链接给文件起名字.当我们创建一个硬链接以后,就为文件创建了一个额外的目录条目.硬链接有两个重要局限性:1.

Unity 游戏开发技巧集锦之创建透明的材质

Unity 游戏开发技巧集锦之创建透明的材质 Unity创建透明的材质 生活中不乏透明或者半透明的事物.例如,擦的十分干净的玻璃,看起来就是透明的:一些塑料卡片,看起来就是半透明的,如图3-23所示.在Unity中,可以创建模拟了透明效果的材质,这也是本节主要讲解的内容. 图3-23  半透明的卡片 Unity创建并配置材质 在Project视图里,创建一个材质,并命名为TransMaterial,选中它然后在Inspector视图里修改Shader属性为Transparent/Diffuse,

Swift游戏实战-跑酷熊猫 02 创建熊猫类

原文:Swift游戏实战-跑酷熊猫 02 创建熊猫类 要点: 如何继承SKSpriteNode :子类必须调用SKSpriteNode的一个指定构造器 init(){ super.init(texture:texture,color:UIColor.whiteColor(),size:size) } 设置场景的背景颜色: self.backgroundColor = SKColor(red:113/255,green:197/255,blue:207,alpha:1) 熊猫类实例化以及定位 @l

Swift游戏实战-跑酷熊猫 06创建平台类以及平台工厂类

这节内容我们一起学习下随机长度的踩踏平台的原理是怎么样的. 要点: 平台类 我们的平台类继承于SKNode,这样就能被添加进其它节点进而显示在场景中. 它有一个方法来创建平台,这个方法接收一个包含SKSpriteNode的数组.将数组里面的对象横向拼接在一起组成一个完整的平台.同时计算出平台的宽度 onCreate(arrSprite:[SKSpriteNode]){ for platform in arrSprite{ platform.position.x=self.width self.a