【问题标题】:How to test if areas overlap (Python)如何测试区域是否重叠(Python)
【发布时间】:2016-12-06 19:49:58
【问题描述】:

我是一名编码新手,正在编写 Python 程序以使用 Pygame 库模拟自然选择。

我正在尝试完成的一件事是使重叠的移动椭圆(或者如果这太复杂,则为矩形)对象产生一个继承其特征的子对象。

我的问题是我无法创建一个工作代码来识别任何两个对象区域何时重叠。我需要代码来识别两个对象何时穿过路径(并且暂时重叠),以便游戏知道生成另一个形状。

有一次我尝试了一个我认为可行的复杂嵌套 for 循环,但这导致模拟崩溃。

这是我的代码:

import pygame, random

# Define some colors
black = (0, 0, 0)
white = (255, 255, 255)
red = (255, 0, 0)
green = (0, 255, 0)
blue = (0, 0, 255)

pygame.init()

# Set the width and height of the screen
map_width = 800
map_height = 800
size = [map_width, map_height]
screen = pygame.display.set_mode(size)

# Display name
pygame.display.set_caption("Natural Selection Game")

# Loop until the user clicks the close button.
done = False

# Used to manage how fast the screen updates
clock = pygame.time.Clock()

# -------- Organism -----------
class Organism:

    def __init__(self):
        # Speed and direction
        self.change_x = random.randrange(0,6)
        self.change_y = random.randrange(0,6)

        # Dimensions
        self.width = random.randrange(5,50)
        self.height = random.randrange(5,50)

        # Diet (WIP)
        self.color = random.choice([red, green, blue])

        # Starting position
        self.x = random.randrange(0 + self.width, 800 - self.width)
        self.y = random.randrange(0 + self.height, 800 - self.height)

    # Spawn
    def spawn(self, screen):
        pygame.draw.ellipse(screen, self.color, [self.x, self.y, self.width, self.height])

    # Initiate movement
    def move(self):
        self.x += self.change_x
        self.y += self.change_y

    # Bounce 
    def bounce(self, map_width, map_height):
        if self.x < 0 or self.x > map_width - self.width:
            self.change_x = self.change_x * -1
        if self.y < 0 or self.y > map_height - self.height:
            self.change_y = self.change_y * -1

# Initial spawning conditions
org_list = []
org_number = 15

for i in range(0,org_number):
    org = Organism()
    org_list.append(org)

# -------- Main Program Loop -----------
while not done:
    # ---- Event Processing ----
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            done = True

    # ---- Logic ----
    # Movement
    for i in org_list:
        i.move()
        i.bounce(map_width, map_height)

    # Reproduction

    # ---- Drawing ----
    # Set the screen background
    screen.fill(white)

    # Spawn organisms
    for i in org_list:
        i.spawn(screen)

    # --- Wrap-up
    # Limit to 60 frames per second
    clock.tick(60)

    # Go ahead and update the screen with what we've drawn.
    pygame.display.flip()

# Close everything down
pygame.quit()

任何见解将不胜感激。

【问题讨论】:

  • 如果您可以使用 MSPaint 或其他您正在寻找的示例,那就太好了。我想我明白了,但我不确定确定
  • 你的形状可以旋转还是有固定的方向?
  • 您没有发布导致模拟崩溃的代码,是吗? (我没有看到嵌套的 for 循环)。
  • 形状不旋转,它们是固定方向但在屏幕上移动。不久前我摆脱了嵌套的 for 循环,甚至还没有想到寻求帮助,因为我认为这毫无意义。
  • 这正是 Pygame Rect 类的用途:pygame.org/docs/ref/rect.html#pygame.Rect.colliderect

标签: python pygame area rectangles ellipse


【解决方案1】:

你应该使用pygame.Rect来保持位置和大小,然后你可以使用Pygame函数来检查碰撞。

one_rect.colliderect(second_rect)

one_rect.coolidepoint(mouse_position)

在文档中查看pygame.Rect的其他功能


Pygame 还有pygame.sprite.Group 来保存一组精灵并检查所有精灵的碰撞。

