Why are my balls sticking together? [closed] - python

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.

Related

Updates to elements of list are applied to the whole list python

I am trying to write just a simple program to simulate an N-Body system using python (and pygame for GUI just because I have more experience than tk). The issue I am running into is I made a body class that is a subclass from a sprite. I have an adaptable list size of n that appends that many bodies into the list. To start I just decided to have updates be performed in a double for loop with a final for loop that passes the updates after they have been calculated (I know this is not efficient, not the point). My issue is that when I pass an update to an element of this list, the update is applied to every thing in that list. What is going on here? Why are these completely separate elements tied together? Necessary code below
Body class
class Body(Sprite):
vel = Vector2(0,0)
acc = Vector2(0,0)
def __init__(self, x, y, size, mass, init_val = True, color=(255,255,0)):
super().__init__()
self.rect = Rect(x-size[0]/2,y-size[1]/2,size[0],size[1])
self.mass = mass
self.pos = Vector2(x,y)
#self.vel = Vector2(random(),random())
if init_val:
self.acc = Vector2(random(),random())
self.image = Surface(size)
self.image.fill((255,255,255))
self.image.set_colorkey((255,255,255))
draw_circle(self.image,color,(size[0]/2,size[1]/2),size[0]/2)
def update(self, force):
self.acc += force
self.vel += self.acc
self.rect.move_ip(self.vel)
Sim loop
class nbody:
def __init__(self, n = 3, screensize=(1366,768)):
self.win = pg.display.set_mode(screensize)
self.clock = pg.time.Clock()
self.bodies = Group()
sareax = [int(screensize[0]/4), int(screensize[0]*3/4)]
sareay = [int(screensize[1]/4), int(screensize[1]*3/4)]
c = [(255,0,0), (0,255,0), (0,0,255)]
self.bodies = []
for i in range(n):
mass = 5 + random()*5
size = [mass*2]*2
self.bodies.append(Body(randint(sareax[0], sareax[1]),randint(sareay[0],sareay[1]),size,mass, init_val=False, color=c[i]))
def run_sim(self, time=None, fps = 0.5):
run = True
while run:
self.clock.tick(fps)
self.win.fill((0,0,0))
for e in pg.event.get():
if e.type == pg.QUIT:
pg.quit()
quit()
elif e.type == pg.KEYDOWN:
if e.key == pg.K_ESCAPE:
run = False
elif e.type == pg.KEYUP:
NotImplemented
updates = []
for b1,i in zip(self.bodies,range(len(self.bodies))):
df = Vector2(0,0)
for b2,j in zip(self.bodies,range(len(self.bodies))):
if i != j:
temp = newton(GRAV_CONSTANT, b1, b2)
mag = Vector2(b2.rect.centerx-b1.rect.centerx, b2.rect.centery-b1.rect.centery)
mag.scale_to_length(temp)
df += temp*mag
#print("Body {} has force {} to body {}".format(i,temp*mag,j))
if pg.sprite.collide_mask(b1,b2):
b1.kill()
b2.kill()
updates.append(df)
print(updates)
for bnum,up in zip(range(len(updates)),updates):
print("Before:", self.bodies[bnum].rect.center, self.bodies[bnum].vel, self.bodies[bnum].acc)
self.bodies[bnum].update(up)
print("Change:", up)
print("After:", self.bodies[bnum].rect.center, self.bodies[bnum].vel, self.bodies[bnum].acc)
pg.draw.line(self.win, (255,255,0), self.bodies[bnum].rect.center, (20*up.normalize())+self.bodies[bnum].rect.center, width=2)
self.win.blit(self.bodies[bnum].image,self.bodies[bnum].rect)
pg.display.flip()
return
See Class and Instance Variables:
Generally speaking, instance variables are for data unique to each instance and class variables are for attributes and methods shared by all instances of the class.
Since val and acc are class attributes they are shared by all Body objects and as you mentioned in your question: "these completely separate elements tied together".
vel and acc have to be instance attribute instead of class attributes:
class Body(Sprite):
def __init__(self, x, y, size, mass, init_val = True, color=(255,255,0)):
super().__init__()
self.rect = Rect(x-size[0]/2,y-size[1]/2,size[0],size[1])
self.mass = mass
self.pos = Vector2(x,y)
self.vel = Vector2(0,0)
self.acc = Vector2(0,0)
if init_val:
self.acc = Vector2(random(),random())
self.image = Surface(size)
self.image.fill((255,255,255))
self.image.set_colorkey((255,255,255))
draw_circle(self.image,color,(size[0]/2,size[1]/2),size[0]/2)

