流利和命令式动画(译文)
By robot-v1.0
本文链接 https://www.kyfws.com/games/fluent-and-imperative-animations-zh/
版权声明 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
- 33 分钟阅读 - 16428 个词 阅读量 0流利和命令式动画(译文)
原文地址:https://www.codeproject.com/Articles/634353/Fluent-and-Imperative-Animations
原文作者:Paulo Zemek
译文由本站 robot-v1.0 翻译
前言
Create animations easily using a Fluent API that integrates well with frame-based animation segments.
使用Fluent API可以轻松创建动画,该API与基于帧的动画片段很好地集成在一起.
- 下载WpfSample-版本-197.3 KB(Download WpfSample - Release - 197.3 KB)
- 下载WinFormsSample-版本-24.1 KB(Download WinFormsSample - Release - 24.1 KB)
- 下载整个源代码946.2 KB(Download Entire Source Code - 946.2 KB)
- 下载Windows Store应用程序库(Download Library for Windows Store Apps) 观看Cyberminds57动画(See the Cyberminds57 animation) -外部来源,需要Silverlight 5(*- External Source and requires Silverlight 5*) 查看使用此库制作的Shoot’em Up游戏(See a Shoot’em Up game made with this library) -外部资源,需要Silverlight5.您可以在以下位置查看有关如何使用此库编写游戏的教程.(*- External Source and requires Silverlight 5. You can see a tutorial on how to use this library to write a game at*) .NET射击游戏(Shoot’em Up .NET) [([) ^)^( ].(].) 您也可以使用(You can also contribute to this library using) CodePlex(CodePlex) .(.)
例(Example)
AnimationBuilder.
BeginSequence().
BeginParallel().
Walk(pfzCharacter, LookDirection.Down, 200).
Walk(sboCharacter, LookDirection.Down, 220).
EndParallel().
Say(1.5, "Hi, my name is Paulo Zemek.", HorizontalAlignment.Right, pfzCharacter).
Say(1.5, "Hi, my name is Sébastien Boudreau.", HorizontalAlignment.Left, sboCharacter).
Say(1.5, "We are aliens.", HorizontalAlignment.Center, pfzCharacter, sboCharacter).
BeginParallel().
Walk(pfzCharacter, LookDirection.Left, -pfzCharacter.Width).
Walk(sboCharacter, LookDirection.Right, canvas.ActualWidth).
EndParallel().
Add(() => { pfzCharacter.Top = 500; pfzCharacter.Left = 200; }).
Walk(pfzCharacter, LookDirection.Up, 300).
Say(3, "Humm... it seems I teleported myself again.\r\nWhy was I walking after all?", HorizontalAlignment.Right, pfzCharacter).
Add(_RandomMoves(pfzCharacter)). // Random moves is frame-based.
Range(1.0, 0.0, 1, (value) => pfzCharacter.Opacity = value).
EndSequence();
请点击(Click) 这里(here) 来查看正在运行的特定动画(需要Silverlight 5).(to see that particular animation running (Requires Silverlight 5).) 或单击下一张图片以查看更完整的示例(与下载示例中的示例相同(它也需要Silverlight 5)):(Or click on the next image to see a more complete example (which is the same found in the download sample [it requires Silverlight 5 too]):)
介绍(Introduction)
写完文章后(After writing the article) 流利的方法和类型生成器(Fluent Method and Type Builder) 我开始考虑游戏和动画,并且正在考虑是否可以创建一些东西来将基于帧和基于时间的动画集成到易于使用的API中.(I got myself thinking about games and animations, and I was thinking if it was possible to create something to integrate frame-based and time-based animations in an easy to use API.)
我必须说,对于复杂的动画,我绝对更喜欢基于帧的动画,因为在每个帧中,我都可以检查状态并确定"下一步".我不需要费力地计算就可以确定如果一整秒过去了该怎么办,因为我可以在内存中逐帧执行直到我处于正确的帧.这极大地简化了代码,但是在某些情况下,基于时间的动画要好得多(因为通过一次计算,我可以拥有任意数量的"中间帧"),但是在集成两种动画方面存在一些问题.(I must say that for complex animations I definitely prefer the frame-based ones, as at each frame I can check states and decide what is the “next step”. I don’t need to deal with hard calculations to decide what to do if an entire second passed as I can execute frame-by-frame in memory until I am at the right frame. This greatly simplifies code but there are situations that time-based animations are much better (as I can have any number of “intermediate frames” with a single calculation) but there is where I have problems to integrate both animation kinds.)
我最初是为了创建一些东西以使其更容易在内部使用基于帧的动画(I originally though about creating something to make it easier to use frame-based animations inside)WPF的(WPF’s) Storyboards
,但最终我做了完全不同的事情,我认为这比我看动画要自然得多.(, but I ended-up doing something completely different, which I consider much more natural to how I see animations.)
实际上,我在动画中看到以下问题(In fact, I see the following problems with animations in)WPF(WPF):(:)
-
他们是(They are)**WPF(WPF)**具体.是的,我说的是(specific. Yeah, I was talking about a)WPF的(WPF’s)
Storyboard
,但是如果我只想在(, but what if I only want to use the animations capabilities in a)**Windows表格(Windows Forms)**应用程序,甚至在"处理动画"但正在通过TCP将结果发送给客户端的服务器应用程序中?(application or even in a server application that is “dealing with the animations” but is sending the results to the clients via TCP?) -
他们对我来说太冗长了.如果可能的话,我更喜欢简单的动画作为单线.(They look too verbose to me. I prefer simple animations to be one-liners if possible.)
-
我认为每个动画都需要有一个开始时间才能紧接着另一个开始,这很糟糕.如果以前的动画需要更长的时间,则这将迫使开发人员重写所有开始时间.同样,创建动画时必须知道所有的持续时间,这不可能告诉我们是否有基于帧的动画在等待用户输入作为序列的一部分.(I consider it terrible that each animation needs to have a start-time to start just after another one. That forces developers to rewrite all the start times if a previous animation should take longer. Also, all the durations must be known when the animation is created, which can be impossible to tell if we have frame-based animations that wait for user inputs as part of the sequence.)
-
的(The)
Timeline
和(and)AnimationTimeline
类已经承担了太多的责任,这使得它们很难被继承和正确实现;(classes already have too many responsibilities, which make them hard to be inherited and implemented properly;) -
的(The)
AnimationTimeline
期望生成影响单个属性的值(必须是(expects to generate values to affect a single property (which must be a)DependencyProperty
).().) 我想要的(以及我将在本文中介绍的)是一个动画框架:(What I wanted (and also what I will be presenting in this article) is an animation framework:) -
能够对基于帧和基于时间的动画进行动画处理;(Capable of animating frame-based and time-based animations;)
-
能够分辨哪些动画是并行运行的,哪些动画是按序列运行的,因此您无需知道每个动画的开始时间;(Capable of telling which animations run in parallel and which animations runs as a sequence, so you don’t need to know the start time of each animation;)
-
能够混合基于帧和基于时间的动画(请注意,这不同于简单地同时支持两种动画);(Capable of mixing frame-based and time-based animations (note that this is different than simply supporting both kinds);)
-
能够以命令式或声明式的方式工作(以及混合使用);(Capable of working in an imperative or in a declarative way (and also mixing them);)
-
能够通过单个动画对所需的多个属性进行动画处理;(Capable of animating as many properties as needed with a single animation;)
-
可用于其他类型的应用程序(例如服务器应用程序,(Usable by other types of application (like server applications,)**Windows表格(Windows Form)**应用等);(applications etc);)
-
只需实现一个接口即可轻松扩展((Easily expandable by implementing a single interface (the)
IAnimation
);();) -
能够通过用其他动画(例如循环,速度修改器和条件)“修饰"动画来更改某些行为.(Capable of changing some behaviors by “decorating” animations with other animations, like loops, speed modifiers and conditions.)
声明式和命令式动画(Declarative and Imperative Animations) 如果条款(If the terms)**陈述式(declarative)**和(and)**势在必行(imperative)**还不够清楚,我将通过示例来展示差异:(aren’t clear enough, I will show the difference by examples:)
陈述式(Declarative)
AnimationBuilder.
BeginLoop().
BeginSequence().
Range(20, 100, 1, (value) => button.Height = value).
Range(100, 20, 1, (value) => button.Height = value).
EndSequence().
EndLoop();
势在必行(Imperative)
IEnumerable<IAnimation> _Animation()
{
while(true)
{
yield return Range.Create(20, 100, 1, (value) => button.Height = value);
yield return Range.Create(100, 20, 1, (value) => button.Height = value);
}
}
的(The)**流利的API(Fluent API)**是库的声明性部分.在其中,我们告诉了整个"动画通量”,但实际上并没有通过单独的指令来控制动画的执行.当然,我们使用指令来创建声明性动画,但是动画本身不再受指令的控制.某些人认为此效果更好,因为我们可以轻松地看到整个动画流,但同时,它的局限性更大,因为在开始动画之前应先知道此类流.(is the declarative part of the library. In it we tell the entire “animation flux” but we don’t really control the execution of the animation by individual instructions. We are of course using instructions to create the declarative animation, but the animation itself is no more controlled by our instructions. Some people consider this better as we can easily see the entire animation flux, but at the same time this is more limited, as such flux should be known before starting the animation.)
另一方面,(On the other hand, the)**势在必行(Imperative)**方式将执行您的代码以知道下一步该怎么做.这样就可以使用所需的任何编程指令,因此您可以检查不同的状态,进行真正复杂的计算,然后返回适当的动画部分,或者甚至可以逐帧制作动画(因此不必返回在下一个动画中,您需要执行的操作并返回一个(way will execute your code to know what to do next. This opens the possibility to use any programming instruction you need, so you can check different states, do really complex calculations and then return the appropriate animation part, or you can even make the animation work frame-by-frame (so instead of returning the next animation, you do what you need to do and return a) WaitNextFrame()
).().)
因此,如果我们这样编写,则先前的示例可以基于框架:(So, the previous example can be frame-based if we wrote it like this:)
IEnumerable<IAnimation> _Animation()
{
var helper = FrameBasedAnimation.CreateHelper(80);
// this helper will keep the animation at 80 frames per second.
while(true)
{
for(int i=20; i<100; i++)
{
button.Height = value;
yield return helper.WaitNextFrame();
}
yield return Range.Create(100, 20, 1, (value) => button.Height = value);
// Yeah, I am mixing the modes here. The first part of the animation is
// frame-based and the second part uses the Range which is time based.
// But it is not hard to imagine that we can copy the first for and
// make it work from 100 to 20.
}
}
Fluent Animation Builder API-基本(Fluent Animation Builder API - Basic)
为了理解这个库,我将从(To understand this library I will start by the)流利的API(Fluent API).这是基础部分,您将使用它来创建许多不同的非交互式动画.即使以后您决定创建基于帧的动画,仍然可以依靠(. It is the basic part and the one that you will use to create many different and non-interactive animations. Even if later you decide to create frame-based animations you can still count on the)**流利的API(Fluent API)**创建较小的序列,这些序列是用户操作的结果,因此我看不出没有从其开始的理由.(to create smaller sequences that are the result of the user actions, so I don’t see a reason not to start by it.)
的心(The heart of the)**流利的API(Fluent API)**是个(is the) AnimationBuilder
类.有了它,您可以创建任何类型的"动画情节提要".(class. With it you can create any kind of “animation storyboard”.)
这里的原理很简单:(The principles here are simple:)
- 创建支持内部动画片段的动画片段的所有方法均以(All the methods that create an animation fragment that supports inner animation fragments start by a)
Begin
然后是(and are followed by the)**动画名称(animation name)**然后您通过致电完成片段(and you finish such fragment by a call to)End**AnimationName**
;(;) - 在任何支持内部片段的片段中,您都可以调用(Inside any fragment that supports inner fragments you can call)
Add()
给出手工创建的片段或给出命令式动画;(to give a fragment created by hand or to give an Imperative animation;) - 您随时可以致电(At any moment you can call)
Range
创建从一个值到另一个值的动画;(to create an animation that goes from one value to another;) - 支持内部片段的动画片段可以是装饰器(它们会稍微改变单个内部动画的行为),也可以是动画组(目前只有两个,一个可以并行运行动画,另一个可以并行运行动画).序列).(The animation fragments that support inner fragments can be either decorators (they somewhat change the behavior of a single inner animation) or they can be animation groups (at this moment there are only two, one that runs animations in parallel and one that run animations as a sequence).) 即使装饰器内部仅支持一个动画,您也可以始终创建一个并行或顺序组作为它们的内容,因此您可以在一个片段中运行许多动画,而这些片段最初仅支持一个片段.(Even if the decorators only support a single animation inside them, you can always create a parallel or sequential group as their content, so you can have many animations running inside a fragment that initially only supports one fragment.) 您是否对这些术语感到困惑(例如装饰)?好的,让我们再次看一下声明性动画的基本示例:(Are you confused by those terms (like decorators)? OK, so let’s see the basic example of declarative animations again:)
AnimationBuilder.
BeginLoop().
BeginSequence().
Range(20, 100, 1, (value) => button.Height = value).
Range(100, 20, 1, (value) => button.Height = value).
EndSequence().
EndLoop();
的(The) BeginLoop()
正在创建一个(is creating a) Loop
装饰.的(decorator. The) Loop
,顾名思义,将(, as the name says, will) Loop
(或重复,如果您愿意的话)其单个内部动画.((or repeat, if you prefer) its single inner animation.)
但正如您所看到的,它的单个动画是一个序列(由(But as you can see, its single animation is a sequence (created by the) BeginSequence
调用),并且在这样的序列内,我们有两个(call) and inside such sequence we have two) Range
电话.(calls.)
如果您按相反的顺序进行分析,我们可以这样说:(If you analyse this in the inverse order, we can say that:)
- 我们有一个片段,它将使按钮的(We have a fragment that will make the button’s)
Height
一秒钟内更大(从20到100的高度);(bigger (from height 20 to 100) in one second;) - 我们有一个片段,它将使按钮的(We have a fragment that will make the button’s)
Height
较小(smaller;) - 然后我们说它们将按顺序运行(一个接一个).(Then we say they will run as a sequence (so one after the other);)
- 最后,该序列将作为无限循环运行.(And finally, that such sequence will run as an infinite loop.)
方法总结(Method Summary)
我认为前面的示例足以让人理解.现在,我将介绍该库附带的所有方法,并说明它们的作用.(I think the previous examples are enough to get an idea. Now I will present all the methods that come with the library and explain what they do.) 在这里,我不会提供真正复杂的示例,但是如果您下载示例,则可以看到许多不同的动画及其代码.(I will not give really complex examples here, but if you download the sample you can see many different animations and their code.)
方法名称(Method Name) | 用法样本+说明(Sample usage + Description) |
---|---|
Range | The Range 是您将要使用的最重要的片段.它负责根据经过的时间进行一些值更改,因此它是一种使颜色在一秒钟内从红色逐渐变为绿色,在屏幕上从左向右移动对象甚至逐渐使颜色逐渐变化的对象.一个不太透明的对象.即使非常简单,它也能够真正显示正在发生的事情.(is probably the most important fragment that you will use. It is the responsible for making some value change based on the elapsed time, so it is the one that will make a color gradually change from red to green in one second, to move objects on the screen from left to right and even to gradually make an object less transparent. Even being extremely simple, it is the one that is capable of really showing something happening.) |
此处呈现的动画仅尝试显示如果两个对象以相同的持续时间但最终值不同进行动画处理会发生什么.与下一项进行比较很有用.(The animation presented here only tries to show what will happen if two objects are animated with the same duration, but different final values. It is useful to compare to the next item.)
|
|RangeBySpeed|这也是(This is also a) Range
,就像上一个一样,但是在这种情况下,您知道动画以什么速度移动,而不是确切需要多少时间.例如,您可能希望角色从屏幕的实际位置向右离开屏幕,但是角色应始终以相同的速度"行走".因此,如果它已经在屏幕的右侧,则可能会花费不到一秒钟的时间,但是如果它在屏幕的左侧,则可能需要3秒钟.在这种情况下,将为您计算需要花费多少时间,您只需要知道速度(通常,这是一秒钟内将改变的量,因此,如果我们说的是从一个向右移动的角色,速度是每秒移动多少像素).(, like the previous one, but in this case you know at which speed the animation moves, not exactly how many time it will take. For example, you may want a character to go off the screen to the right from its actual position, but it should always “walk” at the same speed. So, if it is already at the right part of the screen it may take less than one second, but if it is at the left it may take 3 seconds. In this case how many time it takes will be calculated for you, you only need to know the speed (in general, this is the amount that will be changed in one second so, if we talk from a character moving to the right, the speed is how many pixels it will move per second).)
如果您在声明动画时不知道初始值,则第二个版本会接收一个代表来读取实际值的委托(在本示例中,(The second version, which receives a delegate to read the actual value is useful if you don’t know the initial value when declaring the animation (in the example, the) character.Left
在声明动画时可能会位于位置0,但在执行播放器控制的动画后可能会在任何位置).所以你给一个代表(may be at position 0 when declaring the animation but after executing a player controlled animation it may be anywhere). So you give a delegate to)read动画开始时的实际位置.(the actual position when the animation starts.)
此处呈现的动画试图显示如何使用相同的速度出现两个速度相同但最终值不同的对象(The animation presented here tries to show how two objects with the same speed but different final values will appear by using the) RangeBySpeed
method.
|
|BeginRanges/EndRanges(BeginRanges / EndRanges)|BeginRanges/EndRanges是一种特殊的序列,准备创建许多范围,这些范围将全部执行相同的委托以更新值.因此,在(The BeginRanges/EndRanges is a special kind of sequence prepared to create many ranges that will all execute the same delegate to update the value. So, during the) BeginRanges
您给将要执行的委托和一个初始值.然后,你打电话(you give the delegate that will be executed and an initial value. Then, you call) To()
给出下一个要使用的值,以及达到该值需要花费多少时间.按照顺序,您可以使用(giving the next value to go to and how many time it will take to achive that value. As a sequence, you can use) Wait()
调用,因此动画可以从一个值转到另一个值,在该值上稍等片刻,然后再转到另一个值.(calls, so the animation can go from one value to another, wait a little at that value, and then go to another value.)
|
|Add|The Add()
仅当您实际上在另一个动画容器中时,该方法才存在.它将简单地将给定的动画添加到动画容器中.如果您创建自己的动画片段,或者您具有应在同一序列的不同部分播放的特殊动画,则此功能很有用,因此您可以先准备此类动画,然后再制作(method only exists when you are actually inside another animation container. It will simply add the given animation to the animation container. This is useful if you create your own animation fragments or if you have a special animation that should be played in different parts of the same sequence, so you can first prepare such animation, and then) Add()
它在需要的地方.(it where needed.)
|
|BeginLoop/EndLoop(BeginLoop / EndLoop)|The BeginLoop
可以接收一个参数来告知其内部动画应重复多少次.如果通过(can receive a parameter to tell how many times its inner animation should be repeated. If you pass) -1
或者,如果您不传递此类参数,则循环将是无限的.(or if you don’t pass such parameter the loop will be infinite.)
|
|BeginParallel/EndParallel(BeginParallel / EndParallel)|在并行块内,所有添加的动画都将并行运行.重要的是要注意,并行动画片段本身仅在最长寿命的内部动画结束时(如果完全结束)才被视为结束.(Inside a parallel block all the added animations will run, well, in parallel. It is important to note that the parallel animation fragment itself will only be considered to end when the most long living inner animation ends (if it ends at all).)
上一个动画仅并行显示两个范围.它并没有真正显示所提供的代码.然而,即使(The previous animation is only showing two ranges in parallel. It is not really showing the presented code. Yet, even the) Range
动画显示了两个并行运行的不同范围,需要使用此命令.(animations are showing two different ranges running in parallel, which requires the use of this command.)
|
|BeginSequence/EndSequence(BeginSequence / EndSequence)|我认为序列是最自然的动画组.您知道自己想做很多事情,一个接一个.您不在乎(或不知道)每个"事物"将花费多少时间,但是没有问题,该序列只是告诉一个"事物"(动画片段)在另一个之后开始.(In my opinion the sequence is the most natural animation group. You know that you want to do many things, one after the other. You don’t care (or you don’t know) how much time each “thing” will take, but there is no problem, the sequence simply tells that one “thing” (animation fragment) starts after the other.)
上一个动画显示了如何依次播放两个范围.该示例仅旨在显示第二个"部分"在第一个"部分"完成之后如何开始.(The previous animation shows how two ranges will be played in a sequence. The sample only intends to show how the second “part” starts after the first is finished.)
|
|BeginAcceleratingStart/EndAcceleratingStart(BeginAcceleratingStart / EndAcceleratingStart)|在许多情况下,动画可能需要缓慢开始才能达到正常速度.当然,您可以保留(There are many situations in which an animation may need to start slowly and then achieve its normal speed. Surely you can keep the)time保持不变并加速内部动画.但是,为了简化其中许多您可能需要"加速启动"的情况,您可以使用(unchanged and accelerate the inner animation. But to simplify many of those situations in which you may want an “accelerating start” you can use the) BeginAcceleratingStart
decorator.
这样的装饰者将"撒谎"到内部动画中,说经过的时间比开始时少,以渐进的方式加速直到达到正常速度.它接收到的参数是达到正常速度需要多少时间,但这就是"内部时间",这意味着该时间就是已经受这种加速度影响的时间.这样可以完成操作,因此,如果您可以要求加速开始一秒钟,并且将内部动画设置为仅一秒钟.在这种情况下,它将在达到全速时结束(如果使用了外部时间,则加速将在一秒钟内完成,但是随着内部动画开始的速度变慢,它将在稍后结束).(Such decorator will “lie” to the inner animation, saying that less time was elapsed than it really was in the beginning, accelerating in a progressive manner until the normal speed is achieved. The parameter it receives is how many time it will take to be at normal speed, but that’s the “inner time”, which means that such time is the time already affected by such acceleration. It is done this way so if you can ask for an accelerating start of one second and put an inner animation of only 1 second. In this case, it will end when it achieves full speed (if the outer time was used, the acceleration will finish in one second but, as the inner animation started slower, it will end later).)
| |BeginDeacceleratingEnd/EndDeacceleratingEnd(BeginDeacceleratingEnd / EndDeacceleratingEnd)|此效果与上一个效果相反,它将使动画的结尾减速.如果内部动画大于应减速的部分,则应将整个动画持续时间作为第二个参数.不幸的是,在这种情况下,您应该知道动画将花费多少时间来告诉它应该在哪里开始减速.(This effect is the opposite of the previous one and it will make the ending of an animation to deaccelerate. If the inner animation is bigger than the part that should be deaccelerated you should give the entire animation duration as the second parameter. Unfortunately, in this case, you should know how many time the animation will take to tell where it should start deaccelerating.)
|
|BeginTimeMultiplier/EndTimeMultiplier(BeginTimeMultiplier / EndTimeMultiplier)|在这个例子中,我们可以说(In this example, we can say that such) Range
会在1.5秒内结束,因为时间乘数会简单地将"经过"的时间"撒在"其内部动画中,从而使其以两倍的速度运行.当然,这不是时间乘数的实际使用,因为最好给范围以正确的时间,但是(will end in 1.5 seconds because the time multiplier will simply “lie” the elapsed time to its inner animation, making it run at double speed. Of course this is not a practical use of the time multiplier, as it is better to give the right time to the range, but the) TimeMultiplier
可用于影响任何内部动画的速度,这可能非常复杂.(can be used to affect the speed of any inner animation, which can be extremely complex.)
|
|BeginProgressiveTimeMultiplier/EndProgressiveTimeMultiplier(BeginProgressiveTimeMultiplier / EndProgressiveTimeMultiplier)|The ProgressiveTimeMultiplier
是(is the resource used by the) AcceleratingStart
and DeacceleratingEnd
动画.构造它时,您要告诉您从初始速度(0.2)到最终速度(1.5)需要花费多少时间(在本例中为1秒).在达到最终速度之后,将使用此最终速度乘数来播放其余动画.(animations. When constructing it you tell how many time (in this case, 1 second) it will take to go from an initial speed (0.2) to a final speed (1.5). After the final speed is achieved, the rest of the animation is played using such final speed multiplier.)
|
|BeginRunCondition/EndRunCondition(BeginRunCondition / EndRunCondition)|The RunCondition
是一个装饰器,只有在满足条件时才会开始其内部动画.但是,如果条件在动画开始后发生变化,它将继续运行直到结束.(is a decorator that will only start its inner animation if a condition is met. However, if the condition changes after the animation started, it will continue to run until its end.)
|
|BeginPrematureEndCondition/EndPrematureEndCondition(BeginPrematureEndCondition / EndPrematureEndCondition)|The PrematureEndCondition
类似于(is similar to the) RunCondition
但是它有两个主要区别:(but it has two main differences:)
- 其条件必须是(Its condition must be)
false
允许动画运行,其值为(to allow the animation to run, as a value of)true
表示应该结束动画;(means it should end the animation;) - 价值一变成(As soon as the value becomes)
true
它将停止其内部动画.它不会尝试让它运行到最后,因为(it will stop its inner animation. It will not try to let it run until the end as the)RunCondition
does. | | BeginPauseCondition/EndPauseCondition(BeginPauseCondition / EndPauseCondition) |ThePauseCondition
与以前的条件有所不同,因为它不会决定动画是过早开始还是结束.当条件为时,它将仅避免运行其内部动画(is a different condition than the previous ones in the sense that it will not decide if the animation starts or ends prematurely. It will simply avoid running its inner animation while the condition is)true
.这样,如果内部动画逐帧或声明性地运行,它可以独立地暂停内部动画.(. By doing this, it allows inner animations to be paused, independently if those inner animations run frame-by-frame or are declarative ones.) 对于基于帧的动画,这可能是最有用的一种,因为它使添加对暂停的支持变得非常简单,而无需验证每一帧的暂停条件,因此,它对游戏非常有用.(Probably this one is the most useful one for frame-based animations, as it makes it extremely simple to add support for pauses without requiring to verify for a pause condition at each frame and so, it is very useful for games.) | |BeginTimeLimit/EndTimeLimit(BeginTimeLimit / EndTimeLimit)|此装饰器将限制内部动画的运行时间.这在内部使用在某些序列中具有许多不同装饰器的构造上,所有这些装饰器都在同一动画上工作,因此它们中的每个都限于特定的时间.但是,例如,如果您只想呈现游戏级别的"几秒钟",则可能直接有用.(This decorator will limit the time an inner animation may run. This is internally used on some constructs that have many different decorators in a sequence, all of them working over the same animation, so each one of them is limited to a certain time. But it may be directly useful if you simply want to present “some seconds” of a game level, for example.) | |BeginDisposeOnEnd/EndDisposeOnEnd(BeginDisposeOnEnd / EndDisposeOnEnd)|通常,内部动画即使结束也会保留在内存中.如果将它们置于循环中,则很有用,因此可以将它们重置并再次运行.但是,如果您要创建使用大量资源且不打算重置的动画,则可以将其放在(Usually inner animations are kept in memory even when they are ended. This is useful if you put them inside a loop, so they are reset and run again. But if you are creating an animation that uses a lot of resources and is not going to be reset, you can put it inside a)DisposeOnEnd
块,因此动画将在结束时立即处理.(block, so the animation will be disposed as soon as it ends.) | |Wait|TheWait()
方法仅存在于序列内部(它对(method only exists inside sequences (it will be useless to)Wait()
顾名思义,它只是意味着序列将在开始下一个动画片段之前等待一段时间.(in parallel) and, as the name says, it simply means the sequence will wait some time before starting the next animation fragment.) | | BeginSegmentedTime/EndSegmentedTime(BeginSegmentedTime / EndSegmentedTime) |如果您要使用基于时间的细分为对象设置动画(例如(This method is especially useful if you are animating your objects using time-based segments (like)Range
),但您需要以一致的方式检查冲突.() but you need to check for collisions in a consistent manner.) 因此,要做到这一点,您可以确定应在哪个间隔下测试段(通常用于碰撞检测).每个内部动画将运行到该段大小,执行给定的委托,并继续前进,直到达到实时时间为止.如果值较小,则处理内部动画以生成平滑结果.(So, to do it, you tell at which intervals the segments should be tested (usually used for collision detection). Each inner animation will run up to that segment size, execute the given delegate and continue to advance until the real time elapsed is reached. In case of smaller values, the inner animations are processed to generate smooth results.) |
命令式动画-中介(Imperative Animations - Intermediary)
声明性动画必须从一开始就知道它们将要做的所有事情,即使它们可以通过使用(Declarative animations must know everything they will do from the start and, even if they can have some interactions through the use of the) RunCondition
和(and) Add
可以执行任何代码的调用,但仍然受到限制.他们的目的不是互动.(calls that may execute any code, they are still limited. Their purpose is not to be interactive.)
另一方面,命令式动画可以使用任何编译器构造,例如(Imperative animations, on the other hand, can use any compiler constructs, like) while
s(s,) if
s,甚至(s, and even) try/catch/finally
块,更好的是,他们可以(blocks and, which is better, they can) yield return
其他动画要运行,例如声明性动画,因此您可以在命令性动画中创建复杂的逻辑,然后(other animations to run, like declarative ones, so you can create the complex logic in the imperative animation and then) yield return
您可以使用创建的片段更容易(the easier fragments that you can create with the)声明性API(Declarative API).(.)
我之前已经介绍过命令式动画,但让我们再次看到它们:(I already presented the imperative animations before, but let’s see them again:)
IEnumerable<IAnimation> ImperativeAnimation()
{
while(true)
{
yield return AnimationBuilder.Range(20, 100, 1, (value) => button.Height = value);
yield return AnimationBuilder.Range(100, 20, 1, (value) => button.Height = value);
}
}
如您所见,命令式动画的"心脏"是使用(As you can see, the “heart” of the imperative animations is the use of the) yield return
关键字以给出要执行的下一个动画.但是,如果您的代码已经在更新它需要更新的对象,而您只想等待下一帧,该怎么办?我最初虽然接受(keyword to give the next animation to be executed. But, what if your code is already updating the objects it needs to update and you only want to wait for the next frame? I initially though about accepting) yield return null;
作为(as the) WaitNextFrame()
指令,但是我不能保证所有动画都具有相同的帧频.(instruction, but then I can’t guarantee that all animations will have the same frame-rate.)
因此,为了解决这个问题,我创建了(So, to solve the problem I created the) FrameBasedAnimationHelper
顾名思义,它是一个帮助程序类,旨在制作基于帧的动画.创建动画时,您应该告诉动画的帧速率,并且在需要时可以使用(and, as you can imagine by the name, it is a helper class with the purpose of making frame-based animations. When you create it you should tell the frame-rate of the animation and when you need you can use) yield return helper.WaitNextFrame()
甚至(or even) yield return helper.Wait(someValue)
.(.)
由于它是作为普通实例而不是被创建的(As it is created as a normal instance instead of being) static
您可以让许多动画并行运行,但是帧速率不同.多久不重要(you can have many animations running in parallel, but with different frame rates. It is not important how often)WPF(WPF),(,)**银光(Silverlight)**要么(or)**Windows表格(Windows Forms)**正在触发动画,这样的助手将使您的动画保持正确的帧速率,补偿丢失的帧并在调用彼此之间距离太近时等待.(is triggering the animation, such helper will keep your animation at the right frame-rate, compensating lost frames and waiting when the calls come too near each other.)
private static IEnumerable<IAnimation> _RandomMoves(ICharacterImage character)
{
var random = new Random();
var helper = FrameBasedAnimationHelper.CreateByFps(60);
for (int i = 0; i < 60*4; i++)
{
double value = (i / 60.0) + 1;
int x = random.Next(2);
int y = random.Next(2);
if (x == 0)
x = -1;
if (y == 0)
y = -1;
character.Left += x * value;
character.Top += y * value;
yield return helper.WaitNextFrame();
}
}
ImperativeAnimation.AddSubordinatedParallel(ImperativeAnimation.AddSubordinatedParallel)
您可能知道(You probably understood that the) yield return
的(of the) ImperativeAnimation
s将作为一个序列工作.但是,如果要添加动画以并行运行怎么办?(s will work as a sequence. But what if you want to add an animation to run in parallel?)
正常的答案是将这样的动画添加到(The normal answer is to add such animation to the) AnimationManager
直.动画管理器只是简单地并行运行所有添加的动画.但是在这种情况下,动画将是完全独立的.例如,如果实际动画在(directly. The animation manager simply runs all added animations in parallel. But in such case the animations will be completely independent. For example, if the actual animation is running inside a) TimeMultiplier
新动画将不受其影响.如果实际动画结束,则其他动画将继续运行.(the new animation will not be affected by it. If the actual animation ends, the other animation will continue to run.)
如果那不是您想要的,还有另一种选择.在命令式动画中,您可以调用(If that’s not what you want, there is an alternative. Inside an imperative animation you can call) ImperativeAnimation.AddSubordinatedParallel()
.这种方法将在同一容器内添加一个动画,使其与实际命令式动画并行运行.也就是说,如果命令式动画位于(. Such method will add an animation to run in parallel to the actual imperative animation, inside the same container. That is, if the imperative animation is inside a) TimeMultiplier
,这样的动画也将位于(, such animation will also be inside a) TimeMultiplier
.同样,作为从属动画,当命令式动画结束时,此类并行动画也将结束.就是说:它将与相同的装饰器并行运行,但仍被认为是"子"动画,仅当主动画处于活动状态时才处于活动状态.(. Also, as a subordinated animation, when the imperative animation ends such parallel animation will end too. That is: It will run in parallel, with the same decorators but it is still considered a “child” animation that will only be alive while the main animation is alive.)
我想给出命令式动画的方法摘要,但是,因为它们可以使用任何C#构造,所以没有用.所以我的总结是:(I wanted to give a method summary of the imperative animations but, as they can use any C# construct, it will be of no use. So my summary is:)
- 使用(Using)
yield return
赋予新动画"实时"运行的能力非常好;(is great to give new animations to run “on the fly”;) - 如果要基于帧的动画,请创建一个(If you want frame-based animations create a)
FrameBasedAnimationHelper
并打电话(and call)yield return helper.WaitNextFrame()
;(;) - 如果您不想初始化返回要动画的动画,而是要初始化与实际动画并行运行的动画,请使用(If instead of yield returning an animation you want to initialize an animation that will run in parallel to the actual one, use the)
ImperativeAnimation.AddSubordinatedParallel()
.(.)
扩展库-高级(Extending the Library - Advanced)
我在创建该库时考虑了可扩展性,并且该库中有许多扩展点:您可以创建新的(I created this library with extensibility in mind and there are many extension points in this library: You can create new) RangeCalculator
s(我为(s (I give range calculators for) int
,(,) double
,(,) Point
和(and) Color
,但是如果您要使用范围计算器该怎么办(, but what if you want a range calculator for) Rectangle
或其他数字类型?),您可以创建新的(s or other numeric types?), you can create new) SpeedToTimeCalculator
s(这就是(s (that’s how the) RangeBySpeed
正常工作,它将首先计算时间,然后将其正常(works, it will first calculate the time, then it will be a normal) Range
),您可以通过实施(), you can create new animation fragments by implementing the) IAnimation
界面或创建命令式界面.(interface or by creating imperative ones.)
但是,如果您看到文章中的第一个示例,那么我正在使用方法(But if you see the first example in the article, I am using the methods) Walk()
和(and) Say()
不属于此库的一部分.不幸的是,实际上不可能扩展(which aren’t part of this library. Unfortunately it is not really possible to extend the) AnimationBuilder
类型,而无需更改其源代码,但是我们可以为(type without changing its source-code, but we can create extension methods for the)流利的API(Fluent API).我并不是真的认为这是完美的解决方案,但是整个(. That’s a solution that I don’t really consider perfect, but the entire)**流利的API(Fluent API)**仅仅是为了使您能够编写看起来像声明式的代码,因此,不必担心创建扩展方法.(is only there to make you able to write code that looks declarative, so for that purpose, don’t be afraid of creating extension methods.)
因此,让我们探索如何以许多不同的方式扩展该库:(So, let’s explore how to extend this library in many different ways:)
创建新的RangeCalculators(Creating new RangeCalculators)
的(The) Range
可能是您最常使用的动画片段,但是该库仅支持(is probably the animation fragment that you will use the most, but this library only comes with support for) int
,(,) double
,(,) Color
和(and) Point
.如果要使用任何其他类型的范围,则需要创建自己的范围计算器.创建一个的最简单方法是从(. If you want a range from any other type you will need to create your own range calculator. The easiest way to create one is to inherit from the) RangeCalculator<T>
,其中(, where the) `` 是您要计算范围的数据的类型.通过继承此类,您只需实现(is the type of the data to which you want to calculate the range. By inheriting such class you need only to implement the) Calculate
方法,它接收(method, which receives the) initialValue
,(, the) finalValue
,(, the) actualTime
和持续时间(或(and the duration (or the) totalTime
).对于大多数类型,该方法将如下所示:(). For most types, the method will look like this:)
TimeSpan remaining = duration-actualTime;
return (initialValue * remaining.TotalSeconds + finalValue * actualTime.TotalSeconds) / duration.TotalSeconds;
我说看起来(I say that it looks)**喜欢(like)**这是因为您可能需要强制转换,并非所有类型都可以简单地乘以double(例如,在颜色上,每个元素必须独立处理),并且您可能非常想创建一个不是"线性"的计算器.(this because you will probably need casts, not all types can be simply multiplied by a double (on colors, for example, each element must be processed independently) and you may very well want to create a different calculator that is not “linear”.)
好吧,在创建您的(Well, after creating your) RangeCalculator
您可以将其作为范围的参数,也可以将范围计算器注册为其类型的默认值.就像是:(you can give it as a parameter to the Range or you can register your range calculator as the default one for its type. Something like:)
RangeCalculator<MyType>.Default = new MyTypeRangeCalculator();
例如,这是计算两点之间距离的代码:(For example, this is the code to calculate the range between two points:)
public sealed class PointRangeCalculator:
RangeCalculator<Point>
{
public override Point Calculate(Point initialValue, Point finalValue, TimeSpan actualTime, TimeSpan duration)
{
double initialMultiplier = (duration - actualTime).TotalSeconds;
double finalMultiplier = actualTime.TotalSeconds;
double totalDivider = duration.TotalSeconds;
double x = (((initialValue.X * initialMultiplier) + (finalValue.X * finalMultiplier)) / totalDivider);
double y = (((initialValue.Y * initialMultiplier) + (finalValue.Y * finalMultiplier)) / totalDivider);
return new Point(x, y);
}
}
注意:(Note:)这是生成此类计算器所需的最低代码.如果您看到源代码,您会注意到我创建了一个私有构造函数,并且还声明了Instance属性.我这样做是因为不需要创建多个这样的范围计算器实例,但是如果您不这样做,您的代码将继续起作用,并且如果您仅创建一个实例并将其注册为默认实例,您不会使用过多的内存.(This is the minimum required code to generate such calculator. If you see the source code you will notice that I created a private constructor and I also declared an Instance property. I did it that way because there is no need to create more than one instance of such range calculator, yet if you don’t do that your code will continue to work and if you only create a single instance and register it as the default one you will not use more memory than necessary.)
创建新的SpeedToTimeCalculators(Creating new SpeedToTimeCalculators)
的(The) RangeBySpeed
不使用完全不同的逻辑来计算范围.它只是简单地通过速度计算给定范围的持续时间,然后调用法线(does not use a completely different logic to calculate the range. It simply calculates the duration of a given range by its speed and then calls a normal) Range
.(.)
但是,要根据速度信息计算持续时间,还必须找到该计算器,而且,只有某些类型的计算器((But to calculate the duration based on a speed information it is also necessary to find that calculator and, again, there are only some types that have calculators () int
,(,) double
,(,) Color
和(and) Point
).如果要支持其他类型,则应创建一个(). If you want to support other types, you should create a) SpeedToDurationCalculator
.(.)
为此,请从(To do it, inherit from the) SpeedToDurationCalculator<T>
并实施(and implement the) CalculateDuration
方法.它收到(method. It receives the) initialValue
,(, the) finalValue
和(and the) speed
.重要的是要知道,即使(. It is important to know that even if the) finalValue
小于(is less than the) initialValue
,结果不应为负数(也应为(, the result should not be negative (also the received) speed
不得小于或等于(should never be less than or equal to)零(zero)).().)
的(The) speed
表示该值在一秒钟内将改变多少.所以,对于(represents how much the value will change in one second. So, for a) Color
,则差异较大的元素在一秒钟内将发生多少变化(其他元素不会干扰速度,因为它们是最大变化的一部分).(, it is how much the element with greater difference will change in one second (the other elements don’t interfere in the speed as they are a proportion of the biggest change).)
因此,以颜色为例,这是创建这种颜色的代码(So, using the color as example, this is the code to create such) SpeedToDurationCalculator
:(:)
public sealed class ColorSpeedToDurationCalculator:
SpeedToDurationCalculator<Color>
{
public override TimeSpan CalculateDuration(Color initialValue, Color finalValue, double speed)
{
double a = Math.Abs(finalValue.A - initialValue.A);
double r = Math.Abs(finalValue.R - initialValue.R);
double g = Math.Abs(finalValue.G - initialValue.G);
double b = Math.Abs(finalValue.B - initialValue.B);
double maxValue = Math.Max(a, Math.Max(r, Math.Max(g, b)));
return TimeSpan.FromSeconds(maxValue / speed);
}
}
实施IAnimation接口(Implementing the IAnimation interface)
的(The) IAnimation
接口具有以下成员:(interface has the following members:)
IsUseless
,(,) Reset
,(,) Update
而且它也是一次性的,所以它具有(and it is also disposable, so it has the) Dispose
方法.因此,要实施它,您应该了解这些成员中的每个成员.(method. So, to implement it you should understand each one of those members.)
的(The) IsUseless
如果处理了动画或无法使用动画,则属性应返回true.例如,没有内部动画的装饰器或组是无用的.此值有助于外部容器,然后外部容器可以删除这种无用的动画.(property should return true if the animation was disposed or if it is not functional. For example, a decorator or a group that does not have an inner animation is useless. This value helps the outer containers, which can then remove such useless animation.)
的(The) Reset
方法应清除与动画的实际位置有关的所有已使用资源,并在开始时重新放置它.在正常情况下,(method should clean up any used resources related to the actual position of the animation and also reposition it at the beginning. In normal situations, a call to) Reset
不应使对象变得无用,并且循环使用它,以便动画可以一次又一次地播放.(should not make the object useless and it is used by the the Loop so the animation can play again and again.)
的(The) Dispose
,它应该释放所有使用的资源,并使动画无用.请注意,与通常的预期不同,(, well, it should release all used resources and also make the animation useless. Note that different than what it is usually expected,) Dispose
通常不会在动画上调用.如果要在中间停止动画,这很有用.如果动画正常结束,则应该可以将其重置.(is usually not called on animations. It is useful if you want to stop the animation in the middle. If the animation ends normally it should be able to be reset.)
最后是(And finally, there is the) Update
.预计只会在正的经过时间中调用它.在里面(. It is expected that it will only be called with positive elapsed times. In the) Update
您应该考虑到经过的时间并返回动画,应该做动画应该做的所有事情(you should do everything the animation is supposed to do considering the elapsed time and return) true
动画是否应该再次播放或(if the animation should play again or) false
如果结束了.在正常情况下,结束动画不应丢弃它,因为这样的动画可能会(if it ended. In normal situations ending the animation should not dispose it, as it is possible that such animation will be) Reset
再玩一次(and played again.)
IAfterEndAwareAnimation(IAfterEndAwareAnimation)
假设您的动画是序列的一部分.它的生存时间将超过100毫秒,但速度会有所下降,并且下一次更新只会在500毫秒之后.当然,您的动画将结束,但是如果没有这种减速,则下一个动画将在400毫秒前开始.您应该忽略这一点,下一个动画从零开始,还是下一个动画提前400毫秒?(Suppose your animation is part of a sequence. It will live for more 100 milliseconds, but there is a slowdown and the next update come only after 500 milliseconds. Surely your animation will end, but if there was not such slowdown, the next animation will have started 400 milliseconds ago. Should you ignore that, and the next animation starts from zero, or should the next animation advance those 400 milliseconds?)
如果您可以精确计算动画何时结束,请实施(If you can precisely calculate when the animation ended, then implement the) IAfterEndAwareAnimation
.这样做的唯一目的是告诉动画结束后经过了多少时间,并有顺序地帮助纠正减速(或仅结束于中间的动画),并且避免了从一个动画片段到另一个.(. Its only purpose is to tell how many time elapsed after the end of the animation and, in a sequence, will help correct slowdowns (or simply animations that ended in the middle) and this will avoid noticing “small pauses” from one animation fragment to the other.)
创建新的装饰器(Creating new decorators)
建立新的(Creating new) IAnimation
如果您想要使用Ranges或命令式动画无法实现的另一种动画,则实现可能很有用.但是也许您只想更改现有的动画.(implementations may be useful if you want a different kind of animation that is not possible with the use of the Ranges or imperative animations. But maybe you only want to alter existing animations.)
好吧,您无法做很多更改现有动画的操作(您只能截获对(Well, there is not much that you can do to alter existing animations (you can only intercept the calls to) Reset
,(,) Update
,(,) IsUseless
和(and) Dispose
),并且许多现有的行为更改已实现.但是,如果您对框架中尚未进行的行为更改有所了解,则可以继承() and many of the existing behavior changes are already implemented. But, if you have an idea of a behavior change that is not already done in the framework, you can inherit the) AnimationDecorator
类型.(type.)
您应该在其中实施(In it you are expected to implement the) Update
方法,因为这是您通常会截取的方法,但是您可以随意覆盖其他方法.作为装饰,它具有(method, as this is the method that you will usually intercept, but you are free to override the other methods. As a decorator, it has the) InnerAnimation
属性.例如,(property. For example, the) TimeMultiplier
只需乘以经过的时间,然后再将其用于内部动画和(simply multiplies the elapsed time before giving it to its inner animation and the) PrematureEndCondition
将验证条件,如果是(will verify the condition and, if it is) true
,将返回动画结束而无需调用其内部动画的情况.(, will return that the animation ended without calling its inner animation.)
“添加"方法到Fluent API(“Adding” methods to the Fluent API)
不改变(Without changing the) AnimationBuilder
不可能仅向其中添加新方法,并且至少目前还没有添加静态扩展方法的方法,因此,如果您要添加一个新方法,该方法可以直接使用(unit it is not possible to simply add new methods to it and at least at this moment there aren’t ways to add static extension methods, so if what you want is to add a new method that’s available directly by the) AnimationBuilder
静态类型是不可能的.(static type that’s not possible.)
但是,如果您想要的是能够向容器"添加"新方法(因此,您已经做了类似(But if what you want is to be able to “add” new methods to the containers (so, you already did something like) AnimationBuilder.BeginSequence().
),您可以使用扩展方法,但是还有另一个问题,即() you can use extension methods, but then there is another problem, the signature of the)**流利的API(Fluent API)**方法很成问题.(methods is very problematic.)
解决方案是创建一个通用扩展方法,其中(The solution is to create a generic extension method where the) `` 是(is) IAnimationBuilder
.所有(. All the) AnimationBuilder
的实现此类接口,因此会将此类方法添加到所有这些方法中,并且通过泛型,您可以返回正确的类型.因此,要添加普通方法(即不是Begin方法),可以使用类似以下内容的方法:(s implement such interface, so such method will be added to all of them and, by being generic, you can return to the correct type. So, to add a normal method (that is, not a Begin method), you can use something like:)
public static T MethodName<T>(this T animationBuilder, ... any parameters your method needs ...)
where
T: IAnimationBuilder
{
animationBuilder.Add(someAnimationThatShouldBeCreated);
return animationBuilder;
}
但是,如果您想创建一个(But if you want to create a) Begin/End
扩展方法对将需要一些额外的工作.您应该创建一个继承自(extension method pair you will need a little extra work. You should create a builder that inherits from the) AnimationBuilder<TParent, TThis>
但保持(but keeps the) TParent
作为通用参数.然后,您应该创建一个接收父级和实际动画片段的构造函数,还应该添加一个End(as a generic parameter. Then you should create a constructor that receives the parent and the real animation fragment and you should also add an End)**名称(Name)**这将返回(that will return the) Parent
.您还需要实施(. You also need to implement the) Add
方法(通常会填充(method (which will usually fill the) InnerAnimation
要么(or) Add
给定的动画到您自己的"动画"容器中).(the given animation to your own “animation” container).)
然后,扩展方法应如下所示:(Then, the extension method should be like this:)
public static NameBuilder<T> Name<T>(this T parent, ... parameters ...)
where
T: IAnimationBuilder
{
var animation = new NameAnimation(... parameters ...);
parent.Add(animation);
return new NameBuilder<T>(parent, animation);
}
样品(Samples)
下载中的样本包括三个不同的应用程序:(The samples in the download include three different applications:)
- Windows窗体应用程序仅在此处显示该库可在Windows窗体中使用,但它远不是一个有用的示例.(The Windows Forms application is only there to show that the library can be used in Windows Forms, but it is far from an useful sample.)
- WpfSample是可以更好地用作教程的示例.它具有不同类型的动画,具有不同的复杂性.(The WpfSample is the sample that works better as a tutorial. It has different kind of animations with different complexities.)
- 最后是Silverlight应用程序,它是对该库的更完整利用,也是我们个人演示的开始.(Finally there is the Silverlight application, which is a more complete utilisation of the library and also the beginning of our personal presentation.)
外部档案(*External Files*) 在网站上找到了用于Cyberminds57实际动画的背景图片(*The background image used on the actual Cyberminds57 animation was found in the site*) http://besthdwallpapersdesktop.com/designer-room/(http://besthdwallpapersdesktop.com/designer-room/) 不受本文的许可.(*and is not subject to the this article’s license.*) Cyberminds57中使用的声音位于(*The sounds used in Cyberminds57 were found in*) http://www.freesfx.co.uk/(http://www.freesfx.co.uk/) [([) ^)^( ].(*].*) 我不记得我从哪里得到士兵的角色,但我真的相信这是来自(*I don’t remember where I got the soldier character, but I really believe it is from*) 角色扮演游戏制作人(RPG Maker) .(*.*) 我从中获得的播放和暂停图标(*The play and pause icons I got from*) http://www.iconarchive.com/show/play-stop-pause-icons-by-icons-land.html(http://www.iconarchive.com/show/play-stop-pause-icons-by-icons-land.html) [([) ^)^( ].(].) 最后,我在标题中使用的那个机器人(And finally, that robot that I am using in titles and also on the)**WPF(WPF)**示例我只知道它来自游戏,但我什至都不知道是哪个游戏,因为这是我制作的非常古老的动画.(sample I only know it is from a game, but I don’t even know which one, as this is a very old animation that I had.)
版本记录(Version History)
- 2016年6月18日:添加了该库的Windows应用商店版本的下载;(June 18, 2016: Added a download of a Windows Store version of the library;)
- 2013年10月16日:添加了Shoot’em Up游戏示例,更改了许可证并添加了Codeplex链接;(October 16, 2013: Added a Shoot’em Up game sample, changed the license and added the codeplex link;)
- 2013年8月25日:向Cyberminds57动画添加了声音,并添加了(August 25, 2013: Added sounds to the Cyberminds57 animation and added the) 分段时间(SegmentedTime) 类/动画片段;(class/animation fragment;)
- 2013年8月18日:添加了(August 18, 2013: Added the) 暂停条件(PauseCondition) ,使Cyberminds57演示文稿变得可暂停,并在代码中完成了一些TODO,其中包括如何(, made the Cyberminds57 presentation pausable and finished some TODOs in the code, which includes how) 处理(Dispose) 预计将在此框架中使用;(is expected to be used in this framework;)
- 2013年8月8日:第一个版本.(August, 8, 2013: First version.)
许可
本文以及所有相关的源代码和文件均已获得The Code Project Open License (CPOL)的许可。
C#4.0 C# .NET4 .NET Silverlight VS2010 VS2013 WPF Dev Architect 新闻 翻译