Pygame draw.rect 2d object array black screen - python

I have a problem with a program. I'm trying to code a battleship game and the problem is over here:
class Battleship(object):
def __init__(self):
pygame.init()
pygame.display.set_caption('battleship')
self.gameDisplay = pygame.display.set_mode((DISPLAYWIDTH, DISPLAYHEIGHT))
self.clock = pygame.time.Clock()
class Tile(object):
def __init__(self):
self.occupied = False
self.shipID = 0
self.playerID = 0
def setOccupied(self, occupied):
self.occupied = occupied
def setShipID(self, shipID):
self.shipID = shipID
def setPlayerID(self, pID):
self.playerID = pID
class Board:
shipSizes = [1, 2, 3]
sizeX = 25
sizeY = 25
def __init__(self, playerID):
self.playerID = playerID
self.playerShips = []
for i in range(0, len(self.shipSizes)):
self.playerShips.append(Ship(i, self.shipSizes[i]))
self.tiles = [[Tile() for i in range(self.sizeX)] for j in range(self.sizeY)]
def drawBoard(self):
x = 0
y = 0
for row in self.visual:
for col in row:
pygame.draw.rect(Battleship().gameDisplay, black, (x, y, CASILLA, CASILLA), LINEWIDTH)
x = x + CASILLA
y = y + CASILLA
x=0
I don't get any errors, but the function does not work properly and only shows a black screen, checked everything else and the problem definitely lies in that part.
Edit, i added the clas Battleship

I've written this minimal example to show you what I described in the comments. The board instance is an attribute of the Battleship now and you can just call its draw method in the draw method of the battleship.
import pygame as pg
GRAY = pg.Color('gray20')
BLUE = pg.Color('dodgerblue1')
class Battleship(object):
def __init__(self):
pg.init()
pg.display.set_caption('battleship')
self.display = pg.display.set_mode((800, 600))
self.clock = pg.time.Clock()
self.fps = 30
self.done = False
# Is an attribute of the Battleship now.
self.board = Board()
def run(self):
while not self.done:
self.handle_events()
self.run_logic()
self.draw()
self.clock.tick(self.fps)
def handle_events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.done = True
def run_logic(self):
pass
def draw(self):
self.display.fill(GRAY)
# Call the board's draw method and pass the display.
self.board.draw(self.display)
pg.display.flip()
class Board:
shipSizes = [1, 2, 3]
sizeX = 25
sizeY = 25
def __init__(self):
self.tiles = [['-' for i in range(self.sizeX)] for j in range(self.sizeY)]
def draw(self, display):
for y, row in enumerate(self.tiles):
for x, col in enumerate(row):
pg.draw.rect(
display, BLUE,
(x*self.sizeX, y*self.sizeY, self.sizeX, self.sizeY),
2)
battleship = Battleship()
battleship.run()

Related

Unable to modify value in _init_ function

