二维动画与碰撞侦测

摘要

以 XNA 为基础的游戏可以利 3D 模型为游戏程序加入动画效果,也可以利用简单的程序技巧将 2 维的图片显示成动画。2 维动画在制作上要比 3 维动画简单,在程控上也比较单纯,但是在效果上当然也比较逊色,不过因为游戏会吸引使用者使用的原因,游戏的声光与娱乐效果只占其中的一部分,游戏的内涵、趣味性、和挑战性也是吸引玩家的重要因素。在这一篇文章中我们将要为大家介绍以 XNA 为基础的游戏程序制作 2D 动画与物体碰撞侦测的技巧。

2 维动画技术

游戏程序一般有两种常见的 2 维动画效果,一种是动画物体,另外一种是会移动的背景。动画物体的效果可以利用一连串不同的小图案组成的大图当做动画的内容,由游戏程序一次取出一张小图案来显示。例如图 1 所示即为由 12 张小图组成的大图:

图 1:由 12 张小图组成的大图

(注:本图材自 http://www.emutalk.net/showthread.php?t=43273)

在快速取出不同的小图案来显示的状况下,游戏程序可以显示出生动的 2 维动画效果,以执行在 Windows Phone 7 智能型手机的游戏程序而言,在正常的状况下,每秒可以显示 30 张不同的图案,如果游戏程序是执行在 Windows 平台或是 Xbox/Xbox 360 游戏机,每秒可以显示高达 60 张不同的图案,因为人眼有视觉暂留功能,所以物体在快速转变时,之前显示的影像虽然消失,但是人的眼睛仍然能继续保留影像的内容 0.1 秒 ~ 0.4 秒之久,所以物体可以平顺地呈现不同的图案,形成动画的效果。

第二种常见的 2 维动画常常应用在游戏程序的卷动背景,可以用来营造物体往前移动或是往后倒退的效果,有关如何卷动游戏的背景可以参考 XNA Framework 常用的类别 一文的说明。

实作 2 维动画

欲利用一连串不同的小图案组成的大图当做动画的内容,由游戏程序一次取出一张小图案来显示,游戏程序必须知道组成大图的小图案的高度和宽度,假设组成大图的每一张小图案的高度和宽度皆为 64 ,则从 X 坐标为 0 ,Y 坐标为 0 的位置取出高度为 64 ,宽度为 64 的图案,就可以取出第一张小图案,而从 X 坐标为 64 ,Y 坐标为 0 的位置取出高度为 64 ,宽度为 64 的图案,就可以取出第二张小图,如果要取出第二排的第一张小图案,则可以从 X 坐标为 0 ,Y 坐标为 64 的位置取出高度为 64 ,宽度为 64 的图案,以此类推。其坐标的计算方式为 X 坐标往右增加,Y 坐标往下增加,而坐标的原点在图案的左上角,如图 2 所示:

图 2 :取用大图中的小图案的坐标计算方式

(注:本图材自 http://www.emutalk.net/showthread.php?t=43273)

游戏程序只要计算好欲取用的小图案的起始坐标,以及小图案的高度与宽度,再透过 SpriteBatch 类别的 Draw 方法的 SourceRectangle 参数指定来源图案的矩形(指定来源图案的矩形相当于指定来源图案的起始坐标和高度与宽度),就可以从来源图案取出指定区域的内容,再显示到游戏的窗口供用户检视。有关支持指定 SourceRectangle 参数的 SpriteBatch 类别的 Draw 方法原形如下:

public void Draw (
         Texture2D texture,
         Vector2 position,
         Nullable<Rectangle> sourceRectangle,
         Color color
)

游戏程序可以利用以下的公式取出组成大图的任何一张小图案:

Rectangle SourceRectangle=new Rectangle((Column-1) *小图图宽,

(Row -1) *小图图高,小图图宽,小图图高);

其中的 Row 代表列编号,Column 代表栏编号,所以当游戏程序欲取用第一列的第一张小图时,SourceRectangle 的计算方式如下:

Rectangle SourceRectangle=new Rectangle(0, 0,小图图宽,小图图高);

当游戏欲取第一列的第二张小图时,SourceRectangle 的计算方式如下:

Rectangle SourceRectangle=new Rectangle(小图图宽, 0,

小图图宽,小图图高);

当游戏欲取第二列的第一张小图时,SourceRectangle 的计算方式如下:

Rectangle SourceRectangle=new Rectangle(0,小图图高,

小图图宽,小图图高);

要将图 2 所示的大图显示成 2 维动画,请先启动 Visual Studio 2010 Express for Windows Phone ,建立一个型态为 [Windows Phone Game(4.0)] 型态的项目,然后将图 2 所示的图片加入到 Content Pipeline 项目中。

[提示]

您可以视需要加入背景图案当做游戏的背景。

做好之后请于 Game1 类别加入以下的变量宣告:

Texture2D Mario;						//管理圖檔的變數
Texture2D Background;					//管理背景圖案的變數
int RowCount = 2;						//圖檔每一欄的小圖案數目
int ColumnCount = 6;					//圖檔每一列的小圖案數目
int RowIndex = 1;						//第一個小圖案的列編號
int ColumnIndex = 1;				    //第一個小圖案的欄編號
Vector2 BasePosition;					//存放小圖案顯示的位置的變數
Rectangle SourceRectangle;				//欲取用的小圖案位置
int MarioWidth = 114;			        //組成大圖的小圖案寬度
int MarioHeight = 120;				    //組成大圖的小圖案高度

宣告好变量之后,请于 Game1 类别的建构函式中设定游戏窗口的高度与宽度,编辑好的 Game1 类别建构函式如下:

public Game1()
{
    graphics = new GraphicsDeviceManager(this);
    Content.RootDirectory = "Content";
    graphics.PreferredBackBufferHeight = 800;				//設定遊戲視窗的高度為800
    graphics.PreferredBackBufferWidth = 480;				//設定遊戲視窗的寬度為480
    TargetElapsedTime = TimeSpan.FromTicks(333333);
}

做好之后请编辑 Game1 类别的 LoadContent 方法,从 Content Pipeline 项目加载欲显示的大图,并设定组成大图的小图案欲显示的位置,编辑好的 LoadContent 方法如下:

protected override void LoadContent()
{
    spriteBatch = new SpriteBatch(GraphicsDevice);
    Mario = Content.Load<Texture2D>("MarioAnimate12");	//從Content Pipeline專案載入圖形
    Background = Content.Load<Texture2D>("Background");	//從Content Pipeline專案載入背景
    Viewport viewport = graphics.GraphicsDevice.Viewport;		//取得遊戲視窗的大小
    BasePosition = new Vector2(0, viewport.Height - 220);	    //設定小圖案顯示的位置
}

加载好欲显示的图形之后,请将 Game1 类别的 Update 方法编辑成以下的样子,负责从图 2 所示的大图依序取出每一张小图案来显示:

protected override void Update(GameTime gameTime)
{
    if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
        this.Exit();
    SourceRectangle = new Rectangle((ColumnIndex - 1) * MarioWidth, (RowIndex - 1) * 
MarioHeight, MarioWidth, MarioHeight);	            //計算欲取用的小圖案的位置
    if (ColumnIndex <= ColumnCount)					//如果第一列的小圖案尚未取完
    {
        ColumnIndex++;								//遞增欲取用的小圖案編號
    }
    else										    //否則
    {
        ColumnIndex = 1;						    //設定要讀取第一欄的小圖案
        RowIndex++;							        //遞增欲讀取的圖形列編號
        if (RowIndex > RowCount)				    //如果已經讀完所有的列
        {
            RowIndex = 1;						    //設定要讀取第一列的小圖案
        }
    }
    BasePosition.X++;			                    //令小圖案以每次一個單位往右移動
    base.Update(gameTime);
}

最后请将 Game1 类别的 Draw 方法编辑成以下的样子,负责依据欲取用的小图案的位置取出小图案,并显示在由 BasePosition 变量指定的位置:

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);
    spriteBatch.Begin();									  //宣告繪圖動作開始
    spriteBatch.Draw(Background, Vector2.Zero, Color.White);  //顯示背景
    spriteBatch.Draw(Mario, BasePosition, SourceRectangle, 
Color.White);		                                          //顯示取出的小圖案
    spriteBatch.End();									      //宣告繪圖動作結束
    base.Draw(gameTime);
}

