Why are my balls sticking together?

  • A+
Category:Languages

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) 

 


Sorry, no reproduction, your balls are fine: Why are my balls sticking together?

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() 

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: