Bouncy Balls

Bouncy Balls

Postby Wommbatt » Sun Dec 08, 2013 12:28 pm

Soooo yea, this is my first go about with pygame annnd, there's a lot to absorb.

I was following a tutorial on how to make a bouncing ball. No problem. But to be sure I understood what I was doing I decided to kick it up a bit and add more balls to it. Which I did fairly easily. Now, however, I want to make them bounce off each other as well as the walls.

I'm afraid that I've bitten off more than I can chew and with the plethora of answers out there that I've found. None of them seem to work, I suspect that's because I don't want to use draw to make shapes rather than just use "ball.gif".

So here's my code and I'll highlight my attempts to make balls bounce off each other:

It's not crazy long so please pardon the amount of code here.
Code: Select all
import pygame
import random
import sys

class Ball:
    def __init__(self, X, Y):
        self.velocity = [random.randint(-10, 10), random.randint(-10, 10)]
        self.ball_image = pygame.image.load('ball.gif')
        self.ball_boundary = self.ball_image.get_rect(center=(X, Y))

if __name__ == '__main__':
    width = 800
    height = 700
    MyClock = pygame.time.Clock()
    background_colour = 0, 0, 0
    pygame.init()
    frame = pygame.display.set_mode((width, height))
    pygame.display.set_caption("OH GOD, THE BALLZ!")

    num_balls = 5
    ball_list = []

    for i in range(num_balls):
        ball_list.append(Ball(random.randint(150, 600), random.randint(150, 500)))  # so none of the balls spawn in the walls
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit(0)
        frame.fill(background_colour)

        for ball in ball_list:  # wall collision
            if ball.ball_boundary.left < 0 or ball.ball_boundary.right > width:
                ball.velocity[0] = -1 * ball.velocity[0]
            if ball.ball_boundary.top < 0 or ball.ball_boundary.bottom > height:
                ball.velocity[1] = -1 * ball.velocity[1]

        for ball in ball_list:  # attempts at ball to ball collision
            i = 1
            if ball.ball_boundary.left > ball_list[i].ball_boundary.right:  # first two here check for left and right rect collisions
                ball.velocity[0] = -1 * ball.velocity[0]
            if ball.ball_boundary.right > ball_list[i].ball_boundary.left:
                ball.velocity[0] = -1 * ball.velocity[0]
            if ball.ball_boundary.top > ball_list[i].ball_boundary.bottom:  # these two check for top and bottom collisions
                ball.velocity[1] = -1 * ball.velocity[1]
            if ball.ball_boundary.bottom > ball_list[i].ball_boundary.top:
                ball.velocity[1] = -1 * ball.velocity[1]
                i += 1

            ball.ball_boundary = ball.ball_boundary.move(ball.velocity)
            frame.blit(ball.ball_image, ball.ball_boundary)

        pygame.display.flip()
        MyClock.tick(60)



Those checks do work... sort of. When I run the program it seems to assume that the top, left, right, bottom span the entirety of the window and not just the area of the balls themselves.

Suggestions?
-Wommbatt
Wommbatt
 
Posts: 24
Joined: Thu Jun 13, 2013 2:15 pm

Re: Bouncy Balls

Postby Mekire » Sun Dec 08, 2013 12:43 pm

This is actually not all that easy at all. Even after you get it essentially working, you will probably find there are cases where balls stick together annoyingly. I will take a look and see if I can get it working without over-complicating things too much. Could you provide the image please?

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Bouncy Balls

Postby Wommbatt » Sun Dec 08, 2013 1:00 pm

Certainly, here you go.
If you can get it working and it is super complicated, an explanation would be great. I just need to be able to see how and why it works. Can't learn otherwise 8]

-wommbatt
Attachments
ball.gif
ball.gif (4.9 KiB) Viewed 869 times
Wommbatt
 
Posts: 24
Joined: Thu Jun 13, 2013 2:15 pm

Re: Bouncy Balls

Postby Mekire » Sun Dec 08, 2013 1:16 pm

Well I'll give a shot at getting something working. Basically two balls are colliding if the vector between their center's has a magnitude less than the sum of their radii. That vector will also be the normal vector for the collision; allowing you to also calculate the tangent vector. With those vectors calculated you can find out the new resultant velocity vector.

I'll get back to you if I can get a sample up and running.

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Bouncy Balls

Postby Mekire » Sun Dec 08, 2013 2:52 pm

Well I wrote up something for the moment. Currently it uses blocks and there is no transference of velocities. If you are comfortable with this level of complexity we can extend to balls and momentum (hopefully).

The below program may still have glitches causing blocks to get stuck, though I haven't witnessed any yet:
Code: Select all
import os
import sys
import math
import random
import pygame as pg


BACKGROUND_COLOR = (40,40,40)
RECT_SIZE = (30,30)
CAPTION = "Block Collision"