做好之后请执行项目,您就会看到图 2 所示的大图形中的每一张小图案会依序被游戏程序取出来显示,因为图形更换的速度很快,所以可以形成动画的效果,如图 3 所示:

图 3 :以 XNA 为基础的游戏程序显示 2 维的动画的情形

碰撞侦测

当游戏程序显示的物体和动作过程或是移动过程发生碰撞时,游戏程序必须能够侦测物体是否发生碰撞,并据以做出适当的反应,例如在飞弹击中入侵的异形时发出爆炸的声音,或是在棒搥打中怪兽时发出惨叫的声音,达到与使用者互动的效果,添加游戏的趣味性与临场感。

使用 XNA Framework 开发游戏程序的程序设计师可以利用 BoundingBox 结构、BoundingSphere 结构、或是 BoundingFrustum 类别辅助游戏程序判断物体是否发生碰撞,不需要自己辛苦地利用几何学的技巧进行判断。

使用 BoundingBox 结构判断物体是否发生碰撞主要的概念是利用立方体来描述物体,当两个代表物体的立方体产生交集的时候便是物体发生碰撞;使用 BoundingSphere 结构判断物体是否发生碰撞主要的概念是利用球体来描述物体,当两个代表物体的球体产生交集的时候便是物体发生碰撞;而使用 BoundingFrustum 类别判断物体是否发生碰撞主要的概念是利用断面锥体来描述物体,当两个代表物体的断面锥体产生交集的时候便是物体发生碰撞。

