4

我正在开发一个 iPhone 游戏,并且我有一个NSTimer可以为屏幕上的所有对象设置动画:

EverythingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 target:self selector:@selector(moveEverything) userInfo:nil repeats:YES];

大多数时候它运行得非常顺利,但有时我会看到事情进展缓慢或波涛汹涌。我有暂停和恢复功能,分别停止和启动计时器。当我暂停然后取消暂停时,它似乎可以解决波动问题。

关于为什么会发生这种情况的任何想法?或者我该如何解决?

4

3 回答 3

6

关于为什么会发生这种情况的任何想法?

简短的回答是,在您的情况下,您正在执行基于非同步推送模型的动作(动画),并且您使用的机制不适合您正在执行的任务。

补充说明:

  • NSTimer分辨率低。
  • 您的工作在主运行循环中执行。计时器可能不会在您期望的时候触发,因为在该线程上工作时可能会花费很多时间,这会阻止您的计时器触发。进程中的其他系统活动甚至线程可以使这种变化更大。
  • 不同步的更新可能会导致大量不必要的工作或不稳定,因为您在回调中执行的更新是在它们发生后的某个时间发布的。这为更新的时间准确性增加了更多变化。当更新不同步时,很容易导致丢帧或执行大量不必要的工作。在优化绘图之前,此成本可能并不明显。

NSTimer文档之外(强调我的):

计时器不是实时机制;它仅在已添加计时器的运行循环模式之一正在运行并且能够检查计时器的触发时间是否已过时触发。由于典型的运行循环管理的各种输入源,计时器的时间间隔的有效分辨率被限制在 50-100 毫秒的数量级。如果计时器的触发时间发生在长时间调用期间或运行循环处于不监视计时器的模式下,则计时器不会触发,直到运行循环下次检查计时器。因此,定时器触发的实际时间可能是计划触发时间之后的一段重要时间

解决问题的最佳方法,如果您准备好也优化您的绘图 - 是使用CADisplayLink,正如其他人提到的那样。这CADisplayLink是 iOS 上的一个特殊“计时器”,它以屏幕刷新率的(有能力的)划分执行您的回调消息。这使您可以将动画更新与屏幕更新同步。此回调在主线程上执行。注意:这个功能在 OS X 上不太方便,因为 OS X 可能存在多个显示器 ( CVDisplayLink)。

因此,您可以从创建显示链接开始,并在其回调中执行处理动画和绘图相关任务的工作(例如执行任何必要的-setNeedsDisplayInRect:更新)。确保您的工作非常快,并且您的渲染也很快——然后您应该能够实现高帧速率。避免此回调中的缓慢操作(例如文件 io 和网络请求)。

最后一点:我倾向于将我的计时器同步回调集中到一个 Meta-Callback 中,而不是在运行循环上安装许多计时器(例如以各种频率运行)。如果您的实现可以快速确定当前要执行哪些更新,那么这可以显着减少您安装的计时器数量(精确到一个)。

于 2013-09-03T05:47:54.903 回答
2

NSTimers 不能保证完全按照您要求的速度运行。如果您正在开发游戏,则应该调查CADisplayLink,它实现了与 NSTimer 大致相同的接口,但在每次屏幕重绘时都会触发。

您可以像计时器一样设置显示链接,只是您不必指定刷新周期。

CADisplayLink* displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(moveEverything)];
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

最重要的是,您应该测量自上次迭代以来经过的时间量。不要依赖它恰好是 1/30 秒或 1/60 秒。如果你有这样的代码

thing.origin.x += thing.velocity * 1/30.0;

改成这个

NSTimeInterval timeSince = [displayLink duration];
thing.origin.x += thing.velocity * timeSince;

如果您出于制作游戏以外的原因需要高精度计时器,请参阅技术说明 TN2169

于 2013-09-03T05:22:35.223 回答
1

正如 Hot Licks 所建议的那样,30 Hz 相当快,所以根据你在里面做什么moveEverything,你可能只是压倒了手机的处理器。您没有显示moveEverything,因此我们无法真正评论里面的内容......但是,您可能需要努力使该方法更有效,或者减慢计时器触发的速度。

此外,您NSTimer有时可能根本没有触发,因为尚未为所有正确的运行循环模式添加计时器。

例如,当滚动视图滚动(性能密集型操作)时,UI 处于跟踪模式。默认情况下,按照您创建计时器的方式,计时器在跟踪模式下不会触发。您可以使用以下内容更改此设置:

EverythingTimer = [NSTimer scheduledTimerWithTimeInterval:1.0/30.0 
                                                   target:self 
                                                 selector:@selector(moveEverything) 
                                                 userInfo:nil 
                                                  repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:EverythingTimer forMode:NSRunLoopCommonModes];
于 2013-09-03T05:23:08.543 回答