Search code examples
pythonpygame

I'm having trouble getting a sprite.rect of each instance of draw.line in LineSprite class for collision purposes


As each instance of LineSprite draw.line() is at different angles (depending on start & end mouse pos), and the image.get_rect() takes up more space than line drawn, I want to mask the drawn line to use for collisions between different sprite groups. I've tried various spritecollide's but it doesn't recognize the line as a sprite or having a rect. I can get a collision using a the rect.clipline() but I have to add each instance to a list and look through the list but I can't reverse engineer it to figure out which instance of LineSprite was hit to remove (kill()) from screen.

import sys
import pygame as pg
import math


WIDTH = 800
HEIGHT = 800
FPS = 60

WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)


class Player(pg.sprite.Sprite):
    def __init__(self, game, x, y):
        self.groups = game.all_sprites
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.image = pg.Surface((20, 20))
        self.image.fill(WHITE)
        self.rect = self.image.get_rect()
        self.x = x
        self.y = y

    def update(self):
        self.rect.x = self.x
        self.rect.y = self.y


class LineSprite(pg.sprite.Sprite):
    def __init__(self, game, screen, start_pos, end_pos):
        self.groups = game.all_sprites, game.lines
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.screen = screen
        self.start_pos = start_pos
        self.end_pos = end_pos
        self.length = abs(((self.end_pos[0] - self.start_pos[0]) ** 2 + (self.end_pos[1] - self.start_pos[1]) ** 2) ** 0.5) - 1
        self.image = pg.Surface((self.start_pos[0] + self.length, self.start_pos[1] + self.length))
        self.image.set_colorkey((0, 0, 0))
        self.rect = self.image.get_rect()
        self.border = pg.Rect(self.start_pos[0], self.start_pos[1], self.end_pos[0]-self.start_pos[0], self.end_pos[1]-self.start_pos[1])
        self.border.normalize()
        pg.draw.line(self.image, GREEN, (self.start_pos), (self.end_pos), 5)
        self.game.linelist.append(((self.start_pos), (self.end_pos)))
        pg.draw.rect(self.image, RED, self.border, 5)
        # self.screen.blit(self.image, self.rect)

   def remove(self):
        self.kill()


class BlockSprite(pg.sprite.Sprite):
    def __init__(self, game, colour, x, y):
        self.groups = game.all_sprites, game.blocks
        pg.sprite.Sprite.__init__(self, self.groups)
        self.game = game
        self.colour = colour
        self.x = x
        self.y = y
        self.image = pg.Surface((20, 20))
        self.image.fill(self.colour)
        self.rect = self.image.get_rect()
        self.rect.x = self.x
        self.rect.y = self.y

    def remove(self):
        self.kill()


class Game:
    def __init__(self):
        pg.init()
        pg.display.set_caption("Wizard")
        self.screen = pg.display.set_mode((HEIGHT, WIDTH))
        self.clock = pg.time.Clock()
        self.load_data()
        self.linelist = []

    def load_data(self):
        pass

    def new(self):
        self.all_sprites = pg.sprite.Group()
        self.player = Player(self, 20, 20)
        self.lines = pg.sprite.Group()
        self.blocks = pg.sprite.Group()
        self.run()

    def run(self):
        self.playing = True
        while self.playing:
            self.mx, self.my = pg.mouse.get_pos()
            self.dt = self.clock.tick(FPS) / 1000
            self.events()
            self.update()
            self.draw()

    def quit(self):
        pg.quit()
        sys.exit()

    def update(self):
        self.player.x = self.mx - 10
        self.player.y = self.my - 10
        self.all_sprites.update()

    def draw_grid(self):
        for x in range(0, int(WIDTH / 20)):
            pg.draw.line(self.screen, WHITE, (x * 20, 0), (x * 20, HEIGHT))
        for y in range(0, int(HEIGHT / 20)):
            pg.draw.line(self.screen, WHITE, (0, y * 20), (WIDTH, y * 20))      

    def draw(self):
        self.screen.fill(BLUE)
        self.draw_grid()
        self.all_sprites.draw(self.screen)
        pg.display.flip() 

    def events(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.quit()
            if event.type == pg.KEYDOWN:
                if event.key == pg.K_ESCAPE:
                    self.quit()
            if event.key == pg.K_b:
                self.x, self.y = pg.mouse.get_pos()
                self.block = BlockSprite(self, GREEN, self.x - 10, self.y -10)

            if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
                self.start_pos = None
                self.end_pos = None
                self.start_pos = pg.mouse.get_pos()
        
            if event.type == pg.MOUSEBUTTONUP and event.button == 1:
                    self.end_pos = pg.mouse.get_pos()
                    self.line = LineSprite(self, self.screen, self.start_pos, self.end_pos)

            hits = pg.sprite.spritecollide(self.player, self.blocks, False)
            for hit in hits:
                if event.type == pg.MOUSEBUTTONDOWN and event.button == 3:
                    if hit.colour == GREEN:
                        col = RED
                    else:
                        col = GREEN
                    hit.remove() 
                    self.block = BlockSprite(self, col, hit.x, hit.y)
                if event.type == pg.KEYDOWN:
                    if event.key == pg.K_x:
                        hit.remove()
        
            for block in self.blocks:
                if any(block.rect.clipline(*line) for line in self.linelist):
                    print("hit")
                    # in self.lines - LineSprite.remove()
                                             
g=Game()
g.start_screen()
while True:     
    g.new()

Solution

  • Just use pygame.Rect.normalize:

    This will flip the width or height of a rectangle if it has a negative size. The rectangle will remain in the same place, with only the sides swapped.

    rect = pygame.Rect(x1, y1, x2-x1, y2-y1)
    rect.normalize()
    

    If you want to draw a rotated rectangle, see rotating a rectangle in pygame

    ]


    If you want to detect the collision between a rectangle and a line, see How do I check collision between a line and a rect in pygame?


    If you want to detect the collision of an image and a line, see Make a line as a sprite with its own collision in Pygame