I'm making a Button class in python and I want to give a unique number to each button because when I will draw multiple buttons on pygame window, I will need to differentiate between these buttons using those unique numbers.
The problem is I'm unable to modify the unique number (unq_number += 1 [Line 10 in code]).
Python is giving me this error [local variable 'unq_number' referenced before assignment]
but I have already defined the variable in the code (Line 4 in code).
import pygame
class Button:
unq_number = 0
def __init__(self, text, bg_color, width, height, pos, gui_font):
self.pressed = False
# unique number for each button
unq_number += 1
self.unq_number = unq_number
# Rectangle
self.rect = pygame.Rect(pos, (width, height))
self.bg_color = bg_color
# text
self.text_surf = gui_font.render(text, True, '#FFFFFF')
self.text_rect = self.text_surf.get_rect(center=self.rect.center)
def draw(self, surface, txt_color):
pygame.draw.rect(surface, self.bg_color, self.rect)
surface.blit(self.text_surf, self.text_rect)
return self.check_click(txt_color)
def check_click(self, txt_color):
mouse_pos = pygame.mouse.get_pos()
if self.rect.collidepoint(mouse_pos):
self.bg_color = (0, 125, 110)
if pygame.mouse.get_pressed()[0]:
self.pressed = True
else:
if self.pressed == True:
self.pressed = False
return self.unq_number
else:
self.bg_color = txt_color
You must distinguish between the class attribute (see Class Objects) and the instance attribute (see Instance Objects). The class attribute is addressed with Button.unq_number and the instance attribute with self.unq_number.
minimal example
class Button:
unq_number = 0
def __init__(self):
Button.unq_number += 1
self.unq_number = Button.unq_number
bl = []
for _ in range(4):
bl.append(Button())
print(f"{Button.unq_number} buttons: {[b.unq_number for b in bl]}")
output
1 buttons: [1]
2 buttons: [1, 2]
3 buttons: [1, 2, 3]
4 buttons: [1, 2, 3, 4]
self reference to this class here and you need to write self before your variable
import pygame
class Button:
unq_number = 0
def __init__(self, text, bg_color, width, height, pos, gui_font):
self.pressed = False
# unique number for each button
self.unq_number += 1
#self.unq_number = unq_number
# Rectangle
self.rect = pygame.Rect(pos, (width, height))
self.bg_color = bg_color
# text
self.text_surf = gui_font.render(text, True, '#FFFFFF')
self.text_rect = self.text_surf.get_rect(center=self.rect.center)
def draw(self, surface, txt_color):
pygame.draw.rect(surface, self.bg_color, self.rect)
surface.blit(self.text_surf, self.text_rect)
return self.check_click(txt_color)
def check_click(self, txt_color):
mouse_pos = pygame.mouse.get_pos()
if self.rect.collidepoint(mouse_pos):
self.bg_color = (0, 125, 110)
if pygame.mouse.get_pressed()[0]:
self.pressed = True
else:
if self.pressed == True:
self.pressed = False
return self.unq_number
else:
self.bg_color = txt_color

Black BG during render under tiles, looks fine in Tiled map editor

