概要
私は、iOS 向けのかなり単純な 2D タワー ディフェンス ゲームに取り組んでいます。
これまでは、Core Graphics だけをレンダリングの処理に使用してきました。アプリには(まだ)画像ファイルはまったくありません。比較的単純な描画を行っていると、いくつかの重大なパフォーマンスの問題が発生しました。OpenGL に移行する前に、これを修正する方法についてのアイデアを探しています。
ゲームのセットアップ
UIView
大まかに言えば、ゲーム ボードを表すBoard クラスがあります。これは のサブクラスです。ゲーム内の他のすべてのオブジェクト (タワー、クリープ、武器、爆発など) も のサブクラスでありUIView
、作成時にサブビューとしてボードに追加されます。
オブジェクト内のビュー プロパティからゲームの状態を完全に分離し、各オブジェクトの状態をメインのゲーム ループ (NSTimer
ゲームの速度設定に応じて 60 ~ 240 Hz で起動) で更新します。ビューを描画、更新、またはアニメーション化することなく、ゲームは完全にプレイ可能です。
CADisplayLink
ネイティブ リフレッシュ レート (60 Hz) でタイマーを使用してビューの更新を処理します。これsetNeedsDisplay
は、ゲーム ステートの変化に基づいてビュー プロパティを更新する必要があるボード オブジェクトを呼び出します。ボード上のすべてのオブジェクトをオーバーライドdrawRect:
して、フレーム内に非常に単純な 2D シェイプをペイントします。たとえば、武器がアニメートされると、武器の新しい状態に基づいて再描画されます。
パフォーマンスの問題
ボード上に合計約 20 個のゲーム オブジェクトがある iPhone 5 でテストすると、フレーム レートは 60 FPS (目標フレーム レート) を大幅に下回り、通常は 10 ~ 20 FPS の範囲になります。画面上でのアクションが増えると、ここから下り坂になります。iPhone 4 では、事態はさらに悪化します。
インストゥルメントの使用 実際にゲームの状態を更新するために費やされているのは、CPU 時間の約 5% だけであることがわかりました。その大部分はレンダリングに費やされています。具体的には、CGContextDrawPath
関数 (私の理解では、ベクター パスのラスタライズが行われる場所です) は、膨大な量の CPU 時間を消費しています。詳細については、下部にあるインストゥルメントのスクリーンショットを参照してください。
StackOverflow や他のサイトに関するいくつかの調査によると、Core Graphics は私が必要としているタスクに対応していないようです。どうやら、ベクター パスのストロークは非常にコストがかかるようです (特に、不透明ではなく、アルファ値が 1.0 未満のものを描画する場合)。私は OpenGL が私の問題を解決してくれるとほぼ確信していますが、それはかなり低レベルであり、それを使用しなければならないことにあまり興奮していません。
質問
Core Graphics からスムーズな 60 FPS を引き出すために検討すべき最適化はありますか?
いくつかのアイデア...
誰かが、CALayer
各オブジェクトを個別に持つのではなく、すべてのオブジェクトを 1 つのオブジェクトに描画することを検討することを提案しましCALayer
たが、Instruments が示している内容に基づいて、これが役立つとは確信していません。
個人的にCGAffineTransforms
は、アニメーションを実行するために使用する理論があります(つまり、オブジェクトの形状をdrawRect:
一度に描画し、その後のフレームでレイヤーを移動/回転/サイズ変更するために変換を行います)。 OpenGL。しかし、OpenGLを完全に使用するよりも簡単だとは思いません。
サンプルコード
私が行っている描画のレベルを感じてもらうためdrawRect:
に、武器オブジェクトの 1 つ (タワーから発射される「ビーム」) の実装例を次に示します。
注: このビームは「再ターゲット」することができ、ボード全体を横切るため、簡単にするために、そのフレームはボードと同じ寸法になっています。ただし、ボード上の他のほとんどのオブジェクトのフレームは、可能な限り最小の外接長方形に設定されています。
- (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);
}
楽器の実行
ゲームをしばらく実行させた後のインストゥルメントの外観を次に示します。
このTDGreenBeam
クラスには、drawRect:
上記のサンプル コード セクションに示されている正確な実装があります。