Question

Je travaille sur des jeux 2D avec Pygame. Je dois placer plusieurs objets en même temps au hasard sans les intersection . J'ai essayé quelques méthodes évidentes, mais ils ne travaillaient pas.

Méthodes Obvious suivre (en pseudo):

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

Cette méthode a pris une éternité.

Autre méthode que j'ai essayé:

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

Cette méthode retourne près des listes vides.

Je fais face à une liste qui est partout 2-20 objets grand. Toutes les suggestions?

EDIT:. Les rectangles sont toutes tailles aléatoires différentes

Était-ce utile?

La solution

J'ai changé ma réponse un peu pour répondre à votre question de suivi pour savoir si elle pourrait être modifiée pour générer non entrer en collision au hasard à la place places plutôt que des rectangles arbitrairement. Je l'ai fait dans la manière la plus simple que je pourrais que travaillerais, qui devait post-traitement de la sortie rectangulaire de ma réponse originale et transformer son contenu en sous-régions carrées. Je également mis à jour le code de visualisation en option pour montrer les deux types de sortie. Il est évident que ce genre de filtrage pourrait être étendu à faire d'autres choses comme encartage chaque rectangle ou carré légèrement pour les empêcher de se toucher.

Ma réponse évite à faire ce que bon nombre des réponses déjà affichées font - à savoir générer au hasard des rectangles tout en rejetant toutes celles qui entrent en collision avec ceux déjà créés - parce que cela semble un peu lent par nature et informatiquement gaspillage. Au contraire, elle se concentre uniquement sur la génération ceux qui ne se chevauchent pas en premier lieu.

qui fait ce qui doit être fait relativement simple en le transformant en un problème de subdivision de zone qui peut être fait très rapidement. Voici une mise en œuvre de la façon dont cela pourrait se faire. Elle commence par un rectangle définissant la limite extérieure qui la divise en quatre petits rectangles ne se chevauchent pas. Cela est accompli en choisissant un point intérieur semi-aléatoire et l'utiliser en même temps que les quatre points d'angle existants du rectangle extérieur pour former les quatre sous-sections.

La plupart de la prise d'action dans thequadsect()function. Le choix du point intérieur est crucial pour déterminer ce que l'apparence de sortie comme. Vous pouvez contraindre comme vous le souhaitez, par exemple que la sélection qui se traduirait par des sous-rectangles d'au moins une certaine largeur minimale ou à la hauteur ou pas plus grand que une certaine quantité. Dans le code échantillon dans ma réponse, il est défini comme étant le point central ± 1 / 3 de la largeur et la hauteur du rectangle extérieur, mais au fond tout point intérieur travaillerait à un certain degré.

Étant donné que cet algorithme génère des sous-rectangles très rapidement, il est OK pour passer un peu de temps de calcul déterminer le point de division intérieure.

Pour aider à visualiser les résultats de cette approche, il y a un code non essentiel à la toute fin que utilise le module thePIL (Python Imaging Library) pour créer un fichier image affichant les rectangles générés au cours de certaines courses d'essai j'ai fait.

Quoi qu'il en soit, voici la dernière version du code et des échantillons de sortie:

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'

Sortie Exemple 1

première image de sortie de l'échantillon

Sortie Exemple 2

deuxième échantillon image de sortie

Autres conseils

Idées trois:

Diminuer la taille de vos objets

La première méthode échoue en raison de frapper un réseau aléatoire de 20 objets qui ne se chevauchent est hautement improbable (en fait (1-p)^20, où 0<p<1 est la probabilité de collision des deux objets). Si vous pouvez considérablement (ordres de grandeur dramatique) diminuent leur taille, il pourrait aider.

les sélectionner un par un

La plupart d'amélioration évidente serait:

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)

Cela améliorerait grandement vos performances, comme ayant une seule intersection ne vous forcera pas à générer un nouvel ensemble, juste pour choisir un autre rectangle unique.

Pré-calcul

