11

私は Pygame でいくつかの 2D ゲームに取り組んでいます。複数のオブジェクトを交差させずに同時にランダムに配置する必要があります。いくつかの明白な方法を試しましたが、うまくいきませんでした。

明らかなメソッドは次のとおりです(疑似):

create list of objects
for object in list:
    for other object in list:
        if object collides with other object:
            create new list of objects

その方法は永遠にかかりました。

私が試した他の方法:

create list of objects
for object in list:
    for other object in list:
        if object collides with other object:
             remove object from list

そのメソッドはほとんど空のリストを返しました。

私は 2 ~ 20 オブジェクトの大きなリストを扱っています。助言がありますか?

編集:長方形はすべてランダムな異なるサイズです。

4

7 に答える 7

16

任意の長方形ではなくランダムな非衝突の正方形を生成するように変更できるかどうかについてのフォローアップの質問に対処するために、回答を少し変更しました。これは、元の回答の長方形の出力を後処理し、その内容を正方形のサブ領域に変換するという、最も簡単な方法で行いました。また、オプションの視覚化コードを更新して、両方の種類の出力を表示しました。明らかに、この種のフィルタリングを拡張して、各長方形または正方形をわずかに挿入して、それらが互いに接触しないようにするなどの他のことを行うことができます。

私の答えは、すでに投稿されている回答の多くが行うことを避けています。これは、既に作成されたものと衝突するものを拒否しながら、ランダムに長方形を生成します。これは、本質的に遅く、計算が無駄に聞こえるからです。私のアプローチは、代わりに、そもそも重複しないものだけを生成することに集中しています。

それは、それを非常に迅速に実行できる単純な領域細分割問題に変えることによって、実行する必要があることを比較的単純にします。以下は、それを行う方法の1つの実装です。それは、4つの小さな重なり合わない長方形に分割する外側の境界を定義する長方形から始まります。これは、半ランダムな内部ポイントを選択し、それを外側の長方形の4つの既存のコーナーポイントと一緒に使用して、4つのサブセクションを形成することによって実現されます。

ほとんどのアクションは関数内で行われquadsect()ます。内部ポイントの選択は、出力がどのように見えるかを決定する上で重要です。少なくとも特定の最小の幅または高さ、またはある量以下のサブ長方形になるものを選択するなど、任意の方法で制限できます。私の回答のサンプルコードでは、外側の長方形の幅と高さの中心点± 1 / 3として定義されていますが、基本的にはどの内側の点でもある程度機能します。

このアルゴリズムは非常に高速にサブ長方形を生成するため、内部分割点を決定するために計算時間を費やしても問題ありません。

このアプローチの結果を視覚化するために、最後にPIL(Python Imaging Library)モジュールを使用して、私が行ったいくつかのテスト実行中に生成された長方形を表示する画像ファイルを作成する、必須ではないコードがいくつかあります。

とにかく、コードと出力サンプルの最新バージョンは次のとおりです。

import random
from random import randint
random.seed()

NUM_RECTS = 20
REGION = Rect(0, 0, 640, 480)

class Point(object):
    def __init__(self, x, y):
        self.x, self.y = x, y

    @staticmethod
    def from_point(other):
        return Point(other.x, other.y)

class Rect(object):
    def __init__(self, x1, y1, x2, y2):
        minx, maxx = (x1,x2) if x1 < x2 else (x2,x1)
        miny, maxy = (y1,y2) if y1 < y2 else (y2,y1)
        self.min, self.max = Point(minx, miny), Point(maxx, maxy)

    @staticmethod
    def from_points(p1, p2):
        return Rect(p1.x, p1.y, p2.x, p2.y)

    width  = property(lambda self: self.max.x - self.min.x)
    height = property(lambda self: self.max.y - self.min.y)

plus_or_minus = lambda v: v * [-1, 1][(randint(0, 100) % 2)]  # equal chance +/-1

