Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are my balls sticking together? [closed]

Tags:

python

pygame

I'm making a clone of Ballz, a mobile game where you have to shoot a whole bunch of balls at blocks that break after multiple hits. It's like BrickBreaker on steroids. I've got it working mostly, but I can't figure out how to shoot the balls one after another. I know from testing that at the time of shooting, the balls are at different places, but immediately after that they occupy the same space.

Oh btw, the way that I'm keeping them separate is by making the balls go further back outside of the screen. So you can imagine it like setting them all up one behind the other, off screen, below the bottom of the player.

Here's my code:

import pygame
import math
import random
from vector import *

backgroundColor = (0, 0, 0)
ballColor = (255, 255, 255)

sizeOfOneBlock = 50.0
realDimension = 600.0
blockNumberInLine = int(realDimension/sizeOfOneBlock)
size = [int(realDimension), int(realDimension)]

# eg. probability(1/3)
def probability(chance):
    return random.random() <= chance

def abs(x):
    if x>=0:
        return x
    else:
        return -x

# the classes used:
# Block, BlockHandler, Ball, Player

class Block():
    def __init__(self, strength, i, j):
        self.strength = strength
        # i and j are numbers between 0 and blockNumberInLine-1
        self.i, self.j = i, j
        self.refreshStats()

    def refreshStats(self):
        self.color = (100, 224, 89)

    def display(self, Surface):
        pygame.draw.rect(Surface, (0, 0, 255), (self.i*sizeOfOneBlock, self.j*sizeOfOneBlock, sizeOfOneBlock, sizeOfOneBlock), 0)

class BlockHandler():
    def __init__(self):
        self.blockList = []
        self.blockPositions = []

    def resetPositionArray(self):
        self.blockPositions = []
        for block in self.blockList:
            self.blockPositions.append([block.i*sizeOfOneBlock, block.j*sizeOfOneBlock])

    def addNewLayer(self, gameLevel):
        # move every existing block down
        for block in self.blockList:
            block.j += 1
        # add new layer
        for i in range(blockNumberInLine):
            if probability(1/3):
                # gameLevel determines the strength of the block
                self.blockList.append(Block(gameLevel, i, 0))
        # after all blocks are loaded, do this
        self.resetPositionArray()

    def displayBlocks(self, Surface):
        for block in self.blockList:
            block.display(Surface)

class Ball():
    def __init__(self, posVector, moveVector):
        self.posVector = posVector
        self.moveVector = moveVector
        self.radius = 2
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

    def move(self):
        self.posVector.add(self.moveVector)
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

    def display(self, Surface):
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), self.radius)

    def changeDirection(self, tuple):
        # east
        if tuple[0]>0:
            self.moveVector.x = abs(self.moveVector.x)
        # west
        if tuple[0]<0:
            self.moveVector.x = -abs(self.moveVector.x)
        # south
        if tuple[1]>0:
            self.moveVector.y = abs(self.moveVector.y)
        # north
        if tuple[1]<0:
            self.moveVector.y = -abs(self.moveVector.y)

    def collisionDetect(self, blockX, blockY, blockSize, circleX, circleY, circleRadius):
        xDeflect, yDeflect = 0, 0

        # if in the same column
        if (circleX>=blockX) and (circleX<=(blockX+blockSize)):
            # if touching block from above or below
            distance = circleY-(blockY+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                # either 1 or -1
                if distance!=0:
                    yDeflect = distance/abs(distance)
        # if in the same row
        if (circleY>=blockY) and (circleY<=(blockY+blockSize)):
            # if touching block from left or right
            distance = circleX-(blockX+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                if distance!=0:
                    xDeflect = distance/abs(distance)

        return [xDeflect, yDeflect]

    def checkForCollisions(self, blockPositions):
        # walls
        if (self.x<=(0+self.radius)):
            # east
            self.changeDirection([1,0])
        if (self.x>=(realDimension-self.radius)):
            # west
            self.changeDirection([-1,0])
        if (self.y<=(0+self.radius)):
            # south
            self.changeDirection([0,1])

        # blocks
        for pos in blockPositions:
            collision = self.collisionDetect(pos[0], pos[1], sizeOfOneBlock, self.x, self.y, self.radius)
            self.changeDirection(collision)

class Player():
    def __init__(self, posVector):
        self.posVector = posVector
        self.x = int(self.posVector.x)
        self.y = int(self.posVector.y)

        self.level = 1
        self.numberOfBalls = 3
        self.balls = []

    def resetBalls(self):
        self.balls = []
        for j in range(self.numberOfBalls):
            self.balls.append(Ball(self.posVector, moveVector=Vector(0.0, 0.0)))
            # print(ball)

    def placeBalls(self, separateVector):
        # self.resetBalls()
        for j in range(len(self.balls)):
            ball = self.balls[j]
            for i in range(j):
                ball.posVector.subtract(separateVector)

    def display(self, Surface):
        # possibly change color
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), 20)

    def displayBalls(self, Surface):
        for ball in self.balls:
            ball.display(Surface)

    def updateBalls(self, blockHandler):
        for ball in self.balls:
            ball.move()
            ball.checkForCollisions(blockPositions=blockHandler.blockPositions)

