About releasing memory when using pygame and Python - python

For example, a projectile flies off screen, does the program still compute its location, speed, etc.?
If so, how to release it?
import pygame
from pygame.locals import *
from sys import exit
pygame.init()
screen = pygame.display.set_mode((640, 480), 0, 32)
background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename)
x = 0.
while True:
for event in pygame.event.get():
if event.type == QUIT:
exit()
screen.blit(background, (0,0))
screen.blit(sprite, (x, 100))
x+= 10.
pygame.display.update()

Yes, the location, speed, etc. still have to be computed, otherwise no object that is off-screen could ever enter the screen area again. Pygame is smart enough not to attempt to render these objects.
It's usually advisable to use pygame sprites and sprite groups which allow you to remove sprites simply by calling self.kill(). You could also use lists or sets to store your objects, but then you have to write a bit more code yourself.
So I'd first define a pygame.Rect (the game_area) with the size of your screen or a bit larger (in the example below I use a smaller one). Rects have a contains method that you can use to check if your sprite's rect is inside the game_area rect. If the sprite is outside, just call self.kill() and pygame will remove the sprite from all associated sprite groups.
import random
import pygame as pg
from pygame.math import Vector2
class Projectile(pg.sprite.Sprite):
def __init__(self, pos, game_area):
super().__init__()
self.image = pg.Surface((5, 5))
self.image.fill(pg.Color('aquamarine2'))
self.rect = self.image.get_rect(center=pos)
self.vel = Vector2(2, 0).rotate(random.randrange(360))
self.pos = Vector2(pos)
self.game_area = game_area
def update(self):
self.pos += self.vel
self.rect.center = self.pos
if not self.game_area.contains(self.rect):
self.kill()
def main():
screen = pg.display.set_mode((640, 480))
game_area = pg.Rect(60, 60, 520, 360)
game_area_color = pg.Color('aquamarine2')
clock = pg.time.Clock()
all_sprites = pg.sprite.Group(Projectile(game_area.center, game_area))
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.add(Projectile(game_area.center, game_area))
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
pg.draw.rect(screen, game_area_color, game_area, 2)
pg.display.flip()
clock.tick(60)
if __name__ == '__main__':
pg.init()
main()
pg.quit()

Yes it does, but since you have only a single projectile (incremented using x), you can easily choose what to do using a few if statements. The process becomes harder when there are multiple projectiles (which you need to store in a container), you should apply this.
Here is an example
for projectile in projectile_list:
# Check if the position is inside the screen
if 0 < projectile.x < WIDTH and 0 < projectile.y < HEIGHT:
# Do position operations
This way, you only process what is required. You can apply similar method to remove unused projectiles from the list or whatever container you are using.

Related

