パーリンノイズによって作られた世界のスポーンユニット?


16

Perlinノイズベースのゲームで出会ったいくつかの問題があります。以下の添付のスクリーンショットをご覧ください。

ここに画像の説明を入力してください

あなたが見る白い領域は壁であり、黒い領域は歩行可能です。中央の三角形がプレーヤーです。

このゲームでは、物理学を実装して、テクスチャ(白または黒のピクセル)に描画し、それをCPUから取得します。

しかし、今は別の問題に直面しています。画面の端で、ユニット(またはクリープと呼びます)が常に出現するようにします。ここでのポイントは、最終的なゲームでは、プレイヤーがそこまで見ることができない「戦争の霧」があるということです。

画面の端のピクセルをスキャンして、その物理テクスチャが黒かどうかを確認し、そこにランダムに物を生成できると考えました。ただし、スクリーンショットをもう一度見ると、(左上隅に)クリープが生成されないようにする例があります(そこからプレイヤーに到達できないため) 。

どういうわけかGPUにこれらのスポーンスポットを決定させることは可能ですか、それとも別の方法ですか?画面の端にある提案されたポイントとプレイヤーの間にベクトルを作成し、10ボクセルごとにそれをたどって、そこにユニットをスポーンする前に壁が衝突するかどうかを確認することを考えました。

ただし、上記の提案されたソリューションは、CPUに負荷がかかりすぎる可能性があります。

この問題に関する提案はありますか?

注1 スポーンされるユニットについては、これらのユニットがプレイヤーに向かって走る際の壁の衝突を避けるために、いかなる形態の経路探索も使用したくありません。したがって、ユニットは画面の端に、プレイヤーに向かって直線的に歩いても壁と衝突しない場所に出現する必要があります。


1
マップはプレイヤーと共に移動しますか、それともプレイヤーはマップ内を移動しますか?つまり、マップは変更されますか?そうでない場合は、生成時に到達不能なすべてのポイントを入力することをお勧めします。そうすれば、それらについて心配する必要はありません。
dlras2

プレイヤーが動いている場合、ユニットは経路探索方法を必要とします。凹面領域が必要な場合は、この問題が発生します。また、移動するプレイヤーに到達しようとする移動ユニットに解決策を提供する必要があります。
ブラウ

1
または、Blauのコメントを別の言い方をすれば、プレイヤーが移動できる場合、あなたの質問には有効な回答がありません(壁タイル/ピクセルがまったくないマップのささいなケースは別として)。プレーヤーが静止している場合に答えを得るには、「直線」で何を意味するのかを定義する必要があります。
マーティンソイカ

回答:


3

このジョブには非常に便利なアルゴリズムがあり、この状況ではフラッドアルゴリズムよりもはるかに効率的です(その複雑さは、フラッディングエリアではなく境界サイズに比例します):マーチングスクエアアルゴリズムです。コンセプトはシンプルです。プレイヤーの場所(画面の中間点)から出発し、方向を選択して、壁が見つかるまで歩きます。壁と衝突すると、アルゴリズムを開始します。方向(上または下)を選択し、この領域の境界を越えて行進を開始し、ピクセルを強調表示します。これにより、許可された領域の内部境界が得られます。その後、スポーンユニットの候補点がこの境界上にあるかどうかを確認します。

これは、境界を実行するために従うべき原則です。

http://en.wikipedia.org/wiki/File:Marchsquares.png (私はまだ写真を投稿できません)

ウィキペディアの説明(ただし、他のアプリケーションを念頭に置いて使用されているため、かなり複雑になっています):

http://en.wikipedia.org/wiki/Marching_squares


10

作る塗りつぶしをプレーヤーの位置から、「あふれた」すべてのエリアは有効なプレイエリアであり、他のすべてのエリアは壁です。

編集:「直線で到達可能」の追加要件については、離散空間では、これをもう少し定義する必要があることに留意してください。たとえば、上記のパスはすべて、このような環境で有効な「直線」になる可能性があり、いずれかの時点でゲームで使用されるすべてのパスを見てきました。

「直線」バリアント

特に、それらのほとんどは可換ではありません。つまり、「直線」でAからBに到達できるからといって、BからAに到達できるわけではありません。どちらも逆ではありません。

