46

光源をサポートしたい、タイルベースの小さなゲームを書いています。しかし、私のアルゴリズム-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 - - - - - - -

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

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

テイカーはいますか?

4

12 に答える 12

22

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

この件に関するローグライク wiki 記事へのリンクは次のとおりです

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

「Permissive Field of View」も人気を得ているようです: http://roguebasin.roguelikedevelopment.org/index.php?title=Permissive_Field_of_View

于 2008-10-06T15:35:42.633 に答える
18

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

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

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

于 2008-10-06T15:18:20.173 に答える
6

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

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

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

于 2008-10-06T15:24:56.947 に答える
5

速くて汚い:

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

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

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

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

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

于 2008-10-06T15:09:28.693 に答える
4

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

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

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

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

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

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

シーン内の光源ごとに繰り返します。

于 2008-10-06T16:19:16.900 に答える
3

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

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

于 2008-10-06T15:12:09.157 に答える
3

これは、画面上のタイルの数に線形時間を使用する、非常に単純ですがかなり効果的なアプローチです。各タイルは不透明または透明 (指定されている) のいずれかであり、それぞれが表示または陰影付けされています (これを計算しようとしています)。

アバター自体を「可視」としてマークすることから始めます。

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

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

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

9876789
8543458
7421247
6310136
7421247
8543458
9876789

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

結果は美しい影付けではありませんが、信じられないほどのタイルの可視性を計算します。

于 2012-07-24T17:14:27.513 に答える
2

私は実際、この機能を私のプロジェクトの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位置として定義されています。

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

于 2009-01-30T23:57:39.027 に答える
2

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

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

編集:

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

于 2008-10-06T15:23:17.230 に答える
1

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

于 2014-03-14T17:04:16.857 に答える
0

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

于 2008-10-06T15:14:50.320 に答える