web-dev-qa-db-ja.com

タイルベースのゲームでどのタイルが点灯するかを計算する(「レイトレーシング」)

光源をサポートしたいタイルベースの小さなゲームを書いています。しかし、私のアルゴリズム-fuは弱すぎるので、助けを求めてあなたのところに来ます。

状況は次のようになります。タイルベースのマップ(2D配列として保持)があり、単一の光源といくつかのアイテムが周りに立っています。どのタイルが光源によって照らされ、どのタイルが影になっているのかを計算したいと思います。

おおよそ、それがどのように見えるかを視覚的に補助します。 Lは光源、Xは光を遮るアイテム、0は点灯しているタイル、-sは影になっているタイルです。

0 0 0 0 0 0 - - 0
0 0 0 0 0 0 - 0 0
0 0 0 0 0 X 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 0 L 0 0 0 0
0 0 0 0 0 0 0 0 0
0 0 0 X X X X 0 0
0 0 0 - - - - - 0
0 0 - - - - - - -

もちろん、部分的に隠されているためにタイルが半分影になっている可能性があるフラクショナルシステムはさらに優れています。アルゴリズムは完全である必要はありません-明らかに間違っていなくて、適度に速いだけではありません。

(もちろん、複数の光源がありますが、それは単なるループです。)

テイカーはいますか?

43
Zarkonnen

ローグライク開発コミュニティは、視線、視野アルゴリズムに少し執着しています。

この件に関するローグライクウィキの記事へのリンクは次のとおりです。 http://roguebasin.roguelikedevelopment.org/index.php?title=Field_of_Vision

私のローグライクゲームでは、Pythonでシャドウキャスティングアルゴリズム( http://roguebasin.roguelikedevelopment.org/index.php?title=Shadow_casting )を実装しました。まとめるのは少し複雑でしたが、(純粋なPythonでも)かなり効率的に実行され、素晴らしい結果が得られました。

「許容視野」も人気を集めているようです: http://roguebasin.roguelikedevelopment.org/index.php?title=Permissive_Field_of_View

21
Dana

オクルージョンの計算などであらゆる種類の複雑さに入ることができます。または、単純なブルートフォース法を使用することもできます。すべてのセルに対して、 ブレゼンハム線アルゴリズム などの線描画アルゴリズムを使用してすべてのセルを調べます。現在のものと光源の間。塗りつぶされたセル、または(光源が1つしかない場合は)すでにテストされて影になっていることが判明したセルがある場合、セルは影になっています。点灯していることがわかっているセルに遭遇すると、セルも同様に点灯します。これに対する簡単な最適化は、ラインに沿って遭遇するセルの状態を、最終的な結果が何であれ、設定することです。

これは多かれ少なかれ私が私の 2004 IOCCC受賞エントリー で使用したものです。明らかに、それは良いサンプルコードにはなりません。 ;)

編集:ローレンが指摘しているように、これらの最適化では、トレースするマップのエッジに沿ってピクセルを選択するだけで済みます。

16
Nick Johnson

ここに示されているアルゴリズムは、必要と思われるよりも多くの計算を行っているように見えます。私はこれをテストしていませんが、うまくいくと思います:

最初に、すべてのピクセルを点灯としてマークします。

マップの端にあるすべてのピクセルについて:Arachnidが提案したように、ブレゼンハムを使用して、ピクセルから光までの線をトレースします。その線が障害物に当たった場合は、エッジから障害物のすぐ先までのすべてのピクセルを影になっているものとしてマークします。

6
Loren Pechtel

速くて汚い:

(アレイの大きさによって異なります)

  • 各タイルをループします
  • 光に線を引く
  • 線のいずれかの部分がXに当たると、影になります
  • (オプション):線が通過するXの量を計算し、凝った計算を行って、影のタイルの比率を決定します。注意:これは、しきい値処理中にタイルとライトの間の線をアンチエイリアスすることで実行できます(したがって、光源に戻るルートに沿って他のタイルを見る)。これらは小さな異常として表示されます。使用するロジックによっては、タイルが影になっている量(あるとしても)を特定できる可能性があります。

また、どのピクセルがテストされたかを追跡することもできるため、ソリューションを少し最適化し、ピクセルを2回再テストしないでください。

