[Python] How can I prevent bouncing entities from phasing through walls in pygame? [duplicate]

Discussão em 'Python' iniciado por Stack, Maio 7, 2025.

  1. Stack

    Stack Membro Participativo

    I've been having a lot of issues working out collision for my pygame project. I'm making a little simulation/game where horses bounce around an arena in random directions from their collisions, where the arena and horses use pixel masks for their collision. Currently, if a horse collides with the arena or another horse, the horse will reflect back in the opposite direction with 40 degrees of variance.

    MirroredAngle = (180 - self.direction)
    self.direction = MirroredAngle + random.randint(-20,20)


    Unfortunately, the horses are prone to randomly clipping and phasing through walls, despite all the measures I've taken to prevent this.

    Full code:

    #settingup

    import pygame
    import math
    import random
    import os
    from time import sleep
    clock = pygame.time.Clock()
    pygame.init()
    screen = pygame.display.set_mode((629,400))
    pygame.display.set_caption("Horse Race")

    countdown = False
    paused = False

    #assetgallery
    background = pygame.image.load("assets/backgrounds/paddock.png")
    background = pygame.transform.scale(background,(629,400))
    arena1img = pygame.image.load("assets/arenas/arena1.png")
    arena1img = pygame.transform.scale(arena1img,(629,400))
    arena2img = pygame.image.load("assets/arenas/arena2.png")
    arena2img = pygame.transform.scale(arena2img,(629,400))
    arena3img = pygame.image.load("assets/arenas/arena3.png")
    arena3img = pygame.transform.scale(arena3img,(629,400))
    arena1mask = pygame.mask.from_surface(arena1img.convert_alpha(),1)
    arena2mask = pygame.mask.from_surface(arena2img.convert_alpha(),1)
    gate = pygame.image.load("assets/general/GATE.png")

    class Arena(pygame.sprite.Sprite):
    def __init__(self,arena,name):
    pygame.sprite.Sprite.__init__(self)
    self.image = arena
    self.name = name
    self.mask = pygame.mask.from_surface(self.image.convert_alpha())
    self.rect = arena.get_rect()
    self.rect.topleft = (0,0)


    allgroup = pygame.sprite.Group()

    alllist = []

    '''
    def iterateangles(tsthorse):
    foundgoodangle = False
    angletoreturn = None
    while True:
    if foundgoodangle == True:
    break
    else:
    randangle = random.randint(0,360)
    theoreticalstepx = tsthorse.x + (4*math.sin(math.radians(tsthorse.direction)))
    theoreticalstepy = tsthorse.y + (4*math.cos(math.radians(tsthorse.direction)))
    theoreticalrect = tsthorse.image.get_rect()
    theoreticalrect.topleft = (theoreticalstepx,theoreticalstepy)
    theoreticalmask = pygame.mask.from_surface(tsthorse.image)
    screen.blit(theoreticalmask.to_surface(None,None,None,(255,255,255,255),(0,0,0,255)),(theoreticalrect.x,theoreticalrect.y))
    for x in alllist:
    offsrt = (theoreticalrect.x - x.rect.x), (theoreticalrect.y - x.rect.y)
    if x.mask.overlap(theoreticalmask,offsrt):
    pass
    else:
    foundgoodangle = True
    angletoreturn = randangle

    return angletoreturn
    '''



    #horses
    class Horse(pygame.sprite.Sprite):
    def __init__(self,color,x,y,direction,name):
    pygame.sprite.Sprite.__init__(self)
    self.x = x
    self.y = y
    self.direction = direction
    self.color = color
    self.name = name
    self.archivex = self.x
    self.archivey = self.y
    if self.direction > 44 and self.direction < 135:
    self.image = pygame.image.load(f"assets/horses/{self.color}/down.png")
    self.cardinal = "down"
    elif self.direction > 134 and self.direction < 220:
    self.image = pygame.image.load(f"assets/horses/{self.color}/left.png")
    self.cardinal = "left"
    elif self.direction > 219 and self.direction < 310:
    self.image = pygame.image.load(f"assets/horses/{self.color}/up.png")
    self.cardinal = "up"
    elif self.direction > 310 or self.direction < 45:
    self.image = pygame.image.load(f"assets/horses/{self.color}/right.png")
    self.cardinal = "right"


    self.image = pygame.transform.scale(self.image.convert_alpha(),(25,25))
    self.mask = pygame.mask.from_surface(self.image)
    self.rect = self.image.get_rect()
    self.rect.topleft = (self.x,self.y)

    def update(self):
    if self.direction > 44 and self.direction < 135:
    self.image = pygame.image.load(f"assets/horses/{self.color}/down.png")
    self.cardinal = "down"
    elif self.direction > 134 and self.direction < 220:
    self.image = pygame.image.load(f"assets/horses/{self.color}/left.png")
    self.cardinal = "left"
    elif self.direction > 219 and self.direction < 310:
    self.image = pygame.image.load(f"assets/horses/{self.color}/up.png")
    self.cardinal = "up"
    elif self.direction > 310 or self.direction < 45:
    self.image = pygame.image.load(f"assets/horses/{self.color}/right.png")
    self.cardinal = "right"
    self.image = pygame.transform.scale(self.image.convert_alpha(),(25,25))
    self.mask = pygame.mask.from_surface(self.image)
    self.rect = self.image.get_rect()
    self.rect.topleft = (self.x,self.y)
    #collisions
    for x in alllist:
    if x != self:
    offsrt = (self.rect.x - x.rect.x), (self.rect.y - x.rect.y)
    if x.mask.overlap(self.mask,offsrt):
    if isinstance(x,Horse):
    print(self.name + " has ran into " + x.name)
    x.x = x.archivex
    x.y = x.archivey


    self.x = self.archivex
    self.y = self.archivey
    #self.direction = iterateangles(self)
    # print("collision between " + self.name +" and " + x.name)
    MirroredAngle = (180 - self.direction)
    self.direction = MirroredAngle + random.randint(-20,20)
    '''
    if self.cardinal == "right":
    self.direction += (90)
    elif self.cardinal == "down":
    self.direction -= (180)
    elif self.cardinal == "left":
    self.direction -= (90)
    else:
    self.direction += (180)
    '''
    if self.direction > 360:
    self.direction -= 360
    elif self.direction < 0:
    self.direction = 360 + self.direction
    #print(self.direction)
    else:
    self.archivex = self.x
    self.archivey = self.y





    #arenas
    arena2 = Arena(arena3img,"arena 3")


    activearena = arena2



    #horses
    orange = Horse("orange",20,30,90,"Wonderful Introspective Morning")
    green = Horse("green",60,30,170,"Pay Dock")
    brown = Horse("brown",100,30,60,"Data Same Day")
    pink = Horse("pink",20,70,90,"Troy Matthews")
    cyan = Horse("cyan",60,70,100,"Cynical Chew Toy")

    alllist.append(orange)
    #alllist.append(green)
    #alllist.append(brown)
    #alllist.append(pink)
    #alllist.append(cyan)
    alllist.append(arena2)

    horsegroup = pygame.sprite.Group()

    horsegroup.add(orange)
    horsegroup.add(green)
    horsegroup.add(brown)
    horsegroup.add(pink)
    horsegroup.add(cyan)


    def movehorses():
    for obj in horsegroup:
    # print("hi im going " + str(obj.direction))

    obj.x += (2*math.sin(math.radians(obj.direction)))
    obj.y += (2*math.cos(math.radians(obj.direction)))
    obj.update()


    def showhorses():
    for obj in horsegroup:
    screen.blit(obj.image,(obj.x,obj.y))
    #screen.blit(obj.mask.to_surface(None,None,None,(255,255,255,255),(0,0,0,255)),(obj.x,obj.y))



    while True:
    if countdown == False and paused == False:
    movehorses()
    screen.blit(background,(0,0))
    screen.blit(arena3img,(0,0))
    #screen.blit(arena2.mask.to_surface(None,None,None,(255,255,255,255),(0,0,0,255)),(0,0))
    showhorses()
    for event in pygame.event.get():
    if event.type == pygame.QUIT:
    pygame.quit()
    pygame.display.flip()
    clock.tick(60)





    I've tried making it so that the horses are set back to their last position whenever they are in a collision, but that only helped slightly and did not fully phase out the clipping. I've tried reducing the horse count to only one horse, which stopped the clipping, but when I tried to reintroduce the other horses without them being able to collide with each other, they still phased through the walls.

    Here is a video of this in effect: Video

    Continue reading...

Compartilhe esta Página