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(強調鉱山):

タイマーはリアルタイムのメカニズムではありません。タイマーが追加された実行ループ モードの 1 つが実行中で、タイマーの起動時間が経過したかどうかを確認できる場合にのみ起動します。典型的な実行ループが管理するさまざまな入力ソースのため、タイマーの時間間隔の有効な分解能は 50 ~ 100 ミリ秒程度に制限されます。長いコールアウト中、または実行ループがタイマーを監視していないモードにある間にタイマーの起動時間が発生した場合、実行ループが次にタイマーをチェックするまで、タイマーは起動しません。したがって、タイマーが起動する実際の時間は、スケジュールされた起動時間よりもかなり長い時間になる可能性があります

図面を最適化する準備ができている場合、問題を解決する最善の方法はCADisplayLink、他の人が述べたように、を使用することです。これCADisplayLinkは iOS の特別な「タイマー」で、画面のリフレッシュ レートの (可能な) 分割でコールバック メッセージを実行します。これにより、アニメーションの更新を画面の更新と同期させることができます。このコールバックはメイン スレッドで実行されます。注: この機能は、複数のディスプレイが存在する可能性がある OS X ではあまり便利ではありません ( CVDisplayLink)。

したがって、表示リンクを作成することから始めて、そのコールバックで、アニメーションと描画関連のタスクを処理する作業を実行できます (必要な-setNeedsDisplayInRect:更新を実行するなど)。作業が非常に高速で、レンダリングも高速であることを確認してください。そうすれば、高いフレーム レートを達成できるはずです。このコールバックでの低速な操作は避けてください (ファイル io やネットワーク リクエストなど)。

最後に 1 つ: 私は、実行ループ (さまざまな頻度で実行するなど) に多くのタイマーをインストールするのではなく、タイマー同期コールバックを 1 つのメタコールバックにクラスター化する傾向があります。現時点でどの更新を実行するかを実装ですばやく判断できる場合、インストールするタイマーの数を大幅に (正確に 1 つに) 減らすことができます。

于 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 に答える