图 4 所示即为利用 BoundingBox 结构定义的立方体描述游戏物体的示意图,图 5 所示即为利用 BoundingSphere 结构定义的球体描述游戏物体的示意图,图 6 所示即为利用 BoundingFrustum 类别定义的断面锥体描述游戏物体的示意图。

图 4 :利用 BoundingBoxn 结构定义的立方体描述游戏物体的示意图

图 5 :利用 BoundingSphere 结构定义的球体描述游戏物体的示意图

图 6 :利用 BoundingFrustum 类别定义的断面锥体描述游戏物体的示意图

欲利用 BoundingBox 架构游戏的物体,程序必须定义描述物体的立方体的最小坐标点和最大坐标点,欲利用 BoundingSphere 架构游戏的物体,程序必须定义描述物体的球体的圆心和半径,而欲利用 BoundingSphere 类别描述游戏的物体,程序必须定义描述物体的断面锥体的各个顶点的坐标。

表 1 所示为 BoundingBox 结构常用的属性:

表 1:BoundingBox 结构常用的属性
属性名称说明
Max描述物体的立方体的顶点的坐标中内容值最大者。
Min描述物体的立方体的顶点的坐标中内容值最小者。

表 2 所示为 BoundingBox 结构常用的方法:

表 2 :BoundingBox 结构常用的方法
方法名称说明
Contains判断BoundingBox架构的立方体是否包含指定的立方体、球体、坐标、或是断面锥体。
CreateFromPoints依据一组坐标点建立立方体。
CreateFromSphere依据 BoundingSphere 架构的球体立方体。
CreateMerged依据两个 BoundingBox 架构的立方体组成立方体。
Intersects判断 BoundingBox 架构的立方体是否与指定的立方体、球体、断面锥体、平面、或光束有交集。

BoundingSphere 结构常用的属性可以参考表 3 的说明:

表 3 :BoundingSphere 结构常用的属性
属性名称说明
Center描述球体圆心的坐标。
Radius描述球体的半径。

BoundingSpheren 结构常用的方法可以参考表 4 的说明:

BoundingSphere 结构常用的方法可以参考表 4 的说明:
方法名称说明
Contains判断 BoundingSphere 架构的球体是否包含指定的立方体、球体、坐标、或是断面锥体。
CreateFromBoundingBox依据 BoundingBox 架构的立方体建立球体。
CreateFromFrustum依据 BoundingFrustum 类别描述的断面锥体建立球体。
CreateFromPoints依据一组坐标点建立球体。
CreateMerged依据指定的两个球体建立组合的球体。
Intersects判断 BoundingSphere 架构的球体是否与指定的立方体、球体、断面锥体、平面、或光束有交集。
Transform平移或放大/缩小 BoundingSphere 架构的球体。