Images layering over old image in Python Pygame [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

Python Crash Course 2nd Edition - Creating an Alien [duplicate]

I'm building a pong game trying to get better at programming but Im having trouble moving the ball. When the move_right method is called the ellipse stretches to the right instead of moving to the right. I've tried putting the ball variable in the init method but that just makes it not move at all even though the variables should be changing on account of the move_right method. I have also tried setting the x and y positions as parameters in the Ball class,but that just stretches it also.
I don't understand why when I run the following code the ball I'm trying to move stretches to the right instead of moves to the right. Can someone explain why this is happening? I have tried everything I can think of but i can't get it to do what I want.
import pygame,sys
import random
class Ball:
def __init__(self):
self.size = 30
self.color = light_grey
self.x_pos = width/2 -15
self.y_pos = height/2 -15
self.speed = 1
#self.ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
def draw_ball(self):
ball = pygame.Rect(self.x_pos, self.y_pos,self.size,self.size)
pygame.draw.ellipse(screen,self.color,ball)
def move_right(self):
self.x_pos += self.speed
class Player:
def __init__(self,x_pos,y_pos,width,height):
self.x_pos = x_pos
self.y_pos = y_pos
self.width = width
self.height = height
self.color = light_grey
def draw_player(self):
player = pygame.Rect(self.x_pos,self.y_pos,self.width,self.height)
pygame.draw.rect(screen,self.color,player)
class Main:
def __init__(self):
self.ball=Ball()
self.player=Player(width-20,height/2 -70,10,140)
self.opponent= Player(10,height/2-70,10,140)
def draw_elements(self):
self.ball.draw_ball()
self.player.draw_player()
self.opponent.draw_player()
def move_ball(self):
self.ball.move_right()
pygame.init()
size = 30
clock = pygame.time.Clock()
pygame.display.set_caption("Pong")
width = 1000
height = 600
screen = pygame.display.set_mode((width,height))
bg_color = pygame.Color('grey12')
light_grey = (200,200,200)
main = Main()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#player = pygame.Rect(width-20,height/2 -70,10,140)
#opponent = pygame.Rect(10,height/2-70,10,140)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
#ball = pygame.Rect(main.ball.x_pos, main.ball.y_pos,main.ball.size,main.ball.size)
#pygame.draw.rect(screen,light_grey,player)
#pygame.draw.rect(screen,light_grey,opponent)
#pygame.draw.ellipse(screen,light_grey,ball)
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
clock.tick(60)
You have to clear the display in every frame with pygame.Surface.fill:
while True:
# [...]
screen.fill(0) # <---
main.draw_elements()
main.move_ball()
main.ball.x_pos += main.ball.speed
pygame.display.flip()
# [...]
Everything that is drawn is drawn on the target surface. The entire scene is redraw in each frame. Therefore the display needs to be cleared at the begin of every frame in the application loop. The typical PyGame application loop has to:
handle the events by either pygame.event.pump() or pygame.event.get().
update the game states and positions of objects dependent on the input events and time (respectively frames)
clear the entire display or draw the background
draw the entire scene (blit all the objects)
update the display by either pygame.display.update() or pygame.display.flip()

Pygame sprite constantly redrawn to screen and not moving

I can't see what's wrong with the below code. All I want to do is make the frog move across the screen, but it is simply redrawing many, many frogs all one pixel apart. How do I move the frog rather than just draw it again?
import pygame
from pygame.constants import *
pygame.init()
SCREEN_WIDTH = 800
SCREEN_HEIGHT = 600
screen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
class Frog(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.transform.scale(pygame.image.load('frog.png'), (64, 64))
self.rect = self.image.get_rect()
self.dx = 1
def update(self, *args):
self.rect.x += self.dx
running = True
frog = Frog()
entities = pygame.sprite.Group()
entities.add(frog)
while running:
for event in pygame.event.get():
if event.type == QUIT:
running = False
entities.update()
entities.draw(screen)
That is how you do it in Pygame, you just redraw objects every iteration to give the illusion that they're moving but you must cover up the previous drawn objects by filling your window with a solid color e.g.
screen.fill((255, 255, 255))
This should be at the start of your game loop so you have a fresh canvas for drawing your objects each iteration.
while running:
screen.fill((255,255,255))
for event in pygame.event.get():
if event.type == QUIT:
running = False
entities.update()
entities.draw(screen)
pygame.display.update()
You may have to use the pygame.display.update() function to update the whole screen rather than just your entities.

Sprite not working/not being displayed (Python/Pygame)

Please help me I am starting a game and my sprite is not showing on screen. Take a look, I am using two files, which include pygame and classes. I hope that's enough information.
Adventure.py --
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
from LittleMan import LittleMan
playerSprite = LittleMan(CL, 200, 300)
size = (1000, 600)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
all_sprites_list = pygame.sprite.Group()
playerSprite.rect.x = 200
playerSprite.rect.y = 300
carryOn = True
clock = pygame.time.Clock()
while carryOn:
for event in pygame.event.get():
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.draw(screen)
all_sprites_list.add()
all_sprites_list.update()
pygame.display.flip()
clock.tick(60)
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
LittleMan.moveLeft(5)
if keys[pygame.K_RIGHT]:
LittleMan.moveRight(5)
LitlleMan.py --
import pygame
CL = (156,102,31)
WHITE = (255,255,255)
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
pygame.draw.rect(self.image, CL, [0, 0, width, height])
self.rect = self.image.get_rect()
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
Anyone know why this could be? I've looked everywhere but I've done it in two files and no-one seems to have an answer to that and if there is a decent answer please link it. Thank you.
I think the real crux of the problem is the code is not adding playerSprite to the all_sprites_list. If a sprite is not in this list, the sprite update and paint calls do not include it. At first I thought the initial position of the sprite may be off-screen, so I parameterised the screen dimensions, and positioned the sprite in the middle.
There's a bunch of other indentation issues in the question's code too, but I think these may be from pasting the question into SO.
I cleaned-up and re-organised the code, it seems to run, and pressing Left/right moves the brown box.
I merged both files together to make my debugging easier, my apologies.
import pygame, random
pygame.init()
BROWN = (205,192,176)
DEEPBROWN = (139,131,120)
CL = (156,102,31)
WHITE = (255,255,255)
WINDOW_WIDTH=500
WINDOW_HEIGHT=500
# Setup the pyGame window
size = (WINDOW_WIDTH, WINDOW_HEIGHT)
screen = pygame.display.set_mode(size)
pygame.display.set_caption("Adventure")
class LittleMan (pygame.sprite.Sprite):
def __init__(self, color, width, height):
super().__init__()
self.image = pygame.Surface([50, 75])
self.image.fill(CL)
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
self.rect.center = ( WINDOW_WIDTH//2 , WINDOW_HEIGHT//2 )
def moveRight(self, pixels):
self.rect.x += pixels
def moveLeft(self, pixels):
self.rect.x -= pixels
# Create the player sprite
playerSprite = LittleMan(CL, 200, 300)
# Add user sprite into PyGame sprites list
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(playerSprite);
clock = pygame.time.Clock()
carryOn = True
while carryOn:
# Handle user input
for event in pygame.event.get():
if event.type == pygame.QUIT:
carryOn = False
if event.type==pygame.KEYDOWN:
if event.key==pygame.K_x: #Pressing the x Key will quit the game
carryOn=False
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
playerSprite.moveLeft(5)
if keys[pygame.K_RIGHT]:
playerSprite.moveRight(5)
# Update and Reapint the screen
screen.fill(BROWN)
pygame.draw.rect(screen, DEEPBROWN, [55, 250, 900, 70],0)
all_sprites_list.update()
all_sprites_list.draw(screen)
pygame.display.flip()
clock.tick(60)
The LittleMan class does not include an update() function, which all_sprites_list.update() would normally call. I expect you just haven't needed this part yet.
EDIT: More notes on the sprite update() function ~
The sprite's update() function is called by pygame during the all_sprites_list.update() function. So that means any sprite added to this group, has its update run quasi-automatically. Ideally all sprites have an update function, and it handles the look, position and collisions (etc.) of the sprite.
The idea behind this function is to do any updates to the sprite. So of you had a sprite that was moving, this function would calculate the next position, and set the sprite's self.rect. Or perhaps that sprite is animated - the update function would set the sprite's image to the next frame of the animation based on the time.
Obviously all this work can be performed outside of the update function. But it provides a simple and clean programming mechanism for sprite mechanics.

Pygame shapes cannot take non integer arguments

Does it matter that in the Pygame module, the shapes cannot take float values as their arguments?
This is raised due to the fact that I am currently making a relatively basic physics simulation, and using pygame to do the graphics, and in a physics simulation, it rarely/never happens that an object is centred such that it has an integer value.
I am wondering mainly if this would have a significant effect on the accuracy of the simulation?
I usually recommend storing the position and velocity of the game objects as vectors (which contain floats) to keep the physics accurate. Then you can first add the velocity to the position vector and afterwards update the rect of the object which serves as the blit position and can be used for collision detection. You don't have to convert the position vector to ints before you assign it to the rect, since pygame will do that automatically for you.
Here's a little example with an object that follows the mouse.
import pygame as pg
from pygame.math import Vector2
class Player(pg.sprite.Sprite):
def __init__(self, pos, *groups):
super().__init__(*groups)
self.image = pg.Surface((30, 30))
self.image.fill(pg.Color('steelblue2'))
self.rect = self.image.get_rect(center=pos)
self.direction = Vector2(1, 0)
self.pos = Vector2(pos)
def update(self):
radius, angle = (pg.mouse.get_pos() - self.pos).as_polar()
self.velocity = self.direction.rotate(angle) * 3
# Add the velocity to the pos vector and then update the
# rect to move the sprite.
self.pos += self.velocity
self.rect.center = self.pos
def main():
screen = pg.display.set_mode((640, 480))
clock = pg.time.Clock()
font = pg.font.Font(None, 30)
color = pg.Color('steelblue2')
all_sprites = pg.sprite.Group()
player = Player((100, 300), all_sprites)
done = False
while not done:
for event in pg.event.get():
if event.type == pg.QUIT:
done = True
all_sprites.update()
screen.fill((30, 30, 30))
all_sprites.draw(screen)
txt = font.render(str(player.pos), True, color)
screen.blit(txt, (30, 30))
pg.display.flip()
clock.tick(30)
if __name__ == '__main__':
pg.init()
main()
pg.quit()
In simulation terms/accuracy wise, you should always keep your data as float. You should just round them, and cast them to int at the very point you are calling Pygame's functions that need integers.
Just do something like:
pygame.draw.rect(surface, color, (int(x), int(y), width, height))
for drawing a rectangle, for example.

Categories