def main():
    pygame.init()
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("Ballz")
    done = False
    clock = pygame.time.Clock()

    blockHandler = BlockHandler()
    blockHandler.addNewLayer(1)

    playerPosition = Vector(realDimension/2, realDimension-10)
    player = Player(posVector=playerPosition)
    player.resetBalls()

    # -------- Main Program Loop -----------
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

            if event.type == pygame.KEYDOWN:
                # JFF
                if event.key == pygame.K_w:
                    blockHandler.addNewLayer(1)

                # for debugging
                if event.key == pygame.K_d:
                    for ball in player.balls:
                        print(ball.posVector.x, ball.posVector.y)
                        print(ball.moveVector.x, ball.moveVector.y)
                        print("")
                if event.key == pygame.K_r:
                    player.resetBalls()

            if event.type == pygame.MOUSEBUTTONUP:
                mousePos = pygame.mouse.get_pos()
                player.shootVector = Vector(mousePos[0]-player.x, mousePos[1]-player.y).shortenTo(1)

                for ball in player.balls:
                    for i in range(player.balls.index(ball)*10):
                        ball.posVector.subtract(player.shootVector)
                    ball.moveVector = player.shootVector

                    # test
                    print(ball.posVector.x, ball.posVector.y)
                    print(ball.moveVector.x, ball.moveVector.y)
                    print("")

        # LOGIC
        player.updateBalls(blockHandler)

        # DRAW
        screen.fill(backgroundColor)

        blockHandler.displayBlocks(screen)
        player.displayBalls(screen)
        player.display(screen)

        pygame.display.flip()
        # 60 frames per second
        clock.tick(60)

    pygame.quit()

if __name__ == "__main__":
    main()

Edit: Forgot to add the vector class.

class Vector():
    def __init__(self, x=0, y=0):
        self.x, self.y = x, y

    def magnitude(self):
        return ((self.x)**2 + (self.y)**2)**0.5

    def shortenTo(self, radius):
        magnitude = self.magnitude()
        unitX = self.x/magnitude
        unitY = self.y/magnitude

        return Vector(unitX*radius, unitY*radius)

    def add(self, addedVector):
        self.x += addedVector.x
        self.y += addedVector.y

    def subtract(self, subtractedVector):
        self.x -= subtractedVector.x
        self.y -= subtractedVector.y

    def printCoordinates(self):
        print(self.x, self.y)
like image 656
Shiv Bhatia Avatar asked Feb 04 '23 23:02

Shiv Bhatia


1 Answers

Sorry, no reproduction, your balls are fine: enter image description here

No, but the problem you have is with mutable objects. When you set

ball.moveVector = player.shootVector

you set all moveVector's to the same object, so every collision detection will change the direction of all balls simultaneosly. Simplest fix:

ball.moveVector = player.shootVector + Vector(x=0, y=0)

EDIT

I used a different vector module, in your case you can either use copy.copy or create a custom __add__ method:

def __add__(self, other):
    if not isinstance(other, Vector)
        raise ValueError
    return Vector(self.x+other.x, self.y+other.y)

(This comes inside the Vector class, likewise for subtraction and mult.)

END EDIT

Also there are some problems with the way you reset when the balls leave the image and you should prevent the player from clicking again until the balls are reset, but I guess that comes in later development.

Appendix

Note: I'm working in Python 3 and either I installed a different vector module, or they changed a lot, so had to change some syntax there as well. Hope it helps :)

import pygame
import math
import random
from vector import *

backgroundColor = (0, 0, 0)
ballColor = (255, 255, 255)

sizeOfOneBlock = 50.0
realDimension = 600.0
blockNumberInLine = int(realDimension/sizeOfOneBlock)
size = [int(realDimension), int(realDimension)]

# eg. probability(1/3)
def probability(chance):
    return random.random() <= chance

def abs(x):
    if x>=0:
        return x
    else:
        return -x

# the classes used:
# Block, BlockHandler, Ball, Player

class Block():
    def __init__(self, strength, i, j):
        self.strength = strength
        # i and j are numbers between 0 and blockNumberInLine-1
        self.i, self.j = i, j
        self.refreshStats()

    def refreshStats(self):
        self.color = (100, 224, 89)

    def display(self, Surface):
        pygame.draw.rect(Surface, (0, 0, 255), (self.i*sizeOfOneBlock, self.j*sizeOfOneBlock, sizeOfOneBlock, sizeOfOneBlock), 0)

class BlockHandler():
    def __init__(self):
        self.blockList = []
        self.blockPositions = []

    def resetPositionArray(self):
        self.blockPositions = []
        for block in self.blockList:
            self.blockPositions.append([block.i*sizeOfOneBlock, block.j*sizeOfOneBlock])

    def addNewLayer(self, gameLevel):
        # move every existing block down
        for block in self.blockList:
            block.j += 1
        # add new layer
        for i in range(blockNumberInLine):
            if probability(1/3):
                # gameLevel determines the strength of the block
                self.blockList.append(Block(gameLevel, i, 0))
        # after all blocks are loaded, do this
        self.resetPositionArray()

    def displayBlocks(self, Surface):
        for block in self.blockList:
            block.display(Surface)