I started learning Pygame and Tiled map editor. I have the following test:
Example
It looks fine in the editor, the objects that have the black BG are currently on layer_4 (but they do it regardless of layer count).
I already tried using convert_alpha on it, ticking and unticking the transparency color in Tiled when loading in the tile map.
This is the original picture:
Full picture
This is the part where I load in the picture:
def import_cut_graphics(path):
surface = pygame.image.load(path).convert_alpha()
tile_num_x = int(surface.get_size()[0] / TILESIZE)
tile_num_y = int(surface.get_size()[1] / TILESIZE)
cut_tiles = []
for row in range(tile_num_y):
for col in range(tile_num_x):
x = col * TILESIZE
y = row * TILESIZE
new_surface = pygame.Surface((TILESIZE, TILESIZE))
new_surface.blit(surface, (0, 0), pygame.Rect(x, y, TILESIZE, TILESIZE))
cut_tiles.append(new_surface)
return cut_tiles
And this is where I work with it:
class Level:
def __init__(self, level_data, surface):
# general setup
self.all_layers = import_cut_graphics("img\ProjectUtumno_full.png")
self.display_surface = surface
self.world_shift = 0
# terrain setup
layer1_layout = import_csv_layout(level_data["layer_1"])
self.layer1_sprites = self.create_tile_group(layer1_layout, 'layer_1')
# grass setup
layer2_layout = import_csv_layout(level_data["layer_2"])
self.layer2_sprites = self.create_tile_group(layer2_layout, "layer_2")
# crates
layer3_layout = import_csv_layout(level_data["layer_3"])
self.layer3_sprites = self.create_tile_group(layer3_layout, "layer_3")
# layer 4
layer4_layout = import_csv_layout(level_data["layer_4"])
self.layer4_sprites = self.create_tile_group(layer4_layout, "layer_4")
def create_tile_group(self, layout, type):
sprite_group = pygame.sprite.Group()
for row_index, row in enumerate(layout):
for col_index, val in enumerate(row):
if val != '-1':
x = col_index * TILESIZE
y = row_index * TILESIZE
if type == 'layer_1':
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_2" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_3" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
if type == "layer_4" :
tile_surface = self.all_layers[int(val)]
sprite = StaticTile(TILESIZE, x, y, tile_surface)
sprite_group.add(sprite)
return sprite_group
def run(self):
# run the entire game / level
self.camera()
# layer1
self.layer1_sprites.update(self.world_shift)
self.layer1_sprites.draw(self.display_surface)
# layer2
self.layer2_sprites.update(self.world_shift)
self.layer2_sprites.draw(self.display_surface)
# layer3
self.layer3_sprites.update(self.world_shift)
self.layer3_sprites.draw(self.display_surface)
# layer4
self.layer4_sprites.update(self.world_shift)
self.layer4_sprites.draw(self.display_surface)
This is my main game loop:
class Game:
def __init__(self):
pygame.init()
pygame.font.init()
self.screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT))
self.clock = pygame.time.Clock()
self.screen_name = pygame.display.set_caption("New Game")
self.running = True
self.playing = False
self.level = Level(level_0, self.screen)
self.character_spritesheet = Spritesheet('img\MainCharacter\B_witch_idle.png')
def main(self):
# game loop
while self.running:
self.events()
self.update()
self.draw()
self.running = False
def draw(self):
self.screen.fill(BLACK)
self.level.run()
self.all_sprites.draw(self.screen)
self.clock.tick(FPS)
pygame.display.update()
I did try commenting the screen fill black and got the same result. I tried out set_colorkey with white/black colors but didn't work out.
I add my tile create class too:
class Tile(pygame.sprite.Sprite):
def __init__(self, size, x, y):
super().__init__()
self.image = pygame.Surface((size, size))
self.rect = self.image.get_rect(topleft=(x, y))
def update(self, shift):
self.rect.x += shift
class StaticTile(Tile):
def __init__(self, size, x, y, surface):
super().__init__(size, x, y)
self.image = surface
Thanks in advance if you can figure out why i have this problem.
So for anyone who's having this problem, I managed to solve it by this:
Inside my import_cut_graphics methode I added a set_colorkey(BLACK), turns out the problem occured when I was originally cutting the picture, therefore it didnt matter how much I change it at the latter phases since the problem was in an earlier stage.
def import_cut_graphics(path):
surface = pygame.image.load(path).convert_alpha()
tile_num_x = int(surface.get_size()[0] / TILESIZE)
tile_num_y = int(surface.get_size()[1] / TILESIZE)
cut_tiles = []
for row in range(tile_num_y):
for col in range(tile_num_x):
x = col * TILESIZE
y = row * TILESIZE
new_surface = pygame.Surface((TILESIZE, TILESIZE))
new_surface.blit(surface, (0, 0), pygame.Rect(x, y, TILESIZE, TILESIZE))
new_surface.set_colorkey(BLACK)
cut_tiles.append(new_surface)
return cut_tiles

Putting the scrolling camera in a mini-window in pygame

