.NET射击游戏(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/shootem-up-net-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 32 分钟阅读 - 16014 个词 阅读量 0.NET射击游戏(译文)
原文地址:https://www.codeproject.com/Articles/677417/Shootem-Up-NET
原文作者:Paulo Zemek
译文由本站 robot-v1.0 翻译
前言
This is a small tutorial on how to create a Shoot’em Up game in .NET using mostly declarative animations.
这是一个小教程,介绍如何使用主要是声明性的动画在.NET中创建Shoot’em Up游戏.
- 下载源代码-6.1 MB-(Download source code - 6.1 MB -) http://paulozemek.azurewebsites.net/ShootEmUp.NET.zip(http://paulozemek.azurewebsites.net/ShootEmUp.NET.zip)
介绍之前(Before Introduction)
如果您只想下载代码并查看发生了什么,那么重要的事情是:(If you only want to download the code and see what’s happening, then the important things are:)
- 有一个解决方案(There’s a solution for)**银光5(Silverlight 5)**还有一个(and one for)WPF(WPF),但大多数单位都被两者简单地使用;(, but most units are simply used by both;)
- 游戏使用方向键移动,(The game uses the directional keys to move,)**空间(Space)**射击和(to shot and either)退出(Esc),(,)**P(P)**要么(or)**输入(Enter)**暂停(to pause;)
- 游戏分为三个级别.在这两个级别之间,您可能会获得舰船的升级,因此,如果您认为最初的舰船太弱了,那是故意的.(The game has three levels. Between the levels you may acquire upgrades to your ships, so if you think the initial ships are too weak, well, that’s on purpose.) 你可以玩(You can play the)**银光(Silverlight)**通过打开此链接进行游戏:(game by opening this link:) http://paulozemek.azurewebsites.net/ShootEmUpTestPage.2013_10_10.html(http://paulozemek.azurewebsites.net/ShootEmUpTestPage.2013_10_10.html) 或者您可以下载(Or you can download the)**WPF(WPF)**通过单击下一张图片的版本:(version by clicking on the next image:)
介绍(Introduction)
我知道许多程序员都喜欢写游戏.我本人就开始为计算机编程,因为我想创建游戏,即使我已经写了一些游戏,我也花费了大部分时间来处理系统并解决与数据库连接和更好的缓存机制有关的问题.(I know that many programmers would love to write games. I myself started to program computers because I wanted to create games and, even if I already wrote some games, I’ve spent most of my time dealing with systems and solving problems related to database connectivity and better caching mechanisms.) 我发现编写游戏的问题之一是缺乏材料.在我的特定情况下,我通常会寻找图形和声音,因为这些是我无法做到的.但是,我认为总体上缺少游戏材料.很难找到游戏教程,当我发现一些东西时,我只会看到使用(One of the problems I see to write games is the lack of material. In my particular case, I usually look for graphics and sounds, as those are the things that I am not able to do. Yet, I think there’s missing material for games in general. It is hard to find game tutorials and, when I find something, I only see material to write games using)**XNA(XNA)**要么(or)统一(Unity).几乎没有关于如何使用编写游戏的材料(. There is almost no material on how to write games using)**WPF(WPF)**要么(or)**银光(Silverlight)**或其他类似技术.(or other similar technologies.) 而且,考虑到我通常使用这些技术来编写游戏,尽管提出一种解决方案的方法是一个好主意,但该解决方案是关于如何使用非游戏特定技术编写游戏的.(And, considering that I usually write games using those technologies, I though it will be a good idea to present a solution on how to write games using non-game specific technologies.)
为什么不使用(Why not use)**XNA(XNA)**要么(or)统一(Unity)?(?)
我在不使用游戏的情况下写游戏的想法(My idea on writing games without using)**XNA(XNA)**要么(or)**统一(Unity)**这表明您可以使用普通的UI技术来编写游戏,在许多情况下,由于您无需担心如何在屏幕上渲染内容或重新创建布局控件,因此可以简化这项工作.此外,通过这样做,您将能够创建游戏而无需安装此类库/引擎,甚至可以为(is to show that you can use normal UI technologies to write games, which in many cases simplify the job as you don’t need to care about how to render things to the screen or to recreate layout controls. Also, by doing this you will be able to create games without having to install such libraries/engines and you will be able to write games even for)Windows 8(Windows 8),如使用(, as using)C#+ XAML(C# + XAML)(这是我在本文中将要使用的)的一个特征((which is what I am going to use in this article) is a trait of)银光(Silverlight),(,)**WPF(WPF)**和的(and of)**Windows 8(Windows 8)**应用程序.(applications.)
声明性动画(Declarative Animations)
关于游戏的大多数文章都说,我们必须应对"游戏循环",即每帧或每隔一段时间移动物体(例如玩家的飞船和敌人).即使在非特定的文章中也是如此(Most articles about games say that we must deal with a “game loop”, moving objects (like the player ship and enemies) at each frame or time lapse. That’s said even in articles that aren’t specific for)**XNA(XNA)**要么(or)**统一(Unity)**这实际上是大多数游戏的编写方式,但并非必须如此.(and that’s really how most games are written, yet it is not how it must be.)
看到(Seeing the) Storyboard
两者都使用(used by both)**WPF(WPF)**和(and)**银光(Silverlight)**可能会看到动画是声明性的,并且游戏主要由动画组成.那么,为什么我们要关心游戏循环?(it is possible to see that animations can be declarative and a game is mostly made of animations. So, why should we care about a game loop?)
最有说服力的原因可能是互动.声明性动画不是交互式的.它们更新值,唯一的输入就是时间.好吧,至少那是发生在(The most compelling reason may be interaction. Declarative animations aren’t interactive. They update values and the only input they have is time. Well, at least that’s what happens with)**WPF(WPF)**和(and)**银光(Silverlight)**的(’s) Storyboard
s,但这并不意味着您不能拥有可以对某些外部因素做出反应的声明性动画.(s, but that doesn’t mean you can’t have declarative animations that can react to some external factors.)
出于上述原因以及其他一些原因,我最近编写了一个专门用于动画的库,(That, and some other reasons, made me write a library dedicated to animations recently, which I presented) 这里(here) .在这样的库中,声明性动画仍被编写为(. In such library, the declarative animations are still written in)C#(C#),不在(, not in the)XAML(XAML), 用一个(, using a)**流利的API(Fluent API)**这与常见的声明性代码非常相似,但是它允许使用委托来构建动画的一部分或验证某些条件,这就是我们在用户按下某些键时移动角色所需要的全部.(that’s very similar to common declarative code, yet it allows the use of delegates to build part of the animations or to validate some conditions, which is all we need to move a character when the user press some keys.)
使用这种库,可以编写以下代码来控制玩家角色的移动:(With such library, it is possible to write the following code to control the player character movements:)
AnimationBuilder.
BeginParallel().
BeginLoop().
BeginPrematureEndCondition(() => HorizontalMove >= 0).
RangeBySpeed(() => Canvas.GetLeft(this), 0, 120, (value) => Canvas.SetLeft(this, value)).
EndPrematureEndCondition().
EndLoop().
BeginLoop().
BeginPrematureEndCondition(() => HorizontalMove <= 0).
RangeBySpeed(() => Canvas.GetLeft(this), canvas.Width-Width, 120, (value) => Canvas.SetLeft(this, value)).
EndPrematureEndCondition().
EndLoop().
BeginLoop().
BeginPrematureEndCondition(() => VerticalMove >= 0).
RangeBySpeed(() => Canvas.GetTop(this), 0, 120, setTop).
EndPrematureEndCondition().
EndLoop().
BeginLoop().
BeginPrematureEndCondition(() => VerticalMove <= 0).
RangeBySpeed(() => Canvas.GetTop(this), canvas.Height-Height, 120, setTop).
EndPrematureEndCondition().
EndLoop().
EndParallel();
而且,最重要的是,此类库不会更改您编写的应用程序的类型.您可以继续创建一个(And, the best of all, such library does not change the kind of application you write. You can continue to create a)**WPF(WPF)**应用程序(application, a)**银光(Silverlight)**应用程序(application, a)**Windows表格(Windows Forms)**应用程序,控制台应用程序或您想要的任何内容,甚至(application, a Console application or whatever you want, even a)**XNA(XNA)**应用.该库真正需要的所有内容都是一个"事件",该事件的触发速度足够快,可以播放动画(实际上,例如,如果您想使用以下格式编写视频文件,甚至可以在调用动画时伪造此类事件: “一致的时间安排”).(application. Everything that’s really needed by such library is an “event” that’s triggered fast enough to be able to play the animations (in fact, you can even fake such event when calling the animations if, for example, you want to write a video file with a “consistent timing”).) 因此,如果您担心应该使用外部库来编写演示的游戏,请不要这样做.它确实是一个库,但是可以将其添加到任何应用程序中,并且,当您将收到源代码时,您只能将您使用的部分添加到应用程序和游戏中.(So, if you are worried that you should use an external library to write the presented game, don’t be. It is a library, true, but it can be added to any application and, as you will receive the source code, you can add only the parts that you use to your applications and games.)
在编写一些代码之前(Before writing some code)
本文将解释编写游戏的重要步骤.然而,仅仅复制那些代码块来玩游戏是不够的.实际上,要开始,您需要创建一个新项目((This article will explain important steps to write a game. Yet, it is not enough to simply copy those code blocks to have a game. In fact, to start you will need to create a new project ()**银光(Silverlight)**要么(or)WPF(WPF))并添加对动画库的引用(无论是dll还是其源代码).另外,一些图像,声音和其他文件未作为文章的一部分提供,但您可以通过下载最终应用程序来验证此处提供的所有步骤是否确实在使用.() and add a reference to the animation library (be it to the dll or to its source code). Also, some images, sounds and other files aren’t presented as part of the article itself but you will be able to verify that all steps presented here are really in use by downloading the final application.)
1.星星(1. The stars)
我已经说过,我通常会寻找图形,因为我不是设计师,并且游戏需要好的设计.有时,通过简单地将外观不好的角色更改为看起来更好的角色,游戏似乎更加真实有趣.(I already said that I usually look for graphics as I am not a designer and a game needs a good design. Sometimes, by simply changing a badly written character by a better looking one the game seems to be more real and interesting.)
本来我不确定是否要写空格(Originally I was unsure if I would write a space)**射击游戏(Shoot’em Up)**游戏或平台游戏,但由于找不到很好的背景,我决定去(game or a platform one, but as I didn’t find good looking backgrounds, I decided to go to the)**射击游戏(Shoot’em Up)**游戏,因为我有能力创造背景.实际上,我开始写背景是因为我想检查背景是否可以接受.(game as I am capable of creating its background. In fact, I started writing the background because I wanted to check if it will be acceptable or not.)
为了有背景,我只需要星星,所以我立即创建了一个(To have a background I only needed stars, so I immediately created a) Star
用户控制.唯一的内容是(user control. It’s only content is an) Ellipse
,其实际大小由代码定义.在其代码中(, which has its real size defined by code. In its code, the) Intensity
确定其颜色(也许所有白色都可以,但我希望有一些细微的差别)以及下降的速度.为了产生一种幻想,即船一直在前进,实际上发生的是只有星星向底部移动.(determines its color (maybe all white was OK, but I wanted some small differences) and how fast it will go down. To create the illusion that the ship is always moving forward, what really happens is that only the stars move towards the bottom.)
最初的(The initial) Star
动画很简单(animation was a simple) Range
从它的实际位置到屏幕外的底部,通过在任意位置创建一些恒星,我得到了看起来像雪的东西,因为恒星正在移出屏幕且没有新的恒星出现.(from its actual position to the off screen bottom and, by creating some stars at random positions I had something that looked like snow, as the stars were going off-screen and no new stars were appearing.)
我考虑了许多可能的方法来使新星出现,但我选择了伪造它.每次(I thought about many possible solutions to make new stars appear but I opted to fake it. Each time a) Star
离开屏幕时,它将重新放置在顶部(也位于屏幕外)和新的水平位置,(goes off-screen it is repositioned on the top (also off-screen) and a new horizontal position and) Intensity
计算.因此,我只需重新启动动画即可(也就是说,将其放入(is calculated. So, I can simply restart the animation (that is, put it inside a)循环(Loop)),似乎新星一直在出现.而且,作为新的位置,大小,甚至是离顶部很小的屏幕外值,都可以计算出,您不会一直看到一颗从底部消失的星星立即在顶部重新出现.() and it looks like new stars are appearing all the time. And, as a new position, size and even a small off-screen value to the top is also calculated you don’t keep seeing a star that’s disappearing from the bottom immediately reapearing on the top.)
最终的代码是这样的:(The final code is this:)
public IEnumerable<IAnimation> CreateAnimation()
{
var canvas = MainPage._instance.canvas;
int width = (int)canvas.Width;
canvas.Children.Add(this);
while(true)
{
yield return
AnimationBuilder.
RangeBySpeed
(
(int)Canvas.GetTop(this),
canvas.Height,
(_intensity + 1),
(value) => Canvas.SetTop(this, value)
);
int x = MainPage._random.Next(width);
int y = -MainPage._random.Next(30);
int intensity = MainPage._random.Next(MaxIntensity + 1);
Canvas.SetLeft(this, x);
Canvas.SetTop(this, y);
Intensity = intensity;
}
如果需要,可以在以下位置查看其工作版本:(If you want, you can see a working version of it at:) http://paulozemek.azurewebsites.net/ShootEmUp/StarsSilverlight.html(http://paulozemek.azurewebsites.net/ShootEmUp/StarsSilverlight.html)
2.玩家角色(2. The player character)
移动我使用的角色(To move the character I used) RangeBySpeed
包含的动画(animations that are enclosed by) PrematureEndCondition
s.也就是说,我有一个(s. That is, I have a) RangeBySpeed
可以将字符向右移动,但是如果不按向右箭头,则会提前结束.(that moves the character to the right, but that ends prematurely if the right arrow is not pressed.)
我用了(I used a) RangeBySpeed
而不是正常的(instead of a normal) Range
因为位于屏幕中央的字符比位于屏幕最左侧的字符更快地到达右侧极限.如果我使用普通(because a character at the center of the screen will arrive the right limit faster than a character that is located at the extreme left of the screen. If I was using a normal) Range
那么角色将始终花费相同的时间到达右上角,这代表角色以不同的速度移动.(then the character will always take the same time to arrive to the right corner, which will represent as if it was moving at different speeds.)
这里的一个大技巧是将每个可能的移动(左,右,上,下)范围放在各自的条件内,这些条件在各自的循环内,因此,如果您按住某个键然后停下来然后重新执行,它们可以重新执行再按一次键.最后,所有这些动画都位于(The big trick here was to put each one of the possible moves (left, right, up, down) ranges inside their own conditions, which are inside their own loops, so they can reexecute if you hold a key down, then stop, then press the key again. And finally, all those animations are inside a) ParallelGroup
动画(嗯,至少允许水平和垂直移动并行运行).(animation (well, at least horizontal and vertical movements are allowed to run in parallel).)
唯一缺少的是如何分辨(The only missing thing was how to tell a) PrematureEndCondition
是否应该结束.至少在(if it should end or not. At least in)**银光(Silverlight)**我们不能简单地检查是否按下了一个键.为了解决这个问题,我实施了(we can’t simply check if a key is pressed or not. To solve that, I implemented the) KeyDown
和(and) KeyUp
设置事件(events to set the) HorizontalMove
和(and) verticalMove
,并且条件仅检查那些变量.请注意,如果我们为其他框架编写游戏(即使(, and the conditions only check those variables. Note that if we were writing the game for other frameworks (even for)WPF(WPF)),我们无需执行() we may be able to check if a key is down without having to implement the) KeyDown
和(and) KeyUp
事件.(events.)
我已经显示了动画角色的代码,因此在这里我将展示(I already shown the code to animate the character, so here I will present the) KeyUp
和(and) KeyDown
事件处理程序:(event handlers:)
void _KeyDown(object sender, KeyEventArgs e)
{
switch(e.Key)
{
case Key.Left:
HorizontalMove = -1;
break;
case Key.Right:
HorizontalMove = 1;
break;
case Key.Up:
VerticalMove = -1;
break;
case Key.Down:
VerticalMove = 1;
break;
}
}
void _KeyUp(object sender, KeyEventArgs e)
{
switch(e.Key)
{
case Key.Left:
if (HorizontalMove == -1)
HorizontalMove = 0;
break;
case Key.Right:
if (HorizontalMove == 1)
HorizontalMove = 0;
break;
case Key.Up:
if (VerticalMove == -1)
VerticalMove = 0;
break;
case Key.Down:
if (VerticalMove == 1)
VerticalMove = 0;
break;
}
}
您可以在上看到它的工作版本(And you can see a working version of it at) http://paulozemek.azurewebsites.net/ShootEmUp/StarsAndPlayerSilverlight.html(http://paulozemek.azurewebsites.net/ShootEmUp/StarsAndPlayerSilverlight.html) .(.) 注意:(Note:)您可能需要点击(You may need to click on the)**银光(Silverlight)**窗口,以便能够使用方向箭头控制飞船.(window to be able to control the ship using the directional arrows.)
3.敌人(3. The enemies)
我怎么能说一个游戏就是一个(How can I say that a game is a)**射击游戏(Shoot’em Up)**如果没有什么可拍摄的?(if there’s nothing to shoot?)
我再次从创建一个名为(Again I started by creating a class, named) Enemy
.也许对于一款真正多功能的游戏,我应该将其创建为一个抽象类,然后在其自己的类中实现每个不同的敌人,但是对于这款游戏,只需一个类就足够了.(. Maybe for a really versatible game I should create it as an abstract class and then implement each different enemy in its own class, but for this game, a single class was enough.)
一开始,(At the first moment, the) Enemy
班上有固定的船只图像,动画很简单(class had a fixed ship image and its animation was a simple) Range
从屏幕外的顶部转到屏幕外的底部.然后,还有另一个动画会不时地创造新的敌人.(to go from the off-screen top to the off-screen bottom. Then, there’s another animation that keeps creating new enemies from time to time.)
代码是这样的:(The code is like this:)
return
AnimationBuilder.
BeginSequence().
Add(() => canvasChildren.Add(this)).
BeginPrematureEndCondition(() => Health <= 0).
RangeBySpeed(-Height, MainPage._instance.canvas.Height, (30 + MainPage._random.Next(90)) * speed, (value) => Canvas.SetTop(this, value)).
EndPrematureEndCondition().
ExplodeIfDead(this, 1).
Add(() => canvasChildren.Remove(this)).
EndSequence();
4.矩形碰撞+分段时间(4. The rectangle collisions + segmented time)
此刻,我们有一个移动的玩家角色,屏幕上出现了新的敌人^但飞船从未相撞.(At this moment we have a player character that moves, new enemies that appear on the screen… but the ships never collide.)
为了进行基本的碰撞检测,我们处理矩形.我们认为(To do a basic collision detection, we deal with rectangles. We consider the) Canvas.Left
和(and) Canvas.Top
人物及其(of the characters and their) Width
和(and) Height
来构建矩形,然后使用方法检查是否有交点(to build the rectangles, and then we check if there’s an intersection using the method) Intersect
的(of the) Rect
类型.(type.)
看到此图像,我们可以轻松看到红色和绿色矩形之间的交点,其中我用黄色矩形表示.(Seeing this image, we can easily see the intersection between the red and the green rectangles, with I am representing with the yellow rectangle.)
但是,什么时候检查碰撞?(But, when do we check for the collision?)
游戏使用声明性动画,没有合适的时间进行碰撞检测.您可以轻松地放置命令式动画,并在每次调用它时检查是否有碰撞,但这会创建不精确的碰撞检测,因为(The game is using declarative animations, which don’t have a right moment to do the collision detection. You can easily put an imperative animation and check for collisions everytime it is invoked, but this will create an imprecise collision detection, as the) Update
可以在不同的时间间隔内调用.(s may be invoked with different time lapses.)
解决方案是使用分段时间(在(The solution is the use of a segmented time (be it with the) BeginSegmentedTime
/(/) EndSegmentedTime
或实例化一个(or by instantiating a) SegmentedType
直).这样的分段时间保证了它将以由其确定的最大间隔更新其内部动画.(directly). Such segmented time guarantees that it will update its inner animations at maximum intervals determined by its) SegmentDuration
.请注意,动画会保持流畅,因为如果时间流逝,动画会以较小的间隔进行更新,但是(. Note that animations will stay smooth, as they will be updated in smaller intervals if the time lapses are short, yet the) SegmentCompleted
仅在段完成时调用(即,如果段为15毫秒,则(will only be invoked when a segment is completed (that is, if the segment is of 15 milliseconds, an) Update
10毫秒不会产生段结束,但会产生新的(of 10 milliseconds will not generate a segment end, but a new) Update
(共5个)将完成这15毫秒并生成已完成事件).(of 5 will complete those 15 milliseconds and generate the completed event).)
例如,如果定义的时间间隔为15毫秒,并且有两次更新(均为十毫秒),那么真正发生的是:(If, for example, the time lapse defined is of 15 miliseconds and there are two updates, both of ten miliseconds, what will really happen is:)
- 第一次更新将用这10毫秒来更新内部动画;(The first update will update the inner animation with those 10 miliseconds;)
- 第二次更新将分为两个步骤.第一步将仅更新5毫秒,完成15毫秒即可调用该事件,然后它将调用(The second update will be split in 2 steps. The first step will update only 5 miliseconds, to complete the 15 miliseconds to invoke the event, then it will invoke the)
SegmentCompleted
事件.最后,它将额外进行5毫秒的更新,以将所有对象放置在正确的位置.(event. And then finally it will do an extra update of 5 miliseconds to put all the objects at the right places.)
这里最重要的是,将所有参与碰撞检测的动画作为此分段时间的内部动画,因为不会对外部动画进行分段,因此放慢速度可能会使外部动画直接前进500毫秒,而不是将其分段每次碰撞检测前15毫秒.(The most important thing here is to put all the animations that participate on the collision detection as inner animations of this segmented time, as external animations will not be segmented and so a slowdown may make the external animation advance 500 milliseconds directly instead of segmenting it by 15 milliseconds before each collision detection.) 因此,执行基本冲突检测的代码如下所示:(So, the code to do the basic collision detection can look like this:)
internal static void _CheckAllCollisions()
{
var objects = _instance.canvas.Children;
PlayerCharacter._instance.IsReceivingDamage = false;
foreach(var enemy in objects.OfType<Enemy>())
{
if (PlayerCharacter._instance.Health > 0)
{
if (enemy.CollidesWith((WriteableBitmap)enemy.image.Source, PlayerCharacter._instance, (WriteableBitmap)PlayerCharacter._instance.image.Source))
{
PlayerCharacter._instance.Health --;
enemy.Health --;
PlayerCharacter._instance.IsReceivingDamage = true;
}
}
}
}
5.爆炸(5. Explosions)
由于我们已经在检测碰撞并且我们已经有很多动画,因此爆炸看起来非常简单.但是,这里有些不同.(The explosion may look very simple as we are already detecting collisions and we already have lots of animations. Yet, there’s something very different here.) 到目前为止,所有动画都只是由运动组成,因为我们没有不同的帧.考虑到我不是通过代码模拟爆炸,而是使用已经绘制的动画,所以我必须能够加载和播放它.(Up to this moment, all animations are simply composed of movement as we don’t have different frames. Considering I am not simulating a explosion by code, but using an already drawn animation, I must be able to load and play it.) 在互联网上搜索时,我发现了一些爆炸精灵表.因此,目前,我必须能够使用Sprite工作表.(Searching the internet I found some explosion sprite sheets. So, at this moment, I must be able to use a sprite sheet.) Sprite表单通常由许多大小相等的图像组成(嗯,如果我们在Sprite表单中只有一个动画,至少是最常见的情况).我发现的一张精灵表是这样的:(A sprite sheet is usually composed of many equally sized images (well, at least that’s the most common case if we have a single animation in the sprite sheet). One of the sprite sheets I found is this:)
我们需要做的是一次只显示一个"精灵".为此,我创建了(And what we need to do is to show only one of the “sprites” at once. To do this, I created the) SpriteSheet
用户控制.它的主要目的是简单地加载一张精灵图片,但一次仅显示一帧.(user control. Its main purpose is simply to load a sprite sheet image but present only one frame at a time.)
为此,我们必须应用(To do that we must apply a) RectangleGeometry
到(to the) Clip
的属性(property of the) UserControl
.控件的内容是带有图像的画布,因此,每次我必须显示不同的帧时,我都会更改(. The content of the control is a canvas with an image and so, each time I that must show a different frame, I change the) Canvas.Left
和/或(and/or the) Canvas.Top
图片的(要显示第二帧,我们必须设置(of the image (to show the second frame, we must set the) Canvas.Left
(减去帧宽度),因此第一帧在左侧不可见,第二帧变为可见).(to minus the frame width, so the first frame is invisible at the left and the second frame becomes visible).)
好吧,我做了更多的事情.的(Well, I did something more. The) SpriteSheet
控件能够呈现不同大小的帧.为此,我将整个图像尺寸乘以要显示的帧的尺寸,然后将尺寸除以实际的帧尺寸.随着整个图像的拉伸,在进行Left/Top计算时,我还必须使用修改后的尺寸,因此真正"重新放置框架"的方法最终如下所示:(control is capable of presenting the frames with a different size. To do this, I multiply the entire image size by the size I want to show the frame, then I divide the the size by the real frame size. With the entire image stretched I must also use the modified size when doing the Left/Top calculations, so the method that really “repositions the frame” ended up like this:)
private void _Invalidate()
{
var source = _bitmapSource;
if (source == null)
return;
if (_bitmapSource.PixelWidth <= 0)
return;
if (double.IsNaN(Width) || double.IsNaN(Height))
return;
int itemsPerRow = _bitmapSource.PixelWidth / _frameWidth;
int column = _frameIndex % itemsPerRow;
int row = _frameIndex / itemsPerRow;
Canvas.SetLeft(_image, Width * -column);
Canvas.SetTop(_image, Height * -row);
_image.Width = _bitmapSource.PixelWidth * Width / _frameWidth;
_image.Height = _bitmapSource.PixelHeight * Height / _frameHeight;
_clip.Rect = new Rect(0, 0, Width, Height);
}
最后,考虑到我创建了(Finally, considering that I created the) FrameIndex
属性,为爆炸设置动画就足够了(property, to animate the explosion it is enough to do a) Range
在我们认为可以接受的时间内从第一帧索引到最后一帧索引(在大多数情况下,我使用了1秒).(from the first frame index to the last frame index in a time that we consider acceptable (I used 1 second in most cases).)
当然,我必须为爆炸创建此类spritesheet组件,将其放置在爆炸船的同一位置,将其添加到已经添加了所有角色的画布上,播放它,然后将其从画布中删除.因此,爆炸动画如下所示:(Of course, I have to create such spritesheet component for the explosion, position it at the same place of a exploding ship, add it to the canvas were all characters are already added, play it and then remove it from the canvas. So, the explosion animation is like this:)
var explosion = _SpriteSheetImages.CreateExplosion((int)enemy.Width);
// And inside the animation:
Add
(
() =>
{
Canvas.SetLeft(explosion, Canvas.GetLeft(enemy));
Canvas.SetTop(explosion, Canvas.GetTop(enemy));
Canvas.SetZIndex(explosion, Canvas.GetZIndex(enemy) + 1);
canvasChildren.Add(explosion);
}
).
BeginParallel().
Range(0, explosion.FrameCount, time, (value) => explosion.FrameIndex = value).
Range(1.0, 0.0, time/2, (value) => enemy.Opacity = value).
EndParallel().
Add(() => canvasChildren.Remove(explosion)).
您可能会注意到,我还对敌人的(You may notice that I also apply a range to the enemy’s) Opacity
.对我来说,它看起来更自然,因为爆炸时船开始消失,因为我的爆炸不包括船图像本身被破坏.如果每艘船发生爆炸(已经包括船上零件),我可以在播放爆炸时立即将其从屏幕上移开.(. To me it looks more natural, as the ships start to disappear while it is exploding, as my explosion doesn’t include the ship image itself being destroyed. If there was an explosion per ship, already including the ship parts, I may immediately remove the ship from the screen while playing the explosion.)
5.1.爆炸声.(5.1. Sound of an explosion.)
没有爆炸声,爆炸是不完整的.最初,每次我想播放声音时,我都会创建一个新的媒体元素,效果并不理想.(The explosion isn’t complete without the sound of an explosion. Initially I was creating a new media element each time I wanted to play a sound, which wasn’t working really well.)
我真的不知道为什么,但是有时候声音没有播放.我不相信这是因为使用了太多的声音通道,有时某些情况会发生在第一个声音上.(I don’t really know the reason for this, but sometimes the sounds weren’t playing. I don’t believe it was because too many sound channels were used, as sometimes that happened to the first sounds.)
因此,我发现的解决方案是(So, the solution I found was to have the) MediaElement
一直将它们添加到(s created all the time by adding them to the) MainWindow
.因此,每次我想播放声音时,我只播放了正确的媒体元素.(. So, each time I wanted to play a sound, I simply played the right media element.)
不过,在某些情况下,我希望先播放2到3次相同的声音,然后再停止播放.对于高音量开始减小的声音,我通过停止正在播放的声音然后再次开始播放来解决.但是,如果依次发生一些爆炸,则似乎只是在"延迟"一次爆炸的声音,这是在最后一艘船被摧毁时发生的.(In some cases, though, I wanted to play the same sound 2 or 3 times before letting it stop. For sounds that start with a high volume and then decrease I solved this by simply stopping the sound that was still playing and then starting to play it again. But in case of some explosions that happened in sequence it appeared to be simply “delaying” the sound of a single explosion, which happened when the last ship was destroyed.)
我不能说我一直使用最好的解决方案,但是我通过为爆炸创建3个媒体元素并在播放时进行交替来解决了这个问题.因此,第一次,第二次和第三次爆炸可能会同时播放,如果有第四次爆炸,它将停止第一个爆炸,这可能已经结束了,因此,至少对我而言,爆炸听起来很棒.(I can’t say that I used the best solution of all times, but I solved the issue by creating 3 media elements for the explosions and by alternating them when playing. So, the first, second and third explosion may play at the same time and, if there’s a fourth explosion, it will stop the first one, which is probably ending already and so, at least to me, the explosions sound great.)
因此,在播放爆炸声时,我会使用(So, when playing a explosion sound I get the right media element using the) _GetExplosion
方法:(method:)
private static int _explosionIndex;
// this is initialized in the constructor with 3 explosions.
private static MediaElement[] _explosions;
internal static MediaElement _GetExplosion()
{
var result = _explosions[_explosionIndex];
_explosionIndex++;
if (_explosionIndex >= _explosions.Length)
_explosionIndex = 0;
return result;
}
6.芽(6. The shoots)
当我第一次想到芽的时候,我想到会有各种各样的芽,例如爆炸性芽,激光芽等.但是我从一个单一的开始(When I first thought about the shoots, I thought that there will be all kinds of different shoots, like explosive one, laser ones and the like. But I started with a single one, a) Laser
,您可能会想到它成了一堂课.(, which as you may imagine became a class.)
但是,当我最终决定向游戏中添加新资源时,我决定只在正常和强壮之间更改射击,而没有真正创建新射击.我这样做只是为了避免创建大型游戏,因为我的目的不是创建最佳游戏,而是创建有关如何创建基本游戏的教程.(But when I finally decided to add new resources to the game I decided to only change the shoots between normal and strong, without really creating new shoots. I did it only to avoid creating a huge game, as my purpose is not the create the best game, but to create a tutorial on how to create a basic game.)
所以,我们有一个(So, we have a) Laser
类,此类类负责拍摄动画.我需要更改播放器动画以包括镜头本身,方法是将以下代码添加到(class and such class is responsible for doing the animation of a shoot. I needed to change the player animation to include the shot itself, with is done by adding the following code to the Parallel animation of the) PlayerCharacter
:(:)
BeginLoop().
BeginRunCondition(() => IsShootPressed && _hotOffset > 0).
BeginSequence().
Add(_Shoot).
Wait(() => _traits._waitBetweenShoots / _shootSpeed).
EndSequence().
EndRunCondition().
EndLoop().
镜头本身还是一个简单的RangeBySpeed,但是当我播放声音并在触摸敌人后使激光变得更弱时,最终代码如下:(And the shot itself is again a simple RangeBySpeed, but as I play a sound and make the laser become weaker after touching enemies, the final code is like this:)
AnimationBuilder.
BeginSequence().
PlaySound(() => MainPage._instance.mediaElementLaser, 0.4).
BeginPrematureEndCondition(() => _health <= 0).
RangeBySpeed(Canvas.GetTop(this), -Height, 300, (value) => Canvas.SetTop(this, value)).
EndPrematureEndCondition().
Add(() => MainPage._instance.canvas.Children.Remove(this)).
EndSequence();
而且,由于镜头也会发生碰撞,我添加了一个代码来检查镜头内部是否发生镜头碰撞(And, as the shot also collides, I’ve added a code to check for shot collisions inside the) SegmentCompleted
事件(实际上,(event (in fact, inside the) foreach
击败所有敌人):(over all the enemies):)
foreach(var shoot in objects.OfType<Laser>())
{
Rect shotRect =
new Rect
(
Canvas.GetLeft(shoot) - Canvas.GetLeft(enemy),
Canvas.GetTop(shoot) - Canvas.GetTop(enemy),
shoot.Width,
shoot.Height
);
int oldHealth = enemy.Health;
if (oldHealth > 0 && shotRect.CollidesWith((WriteableBitmap)enemy.image.Source))
{
if (enemy._hitBy.Add(shoot))
{
byte value = (byte)shoot.Health;
var hit = new Hit();
hit.Intensity = value;
hit.SetCenterX(shoot.GetCenterX());
hit.SetCenterY(shoot.GetCenterY());
AnimationManager.Add(hit.CreateAnimation());
}
int shootDamage = PlayerCharacter._instance._traits._shootDamage;
if (shoot._isGreen)
shootDamage *= 2;
enemy.Health -= shootDamage;
shoot.Health -= 10;
PlayerCharacter._instance.Score += oldHealth - enemy.Health;
if (enemy.Health <= 0)
{
enemy._killedByShoot = true;
shoot._killCount++;
double enemyValue = enemy.MaxHealth / 40.0;
enemyValue *= enemyValue;
enemyValue *= 100;
int killScore = (int)enemyValue / enemy._hitBy.Count;
if (killScore < 1)
killScore = 1;
killScore *= shoot._killCount;
PlayerCharacter._instance.Score += killScore;
var positionedScore = new PositionedScore();
var animation = positionedScore.CreateAnimation(enemy.GetCenterX(), enemy.GetCenterY(), killScore);
AnimationManager.Add(animation);
}
}
}
如您所见,我仍然无视单一责任原则.我必须改变(As you can see, I am still ignoring the single responsibility principle. I am having to change the) SegmentCompleted
每次发生新的有效碰撞时,都会发生如果我真的想拥有一款能够成长的游戏,那我就得回顾一下这种实现方式,但我仍然认为,对于只有3个关卡的游戏,保持这种方式还是可以的.(handler everytime there’s a new kind of valid collision. If I really wanted to have a game that grows I would have to review such implementation, but I still think it is OK to keep it that way for a game with only 3 levels.)
7.逐像素碰撞检测(7. The pixel by pixel collision detection)
您可能已经注意到,某些东西不需要特定的顺序.在支持拍摄之前,我可以逐个像素地添加碰撞检测,但是我选择先拥有有效的拍摄,然后编写更好的碰撞检测算法.如果我与其他人一起工作,我们可以同时从事不同的工作,所以不要接受(You probably noticed that somethings don’t require a specific order. I could’ve added the pixel by pixel collision detection before supporting the shoots, yet I opted to first have the working shoots to then write the better collision detection algorithm. If I was working with somebody else we could’ve worked at the same time on different things, so don’t take that)**七(seven)**编号为必填项.(number as a required order.)
考虑到我以(Considering I started the project as a)**银光(Silverlight)**一,考虑到我已经知道(one, and considering I already knew that the) WriteableBitmap
有(has the) Pixels
属性,尽管我立即将位图转换为(property, I immediately though that I should convert my bitmaps to) WriteableBitmap
检查其像素.也许我应该使用一种更好的方法,因为它不是最好的方法(s to check their pixels. Maybe I should’ve used a better approach, as it is not the best one for)**WPF(WPF)**我确实创建了一个(and I did create a)**WPF(WPF)**重用大多数项目(project reusing most of the)**银光(Silverlight)**代码未修改.但是,这就是我所做的,并且我正在介绍它.对于将参与碰撞检测的图像(即船舶图像),我将其加载为(code untouched. Yet, that’s what I did and I am presenting it. For the images that will participate on collision detection (that is, the ship images) I load them as) WriteableBitmap
s.(s.)
的(The)**银光(Silverlight)**的(’s) WriteableBitmap.Pixels
属性以数组形式返回它们(property returns them as an array of) int
.颜色在(. The colors are in the)RGB(ARGB)格式,并且为了检测冲突,我只考虑不透明度大于127的像素(即A(lpha)元素应大于127).(format and, to detect a collision, I only consider the pixels that have a opacity greater than 127 (that is, the A(lpha) element should be bigger than 127).)
也许这是在此应用程序中最难理解的代码,所以我将尝试将其分为几个部分:(Maybe this is the hardest code to understand in this application, so I will try to split it in some parts:)
- 我仍然保留执行矩形交点的代码.实际上,如果没有交叉点,就没有碰撞的机会;如果有交叉点,我们必须检查像素值;(I still keep the code that does a rectangle intersection. In fact, if there is no intersection there is no chance of collision and, if there is an intersection we must check the pixel values;)
- 在给定的像素(To get a pixel at a given)X,Y(X, Y)坐标,考虑到Pixels属性是一维数组,我们应该像这样进行计算:(coordinate, considering the Pixels property is a single dimension array, we should do a calculation like this:)
- 看相交图像.您会看到在红色和绿色"正方形"之间(不要判断我的绘图的精度)之间有一个黄色相交.在分析碰撞时,我们将从两侧分析所有相交像素,因此我们需要计算应该在哪里检查每个图像.交点的位置" 0,0"与绿色正方形的0,0重合,但不是红色正方形的0,0.(Look at the intersection image. You can see that betwen the Red and the Green “squares” (don’t judge the precision of my drawing) there is a yellow intersection. When analysing for the collision, we will be analysing all of the intersection pixels from both sides, so we need to calculate where exactly we should check each image. The position “0, 0” of the intersection coincides as the 0, 0 of the Green square, but it is not the 0, 0 of the red square.) 好吧,考虑到这些因素并尝试优化读取,我完成了以下代码:(Well, considering those factors and also trying to optimize the reads, I finished up with this code:)
public static bool CollidesWith(this FrameworkElement element1, WriteableBitmap bitmap1, FrameworkElement element2, WriteableBitmap bitmap2)
{
int left1 = (int)Canvas.GetLeft(element1);
int top1 = (int)Canvas.GetTop(element1);
int width1 = (int)element1.Width;
int height1 = (int)element1.Height;
int left2 = (int)Canvas.GetLeft(element2);
int top2 = (int)Canvas.GetTop(element2);
int width2 = (int)element2.Width;
int height2 = (int)element2.Height;
Rect rect1 = new Rect(left1, top1, width1, height1);
Rect rect2 = new Rect(left2, top2, width2, height2);
Rect intersect = rect1;
intersect.Intersect(rect2);
if (intersect.IsEmpty)
return false;
int intersectLeft = (int)intersect.Left;
int intersectTop = (int)intersect.Top;
int intersectRight = (int)intersect.Right;
int intersectBottom = (int)intersect.Bottom;
var pixels1 = bitmap1.Pixels;
var pixels2 = bitmap2.Pixels;
for(int y=intersectTop; y<intersectBottom; y++)
{
int index1 = (y-top1)*width1 + (intersectLeft-left1);
int index2 = (y-top2)*width2 + (intersectLeft-left2);
for(int x=intersectLeft; x<intersectRight; x++)
{
if ((pixels1[index1] & 0xFF000000) > 0x7F000000 && (pixels2[index2] & 0xFF000000) > 0x7F000000)
return true;
index1++;
index2++;
}
}
return false;
}
的(The) & 0xFF000000
用于仅提取ARGB像素的Alpha元素.的(is used to extract only the Alpha element of the ARGB pixel. The) > 0x7F000000
正在比较该值是否大于127.实际上,我本可以仅将alpha元素提取为0到255之间的值,但是此代码避免了额外的操作.(is comparing if such value is greater than 127. In fact, I could’ve extracted only the alpha element as a value from 0 to 255, but this code avoids an extra operation.)
我只计算(I only calculate the) index1
和(and) index2
从一行移到另一行时的变量.我可以把(variables when I move from a line to another. I could’ve put the) index1
和(and) index2
内部计算(calculation inside the inner) for
,但是据我所知,我只需要读取"下一个"像素,我更喜欢每行只计算一次,然后将两者递增(, but as I know I only need to read the “next” pixels, I prefer to calculate it only once per line and then increment both) index1
和(and) index2
在每次迭代中.(at each iteraction.)
您可以看到,如果发现碰撞,我会立即返回.就我而言,我只想知道是否有碰撞.如果您需要了解碰撞中的像素数,则需要增加一个变量(例如(You can see that if I find a collision I immediately return. In my case, I only want to know if there’s a collision or not. If you needed to know the number of pixels in collision, you would need to increment a variable (like a) result
),然后继续检查像素.() and continue checking the pixels.)
8.老板.(8. The boss.)
在最初的创建过程中,我只写了一个(During the initial creation I simply wrote a single) Enemy
类.要创建老板,我考虑过对其进行重构,但是我决定仍然不需要它(通常这是邪恶的根源,但有时我很懒惰).我只是为老板创建了一种新的动画方法,并在构建时给了它不同的图像和健康状况.(class. To create the boss I considered refactoring it, but I decided that it was still not needed (that’s usually a source of evil, but I am lazy sometimes). I simply created a new animation method for the boss and, at construction, I gave it a different image and health.)
老板不能像普通船一样简单消失,所以我使用了命令式动画(The boss can’t simply disappear like normal ships, so I used an imperative animation that keeps) yield return
ing(ing) RangeBySpeed
带有随机值的动画,以在屏幕上移动凸台.而且,在关卡动画中,我将老板动画放入一个序列中,该序列首先等待60秒(后来我改变了3分钟),然后使老板出现.(animation with random values to move the boss over the screen. And, inside the level animation itself, I put the boss animation inside a sequence that first waits 60 seconds (later I changed for 3 minutes) before making the boss appear.)
因此,老板动画是这样的:(So, the boss animation is like this:)
public IAnimation CreateBoss1Animation()
{
Canvas.SetTop(this, -Height);
this.SetCenterX(MainPage._instance.canvas.Width / 2);
return
AnimationBuilder.
BeginSequence().
Add(() => MainPage._instance.canvas.Children.Add(this)).
BeginPrematureEndCondition(() => Health <= 0).
Add(new ImperativeAnimation(_Boss1Animation())).
EndPrematureEndCondition().
ExplodeIfDead(this, 3).
Add(() => MainPage._instance.canvas.Children.Remove(this)).
Add(() => _bossDefeated = true).
EndSequence();
}
将老板置于水平的动画是这样的:(And the animation that puts the boss on the level is like this:)
BeginSequence().
Wait(3 * 60).
Add(CreateBossAnimation(boss)).
EndSequence().
9.关卡和游戏动画(9. The levels and the game animation)
好吧,我是通过简单地将"东西"扔到屏幕上来开始游戏的.游戏是直接开始的.没有开始屏幕或不同级别.我什至(Well, I started the game by simply “throwing” things to the screen. The game was starting directly. There wasn’t a start screen or different levels. I even made the)游戏结束(Game Over)屏幕作为(screen as part of the) PlayerCharacter
动画,所以我忽略了(animation, so I was ignoring the)**单一责任原则(Single Responsibility Principle)**在那一刻.(at that moment.)
实际上,您可能会注意到,即使在最新版本中,您也可能不喜欢某些静态变量.好吧,我只能说我只纠正和抽象了我需要的东西.当我开始写游戏时,我确实做得很懒.这个理论并不难:“每个班级应负有单一责任”. “您应该具有良好的抽象性”.但是,当我刚开始的时候,我一次只专注于非常有限的结果(例如创建星空背景或使角色移动).直到后来,我决定创建级别,带有不同选项的开始屏幕等.(In fact, you may notice that even in the last version there are some static variables that you may not like. Well, I can only say that I only corrected and abstracted things that I needed. I really did a lazy job when I started to write the game. The theory is not hard: “Each class should have a single responsibility”. “You should have good abstractions”. But, when I started, I was only focusing on a really limited result at a time (like creating the star background or making the character move). Only later I decided to create levels, a start screen with different options and the like.)
然后,我所做的最后一件事是真正添加重新启动游戏的选项,就像在游戏结束之前一样,我需要重新加载页面(或关闭并打开页面)(Then, the last thing I did was to really add the option to restart the game, as before the game ended and I needed to reload the page (or close and open the)**WPF(WPF)**应用程序)再次玩游戏.(application) to play the game again.)
好吧,我想我太懒了,因为我只能说这个动画播放了"整个游戏":(Well, I think I am getting too lazy, as I will only say that the “entire game” is played by this animation:)
AnimationBuilder.
BeginLoop().
BeginSequence().
Add(_ShowStartMenu()).
BeginPrematureEndCondition(() => PlayerCharacter._instance.Health <= 0).
Add(_PlayLevels()).
EndPrematureEndCondition().
BeginRunCondition(() => PlayerCharacter._instance.Health <= 0).
Add(_GameOver()).
EndRunCondition().
BeginRunCondition(() => PlayerCharacter._instance.Health > 0).
Add(_YouConqueredTheUniverse()).
EndRunCondition().
EndSequence().
EndLoop();
因此,我真的认为,如果您想了解我对游戏所做的改进,是时候查看代码并使用它了(或者如果您到目前为止还没有使用它,那就去玩).(So, I really think that if you want to understand the improvements I made to the game, it is time to see the code and play with it (or play it if you didn’t do it up to this moment).)
WPF(WPF)
我想我开始了(I think I started the)**WPF(WPF)**仅当我将第一个老板加入游戏时才应用.我创建了一个新项目,然后仅将项目文件复制到该文件夹的相同目录中.(application only when I put the first boss into the game. I created a new project and I then copied only the project file to the same directory of the)**银光(Silverlight)**应用.考虑到我不能使用(application. Considering I can’t use the) #if
指令中(directive in)**XAML(XAML)**然后(and that)**WPF(WPF)**使用一个(uses a) Window
代替(instead of a) UserControl
作为初始的"页面",我创建了一个新窗口.但是,如果您看到的话,我就使用了与(as the initial “page”, I created a new Window. But, if you see, I made it use the same class as the)**银光(Silverlight)**一.(one.)
我确实做了最少的工作才能使游戏正常运行(I really did the minimum to make the game work in)WPF(WPF).例如,(. For example,)**WPF(WPF)**没有一个(doesn’t have a) Pixels
其财产(property in its) WriteableBitmap
,但它允许复制任何像素(, yet it allows to copy the pixels of any) BitmapSource
到一个int数组,所以我可以避免(to an int array, so I could’ve avoided the) WriteableBitmap
在(in)WPF(WPF),但我只做了最少的工作即可访问"像素"属性.所以,如果你发现一些(, but I only did the minimum to have the “Pixels” property accessible. So, if you find some) #if WPF
那是我进行特定于的更改的地方(that’s where I did changes specific to)WPF(WPF).(.)
好吧,我认为,如果您看一下代码,将会发现其中没有很多(Well, I think that if you look at the code you will see that there aren’t lots of those) #if
,但是有足够的证据证明(s, but there are enough to prove that)**WPF(WPF)**不只是比(isn’t simply more complete than)银光(Silverlight), 他们是不同的.(, they are different.)
兴趣点(Points of Interest)
也许使用声明性动画编写游戏是最有趣的部分.但是,对我来说,最让我印象深刻的是,有些课程在(Maybe writing a game using declarative animations is the most interesting part. But, to me, the thing that most impressed me is the fact that some classes are more complete and friendly in)**银光(Silverlight)**比在(than in)**WPF(WPF)**在很多时候,我相信(when, for many time, I believed)**WPF(WPF)**比较完整.(was the more complete one.)
在最终版本中,当您杀死老板时,声音播放的速度会变慢,以尝试模拟大爆炸,但此类代码仅适用于(In the final version, when you kill the boss the sound is played more slowly to try to simulate a big explosion, yet such code only works in)**银光(Silverlight)**因为没有(as there isn’t a) PlaybackRate
在(in)**WPF(WPF)**的(’s) MediaElement
和(and the) SpeedRatio
根本没有给我预期的结果.(simply didn’t give me the expected result.)
最后评论(Final Comment)
我知道我没有制作出最吸引人的游戏,而3D游戏通常是您在这个时代对游戏的期望.但是我不是设计师,我没有找到许多2D优质且兼容的图像,至少对我而言,要找到3D图像更加困难.(I know that I didn’t made the most appealing game and that 3D games are usually what you would expect for games in this era. But I am no designer and I didn’t find many good and compatible images in 2D and, at least to me, finding them is 3D is harder.)
但请放心,大多数概念(如通过时间进行的动画制作,可以通过声明性动画实现,以及进行碰撞检测的时间)在3D游戏中可能是相同的.当然,您将需要一些不同的算法来进行碰撞检测,但是基本思想是:您将更新"属性",检查状态(如碰撞和关键点),并且可能会创建新动画并将其添加到某些动画中.(But don’t worry, most concepts (like the animations made by time, which can be achieved through the declarative animations, and the moments to do collision detection) are probably going to be the same on 3D games. Surely you will need some different algorithms to do the collision detection, but the basic idea is: You will be updating “properties”, checking states (like collisions and keys) and you may be creating and adding new animations to some) ParallelGroup
s.而且,在类似(s. And, in frameworks like)**XNA(XNA)**您还需要实现一个动画管理器来处理(you will also need to implement an animation manager that deals with the) Draw
在更新动画对象之后.(s after updating the animation objects.)
我真的希望您喜欢这篇文章,并且至少完成一次游戏(或者我应该说"我希望您征服了宇宙!"?)(I really hope you enjoy this article and that you at least finish the game once (or should I say that “I hope you conquer the universe!” ?))
图像和声音(Images and Sounds)
游戏中使用的所有图像和声音均来自互联网.对于我使用该网站的声音(All the images and sounds used in this game were got from the internet. For the sounds I used the site) http://www.freesfx.co.uk/(http://www.freesfx.co.uk/) .(.) 这些图像来自不同的站点.我在本文中显示的爆炸精灵来自(The images were got from different sites. The explosion spritesheet that I am showing in the article was got from) https://www.touchdevelop.com/lvxdmali(https://www.touchdevelop.com/lvxdmali) ,但又发生了另一起爆炸(很大的爆炸)(, but there’s another explosion (a big one) that was got from) http://april-young.com/home/wp-content/uploads/2012/04/Explosion1spritesheet.png(http://april-young.com/home/wp-content/uploads/2012/04/Explosion1spritesheet.png) .(.) 我真的不记得在哪里找到这些角色,因为在真正选择我认为最好的图像之前,我下载了很多图像.(I really don’t remember where I found the characters, as I downloaded lots of images before really choosing the ones I considered the best.) 实际上,本文的许可证不适用于这些图像,并且我不知道这些图像上是否有任何许可证.但是我希望我不会做任何不好的事情,因为我没有从游戏中赚钱.(In fact, the license of this article doesn’t apply to the images and I don’t know if there’s any license on those images. Yet I hope I am not doing anything bad as I am not gaining money from the game.)
隐藏功能:5598(A Hidden Feature: 5598)
我儿子有时会"打键盘"(当时他9个月大),因此写下了5598.我应该告诉你,我确实考虑过使用此类代码在游戏中添加一些隐藏功能,但是,正如他确实将其写到文章中一样,我认为在本文中完成文章将很有趣.而且,如果您看到此消息,则可以很好地"破解"本文的源代码.(*My son wrote 5598 by “hitting the keyboard” sometimes (he was 9 months old when he did it). I should tell you that I really considered putting some hidden feature using such code in the game, but as he did write this into the article, well, I decided it will be interesting to finish the article with it. And if you are seeing this message, good job at “hacking” the source code of the article.*)## Facebook应用(*Facebook applications*) 文章本身已完成.现在我想问些什么,因为直到今天我才真正决定研究如何为(*The article itself is finished. Now I want to ask for something as only today I really decided to look at how to write things for*)**脸书(*Facebook*)**.实际上,我是无意中开始的,因为我决定杀死个人资料中的所有应用程序,并且在详细信息中输入帮助,以说明如何构建Facebook应用程序.而且我很容易地将此游戏制作成了一个Facebook游戏(嗯,还不是社交游戏,但它可以在facebook上运行).(*. In fact, I started that by accident, as I decided to kill all the applications that were in my profile and in the details I entered the help that explains how to build facebook applications. And I easily made this game into a facebook game (well, not a social game yet, but it works inside facebook).*) 我现在真正需要的是知道与我无关的用户是否正在看游戏(*What I really need now is to know if is users unrelated to me are seeing the game at*) https://apps.facebook.com/pfzshootemup/(https://apps.facebook.com/pfzshootemup/) [([) ^)^( ],我想问问是否有人可以告诉我如何显示来自某人的用户个人资料信息(以及朋友列表信息)(] and I want to ask if someone can tell me how can I show the user profile information (and the friend list information) from a)**银光(Silverlight)**应用程序,以及是否有可能从外部应用程序显示此类信息(即(application and if it is possible to show such information from an external application (that is, the)**WPF(WPF)**应用程序的版本).(version of the application).) 欢迎任何帮助,您可以使用电子邮件与我联系(Every help is welcome and you can contact me by using the e-mail)paulozemek@outlook.com(paulozemek@outlook.com)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C#4.0 C# .NET4 .NET Silverlight WPF Dev 新闻 翻译