4

まず、XNAフレームワークを使用して2D戦略ゲームに取り組んでいます。

ゲームに2Dの戦場の霧を実装しています。グラフィック部分はすでに完成していて、かなりうまく機能していますが、私は今、この戦場の霧の「論理」部分を実装しようとしています。

レベルを表す2Dグリッドを作成しました。各フレーム、各ユニットは、ブレゼンハムのアルゴリズムを使用して、その周りの円内のセルを更新します(これは、特定の円内にあるセルを把握するための最良の方法のようです)。これは実際に機能しています...特定の位置が表示されているかどうかを知りたい場合は、セルの状態を取得する必要があります...

問題は、スポーンされたユニットが大量にあると、ゲームの実行が非常に遅くなることです...このパフォーマンスの問題の最初の理由は、すべてのユニットが周囲のセルを更新するため、多くのセルが複数回更新されることです...しかし、私はこれに対する解決策を見ることができません...

だから...多分私はそれをこのように実装するのが間違っていたのかもしれません、あるいは私は明らかな最適化を逃しているのかもしれませんが、私はちょっと立ち往生しています...

コードは次のとおりです。

class LevelGridCell
{
    public void SetVisible(float a_time)
    {
        if (m_visibleTime < a_time)
            m_visibleTime = a_time;
    }
    public bool IsVisible(float a_time)
    {
        return (m_visibleTime != 0f && m_visibleTime >= a_time);
    }

    float m_visibleTime = 0;
}

class LevelGrid
{
    public LevelGridCell GetAt(int a_x, int a_y)
    {
        return m_grid[a_x + a_y * m_width];
    }

    public void SetVisible(float a_time, int a_x, int a_y, float a_radius)
    {
        GetAt(a_x, a_y).SetVisible(a_time);

        int intRadius = (int)(a_radius / m_cellSize);
        int x = 0, y = intRadius, p = 1 - intRadius;
        PlotSetVisible(a_x, a_y, x, y, a_time);
        while (x < y)
        {
            x++;
            if (p < 0)
                p += 2 * x + 1;
            else
            {
                y--;
                p += 2 * (x - y) + 1;
            }
            PlotSetVisible(a_x, a_y, x, y, a_time);
        }
    }
    private void SafeSetVisible(int a_x, int a_y, float a_time)
    {
        if (a_x >= 0 && a_x < m_width && a_y >= 0 && a_y < m_height)
        {
            GetAt(a_x, a_y).SetVisible(a_time);
        }
    }
    private void PlotSetVisible(int xctr, int yctr, int x, int y, float a_time)
    {

        for (int i = xctr - x; i <= xctr + x; ++i)
        {
            SafeSetVisible(i, yctr + y, a_time);
            SafeSetVisible(i, yctr - y, a_time);
        }
        for (int i = xctr - y; i <= xctr + y; ++i)
        {
            SafeSetVisible(i, yctr + x, a_time);
            SafeSetVisible(i, yctr - x, a_time);
        }
    }

    List<LevelGridCell> m_grid = new List<LevelGridCell>();
    float m_cellSize;
    int m_width;
    int m_height;
}
4

2 に答える 2

6

あなたのコードを見ずに、私たちは問題が何であるかを推測する義務があります。コードのプロファイルを作成した場合、どの部分が特に遅いかを把握できるはずです。その情報があれば、問題を直接攻撃することができます。

ここに、どのビットが遅いかについてのいくつかの推測があります:

  • 円は円です。ユニットごとにブレゼンハムのサークルアルゴリズムに従っていますか?(0,0)を基準にして、円を1回だけ計算できるようです。次に、(x、y)にあるユニットの場合、円を検索し、円内のポイントを(x、y)にオフセットしてから、そのユニットに戦場の霧ロジックを適用できます。

  • 戦場の霧は、最近移動したユニットに対してのみ変更されます。ユニットが静止している場合は、その可視性を再度計算する必要がない場合があります。このような最適化を、戦場の霧の可視性のルールに適用できますか?

  • ブレゼンハムの円アルゴリズムは、円のエッジをプロットするのに役立ちます。円の内側をどのように埋めていますか?範囲の内部を埋めるためのより良いアルゴリズムを見つける必要がありますか?

