視覚的に魅力的で直感的な方法でラベルを広げるアルゴリズム


24

短縮版

車両ラベルを重複しない方法で配布し、参照する車両のできるだけ近くに配置するための設計パターンはありますか?そうでない場合、私が提案する方法のいずれかが実行可能ですか?これを自分でどのように実装しますか?

拡張版

私が書いているゲームでは、空飛ぶ車両の鳥瞰図を持っています。また、各車両の隣には、車両に関する重要なデータが記載された小さなラベルがあります。これは実際のスクリーンショットです。

ラベルの付いた2台の車両

現在、車両は異なる高度で飛行する可能性があるため、アイコンが重なる可能性があります。ただし、ラベルが重複しないようにします(または、車両「A」のラベルが車両「B」のアイコンに重複することはありません)。

現在、スプライト間の衝突を検出することができ、問題のラベルを、それ以外の場合はオーバーラップしているスプライトとは反対の方向に押すだけです。これはほとんどの状況で機能しますが、空域が混雑すると、代替の「スマート」な代替手段があったとしても、ラベルが車両から非常に遠くに押し出される可能性があります。たとえば、次のようになります:

  B - label
A -----------label
  C - label

取得する方が良い場所(=車両に近いラベル):

          B - label
label - A
          C - label

編集:また、重複車両の場合のほかに、車両のラベルAが重複する可能性のある他の構成が存在する可能性があることも考慮する必要があります(ASCIIアートの例は、たとえば、のラベルがBおよびC)。

現在の状況を改善する方法について2つのアイデアがありますが、それらを実装する時間を費やす前に、アドバイスを求めてコミュニティに頼ることを考えました(結局、そのためのデザインパターンが存在する可能性がある「共通の十分な問題」のようです)。

それが価値があるものについて、私が考えていた2つのアイデアがあります:

ラベルスペースのスロット化

このシナリオでは、すべての画面をラベル用の「スロット」に分割します。次に、各車両のラベルは常に最も近い空のラベルに配置されます(空=その場所に他のスプライトはありません)。

スパイラル検索

画面上の車両の位置から、重複しない位置が見つかるまで、角度を大きくしてから半径を大きくしてラベルを配置しようとします。以下のようなもの:

try 0°, 10px
try 10°, 10px
try 20°, 10px
...
try 350°, 10px
try 0°, 20px
try 10°, 20px
...

1
一度にいくつの平面が重なる可能性がありますか?
ワンバーガー

1
@wangburger-これは関連性があるとは思っていませんでしたが(あなたの考えをもっと知りたいと思うでしょう)、答えは次のとおりです。それはプレイヤーのゲーム戦略に依存します。技術的には、世界では24台の車両がオーバーラップする可能性がありますが、ほとんどのゲーム条件での現実的な数値は3〜4です。
mac

3
ラベルを平面に対して相対的に移動させることは、静的ではあるものの妥当な時間をかけて重ねることよりも混乱しませんか?
マイクセンダー

3
en.wikipedia.org/wiki/に興味があるかもしれません -これは簡単な問題ではありません。完璧なソリューションを見つけることを期待しないでください。
ブレッキ

2
GraphVizは、視覚的に快適な方法でグラフをレイアウトするための一連のツールであり、ラベルの重複を避ける傾向があります。直接使用できない場合もありますが、グラフのレイアウトに使用するアルゴリズムの種類に関するドキュメントまたはソースコードから情報を収集できる場合があります。たとえば、エネルギーベースのモデルとバネベースのモデルの両方があるようです。
ラースヴィクルンド

回答:


14

基本的に、この問題は衝突回避の問題に似ています。はい、飛行機は異なる高度で飛行できますが、ラベルはすべて同じ「高度」です。

Unaligned Collision Avoidanceのようなアルゴリズムがあり、これはあなたにとって正しい方向への一歩です。もちろん、あなたの状況では、ラベルは飛行機に「つながれ」ているので、動きの範囲は限られています。

群れ行動を見ると、群れの最初の「ルール」である短距離反発を実装したいと考えています。ただし、最近隣から離れる方向に「ステアリング」する代わりに、ラベルの配置場所として「離れた」ベクトルを使用します。

例えば:

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

大きな黒い円は影響範囲を表し、緑の円はラベルの有効な配置を表します。中央の緑の点は現在検討中の平面であり、小さな緑の点はラベルの配置に選択した円上の点です。

これで、黒い点は他のラベルまたは他の平面を表すことができます。どちらが最適かはわかりませんが、他のラベルである場合は、より良い回避策が得られるかもしれませんが、わかりません。明らかに、「力」の矢印は、現在の平面と「影響を受けるオブジェクト」の間の方向ベクトルです。最後に、ボックスはラベルです。

上記の例を使用すると、次のような結果が得られると思います。

            - label
           / 
          B 