これは、画像操作を使用し、ピクセル(タイル)の間に直線を描画することで、かなりうまくドーム状になる可能性があります。線が半透明で、Xブロックが再び半透明の場合。画像にしきい値を設定して、線が「X」と交差しているかどうかを判断できます。

サードパーティのツールを使用するオプションがある場合は、おそらくそれを使用します。長い目で見れば、それはより速いことがわかるかもしれませんが、あなたはあなたのゲームについてあまり理解していないでしょう。

5
TK.

これはただの楽しみです:

最初にタイルを線に変換する手順を実行すると、Doom3アプローチを2Dで複製できます。例えば、

- - - - -
- X X X -
- X X - -
- X - - -
- - - - L

...ソリッドオブジェクトの角を三角形で結ぶ3本の線に縮小されます。

次に、Doom 3エンジンが行うことを実行します。光源の観点から、光に面する各「壁」を検討します。 (このシーンでは、対角線のみが考慮されます。)このような各線について、前端が元の線で、側面が光源から各終点を通る線上にあり、後端が台形である台形に投影します。遠く、シーン全体を過ぎて。つまり、光を「指す」のは台形です。壁が影を落とすすべてのスペースが含まれています。この台形のすべてのタイルを暗闇で満たします。

そのようなすべての線を進むと、光源から見えるすべてのタイルを含む「ステンシル」ができあがります。これらのタイルを明るい色で塗りつぶします。ソースから離れるとき(「減衰」)、または他の凝ったことをするときに、タイルを少し明るくしたいと思うかもしれません。

シーン内のすべての光源に対して繰り返します。

4
Kevin Conner

タイルが影になっているかどうかを確認するには、光源に直線を引く必要があります。線が占有されている別のタイルと交差する場合、テストしていたタイルは影になっています。レイトレーシングアルゴリズムは、ビュー内のすべてのオブジェクト(この場合はタイル)に対してこれを実行します。

ウィキペディアの レイトレーシング記事 には擬似コードがあります。

3
Bill the Lizard

これは、画面上のタイルの数に線形時間を使用する、非常に単純ですがかなり効果的なアプローチです。各タイルは不透明または透明(私たちに与えられたもの)であり、それぞれを表示またはシェーディングすることができます(これが私たちが計算しようとしていることです)。

まず、アバター自体を「表示」としてマークします。

次に、この再帰ルールを適用して、残りのタイルの可視性を判断します。

  1. タイルがアバターと同じ行または列にある場合、アバターに近い隣接するタイルが表示され、透明である場合にのみ表示されます。
  2. タイルがアバターから45度の対角線上にある場合、隣接する対角線のタイル(アバターに向かって)が表示され、透明である場合にのみ表示されます。
  3. 他のすべての場合では、問題のタイルよりもアバターに近い3つの隣接するタイルを検討してください。たとえば、このタイルが(x、y)にあり、アバターの右上にある場合、考慮すべき3つのタイルは(x-1、y)、(x、y-1)、および(x- 1、y-1)。問題のタイルは、これら3つのタイルのanyが表示され、透明である場合に表示されます。

これを機能させるには、タイルを特定の順序で検査して、再帰的なケースがすでに計算されていることを確認する必要があります。これは、0(アバター自体)から始まり、カウントアップする作業順序の例です。

9876789
8543458
7421247
6310136
7421247
8543458
9876789

同じ番号のタイルは、それらの間で任意の順序で検査できます。

結果は美しいシャドウキャスティングではありませんが、信頼できるタイルの可視性を計算します。

3
pents90

これは何年も前の質問ですが、このスタイルのものを探している人には、私がかつて自分のローグライクゲームに使用したソリューションを提供したいと思います。手動で「事前計算された」FOV。光源の視野に最大の外距離がある場合、オブジェクトをブロックすることによって作成された影を手で描くことは実際にはそれほど労力を要しません。円の1/8(および直線方向と対角線方向)を描画するだけで済みます。あなたは他のeigthsのためにsymmertyを使うことができます。円の1/8に正方形があるのと同じ数のシャドウマップがあります。次に、オブジェクトに応じてOR一緒に)します。