I attempted to make a function that allows my sprites to re-produce but it create an infinite amount [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I made a class function that will allow the animals to reproduce once they have eaten enough of a group, from there it would create a new sprite on that location, and then it should lower the energy Val of that sprite, instead it does not do this and instead causes an infinite amount of sprites to be created causing the program to slow down and crash.
this is the functions I made for it to work
def checkEnergy(self):
EnergyVal=self.energy
return EnergyVal
def reproduce(self,energy,Xpos,Ypos):
if energy==10:
prey = Prey(prey_image,Xpos,Ypos )
all_sprites.add(prey)
Rabbit_group.add(prey)
self.energy-=4
here is where I implemented it:
if len(carrots) > 0:
for rabbit in Rabbit_group:
movementx, movementy = Track(rabbit.x, carrots[0].x, rabbit.y, carrots[0].y)#how the prey move
rabbit.move(movementx, movementy)
energyval=prey.checkEnergy()
rabbit.reproduce(energyval,600,700)
full code:
#simulates interactions of wild life
import math
import random
import pygame,sys
import random
import pdb
from pygame.locals import *
#classes--------------
class Animal(pygame.sprite.Sprite):#main class
def __init__(self, image, x, y):
super().__init__()
self.x = x
self.y = y
self.image = image
self.rect = self.image.get_rect(center = (x, y))
self.Energy=10
def move(self, mx, my):#moves the sprite
self.x += mx
self.y += my
self.rect = self.image.get_rect(center = (self.x, self.y))#sets the image to the position
def is_collided_with(self, sprite):#is the eat function
return self.rect.colliderect(sprite.rect)
def checkEnergy(self):
EnergyVal=self.energy
return EnergyVal
def reproduce(self,energy,Xpos,Ypos):
if energy==10:
prey = Prey(prey_image,Xpos,Ypos )
all_sprites.add(prey)
Rabbit_group.add(prey)
self.energy-=4
class Predator(Animal):#class for predators
def __init__(self):
image = pygame.image.load('Icon.png')
self.energy=10
super().__init__(image, 600, 700)
class Prey(Animal):
"""prey class"""
def __init__(self, image, x, y):
super().__init__(image, x, y)
self.energy=10
#self.rect = self.image.get_rect(center = (self.x, self.y))
class Carrot(pygame.sprite.Sprite):#food for prey class
def __init__(self):
super().__init__()
self.x = 100
self.y = 100
self.image = pygame.image.load('carrot.png')
self.rect = self.image.get_rect(center = (self.x, self.y))
# Functions-----------
def Track(AgressorX,DefenderX,AgressorY,DefenderY):#finds comapres X and Y, tells sprite how to move
if AgressorX > DefenderX:
XMovement=-1
elif AgressorX< DefenderX:
XMovement=1
else:
XMovement=0
if AgressorY > DefenderY:
YMovement=-1
elif AgressorY < DefenderY:
YMovement=1
else:
YMovement=0
return XMovement, YMovement
def leavingWindow(Xpos, Ypos):#boundry, keeps sprites in the window
if Xpos<=0:
return True
elif Xpos>=950:
return True
if Ypos<=0:
return True
elif Ypos>=750:
return True
def Boundry(Xpos, Ypos):
if Xpos<=0:
Xmovement=0
elif Xpos>=950:
Xmovement=950
else:
Xmovement=+0
if Ypos<=0:
ymovement=0
elif Ypos>=750:
ymovement=60
else:
ymovement=+0
return Xmovement, ymovement
pygame.init()#setsup pygame
screen = pygame.display.set_mode((1000,800))#setsup screen
clock = pygame.time.Clock()#tells program how fast to update
timmer = 1#how long program has gone on for
#setsup the classes
predator = Predator()
predator2=Predator()
#prey = Prey()
carrot = Carrot()
all_sprites = pygame.sprite.Group()
prey_image = pygame.image.load('Prey.png')
carrot_group = pygame.sprite.Group()
carrot_group.add(Carrot())
Wolf_group= pygame.sprite.Group()
Wolf_group.add(Predator())
Rabbit_group= pygame.sprite.Group()
for i in range(0, 2):
prey = Prey(prey_image, 100 * i, 700)
all_sprites.add(prey)
Rabbit_group.add(prey)
#setsup the classes as sprites
all_sprites.add(carrot_group)
all_sprites.add(Wolf_group)
all_sprites.add(Rabbit_group)
#start of program
running=True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type==pygame.QUIT:
running=False
carrots = carrot_group.sprites()
preys = Rabbit_group.sprites()
predators = Wolf_group.sprites()
if len(preys) > 0:
for wolf in Wolf_group:
movementx, movementy = Track(wolf.x, preys[0].x, wolf.y, preys[0].y)#how the predators move
wolf.move(0,1)#movementx, #movementy)
Leaving=leavingWindow(wolf.x,wolf.y)#keeps sprites in window
if Leaving == True:
movementx,movementy=Boundry(wolf.x,wolf.y)
wolf.x=movementx
wolf.y=movementy
if len(carrots) > 0:
for rabbit in Rabbit_group:
movementx, movementy = Track(rabbit.x, carrots[0].x, rabbit.y, carrots[0].y)#how the prey move
rabbit.move(movementx, movementy)
energyval=prey.checkEnergy()
rabbit.reproduce(energyval,600,700)
screen.fill((0,128,0))#background
all_sprites.draw(screen)#makes the sprites
pygame.display.update()#updates screen
timmer=timmer+1
for carrot in carrots:#removes the carrots if they contact a rabbit
for rabbit in Rabbit_group:
if rabbit.is_collided_with(carrot):
print('works')
carrot.kill()
for wolf in Wolf_group:
for rabbit in Rabbit_group:
if rabbit.is_collided_with(wolf):
print("works 2")
rabbit.kill()
pygame.quit()
exit()
MRE Here:
import pygame,sys
import random
import pdb
from pygame.locals import *
class Animal(pygame.sprite.Sprite):#main class
def __init__(self, image, x, y):
super().__init__()
self.x = x
self.y = y
self.image = image
self.rect = self.image.get_rect(center = (x, y))
self.Energy=10
def move(self, mx, my):#moves the sprite
self.x += mx
self.y += my
self.rect = self.image.get_rect(center = (self.x, self.y))#sets the image to the position
def checkEnergy(self):
EnergyVal=self.energy
return EnergyVal
def reproduce(self,energy,Xpos,Ypos):
if energy==10:
prey = Prey(prey_image,Xpos,Ypos )
all_sprites.add(prey)
Rabbit_group.add(prey)
self.energy-=4
class Prey(Animal):
"""prey class"""
def __init__(self, image, x, y):
super().__init__(image, x, y)
self.energy=10
#self.rect = self.image.get_rect(center = (self.x, self.y))
pygame.init()#setsup pygame
screen = pygame.display.set_mode((1000,800))#setsup screen
clock = pygame.time.Clock()#tells program how fast to update
timmer = 1#how long program has gone on for
prey_image = pygame.image.load('Prey.png')
Rabbit_group= pygame.sprite.Group()
all_sprites=pygame.sprite.Group()
for i in range(0, 2):
prey = Prey(prey_image, 100 * i, 700)
all_sprites.add(prey)
Rabbit_group.add(prey)
#setsup the classes as sprites
all_sprites.add(Rabbit_group)
#start of program
running=True
while running:
clock.tick(60)
for event in pygame.event.get():
if event.type==pygame.QUIT:
running=False
preys = Rabbit_group.sprites()
for rabbit in Rabbit_group:
rabbit.move(0, -1)
energyval=prey.checkEnergy()
rabbit.reproduce(energyval,600,700)
screen.fill((0,128,0))#background
all_sprites.draw(screen)#makes the sprites
pygame.display.update()#updates screen
timmer=timmer+1
pygame.quit()
exit()
I'm pretty sure the issue is self.energy-=4 I don't think its working properly but I am unsure on how to fix it.
I made a class function that will allow the animals to reproduce once they have eaten enough of a group [...]
You do the evaluation at the wrong place.
if len(carrots) > 0:
for rabbit in Rabbit_group:
movementx, movementy = Track(rabbit.x, carrots[0].x, rabbit.y, carrots[0].y)#how the prey move
rabbit.move(movementx, movementy)
# energyval=prey.checkEnergy() <--- DELETE
# rabbit.reproduce(energyval,600,700) <--- DELETE
Increment the energy when the rabbit eats a carrot. And reproduce the rabbit if the energy reaches the threshold:
class Animal(pygame.sprite.Sprite):#main class
# [...]
def reproduce(self):
if self.energy >= 10:
prey = Prey(self.image, self.x, self.y)
all_sprites.add(prey)
Rabbit_group.add(prey)
self.energy -= 4
while running:
# [...]
for carrot in carrots:#removes the carrots if they contact a rabbit
for rabbit in Rabbit_group:
if rabbit.is_collided_with(carrot):
print('works')
carrot.kill()
rabbit.energy += 1
rabbit.reproduce()

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.

atan2 isn't providing me with the angle I want

I'm trying to write a game in pygame, involving a moving object with a "turret" that swivels to follow a mouse. As of now, I'm mostly trying to expand upon examples, so the code's not entirely mine (credit to Sean J. McKiernan for his sample programs); however, this portion is. Below is my code; I use the center of rect (the "base" shape and the point around which the "turret" swivels) as the base point, and the position of the mouse as the other point. By subtracting the mouse's displacement from the displacement of the "center," I effectively get a vector between the two points and find the angle between that vector and the x-axis with atan2. Below is the code I use to do that:
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % 2*math.pi)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
According to multiple tutorials I've reviewed, this SHOULD be the correct code; however, I discovered later that those tutorials (and, in fact, this tutorial) were all for Python 2.7, while I am trying to write in Python 3.6. I don't think that should make a difference in this scenario, though. As it stands, the view appears to depend entirely upon the "character's" position on the screen. If the "character" is in one corner, the reaction of the "turret" is different than the reaction if the "character" is in the middle of the screen. However, this shouldn't matter; the position of the "character" relative to the mouse is the exact same no matter where on the screen they are. Any ideas, or do I need to supply more code?
Edit: Apparently, more code is required. Rather than attempt to extricate only the entirely necessary parts, I've provided the entire code sample, so everyone can run it. As a side note, the "Bolt" things (intended to fire simple yellow blocks) don't work either, but I'm just trying to get the arm working before I start in on debugging that.
Edit the second: I have discovered that the "Bolt" system works within a certain distance of the origin (0,0 in the window coordinate system), and that the arm also works within a much lesser distance. I added the line Block(pg.Color("chocolate"), (0,0,100,100)) under the "walls" grouping as a decision point, and the block was positioned in the top left corner. I've corrected Bolt by changing screen_rect to viewport in the control loop; however, I don't know why the "arm" swinging is dependent on adjacency to the origin. The positions of the mouse and "character" SHOULD be absolute. Am I missing something?
"""
Basic moving platforms using only rectangle collision.
-Written by Sean J. McKiernan 'Mekire'
Edited for a test of "arms"
"""
import os
import sys
import math
import pygame as pg
CAPTION = "Moving Platforms"
SCREEN_SIZE = (700,700)
BACKGROUND_COLOR = (50, 50, 50)
COLOR_KEY = (255, 255, 255)
class _Physics(object):
"""A simplified physics class. Psuedo-gravity is often good enough."""
def __init__(self):
"""You can experiment with different gravity here."""
self.x_vel = self.y_vel = 0
self.grav = 0.4
self.fall = False
def physics_update(self):
"""If the player is falling, add gravity to the current y velocity."""
if self.fall:
self.y_vel += self.grav
else:
self.y_vel = 0
class Player(_Physics, object):
def __init__(self,location,speed):
_Physics.__init__(self)
HAND = pg.image.load("playertst2.png").convert()
HAND.set_colorkey(COLOR_KEY)
self.image = pg.image.load('playertst.png').convert()
self.rect = self.image.get_rect(topleft=location)
self.speed = speed
self.jump_power = -9.0
self.jump_cut_magnitude = -3.0
self.on_moving = False
self.collide_below = False
self.original_hand = HAND
self.fake_hand = self.original_hand.copy()
self.hand = self.original_hand.copy()
self.hand_rect = self.hand.get_rect(topleft=location)
self.angle = self.get_angle(pg.mouse.get_pos())
def check_keys(self, keys):
"""Find the player's self.x_vel based on currently held keys."""
self.x_vel = 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.x_vel -= self.speed
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.x_vel += self.speed
def get_position(self, obstacles):
"""Calculate the player's position this frame, including collisions."""
if not self.fall:
self.check_falling(obstacles)
else:
self.fall = self.check_collisions((0,self.y_vel), 1, obstacles)
if self.x_vel:
self.check_collisions((self.x_vel,0), 0, obstacles)
def check_falling(self, obstacles):
"""If player is not contacting the ground, enter fall state."""
if not self.collide_below:
self.fall = True
self.on_moving = False
def check_moving(self,obstacles):
"""
Check if the player is standing on a moving platform.
If the player is in contact with multiple platforms, the prevously
detected platform will take presidence.
"""
if not self.fall:
now_moving = self.on_moving
any_moving, any_non_moving = [], []
for collide in self.collide_below:
if collide.type == "moving":
self.on_moving = collide
any_moving.append(collide)
else:
any_non_moving.append(collide)
if not any_moving:
self.on_moving = False
elif any_non_moving or now_moving in any_moving:
self.on_moving = now_moving
def check_collisions(self, offset, index, obstacles):
"""
This function checks if a collision would occur after moving offset
pixels. If a collision is detected, the position is decremented by one
pixel and retested. This continues until we find exactly how far we can
safely move, or we decide we can't move.
"""
unaltered = True
self.rect[index] += offset[index]
self.hand_rect[index] += offset[index]
while pg.sprite.spritecollideany(self, obstacles):
self.rect[index] += (1 if offset[index]<0 else -1)
self.hand_rect[index] += (1 if offset[index]<0 else -1)
unaltered = False
return unaltered
def check_above(self, obstacles):
"""When jumping, don't enter fall state if there is no room to jump."""
self.rect.move_ip(0, -1)
collide = pg.sprite.spritecollideany(self, obstacles)
self.rect.move_ip(0, 1)
return collide
def check_below(self, obstacles):
"""Check to see if the player is contacting the ground."""
self.rect.move_ip((0,1))
collide = pg.sprite.spritecollide(self, obstacles, False)
self.rect.move_ip((0,-1))
return collide
def jump(self, obstacles):
"""Called when the user presses the jump button."""
if not self.fall and not self.check_above(obstacles):
self.y_vel = self.jump_power
self.fall = True
self.on_moving = False
def jump_cut(self):
"""Called if player releases the jump key before maximum height."""
if self.fall:
if self.y_vel < self.jump_cut_magnitude:
self.y_vel = self.jump_cut_magnitude
def get_angle(self, mouse):
x_off = (mouse[0]-self.rect.centerx)
y_off = (mouse[1]-self.rect.centery)
self.angle = math.degrees(math.atan2(-y_off, x_off) % (2*math.pi))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.hand_rect.center)
"""
offset = (mouse[1]-self.hand_rect.centery, mouse[0]-self.hand_rect.centerx)
self.angle = math.atan2(-offset[0], offset[1]) % (2 * math.pi)
self.angle = math.degrees(self.angle)
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(center=self.rect.center)
self.angle = 135-math.degrees(math.atan2(*offset))
self.hand = pg.transform.rotate(self.original_hand, self.angle)
self.hand_rect = self.hand.get_rect(topleft=self.rect.topleft)
"""
def pre_update(self, obstacles):
"""Ran before platforms are updated."""
self.collide_below = self.check_below(obstacles)
self.check_moving(obstacles)
def update(self, obstacles, keys):
"""Everything we need to stay updated; ran after platforms update."""
self.check_keys(keys)
self.get_position(obstacles)
self.physics_update()
def get_event(self, event, bolts):
if event.type == pg.MOUSEBUTTONDOWN and event.button == 1:
bolts.add(Bolt(self.rect.center))
elif event.type == pg.MOUSEMOTION:
self.get_angle(event.pos)
def draw(self, surface):
"""Blit the player to the target surface."""
surface.blit(self.image, self.rect)
surface.blit(self.hand, self.hand_rect)
class Bolt(pg.sprite.Sprite):
def __init__(self, location):
pg.sprite.Sprite.__init__(self)
"""self.original_bolt = pg.image.load('bolt.png')"""
"""self.angle = -math.radians(angle-135)"""
"""self.image = pg.transform.rotate(self.original_bolt, angle)"""
"""self.image = self.original_bolt"""
self.image=pg.Surface((5,10)).convert()
self.image.fill(pg.Color("yellow"))
self.rect = self.image.get_rect(center=location)
self.move = [self.rect.x, self.rect.y]
self.speed_magnitude = 5
"""self.speed = (self.speed_magnitude*math.cos(self.angle), self.speed_magnitude*math.sin(self.angle))"""
"""self.speed = (5,0)"""
self.done = False
def update(self, screen_rect, obstacles):
self.move[0] += self.speed_magnitude
"""self.move[1] += self.speed[1]"""
self.rect.topleft = self.move
self.remove(screen_rect, obstacles)
def remove(self, screen_rect, obstacles):
if not self.rect.colliderect(screen_rect):
self.kill()
class Block(pg.sprite.Sprite):
"""A class representing solid obstacles."""
def __init__(self, color, rect):
"""The color is an (r,g,b) tuple; rect is a rect-style argument."""
pg.sprite.Sprite.__init__(self)
self.rect = pg.Rect(rect)
self.image = pg.Surface(self.rect.size).convert()
self.image.fill(color)
self.type = "normal"
class MovingBlock(Block):
"""A class to represent horizontally and vertically moving blocks."""
def __init__(self, color, rect, end, axis, delay=500, speed=2, start=None):
"""
The moving block will travel in the direction of axis (0 or 1)
between rect.topleft and end. The delay argument is the amount of time
(in miliseconds) to pause when reaching an endpoint; speed is the
platforms speed in pixels/frame; if specified start is the place
within the blocks path to start (defaulting to rect.topleft).
"""
Block.__init__(self, color, rect)
self.start = self.rect[axis]
if start:
self.rect[axis] = start
self.axis = axis
self.end = end
self.timer = 0.0
self.delay = delay
self.speed = speed
self.waiting = False
self.type = "moving"
def update(self, player, obstacles):
"""Update position. This should be done before moving any actors."""
obstacles = obstacles.copy()
obstacles.remove(self)
now = pg.time.get_ticks()
if not self.waiting:
speed = self.speed
start_passed = self.start >= self.rect[self.axis]+speed
end_passed = self.end <= self.rect[self.axis]+speed
if start_passed or end_passed:
if start_passed:
speed = self.start-self.rect[self.axis]
else:
speed = self.end-self.rect[self.axis]
self.change_direction(now)
self.rect[self.axis] += speed
self.move_player(now, player, obstacles, speed)
elif now-self.timer > self.delay:
self.waiting = False
def move_player(self, now, player, obstacles, speed):
"""
Moves the player both when on top of, or bumped by the platform.
Collision checks are in place to prevent the block pushing the player
through a wall.
"""
if player.on_moving is self or pg.sprite.collide_rect(self,player):
axis = self.axis
offset = (speed, speed)
player.check_collisions(offset, axis, obstacles)
if pg.sprite.collide_rect(self, player):
if self.speed > 0:
self.rect[axis] = player.rect[axis]-self.rect.size[axis]
else:
self.rect[axis] = player.rect[axis]+player.rect.size[axis]
self.change_direction(now)
def change_direction(self, now):
"""Called when the platform reaches an endpoint or has no more room."""
self.waiting = True
self.timer = now
self.speed *= -1
"""class Spell(pg.sprite.Sprite):
def __init__(self, location, angle)"""
class Control(object):
"""Class for managing event loop and game states."""
def __init__(self):
"""Initalize the display and prepare game objects."""
self.screen = pg.display.get_surface()
self.screen_rect = self.screen.get_rect()
self.clock = pg.time.Clock()
self.fps = 60.0
self.keys = pg.key.get_pressed()
self.done = False
self.player = Player((50,875), 4)
self.viewport = self.screen.get_rect()
self.level = pg.Surface((1000,1000)).convert()
self.level_rect = self.level.get_rect()
self.win_text,self.win_rect = self.make_text()
self.obstacles = self.make_obstacles()
self.bolts = pg.sprite.Group()
def make_text(self):
"""Renders a text object. Text is only rendered once."""
font = pg.font.Font(None, 100)
message = "You win. Celebrate."
text = font.render(message, True, (100,100,175))
rect = text.get_rect(centerx=self.level_rect.centerx, y=100)
return text, rect
def make_obstacles(self):
"""Adds some arbitrarily placed obstacles to a sprite.Group."""
walls = [Block(pg.Color("chocolate"), (0,980,1000,20)),
Block(pg.Color("chocolate"), (0,0,20,1000)),
Block(pg.Color("chocolate"), (980,0,20,1000))]
static = [Block(pg.Color("darkgreen"), (250,780,200,100)),
Block(pg.Color("darkgreen"), (600,880,200,100)),
Block(pg.Color("darkgreen"), (20,360,880,40)),
Block(pg.Color("darkgreen"), (950,400,30,20)),
Block(pg.Color("darkgreen"), (20,630,50,20)),
Block(pg.Color("darkgreen"), (80,530,50,20)),
Block(pg.Color("darkgreen"), (130,470,200,215)),
Block(pg.Color("darkgreen"), (20,760,30,20)),
Block(pg.Color("darkgreen"), (400,740,30,40))]
moving = [MovingBlock(pg.Color("olivedrab"), (20,740,75,20), 325, 0),
MovingBlock(pg.Color("olivedrab"), (600,500,100,20), 880, 0),
MovingBlock(pg.Color("olivedrab"),
(420,430,100,20), 550, 1, speed=3, delay=200),
MovingBlock(pg.Color("olivedrab"),
(450,700,50,20), 930, 1, start=930),
MovingBlock(pg.Color("olivedrab"),
(500,700,50,20), 730, 0, start=730),
MovingBlock(pg.Color("olivedrab"),
(780,700,50,20), 895, 0, speed=-1)]
return pg.sprite.Group(walls, static, moving)
def update_viewport(self):
"""
The viewport will stay centered on the player unless the player
approaches the edge of the map.
"""
self.viewport.center = self.player.rect.center
self.viewport.clamp_ip(self.level_rect)
def event_loop(self):
"""We can always quit, and the player can sometimes jump."""
for event in pg.event.get():
if event.type == pg.QUIT or self.keys[pg.K_ESCAPE]:
self.done = True
elif event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.player.jump(self.obstacles)
elif event.type == pg.KEYUP:
if event.key == pg.K_SPACE:
self.player.jump_cut()
elif event.type == pg.MOUSEMOTION or event.type == pg.MOUSEBUTTONDOWN:
self.player.get_event(event, self.bolts)
def update(self):
"""Update the player, obstacles, and current viewport."""
self.keys = pg.key.get_pressed()
self.player.pre_update(self.obstacles)
self.obstacles.update(self.player, self.obstacles)
self.player.update(self.obstacles, self.keys)
self.update_viewport()
self.bolts.update(self.screen_rect, self.obstacles)
def draw(self):
"""
Draw all necessary objects to the level surface, and then draw
the viewport section of the level to the display surface.
"""
self.level.fill(pg.Color("lightblue"))
self.obstacles.draw(self.level)
self.level.blit(self.win_text, self.win_rect)
self.player.draw(self.level)
self.bolts.draw(self.level)
self.screen.blit(self.level, (0,0), self.viewport)
def display_fps(self):
"""Show the programs FPS in the window handle."""
caption = "{} - FPS: {:.2f}".format(CAPTION, self.clock.get_fps())
pg.display.set_caption(caption)
def main_loop(self):
"""As simple as it gets."""
while not self.done:
self.event_loop()
self.update()
self.draw()
pg.display.update()
self.clock.tick(self.fps)
self.display_fps()
if __name__ == "__main__":
os.environ['SDL_VIDEO_CENTERED'] = '1'
pg.init()
pg.display.set_caption(CAPTION)
pg.display.set_mode(SCREEN_SIZE)
PLAYERIMG = pg.image.load("playertst.png").convert()
PLAYERIMG.set_colorkey(COLOR_KEY)
run_it = Control()
run_it.main_loop()
pg.quit()
sys.exit()
The % 2*pi unnecessary, and your get_angle function has no return value, but you do an assignment to self.angle = self.get_angle, but that is not the issue. The issue is that the mouse position is relative to the screen (i.e. clicking in the top right area of your game screen will always yield (0,480) if your screen is 640x480), while the position of the (character) rectangle is given in your game play area, which is larger than the screen, ergo if you move the character and thus the view shifts, you are getting coordinates in two different coordinate systems. You will have to keep track of where the view is in your game play area and add the offset to the mouse coordinates.