class Ball():
    def __init__(self, posVector, moveVector):
        self.posVector = posVector
        self.moveVector = moveVector
        self.radius = 2
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

    def move(self):
        self.posVector += self.moveVector
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

    def display(self, Surface):
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), self.radius)

    def changeDirection(self, tuple):
        # east
        if tuple[0]>0:
            self.moveVector['x'] = abs(self.moveVector['x'])
        # west
        if tuple[0]<0:
            self.moveVector['x'] = -abs(self.moveVector['x'])
        # south
        if tuple[1]>0:
            self.moveVector['y'] = abs(self.moveVector['y'])
        # north
        if tuple[1]<0:
            self.moveVector['y'] = -abs(self.moveVector['y'])

    def collisionDetect(self, blockX, blockY, blockSize, circleX, circleY, circleRadius):
        xDeflect, yDeflect = 0, 0

        # if in the same column
        if (circleX>=blockX) and (circleX<=(blockX+blockSize)):
            # if touching block from above or below
            distance = circleY-(blockY+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                # either 1 or -1
                if distance!=0:
                    yDeflect = distance/abs(distance)
        # if in the same row
        if (circleY>=blockY) and (circleY<=(blockY+blockSize)):
            # if touching block from left or right
            distance = circleX-(blockX+0.5*blockSize)
            if abs(distance)<=(0.5*blockSize+circleRadius):
                if distance!=0:
                    xDeflect = distance/abs(distance)

        return [xDeflect, yDeflect]

    def checkForCollisions(self, blockPositions):
        # walls
        if (self.x<=(0+self.radius)):
            # east
            self.changeDirection([1,0])
        if (self.x>=(realDimension-self.radius)):
            # west
            self.changeDirection([-1,0])
        if (self.y<=(0+self.radius)):
            # south
            self.changeDirection([0,1])

        # blocks
        for pos in blockPositions:
            collision = self.collisionDetect(pos[0], pos[1], sizeOfOneBlock, self.x, self.y, self.radius)
            self.changeDirection(collision)

class Player():
    def __init__(self, posVector):
        self.posVector = posVector
        self.x = int(self.posVector['x'])
        self.y = int(self.posVector['y'])

        self.level = 1
        self.numberOfBalls = 3
        self.balls = []

    def resetBalls(self):
        self.balls = []
        for j in range(self.numberOfBalls):

            x = Vector(x=j, y=j) - Vector(x=j, y=j)
            self.balls.append(Ball(self.posVector, x))
            # print(ball)

    def placeBalls(self, separateVector):
        # self.resetBalls()
        for j in range(len(self.balls)):
            ball = self.balls[j]
            for i in range(j):
                ball.posVector -= separateVector

    def display(self, Surface):
        # possibly change color
        pygame.draw.circle(Surface, ballColor, (self.x, self.y), 20)

    def displayBalls(self, Surface):
        for ball in self.balls:
            ball.display(Surface)

    def updateBalls(self, blockHandler):

        for ball in self.balls:

            ball.move()

            ball.checkForCollisions(blockPositions=blockHandler.blockPositions)

def main():
    pygame.init()
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("Ballz")
    done = False
    clock = pygame.time.Clock()

    blockHandler = BlockHandler()
    blockHandler.addNewLayer(1)

    playerPosition = Vector(x=realDimension/2, y=realDimension-10)
    player = Player(posVector=playerPosition)
    player.resetBalls()

    # -------- Main Program Loop -----------
    while not done:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                done = True

            if event.type == pygame.KEYDOWN:
                # JFF
                if event.rrrr == pygame.K_w:
                    blockHandler.addNewLayer(1)

                # for debugging
                if event.key == pygame.K_d:
                    for ball in player.balls:
                        print(ball.posVector['x'], ball.posVector['y'])
                        print(ball.moveVector['x'], ball.moveVector['y'])
                        print("")
                if event.key == pygame.K_r:
                    player.resetBalls()

            if event.type == pygame.MOUSEBUTTONUP:
                mousePos = pygame.mouse.get_pos()
                player.shootVector = Vector(x=mousePos[0]-player.x, y=mousePos[1]-player.y) / ((mousePos[0]-player.x)**2 + (mousePos[1]-player.y))**.5

                for ball in player.balls:
                    for i in range(player.balls.index(ball)*10):
                        ball.posVector -= player.shootVector
                    ball.moveVector = player.shootVector + Vector(x=0, y=0)

                    # test
                    print(ball.posVector['x'], ball.posVector['y'])
                    print(ball.moveVector['x'], ball.moveVector['y'])
                    print("")

        # LOGIC
        player.updateBalls(blockHandler)

        # DRAW
        screen.fill(backgroundColor)

        blockHandler.displayBlocks(screen)
        player.displayBalls(screen)
        player.display(screen)

        pygame.display.flip()
        # 60 frames per second
        clock.tick(60)
main()
like image 128
Snow bunting Avatar answered Feb 06 '23 14:02

Snow bunting