【讨论】:

    【解决方案2】:

    在我看来,给你的有机体一个 self.id = random.randomrange(11111,99999) 可能有助于确定两个有机体是相同的还是只是在相同的位置具有相同的尺寸 - 虽然可能有一个独特的属性可以访问有机体对象,您可能需要确定性的 id 生成方法,因此不会出现无意相等的可能性。

    您需要构建一个接收两个有机体的 spawn 函数,或者在您的有机体对象上实现您的 spawn 方法,以便它可以将 spawn 伙伴作为参数,这样就可以访问每个有机体的属性并将它们混合在一起,然后创造后代。所以我假设OrganismA.spawn(OrganismB) 将创建一个OrganismA-B spawn 的后代。

    您可以在下面看到重叠逻辑。如果以下四个逻辑表达式有效,则两个框重叠。椭圆的数学运算会有所不同,但如果您愿意,可以对椭圆使用矩形近似值。我们将从 A 和 B 作为有机体对象开始,并定义它们的最小值和最大值 x 和 y。所以 minXB 表示 b 的最小 x 坐标。

    maxXB = B.x - B.width/2
    maxXA = A.x - A.width/2
    minYB = B.y - B.height/2
    ...etc.. below are the equalites that hold for overlapping boxes
    maxXB > minXA
    maxYB > minYA
    maxYA > minYB
    maxXA > minXA
    

    为了提高效率,我建议您按地图的某些部分划分有机体。示例象限,其中 0-400 宽度 0-400 高度中的每个生物都在象限“I”等。从那里你将有更好的表现,当你做如下的事情。

    def quadrant(Organism):
        if (Organism.x < 400 and Organism.y < 400):
            return 'III'
        elif (...
    
    def spawnOrganisms():
        # create org_subLists for each quadrant
        org_subLists = { 'I': [], 'II': [], 'III': [], 'IV': [] }
        for i in org_list:
            org_quadrant = quadrant(i)
            org_subLists[org_quadrant].append(i)
    
        for quad in org_subLists:
            # only spawn progeny for lowerID to higerID parir to prevent A-B and B-A from each spawning the same/a similar org and avoid spawining an A-A spawn
            for orgA in org_subLists[quad]:
                for orgB in org_subLists[quad]:
                    if (orgA.id < orgB.id):
                        if(maxXB > minXA and maxYB > minYA and maxYA > minYB and maxXA > minXA):
                            orgA.spawn(orgB)
    

    【讨论】:

      【解决方案3】:

      您应该为您的精灵使用 pygame 的 Sprite 类,因为它使您的整个应用程序更易于管理。

      我们不需要spawnmovebounce,我们只需使用单个函数update,它将be called 通过Group,而我们使用它而不是简单的列表来保存我们的对象。

      每个 Sprite 都有自己的图像,我们在其上画一个省略号。然后我们创建一个Maskdo pixel perfect collision

      这是一个完整的运行示例。请注意,我用我自己的替换了所有的 cmets,解释了发生了什么:

      import pygame, random
      
      pygame.init()
      
      map_width = 800
      map_height = 800
      size = [map_width, map_height]
      screen = pygame.display.set_mode(size)
      
      # pygame already defines a lot of colors, we we just use them
      colors = pygame.color.THECOLORS
      pygame.display.set_caption("Natural Selection Game")
      done = False
      clock = pygame.time.Clock()
      
      # just a simple generator to generate an id for each object 
      def id_generator():
          i = 0
          while True:
              i += 1
              yield i
      
      ids = id_generator()
      
      # just a helper function that wraps pygame.sprite.collide_mask
      # to prevent a sprite from colliding with itself
      def collide(a, b):
          if a.id == b.id:
              return False
          return pygame.sprite.collide_mask(a, b)
      
      class Organism(pygame.sprite.Sprite):
      
          def __init__(self, id, org_list, color = None):
              pygame.sprite.Sprite.__init__(self, org_list)
              self.org_list = org_list
              self.id = id
              # Speed and direction
              self.change_x = random.randrange(0,6)
              self.change_y = random.randrange(0,6)
      
              # Dimensions
              width = random.randrange(5,50)
              height = random.randrange(5,50)
              x = random.randrange(0 + width, map_width - width)
              y = random.randrange(0 + height, map_height - height)
              self.rect = pygame.rect.Rect(x, y, width, height)
              self.image = pygame.surface.Surface((width, height))
              self.image.fill(colors['hotpink2'])
              self.image.set_colorkey(colors['hotpink2'])
      
              # we either pass in the color, or create a random one
              self.color = color or random.choice([colors['red'], colors['green'], colors['blue']])
              pygame.draw.ellipse(self.image, self.color, [0, 0, width, height])
              self.mask = pygame.mask.from_surface(self.image)
      
              # we keep track of collisions currently happening
              # so we only spawn one children for each collisions  
              self.collisions = set()
      
              # just something to limit the number of organisms
              self.age = 0
              self.children = 0
      
          # Initiate movement
          def update(self):
              self.age += 1
      
              # we move by simply moving the rect
              # the Group's draw function will look that the rect attribute 
              # to determine the position for drawing the image 
              self.rect.move_ip(self.change_x, self.change_y)
      
              # we can make use of a lot of Rect's attributes to make 
              # this computation simpler
              if self.rect.left < 0 or self.rect.right > map_width:
                  self.change_x *= -1
      
              if self.rect.top < 0 or self.rect.bottom > map_height:
                  self.change_y *= -1
      
              # only reproduce if we are at least 200 ticks old
              # so newly created organisms spwan new ones at the
              # very moment they spawned themself
              if self.age < 200:
                  return
      
              # just a narbitary limit so the screen does not get too full
              if self.age > 500:
                  print str(self.id) + ' died of age'
      
                  # kill() removes the Sprite from all its Groups (which is only org_list at the moment)
                  self.kill()
                  return
      
              # just an arbitary limit so the screen does not get too full 
              if self.children > 4:
                  print str(self.id) + ' died of too many children'
                  self.kill()
                  return
      
              # check if we collided with another Sprite
              collided = pygame.sprite.spritecollideany(self, self.org_list, collide)
      
              # also check if this 
              # - is a new collision
              # - the other organism is at least 200 ticks old
              # - there are not too many organisms at the screen at the moment
              if collided and not collided.id in self.collisions and collided.age > 200 and len(self.org_list) < 100:
      
                  # keep track of the current collision, so this code is not triggerd 
                  # every frame while the colliding organisms move other each other
                  self.collisions.add(collided.id)
                  collided.collisions.add(self.id)
                  print str(self.id) + ' collided with ' + str(collided.id)
      
                  # we create a new color out of the colors of the parents
                  r, g, b = (self.color[0] + collided.color[0]) / 2, \
                            (self.color[1] + collided.color[1]) / 2, \
                            (self.color[2] + collided.color[2]) / 2
                  color = [r, g, b]
      
                  # let the color mutate sometimes for fun
                  if random.randrange(0, 100) < 10:
                      color[random.randrange(0, 3)] = random.randrange(0, 256)
                      print 'Offspring of ' + str(self.id) + ' and ' + str(collided.id) + ' mutates'
      
                  # create the new child with the new color
                  Organism(next(ids), self.org_list, map(int, color))
                  self.children += 1
                  collided.children += 1
              else:
                  # if there are currently no collisions, we clear the collisions set
                  # so new collisions can happen
                  self.collisions = set()
      
      # we use a Group for all the draw/update/collision magic
      org_list = pygame.sprite.Group()
      
      for _ in range(15):
          Organism(next(ids), org_list)
      
      while not done:
          for event in pygame.event.get():
              if event.type == pygame.QUIT:
                  done = True
      
          # we just call update on the group so update is called
          # an every sprite in the group
          org_list.update()
      
          screen.fill(colors['white'])
      
          # same for drawing: just call draw on the group
          org_list.draw(screen)
      
          clock.tick(60)
          pygame.display.flip()
      
      pygame.quit()
      

      我对屏幕上同时显示的对象数量设置了一些任意限制。如果您想加快碰撞检测,请跳过像素完美碰撞检测并使用基于 rect 或 circle 的。但最大的改进可能是使用四叉树碰撞检测算法(但这超出了此答案的范围;只需 google 即可)。

      【讨论】:

      • 谢谢!我尝试更新它以使用 Python 3。但是,一旦第一个对象发生碰撞,我始终会收到一条错误消息,显示第 129 行和第 54 行的“颜色参数无效”。
      • @ChristopherCostello 是的,因为在 Python 3 中,map 不返回一个列表,而是一个迭代器。只需将其包装在列表调用中,例如 Organism(next(ids), self.org_list, list(map(int, color)))
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-01-13
      相关资源
      最近更新 更多