pygame particle: issues removing from list

I have Particle and Explosion classes I am drawing using pygame.
An Explosion represents a bunch of flying Particles.
A Particle fades eventually; when its ttl property becomes 0 it should not be visible and should be removed from the Explosion.particles.
I want the program to delete dead particles so that they are not updated.
The issue I am having is with the Explosion.update() method. It seems it's not removing any Particles. I am experiencing a bug where supposedly 'dead' Particles are still drawn but their movement is not updated.I've experimented on lists in python and verified that the premise of iterating through a 'dead' list to remove from the other list works.Any suggestions on where the fault lies in this code would be greatly appreciated.
Edit: I've attached and shortened both source files for better context.
explosion.py
import sys
import pygame
from particleac import *
class App:
def __init__(self):
pygame.init()
self.screen = pygame.display.set_mode(SCREEN_SIZE, pygame.HWSURFACE|pygame.DOUBLEBUF)
self.clock = pygame.time.Clock()
self.running = True
self.explosions = []
self.frame_no = 0
def check_input(self):
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
if event.type == pygame.MOUSEBUTTONDOWN or event.type == pygame.MOUSEMOTION:
x, y = pygame.mouse.get_pos()
self.explosions.append(Explosion(x, y))
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
# remove explosion if particles all gone
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
p.update()
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.frame_no += 1
self.frame_no %= 60
self.clock.tick(FRAMERATE)
def run(self):
while self.running:
self.check_input()
self.update_screen()
pygame.quit()
app = App()
app.run()
particleac.py
import random
import sys
BLACK = [ 0, 0, 0]
WHITE = [255, 255, 255]
BLUE = [0, 0, 255]
SCREEN_WIDTH = 600
SCREEN_HEIGHT = 400
SCREEN_CENTRE = [SCREEN_WIDTH/2, SCREEN_HEIGHT/2]
FRAMERATE = 40
SCREEN_SIZE = [SCREEN_WIDTH, SCREEN_HEIGHT]
GRAVITY = 1
TERMINAL_VELOCITY = 10
class Particle:
def __init__(self, x=SCREEN_CENTRE[0], y=SCREEN_CENTRE[1], colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.brightness = 255
self.size = 2
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-8, 3)
self.ttl = random.randrange(50, 200)
self.decay = (self.ttl / FRAMERATE) * 2
def update(self):
if self.ttl > 0:
self.ttl -= 1
self.brightness -= self.decay
if self.brightness < 0:
self.brightness = 0
self.colour[0] = self.colour[1] = self.colour[2] = self.brightness
self.y_velocity += GRAVITY
self.y += self.y_velocity
self.x += self.x_velocity
if self.y > SCREEN_HEIGHT:
self.y -= SCREEN_HEIGHT
class Explosion:
MAX_NUM_PARTICLES = 2
def __init__(self, x=0, y=0, colour=WHITE):
self.x = x
self.y = y
self.colour = colour
self.x_velocity = random.randrange(-4, 4)
self.y_velocity = random.randrange(-6, -1)
self.num_particles = random.randrange(1, self.MAX_NUM_PARTICLES)
self.particles = []
for i in range(self.MAX_NUM_PARTICLES):
p = Particle(self.x, self.y)
p.colour = self.colour
p.x_velocity += self.x_velocity
p.y_velocity += self.y_velocity
self.particles.append(p)
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
sys.stdout.write("len(self.particles) == {}".format(len(self.particles)))
sys.stdout.flush()
Your code works for me, once sys.out.write() is changed to sys.stdout.write(). Since there is that error in your code, are you sure that the code in your post the same as the code that is failing?
You can simplify Explosion.update() to this:
def update(self):
for p in self.particles:
p.update()
self.particles = [p for p in self.particles if p.ttl > 0]
Possibly this change might fix the problem because you are now dealing with only one list, but I believe that your code should work.
The problem may come from the fact that self.particles is defined at the class level. This may cause some problems elsewhere in your code of you create more than one instance of the Explosion class. Try moving the self.particle in the constructor, and see what happens.
(As a suggestion) Since you build a list anyways, why not copy the ones that are alive?
def update(self):
particles_alive = []
for p in self.particles:
p.update()
sys.out.write("p.ttl == {}\n".format(p.ttl))
if p.ttl == 0:
sys.out.write("Particle died.\n")
else:
particles_alive.append(p)
self.particles = particles_alive
Solution: I was never calling e.update() !Therefore Explosions weren't updating while iterating through them, which meant the dead Particles weren't being removed. Aaargh!I added e.update() to app.update_screen() and removed the incorrect p.update() (which is called for each Particle in the e.update()).
def update_screen(self):
self.screen.fill(BLACK)
dead_explosions = []
for e in self.explosions:
if len(e.particles) == 0:
dead_explosions.append(e)
else:
e.update()
for p in e.particles:
pygame.draw.circle(self.screen, p.colour, [p.x, p.y], p.size)
for e in dead_explosions:
self.explosions.remove(e)
pygame.display.flip()
self.clock.tick(FRAMERATE)
I now know to give better context when posting up code. Thank you kindly for your trouble guys!

Categories