class Block(pg.sprite.Sprite):
    def __init__(self,color,position):
        pg.sprite.Sprite.__init__(self)
        self.rect = pg.Rect((0,0),RECT_SIZE)
        self.rect.center = position
        self.image = pg.Surface(self.rect.size).convert()
        self.image.fill(color)
        self.true_pos = list(self.rect.center)
        self.velocity = [random.randint(-5, 5), random.randint(-5, 5)]
        self.unit_vector = self.get_unit_vector(self.velocity)

    def get_unit_vector(self,vector):
        magnitude = math.hypot(*vector)
        unit = float(vector[0])/magnitude, float(vector[1])/magnitude
        return unit

    def update(self,screen_rect,others):
        self.true_pos[0] += self.velocity[0]
        self.true_pos[1] += self.velocity[1]
        self.rect.center = self.true_pos
        for other in others:
            if other is not self:
                self.collide(other)
        self.collide_walls(screen_rect)

    def collide_walls(self,screen_rect):
        out_left = self.rect.left < screen_rect.left
        out_right = self.rect.right > screen_rect.right
        out_top = self.rect.top < screen_rect.top
        out_bottom = self.rect.bottom > screen_rect.bottom
        if out_left or out_right:
            self.velocity[0] *= -1
        if out_top or out_bottom:
            self.velocity[1] *= -1
        if any((out_left,out_right,out_top,out_bottom)):
            self.constrain(screen_rect)
            self.unit_vector = self.get_unit_vector(self.velocity)

    def collide(self,other):
        changed = False
        while self.rect.colliderect(other):
            self.true_pos[0] -= self.unit_vector[0]
            self.true_pos[1] -= self.unit_vector[1]
            self.rect.center = self.true_pos
            changed = True
        if changed:
            on_right = self.rect.right <= other.rect.left
            on_left = self.rect.left >= other.rect.right
            if on_left or on_right:
                self.velocity[0] *= -1
            else:
                self.velocity[1] *= -1
            self.unit_vector = self.get_unit_vector(self.velocity)

    def constrain(self,screen_rect):
        while not screen_rect.contains(self.rect):
            self.true_pos[0] -= self.unit_vector[0]
            self.true_pos[1] -= self.unit_vector[1]
            self.rect.center = self.true_pos


class Control(object):
    def __init__(self):
        pg.init()
        os.environ["SDL_VIDEO_CENTERED"] = "True"
        pg.display.set_caption(CAPTION)
        self.screen = pg.display.set_mode((500,500))
        self.screen_rect = self.screen.get_rect()
        self.clock = pg.time.Clock()
        self.fps = 60.0
        self.done = False
        self.blocks = pg.sprite.Group([Block((255,50,50),(50,50)),
                                       Block(pg.Color("cyan"),(240,340)),
                                       Block(pg.Color("yellow"),(180,250)),
                                       Block(pg.Color("green"),(35,450))])

    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True

    def main_loop(self):
        while not self.done:
            self.event_loop()
            self.blocks.update(self.screen_rect,self.blocks)
            self.screen.fill(BACKGROUND_COLOR)
            self.blocks.draw(self.screen)
            pg.display.update()
            self.clock.tick(self.fps)
            caption = "{} - FPS: {:.2f}".format(CAPTION,self.clock.get_fps())
            pg.display.set_caption(caption)


if __name__ == "__main__":
    Control().main_loop()
    pg.quit()
    sys.exit()

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Bouncy Balls

Postby Wommbatt » Sun Dec 08, 2013 4:47 pm

Oh my goodness. That is really cool. And I am totally NOT ready for this level of trickery yet.

I think I can read through and understand what is going on, but to write it myself or go further... yea, I'm not there lol.

Thanks for taking the time, I'm going to use this as a template to experiment and try to learn from trial and error.

Appreciate the assistance,
-Wommbatt
Wommbatt
 
Posts: 24
Joined: Thu Jun 13, 2013 2:15 pm

Re: Bouncy Balls

Postby Mekire » Sun Dec 08, 2013 5:18 pm

Collision detection really isn't easy. I struggle with it myself. Note however in practical situations, you wouldn't really program the physics engine yourself (unless your goal was to create a physics engine). Even (or rather especially) AAA games, use already written physics engines. Writing the engine yourself is generally much too time consuming.

This one is a little more fun, velocity transference is enabled and you can add blocks by clicking. Even so I had to add a hack to kill blocks if the collision detection couldn't find a free space.
Code: Select all
import os
import sys
import math
import random
import pygame as pg


BACKGROUND_COLOR = (40,40,40)
RECT_SIZE = (30,30)
CAPTION = "Block Collision"


