カジュアルメカニクス

habrahabrでは、3Dグラフィックスの実施からネットワークプロトコルの作成まで、さまざまな側面からゲーム作成プロセスを説明する試みが定期的に行われています。 これらのトピックは確かに重要ですが、かなり狭いものです。 この記事では、より広範なアプローチを使用してみます-いわゆるゲームエンジンを作成する原理を検討します カジュアルゲーム。 説明されたメカニズムは、あらゆる種類のパックマン、アルカノイド、プラットフォーマーなどを作成するのに非常に適しています。プロセスの説明は、原始的なスクロールダウンシューティングゲームの例になります( ZybexXeviousのノスタルジックな感情から)-私たちはフィールドを飛び回り、fly をノックします。 ツール-Qt。



コードに美しさと完全性がないことを直ちに予約してください。 クラスは原始的であり、コードを繰り返します。機能は最適ではありません。グラフィックスはいものではありませんが、パフ、トス、ターンします。 これは、さらに作業を進めるための基礎です。 経験豊富なプログラマー-一杯の熱い何かをめくって、トピックを開始したり、トピックに注いだりすることは、思考の糧になるかもしれません。 始めます。



適用サイクル



プログラムを編成する正しい方法を選択するには、メインサイクルの参加者を決定する必要があります。 カジュアル(およびほとんどの非カジュアル)ゲームには、少なくとも3つあります。



他のポイントも可能です-アニメーションマネージャー、人工知能など。たとえば、これら3つで十分です。



どんな参加者ですか?



ゲームプレイクロックはタイマーに関連付けられています...えーと...ゲームプレイクロックです。 競技場でのオブジェクトの動きを制御します。 その主な目的は、ゲームプロセスとそのアイデンティティの整合性を確保することです。 これは非常に重要です-ゲームの速度がコンピューターのパフォーマンスに依存しないようにするためだけでなく、ネットワークでプレイするときに同期を確保するためにも。



連続性シミュレーターは補助機能であり、その主な目的は、ゲームジェネレーターの呼び出しの間に重要なことが発生しないようにすることです。 たとえば、次のようなゲームの瞬間を考えてみましょう。

画像



左右、ゲームプレイクロックの2つの連続した呼び出しが表示されます。 黄色の円の速度= 3であるとします。図からわかるように、円と長方形の間の距離= 2です。円と長方形が助けられなければ衝突しないことがわかります。 連続性シミュレーターもこの支援を提供します。



シーンのレンダリング -ここではすべてが明らかなようです。 次の理由から、これは別個のアイテムです。



ループを整理する可能な方法



すぐに、参加者ごとに個別のストリームを作成することになります。 ただし、次の理由により、このアプローチは最適ではありません。



したがって、スレッドを実行しますが、わずかに異なる方法で-OSからのメッセージを処理するための個別のスレッド(レンダリング、キーボードポーリング)、および3人の参加者全員が呼び出されるゲームプロセスのための個別のスレッド。

キーボードのレンダリングとポーリングにより、すべてが明確になります。これは、アプリケーションのメインフォームの単なるストリームです。 ゲームプレイの流れを扱います。



ゲームストリーム



フロー構造を図に示します。

画像



今、説明付きの小さなコード。

まず、周波数を設定します。 大まかに言えば、処理ロジックの呼び出しとレンダリングの間に何ミリ秒を渡す必要があります:

ソースをコピー | HTMLをコピー
  1. //クロック:FREQ-ロジック、FPS-レンダリング
  2. const int FREQ = 1000/40; // 1000 / FPS
  3. const int MAX_FPS = 1000/180;




そして、ここにメインループコードがあります-すべての参加者への呼び出し、時間のチェックなど。

