- PR -

【C#】マルチスレッドによる処理が重い(ゲーム作成)

1
投稿者投稿内容
うちうせん
ベテラン
会議室デビュー日: 2003/08/08
投稿数: 96
お住まい・勤務地: 福岡県
投稿日時: 2007-03-26 00:36
現在C#にてブロック崩しのようなゲーム制作をしています。
C#とマルチスレッドが初挑戦なので手探りで進めていますが、動作がカクカクしたりするので、どこかおかしければアドバイスお願いします。
現在背景画像とボールのアニメーションのみ実装済みです。
--------------------------------------------------------------------
[フォームクラス]
・FPSの管理スレッド:60FPSになるようにフォームの再描画を管理(whileループ)
    private void thd_Paint()
    {

      frame = 0;
      int before = Environment.TickCount;

      while (!IsDisposed)
      {
        int now = Environment.TickCount;
        int progress = now - before;
        int ideal = (int)(frame * (1000.0F / FPS));
        if (ideal > progress) Thread.Sleep(ideal - progress);
        Invalidate();
        frame++;
        if (progress >= 1000)
        {
          framer = frame;
          before = now;
          frame = 0;
        }
      }
    }
・OnPaint:背景画の描画とボールの描画
    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);

      //背景の描画
      e.Graphics.DrawImage(m_cImgLst.imgFBack, 0, 0);
      e.Graphics.DrawImage(m_cImgLst.imgBBack, CConst.DF_RECT_BBACK, CConst.DF_RECT_BBACK, GraphicsUnit.Pixel);
      //ボールの描画
      e.Graphics.DrawImage(m_cImgLst.imgYBall, cBall.Pos.X, cBall.Pos.Y);
    }
[ボールクラス]
・ボールの移動スレッド:whileループで座標を変更(10msのスリープあり)
    private void thd_Move()
    {
      while (true)
      {
        if (m_Pos.X + m_Speed.X < 250)
        {
          m_Speed.X = m_Speed.X * -1;
        }
        if (m_Pos.X + m_Speed.X > 774 - 11 / 2)
        {
          m_Speed.X = m_Speed.X * -1;
        }
        if (m_Pos.Y + m_Speed.Y < 40)
        {
          m_Speed.Y = m_Speed.Y * -1;
        }
        if (m_Pos.Y + m_Speed.Y > 700 - 11 / 2)
        {
          m_Speed.Y = m_Speed.Y * -1;
        }
        m_Pos.X+=m_Speed.X;
        m_Pos.Y+=m_Speed.Y;
        Thread.Sleep(10);
      }
    }
--------------------------------------------------------------------
ちなみにフォームの描画設定は以下のようにしています。
SetStyle(ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.DoubleBuffer, true);

[開発環境]
WindowsXP Pro SP2
Microsoft Visual C# 2005 Express Edition

_________________
うちうせん

[ メッセージ編集済み 編集者: うちうせん 編集日時 2007-03-26 00:50 ]

[ メッセージ編集済み 編集者: うちうせん 編集日時 2007-03-26 02:06 ]
甕星
ぬし
会議室デビュー日: 2003/03/07
投稿数: 1185
お住まい・勤務地: 湖の見える丘の上
投稿日時: 2007-03-26 11:39
引用:

うちうせんさんの書き込み (2007-03-26 00:36) より:
・FPSの管理スレッド:60FPSになるようにフォームの再描画を管理(whileループ)


無意味に高FPSを目指したりしない。人間に知覚できる限界を超えても意味が無い。

また画面のリフレッシュレートに近い周期で画面を更新をするなら、リフレッシュレートとの同期を取る必要がある。同期を取らないとブラウン管だと画面がちらついて見えたりする(液晶なら目立たない)。リフレッシュレートと同期を取るためには・・・DirectXを使ってください。

引用:

        int now = Environment.TickCount;
        int progress = now - before;
        int ideal = (int)(frame * (1000.0F / FPS));
        if (ideal > progress) Thread.Sleep(ideal - progress);


Sleepの使い方を間違っている。Sleepの分解能はシステム構成にもよるが10ms程度しかない。したがってそれ以上に細かな時間を指定しても10msのn倍に丸められてしまう。この例だと"Thread.Sleep(10);"と指定しているのと大差ない。