BoundingFrustum 类别常用的属性可以参考表 5 的说明:

表 5 :BoundingFrustum 类别常用的属性
属性名称说明
Bottom取得 BoundingFrustum 类别描述的断面锥体的底部平面。
Far取得 BoundingFrustum 类别描述的断面锥体的远平面。
Left取得 BoundingFrustum 类别描述的断面锥体的左方平面。
Matrix取得 BoundingFrustum 类别描述的断面锥体的矩阵表示法。
Near取得 BoundingFrustum 类别描述的断面锥体的近平面。
Right取得 BoundingFrustum 类别描述的断面锥体的右方平面。
Top取得 BoundingFrustum 类别描述的断面锥体的上方平面。

BoundingFrustum 类别常用的方法可以参考表 6 的说明:

表 6 :BoundingFrustum 类别常用的方法
方法名称说明
Contains判断 BoundingFrustum 类别描述的断面锥体是否包含指定的立方体、球体、坐标、或是断面锥体。
GetCorners取得包含 BoundingFrustum 类别描述的断面锥体的所有顶点的数组。
Intersects判断 BoundingFrustum 类别描述的断面锥体是否与指定的立方体、球体、断面锥体、平面、或光束有交集。

[提示]

不管是 BoundingBox 结构的 Min/Max 属性,BoundingSphere 结构的 Center 属性,还是 BoundingFrustum 类别的 GetCorners 方法传回的数组的元素型态,都是 Vector3 结构型态的数据,也就是 3 度空间的坐标,换句话说,BoundingBox 结构、 BoundingSphere 结构、以及 BoundingFrustum 类别都可以适用于侦测 3 维游戏的物体碰撞,如果要应用在2维游戏的物体碰撞侦测,则将代表Z轴的坐标的内容值设定成0即可,因为2维空间的物体没有Z轴的坐标。透过将Z轴的坐标的内容值设定成0,2维游戏程序一样能够利用 BoundingBox 结构、BoundingSphere 结构、或是 BoundingFrustum 类别来判断物体与物体是否发生碰撞。

了解使用 XNA Framework 的游戏程序如何利用 XNA 支持的结构和类别进行碰撞侦测判断之后,接下来我们就要修改上述的游戏程序,在物体移动到与背景右下角的圆柱体发生碰撞时,禁止物体继续往右移动。

首先请在 Game1 类别中加入以下的变量宣告

BoundingBox bbActor;								//負責定義移動物體大小的立方體
BoundingBox bbCylinder;					              //負責定義遊戲背景右下角的圓柱體的立方體

做好之后请于 Game1 类别的 LoadContent 方法中加入以下的程序代码,负责设定游戏背景右下角的圆柱体的立方体的位置:

bbCylinder.Min = new Vector3(396, 545, 0);		//設定定義圓柱體的立方體的最小座標點
bbCylinder.Max = new Vector3(463, 698, 0);		//設定定義圓柱體的立方體的最大座標點

然后请编辑 Game1 类别的 Update 方法,以下的程序代码:

base.Update(gameTime);

的前面加入下列的程序代码,负责设定描述移动物体的立方体的大小,并在移动的物体触及游戏背景右下角的圆柱体时停止往右移动物体的动作:

bbActor.Min = new Vector3(BasePosition.X, 
BasePosition.Y, 0);			   //設定定義移動物體的立方體的最小座標點
bbActor.Max = new Vector3(BasePosition.X + MarioWidth, 
BasePosition.Y + MarioHeight, 0);//設定定義移動物體的立方體的最大座標點

if (!bbActor.Intersects(bbCylinder))			//如果移動的物體尚未與遊戲背景右下角
//的圓柱體發生碰撞
{
    BasePosition.X++;								//將物體往右移動一個單位
}

做好之后请执行项目,待游戏程序显示的物体一直往右移动至碰撞到游戏背景右下角的圆柱体时,就会停止继续往右移动的动作,如图7所示:

图 7 :游戏程序显示的物体碰撞到游戏背景右下角的圆柱体后停止继续往右移动的情形

范例下载:SimpleAnimation.zip

原文链接:http://msdn.microsoft.com/zh-cn/windowsphone/gg566442.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值