生成された1つの円の使用についての詳細を尋ねられたコメントがあるので、ここでそれについていくつかのメモを追加します。(答えを編集するのはその方法ですか?申し訳ありませんが、私はStack Exchangeに比較的慣れていません。)

「戦場の霧」とは、通常、ユニットがその周囲の半径を見ることができることを意味します。あなたのユニットのその半径は何ですか?ユニットタイプごとに半径は異なりますか?ユニットタイプはいくつありますか?

1つのユニットタイプの可視範囲の半径が5平方であるとします。これにより、次のような円が残ります。

00001110000
00010001000
00100000100
01000000010
10000000001
10000x00001
10000000001
01000000010
00100000100
00010001000
00001110000

サークルがあるので、それを埋めるのにそれほど難しいことをする必要はないことがわかります。単純なアルゴリズムは、右端の1と左端の1の間を埋める各行を移動します。これは、すべての内部ポイントに対してBreshenhamのアルゴリズムを解決するよりもはるかに高速です。

塗りつぶされた円で、この配列を見つけます。

00001110000
00011111000
00111111100
01111111110
11111111111
11111x11111
11111111111
01111111110
00111111100
00011111000
00001110000

半径5平方の可視性を持つユニットがある場合、そのユニットに戦争の霧を適用するということは、この事前計算された配列の中心が現在のユニット上にあるように、この事前計算された配列を戦争の霧配列に適用することを意味します。処理。中心からのオフセットを計算し、配列の境界をマップの端にクリップしたら、単純なネストされたループでこれを行うことができます。

戦場の霧の可視性にいくつかの異なる半径がある場合は、いくつかの異なる配列を事前に計算することができます。障害物(地形、建物、風景など)が原因で戦場の霧が変化するというルールがある場合は、もう少し処理を行う必要がありますが、事前計算のアイデアは引き続き適用できます。

于 2012-12-18T15:45:58.527 に答える
1

接吻

毎回円を計算する必要があるのはなぜですか? それは高価です。

このように考えてみてください。大きなブラシを選択して paint.exe で画像を編集しているとき、毎回その円を計算していると思いませんか? Photoshop でファンシーまたはカスタム形状のブラシを選択すると、どのように機能しますか?

円を 1 回計算するだけで済みます (ハード コードしたいのですが、そこにいる仲間が既に高速トラックを提供しています)。

const char circle[] = {
00001110000
00011111000
00111111100
01111111110
11111111111
11111x11111         <-- put this into a static C array
11111111111
01111111110
00111111100
00011111000
00001110000   
}

そこで、その配列を使用して、fog of war 配列全体と比較することができます。その上で 0 に出くわした場合、1 がある場合は何もせず (重複している可能性があります)、fog of war アレイに 1 を設定します。

サイトの範囲が異なるユニットが必要な場合は、そのための配列を計算またはハードコーディングするだけです。せいぜい 1 キロバイトのメモリを使い果たすことになりますが、非常に多くのサイクルを節約できます。

しかし、障害がある場合はどうなりますか?高さのある配列を含む別のマップを作成する必要があるかもしれません (既にこれを持っていますか?) またはビューをブロックする領域があります。ビューをブロックするエリアだとしましょう (高さは関係ありません。ビューをブロックするだけです)。ビューサークル配列を直接比較する代わりに(上にリストされています)。それをビュー ブロッキング アレイ (1 と 0 も) と比較する必要があります。ビューがブロックされていることを示す 1 に遭遇した場合は、ブレゼンハム関数を使用して、ユニットからその先までラインを発射する必要があります。ビュー ブロッキング アレイ内の 1 つに遭遇したポイントで、可視として設定する必要がある領域が得られます。

コミュニケーションが下手だったので、分からなかったらまた連絡します。

そうは言っても、これはとにかく貧弱な方法かもしれません。ユニットが移動するたびにグリッド全体を再計算すれば実装は簡単ですが、それは恐ろしく遅く、非効率的です。そのためには、FOW アレイが移動したら、そのユニット マークをクリーンアップする必要があります。(画面のクリアやダブルバッファリングを行わずに 2D アニメーションを考えてください)

于 2013-04-29T03:49:08.407 に答える