适用于Windows Phone,PC和Xbox的XNA动画Sprite组件(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/an-xna-animated-sprite-component-for-windows-phone-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 29 分钟阅读 - 14086 个词 阅读量 0适用于Windows Phone,PC和Xbox的XNA动画Sprite组件(译文)
原文地址:https://www.codeproject.com/Articles/223614/An-Xna-Animated-Sprite-Component-for-Windows-Phone
原文作者:Joel Ivory Johnson
译文由本站 robot-v1.0 翻译
前言
A proof of concept for an animated sprite component that I am developing to run in XNA projects on Windows Phone, PC, and Xbox. The component allows animation information to be a part of the project’s content and is a step in the direction of allowing an artist to be completely over creating animati
我正在开发要在Windows Phone,PC和Xbox的XNA项目中运行的动画精灵组件的概念证明.该组件允许动画信息成为项目内容的一部分,并且是朝着让艺术家完全超越创造动画的方向迈出的一步
介绍(Introduction)
动画的基础知识是众所周知的.通过在一组静止图像之间快速切换,我们可以创造出某种事物在运动的错觉.简单地设置动画不是什么挑战.最近,在开发游戏时,我发现有必要提出一种更好的方式来管理动画状态.通常,屏幕上可能会显示多个对象,这些对象是从同一个Sprite表绘制的.但是这些对象中的每个对象在动画中都可能处于不同的状态.还有将精灵坐标添加到程序中的问题.尽管您可以将这些坐标键入程序代码中,但这并不是最理想的解决方案.如果图像中的某些内容发生更改,则有必要对代码进行更改.本文所附代码的目的是解决以下两个问题:管理动画状态和将信息作为内容进行管理.(The basics of animation are well known; by rapidly switching among a group of still images, we can create the illusion that something is moving. Simply animating something isn’t a challenge. In working on a game recently, I found the need to come up with a better way of managing my animation state. Often times there may be multiple objects displayed on the screen that are drawn from the same sprite sheet. But each one of these objects could be in a different state in its animation. There’s also the matter of getting the sprite coordinates into the program. While you could type these coordinates into the program’s code, that’s not the most desired solution; if something in the images change, it would be necessary to make changes to the code. The purpose of the code attached to this article is to address both of those problems: managing animation state and managing the information as content.) 在确定需要更好的解决方案的第二天,我开车去了几个小时,去了一家家庭聚会.我在下面记录的是我在驱动器前一天晚上考虑并在驱动器中键入的解决方案.这是概念证明,而不是最终代码,但我认为其他人可以轻松扩展以满足他们自己的需求.(The day after concluding that I needed a better solution, I was on a car ride for a few hours heading to a family reunion. What I document below is the solution I thought out the night before the drive and typed during the drive. This is a proof of concept and not final code, but it’s an idea that I think others can easily extend to suit their own needs.)
先决条件(Prerequisites)
要利用本文中的信息,您将需要已经熟悉XNA和处理Sprite.作为.NET技术,这还意味着您需要熟悉C#和Visual Studio.对于软件,您需要有一台运行Windows Vista或更高版本的PC,并且您必须拥有(To take advantage of the information in this article, you will need to already have familiarity with XNA and handling sprites. Being a .NET technology, this also means that you need to have familiarity with C# and Visual Studio. For software, you need to have a PC running Windows Vista or higher, and you need to have the)Windows Phone开发人员工具(Windows Phone Developer Tools)已安装(即使您不打算定位Windows Phone).虽然下载的内容标记为适用于Windows Phone,但它实际上是一个软件包,其中包含适用于Windows Phone,Xbox和PC的开发工具.(installed (even if you don’t plan on targeting Windows Phone). While the download is labeled as being for Windows Phone, it is actually a package that contains the dev tools for Windows Phone, Xbox, and PC.)
PC,Xbox和Windows Phone.本文中的代码可以编译为在所有这三个代码上运行.(PC, Xbox, and Windows Phone. The code in this article can be compiled to run on all three.)## 运行项目(Running the Project) 我知道有些人希望看到项目正在运行,而无需下载和设置环境.为了该小组中的人们的利益,我上传了一个(I know there are some that want to see the project running without needing to download and setup the environment. For the benefit of the people in that group, I’ve uploaded a) 视频到YouTube(vide to YouTube) 在此我简要地介绍了该项目,然后运行它.(in which I briefly talk through the project and then run it.)
Sprite动画基础(Sprite Animation Basics)
渲染精灵时,通常您的源上带有一个或多个图像.您可以指定包含要显示的图像的边界矩形的坐标,以及要显示在屏幕上的坐标.如果要动画化精灵,则需要为动画的每一帧都有一个图像以供参考.您需要按顺序在一组源图像之间切换. XNA可以每秒最多60帧的速度渲染到屏幕上(可能更快,但是我们可以忽略它).但是对于这60帧中的每帧,您可能没有不同版本的图像.当需要切换到下一张图像时,您将需要适当的时间.(When rendering a sprite, you will normally have a source that has one or more images on it. You specify the coordinates of the bounding rectangle that contains the image you want to display along with the coordinates on the screen where you want it displayed. If you want a sprite to be animated, then you need to have an image for each frame of the animation to reference. You’ll need to switch among the set of source images in sequence. XNA can render to the screen up to 60 frames per second (possibly faster, but let’s ignore that). But you probably do not have a different version of your image for each one of those 60 frames. You will need to appropriately time when it is time to switch to the next image.) 为了更好地理解,如果您首先下载一个精灵表来查看它会很有帮助.有无数的精灵表.我决定(To better understand, it’s helpful if you first download a sprite sheet to look at. There are an uncountable number of sprite sheets out there. I decided to) ing(Bing) 一个精灵表(a sprite sheet for one of the) 马里奥兄弟(Mario Brothers) 游戏,并发现它们并不短缺.我发现(games and found no shortage of them. I found) 这个小宝石(this little gem) .这实际上不是任天堂创建的,而是由任天堂游戏迷创建的.最棒的是,这张纸上的子画面经过精心组织和标记.我要使用的是Mario运行中的一种.所以我用(. This wasn’t actually created by Nintendo but was created by fans of Nintendo games. Best of all, the sprites on this sheet are nicely organized and labeled. There’s one of Mario running that I’m going to use. So I used)油漆网(Paint.Net)复印图像并清除白色背景,以使图像透明.(to cop the image and erase the white background so that the image would be transparent.)
马里奥跑步雪碧表(Sprite sheet for Mario running)当您的眼睛跟随精灵表中的每个图像时,您可以看到动画的外观.理想情况下,子画面中的图像应均匀分布.对于来自以千字节为单位衡量可用内存的日子的图像,您可能会发现情况并非如此,因为可用内存对决策的影响大于便利.制作此精灵的人似乎只是试图最小化图像之间的空间,以使它们之间的间隔不均匀.但是我认为动画很酷,并且带回了回忆,所以我坚持使用它.我还从CodeProject.com的另一篇文章中用旋转的螺旋桨拍摄了一架飞机的图像,发现其中一只翅膀在拍打.使用这三个预先存在的图像,我发现需要花很多精力才能使它们具有动画效果(这也为我提供了将来如何改进此代码的想法).我将从最简单的图像开始(CodeProject.com文章中的平面),然后再转到更复杂的图像.(As your eyes follow each image in the sprite sheet, you can see how the animation will look. Ideally, the images in the sprite would be spaced evenly apart. For images that come from the days when available memory was measured in kilobytes, you may find this to not be the case since available memory had more impact on decisions than convenience. Who ever made this sprite seemed to only try to minimize space between the images so they are not evenly spaced. But I think the animation is cool and it brings back memories, so I am sticking with it. I also grabbed an image of a plane with a rotating propeller from another CodeProject.com article and found one of a bird flapping its wings. With these three pre-existing images, I found that a different amount of effort was needed to get them animated (which also gives me ideas on how I can improve this code in the future). I’ll start with the simplest of the images (the planes from the CodeProject.com article) and then move to the more complex.) 非常感谢(Many thanks to) 乔纳斯`弗勒索(JonasFollesø)(Jonas Follesø) 他在游戏上的文章(for his article on the game) 1945年为PocketPC(1945 for PocketPC) 并发布到(and publishing it under) 警察局(CPOL) .我从那里拍摄了飞机图像.(. I took the plane images from there.)
动画平面(Animating the Plane)
本文中的一个文件名为(One of the files in the article is named)BigPlanes.bmp(BigPlanes.bmp).我将其从位图转换为PNG,以便使其透明.该图像包含三个平面的精灵.每架飞机都有一个动画螺旋桨.这些图像均匀分布.第一个平面始于坐标(1,1),高64像素.在第一个平面的图像之后是一个灰色的1像素边框,然后第二个平面在位置(66,1)开始.由于图像的1像素边框和64像素宽度,您可以得到n的起点(. I converted it from a bitmap to a PNG so that I could make it transparent. The image contains sprites for three planes. Each one of the planes has an animated propeller. These images are evenly spaced. The first plane starts at coordinate (1,1) and is 64 pixels wide and high. After the image of the first plane is a gray 1 pixel border, and then the second plane starts at position (66,1). Because of the 1 pixel border and 64 pixel width of the images, you can get the starting point of the n)日(th)乘(n * 65)+1这使将这张Sprite表转换为动画变得更加容易.我不必查找每个图像的起点.(plane by taking (n65)+1. This makes it much easier to convert this sprite sheet into an animation. I don’t have to look up the starting point of each image.*)
BigPlanes精灵表(BigPlanes sprite sheet)我将为工作表中的前两个平面设置动画.我将它们作为同一对象不同状态的动画.我得到了三个图像{(1,1),(66,1),(131,1)}的起始X和Y位置,后三个图像{(196,1),(261,1),( 326,1)},并将它们放入我的代码中.现在,假设屏幕每秒更新30次,但我只希望此动画以每秒10帧的速度发生.以30fps的速度,播放器将能够在屏幕上平滑地移动飞机.因此,在推进框架之前,我需要跟踪经过了多少时间.在下面的代码中,我为平面设置了动画,并提供了从一个平面动画切换到另一个平面动画的方法.我遗漏了一些您可能希望在XNA程序中找到的典型代码,因此我可以突出显示特定于此任务的部分.(I’m just going to animate the first two planes in the sheet. I’ll have them as animations for different states of the same object. I got the starting X and Y positions of the three images {(1,1), (66, 1), (131, 1)} and the second three images {(196,1), (261, 1), (326, 1)} and put them in my code. For now, assume that the screen is being updated 30 times per second but I only want this animation to occur at 10 frames per second. At 30fps, the player will be able to smoothly move the plane around on the screen. So I will need to track how much time has passed before I advance the frame. In the following code, I animate the plane and provide a way of switching from one plane animation to another. I’m leaving out some of the typical pieces of code that you may expect to find in an XNA program so that I can highlight the parts that are specific to this task.)
Rectangle[] _planeSourceSpriteLocation_1 = new Rectangle[]
{
new Rectangle( 1, 1, 64, 64),
new Rectangle( 66, 1, 64, 64),
new Rectangle(131, 1, 64, 64),
};
Rectangle[] _planeSourceSpriteLocation_2 = new Rectangle[]
{
new Rectangle(196, 1, 64, 64),
new Rectangle(261, 1, 64, 64),
new Rectangle(326, 1, 64, 64),
};
Rectangle[] _planeSourceSpriteLocation_current;
int _frameNumber;
TimeSpan _frameLength;
Vector2 _position = new Vector2(100,100);
Texture2D _planeTexture;
void SetPlaneSource(int i)
{
_planeSourceSpriteLocation_current= (i==1)?
_planeSourceSpriteLocation_2 :
_planeSourceSpriteLocation_1;
}
protected override void LoadContent()
{
///code for loading sprites here
SetPlaneSource(1);
}
protected override void Update(GameTime gameTime)
{
_frameLength += gameTime.ElapsedGameTime;
//if the frame has been displayed for more than 0.1 seconds then
//advance to the next frame.
if(_frameLength.TotalSeconds > 0.1d)
{
_frameLenth -= TimeSpan.FromSeconds(0.1);
++_frameNumber;
//Ensure we haven't advanced past the last frame by looping
//back to the first frame.
if(_frameNumber >
显示精灵的一种方式.(One way of displaying the sprites.)您会看到,跟踪和处理单个精灵的大量信息.这不是您要对多个精灵执行此操作的方式;代码会很快变得混乱.在本文结束之前,我将展示一种组织此信息的方法.(You can see that’s a lot of information to track and handle for a single sprite. This isn’t the way you would want to do this for multiple sprites; the code would get messy fast. I’ll show one way of organizing this information before this article is over.)
动画鸟(Animating the Bird)
对于另一个我发现的鸟的雪片,图像的间距不是均匀的,但这在使用它时并没有太大的障碍.这只鸟拍动翅膀,但头部静止不动.因此,当定义边界矩形时,请确保在每个矩形中鸟的头都位于同一位置.如果不这样做,当动画设置时,这只鸟似乎会抖动.一旦有了绑定矩形,其余的任务将与平面相同.(For another sprite sheet I found of a bird, the images were not evenly spaced, but this didn’t make for much of an obstacle in using it. The bird is flapping its wings but its head is stationary. So when I defined my bounding rectangles, I ensured that the bird’s head was in the same place in each one of the rectangles. If this isn’t done, the bird would appear to shake when it is animated. Once I have my binding rectangles, the rest of the task would be the same as it was with the plane.)
动画的鸟图像.(Animated bird image.)尽管我的矩形数组将具有4个而不是3个矩形,但是上一个示例中的代码可为该鸟设置动画.(The code from the previous example would work for animating this bird, though my array of rectangles would have 4 rectangles instead of 3.)
动画马里奥跑步(Animating Mario Running)
在尝试制作Mario动画时,遇到了第一个真正的障碍.该精灵中的图像间隔不均匀.所以我用了和鸟一样的技术.我选择了图像的静止部分,并获得了相对于该静止位置的坐标.当我这样做时,我遇到了第二个问题.图像的宽度不同.如果我使用与图像之一的最小宽度匹配的边界矩形,则在某些帧中,动画的某些部分将被剪切.如果我使用与最大宽度匹配的矩形,那么我会无意间以某些帧中不适当地显示相邻图像的某些部分而告终.我可以使用各种宽度的矩形,以便每个矩形适当地绑定其预期的框架而不会溢出到其他框架中,但是这样做将使(In trying to animate Mario, the first real obstacle was encountered. The images in that sprite were not evenly spaced. So I used the same technique that I did on the bird; I chose a stationary part of the image and got my coordinates relative to that stationary position. When I did this, I ran into a second problem; the images were not the same width. If I use a bounding rectangle that matches the minimum width of one of the images, then in some of the frames, parts of the animation get clipped. If I use a rectangle that matches the maximum width, then I will unintentionally end up with parts of the neighboring images inappropriately showing up in some frames. I could use rectangles of various widths so that each rectangle appropriately bound its intended frame without spilling over into the other frames, but doing that would obligate the) Draw
修改代码以适应需要绘制的矩形稍有不同.我考虑过要解决这个问题,但还是决定反对.像这样容纳图像会使代码复杂得多.经过深思熟虑,我决定将与动画关联的所有子画面的长度都设为相同.我没有在代码中强制执行此操作,但是我也不支持它.(code to be modified to accommodate the rectangles needing to be drawn slightly different. I thought about making a solution to address this but decided against it. Accommodating images like this makes the code considerably more complex. After much thought, I decided instead to make it a requirement that all sprites associated with an animation be of the same length. I don’t enforce this within my code, but I don’t support it either.)
表示精灵动画(Representing a Sprite Animation)
我已经经历了一些简单的精灵动画场景,并对代码需要做的事情有很好的感觉.接下来是一种将子画面动画化为类所需的信息的组织方式.(I’ve run through a few simple sprite animation scenarios and have a good feel for what I need for the code to do. What follows is one way of organizing the information needed for animating sprites into classes.)
指定精灵坐标(Specifying Sprite Coordinates)
精灵表本质上只是一张图像,上面有很多较小的图像.通常,从精灵表绘制图像时,必须指定两组坐标.让我们看一下在屏幕上绘制精灵的典型调用.我已将数据类型放入注释中.(A sprite sheet is essentially just an image that has a lot of smaller images on it. Normally, when you are drawing an image from a sprite sheet, you will have to specify two sets of coordinates. Let’s take a look at a typical call to draw a sprite onto the screen. I’ve put the data types in comments.)
spriteBatch.Draw(
/*Texture2D*/ sourceTexture,
/*Rectangle*/ destinationRectangle,
/*Rectangle*/ sourceRectangle,
/*Color*/ tint
);
目标矩形将完全取决于您需要在屏幕上绘制对象的位置,这取决于对象当前在游戏世界中的位置.但是源矩形将具有较小的方差.每次绘制同一对象时,纹理的源坐标将在编译时确定.因此,所寻求解决方案的一个特性是它将需要能够跟踪子图形表的源坐标.每个源矩形坐标还将与需要显示子画面的一定时间长度相关联.如果我的动画每秒播放15帧,则与每个帧相关的时间长度将是1/15秒.(The destination rectangle is going to depend on exactly where on the screen your object needs to be drawn, which will be dependent on where within your game world the object is currently positioned. But the source rectangle will have less variance. Every time you draw the same object, the source coordinates for the texture are going to be decided at compile time. So an attribute of the solution being sought is that it will need to be able to keep track of the source coordinates from the sprite sheet. Each source rectangle coordinates will also be associated with some length of time for which the sprite needs to be displayed. If I had an animation that was playing 15 frames per second, then the length of time associated with each frame would be 1/15 of a second.)
public class SpriteSource
{
public TimeSpan FrameLength { get; set; }
public double FrameLengthSeconds
{
get { return FrameLength.TotalSeconds; }
set { FrameLength = TimeSpan.FromSeconds(value); }
}
public Rectangle SourceRectangle { get; set; }
}
的(The) FrameLengthSeconds
字段看起来是多余的,但是由于此数据将保存到文件中,因此必须可序列化.的(field looks redundant, but since this data will be saved to a file, it must be serializable. The) TimeSpan
成员未序列化,因此我添加了一个别名字段((member doesn’t serialize, so I have added an alias field () FrameLengthSeconds
)类型() of type) double
将会序列化.(that will serialize.)
将坐标分组为框架(Grouping Coordinates into Frames)
动画是帧的集合,因此我需要能够将帧组织到列表中.通用列表类可满足此需求.但是任何给定的动画对象都可以与多个动画相关联.例如,对于某一个步行与某个跑步,您可能具有不同的动画.当播放器处于正常模式还是通电时,您可能会有不同的动画.或者,您可能对角色可能走过的每个方向都有一个动画.因此,我的动画将具有帧的集合,但是每个对象也可能具有动画的集合.我更喜欢在我的班级中按名称引用动画,以存储属于动画的帧.我还需要能够确定应在特定时间索引处显示哪个帧.由于显示的动画将取决于对象的状态,因此我借鉴了XAML概念,并将调用表示特定状态下的对象的帧的集合,(An animation is a collection of frames, so I need to be able to organize my frames into a list. A generic list class meets this need. But any given animated object could be associated with more than one animation. For example, you may have different animations for some one walking vs. some one running. You may have a different animation for when a player is in normal mode vs. powered up. Or you may have an animation for each direction in which a character could walk. So my animation will have a collection of frames, but each object could also have a collection of animations. I prefer to reference to animations by name in my class for storing the frames that belong to an animation. I also need to be able to identify which frame should be displayed at a specific time index. Since the animation shown is going to be dependent on an object’s states, I’m borrowing from a XAML concept and will call the collection of frames that represent an object in a specific state, the) VisualState
.(.)
public class VisualState
{
public VisualState()
{
SpriteSourceList = new List<SpriteSource>();
}
public SpriteSource this[TimeSpan index]
{
get {
if(TotalLength.Ticks>0)
while (index > TotalLength)
index -= TotalLength;
int i = 0;
while(index>SpriteSourceList[i].FrameLength)
{
++i;
index -= SpriteSourceList[i].FrameLength;
}
return SpriteSourceList[i];
}
}
public TimeSpan TotalLength
{
get { return TimeSpan.FromTicks(
SpriteSourceList.Sum((ss) => ss.FrameLength.Ticks)); }
}
public string Name { get; set; }
public List<SpriteSource> SpriteSourceList { get; set; }
}
将单个对象的动画分组(Grouping Animations for a Single Object)
由于任何对象都可以具有多个动画,因此列表可以很好地将这些动画分组为一个实体.仅仅为了这个目的,我还给动画集命名(没有功能上的原因,但是在调试时,它有助于将集合与名称相关联),并且还添加了一个成员来引用动画(Since any object can have more than one animation, a list works out fine for grouping those animations into a single entity. Just for the sake of it, I also gave the collection of animations a name (there’s no functional reason for doing this, but when debugging, it helps to be able to associate the collection with a name) and also added a member to reference the) Texture2D
动画将从中提取图像.(from which the animation will be pulling its images.)
public class AnimatedSprite
{
public string Name { get; set; }
public string PreferredTextureName { get; set; }
[XmlIgnore]
internal Texture2D CurrentTexture { get; set; }
public void SetTexture(Texture2D sourceTexture)
{
CurrentTexture = sourceTexture;
}
public List<VisualState> VisualStateList { get; set; }
public AnimatedSprite()
{
VisualStateList = new List<VisualState>();
}
public AnimatedSpriteInstance CreateInstance()
{
return new AnimatedSpriteInstance(this);
}
}
上面的代码中有两件事我还没有谈到.的(There are two things in the above code that I haven’t talked about. The) CreateInstance()
方法尚未解释,也没有理由(method hasn’t been explained nor has the reason that) CurrentTexture
被标记为(is marked as) internal
被解释了.我会回到(been explained. I’ll come back to the) internal
标记在(marking on) CurrentTexture
一会儿.让我们来谈谈(in a moment. Let’s talk about) CreateInstance()
.(.)
动画跟踪实例(Tracking Instances of Animations)
屏幕上可能有多个项目正在使用同一动画.为每个对象保留每个动画的精确副本可能会过大.但是也有必须特定于每个实例的数据.因此,我已将常见数据与实例数据分开.的(There are likely to be multiple items on the screen that are using the same animation. It would be overkill to keep an exact duplicate of each animation for each object. But there is also data that must be specific to each instance. So I’ve separated the common data from the instance data. The) AnimatedSprite
类包含公共数据.每个具有自己动画的对象都需要具有实例数据,该实例数据可以从(class contains the common data. Each object that has its own animation will need to have instance data which can be acquired from the) CreateInstance()
类.该方法实例化一个新的(class. This method instantiates a new) AnimatedSpriteInstance()
.我尚未将此类的构造函数公开,因此创建一个的唯一方法是首先拥有一个(. I’ve not made the constructor for this class public so the only way to create one is to first have an) AnimatedSprite
然后致电(and then call the) CreateInstance()
方法.(method.)
此类所包含的最重要的信息是对引用实例的动画效果的引用(在(The most vital pieces of information that this class contains are a reference to the animated spite from which the instance was made (in the) Parent
属性),动画的时间进度(在(property), the time progress of the animation (in the) _timeOffset
字段)和状态(用于确定要使用哪个动画集)(field), and the state, which determines which animation set is being used (in the) PresentState
财产,但通过(property, but manipulated through the) StateName
属性).如果出于某种原因我决定某个动画的特定实例应从其他纹理中拉出,那么我公开了一个名为(property). If for some reason I decide that a specific instance of an animation should pull from a different texture, I’ve exposed a property called) TextureOverride
默认为(that defaults to) null
.将此设置为另一个纹理将使动画实例使用该新纹理.将其设置回(. Setting this to another texture will cause the animation instance to use that new texture. Setting it back to) null
会使它恢复到原来的纹理.还有一个(will cause it to revert back to its original texture. There is also a) Position
成员,您可以用来设置精灵在屏幕上的显示位置.(member that you can use to set where the sprite will appear on the screen.)
动画通过(The animation is advanced through) AnimationInstance.Update(GameTime)
.通常,您会在(. In general, you would call this early within the) Draw
方法.但是,如果动画需要与某个定时事件进行同步,那么最好在游戏的(method. But if an animation needs to be synced with some timed event, then it would be better to call this in your game’s) Update()
方法.的(method. The) Draw()
如果某件事会使游戏变慢,则可能不会在每个周期都调用此方法,但是(method may not get called every cycle if something slows a game down, but) Update
将始终被调用.到达最后一帧后,所有动画将循环播放.所以(will always be called. All animations will loop once the last frame is reached. So the) AnimatedSpriteInstance.Update()
方法将重置(method will reset the) _timeOffset
只要到达动画的末尾.(whenever it reaches the end of the animation.)
的(The) Draw
方法将照顾到绘制精灵的适当框架.您只需要给它(method will take care of drawing the appropriate frame of the sprite. You only need to give it the) SpriteBatch
实例使用.您可能还记得(instance to use. You might recall that the) SpriteBatch
方法有很多重载(method has a number of overloaded) Draw
方法.在此示例代码中,我只使用了一个,但是您可能需要更改它,以使用提供更多选项的重载方法之一.您可以通过将新成员添加到(methods. I’ve only used one in this example code, but you may want to change this to use one of the overloaded methods that provides more options. You could either expose these options by adding new members onto the) AnimatedSpriteInstance.Draw()
方法,也可以添加新属性来保存将传递给其他参数的值.例如,我添加了一个(method, or you could add new properties to hold values that would be passed to the additional parameters. For example, I’ve added a) Color Tint { get; set; }
将作为(property that will get passed as the) Tine
动画的参数.(parameter for the animation.)
public class AnimatedSpriteInstance
{
private TimeSpan _timeOffset;
public Vector2 Position { get; set; }
public AnimatedSprite Parent { get; internal set; }
public SpriteBatch TargetSpriteBatch { get; set; }
public Texture2D TextureOverride { get; set; }
public VisualState PresentState { get; protected set; }
public Color Tint { get; set; }
private string _stateName;
public string StateName
{
get { return _stateName; }
set
{
VisualState newState =
(from VisualState s in Parent.VisualStateList where
s.Name.Equals(value) select s).FirstOrDefault();
if(newState==null)
throw new IndexOutOfRangeException(String.Format(
"There is no state named {0} in this sprite", value));
_stateName = value;
PresentState = newState;
}
}
public AnimatedSpriteInstance()
{
Tint = Color.White;
TextureOverride = null;
Reset();
}
public AnimatedSpriteInstance(AnimatedSprite parent):this()
{
Parent = parent;
Reset();
}
public void Reset()
{
_timeOffset = TimeSpan.Zero;
if (Parent != null)
{
PresentState = Parent.VisualStateList[0];
_stateName = PresentState.Name;
}
}
public void Update(GameTime gameTime)
{
_timeOffset += gameTime.ElapsedGameTime;
while (_timeOffset >= PresentState.TotalLength)
_timeOffset -= PresentState.TotalLength;
}
public void Draw(SpriteBatch targetBatch)
{
var spriteSource = PresentState[_timeOffset].SourceRectangle;
targetBatch.Draw( (TextureOverride ?? Parent.CurrentTexture),
Position, spriteSource, Color.White);
}
public void Draw()
{
Draw(TargetSpriteBatch);
}
}
类图(Class Diagram)
类图.(Class diagram.)## 支持动画成为内容(Making Support for an Animation to be Content) 我编写此代码的部分原因是,我想开始从代码中删除动画的详细信息,然后将其放入另一个资源中,以便某些没有编程技能的人可以修改和创建动画.我的第一步是允许在XML文件中指定有关精灵位置的信息.尽管我不希望大多数艺术家都熟悉XML文件,但这是离人类(艺术家)友好的解决方案越来越近的一步.下一步将是创建一种工具,以便某人可以以图形方式指定精灵的位置.我不会在本文中讨论这一部分.因此,我只想谈论在这里创建XML文件并在XNA项目中使用它.在将这种代码用于其他几种场景之后,我将讨论创建用于处理此信息的图形工具.我不知道将来的需求会给代码带来什么变化,也不想拥有一个图形编辑器,当我调整动画代码时可能需要对其进行更新.一旦动画代码进一步成熟并可以处理更广泛的场景,那么我将研究制作图形工具.(Part of the reason that I wrote this code is I wanted to start removing the specifics of an animation from my code and put it into another resource that some one without a programming skill set can modify and create the animations. My first step in this is to allow the information on the sprite locations to be specified in an XML file. While I don’t expect most artists to be familiar with XML files, it is an incremental step closer to a human (artist) friendly solution. The next step would be to create a tool so that some one could specify the locations of the sprites graphically. I won’t be tackling that part in this article. So I only want to talk about creating the XML file here and consuming it in an XNA project. I’ll talk on creating a graphical tool for manipulating this information after I’ve used this code for several more scenarios. I don’t know what changes future needs will bring to the code, and don’t want to yet have a graphical editor that would possibly require being updated as I adjust the animation code. Once the animation code further matures and handles a wider range of scenarios, then I will look at making a graphical tool.) XNA具有称为内容管道的功能,用于处理项目中的内容处理.如果您不熟悉XNA,则可能很高兴使用了内容管道,而不必考虑其内部工作原理.内容管道已经具有处理某些常见文件类型(MP3,PNG,3D模型格式等)所需的内容,我想对其进行扩展以使其也能够处理我的动画.完成后,我将能够对PC,Xbox和Windows Phone制作的游戏使用相同的技术.(XNA has a facility called the content pipeline made to handle the processing of contents in a project. If you are new to XNA, you’ve probably been happily using the content pipeline without having to consider its inner workings. The content pipeline already has what is needed to handle some common file types (MP3, PNG, 3D Model Formats, and so on) and I wanted to extend it to also be able to handle my animations. Once done, I would be able to use the same technique for games made for the PC, the Xbox, and the Windows Phone.)
内容管道基础(Content Pipeline Basics)
通过内容管道的资产在四个不同的对象中进行处理:导入器,处理器,写入器和读取器.进口商读取资产并将其传递给处理器.处理器查看从资产中读取的数据并对其进行解释,并构建其他一些对象,这些对象表示您希望数据在加载内容时所处的形式.编写器将对象序列化为流.游戏将在运行时使用读取器来加载保存的内容. XNA定义了用于实现这些对象中的每种类型的通用基类.(Assets that go through the content pipeline are processed in four different objects: an importer, a processor, a writer, and a reader. The importer reads the asset and passes it off to the processor. The processor looks at the data that was read from the asset and interprets it, building some other object that represents the form that you want the data to be in when the content is loaded. The writer serializes the object to a stream. And the reader will be used by the game at runtime to load the saved content. XNA defines generic base classes for implementing each one of these types of objects.) 在这四种类型的对象中,其中三种在设计时使用(导入程序,处理器,编写器),一种在运行时使用(读取器).这三个设计时类可以是一个项目的一部分.运行时类必须与您的项目一起部署,并且必须为将在其上运行游戏的每个平台创建一个运行时类的版本.在我的情况下,运行时项目将只是共享相同文件的项目,因为从Windows Phone到PC到Xbox的源代码都没有区别.让我们更详细地了解一下如何实现这些类中的每一个.我从创建一个新项目开始.在新项目对话框中,XNA组下的Content Extension项目有一个选项.制作完项目后,我开始向其中添加以下内容.(Of those four types of objects, three of them are used at design time (importer, processor, writer) and one is used at runtime (reader). The three design-time classes can be part of one project. The runtime class must be deployed with your project and a version of the runtime class must be created for each platform on which your game will run. In my case, the runtime projects will just be projects that share the same files since there will be no difference in the source code from Windows Phone to PC to Xbox. Let’s look at how I implemented each one of these classes in more detail. I started off by creating a new project. In the new project dialog, there is an option for a Content Extension project under the XNA group. After I made the project, I started adding the following to it.)
内容导入器(Content Importer)
XNA定义通用基类(XNA defines the generic base class) ContentImporter<T>
用于实现内容导入器.此类仅将数据加载到可以传递给处理器的内容中,否则不对文件进行任何处理.内容导入器必须声明其处理的文件类型的扩展名,并定义要使用的处理器以及友好的显示名称.所有这些信息都通过一个(for implementing a content importer. This class only loads the data into something that can be passed to the processor, but otherwise does not do any processing on the file. The content importer must declare the extension for the types of files it processes and define the processor to be used along with a friendly display name. All of this information is passed through a) ContentImporterAttribute
在课堂上.(on the class.)
[ContentImporter(".animatedSprite",
DefaultProcessor = "AnimatedSpriteProcessor",
DisplayName = "Animated Sprite Importer")]
public class AnimatedSpriteImporter: ContentImporter<AnimatedSpriteDescription>
{
public override AnimatedSpriteDescription Import(
string filename, ContentImporterContext context)
{
string spriteXml = File.ReadAllText(filename);
return new AnimatedSpriteDescription(
Path.GetFileNameWithoutExtension(filename), spriteXml);
}
}
AnimatedSpriteDescription
是我定义的用于保存文件内容的类.它仅包含两个字段,(is a class I’ve defined for holding the contents of the file. It only contains two fields,) AnimatedSpriteXml
和(and) AnimationName
,两者都是字符串.(, both of which are strings.)
public class AnimatedSpriteDescription
{
public string AnimatedSpriteXml { get; set; }
public string AnimationName { get; set; }
public AnimatedSpriteDescription(string name, string xml)
{
AnimationName = name;
AnimatedSpriteXml = xml;
}
}
内容处理器(Content Processor)
内容处理器将从其接收的数据转换为(The content processor converts the data it receives from the) ContentImporter
到一个对象.通用基本类,用于实现(to an object. The generic base class for implementing a) ContentProcessor
您可能已经猜到了(, as you may have guessed, is) ContentProcessor<InputType, OutputType>
.(.) InputType
需要您的进口商类型,并且(needs the type of your importer and) OutputType
应该设置为处理器产生的类型.我刚刚向您介绍了进口商.我的处理器的输出是(should be set to the type that the processor produces. I just introduced you to the importer. The output of my processor is an) AnimatedSprite
;与我在本文开头描述的类型相同.需要在派生类中重写的唯一方法是(; the same type that I described in the beginning of this article. The only method that needs to be overridden in the derived class is the) Process
方法.(method.)
[ContentProcessor(DisplayName = "Animated Sprite Processor")]
public class AnimatedSpriteProcessor :
ContentProcessor<AnimatedSpriteDescription, AnimatedSprite>
{
public override AnimatedSprite Process(AnimatedSpriteDescription input,
ContentProcessorContext context)
{
XmlSerializer xs = new XmlSerializer(typeof(AnimatedSprite));
StringReader sr = new StringReader(input.AnimatedSpriteXml);
var entry = (AnimatedSprite)xs.Deserialize(sr);
//----
//additional processing done here
//----
return entry;
}
}
由于我使用的是XML格式,因此您可以看到我不需要做很多处理.(Since I went with an XML format, you can see that I didn’t have to do much processing.)
内容作家(Content Writer)
您的资产将通过的最后一个设计时组件是一个类,它源自(The last design time component through which your asset will pass is a class derived from) ContentTypeWriter<ContentType>
.它有一些方法需要重写.如您所料,(. It has a few methods that need to be overridden. As you would expect, there is a) Write
必须序列化您的内容的方法.作为参数,它收到一个(method that must serialize your content. As arguments, it receives a) ContentWriter
以及由(and the object produced by the) ContentProcessor
需要写的.还有其他两种方法也需要重写:(that needs to be written. There are two other methods that need to be overridden too:) GetRuntimeType
和(and) GetRuntimeReader
.方法(. The method) GetRuntimeType
应该返回一个字符串,该字符串标识该程序集以及该程序集内必须实例化以保存您的内容的类型.(should return a string that identifies the assembly and the type within the assembly that must be instantiated for holding your content.) GetRuntimeType
返回一个字符串,该字符串标识程序集以及包含该程序集的程序集中的类(returns a string that identifies the assembly and the class within the assembly that contains the) ContentReader
.(.)
class AnimatedSpriteWriter: ContentTypeWriter<AnimatedSprite>
{
protected override void Write(ContentWriter output, AnimatedSprite value)
{
XmlSerializer xs = new XmlSerializer(typeof(AnimatedSprite));
System.IO.StringWriter sw = new StringWriter();
xs.Serialize(sw, value);
output.Write(value.Name);
output.Write(sw.ToString());
}
public override string GetRuntimeType(
Microsoft.Xna.Framework.Content.Pipeline.TargetPlatform targetPlatform)
{
return "J2i.Net.AnimatedSriteLibrary, AnimatedSprite, " +
"Version=1.0.0.0, Culture=neutral";
}
public override string GetRuntimeReader(
Microsoft.Xna.Framework.Content.Pipeline.TargetPlatform targetPlatform)
{
return "J2i.Net.AnimatedSriteLibrary, AnimatedSpriteReader, " +
"Version=1.0.0.0, Culture=neutral";
}
}
ContentTypeReader(ContentTypeReader)
我讨论过的其他三个内容类都在您构建游戏时在您的PC上执行.他们都没有被推出到将运行游戏的机器或设备上.的(The other three content classes that I’ve talked about all execute on your PC while you are building a game. None of them get pushed out to the machine or device that will run the game. The) ContentTypeReader
与这些不同之处在于它必须随游戏一起分发.由于您的XNA游戏可以在三个平台(Windows Phone,Xbox和PC)之一上运行,因此您将需要具有三个版本的内容阅读器.这三个代码的源代码可以相同.您甚至可以使用同一文件来编译所有三个版本.但是这些平台中的每个平台都有其自己的可执行文件二进制格式,因此对于这三个平台,您将不会拥有完全相同的二进制文件.现在,我将只针对PC.添加Windows Phone和Xbox所需的支持很简单,因此我将保存该任务以供以后使用.(differs from these in that it must be distributed with the game. Since your XNA game could be running on one of three platforms (Windows Phone, Xbox, and PC), you will need to have three versions of your content reader. The source code for all three can be identical. You can even use the same file to compile all three versions. But each one of these platforms has its own binary format for executables so you won’t have the exact same binary for all three platforms. For now, I am going to stick with targeting PC only. Adding the support needed for Windows Phone and Xbox is a trivial effort so I will save that task for later.)
自从(Since the) ContentTypeReader
将由使用以下代码的相同代码使用(will be used by the same code that uses the) AnimatedSprite
我之前构建的项目适合将其纳入同一项目.我向该项目添加了一个新类,并使其继承自(that I built earlier, I find it appropriate to make it part of the same project. I added a new class to that project and had it inherit from) ContentTypeReader<AnimatedSprite>
并覆盖(and overrode the) Read
方法.作为参数,此方法接收对(method. As arguments, this method receives a reference to a) ContentReader
以及可以写入内容的实例化对象.由于我使用XML序列化来编写资产,因此我可以使用(and an instantiated object that the contents could be written to. Since I used XML serialization to write the asset, I can use the) Deserialize
上的方法(method on the) XmlSerializer
一旦我从(class once I read the data from the) ContentTypeReader
.(.)
public class AnimatedSpriteReader : ContentTypeReader<AnimatedSprite>
{
protected override AnimatedSprite Read(ContentReader input,
AnimatedSprite existingInstance)
{
var xs = new System.Xml.Serialization.XmlSerializer(typeof(AnimatedSprite));
string name = input.ReadString();
string info = input.ReadString();
System.IO.StringReader sr = new System.IO.StringReader(info);
var animatedSprite = (AnimatedSprite)xs.Deserialize(sr);
return animatedSprite;
}
}
制作动画(Creating the Animation)
创建动画仅需输入具有正确坐标的XML文件并将其赋予(Creating an animation is just a matter of typing an XML file with the right coordinates and giving it the)**.animatedSprite(.animatedSprite)**延期.我在下面输入了平面动画的代码.您可能会注意到每个框架都有自己的(extension. I’ve typed the code for the plane animation in the following. You might notice that each frame has its own) FrameLengthSeconds
设置.这是因为并非每个帧都必须具有相同的长度.我还给了动画两个状态.一个叫(setting. This is because not every frame has to be the same length. I’ve also given the animation two states; one called) Main
这将是动画的默认状态,另一个称为(that will be the animation’s default state and another called) Green
.(.)
<?xml version="1.0" encoding="utf-8"?>
<AnimatedSprite xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Name>Plane Map</Name>
<VisualStateList>
<!-- The first visual state for the main animation -->
<VisualState>
<Name>Main</Name>
<SpriteSourceList>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>1</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>67</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>133</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
</SpriteSourceList>
</VisualState>
<!-- The second visual state for the Green animation -->
<VisualState>
<Name>Green</Name>
<SpriteSourceList>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>199</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>265</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
<SpriteSource>
<FrameLengthSeconds>0.1</FrameLengthSeconds>
<SourceRectangle>
<X>331</X>
<Y>1</Y>
<Width>64</Width>
<Height>64</Height>
</SourceRectangle>
</SpriteSource>
</SpriteSourceList>
</VisualState>
</VisualStateList>
</AnimatedSprite>
要使用精灵,我必须在XNA游戏中对内容项目做一些事情:(To use the sprite, I had to do a few things to the content project in my XNA game:)
- 添加对动画设计时项目的引用(Add a reference to the animation design time project)
- 添加(Add the)**.animatedSprite(.animatedSprite)**文件到内容项目(file(s) to the content project)
- 将图像资源添加到内容项目(Add the image resources to the content project)
添加了带有动画精灵资源的内容项目.(Content project with animated sprite assets added.)在"游戏"项目中,还需要做一些事情.我需要添加对包含运行时信息的项目的引用.我都把(In the Game project, there are also a few things that need to be done. I need to add a reference to the project that contains the runtime information. I put both the) ContentTypeReader
和同一项目中我的精灵的类声明,尽管这不是强制性的.一旦完成,我就可以加载精灵动画,图像并开始显示它们.回想一下您必须创建一个实例(and the class declarations for my sprites in the same project, though this isn’t mandatory. Once that is done, I can load the sprite animations, images, and start displaying them. Recall that you must create an instance of) AnimatedSpriteInstance
在屏幕上显示某些内容.创建精灵实例后,我需要调用以使其保持动画效果的唯一方法是(to display something on screen. Once the sprite instance is created, the only methods I need to call to keep it animating are) Update
和(and) Draw
.该课程将自己跟踪其他所有内容.(. The class will keep track of everything else on its own.)
private AnimatedSprite _animatedSprite;
private Texture2D _spriteTexture;
private AnimatedSpriteInstance _spriteInstance;
protected override void LoadContent()
{
// Create a new SpriteBatch, which can be used to draw textures.
spriteBatch = new SpriteBatch(GraphicsDevice);
//Load assets
_spriteTexture = Content.Load<Texture2D>("allies");
_animatedSprite = Content.Load<AnimatedSprite>("MyAnimatedSprite");
SpriteFont sf = Content.Load<SpriteFont>("DebugFont");
//Give the animatedSprite object its sprite sheet
_animatedSprite.SetTexture(_spriteTexture);
//Create an instance of the animation for display
_spriteInstance = _animatedSprite.CreateInstance();
}
protected override void Update(GameTime gameTime)
{
_spriteInstance.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(Color.Black);
// TODO: Add your drawing code here
spriteBatch.Begin();
_spriteInstance.Draw(spriteBatch);
spriteBatch.End();
base.Draw(gameTime);
}
当动画具有多个视觉状态时,我可以通过设置(When an animation has multiple visual states, I can switch between the states by setting the) StateName
实例上的值.(value on the instance.)
_spriteInstance.StateName="NewState";
在其他平台上运行(Running on Other Platforms)
前面我提到过,您需要为要在其上运行的每个平台制作一个版本的运行时代码.当我刚开始时,我只是在PC上运行此代码.使它在Windows Phone或Xbox上运行几乎不需要任何努力.首先,让我们看一下项目布局.(I mentioned earlier that you would need to make a version of the run time code for each platform on which you intend it to run. When I started off, I was only running this code on a PC. Making it run on a Windows Phone or Xbox requires little effort. First, let’s take a look at the project layout.)
项目布局.(Project layout.)我将游戏作为Windows XNA项目,游戏的内容项目,游戏类库(包含运行时类)和内容扩展项目(包含设计时组件)来使用.在这些项目中,仅Windows XNA游戏项目和类库需要转换.其他将保持不变.右键单击游戏项目,然后选择复制Xbox 360或Windows Phone项目的选项.(I have the game as a Windows XNA project, the game’s content project, a game class library (which contains the runtime classes), and the content extension project (which contains the design time components). Of these projects, only the Windows XNA game project and the class library need to be converted. The others will remain untouched. Right-click on the game project and select either the option to copy the project for Xbox 360 or Windows Phone.)
游戏及其依赖的类库都将被复制到Windows Phone或Xbox项目中.也许我应该使用"复制"一词,因为两个项目都引用了相同的源文件.如果您在一个项目中对文件进行更改,由于该文件是共享的,因此您还将在另一个项目中看到更改.当您尝试编译项目时,它将失败,说明无法找到该项目的类定义.(Both the game and the class library on which it depends will be copied into a Windows Phone or Xbox project. Perhaps I should use the word “copy” because the same source files are being referenced by the two projects. If you make a change to a file in one project, since the file is shared, you will see the change in the other also. When you try to compile the project, it will fail, stating that it cannot find the class definition for) System.Xml.Serialization.XmlSerialization
.在Windows Phone和Xbox上,此类存在于自己的程序集中,而在PC上,则存在于(. On Windows Phone and Xbox, this class exists in its own assembly, while on the PC, it exists in the)**System.Xml.dll(System.Xml.dll)**部件.因此,您需要添加对(assembly. So you will need to add a reference to the)**System.Xml.Serialization.dll(System.Xml.Serialization.dll)**运行时项目和游戏项目中的程序集.(assembly in both the runtime project and the game project.)
未来的改进(Future Improvements)
我已经写了这作为概念证明.我还希望它支持其他一些功能,但与其尝试使它立即完成我想做的所有事情,不如从小做起.我的目标之一是在开车过程中完成这项工作,这限制了我可以完成的工作量.尽管我认为时间限制不是一件坏事;这个项目的目标是获得最初的东西(I’ve written this as a proof of concept. There are some other features I want it to support but rather than trying to get it to do everything I wanted to do at once, I decided it was best to start small. Part of my goal was to get this working during the car ride and that limits how much I can get done. Though I don’t view that time limit as a bad thing; the goal for this project was to get something that is initially) 满足(satisficing) .在使用到目前为止的内容时,我有一些认识,例如需要支持"调试"精灵.我实施了一个快速解决方案来满足我的需求,以便可以随时查看正在使用的帧索引.我还需要以其自然大小以外的其他方式来绘制精灵,并在将来需要旋转支持.现在,我只是要跟踪对未来的需求,并充分修改此代码,以使其满足我现在正在开发的游戏的需求(我不想随范围的推移而被跟踪).我还添加了一个(. In using what I have so far, I had some realizations such as needing support for “debugging” sprites. I implemented a quick solution to fit my needs so that I could see the frame index being used at any point in time. I also will need support for drawing sprites at something other than their natural size and support for rotation in the future. For now, I am just going to keep track of needs for the future and modify this code enough so that it meets the needs for the game I am working on now (I don’t want to get side tracked with scope creep). I’ve also added a) Scale
属性,用于放大图像,尽管代码中的实现不是我想要的最终代码中的实现.(property for enlarging the image, though the implementation in the code isn’t the implementation I want to have in the final code.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C# Mobile XBox Windows-Phone-7 Windows Dev XNA 新闻 翻译