label - A
          C 
           \
            - label

この方法を使用すると、3つの平面が垂直に配置されるなど、特別なケースを作成する必要がある状況がいくつかあります。

          - label       label -
          |                   |
          B                   B
  label - A                   A - label
          C                   C
          |                   |
          - label       label -

ラベルコーナーの定義方法によっては、3つのラベルすべてが右から左にフロップする可能性があります。基本的には、ラベルがどのコーナーから描画されるかを切り替えることができる円の周りの角度に注意する必要があります:0、90、180、270。

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

最終的には、ラベルがお互いを避けるのを見て、これは一種のきちんとしたものになると思います。気が散りすぎる場合は、10度単位で四捨五入して動きを少なくすることができます。

奇妙な詳細については申し訳ありませんが、私がゲーム用に放射状メニューを作成するときに考えたもののほとんどは、この「動的な」形式ではかなりうまくいくと思います。


1
実際、私の例では、x座標とz座標が正確に一致しているため、互いの真上にある平面も考慮していません(ただし、floatを使用している場合、これは起こりません)。私が与えた例は、互いに近い平面のものです。さらに、前述したように、上の画像の黒い点を他のラベルと考えることもできます。
マイケルハウス

1
ああ、コメントを削除したようです。
マイケルハウス

1
以前の[不十分に定式化され、現在削除されている]コメントで私が意味したことは、ラベルが他の移動不可能な(車両などの)スプライトによって「閉じ込められる/囲まれる」というシナリオがあることです。この場合、ラベルはおそらく囲み円の外側に「ジャンプ」するはずですが、これがどのように起こるかは明確ではありません。ところで:私はもともとあなたの写真の緑の点がいくつかの飛行機が上下に積み重なっていると思っていましたが、あなたのコメントの後、私は間違っていることに気づきました...ごめんなさい!)
mac

1
ああ、なるほど。そのため、黒い点を両方のプレーンとラベルとして扱います。最初の反復で見つかった位置が適切でない場合、半径を2倍にして再度確認します。または、大部分の群衆から離れた方向を指すベクトルが既にある場合は、動作する場所が見つかるまでそれをたどることができます。ただし、最初の方法の方が良い結果が得られると思います。
マイケルハウス

9

少し考えて、私は最終的に、最初の質問で簡単に説明したスパイラル検索方法を実装することにしました。

原理は、Byte56のメソッドは特定の条件に対して特別な処理を必要とするのに対し、スパイラル検索は必要とせず、非常にコンパクトな方法でコーディングすることです。また、スプラーリング検索では、ラベルを配置するために車両近い場所を見つけることが重要です。これは、マップを読みやすくするための主な要因です。

しかし、有用であるだけでなく、非常によく書かれているので、引き続き彼の答えを支持してください!

以下は、スパイラルコードで達成された結果のスクリーンショットです。

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

そして、以下は自己完結型ではありませんが、実装がいかに簡単かを示すコードです。

def place_tags(self):
    for tag in self.tags:
        start_angle = tag.angle
        while not tag.place() or is_colliding(tag):  #See note n.1
            tag.angle = (tag.angle + angle_step) % 360
            if tag.angle == start_angle:
                tag.radius += radius_step
        tag.connector.update()                       #See note n.2

注1 - tag.place()タグがスクリーン/レーダーの可視領域に完全にある場合にTrueを返します。そのため、その行は「タグがレーダーの外側にあるか、他のタグと重複している場合、ループを維持します...」のようになります。

注2 - tag.connector.updateテキスト情報とラベル/タグに飛行機のアイコンを結ぶ線を描画する方法です。


よくやった、それは確かに非常にコンパクトです。称賛をありがとう。また、あなたがやったことについて投稿してくれたことに感謝します。それは、後で答えを探している人にとって常に便利です。スクリーンショットから、非常にうまく機能しているようです!答えは必ず受け入れてください。それが最終的な目的です。
マイケルハウス

@ Byte56-「レトロ賞賛」をありがとう;)私はまだあなたのソリューションもコーディングして結果を比較したいので、受け入れられた答えを選択するのを待っています。1つには、あなたのソリューションがより長いコードを実行するだけでなく、より高速なコードを実行する可能性があると思われます。また、非常に詰め込まれた空域で2つがどのように比較されるかを確認したいので、まだアルゴリズムでコードをリリースできる可能性があります。この空間を見て!;)
mac

@ Byte56-アルゴリズムの実装に取り​​組みました。低密度でうまく動作しましたが、スペースがぎゅうぎゅう詰めになるとすぐに、ラベルが他のラベルのブロックを「ジャンプ」したり、非-移動可能な(平面アイコン)スプライト。この回答を選択済みとしてマークしますが、再度:時間と貢献に感謝します!:)
mac
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.