您好,欢迎访问三七文档
当前位置:首页 > 临时分类 > Andriod游戏开发第七章精灵帧动画与碰撞检测
从零开始Android游戏编程(第二版)第七章精灵、帧动画与碰撞检测收藏第七章精灵、帧动画与碰撞检测经过前几章的学习,大家对使用位图、接受用户控制应该已经有了初步的概念,也可以运用这些知识完成简单的小游戏。这一章中,我们会为游戏中最重要的部分——图形处理建立一个基本的框架,这还不是游戏引擎,但是其中很多方法可以为读者以后创建自己的游戏引擎提供借鉴。这一章的涉及的内容比较多,既有2D游戏的基础理论,又有复杂的代码。尤其是代码部分,如果详细讲解,恐怕会占用很大的篇幅。所以我们只对关键的函数进行讲解,以方便读者今后灵活运用这些代码(所有的源代码都与本章节内容一同提供下载)。这个框架是完全依照MIDP中javax.microedition.lcdui.game包设计的:ClassesGameCanvasLayerLayerManagerSpriteTiledLayergame包中有5个类,其中Layer(层)是一个抽象类,对图形显示作了基本的定义。以我们的目标游戏《坦克大战》为例,在游戏中有这样一些图形元素:我方和敌方的坦克、坦克发出的子弹、地面、墙体、水域掩体等。这些元素虽然外观不同,但是本质上却非常相似:都是在特定位置以特定尺寸显示一个或一组位图,有些位图位置还会变动,Layer就定义了位置,尺寸,显示等相关的功能。之所以叫做Layer,与游戏中分层地图的概念有关,先让我们了解一下什么是分层地图:还是说坦克大战,当我们的坦克行驶在普通地面上时,坦克的图像肯定是覆盖了地面的图像,这样我们能看到坦克。当坦克行驶到掩体时,我们会发现,掩体的图像覆盖了坦克的图像,如图所示:实际上,我们在程序中,只要首先显示地面的图像,然后显示坦克的图像,最后显示掩体的图像(掩体图片是镂空的),就能达到这种效果,这就是分层地图。通常我们把最下面的叫做地面层,中间的叫做物件层,最上面的叫做天空层。关于地图我们就讲这么多,这里只介绍图形意义上的分层,是为了帮助大家理解Layer一词的意义。关于地图的详细内容我们在第十章会深入讲解。首先让我们看一下抽象类Layer的定义:packageorg.yexing.android.games.common;importandroid.graphics.Canvas;publicabstractclassLayer{intx=0;//Layer的横坐标inty=0;//Layer的纵坐标intwidth=0;//Layer的宽度intheight=0;//Layer的高度booleanvisible=true;//Layer是否可见Layer(intwidth,intheight){setWidthImpl(width);setHeightImpl(height);}/***设定Layer的显示位置**@paramx*横坐标*@paramy*纵坐标*/publicvoidsetPosition(intx,inty){this.x=x;this.y=y;}/***相对于当前的位置移动Layer**@paramdx*横坐标变化量*@paramdy*纵坐标变化量*/publicvoidmove(intdx,intdy){x+=dx;y+=dy;}/***取得Layer的横坐标**@return横坐标值*/publicfinalintgetX(){returnx;}/***取得Layer的纵坐标**@return纵坐标值*/publicfinalintgetY(){returny;}/***取得Layer的宽度**@return宽度值*/publicfinalintgetWidth(){returnwidth;}/***取得Layer的高度**@return高度值*/publicfinalintgetHeight(){returnheight;}/***设置Layer是否可见**@paramvisible*trueLayer可见,falseLayer不可见*/publicvoidsetVisible(booleanvisible){this.visible=visible;}/***检测Layer是否可见**@returntrueLayer可见,falseLayer不可见*/publicfinalbooleanisVisible(){returnvisible;}/***绘制Layer,必须被重载**@paramc*/publicabstractvoidpaint(Canvasc);/***设置Layer的宽度**@paramwidth*/voidsetWidthImpl(intwidth){if(width0){thrownewIllegalArgumentException();}this.width=width;}/***设置Layer的高度**@paramheight*/voidsetHeightImpl(intheight){if(height0){thrownewIllegalArgumentException();}this.height=height;}}Layer的代码不多,根据函数名称就可以知道它的功能,主要是Layer尺寸、位置的设定和获取。其中最重要的方法paint是虚方法,Layer图像就是通过这个方法显示出来的。因此继承自Layer的所有类都要实现这个方法。Sprite(精灵)继承自Layer,同时又增加了帧动画,图形变换和碰撞检测的功能。Sprite是我们这一章重点介绍的内容。首先让我们了解一下精灵的概念。Sprite这个词在2D游戏中非常常见,一般指游戏中具有独立外观和属性的个体元素。如主角、NPC、宝箱、子弹等等,这些都是精灵。下面就让我们来创建Sprite类并使其继承自Layer。创建完毕时,IDE会提示必须实现paint方法。但是这时候我们会发现,paint方法要显示那些图形呢?没有。因此我们需要为Sprite增加一个Bitmap类型变量,用来存放paint要显示的图形。同时,我们要创建一个构造函数用来初始化这个Bitmap变量。publicSprite(Bitmapimage){super(image.getWidth(),image.getHeight());initializeFrames(image,image.getWidth(),image.getHeight(),false);initCollisionRectBounds();this.setTransformImpl(TRANS_NONE);}虽然这个构造函数只有几行,却涉及到不少的知识。super不用说了,initializeFrames是做什么的呢?这就要提到帧动画的概念了。什么是帧动画呢?如下图,我们看到一组星星的图片(4张16x16的位图)当我们在同一个位置以一定的时间间隔连续显示这几幅图片的时候就变成了这个样子我们看到,星星在发光,这就是帧动画。即取得一个连续画面中的几个关键帧,在一定的时间间隔下连续的显示这些帧从而形成动画,initializeFrames的功能就是初始化这些关键帧。那么又为什么要初始化呢?通常情况下,我们为了节省空间也为了便于管理,会将一组动画的多个帧保存在同一个图片文件中(如上图的星星)。这样一来每次显示的时候就不能显示整张图片,而只能显示这个图片的一部分。因此,我们要计算每一帧在整张图片上的位置以便正确显示。就让我们来看看initializeFrames的定义privatevoidinitializeFrames(Bitmapimage,intfWidth,intfHeight,booleanmaintainCurFrame)initializeFrames作了这样的工作,首先取得一个位图,然后根据用户设置的单一帧的宽度和高度计算这个位图中包括多少帧。如刚刚我们看到的星星的图片(64x16像素),当单一帧的宽度和高度与图片相同的时候,就只有一帧。但是当一帧的宽度和高度均为16像素时,整个图片就可以分为4帧了。这时候,函数会计算每一帧的顶点坐标,如第一帧的顶点是(0,0),第二帧的顶点是(16,0),并依次类推。这个函数还可以处理更复杂的情况,如下图:函数会将计算好的各个帧的顶点横纵坐标分别保存在两个数组中(frameCoordsX[],frameCoordsY[]),下次我们使用帧的序号访问各个帧时(在paint中)就可以很快找到它的所对应的位图区域了。在这个Sprite的构造函数中,initializeFrames使用了image.getWidth()和image.getHeight()作为一帧的高度和宽度,所以这个精灵注定只能有一帧。Sprite的构造函数还有其他样式,如publicSprite(Bitmapimage,intframeWidth,intframeHeight)这时候我们就可以指定帧的宽度和高度,定义具有多个帧的Sprite了。说完了为帧动画做初始化工作的initializeFrames,让我们来看下一个函数initCollisionRectBounds。privatevoidinitCollisionRectBounds(){collisionRectX=0;collisionRectY=0;collisionRectWidth=this.width;collisionRectHeight=this.height;}这个函数的代码不多,名字翻译过来就是“初始化碰撞矩形边缘”。由此引出另外一个游戏中的重要概念——碰撞检测。在我们的目标游戏坦克大战中,碰撞检测可是少不了的,我们的子弹击中敌方坦克就是一次碰撞,只有进行了碰撞检测才能够触发这次击中事件,不然我们就没法消灭敌人的坦克了。2D游戏中的碰撞检测有几种,最简单的是矩形碰撞检测,复杂一些的有多边形检测和像素检测等。这里我们只介绍一下矩形检测。如图:我们取坦克和子弹的矩形外框,当这两个矩形重叠的时候就认为是碰撞了。函数initCollisionRectBounds的功能就是设定Sprite的矩形外框。同前面初始化帧的原理一样,我们需要这个函数在多个帧组合成的图片中确定一帧的大小。现在我们还剩下构造函数中最后一行代码了:this.setTransformImpl(TRANS_NONE);这行代码设置了Sprite图形的变换方式。变换一共有8种,定义如下:publicstaticfinalintTRANS_NONE=0;publicstaticfinalintTRANS_ROT90=5;publicstaticfinalintTRANS_ROT180=3;publicstaticfinalintTRANS_ROT270=6;publicstaticfinalintTRANS_MIRROR=2;publicstaticfinalintTRANS_MIRROR_ROT90=7;publicstaticfinalintTRANS_MIRROR_ROT180=1;publicstaticfinalintTRANS_MIRROR_ROT270=4;所谓变换,就是对图形进行旋转和镜像等操作,这就相当于增加了图形资源。如图:因为要显示向4个方向行驶的坦克,每个坦克都需要4组图片。如果我们使用了旋转变换功能,每个坦克只需要一组图片就够了,其他的图片完全可以由旋转获得。需要指出的是,旋转是围绕参照点(referencepixel)进行的,如果没有使用函数setRefPixelPosition设定参照点,缺省情况下参照点就是(0,0)。因此如果我们使用参数TRANS_ROT90旋转的话,图形应该是这样这时候有一点必须要特别提示一下,旋转之后,getX和getY的返回值将发生变化。讲到这里,这一章的理论实在是够多了,我们必须要总结一下:首先这一章讲的是图形显示。我们依照j2me中的games包建立了两个类Layer和Sprite。重点介绍了Sprite(精灵)类相关的知识,包括桢动画、碰撞检测和旋转变换。下面我们来看一组Spri
本文标题:Andriod游戏开发第七章精灵帧动画与碰撞检测
链接地址:https://www.777doc.com/doc-2901295 .html