さらに、一方または両方の「サイド」ポイントがブロックされている場合、斜めの動きをどのように処理するかという問題があります。


これを完全にHLSLに実装できますか?
マティアスリュックガードローレンツェン

@Mathias Lykkegaard Lorenzen:はい、ピクセルシェーダーとしてアルゴリズムの各ステップを実行し、たとえば2つのテクスチャターゲット間でレンダリングすることにより、疑いなく、しかし... なぜですか?とにかく、少なくともパスを見つけるために、CPUのアルゴリズムからの情報が必要になるでしょう。
マーティンソーカ

1
@Mathias Lykkegaard Lorenzen:しかし、それはあなたが求めたものとは少し異なります。この場合:個別のスペースパーティションスキーマが与えられた場合、どのように「直線」を定義しますか
マーティンソイカ

2
パスファインディングを使用したくない場合でも、CPUにフラッドフィルジョブを実行するように依頼することは可能です。フラッドフィルを1回呼び出すだけで、壁、空きスペース、スポーン可能スペースを定義する3色のテクスチャが得られることに注意してください。4096x4096テクスチャの場合、CPUがフラッドフィルジョブを完了するのに1秒もかかりません。
Ali1S232

1
ポイントは、この塗りつぶしを1回実行するだけでよく、ゲームプレイ中に地形が変わっても、影響を受けるセクションを更新するだけで、塗りつぶしが非常に高速になります。
TravisG

1

スポーンを発生させてみてはどうですか?特に問題はありません。


そして、彼らが壁の後ろに出現したらどうでしょうか?どのようにしてそれらをプレーヤーに届けますか?
マティアスリュックガードローレンツェン

1
ゲームにすべての敵を殺すシナリオがあり、50人の敵をスポーンすると、問題になる可能性がありますが、壁の後ろに数人がスポーンしました。プレイヤーは敵を殺すことができず、シナリオは終了しません。
ライライアン

1
スポーン後のプレイヤーの動きによっては、他のユニットがプレイヤーに到達できない場合があります。いずれの場合も、いくつかのユニットのスポーンを解除する必要があります。
aaaaaaaaaaaa

戦争の霧がくだらないスポーンを覆います
KRB

1
とにかく、プレイヤーが動いても機能しません。
aaaaaaaaaaaa

1

プレイヤーに対して有効な直線でポイントのみをマークすることが重要な場合、次のようなアルゴリズムを使用できます(c ++コード)、通常よりも多くのフラッドフィルを消費します。私は自分でコードをテストしなかったので、あなたはアイデアを得るので、いくつかのマイナーなバグがあるかもしれません(誰かがそれらを修正してくれれば嬉しいです)。

