如何找到Sprite和屏幕角之间的距离(以像素为单位)?

时间:2018-08-10 11:52:49

标签: python python-3.x pygame

我需要找到动画的worker对象与屏幕的4个角(左上,右上,左下,右下)之间的距离(以像素为单位)。 pygame的哪个功能可提供此信息?我需要在每次update迭代中获取此信息。

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)

1 个答案:

答案 0 :(得分:2)

您可以为拐角创建点矢量,然后减去精灵的位置(我在这里仅使用鼠标pos)以获得指向拐角的矢量,最后调用length方法以获取这些向量的长度。

import pygame as pg
from pygame.math import Vector2


pg.init()
screen = pg.display.set_mode((640, 480))
width, height = screen.get_size()
clock = pg.time.Clock()
BG_COLOR = pg.Color('gray12')
# Create point vectors for the corners.
corners = [
    Vector2(0, 0), Vector2(width, 0),
    Vector2(0, height), Vector2(width, height),
    ]

done = False
while not done:
    for event in pg.event.get():
        if event.type == pg.QUIT:
            done = True

    mouse_pos = pg.mouse.get_pos()
    # Subtract the position from the point vectors and call the `length`
    # method of the resulting vectors to get the distances to the points.
    # Subtract the position from the point vectors and call the `length`
    # method of the resulting vectors to get the distances to the points.
    distances = []
    for vec in corners:
        distance = (vec - mouse_pos).length()
        distances.append(distance)

    # The 4 lines above can be replaced by a list comprehension.
    # distances = [(vec - mouse_pos).length() for vec in corners]

    # I just display the distances in the window title here.
    pg.display.set_caption('{:.1f}, {:.1f}, {:.1f}, {:.1f}'.format(*distances))
    screen.fill(BG_COLOR)
    pg.display.flip()
    clock.tick(60)