RectMake(64, 0, 64, 64)];
34 [self addCrateWithFrame:CGRectMake(128, 0, 32, 32)];
35 [self addCrateWithFrame:CGRectMake(0, 32, 64, 64)];
36 //start the timer
37 self.lastStep = CACurrentMediaTime();
38 self.timer = [CADisplayLink displayLinkWithTarget:self
39 selector:@selector(step:)];
40 [self.timer addToRunLoop:[NSRunLoop mainRunLoop]
41 forMode:NSDefaultRunLoopMode];
42 //update gravity using accelerometer
43 [UIAccelerometer sharedAccelerometer].delegate = self;
44 [UIAccelerometer sharedAccelerometer].updateInterval = 1/60.0;
45 }
46
47 - (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
48 {
49 //update gravity
50 cpSpaceSetGravity(self.space, cpv(acceleration.y * GRAVITY, -acceleration.x * GRAVITY));
51 }
View Code
图11.1 真实引力场下的木箱交互
模拟时间以及固定的时间步长
对于实现动画的缓冲效果来说,计算每帧持续的时间是一个很好的解决方案,但是对模拟物理效果并不理想。通过一个可变的时间步长来实现有着两个弊端:
-
如果时间步长不是固定的,精确的值,物理效果的模拟也就随之不确定。这意味着即使是传入相同的输入值,也可能在不同场合下有着不同的效果。有时候没多大影响,但是在基于物理引擎的游戏下,玩家就会由于相同的操作行为导致不同的结果而感到困惑。同样也会让测试变得麻烦。
-
由于性能故常造成的丢帧或者像电话呼入的中断都可能会造成不正确的结果。考虑一个像子弹那样快速移动物体,每一帧的更新都需要移动子弹,检测碰撞。如果两帧之间的时间加长了,子弹就会在这一步移动更远的距离,穿过围墙或者是别的障碍,这样就丢失了碰撞。
我们想得到的理想的效果就是通过固定的时间步长来计算物理效果,但是在屏幕发生重绘的时候仍然能够同步更新视图(可能会由于在我们控制范围之外造成不可预知的效果)。
幸运的是,由于我们的模型(在这个例子中就是Chipmunk的cpSpace
中的cpBody
)被视图(就是屏幕上代表木箱的UIView
对象)分离,于是就很简单了。我们只需要根据屏幕刷新的时间跟踪时间步长,然后根据每帧去计算一个或者多个模拟出来的效果。
我们可以通过一个简单的循环来实现。通过每次CADisplayLink
的启动来通知屏幕将要刷新,然后记录下当前的CACurrentMediaTime()
。我们需要在一个小增量中提前重复物理模拟(这里用120分之一秒)直到赶上显示的时间。然后更新我们的视图,在屏幕刷新的时候匹配当前物理结构体的显示位置。
清单11.5展示了固定时间步长版本的代码
清单11.5 固定时间步长的木箱模拟
避免死亡螺旋
当使用固定的模拟时间步长时候,有一件事情一定要注意,就是用来计算物理效果的现实世界的时间并不会加速模拟时间步长。在我们的例子中,我们随意选择了120分之一秒来模拟物理效果。Chipmunk很快,我们的例子也很简单,所以cpSpaceStep()
会完成的很好,不会延迟帧的更新。
但是如果场景很复杂,比如有上百个物体之间的交互,物理计算就会很复杂,cpSpaceStep()
的计算也可能会超出1/120秒。我们没有测量出物理步长的时间,因为我们假设了相对于帧刷新来说并不重要,但是如果模拟步长更久的话,就会延迟帧率。
如果帧刷新的时间延迟的话会变得很糟糕,我们的模拟需要执行更多的次数来同步真实的时间。这些额外的步骤就会继续延迟帧的更新,等等。这就是所谓的死亡螺旋,因为最后的结果就是帧率变得越来越慢,直到最后应用程序卡死了。
我们可以通过添加一些代码在设备上来对物理步骤计算真实世界的时间,然后自动调整固定时间步长,但是实际上它不可行。其实只要保证你给容错留下足够的边长,然后在期望支持的最慢的设备上进行测试就可以了。如果物理计算超过了模拟时间的50%,就需要考虑增加模拟时间步长(或者简化场景)。如果模拟时间步长增加到超过1/60秒(一个完整的屏幕更新时间),你就需要减少动画帧率到一秒30帧或者增加CADisplayLink
的frameInterval
来保证不会随机丢帧,不然你的动画将会看起来不平滑。