I'm trying to create a game where the action is shown in a little box within the main screen object, freeing up the surrounding space for text and menus and what-not. Since the map is larger than the allotted window, I coded a basic "camera" that follows the player around. It mostly works, but I'm having trouble "trimming off" the area outside of this window.
Here's the relevant bits of code (EDITED to provide Working Example):
import pygame, os, sys
from pygame.locals import *
pygame.init()
RIGHT = 'RIGHT'
LEFT = 'LEFT'
UP = 'UP'
DOWN = 'DOWN'
class Camera():
def __init__(self, screen, x_ratio = 1, y_ratio = 1, x_offset = 0, y_offset = 0):
self.screen = screen.copy()
self.rec = self.screen.get_rect()
self.rec.width *= x_ratio
self.rec.height *= y_ratio
self.x_offset = x_offset
self.y_offset = y_offset
def get_pos(self):
return (self.x_offset - self.rec.x, self.y_offset - self.rec.y)
def get_window(self):
w = pygame.Rect(self.rec)
w.topleft = (0 - self.rec.x, 0 - self.rec.y)
return w
def move(self, x, y):
"""Move camera into new position"""
self.rec.x = x
self.rec.y = y
def track(self, obj):
while obj.rec.left < self.rec.left:
self.rec.x -= 1
while obj.rec.right > self.rec.right:
self.rec.x += 1
while obj.rec.top < self.rec.top:
self.rec.y -= 1
while obj.rec.bottom > self.rec.bottom:
self.rec.y += 1
class Map:
def __init__(self, width, height):
self.width = width
self.height = height
self.rec = pygame.Rect(0,0,self.width,self.height)
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
class Obj:
def __init__(self, char, x = 0, y = 0, width = 0, height = 0):
self.width = width
self.height = height
self.rec = pygame.Rect(x, y, width, height)
self.cur_map = None
self.timers = {}
#Dummying in chars for sprites
self.char = char
self.x_dir = 1
self.y_dir = 1
self.speed = 1
self.moving = False
def move(self):
if self.x_dir != 0 or self.y_dir != 0:
new_x = self.rec.x + (self.x_dir*self.speed)
new_y = self.rec.y + (self.y_dir*self.speed)
new_rec = pygame.Rect(new_x, new_y, self.width, self.height)
#Keep movement within bounds of map
while new_rec.left < self.cur_map.rec.left:
new_rec.x += 1
while new_rec.right > self.cur_map.rec.right:
new_rec.x -= 1
while new_rec.top < self.cur_map.rec.top:
new_rec.y += 1
while new_rec.bottom > self.cur_map.rec.bottom:
new_rec.y -= 1
self.rec = new_rec
def set_dir(self, d):
self.x_dir = 0
self.y_dir = 0
if d == LEFT:
self.x_dir = -1
elif d == RIGHT:
self.x_dir = 1
elif d == UP:
self.y_dir = -1
elif d == DOWN:
self.y_dir = 1
def set_moving(self, val = True):
self.moving = val
class Game:
def __init__(self):
self.screen_size = (800, 600)
self.screen = pygame.display.set_mode(self.screen_size)
self.map_screen = self.screen.copy()
self.title = 'RPG'
pygame.display.set_caption(self.title)
self.camera = Camera(self.screen, 0.75, 0.75)#, 10, 75)
self.fps = 80
self.clock = pygame.time.Clock()
self.debug = False
self.bg_color = (255,255,255)
self.text_size = 18
self.text_font = 'Arial'
self.text_style = pygame.font.SysFont(self.text_font, self.text_size)
self.key_binds = {LEFT : [K_LEFT, K_a], RIGHT : [K_RIGHT, K_d], UP : [K_UP, K_w], DOWN : [K_DOWN, K_s],
'interact' : [K_RETURN, K_z], 'inventory' : [K_i, K_SPACE], 'quit' : [K_ESCAPE]}
self.player = Obj('p', 0, 0, 10, self.text_size)
def draw(self, obj):
char = obj.char
self.draw_text(char, obj.rec.x, obj.rec.y, screen = self.map_screen)
def draw_text(self, text, x, y, color = (0,0,0), screen = None):
textobj = self.text_style.render(text, 1, color)
textrect = textobj.get_rect()
textrect.x = x
textrect.y = y
if screen == None:
"""Use default screen"""
self.screen.blit(textobj, textrect)
else:
screen.blit(textobj, textrect)
def play(self):
done = False
cur_map = Map(800, 800)
self.map_screen = pygame.Surface((cur_map.width, cur_map.height))
self.map_screen.fill(self.bg_color)
bg = pygame.Surface((cur_map.width, cur_map.height))
cur_map.draw(bg)
self.player.cur_map = cur_map
while not done:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key in self.key_binds[LEFT]:
self.player.set_dir(LEFT)
self.player.set_moving()
elif event.key in self.key_binds[RIGHT]:
self.player.set_dir(RIGHT)
self.player.set_moving()
elif event.key in self.key_binds[UP]:
self.player.set_dir(UP)
self.player.set_moving()
elif event.key in self.key_binds[DOWN]:
self.player.set_dir(DOWN)
self.player.set_moving()
elif event.type == KEYUP:
self.player.set_moving(False)
if self.player.moving:
self.player.move()
self.camera.track(self.player)
self.clock.tick()
self.screen.fill(self.bg_color)
self.map_screen.blit(bg, (0,0))
self.draw(self.player)
pygame.draw.rect(self.map_screen, (0,0,0), self.camera.rec, 1)
#self.screen.blit(self.map_screen, (0,0), [0 - self.camera.rec.x, 0 - self.camera.rec.y, self.camera.rec.width, self.camera.rec.height])
self.screen.blit(self.map_screen, self.camera.get_pos(), self.camera.get_window())
pygame.display.flip()
game = Game()
game.play()
Moving the player past past the bounds of the camera's window causes the window to roll up completely and disappear. I tried adjusting the blitting coordinates, as advised earlier, but it seems to only change the direction in which the window rolls up.
From your updated code, the blitting coordinates for self.screen.blit(...) are still changing: self.camera.get_window() changes value because rec.x and rec.y are values referring to the player position within the map. Hence you should define a constant minimap coordinate, this should be the same as the camera offset.
self.screen.blit(self.map_screen, (self.camera.x_offset,self.camera.y_offset), (*self.camera.get_pos(), self.camera.rec.width, self.camera.rec.height))
Change the Camera().get_pos() to:
def get_pos(self):
return (self.rec.x, self.rec.y)
I believe I only changed the self.screen.blit(...) and stopped using or rewrote your Camera functions as you're confusing yourself with all the rec variables.
To illustrate it working amend the Map().draw(screen) to:
def draw(self, screen):
pygame.draw.rect(screen, (200,200,200), self.rec)
pygame.draw.circle(screen, (255, 255, 255), (50, 50), 20, 2)
One tip as well don't draw the entire map at each loop, just the part that will be visible.