def quadsect(rect, factor):
    """ Subdivide given rectangle into four non-overlapping rectangles.
        'factor' is an integer representing the proportion of the width or
        height the deviatation from the center of the rectangle allowed.
    """
    # pick a point in the interior of given rectangle
    w, h = rect.width, rect.height  # cache properties
    center = Point(rect.min.x + (w // 2), rect.min.y + (h // 2))
    delta_x = plus_or_minus(randint(0, w // factor))
    delta_y = plus_or_minus(randint(0, h // factor))
    interior = Point(center.x + delta_x, center.y + delta_y)

    # create rectangles from the interior point and the corners of the outer one
    return [Rect(interior.x, interior.y, rect.min.x, rect.min.y),
            Rect(interior.x, interior.y, rect.max.x, rect.min.y),
            Rect(interior.x, interior.y, rect.max.x, rect.max.y),
            Rect(interior.x, interior.y, rect.min.x, rect.max.y)]

def square_subregion(rect):
    """ Return a square rectangle centered within the given rectangle """
    w, h = rect.width, rect.height  # cache properties
    if w < h:
        offset = (h - w) // 2
        return Rect(rect.min.x, rect.min.y+offset,
                    rect.max.x, rect.min.y+offset+w)
    else:
        offset = (w - h) // 2
        return Rect(rect.min.x+offset, rect.min.y,
                    rect.min.x+offset+h, rect.max.y)

# call quadsect() until at least the number of rects wanted has been generated
rects = [REGION]   # seed output list
while len(rects) <= NUM_RECTS:
    rects = [subrect for rect in rects
                        for subrect in quadsect(rect, 3)]

random.shuffle(rects)  # mix them up
sample = random.sample(rects, NUM_RECTS)  # select the desired number
print '%d out of the %d rectangles selected' % (NUM_RECTS, len(rects))

#################################################
# extra credit - create an image file showing results

from PIL import Image, ImageDraw

def gray(v): return tuple(int(v*255) for _ in range(3))

BLACK, DARK_GRAY, GRAY = gray(0), gray(.25), gray(.5)
LIGHT_GRAY, WHITE = gray(.75), gray(1)
RED, GREEN, BLUE = (255, 0, 0), (0, 255, 0), (0, 0, 255)
CYAN, MAGENTA, YELLOW = (0, 255, 255), (255, 0, 255), (255, 255, 0)
BACKGR, SQUARE_COLOR, RECT_COLOR = (245, 245, 87), (255, 73, 73), (37, 182, 249)

imgx, imgy = REGION.max.x + 1, REGION.max.y + 1
image = Image.new("RGB", (imgx, imgy), BACKGR)  # create color image
draw = ImageDraw.Draw(image)

def draw_rect(rect, fill=None, outline=WHITE):
    draw.rectangle([(rect.min.x, rect.min.y), (rect.max.x, rect.max.y)],
                   fill=fill, outline=outline)

# first draw outlines of all the non-overlapping rectanges generated
for rect in rects:
    draw_rect(rect, outline=LIGHT_GRAY)

# then draw the random sample of them selected
for rect in sample:
    draw_rect(rect, fill=RECT_COLOR, outline=WHITE)

# and lastly convert those into squares and re-draw them in another color
for rect in sample:
    draw_rect(square_subregion(rect), fill=SQUARE_COLOR, outline=WHITE)

filename = 'square_quadsections.png'
image.save(filename, "PNG")
print repr(filename), 'output image saved'

出力サンプル1

最初のサンプル出力画像

出力サンプル2

2番目のサンプル出力画像

于 2010-12-07T22:27:56.863 に答える
1

あなたの問題には非常に簡単な近似があり、私にとってはうまくいきました:

  • グリッドを定義します。たとえば、100 ピクセルのグリッドは (x,y) -> (int(x/100),int(y/100)) と記述します。グリッド要素は重なりません。
  • 各オブジェクトを異なるグリッドに配置するか (グリッド内でランダムに配置すると見栄えが良くなります)、いくつかのオブジェクトを重ねることができる場合は、各グリッドにいくつかのオブジェクトをランダムに配置します。

これを使用して、2D マップ (ゼルダのようなもの) をランダムに生成しました。私のオブジェクトの画像は <100*100> より小さいので、サイズ <500*500> のグリッドを使用し、各グリッドに 1 ~ 6 個のオブジェクトを許可しました。

于 2011-09-09T14:24:09.027 に答える
1

3 つのアイデア:

オブジェクトのサイズを小さくする

最初の方法は、20 個の重複しないオブジェクトのランダムな配列にヒットする可能性が非常に低いため失敗します (実際(1-p)^200<p<1は 2 つのオブジェクトが衝突する確率です)。サイズを劇的に(桁違いのドラマで)減らすことができれば、役立つかもしれません。

それらを1つずつ選択します

最も明らかな改善点は次のとおりです。

while len(rectangles)<N:
    new_rectangle=get_random_rectangle()
    for rectangle in rectangles:
        if not any(intersects (rectangle, new_rectangle) for rectangle in rectangles)
            rectangles.add(new_rectangle)

これにより、パフォーマンスが大幅に向上します。交差が 1 つあるからといって、まったく新しいセットを生成する必要はなく、別の 1 つの四角形を選択するだけです。

事前計算

ゲームでこれらのセットをどのくらいの頻度で使用しますか? 毎秒異なるセットを使用することは、1 時間に 1 回セットを使用することとは異なるシナリオです。これらのセットを頻繁に使用しない場合は、ゲーマーが同じセットを 2 回目にすることがないように、十分な大きさのセットを事前に計算してください。事前計算するときは、費やされた時間をあまり気にしません (そのため、非効率的な最初のアルゴリズムを使用することさえできます)。

実行時に実際にこれらの四角形が必要な場合でも、CPU が何らかの理由でアイドル状態のときに、必要になる前に少し計算して、常にセットを手元に用意しておくことをお勧めします。

実行時に、セットをランダムに選択します。これは、おそらくリアルタイム ゲームの最良のアプローチです。

注:(x, y)このソリューションでは、四角形がスペースを節約する方法 (座標 のペアなど) で保持されていることを前提としています。これらのペアはほとんどスペースを消費せず、妥当なサイズのファイルで実際に数千、さらには数百万を節約できます。

便利なリンク:

于 2010-12-07T05:52:59.933 に答える
0

試しましたか:

Until there are enough objects:
    create new object
    if it doesn't collide with anything in the list:
        add it to the list

リスト全体を再作成したり、衝突に関係するすべてのものを取り出したりしても意味がありません。

別のアイデアは、次のいずれかの方法で衝突を「修正」することです。

1) 交差領域の中心を見つけ、交差する代わりにコーナー/エッジに接触するように、交差する各四角形の適切なコーナーをそのポイントに調整します。

2) 長方形が何かと衝突した場合、その長方形のサブ領域をランダムに生成し、代わりにそれを試します。

于 2010-12-07T05:58:36.747 に答える
0

すでに述べたものの代替の擬似コード:

while not enough objects:
  place object randomly
  if overlaps with anything else:
    reduce size until it fits or has zero size
  if zero size: 
    remove

またはそのようなもの。

しかし、これには、意図したよりも小さなオブジェクトが作成される可能性があり、ほぼ交差する (つまり、接触する) オブジェクトが作成される可能性があるという利点があります。

プレイヤーが通過するマップの場合、パスがブロックされる可能性があるため、通過できない可能性があります。

于 2010-12-07T07:54:48.797 に答える
0
list_of_objects = []
for i in range(20):
    while True:
        new_object = create_object()
        if not any(collides(new_object, x) for x in list_of_objects):
            break
    list_of_objects.append(new_object)

私はあなたがすでにcreate_object()andcollides()関数を持っていると仮定します

これが何度もループする場合は、四角形のサイズを小さくする必要があるかもしれません

于 2010-12-07T05:51:26.710 に答える