用CompositionTarget_Rendering实现固定时间间隔定时器
Silverlight提供了大致五种定时方式,
这篇文章比较了各种定时方式的特点和应用环境。限于篇幅,本文就不赘述了。本文主要讨论实现游戏动画的定时器应用,当然还是针对正在开发的MMORPG游戏。 一般来说,除非是很特殊很复杂的应用,storyboard,dispatcher timer和CompositionTarget_Rendering三种方式就足够应付了。
简单说来,storyboard动画的实质就是控制UIElement的属性随时间的变化来实现动画效果。因为silverlight控制所有内部变化细节,动画效果比较平滑。相比之下,dispatcher timer来实现相同的动画,就需要多一些代码来控制了。不过有些动画无法用UIElement的属性变化来体现,也就不能直接用storyboard来实现,这时候就需要考虑dispatcher timer了。dispatcher timer其实跟普通定时器(例如Thread.Timer)没有太大差别,唯一特殊的是dispatcher timer和UI运行在同一个线程,所以比较适合实现UI动画。总的来说,几种定时方式并无孰优孰劣之分,只有使用场合的不同。
本文重点讨论CompositionTarget_Rendering定时方式来实现动画。为什么青睐于CompositionTarget_Rendering呢?可能读者已经知道CompositionTarget_Rendering的事件在UI即将提交的时候触发,与silverlight应用程序的帧频是一致的。换句话说,无论动画理论上变化多快,帧频多高,最终反映到计算机显示器上,帧频是不可能高于silverlight应用程序的最大帧频的。也就是说,很多帧只是在内存中闪了一下,并没有机会显示在屏幕上。可以想象这是多么大的浪费,尽管没有验证过,相信性能上会受影响。因为CompositionTarget_Rendering的事件在UI即将提交的时候触发,此时来决定需要显示的帧,避免了资源浪费。尤其在silverlight应用程序的最大帧频设置变化的情况,CompositionTarget_Rendering可以自适应的调整动画的帧频,保证最大限度的节省资源消耗。
CompositionTarget_Rendering严格讲不是一个定时器,因为我们不能控制触发的时间间隔。因为CompositionTarget_Rendering事件的触发由silverlight应用程序的帧频决定,而silverlight应用程序的帧频由最大帧频设置和硬件环境等因素决定,所以CompositionTarget_Rendering事件的触发的时间间隔是不固定的,大致上等于(1000/最大帧频设置)毫秒。对于动画控制,变化的时间间隔是不能接受的。所以我们需要模拟一个固定时间间隔的CompositionTarget_Rendering定时器。具体看下面的代码。
public abstract class MainLoop
{
protected DateTime lastTick;
public delegate void UpdateHandler(object sender, int elapsed);
public event UpdateHandler Timeout;
public void Tick()
{
DateTime now = DateTime.Now;
var elapsed = (now - lastTick).Milliseconds;
lastTick = now;
if (Timeout != null) Timeout(this, elapsed);
}
public virtual void Start()
{
lastTick = DateTime.Now;
}
public virtual void Stop()
{
}
}
MainLoop类是抽象基类,定义公共的方法和事件接口。CompositionTargetMainLoop类继承并实现MainLoop类。当然也可以只用一个类来实现。用基类和具体类来实现的好处是可以用不同的方法(例如dispatcher timer)来实现该基类,但是保持相同的接口,便于不同定时方式实现之间的互换。
该定时器本身并不提供固定时间间隔的定时,实际上定时的事件总是和UI的帧频保持一致的,由前面的讨论可知,该时间间隔受最大帧频设置,硬件配置,运行环境的因素影响而变化,是不确定的。解决的办法是在每次定时事件中提供距离上次定时事件的时间间隔,然后由使用该定时器的类来计算模拟固定时间间隔。代码如下。
void Instance_Timeout(object sender, int elapsed)
{
_lastElapsed += elapsed;
if (_lastElapsed >= _timerlInterval)
{
while (_lastElapsed >= _timerlInterval)
_lastElapsed -= _timerlInterval;
...
}
}
阅读(1914) | 评论(0) | 转发(0) |