Why are my balls sticking together? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 4 years ago.
Improve this question
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:
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()
Since you're passing the player's self.posVector to the ball instances and then just assign it to their posVector attributes, the positions of the player and the balls all refer to the same Vector object in the memory. You can instead make copies of the vector, for example with the copy module, so that every ball.posVector refers to a separate vector object.
First import the copy function (it creates shallow copies).
from copy import copy
Then copy the vector objects before you pass them.
def resetBalls(self):
self.balls = []
for j in range(self.numberOfBalls):
self.balls.append(
Ball(copy(self.posVector), moveVector=Vector(0.0, 0.0)))
# ...
for ball in player.balls:
for i in range(player.balls.index(ball)*10):
ball.posVector.subtract(player.shootVector)
ball.moveVector = copy(player.shootVector)
I also recommend using pygame's Vector2 class instead of your own Vector, because it is more feature-rich and efficient.
And abs is a built-in function.

PyGame Simulation Coordinate Bug

I want to create a simulation. (not sure where it will head)
I created a Human class and HumanRace class.
The HumanRace class contains every Human class object within an list.
Currently I'm using 2 HumanRaces and 10 Humans per race.
Humans get represented with a circle, the color of it is assigned to the HumanRace. Also there will be a circle representing the spawn area of this race, although the math behind this is a bit sloppy.
Humans spawn within the spawn area of their race. That's where the problem comes in. If I render only 1 race everything is okay, but if I do render the second too, it will redraw those positions with a different color, even tho my the Humans added to the race have different positions. So I guess my render definition is incorrect.
Main.py
import sys, pygame
from HumanRace import *
from random import randint
BLACK = 0, 0, 0
WHITE = 255, 255, 255
GREEN = 124, 252, 0
RED = 255, 0, 0
PURPLE = 255, 23, 240
BLUE = 0, 68, 255
YELLOW = 255, 255, 0
humanRaces = []
def generateHuman(race):
spawnpos = (race.getSpawnX(), race.getSpawnY())
rsize = race.getSize()
rHP = randint(10, 100)
rATK = randint(20, 100)
rAGIL = randint(20, 50)
x = randint(spawnpos[0] - rsize, spawnpos[0] + rsize)
y = randint(spawnpos[1] - rsize, spawnpos[1] + rsize)
print(x, y, rHP)
return Human(x, y, rHP, rATK, rAGIL)
def generateHumans(race, amount):
for i in range(0, amount):
newHuman = generateHuman(race)
race.addHuman(newHuman)
barbaren = HumanRace("Barbar", 300, 320)
barbaren.setColor(GREEN)
barbaren.setSpawnColor(PURPLE)
generateHumans(barbaren, 4)
chinesen = HumanRace("Chinese", 100, 100)
chinesen.setColor(YELLOW)
chinesen.setSpawnColor(BLUE)
generateHumans(chinesen, 4)
humanRaces.append(barbaren)
humanRaces.append(chinesen)
pygame.init()
screen = pygame.display.set_mode((640, 480))
pygame.display.set_caption("Fighting Era")
clock = pygame.time.Clock()
def renderHumanRaceSpawn():
raceNumber = len(humanRaces)
for i in range(0, raceNumber):
currRace = humanRaces[i]
scolor = currRace.getSpawnColor()
spawnX = currRace.getSpawnX()
spawnY = currRace.getSpawnY()
radius = currRace.getSize()
pygame.draw.circle(screen, scolor, (spawnX, spawnY), radius * 2, 0)
def renderHumanRace():
for i in range(0, 2):
currRace = humanRaces[i]
color = currRace.getColor()
for x in range(0, currRace.getSize()):
currHuman = currRace.getAt(x)
posX = currHuman.getPosX()
posY = currHuman.getPosY()
pygame.draw.circle(screen, color, (posX, posY), 3, 0)
while 1:
clock.tick(246)
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(BLACK)
renderHumanRaceSpawn()
renderHumanRace()
pygame.display.flip()
Human and HumanRace Class
class Human:
def __init__(self, posX, posY, HP, ATK, AGIL):
self.posX = posX
self.posY = posY
self.HP = HP
self.ATK = ATK
self.AGIL = AGIL
def getIndex(self):
return self.index
def getPosX(self):
return self.posX
def getPosY(self):
return self.posY
def setPosX(self, posX):
self.posX = posX
def setPosY(self, posY):
self.posY = posY
from Human import *
class HumanRace:
size = 0
humans = []
spawn = (0, 0)
color = 0, 0, 0
spawnColor = 1, 1, 1
def __init__(self, name, spawnX, spawnY):
self.name = name
self.spawnX = spawnX
self.spawnY = spawnY
def getName(self):
return self.name
def getSpawnX(self):
return self.spawnX
def getColor(self):
return self.color
def setColor(self, color):
self.color = color
def getSpawnColor(self):
return self.spawnColor
def setSpawnColor(self, color):
self.spawnColor = color
def getSpawnY(self):
return self.spawnY
def getSize(self):
return self.size
def addHuman(self, human):
global size
self.humans.append(human)
self.size += 1
def getAt(self, index):
return self.humans[index]
def removeAt(self, index):
global size
self.humans.delete(index)
self.size -= 1
Picture of Window
Your humans list inside the HumanRace class is a class variable. All instances of HumanRace will share the same humans list. You should change it to an instance variable by putting inside the __init__ function.
Something like this:
class HumanRace:
def __init__(self, name, spawnX, spawnY):
self.name = name
self.spawn = (spawnX, spawnY)
self.size = 0 # dont really need this, you can just len(humans)
self.humans = []
self.color = 0, 0, 0
self.spawnColor = 1, 1, 1

Categories