コードに美しさと完全性がないことを直ちに予約してください。 クラスは原始的であり、コードを繰り返します。機能は最適ではありません。グラフィックスは
適用サイクル
プログラムを編成する正しい方法を選択するには、メインサイクルの参加者を決定する必要があります。 カジュアル(およびほとんどの非カジュアル)ゲームには、少なくとも3つあります。
- ゲームプレイクロック
- 連続性シミュレーター
- シーンのレンダリング
他のポイントも可能です-アニメーションマネージャー、人工知能など。たとえば、これら3つで十分です。
どんな参加者ですか?
ゲームプレイクロックはタイマーに関連付けられています...えーと...ゲームプレイクロックです。 競技場でのオブジェクトの動きを制御します。 その主な目的は、ゲームプロセスとそのアイデンティティの整合性を確保することです。 これは非常に重要です-ゲームの速度がコンピューターのパフォーマンスに依存しないようにするためだけでなく、ネットワークでプレイするときに同期を確保するためにも。
連続性シミュレーターは補助機能であり、その主な目的は、ゲームジェネレーターの呼び出しの間に重要なことが発生しないようにすることです。 たとえば、次のようなゲームの瞬間を考えてみましょう。
左右、ゲームプレイクロックの2つの連続した呼び出しが表示されます。 黄色の円の速度= 3であるとします。図からわかるように、円と長方形の間の距離= 2です。円と長方形が助けられなければ衝突しないことがわかります。 連続性シミュレーターもこの支援を提供します。
シーンのレンダリング -ここではすべてが明らかなようです。 次の理由から、これは別個のアイテムです。
- ゲームプレイの速度に依存しないでください。
- 画像の滑らかさを確保する必要があります(画面上のオブジェクト速度= 10ポイント、ゲーム頻度= 30で、ゲームプレイジェネレーターが呼び出されたときにのみフレームがレンダリングされる場合、移動するオブジェクトのジャークが表示されます)
ループを整理する可能な方法
すぐに、参加者ごとに個別のストリームを作成することになります。 ただし、次の理由により、このアプローチは最適ではありません。
- ループの参加者間のデフォルトの同期は提供しません。 ネットワークバトルの同期を維持するために、突然レンダリングとアニメーションを犠牲にする必要がありますか?
- 開発が大幅に複雑になり、その結果エラーの数が増えます。 異なるスレッド=異なるリソース、同期と共有の問題、およびその他のマルチスレッドプログラムの利点。
したがって、スレッドを実行しますが、わずかに異なる方法で-OSからのメッセージを処理するための個別のスレッド(レンダリング、キーボードポーリング)、および3人の参加者全員が呼び出されるゲームプロセスのための個別のスレッド。
キーボードのレンダリングとポーリングにより、すべてが明確になります。これは、アプリケーションのメインフォームの単なるストリームです。 ゲームプレイの流れを扱います。
ゲームストリーム
フロー構造を図に示します。
今、説明付きの小さなコード。
まず、周波数を設定します。 大まかに言えば、処理ロジックの呼び出しとレンダリングの間に何ミリ秒を渡す必要があります:
ソースをコピー | HTMLをコピー
- //クロック:FREQ-ロジック、FPS-レンダリング
- const int FREQ = 1000/40; // 1000 / FPS
- const int MAX_FPS = 1000/180;
そして、ここにメインループコードがあります-すべての参加者への呼び出し、時間のチェックなど。
ソースをコピー | HTMLをコピー
- while( true )
- {
- qint64 time_cur_tick = QDateTime :: currentMSecsSinceEpoch();
- int numLoops = 0 ;
- bool ft = true;
- while( time_prev_tick <time_cur_tick && numLoops <MAX_LOOPS )
- {
- //呼び出しロジック
- w-> UpdateLogic( 1 / FREQ );
- numLoops ++;
- if( ft )
- {
- ft = false;
- last_freq = time_cur_tick;
- }
- time_prev_tick + = FREQ;
- //より正確なタイミングのためにtime_cur_tickを更新します
- time_cur_tick = QDateTime :: currentMSecsSinceEpoch();
- }
- time_tmp = QDateTime :: currentMSecsSinceEpoch();
- w-> SimulateConsistLogic( (float )( time_tmp-last_freq )/ FREQ);
- time_tmp = QDateTime :: currentMSecsSinceEpoch();
- if( time_tmp-time_lastrender> = MAX_FPS &&
w-> paint_mx.tryLock( ))- {
- time_lastrender = time_tmp;
- float freq_bit = 0 ;
- if( time_tmp!= last_freq )
- freq_bit =( float )( time_tmp-last_freq )/ FREQ;
- signalGUI( freq_bit )を発行します。
- w-> paint_mx.unlock();
- }
- }
(注-ソースコードを見ると、すべてが少し複雑になっています。1秒あたりのフレーム数のカウント、バズした情報の出力などがあります。)
確かに疑問が生じました-レンダリングおよび連続性シミュレーターは、ゲームロジックの最後の更新から経過した時間を知っていたのはなぜですか? すべてが簡単です-シーンの瞬間的な状態を計算し、正しく処理して画面に表示するため。 リソースを節約するために、連続性シミュレーターを呼び出すと、最後の呼び出しの時刻も転送できます。
すべての仕組み
この例では、3種類のオブジェクトがあります。
- プレイヤーシップ
- 弾丸
- met石
対応するクラスが作成されます(CShip、CBullet、CMeteorite)。 弾丸とmet石の場合、QVectorストレージコンテナーが指定されます。
ユーザー入力を処理するために、「移動の方向」の配列が作成され、関数keyReleaseEventおよびkeyPressEventが再定義されました。
keyReleaseEventは、押されたキーの配列にリリースされたキーがあるかどうかを確認し、キーがある場合は削除します。
keyPressEventは、それに応じて、押されたキーの配列に押されたキーを配置します(存在しない場合)。 この配列の処理は、ゲームプロセスのクロックジェネレーターの機能で行われます。 ゲームの移動オブジェクト、船の移動中の慣性の計算、met石の作成があります:
ソースをコピー | HTMLをコピー
- void MainWindow :: UpdateLogic( float ftime )
- {
- フロート速度= 2 ;
- for( int i = 0; i <m_dir.size( ); i ++)
- {
- if( m_dir [i] == MainWindow :: UP )
- actor1.adjust Direction ( QVector2D(0、-speed ));
- if( m_dir [i] == MainWindow :: DOWN )
- actor1.adjust Direction ( QVector2D(0、speed ));
- if( m_dir [i] == MainWindow :: LEFT )
- actor1.adjust Direction ( QVector2D(-speed、0 ));
- if( m_dir [i] == MainWindow :: RIGHT )
- actor1.adjust Direction ( QVector2D(speed、0 ));
- if( m_dir [i] == MainWindow :: SPACE &&
m_allowbullet == 0 )- {
- m_bullets.push_back( CBullet(actor1.getX( )、actor1.getY()- 1 、QVector2D( 0、-15 )));
- qDebug( QString( "追加された箇条書き 。Pos %1" ).arg( m_bullets.size( )- 1 ).toAscii());
- m_allowbullet = 5 ;
- 解雇++;
- }
- }
- actor1.step Direction ();
- bool dir_touched = false;
- for( int i = 0; i <m_dir.size( ); i ++)
- {
- if( m_dir [i]!= MainWindow :: SPACE )
- {
- dir_touched = true;
- 休憩;
- }
- }
- if( !dir_touched )
- {
- m_allowmove = 0 ;
- フロート慣性= 0 。 5 ;
- if( actor1.getSpeed( )< 0。5 )
- 慣性= 1 ;
- actor1.adjustSpeed( 慣性 );
- }
- for( int i = 0; i <m_bullets.size( ); i ++)
- m_bullets [ i ] .step Direction ();
- for( int x = 0; x <m_enemies1.size( ); x ++)
- m_enemies1 [x] .step Direction ();
- CheckGameRules();
- if( m_enemies1.size( )<max_enemies)
- {
- CMeteorite meteo( mrand(field_ident + CMeteorite :: meteo_size、
field_ident + field_w-CMeteorite :: meteo_size )、- -mrand(0、20)、
- QVector2D( 0、1 ));
- while( true )
- {
- int i = 0 ;
- while( i <m_enemies1.size( ))
- {
- if( meteo.getBoundsT( ).intersects( m_enemies1 [i] .getBoundsT( )))
- 休憩;
- i ++;
- }
- if( i == m_enemies1.size( ))
- 休憩;
- meteo = CMeteorite(mrand( 1、100 )、-mrand(0、20)、
- QVector2D( 0、1 ));
- }
- m_enemies1.push_back( meteo );
- }
- UpdateBullet();
- }
CheckGameRules関数は、ゲームルールをチェックします-誰が誰かに衝突したか、何を超えたかなどです。 ところで、2Dでは、これはすべてQPolygon、QRectクラス、およびそれらのような他の機能によって非常に便利に実行されます。
ソースをコピー | HTMLをコピー
- void MainWindow :: CheckGameRules( const float ftime )
- {
- QRect field_rect( field_ident、field_ident、
field_w
field_h );- for( int i = 0; i <m_bullets.size( ); i ++)
- {
- CBullet blt = m_bullets [ i ];
- float tx = 0 、ty = 0 ;
- blt.getTickCoords( ftime、tx、ty );
- blt.setX( tx );
- blt.setY( ty );
- if( !field_rect.contains(m_bullets [i] .getX( )、m_bullets [ i ] .getY()))
- {
- m_bullets.remove( i-- );
- }
- 他に
- {
- for( int j = 0; j <m_enemies1.size( ); j ++)
- {
- CMeteorite enm = m_enemies1 [j];
- フロート etx = 0 、ety = 0 ;
- enm.getTickCoords( ftime、etx、ety );
- enm.setX( etx );
- enm.setY(ety);
- if( blt.checkCollision(enm.getBodyT( )))
- {
- m_enemies1.remove( j-- );
- m_bullets.remove( i-- );
- スコア++;
- 休憩;
- }
- } // for
- }
- }
- for( int j = 0; j <m_enemies1.size( ); j ++)
- {
- CMeteorite enm = m_enemies1 [j];
- if( !field_rect.contains(enm.getBoundsT( ))&&
- field_rect。 bottomRight ().y()<enm.getBoundsT()。 topLeft (). y ())
- {
- m_enemies1.remove( j-- );
- }
- if( actor1.checkCollision(enm.getBodyT( )))
- {
- m_enemies1.remove( j-- );
- ヒット++;
- }
- }
- if( !field_rect.contains(actor1.getBoundsT( )、true))
- {
- while( field_rect.x( )> = actor1.getBoundsT()。 left ())
- actor1.setX( actor1.getX( )+ 1 );
- while( field_rect.x( )* 2 + field_rect。width()<= actor1.getBoundsT()。x()+ actor1.getBoundsT()。 width ())
- actor1.setX( actor1.getX( )- 1 );
- while( field_rect.top( )> = actor1.getBoundsT()。 top ())
- actor1.setY( actor1.getY( )+ 1 );
- while( field_rect.y( )* 2 + field_rect。height()<= actor1.getBoundsT()。y()+ actor1.getBoundsT()。 height ())
- actor1.setY( actor1.getY( )- 1 );
- actor1.s top ();
- }
- }
したがって、連続性のシミュレーターの呼び出しは不名誉に簡単です。 わずかなステップで、ゲームロジックを確認します。
ソースをコピー | HTMLをコピー
- void MainWindow :: SimulateConsistLogic( float ftime )
- {
- for( float bt = 0; bt <ftime; bt = bt + 0.1 )
- {
- CheckGameRules( bt );
- }
- }
レンダリングは、ゲームフィールドを描画し、ゲームジェネレータークロックの最後の呼び出しからの現在のインデントのパラメーターを使用して、すべてのオブジェクトのDraw()を呼び出します。 サービス情報のプラス出力:
ソースをコピー | HTMLをコピー
- void MainWindow :: Render()
- {
- QPainter qpainter( これ );
- const int bgw = 2 ;
- qpainter.setPen( QPen(Qt :: black、bgw ));
- qpainter.setBrush( QBrush(Qt :: darkGray ));
- qpainter.drawRect( field_ident、field_ident、
field_w + field_ident、
field_h + field_ident );- for( int i = 0; i <m_bullets.size( ); i ++)
- {
- CBullet blt = m_bullets [ i ];
- blt.Draw( qpainter、freq_bit );
- }
- for( int i = 0; i <m_enemies1.size( ); i ++)
- {
- CMeteorite enm = m_enemies1 [ i ];
- enm.Draw( qpainter、freq_bit );
- }
- actor1.Draw( qpainter、freq_bit );
- QPalette pal;
- qpainter.setBrush( pal.brush(QPalette :: Window ));
- qpainter.setPen( QPen(pal.color(QPalette :: Window )、 1 ));
- qpainter.drawRect( field_ident-bgw / 2、0、
field_w + field_ident + bgw / 2
field_ident-bgw );- qpainter.setPen( QPen(Qt :: black、bgw ));
- qpainter.setBrush( QBrush(Qt :: darkGray、Qt :: NoBrush ));
- qpainter.drawRect( field_ident、field_ident、
field_w + field_ident、
field_h + field_ident );- ui-> label_freq-> setText( QString( "%1" ).arg( freq ).toAscii());
- ui-> label_fps-> setText( QString( "%1" ).arg( fps ).toAscii());
- ui-> label_speed-> setText( QString( "%1" ).arg( actor1.getSpeed( )).toAscii());
- ui-> label_score-> setText( QString( "%1" ).arg( score ).toAscii());
- ui-> label_fired-> setText( QString( "%1" ).arg(発生).toAscii());
- ui-> label_hits-> setText( QString( "%1" ).arg( ヒット ).toAscii());
- }
実際、残りは簡単なプログラミングです。 アプリケーションスケルトンは逆アセンブルされ、実装の詳細は添付のソースコードに記載されています。 結果-私が得たものの外観:
ソースはこちら 。 矢で飛んで、スペースで撃ちます。
githubのソース 。