我想检查worker
对象何时接近任何fence
对象,以便打印通知消息。 “紧密”是指worker
与fence
的边界之间20个像素的距离。例如,“ distances_list = worker.get_dist(fences)”之类的东西,其中“ distances_list”将包含所有栅栏的当前距离。
我可以采用建议的方法here,但是也许我的任务有一些内置功能?
在pygame
中,我们可以使用不同的碰撞检测功能,但是在上述情况下,没有碰撞。是否有任何内置函数可以查找精灵之间的距离?
import pygame, random
import sys
WHITE = (255, 255, 255)
GREEN = (20, 255, 140)
GREY = (210, 210 ,210)
RED = (255, 0, 0)
PURPLE = (255, 0, 255)
SCREENWIDTH=1000
SCREENHEIGHT=578
IMG_BACKGROUND = "background.jpg"
IMG_WORKER_RUNNING = "images/workers/worker_1.png"
IMG_WORKER_IDLE = "images/workers/worker_2.png"
IMG_WORKER_ACCIDENT = "images/workers/accident.png"
class Background(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the background to be actually in the back
self._layer = -1
pygame.sprite.Sprite.__init__(self, groups)
# let's resize the background image now and only once
self.image = pygame.transform.scale(pygame.image.load(image_file).convert(), (SCREENWIDTH, SCREENHEIGHT))
self.rect = self.image.get_rect(topleft=location)
class GeoFenceInfluenceZone(pygame.sprite.Sprite):
def __init__(self, rect, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
self._layer = 0
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.surface.Surface((rect.width, rect.height))
self.image.fill(GREY)
self.rect = rect
class GeoFence(pygame.sprite.Sprite):
def __init__(self, rect, risk_level, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
self._layer = 1
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.surface.Surface((rect.width, rect.height))
self.image.fill(GREEN)
self.rect = rect
self.risk_level = risk_level
self.font = pygame.font.SysFont('Arial', 20)
text = self.font.render(risk_level, 1, (255,0,0), GREEN)
text_rect = text.get_rect(center=(rect.width/2, rect.height/2))
self.image.blit(text, text_rect)
class Worker(pygame.sprite.Sprite):
# we introduce to possible states: RUNNING and IDLE
RUNNING = 0
IDLE = 1
ACCIDENT = 2
NUMBER_OF_ACCIDENTS = 0
def __init__(self, image_running, image_idle, image_accident, location, *groups):
self.font = pygame.font.SysFont('Arial', 10)
# each state has it's own image
self.images = {
Worker.RUNNING: pygame.transform.scale(get_image(image_running), (45, 45)),
Worker.IDLE: pygame.transform.scale(get_image(image_idle), (20, 45)),
Worker.ACCIDENT: pygame.transform.scale(get_image(image_accident), (40, 40))
}
# we set a _layer attribute before adding this sprite to the sprite groups
# we want the workers on top
self._layer = 2
pygame.sprite.Sprite.__init__(self, groups)
# let's keep track of the state and how long we are in this state already
self.state = Worker.IDLE
self.ticks_in_state = 0
self.image = self.images[self.state]
self.rect = self.image.get_rect(topleft=location)
self.direction = pygame.math.Vector2(0, 0)
self.speed = random.randint(1, 3)
self.set_random_direction()
def set_random_direction(self):
# random new direction or standing still
vec = pygame.math.Vector2(random.randint(-100,100), random.randint(-100,100)) if random.randint(0, 5) > 1 else pygame.math.Vector2(0, 0)
# check the new vector and decide if we are running or fooling around
length = vec.length()
speed = sum(abs(int(v)) for v in vec.normalize() * self.speed) if length > 0 else 0
if (length == 0 or speed == 0) and (self.state != Worker.ACCIDENT):
new_state = Worker.IDLE
self.direction = pygame.math.Vector2(0, 0)
elif self.state != Worker.ACCIDENT:
new_state = Worker.RUNNING
self.direction = vec.normalize()
else:
new_state = Worker.ACCIDENT
self.ticks_in_state = 0
self.state = new_state
# use the right image for the current state
self.image = self.images[self.state]
def update(self, screen):
self.ticks_in_state += 1
# the longer we are in a certain state, the more likely is we change direction
if random.randint(0, self.ticks_in_state) > 70:
self.set_random_direction()
# now let's multiply our direction with our speed and move the rect
vec = [int(v) for v in self.direction * self.speed]
self.rect.move_ip(*vec)
# if we're going outside the screen, change direction
if not screen.get_rect().contains(self.rect):
self.direction = self.direction * -1
# spritecollide returns a list of all sprites in the group that collide with
# the given sprite, but if the sprite is in this group itself, we have
# to ignore a collision with itself
if any(s for s in pygame.sprite.spritecollide(self, building_materials, False) if s != self):
self.direction = self.direction * -1
if any(s for s in pygame.sprite.spritecollide(self, machines, False) if s != self):
self.direction = self.direction * -1
# Risk handling
self.handle_risks()
self.rect.clamp_ip(screen.get_rect())
def handle_risks(self):
for s in pygame.sprite.spritecollide(self, fences, False):
if s != self:
self.speed = 0
self.state = Worker.ACCIDENT
self.image = self.images[self.state]
Worker.NUMBER_OF_ACCIDENTS += 1
class BuildingMaterials(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
self._layer = 2
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.transform.scale(pygame.image.load(image_file).convert_alpha(), (40, 40))
self.rect = self.image.get_rect(topleft=location)
class Excavator(pygame.sprite.Sprite):
def __init__(self, image_file, location, *groups):
# we set a _layer attribute before adding this sprite to the sprite groups
self._layer = 3
pygame.sprite.Sprite.__init__(self, groups)
self.image = pygame.transform.scale(pygame.image.load(image_file).convert_alpha(), (170, 170))
self.rect = self.image.get_rect(topleft=location)
image_cache = {}
def get_image(key):
if not key in image_cache:
image_cache[key] = pygame.image.load(key)
return image_cache[key]
pygame.init()
# currently, one group would be enough
# but if you want to use some collision handling in the future
# it's best to group all sprites into special groups (no pun intended)
all_sprites = pygame.sprite.LayeredUpdates()
workers = pygame.sprite.Group()
building_materials = pygame.sprite.Group()
fences = pygame.sprite.Group()
fences_infl_zones = pygame.sprite.Group()
screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT))
pygame.display.set_caption("TEST")
# create multiple workers
for pos in ((30,30), (50, 400), (200, 100), (700, 200)):
Worker(IMG_WORKER_RUNNING, IMG_WORKER_IDLE, IMG_WORKER_ACCIDENT, pos, all_sprites, workers, building_materials, machines, fences)
# create multiple building material stocks
for pos in ((50,460),(50,500),(100,500),(850,30),(800,30)):
BuildingMaterials("images/materials/building_blocks{}.png".format(random.randint(1,3)), pos, all_sprites, building_materials)
# create multiple geo-fences
risks = ["H","M","L"]
for rect in (pygame.Rect(510,150,75,52), pygame.Rect(450,250,68,40), pygame.Rect(450,370,68,48),
pygame.Rect(0,0,20,SCREENHEIGHT),pygame.Rect(0,0,SCREENWIDTH,20),
pygame.Rect(SCREENWIDTH-20,0,20,SCREENHEIGHT),pygame.Rect(0,SCREENHEIGHT-20,SCREENWIDTH,20)):
risk = risks[random.randint(0,2)]
GeoFence(rect, risk, all_sprites, fences)
# create influence zones for all geo-fences
for rect in (pygame.Rect(495,135,105,80), pygame.Rect(435,235,98,68), pygame.Rect(435,355,98,76)):
GeoFenceInfluenceZone(rect, all_sprites, fences_infl_zones)
# and the background
Background(IMG_BACKGROUND, [0,0], all_sprites)
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
if event.type==pygame.QUIT:
carryOn = False
pygame.display.quit()
pygame.quit()
quit()
all_sprites.update(screen)
all_sprites.draw(screen)
pygame.display.flip()
clock.tick(20)
答案 0 :(得分:1)
此处this solution已移植到pygame。 pygame中的y轴是翻转的,因此我不得不交换top
和bottom
变量,并且可以使用math.hypot
计算距离。您需要将两个精灵的rect
传递到rect_distance
,在这里它们将被解压缩为x1, y1
(左上方)和x1b, y1b
(右下方)变量。 (您可以在窗口标题中看到距离。)
import math
import pygame as pg
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 50))
self.image.fill(pg.Color('dodgerblue1'))
self.rect = self.image.get_rect(topleft=pos)
def rect_distance(rect1, rect2):
x1, y1 = rect1.topleft
x1b, y1b = rect1.bottomright
x2, y2 = rect2.topleft
x2b, y2b = rect2.bottomright
left = x2b < x1
right = x1b < x2
top = y2b < y1
bottom = y1b < y2
if bottom and left:
print('bottom left')
return math.hypot(x2b-x1, y2-y1b)
elif left and top:
print('top left')
return math.hypot(x2b-x1, y2b-y1)
elif top and right:
print('top right')
return math.hypot(x2-x1b, y2b-y1)
elif right and bottom:
print('bottom right')
return math.hypot(x2-x1b, y2-y1b)
elif left:
print('left')
return x1 - x2b
elif right:
print('right')
return x2 - x1b
elif top:
print('top')
return y1 - y2b
elif bottom:
print('bottom')
return y2 - y1b
else: # rectangles intersect
print('intersection')
return 0.
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
all_sprites = pg.sprite.Group()
player = Player((50, 80), all_sprites)
player2 = Player((100, 200), all_sprites)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
elif event.type == pg.MOUSEMOTION:
player.rect.topleft = event.pos
distance = rect_distance(player.rect, player2.rect)
pg.display.set_caption(str(distance))
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()