class Block(pg.sprite.Sprite):
    def __init__(self,color,position):
        pg.sprite.Sprite.__init__(self)
        self.rect = pg.Rect((0,0),RECT_SIZE)
        self.rect.center = position
        self.image = pg.Surface(self.rect.size).convert()
        self.image.fill(color)
        self.true_pos = list(self.rect.center)
        self.velocity = [random.randint(-5, 5), random.randint(-5, 5)]
        self.unit_vector = self.get_unit_vector(self.velocity)

    def get_unit_vector(self,vector):
        """Return the unit vector of vector."""
        magnitude = math.hypot(*vector)
        if magnitude:
            unit = float(vector[0])/magnitude, float(vector[1])/magnitude
            return unit
        else:
            return 0,0

    def update(self,screen_rect,others):
        """Update position; check collision with other blocks; and check
        collision with screen boundaries."""
        self.true_pos[0] += self.velocity[0]
        self.true_pos[1] += self.velocity[1]
        self.rect.center = self.true_pos
        for other in others:
            if other is not self:
                self.collide(other)
        self.collide_walls(screen_rect)

    def collide_walls(self,screen_rect):
        """Reverse relevent velocity component if colliding with
        edge of screen."""
        out_left = self.rect.left < screen_rect.left
        out_right = self.rect.right > screen_rect.right
        out_top = self.rect.top < screen_rect.top
        out_bottom = self.rect.bottom > screen_rect.bottom
        if out_left or out_right:
            self.velocity[0] *= -1
        if out_top or out_bottom:
            self.velocity[1] *= -1
        if any((out_left,out_right,out_top,out_bottom)):
            self.constrain(screen_rect)
            self.unit_vector = self.get_unit_vector(self.velocity)

    def collide(self,other):
        """Check collision with other and switch components if hit."""
        changed = False
        count = 0
        while self.rect.colliderect(other):
            self.step_back()
            if count > self.rect.width+self.rect.height:
                self.kill()
                print("Unjustly murdered in collide.")
                break
            count += 1
            changed = True
        if changed:
            on_right = self.rect.right <= other.rect.left
            on_left = self.rect.left >= other.rect.right
            if on_left or on_right:
                self.switch_components(other,0)
            else:
                self.switch_components(other,1)

    def switch_components(self,other,i):
        """Exchange the i component of velocity with other."""
        self.velocity[i],other.velocity[i] = other.velocity[i],self.velocity[i]
        self.unit_vector = self.get_unit_vector(self.velocity)
        other.unit_vector = other.get_unit_vector(other.velocity)

    def constrain(self,screen_rect):
        """Step back one unit pixel until contained within screen_rect."""
        count = 0
        while not screen_rect.contains(self.rect):
            self.step_back()
            if count > self.rect.width+self.rect.height:
                self.kill()
                print("Unjustly murdered in constrain.")
                break
            count += 1

    def step_back(self):
        """Decrement block's position by one unit pixel."""
        self.true_pos[0] -= self.unit_vector[0]
        self.true_pos[1] -= self.unit_vector[1]
        self.rect.center = self.true_pos


class Control(object):
    def __init__(self):
        pg.init()
        os.environ["SDL_VIDEO_CENTERED"] = "True"
        pg.display.set_caption(CAPTION)
        self.screen = pg.display.set_mode((500,500))
        self.screen_rect = self.screen.get_rect()
        self.clock = pg.time.Clock()
        self.fps = 60.0
        self.done = False
        self.blocks = pg.sprite.Group([Block(pg.Color("tomato"),(50,50)),
                                       Block(pg.Color("cyan"),(240,340)),
                                       Block(pg.Color("yellow"),(180,250)),
                                       Block(pg.Color("green"),(35,450))])

    def event_loop(self):
        for event in pg.event.get():
            if event.type == pg.QUIT:
                self.done = True
            elif event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
                color = [random.randint(30,255) for _ in range(3)]
                self.blocks.add(Block(color,event.pos))

    def main_loop(self):
        while not self.done:
            self.event_loop()
            self.blocks.update(self.screen_rect,self.blocks)
            self.screen.fill(BACKGROUND_COLOR)
            self.blocks.draw(self.screen)
            pg.display.update()
            self.clock.tick(self.fps)
            caption = "{} - FPS: {:.2f}".format(CAPTION,self.clock.get_fps())
            pg.display.set_caption(caption)


if __name__ == "__main__":
    Control().main_loop()
    pg.quit()
    sys.exit()

-Mek
User avatar
Mekire
 
Posts: 988
Joined: Thu Feb 07, 2013 11:33 pm
Location: Amakusa, Japan

Re: Bouncy Balls

Postby Wommbatt » Sun Dec 08, 2013 5:45 pm

lol even more neatness, can't wait till I get a good grip of pygame.

-wommbatt
Wommbatt
 
Posts: 24
Joined: Thu Jun 13, 2013 2:15 pm


Return to Game Development

Who is online

Users browsing this forum: No registered users and 2 guests