演算があっているか否かまでは確認していないけど、期待したとおりにちゃんと動いていますか?カクカクした動作をする(フレーム抜けが発生する)のであれば、このループの処理に時間がかかっている(例えば100ms以上かかったりしている)と思うのですけど。

引用:

    protected override void OnPaint(PaintEventArgs e)
    {
      base.OnPaint(e);

      //背景の描画
      e.Graphics.DrawImage(m_cImgLst.imgFBack, 0, 0);
      e.Graphics.DrawImage(m_cImgLst.imgBBack, CConst.DF_RECT_BBACK, CConst.DF_RECT_BBACK, GraphicsUnit.Pixel);
      //ボールの描画
      e.Graphics.DrawImage(m_cImgLst.imgYBall, cBall.Pos.X, cBall.Pos.Y);
    }


画面に描画する命令は非同期に動作するので注意。DrawImageから戻った時点では、まだ画面上に表示されていない事もある。また命令をためて置くバッファがいっぱいになると、応答が帰ってくるまでしばらく待たされることもある。

引用:

[ボールクラス]
・ボールの移動スレッド:whileループで座標を変更(10msのスリープあり)


描画と同じ程度の周期で演算させるなら、きちんと同期を取らないとまずい。微妙なタイミングのずれで、一回描画している間に2回移動させていたり、あるいは1回だけだったりするだろう。これもがくがくした動作の原因となりうる。
うちうせん
ベテラン
会議室デビュー日: 2003/08/08
投稿数: 96
お住まい・勤務地: 福岡県
投稿日時: 2007-03-26 15:26
返答ありがとうございます。
思ってた以上に難易度が高そうですね・・・。
完全に知識外なので勉強しなおして再チャレンジしてみます。
アドバイスありがとうございました。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2007-03-26 23:24
 まず、小言から。
 よく、「プログラム コードが仕様書だ」とおっしゃる方がいますが、私はそうは思いません。コードは計算式でしかないからです。数学に「証明問題」というものがあります。証明自体は計算式で示しますが、計算式だけ示しても、正解とはしてもらえません。計算式を補足するための文が必要です。コードに対するあなたの意図を付けていただけないですか?それがあって初めて、「コードが仕様書だ」といえると思います。

 そして本題。
 上のような理由で、書いてあることを理解できているかどうか怪しいですが、コード上の怪しいところはここです。
引用:
コード:
while (!IsDisposed)
{
    int now = Environment.TickCount;
    int progress = now - before;
    int ideal = (int)(frame * (1000.0F / FPS));
    if (ideal > progress) Thread.Sleep(ideal - progress);
    Invalidate();
    frame++;
    if (progress >= 1000)
    {
        framer = frame;
        before = now;
        frame = 0;
    }
}



初めて実行されるとき、frame が 0 なので、ideal も 0 となります。
次の回では、frame が 1 なので、ideal は 17 です。前回実行時との Tick 差が 17 以下の時、たとえば 8 の時、9 マイクロ秒スリープします。
その次。frame が 2 なので、ideal は 33 です。1回目を実行したときと、今回の実行時との Tick 差が 33 以下の時…この先、とても無駄な処理になることはわかりますか?何のためにこの処理が必要なのかわかりません。

 ボールの位置計算もおかしいですよね?この計算式だと、ボールが速いとき、壁につく前にバウンドしてしまいます。また、計算回数を多くして対応しないといけないのに、移動距離を大きくしています。これではすり抜けを起こしてしまいます。


 私だったら、、、

1.描画する
2.次に描画するタイミングを計算する
3.描画する絵を作る
4.次に描画するタイミングまで時間があるなら待つ

のように作るかな。

_________________
七味唐辛子
ぬし
会議室デビュー日: 2001/12/25
投稿数: 660
投稿日時: 2007-03-26 23:46
タイトル
「シューティングゲーム プログラミング 」


http://www.amazon.co.jp/%E3%82%B7%E3%83%A5%E3%83%BC%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%82%B2%E3%83%BC%E3%83%A0-%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E6%9D%BE%E6%B5%A6-%E5%81%A5%E4%B8%80%E9%83%8E/dp/4797337214/ref=sr_1_2/249-0239508-3445934?ie=UTF8&s=books&qid=1174920000&sr=1-2

こんな感じの本でもよんでから始めたほうがいいかも
基本的なアルゴリズムや考え方が列挙されています。

ブロック崩し系は入門には最適だと思います。
1

スキルアップ/キャリアアップ(JOB@IT)