ゲームのスレッドの排他制御について

解決


ぴょぴょ  2008-03-05 22:39:49  No: 67738

こんにちは。
最初にシューティング・ゲームを製作中です。

今まではゲームループをWinMainにPeekMessage関数で
メッセージがない時に呼ぶようにしてゲームループを
組んできました。

それで今回はゲームループをスレッドで書き直そうとしています。
今はスレッドを2つ考えています。
1つ目はゲームの進行(データ)を処理するスレッドA
2つ目はゲームの描画(60FPS)を処理するスレッドB

ここで質問。
スレッドAはデータに書き込むだけ、
スレッドBはデータから読み込むだけの場合でも排他制御が必要ですか?
1つの変数(データ)に複数のスレッドから読み書きする場合には
排他制御が必要なのはわかります。
今回の場合はどうなりますか?
教えて下さい。

[開発環境]
Windows XP Home SP2
VC++2003.NET(SDK)

[補足]
今回が初のマルチスレッド・プログラミングになります。
大よその仕組みは本、サイトから分かりましたが
まだ排他制御をすべきかどうかが不安です。
アドバイスなどをお願いします。


そだ  2008-03-06 05:48:54  No: 67739

排他制御すべきだと思います。

経験から
物理計算やっているときに描画すると
絵が変にずれます。

あとFPSはできればPCの性能によって
可変することが好ましいです・・・


そだ  2008-03-06 06:05:49  No: 67740

ついでに書くと、
単純な2Dのシューティング・ゲームなら
あえて描画とデータ処理をスレッドで分ける
メリットはないような気がします・・・


ぴょぴょ  2008-03-06 06:13:52  No: 67741

> 経験から
> 物理計算やっているときに描画すると
> 絵が変にずれます。
なるほどです。

> 単純な2Dのシューティング・ゲームなら
> あえて描画とデータ処理をスレッドで分ける
> メリットはないような気がします・・・
そうですか。
じゃあ。
1つのスレッドでゲームループを組めば良さそうですね。
この場合なら排他制御も必要なさそうです。

> あとFPSはできればPCの性能によって
> 可変することが好ましいです・・・
これはどう可変すればいいのでしょうか?
PCの性能を取得してどのように可変すればいいのですか?
そもそもPCの何の性能でしょうか?

そださん。
また見てくれたら何かお願いします。
他の方でもかまいません。


そだ  2008-03-06 06:35:58  No: 67742

>PCの性能によって
色々方法はあると思うし私も専門家じゃないので
あくまで参考までに見てください。

基本的には PeekMessageを使ってメッセージが流れていない間
動かし続けます。(単発スレッドでもいいです)
データ処理の際、時間dt間での移動を求めるのに、
p = p + v * dt;
という処理を書くと思いますが、このdtをうまく決めてやります。

while(終了条件) { // ゲームのメインループ
 nowtime = timeGetTime();
 dt = nowtime - lasttime;
 lastime = nowtime;
 if( dt > 500 ) { // 安全のため
   dt = 500;
 }
 描画関数();
 データ処理関数(dt);
}

こうすることで描画関数とデータ処理関数を実行した時間に応じて
絵を移動させることができるため、
高性能なPCほど細かい時間刻みで、
低スペックなPCほど荒い時間刻みで動くゲームを作ることができます。


ぴょぴょ  2008-03-06 07:15:52  No: 67743

> 高性能なPCほど細かい時間刻みで、
> 低スペックなPCほど荒い時間刻みで動くゲームを作ることができます
これだと移動スピードが変化しませんか?
高スペックでは早く移動
低スペックでは遅い移動

今は次のようにしています。
while ( 終了条件 ){
  dwGetTime = timeGetTime();
  gameMain( dwGetTime );
  
  // 描画周期
  if ( (dwGetTime - dwCntTime) >= (1000 / 60) ){
    gameDraw();
    dwCount++;
    dwCntTime = dwGetTime;
  }
  // FPS表示周期(テスト用に設置)
  if ( (dwGetTime - dwFPSTime) >= 1000 ){
    setFPS( dwCount );
    dwCount = 0;
    dwFPSTime = dwGetTime;
  }
  Sleep( 1 );
}
gameMain()関数で1フレームの処理を行いますが
自機移動は1000/60ミリ秒周期、
敵機移動は1000/60ミリ秒周期、
弾丸移動は1000/120ミリ秒周期
としています。

具体的に自機移動は
if ( (dwGetTime - dwSaveTime) >= (1000 / 60) ){
  移動処理
  弾の発射
  当たり判定
  dwSaveTime = dwGetTime;
}
としています。
敵機移動、弾丸移動も同じ感じです。
そのほか背景やアイテムも同じ考え。

> データ処理関数(dt);
この関数で移動スピードの調整とかは
どうなっているのですか?


そだ  2008-03-06 07:43:25  No: 67744

話が物理エンジンの話にそれてるような気もしますが
この際いいでしょう。

>移動スピードが変化
1フレームあたりの変化量はPCによって変化します。
ただし、
実時間(プレーヤーが見るスピード)では速度はvで一定です。
なので、
>高スペックでは早く移動
>低スペックでは遅い移動
ということにはなりません。

>> データ処理関数(dt);
>この関数で移動スピードの調整とかは
>どうなっているのですか?
実時間でフレーム間の経過時間dtを元に
移動量を計算するため特に調整しなくても適宜な
スピードになります。
強いて言えばdtを使って移動スピードを調整しています。
例えば、
e_vx = -1.f; //敵機のx座標速度
e_vy = 0.f;  //敵機のy座標速度
とあらかじめ定義して、敵機の座標をe_p=(e_x,e_y)としたとき、
処理関数(dt) {
e_x += e_vx * dt / 1000.f;
e_y += e_vy * dt / 1000.f;
}
とすれば、どんなPCでも毎秒左に1ピクセルずつ動く敵機が作れます。


ぴょぴょ  2008-03-06 08:08:38  No: 67745

> 処理関数(dt) {
> e_x += e_vx * dt / 1000.f;
> e_y += e_vy * dt / 1000.f;
> }
> とすれば、どんなPCでも毎秒左に1ピクセルずつ動く敵機が作れます。
この例えで前回の

> p = p + v * dt;
の意味が分かりました。

もう一度いろいろと考え直してみます。
スレッドに関しては1つだけにしてゲームループを組むようにします。
そださん。
最後までお付き合い。
ありがとうございました。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加