これの3つの主な長所は次のとおりです。1。正しく実装すれば非常に迅速です。2。シャドウをどのようにキャストするかを決定できます。どのアルゴリズムがどの状況を最適に処理するかを比較する必要はありません。3。奇妙なアルゴリズムによって引き起こされるエッジケースはありません。どういうわけか修正

短所は、楽しいアルゴリズムを実際に実装することができないということです。

2
Guest

私は実際、最近この機能を自分のプロジェクトの1つに書き込んだばかりです。

void Battle::CheckSensorRange(Unit* unit,bool fog){
    int sensorRange = 0;
    for(int i=0; i < unit->GetSensorSlots(); i++){
        if(unit->GetSensorSlot(i)->GetSlotEmpty() == false){
            sensorRange += unit->GetSensorSlot(i)->GetSensor()->GetRange()+1;
        }
    }
    int originX = unit->GetUnitX();
    int originY = unit->GetUnitY();

    float lineLength;
    vector <Place> maxCircle;

    //get a circle around the unit
    for(int i = originX - sensorRange; i < originX + sensorRange; i++){
        if(i < 0){
            continue;
        }
        for(int j = originY - sensorRange; j < originY + sensorRange; j++){
            if(j < 0){
                continue;
            }
            lineLength = sqrt( (float)((originX - i)*(originX - i)) + (float)((originY - j)*(originY - j)));
            if(lineLength < (float)sensorRange){
                Place tmp;
                tmp.x = i;
                tmp.y = j;
                maxCircle.Push_back(tmp);
            }
        }
    }

    //if we're supposed to fog everything we don't have to do any fancy calculations
    if(fog){
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            Map->GetGrid(maxCircle[circleI].x,maxCircle[circleI].y)->SetFog(fog);
        }
    }else{

        bool LOSCheck = true;
        vector <bool> placeCheck;

        //have to check all of the tiles to begin with 
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            placeCheck.Push_back(true);
        }

        //for all tiles in the circle, check LOS
        for(int circleI = 0; circleI < (int) maxCircle.size(); circleI++){
            vector<Place> lineTiles;
            lineTiles = line(originX, originY, maxCircle[circleI].x, maxCircle[circleI].y);

            //check each tile in the line for LOS
            for(int lineI = 0; lineI < (int) lineTiles.size(); lineI++){
                if(false == CheckPlaceLOS(lineTiles[lineI], unit)){
                    LOSCheck = false;

                    //mark this tile not to be checked again
                    placeCheck[circleI] = false;
                }
                if(false == LOSCheck){
                    break;
                }
            }

            if(LOSCheck){
                Map->GetGrid(maxCircle[circleI].x,maxCircle[circleI].y)->SetFog(fog);
            }else{
                LOSCheck = true;
            }
        }
    }

}

あなたがそれをあなた自身の使用のために適応させているならばあなたが必要としないであろういくつかの余分なものがそこにあります。タイプPlaceは、便宜上、xおよびy位置として定義されています。

ライン機能は、ウィキペディアからわずかな変更を加えたものです。 x y座標を出力する代わりに、ライン内のすべてのポイントを含む場所ベクトルを返すように変更しました。 CheckPlaceLOS関数は、タイルにオブジェクトがあるかどうかに基づいてtrueまたはfalseを返すだけです。これで実行できる最適化は他にもいくつかありますが、これは私のニーズには問題ありません。

2
DShook

TKのソリューションは、この種のことに対して一般的に使用するソリューションです。

部分照明のシナリオでは、タイルが影になっている場合、そのタイルを4つのタイルに分割し、それぞれをテストするようにすることができます。次に、それを好きなだけ分割できますか?

編集:

ライトに隣接するタイルをテストしないことで、少し最適化することもできます。これは、複数の光源がある場合に行うことがより重要になると思います...

2
Carl

単一のC関数にタイルベースの視野を実装しました。ここにあります: https://Gist.github.com/zloedi/9551625

1
Stoiko

これを再発明/再実装するために時間を費やしたくない場合は、そこにたくさんのゲームエンジンがあります。 Ogre3D は、照明、サウンド、ゲームのコントロールを完全にサポートするオープンソースのゲームエンジンです。

0
Scottie T