Combien de fois allez-vous utiliser ces jeux dans votre jeu? L'utilisation d'un ensemble différent chaque seconde est un scénario différent que d'utiliser un ensemble une fois en une heure. Si vous n'utilisez pas ces jeux trop souvent, précalculer s ensemble suffisamment grande pour que le joueur aurait probablement jamais voir le même jeu deux fois. Lorsque pré-calcul, vous ne trop pas beaucoup de soin pour le temps passé (vous pouvez même utiliser votre premier algorithme inefficace).

Même si vous avez réellement besoin de ces rectangles au moment de l'exécution, il pourrait être une bonne idée de les calculer un peu avant de vous en avez besoin, lorsque le processeur est inactif pour une raison quelconque, de sorte que vous aurez toujours un ensemble prêt à main.

Au moment de l'exécution, il suffit de choisir un jeu au hasard. Ceci est probablement la meilleure approche pour les jeux en temps réel.

Remarque: Cette solution suppose que vous rectangles sont conservés dans un gain de place, par exemple des paires de coordonnées (x, y). Ces paires consomment très peu d'espace, et vous pouvez réellement sauver des milliers et des millions même, dans un fichier avec une taille raisonnable.

Liens utiles:

Il y a une approximation très simple à votre problème qui a bien fonctionné pour moi:

  • définir une grille. Par exemple, une grille de pixels de 100 écritures (x, y) -> (int (x / 100), int (a / 100)). Les éléments de grille ne se chevauchent pas.
  • Vous pouvez soit placer chaque objet dans une grille différente (au hasard dans la grille se penchera plus joli), soit mis au hasard quelques objets dans chaque grille, si vous pouvez autoriser quelques objets à se chevaucher.

I utilisé ceci pour générer de façon aléatoire une carte 2D (Zelda similaires). Les images de mes objets sont plus petits que <100 * 100>, de sorte que je grille de taille <500 * 500> et acceptés pour 1-6 objets dans chaque grille.

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)

Je suppose que vous avez déjà les fonctions de create_object() et collides()

Vous pouvez aussi avoir besoin de diminuer la taille des rects si cette boucles trop de fois

Avez-vous essayé:

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

Aucun sens de recréer la liste entière, ou en prenant tout ce qui est impliqué dans une collision.

Une autre idée est de collisions « fix » par l'une des approches suivantes:

1) Trouvez le centre de la zone d'intersection, et d'ajuster l'angle approprié de chaque intersection rect à ce point, de sorte qu'ils touchent maintenant dans le coin / bord au lieu de l'intersection.

2) Quand un rectangle entre en collision avec quelque chose, générer de façon aléatoire une sous-région de ce rectangle et essayer au lieu.

un autre pseudo-code, à ceux déjà mentionnés:

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

Ou quelque chose comme ça.

Mais cela a l'avantage de créer éventuellement des objets plus petits que vous aviez l'intention, et la création d'objets qui presque Intersection (à savoir toucher).

Si c'est une carte pour le joueur à traverser, ils peuvent toujours pas être en mesure de le traverser parce que leur chemin pourrait être bloqué.

Dans mon cas, j'ai eu un problème similaire, sauf que j'ai eu quelques rectangles de pré-sortie à l'intérieur du rectangle global. Ainsi, les nouveaux rectangles devaient être placés autour de ces ceux qui existent déjà.

J'utilisé une approche gourmande:

  • Rectangularize le rectangle global (global): créer une grille à partir de la triée x et y de coordonnées triée tous les rectangles jusqu'à présent. Alors que vous donnera une grille irrégulière (mais rectangulaire).
  • Pour chaque calcul des cellules de grille la zone, cela vous donne une matrice de zones.
  • Utilisez Kadanes 2D algorithme pour trouver la sous-matrice qui vous donne la superficie maximale (= le plus grand rectangle libre)
  • Placez un rectangle aléatoire dans cet espace libre
  • Répéter

Cela nécessite une conversion de votre espace de coordonnées d'origine vers / depuis l'espace de la grille, mais simple à faire.

(Notez que l'exécution Kadene directement sur l'original, rectangle global prend trop de temps. Passer par une approximation de la grille est beaucoup rapide pour mon application)

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top