void straightlineFill(Point startPoint, Texture target)
{
    queue<Point> pointQueue;
    for (int dx = -1;dx <=1;dx ++)
            for(int dy = -1;dy <=1;dy++)
                if(dx != 0 && dy != 0)
                    pointQueue.push(point(startPoint.x + dx, startPoint.y + dy));
    while (!pointQueue.empty())
    {
        point front = pointQueue.front();
        pointQueue.pop();
        if (target.pixelAt(front) == COLOR_SPAWNABLE||
            target.pixelAt(front) == COLOR_WALL||
            target.pixelAt(front) == COLOR_NOT_SPAWNABLE)
                continue;
        taraget.setPixelAt(front, colorFilled); 
        for (int dx = -1;dx <=1;dx ++)
            for(int dy = -1;dy <=1;dy++)
                if(dx != 0 && dy != 0)
                    pointQueue.push(point(front.x + dx, front.y + dy));
        // up until now was the normal floodfill code
        // and here is the part that will do the real straight line checking

        // lineDX & lineDY will keep how much the line we are checking is skewed
        int lineDX = front.x - startPoint.x;
        int lineDY = front.y - startPoint.y;

        // step will show us how much we have to travel to reach each point of line
        point step;
        if (abs(lineDX) < abs(lineDY))
        {
            if (lineDX < 0)
                step = point(-1,0);
            if (lineDX == 0)
                if (lineDY < 0)
                    step = point(0,-1);
                else
                    step = point(0,1);
            if (lineDX > 0)
                step = point(1,0);
        }

        if (abs(lineDX) < abs(lineDY))
        {
            if (lineDY < 0)
                step = point(0,-1);
            if (lineDY == 0)
                if (lineDX < 0)
                    step = point(-1,0);
                else
                    step = point(1,0);
            if (lineDY > 0)
                step = point(0,1);
        }

        // moved will keep how much we have traveled so far, it's just some value that will speed up calculations and doesn't have any mathematical value
        point moved = 0;

        // spawnable will keep if the current pixel is a valid spawnpaint

        bool spawnable = true;

        // now we will travel on the straight line from player position to the edges of the map and check if whole line is valid spawn points or not.

        while (target.isInside(front))
        {
            front = front + step;
            moved = moved + point(step.x * lineDX, step.y * lineDY);
            if (step.x != 0 && moved.x < moved.y)
            {
                moved.x = moved.x - lineDY * sign(lineDY);
                front.y = front.y + sign(lineDY);
            }
            if (step.y != 0 && moved.y < moved.x)
            {
                moved.y = moved.y - lineDX * sign(lineDX);
                front.x = front.x + sign(lineDX);
            }

            if (target.getPixelAt(front) == COLOR_WALL)
                spawnable = false;

            if (spawnable)
            {
                target.setPixelAt(front,COLOR_SPAWNABLE);
                for (int dx = -1;dx <=1;dx ++)
                    for(int dy = -1;dy <=1;dy++)
                        if(dx != 0 && dy != 0)
                            pointQueue.push(point(front.x + dx, front.y + dy));             
            }
            else
            {
                target.setPixelAt(front,COLOR_NOT_SPAWNABLE);
            }
        }
    }
}

1

凸面領域を表す色でマップを塗りつぶすことができます...この方法で、同じ領域にある場合はユニットをスポーンできます。または、到達可能なエリアを簡単に検索できます。

これは静的データであるため、事前計算できます。

凸から凸への変化がある画像検索ポイントを埋める必要がありました。視覚的には見つけやすいように見えます。次の2つの状況があります。

  1. 水平:オレンジから青に変わる場所。
  2. 垂直:赤が緑とオレンジに変わるところ。

ここに画像の説明を入力してください


これは機能しません。右下、特に赤い領域のブロブを見てください。完全に凸状であるため、別の色を使用するための変更はありませんが、右端の赤の下端から下端の赤の右端まで直線が存在しないことは明らかです。
ローレンペクテル

@Loren Pechtelこれは手作りです、あなたは正しいです、そこにエラーがあります、それは私のせいです、しかしあなたはそれがオレンジから青への移行と同じ状況であることを理解できます。
ブラウ

@Loren Pechtel、黄色のようなエリアでの出現を避けることを忘れないでください。この方法により、プレイヤーがいる同じエリアで敵を解放した場合、確実に到達可能になります。もちろん、実装するのは難しいかもしれませんが、アイデアは有効です。
ブラウ

あなたの改訂は役に立ちません。凸曲線上の2点は、それらの間に法的な直線(期間)を持ちません。それ以上の部門は助けにはなりません。
ローレンペクテル

エリアまたはポイントのセットを指す凸の定義を確認してください... en.wikipedia.org/wiki/Convex
Blau

1

ここに私が実際に自分のゲームで使用したもの(2dシンプレックスノイズが生成された世界、ほぼあなたのものと同じ)-レイ。プレイヤーから始めて、向きを決め(必要に応じてランダム)、何かにぶつかるまで(画面の端または小惑星)その線に沿って進みます。画面の端(小惑星や白い小塊ではない)を叩くと、画面の端からプレーヤーまでまっすぐな開いた線があることがわかります。その後、ヒットしたポイントでモンスターを生成します。小惑星にぶつかった場合は、テストをやり直してください。


0

使用できる別の(GPU以外の)ソリューションは、パス検索です。マップを描画する前に、マップの各エッジの潜在的な各スポーンポイントからパスを検索し、中心へのパスがあるかどうかを確認します。A *パス検索はコスト/パフォーマンスに関してはかなり問題ありませんが、問題があればゲームを開始する前にこれを行うことができます。

パスを持たないスポーンポイントは、除外リストに追加できます。またはその逆(パスを持つ任意のポイントを包含リストに入れることができます)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.