ソースをコピー | HTMLをコピー
  1. while( true
  2. {
  3. qint64 time_cur_tick = QDateTime :: currentMSecsSinceEpoch();
  4. int numLoops = 0 ;
  5. bool ft = true;
  6. while( time_prev_tick <time_cur_tick && numLoops <MAX_LOOPS
  7. {
  8. //呼び出しロジック
  9. w-> UpdateLogic( 1 / FREQ );
  10. numLoops ++;
  11. if( ft
  12. {
  13. ft = false;
  14. last_freq = time_cur_tick;
  15. }
  16. time_prev_tick + = FREQ;
  17. //より正確なタイミングのためにtime_cur_tickを更新します
  18. time_cur_tick = QDateTime :: currentMSecsSinceEpoch();
  19. }
  20. time_tmp = QDateTime :: currentMSecsSinceEpoch();
  21. w-> SimulateConsistLogic( (float )( time_tmp-last_freq )/ FREQ);
  22. time_tmp = QDateTime :: currentMSecsSinceEpoch();
  23. if( time_tmp-time_lastrender> = MAX_FPS &&

    w-> paint_mx.tryLock( ))
  24. {
  25. time_lastrender = time_tmp;
  26. float freq_bit = 0 ;
  27. if( time_tmp!= last_freq
  28. freq_bit =( float )( time_tmp-last_freq )/ FREQ;
  29. signalGUI( freq_bit )を発行します。
  30. w-> paint_mx.unlock();
  31. }
  32. }




(注-ソースコードを見ると、すべてが少し複雑になっています。1秒あたりのフレーム数のカウント、バズした情報の出力などがあります。)



確かに疑問が生じました-レンダリングおよび連続性シミュレーターは、ゲームロジックの最後の更新から経過した時間を知っていたのはなぜですか? すべてが簡単です-シーンの瞬間的な状態を計算し、正しく処理して画面に表示するため。 リソースを節約するために、連続性シミュレーターを呼び出すと、最後の呼び出しの時刻も転送できます。



すべての仕組み



この例では、3種類のオブジェクトがあります。



対応するクラスが作成されます(CShip、CBullet、CMeteorite)。 弾丸とmet石の場合、QVectorストレージコンテナーが指定されます。

ユーザー入力を処理するために、「移動の方向」の配列が作成され、関数keyReleaseEventおよびkeyPressEventが再定義されました。

keyReleaseEventは、押されたキーの配列にリリースされたキーがあるかどうかを確認し、キーがある場合は削除します。

keyPressEventは、それに応じて、押されたキーの配列に押されたキーを配置します(存在しない場合)。 この配列の処理は、ゲームプロセスのクロックジェネレーターの機能で行われます。 ゲームの移動オブジェクト、船の移動中の慣性の計算、met石の作成があります:

ソースをコピー | HTMLをコピー
  1. void MainWindow :: UpdateLogic( float ftime
  2. {
  3. フロート速度= 2 ;
  4. for( int i = 0; i <m_dir.size( ); i ++)
  5. {
  6. if( m_dir [i] == MainWindow :: UP
  7. actor1.adjust DirectionQVector2D(0、-speed ));
  8. if( m_dir [i] == MainWindow :: DOWN
  9. actor1.adjust DirectionQVector2D(0、speed ));
  10. if( m_dir [i] == MainWindow :: LEFT
  11. actor1.adjust DirectionQVector2D(-speed、0 ));
  12. if( m_dir [i] == MainWindow :: RIGHT
  13. actor1.adjust DirectionQVector2D(speed、0 ));
  14. if( m_dir [i] == MainWindow :: SPACE &&

    m_allowbullet == 0
  15. {
  16. m_bullets.push_back( CBullet(actor1.getX( )、actor1.getY()- 1 、QVector2D( 0、-15 )));
  17. qDebug( QString( "追加された箇条書き 。Pos %1" ).arg( m_bullets.size( )- 1 ).toAscii());
  18. m_allowbullet = 5 ;
  19. 解雇++;
  20. }
  21. }
  22. actor1.step Direction ();
  23. bool dir_touched = false;
  24. for( int i = 0; i <m_dir.size( ); i ++)
  25. {
  26. if( m_dir [i]!= MainWindow :: SPACE
  27. {
  28. dir_touched = true;
  29. 休憩;
  30. }
  31. }
  32. if( !dir_touched
  33. {
  34. m_allowmove = 0 ;
  35. フロート慣性= 05 ;
  36. if( actor1.getSpeed( )< 0。5
  37. 慣性= 1 ;
  38. actor1.adjustSpeed( 慣性 );
  39. }
  40. for( int i = 0; i <m_bullets.size( ); i ++)
  41. m_bullets [ i ] .step Direction ();
  42. for( int x = 0; x <m_enemies1.size( ); x ++)
  43. m_enemies1 [x] .step Direction ();
  44. CheckGameRules();
  45. if( m_enemies1.size( )<max_enemies)
  46. {
  47. CMeteorite meteo( mrand(field_ident + CMeteorite :: meteo_size、

    field_ident + field_w-CMeteorite :: meteo_size )、
  48. -mrand(0、20)、
  49. QVector2D( 0、1 ));
  50. while( true
  51. {
  52. int i = 0 ;
  53. while( i <m_enemies1.size( ))
  54. {
  55. if( meteo.getBoundsT( ).intersects( m_enemies1 [i] .getBoundsT( )))
  56. 休憩;
  57. i ++;
  58. }
  59. if( i == m_enemies1.size( ))
  60. 休憩;
  61. meteo = CMeteorite(mrand( 1、100 )、-mrand(0、20)、
  62. QVector2D( 0、1 ));
  63. }
  64. m_enemies1.push_back( meteo );
  65. }
  66. UpdateBullet();
  67. }


CheckGameRules関数は、ゲームルールをチェックします-誰が誰かに衝突したか、何を超えたかなどです。 ところで、2Dでは、これはすべてQPolygon、QRectクラス、およびそれらのような他の機能によって非常に便利に実行されます。



ソースをコピー | HTMLをコピー
  1. void MainWindow :: CheckGameRules( const float ftime
  2. {
  3. QRect field_rect( field_ident、field_ident、

    field_w

    field_h );
  4. for( int i = 0; i <m_bullets.size( ); i ++)
  5. {
  6. CBullet blt = m_bullets [ i ];
  7. float tx = 0 、ty = 0 ;
  8. blt.getTickCoords( ftime、tx、ty );
  9. blt.setX( tx );
  10. blt.setY( ty );
  11. if( !field_rect.contains(m_bullets [i] .getX( )、m_bullets [ i ] .getY()))
  12. {
  13. m_bullets.remove( i-- );
  14. }
  15. 他に
  16. {
  17. for( int j = 0; j <m_enemies1.size( ); j ++)
  18. {
  19. CMeteorite enm = m_enemies1 [j];
  20. フロート etx = 0 、ety = 0 ;
  21. enm.getTickCoords( ftime、etx、ety );
  22. enm.setX( etx );
  23. enm.setY(ety);
  24. if( blt.checkCollision(enm.getBodyT( )))
  25. {
  26. m_enemies1.remove( j-- );
  27. m_bullets.remove( i-- );
  28. スコア++;
  29. 休憩;
  30. }
  31. } // for
  32. }
  33. }
  34. for( int j = 0; j <m_enemies1.size( ); j ++)
  35. {
  36. CMeteorite enm = m_enemies1 [j];
  37. if( !field_rect.contains(enm.getBoundsT( ))&&
  38. field_rect。 bottomRight ().y()<enm.getBoundsT()。 topLeft (). y ())
  39. {
  40. m_enemies1.remove( j-- );
  41. }
  42. if( actor1.checkCollision(enm.getBodyT( )))
  43. {
  44. m_enemies1.remove( j-- );
  45. ヒット++;
  46. }
  47. }
  48. if( !field_rect.contains(actor1.getBoundsT( )、true))
  49. {
  50. while( field_rect.x( )> = actor1.getBoundsT()。 left ())
  51. actor1.setX( actor1.getX( )+ 1 );
  52. while( field_rect.x( )* 2 + field_rect。width()<= actor1.getBoundsT()。x()+ actor1.getBoundsT()。 width ())
  53. actor1.setX( actor1.getX( )- 1 );
  54. while( field_rect.top( )> = actor1.getBoundsT()。 top ())
  55. actor1.setY( actor1.getY( )+ 1 );
  56. while( field_rect.y( )* 2 + field_rect。height()<= actor1.getBoundsT()。y()+ actor1.getBoundsT()。 height ())
  57. actor1.setY( actor1.getY( )- 1 );
  58. actor1.s top ();
  59. }
  60. }




したがって、連続性のシミュレーターの呼び出しは不名誉に簡単です。 わずかなステップで、ゲームロジックを確認します。

ソースをコピー | HTMLをコピー
  1. void MainWindow :: SimulateConsistLogic( float ftime
  2. {
  3. for( float bt = 0; bt <ftime; bt = bt + 0.1
  4. {
  5. CheckGameRules( bt );
  6. }
  7. }




レンダリングは、ゲームフィールドを描画し、ゲームジェネレータークロックの最後の呼び出しからの現在のインデントのパラメーターを使用して、すべてのオブジェクトのDraw()を呼び出します。 サービス情報のプラス出力:

ソースをコピー | HTMLをコピー
  1. void MainWindow :: Render()
  2. {
  3. QPainter qpainter( これ );
  4. const int bgw = 2 ;
  5. qpainter.setPen( QPen(Qt :: black、bgw ));
  6. qpainter.setBrush( QBrush(Qt :: darkGray ));
  7. qpainter.drawRect( field_ident、field_ident、

    field_w + field_ident、

    field_h + field_ident );
  8. for( int i = 0; i <m_bullets.size( ); i ++)
  9. {
  10. CBullet blt = m_bullets [ i ];
  11. blt.Draw( qpainter、freq_bit );
  12. }
  13. for( int i = 0; i <m_enemies1.size( ); i ++)
  14. {
  15. CMeteorite enm = m_enemies1 [ i ];
  16. enm.Draw( qpainter、freq_bit );
  17. }
  18. actor1.Draw( qpainter、freq_bit );
  19. QPalette pal;
  20. qpainter.setBrush( pal.brush(QPalette :: Window ));
  21. qpainter.setPen( QPen(pal.color(QPalette :: Window )、 1 ));
  22. qpainter.drawRect( field_ident-bgw / 2、0、

    field_w + field_ident + bgw / 2

    field_ident-bgw );
  23. qpainter.setPen( QPen(Qt :: black、bgw ));
  24. qpainter.setBrush( QBrush(Qt :: darkGray、Qt :: NoBrush ));
  25. qpainter.drawRect( field_ident、field_ident、

    field_w + field_ident、

    field_h + field_ident );
  26. ui-> label_freq-> setText( QString( "%1" ).arg( freq ).toAscii());
  27. ui-> label_fps-> setText( QString( "%1" ).arg( fps ).toAscii());
  28. ui-> label_speed-> setText( QString( "%1" ).arg( actor1.getSpeed( )).toAscii());
  29. ui-> label_score-> setText( QString( "%1" ).arg( score ).toAscii());
  30. ui-> label_fired-> setText( QString( "%1" ).arg(発生).toAscii());
  31. ui-> label_hits-> setText( QString( "%1" ).arg( ヒット ).toAscii());
  32. }




実際、残りは簡単なプログラミングです。 アプリケーションスケルトンは逆アセンブルされ、実装の詳細は添付のソースコードに記載されています。 結果-私が得たものの外観:

画像

ソースはこちら 。 矢で飛んで、スペースで撃ちます。

githubのソース



All Articles