概括
我正在为iOS开发一款非常简单的2D塔防游戏。
到目前为止,我一直在专门使用Core Graphics来处理渲染。该应用程序中根本没有图像文件(尚未)。在进行相对简单的绘制时,我一直遇到一些重大的性能问题,并且我一直在寻找有关如何解决此问题的想法,而不是转向OpenGL。
游戏设定
在较高的层次上,我有一个Board类,它是UIView
的子类,用来表示游戏板。游戏中的所有其他对象(塔,小兵,武器,爆炸等)也是UIView
的子类,并且在创建时作为 subview 添加到Board中。
我将游戏状态与对象内的 View 属性完全分开,并且每个对象的状态在主游戏循环中更新(由NSTimer
以60-240 Hz的频率触发,具体取决于游戏速度设置)。该游戏完全可玩,无需绘制,更新或设置 View 动画。
我使用CADisplayLink
计时器以 native 刷新率(60 Hz)处理 View 更新,该计时器在需要根据游戏状态变化更新其 View 属性的棋盘对象上调用setNeedsDisplay
。板上的所有对象都覆盖drawRect:
,以在其框架中绘制一些非常简单的2D形状。因此,例如,对某件武器进行动画处理时,它将根据该武器的新状态重新绘制自身。
性能问题
在iPhone 5上进行测试,板上总共有大约2打游戏对象,帧速率明显低于60 FPS(目标帧速率),通常降至10-20 FPS范围内。随着屏幕上的更多 Action ,它从这里下坡。在iPhone 4上,情况甚至更糟。
通过使用Instruments,我已经确定只有5%的CPU时间用于实际更新游戏状态-绝大部分时间都用于渲染。具体来说,CGContextDrawPath
函数(据我所知是矢量路径的栅格化是在哪里完成的)占用了大量CPU时间。有关更多详细信息,请参见底部的Instruments屏幕截图。
从对StackOverflow和其他站点的一些研究来看,似乎Core Graphics不能完全满足我的需求。显然,抚摸矢量路径非常昂贵(尤其是在绘制不透明且alpha值<1.0的东西时)。我几乎可以确定OpenGL可以解决我的问题,但是它的水平很低,我对使用它并不感到非常兴奋-看来我在这里所做的事情似乎没有必要。
问题
我是否应该进行一些优化以尝试使Core Graphics达到60 FPS的平滑度?
一些想法...
有人建议我考虑将所有对象绘制到单个CALayer
上,而不是将每个对象放在自己的CALayer
上,但是我不认为这会根据Instruments的显示而有所帮助。
就我个人而言,我有一个理论,即使用CGAffineTransforms
进行动画处理(即在drawRect:
中绘制对象的形状一次,然后进行变换以在后续帧中移动/旋转/调整其图层的大小)将解决我的问题,因为这些基于直接在OpenGL上。但是我认为,做到这一点不会比完全使用OpenGL更容易。
样例代码
为了让您感觉到我正在做的绘图水平,以下是我的一个武器对象(从塔发射的“光束”)的drawRect:
实现的示例。
注意:该光束可以“重新定向”,并横穿整个电路板,因此,为简单起见,其框架与电路板的尺寸相同。但是,板上的大多数其他对象的框架都设置为可能的最小外接矩形。
- (void)drawRect:(CGRect)rect
{
CGContextRef c = UIGraphicsGetCurrentContext();
// Draw beam
CGContextSetStrokeColorWithColor(c, [UIColor greenColor].CGColor);
CGContextSetLineWidth(c, self.width);
CGContextMoveToPoint(c, self.origin.x, self.origin.y);
CGPoint vector = [TDBoard vectorFromPoint:self.origin toPoint:self.destination];
double magnitude = sqrt(pow(self.board.frame.size.width, 2) + pow(self.board.frame.size.height, 2));
CGContextAddLineToPoint(c, self.origin.x+magnitude*vector.x, self.origin.y+magnitude*vector.y);
CGContextStrokePath(c);
}
仪器运行
让游戏运行一会儿后,再来看一下Instruments:
TDGreenBeam
类具有上面“示例代码”部分中所示的drawRect:
实现。 Full Size Screenshot
最佳答案
核心图形工作由CPU执行。然后将结果推送到GPU。当您调用setNeedsDisplay
时,表明需要重新进行绘图工作。
假设您的许多对象保持一致的形状并且只是移动或旋转,则只需在父 View 上调用setNeedsLayout
,然后将该 View 的layoutSubviews
中的最新对象位置推送到center
属性即可。仅调整位置就不会导致需要重画任何东西。合成器将只要求GPU在不同的位置重现它已经具有的图形。
对于游戏而言,更通用的解决方案可能是忽略center
,bounds
和frame
,而不是进行初始设置。只需将您想要的仿射变换推送到transform
,可能是使用these helpers的某种组合创建的。这样一来,您就可以在无需CPU干预的情况下灵活地重新定位,旋转和缩放对象,而这一切都将由GPU来完成。
如果您想要更多的控制权,则每个 View 都有一个带有自己的CALayer
的affineTransform
,但它们也有一个与子图层的转换结合在一起的sublayerTransform
。因此,如果您对3d如此感兴趣,那么最简单的方法是将合适的透视图矩阵作为sublayerTransform
加载到超层上,然后将合适的3d变换推送到子层或 subview 。
这种方法有一个明显的缺点,即如果您绘制一次然后按比例放大,您将能够看到像素。您可以预先调整图层的contentsScale
尝试对此进行改善,但是否则,您只会看到允许GPU进行合成的自然结果。如果要在线性过滤和最接近过滤之间切换,则图层上有一个magnificationFilter
属性。线性是默认设置。
关于iphone - iOS上的核心图形性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13277031/