Python Forum

Full Version: Making Player Sprite Ricochet of walls
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I have vector ("Asteroids") type movement and I want my player sprite to bounce of things they bump into.

It is best to use 2 .spritecollide checks, one for vertical walls and one for horizontal? That won't work for a tile that can be hit from both angles.

I'm sure it's been done a million times, but I could sure use help finding where to start. Can I somehow check what side of a tile is hit?
You can easily adapt a pong game to use. As the ball is meant to bounce of the top and bottom, and two paddles on right and left.
https://github.com/metulburr/pong/blob/m...all.py#L53

The part that actually changes the velocity is
 self.vel[1] *= -1
 self.vel[0] *= -1
which reverse the velocity of the objects direction once it hits another object, making it appear to bounce off objects like a ball.

By tile; do you mean tilemaps or do you mean a 4 sided object?

It doesnt matter what the angle the object that is being hit at. The angle of the object in which is initiating the hit, is. Unless you want to take into account of that angle too.
Use clipping rect size. Determine which is smaller. Width or Height. If even then you can says it hit both angles.
Then you can compare rect.centerx or rect.centery to collision rect.centerx or rect.centery.

I like displacement the best. It the easiest to start with.
Example.
import os
import pygame
import random
from pygame.sprite import Sprite, Group, spritecollide

class Block(Sprite):
    def __init__(self, image, position, group):
        Sprite.__init__(self)
        # Reference the image. Doesn't make new one
        self.image = image
        # Collision group reference
        self.group = group
        self.rect = image.get_rect()
        self.rect.topleft = position
        # Hold the floats position. Since rect only does int
        self.position = pygame.Vector2(position)
        self.direction = pygame.Vector2(
            (random.randint(0, 600) - 300) / 100 / Game.fps,
            (random.randint(0, 600) - 300) / 100 / Game.fps)

    def update(self):
        self.position += self.direction * Game.delta
        self.rect.topleft = self.position
        self.collision()

    def collision(self):
        self.wall_bounce()
        blocks = spritecollide(self, self.group, False)
        for block in blocks:
            if block != self:
                # Simple displacement
                w, h = self.rect.clip(block.rect).size
                if w < h:
                    if self.rect.centerx > block.rect.centerx:
                        self.rect.left = block.rect.right
                        self.position.x = self.rect.x
                        self.direction.x = abs(self.direction.x)
                        block.direction.x = -abs(block.direction.x)
                    else:
                        self.rect.right = block.rect.left
                        self.position.x = self.rect.x
                        self.direction.x = -abs(self.direction.x)
                        block.direction.x = abs(block.direction.x)
                else:
                    if self.rect.centery > block.rect.centery:
                        self.rect.top = block.rect.bottom
                        self.position.y = self.rect.y
                        self.direction.y = abs(self.direction.y)
                        block.direction.y = -abs(block.direction.y)
                    else:
                        self.rect.bottom = block.rect.top
                        self.position.y = self.rect.y
                        self.direction.y = -abs(self.direction.y)
                        block.direction.y = abs(block.direction.y)

    def wall_bounce(self):
        clamp = self.rect.clamp(Game.rect)

        if clamp.x != self.rect.x:
            self.rect.x = clamp.x
            self.position.x = clamp.x
            self.direction.x = -self.direction.x

        if clamp.y != self.rect.y:
            self.rect.y = clamp.y
            self.position.y = clamp.y
            self.direction.y = -self.direction.y

class Scene:
    def __init__(self):
        self.sprites = Group()
        self.create_block_images()
        self.add_blocks(15)

    def add_blocks(self, number):
        for n in range(number):
            image = random.choice(self.block_images)
            position = (random.randint(30, Game.rect.width - 30),
                        random.randint(30, Game.rect.height - 30))

            block = Block(image, position, self.sprites)
            block.add(self.sprites)

    def create_block_images(self):
        self.block_images = []

        for x in range(20):
            color = pygame.Color('black')
            while color.r < 100 and color.g < 100 and color.b < 100:
                color.r = random.randint(0, 255)
                color.g = random.randint(0, 255)
                color.b = random.randint(0, 255)

            low, high = 20, 30
            size = random.randint(low, high), random.randint(low, high)
            surface = pygame.Surface(size)
            surface.fill(color)
            self.block_images.append(surface)

    def draw(self, surface):
        self.sprites.draw(surface)
        self.sprites.update()

# Namespace for pygame game loop
class Game:
    @classmethod
    def setup(cls, title, width, height):
        # Basic pygame setup
        pygame.display.set_caption(title)
        cls.surface = pygame.display.set_mode((width, height))
        cls.rect = cls.surface.get_rect()
        cls.clock = pygame.time.Clock()
        cls.delta = 0
        cls.fps = 30

        cls.scene = Scene()

    @classmethod
    def mainloop(cls):
        cls.running = True
        while cls.running:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    cls.running = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_ESCAPE:
                        cls.running = False

            cls.surface.fill(pygame.Color('black'))
            cls.scene.draw(cls.surface)
            pygame.display.flip()
            cls.delta = cls.clock.tick(cls.fps)

if __name__ == '__main__':
    pygame.init()
    os.environ['SDL_VIDEO_CENTERED'] = '1'
    Game.setup('Block Bouncing', 800, 600)
    Game.mainloop()
    pygame.quit()
(Jun-03-2019, 12:28 PM)metulburr Wrote: [ -> ]By tile; do you mean tilemaps or do you mean a 4 sided object?

It's txt file that is read and translated into a map of 32X32 tile sprites. I'll look at the pong code and see what it is, but if there are sprite groups for walls and paddles, that won't be useful if there is a single tile in the room that can be hit from all sides. Will it??


(Jun-03-2019, 02:03 PM)Windspar Wrote: [ -> ]Use clipping rect size. Determine which is smaller. Width or Height. If even then you can says it hit both angles.
Then you can compare rect.centerx or rect.centery to collision rect.centerx or rect.centery.

ok... I think i sort of get it. Long story short, if the smallest of the remainders of the rect.centery or rect.centerx comparison will determine if it is a vertical surface or a horizontal surface?



I'll look into all of this and see where I get.

Thanks guys
Here. I try to explain it better.
Displacement collision. Moving Object hit a Stationary Object.
1. get the clipping rect size
width, height = object.rect.clip(collision.rect).size
2. Determine the Axis.
if width < height:
    # X Axis
else:
    # Y Axis
3. Determine which side. Displace object and Change direction.
# Determine Axis X or Y
if width < height:
    # X Axis
    # Determine which side
    if object.rect.centerx > collision.rect.centerx:
        # Displace object
        object.rect.left = collision.rect.right
    else:
        # Displace object
        object.rect.right = collision.rect.left

    # Change direction
    object.direction.x = -object.direction.x
else:
    # Y Axis
    # ...