Preamble: I feel I probably have wasted so much time for a simple situation...
Now, I am making a game with pygame, and at some point, I wanted to split files into two, namely main.py and configurations.py in order to make it more readable.
Everything was going good, until I ran into this problem.
I will share whole code at the bottom, but I want to summarize first:
Now first of all, in main.py, I am importing by,
from configurations import *
now, the game loop on the main.py depends on the variable running by
while running:
.......
.......
.......
And the variable running is initialized in configurations.py by,
# initialize some variables
running = True
So, the main.py has to be getting variable running because it does not give any error and uses it in while running statement.
In the main loop, there is a section where I check for events as follows,
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
This part works just as expected, it alters variable running and program gets out of the while loop.
Now, here comes the problematic part.
In one of the classes(Player class) there is a method as decrease_HP,
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
However, the point I could not figure out is that, it is not changing running variable properly, and game never stops(gets out of while loop). Here is one example output which shows the problem.
pygame 1.9.6
Hello from the pygame community. https://www.pygame.org/contribute.html
-1 HP Current HP: 2
-1 HP Current HP: 1
-1 HP Current HP: 0
-1 HP Current HP: -1
-1 HP Current HP: -2
-1 HP Current HP: -3
-1 HP Current HP: -4
-1 HP Current HP: -5
-1 HP Current HP: -6
So, I hope I could make it clear. I probably have a misunderstanding about importing variables or variable scopes.
By the way, I have tried adding global running above the running = False statement in Player.decrease_HP function.
Thanks in advance.
Exact codes in the files
main.py
# Pygame template - skeleton for a new pygame project
from configurations import *
# initiate some variables
max_bullet = 10
# initialize pygame and create window
pygame.init()
pygame.mixer.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("CORONA RACE")
clock = pygame.time.Clock()
player = Player()
all_sprites.add(player)
# initialize some variables
running = True
# have to use this because, otherwise, for the first SPACE key pressing, the newest_bullet is not defined yet.
newest_bullet = Bullet(0, 0)
# Game loop
while running:
# keep loop running at the right speed
clock.tick(FPS)
# Process input (events)
for event in pygame.event.get():
# check for closing window
if event.type == pygame.QUIT:
running = False
else:
pass
while len(mobs) != 5:
m = Mob()
all_sprites.add(m)
mobs.add(m)
keystate = pygame.key.get_pressed()
player.speedx = 0
if keystate[pygame.K_RIGHT]:
player.speedx += player.SPEED
if keystate[pygame.K_LEFT]:
player.speedx -= player.SPEED
if keystate[pygame.K_SPACE] and player.rect.top - newest_bullet.rect.bottom > BULLET_H + MARGIN and not len(bullets) >= max_bullet:
newest_bullet = player.shoot()
# BULLET_H refers to height of the bullet and margin refers to the minimum allowable margin between two consequent b
# If there are more than 10 bullets at a time on the screen, then no more new bullets can be fired.
if keystate[pygame.K_ESCAPE]:
running = False
if random.randint(0, 14530) > 14470:
power_up = PowerUp()
all_sprites.add(power_up)
powerups.add(power_up)
hits = pygame.sprite.spritecollide(player, powerups, True)
for pu in hits:
power_up_funcs[pu.type](player)
hits = pygame.sprite.groupcollide(mobs, bullets, True, True)
for m in hits:
pass
hits = pygame.sprite.spritecollide(player, mobs, True)
if hits:
player.decrease_HP()
# print(player.HP)
# Update
all_sprites.update()
# Draw / render
screen.fill(WHITE)
all_sprites.draw(screen)
# *after* drawing everything, flip the display
pygame.display.flip()
pygame.quit()
raise SystemExit # to exit python
configurations.py
import pygame
import random
# define constants
WIDTH = 600
HEIGHT = 960
FPS = 30
BULLET_H = 24
BULLET_W = 8
POWERUP_H = 30
POWERUP_W = 30
MOB_W = 50
MOB_H = 80
MARGIN = 10
# define colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
YELLOW = (255, 255, 0)
MAGENTA = (255, 0, 255)
CYAN = (0, 255, 255)
# create sprite groups
all_sprites = pygame.sprite.Group()
bullets = pygame.sprite.Group()
powerups = pygame.sprite.Group()
mobs = pygame.sprite.Group()
# initialize some variables
running = True
# player sprite
class Player(pygame.sprite.Sprite):
SPEED = 15
def __init__(self):
super().__init__()
# pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface((100, 150))
self.image.fill(CYAN)
pygame.draw.circle(self.image, RED, (50, 75), 15, 5)
self.rect = self.image.get_rect()
self.rect.centerx = WIDTH / 2
self.rect.bottom = HEIGHT - 5
self.speedx = 0
self.HP = 3
def update(self):
self.rect.x += self.speedx
if self.rect.right > WIDTH:
self.rect.right = WIDTH
if self.rect.left < 0:
self.rect.left = 0
def shoot(self):
bullet = Bullet(self.rect.centerx, self.rect.top)
all_sprites.add(bullet)
bullets.add(bullet)
return bullet # I need this to set the margin in continious fire.
def change_color(self):
pass
def increase_HP(self):
if self.HP <= 2:
self.HP += 1
print("+1 HP", "Current HP:", self.HP)
else:
print("HP IS ALREADY FULL", "Current HP:", self.HP)
def decrease_HP(self):
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
class Mob(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.image = pygame.Surface((MOB_W, MOB_H))
self.image.fill(MAGENTA)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
self.rect.bottom = random.randint(-2 * MOB_H, 0)
def update(self):
self.rect.y += 6
if self.rect.top > HEIGHT:
self.kill()
# Bullet sprite
class Bullet(pygame.sprite.Sprite):
def __init__(self, x, y):
super().__init__()
self.image = pygame.Surface((BULLET_W, BULLET_H))
self.image.fill(RED)
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.bottom = y
self.speedx = 0
self.speedy = -20
def update(self):
self.rect.y += self.speedy
# kill it if it moves away from the screen
if self.rect.bottom < 0:
self.kill() # built in method of pygame.sprite
# powerup sprite
power_up_funcs = [Player.increase_HP, print] # container for to-do functs.
class PowerUp(pygame.sprite.Sprite):
SPEEDY = 8
def __init__(self):
super().__init__()
self.type = random.randint(0, 1) # [0,1] integer
if self.type == 0: # HP power up
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(GREEN)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx #debug
self.rect.bottom = 0
elif self.type == 1: # shield
self.image = pygame.Surface((POWERUP_W, POWERUP_H))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.left = random.randint(0, WIDTH - POWERUP_W)
# self.rect.centerx = player.rect.centerx # debug
self.rect.bottom = 0
else:
pass
def update(self):
self.rect.y += self.SPEEDY
if self.rect.top > HEIGHT:
self.kill()
TLDR: Use import configuration and fully qualified names, e.g. configuration.running.
If a function inside configuration needs to modify a top-level value, it must use global.
def decrease_HP(self):
global running
self.HP -= 1
print("-1 HP", "Current HP:", self.HP)
if self.HP <= 0:
running = False
Using from configurations import running (or equivalent via ... import *) in main binds the value of configurations.running to a new name main.running. While these names initially share the same value, re-assigning either breaks this equivalency. This is exactly the same as rebinding other names.
>>> a = 1
>>> b = a # a and b point to same value
>>> a == b
True
>>> b = 2 # rebind only b
>>> a == b
False
To make changes visible across the application, one should use an object an modify its value. A common example are containers, such as lists.
>>> a = [1]
>>> b = a # a and b point to same value
>>> a == b
True
>>> b[0] = 2 # modify content of value of b
>>> a == b
True
>>> a[0] == b[0] # content is the same
True
Since modules are objects, it is possible to use them directly to store state.
>>> import configuration
>>> b = configuration # configuration and b point to same value
>>> configuration == b
True
>>> b.running = False # modify content of value of b
>>> configuration == b
True
>>> configuration.running == b.running # content is the same
True
Functions have local scope. Any assignment to a name inside a function implicitly declares the target as local to the function.
>>> running = True
>>> def stop():
... running = False
...
>>> stop() # changes only running inside the function
>>> running
True
This can be made visible by accessing a local name before it has a value.
>>> running = True
>>> def stop():
... print(running)
... running = False
...
>>> stop()
UnboundLocalError: local variable 'running' referenced before assignment
By declaring the name global, no local name is created. The global value can be directly read and written.
>>> running = True
>>> def stop():
... global running
... print(running)
... running = False
...
>>> stop() # will print the global value before the change
True
>>> running # global value was changed
False
Related
What am trying to do is to create an Arkanoid game, where the bricks have 3 points of strength each and then they die. The issue is that instead of just the particular brick that gets hit, to lose the points, the whole brick_sprite group is loosing the points. And when one suppose to die, all the previous within the list up to that one dies. I think the issue is that it loops considering the update on line #240. Please check line 65 at def collision(self): under Brick Class. The issue is somewhere there.
"""This is a simple version of arkanoid game"""
import sys
import pygame
import random
# Set colors R G B
white = (255, 255, 255)
black = (0, 0, 0)
orange = (255, 100, 10)
light_blue = (0, 144, 255)
shadow = (192, 192, 192)
purple = (152, 0, 152)
# Display
display_height = 999
display_width = 444
pygame.display.set_caption = ("Arkanoid 1.0")
FPS = 60
# Movement speed
speed = display_width // 60
# Movements
left = (-speed, 0)
right = (speed, 0)
up = (0, speed)
diagonal_left = [-speed, -speed]
diagonal_right = [speed, -speed]
# Game objects dimentions
base_dimentions = (display_width // 5, display_height // 100)
[brick_width, brick_height] = [display_width // 20 * 2, display_height // 100]
brick_dimentions = [brick_width, brick_height]
ball_dimentions = (display_height // 100, display_height // 100)
# Initializing text font
pygame.font.init()
txt_font = pygame.font.SysFont("Score: ", display_height//44)
# Initializing sprite lists
all_sprites = pygame.sprite.Group()
brick_sprites = pygame.sprite.Group()
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def update(self):
self.collision()
def collision1(self): #This works no issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, True)
return collision
def collision(self): #Here is the issue.
# If brick is hit, loses a point
collision = pygame.sprite.spritecollide(ball, brick_sprites, False)
if collision:
self.point_value -= 1
if self.point_value == 0:
self.kill() ## BUGGISH ##"""
class Ball(pygame.sprite.Sprite):
"""Initiates a moving ball and its' attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(ball_dimentions)
self.image.fill(light_blue)
self.rect = self.image.get_rect()
self.rect.center = self.init_position()
self.direction = random.choice([diagonal_left, diagonal_right])
self.score = 0
def update(self):
self.movement()
def init_position(self):
# Initialize position of the ball
init_position = (board.rect.center[0],
(board.rect.center[1] - (base_dimentions[1] / 2)
- (ball_dimentions[1] / 2)))
return init_position
def collision(self):
# If hit bricks
collision = pygame.sprite.spritecollideany(ball, brick_sprites)
if collision:
self.direction[1] *= -1
self.score += 1
enter code here
def movement(self):
self.containment()
self.rect[1] += self.direction[1]
self.rect[0] += self.direction[0]
self.deflect()
self.ball_loss()
self.collision()
def containment(self):
if self.rect.right >= display_width or self.rect.left <= 0:
self.direction[0] *= -1
if self.rect.top <= 0:
self.direction[1] *= -1
def ball_loss(self):
if self.rect.bottom >= display_height:
self.reset()
bricks_reset()
def reset(self):
self.rect.center = self.init_position()
self.direction[1] *= -1
self.score = 0
def deflect(self):
# If hit base_board, deflect
if (self.rect.bottom == board.rect.top and
(board.rect.left <= self.rect.left <= board.rect.right or
board.rect.left <= self.rect.right <= board.rect.right)):
self.direction[1] *= -1
self.board_ball_interaction()
def board_ball_interaction(self):
# When board is moving, effects balls direction/speed
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT] and board.rect.left > 0:
self.direction[0] -= speed // 2
elif keystate[pygame.K_RIGHT] and board.rect.right < display_width:
self.direction[0] += speed // 2
class Base_board(pygame.sprite.Sprite):
"""Initiates base_board class and it's attributes"""
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(base_dimentions)
self.image.fill(orange)
self.rect = self.image.get_rect()
self.rect.center = (display_width // 2,
display_height - 2 * base_dimentions[1])
self.x_direction = 0
def update(self):
# Up-dates classes' position according to user's imput
self.x_direction = 0
self.movement()
self.rect.x += self.x_direction
def movement(self):
# Creates movement and constrains object within screen dimentions
keystate = pygame.key.get_pressed()
if keystate[pygame.K_LEFT]:
if not self.rect.left <= 0:
self.x_direction = -speed
elif keystate[pygame.K_RIGHT]:
if not self.rect.right >= display_width:
self.x_direction = speed
def shoot(self):
pass
def enlogate(self):
pass
def control():
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
# and adding all sprites on lists
board = Base_board()
ball = Ball()
all_sprites.add(board)
all_sprites.add(ball)
def bricks_list_creator():
# Creates and adds bricks into a list
i = 9
point_value = 2 ####
coordinates = [display_width // 20 + brick_width / 6, display_height // 20]
while i > 0:
brick = Brick(point_value, (coordinates)) ####
coordinates[0] += brick_width * 1.1
brick_sprites.add(brick)
i -= 1
return brick_sprites
def bricks_reset():
# Reset brick list
brick_sprites.empty()
bricks_list_creator()
return brick_sprites
def render_text(screen):
text = txt_font.render("Score: {0}".format(ball.score), 1, (0, 0, 0))
return screen.blit(text, (5, 10))
def render_main(screen):
all_sprites.draw(screen)
brick_sprites.draw(screen)
render_text(screen)
# Game main
def main():
pygame.init()
clock = pygame.time.Clock()
screen = pygame.display.set_mode((display_width, display_height))
bricks_list_creator()
while True:
# Events
clock.tick(FPS)
control()
# Update
brick_sprites.update()
all_sprites.update()
# Render
screen.fill(shadow)
render_main(screen)
pygame.display.flip()
pygame.display.update()
main()
I think the issue is in the update() of your Brick class calling the collision.
The sprite update function is typically used for changing the position or look of your sprite, and is called every frame. So it's not a good place to check for collisions.
A Brick only needs to know its point_value, it doesn't move (AFAIK).
class Brick(pygame.sprite.Sprite):
def __init__(self, point_value, center):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface(brick_dimentions)
self.image.fill(purple)
self.rect = self.image.get_rect()
self.rect.center = center
self.point_value = point_value
def takeHit( self, ball_sprite ):
# the ball has collided with *this* brick
self.point_value -= 1
if self.point_value == 0:
self.kill()
Then in Ball.collision() use the pygame.sprite.spritecollide() to get the list of Bricks the Ball has collided with, and reduce their hit points:
class Ball:
# [...]
def collision(self):
# calculate the list of bricks hit
hit_list = pygame.sprite.spritecollide( self, brick_sprites, False )
for brick in hit_list:
brick.takeHit() # may (or may not) kill the brick
Most of the time the hit_list is going to be a single Brick, but depending on the size of the ball, perhaps occasionally it's two bricks.
I am trying to create a game using pygame for a school project. I would like obstacles(in this case boxes) which get in the way of the player. The player is able to destroy the boxes which would result in another box to be spawned in a random location at the same height.
I've split the code into 3 seperate modules seperating the sprites, the main code and the settings(game variables).
main:
import pygame as pg
import random
from sprites import *
from settings import *
import os
import sys
import time
class Game:
def __init__(init):#initialising the games properties(window,sound,speed,etc).
pg.init()
pg.mixer.init()
init.clock = pg.time.Clock()
init.screen = pg.display.set_mode((WIDTH,HEIGHT))
pg.display.set_caption(TITLE)
init.clock = pg.time.Clock()
init.running = True
init.font_name = pg.font.match_font(FONT_NAME)
init.data()
def data(load):
load.dir = os.path.dirname(__file__)
def new(new):#starts the game again.
new.score = 0
new.obstacles = pg.sprite.Group()
new.platforms = pg.sprite.Group()
new.bullets = pg.sprite.Group()
new.all_sprites = pg.sprite.Group()
new.player = Player(new)
new.all_sprites.add(new.player)
for plat in PLATFORM_LIST:
p = Platform(*plat)
new.all_sprites.add(p)
new.platforms.add(p)
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
new.run()
def run(run):
run.playing = True
while run.playing:
run.cooldown = 0
run.clock.tick(FPS)
run.events()
run.update()
run.draw()
def update(update):
bullet = Bullet
#game update.
update.all_sprites.update()
#spawning obstacles lower half
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
#spawning obstacles randomly throughout the middle half
#spawning obstacles randomly throughout the map upper half
#check if bullet collides with an obstacles.
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
#check if player hits the sides of an obstacle.
if update.player.velocity.x >0:#when moving right.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery+20:#if the player is above the platform.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
update.player.acceleration.y = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y >= collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits side of platforms
if update.player.velocity.x >0 and (update.player.velocity.y < 0):#when moving right.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y < collide[0].rect.centery+50:#if the player is below the obstacle.
update.player.pos.x = collide[0].rect.left - (PLAYER_WIDTH/2)
update.player.velocity.x = 0
if update.player.velocity.x <0:#when moving left.
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y > collide[0].rect.centery:
update.player.pos.x = collide[0].rect.right + (PLAYER_WIDTH/2)
update.player.velocity.x = 0
#check if player hits a platform while ascending:
if update.player.velocity.y <0:#only when moving up.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)
if collide:
if update.player.pos.y > collide[0].rect.bottom:
update.player.pos.y = collide[0].rect.bottom + (PLAYER_HEIGHT/2) + PLAYER_JUMP
update.player.velocity.y = 0
#check if a player hits a platform while falling.
if update.player.velocity.y >0:#only while falling will this apply.
collide = pg.sprite.spritecollide(update.player,update.platforms,False)#false allows you to avoid deleting the object you jump into.
if collide:
if update.player.pos.y < collide[0].rect.centery:#if the player is above the center of the platform.
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
collide = pg.sprite.spritecollide(update.player,update.obstacles,False)
if collide:
if update.player.pos.y < collide[0].rect.centery:
update.player.pos.y = collide[0].rect.top +1
update.player.velocity.y = 0
#spawning obstacles randomly throughout the map upper half
#spawning obstacles randomly throughout the middle half
def events(events):
events.cooldown += events.clock.get_time()
#processes inputs.
for event in pg.event.get():
#check for window closing.
if event.type == pg.QUIT:#if the 'x' button is clicked
if events.playing:
events.playing = False#stop the game loop.
events.running = False#stop the main loop.
if event.type == pg.KEYDOWN:
if event.key == pg.K_UP:
events.player.jump()
if event.key == pg.K_SPACE:
events.player.bullet_list.append(events.player.shoot())
#print(len(events.player.bullet_list))
def draw(draw):
draw.screen.fill(GREY)# creates a black screen.
draw.draw_text(str(draw.player.PLAYER_HEALTH),24,BLACK,WIDTH/32,HEIGHT /32)
draw.all_sprites.draw(draw.screen)#draws the sprites in the group all_sprites.
#after drawing the screen is flipped.
pg.display.flip()
def start_screen(start):#screen displayed when the game is started.
start.screen.fill(BGCOLOR)
start.draw_text(TITLE,48,WHITE,WIDTH/2,HEIGHT /4)
start.draw_text("Arrows to move,UP to jump", 22,WHITE,WIDTH/2,HEIGHT/2)
start.draw_text("Press a key to play",22,WHITE,WIDTH/2,HEIGHT*3/4)
pg.display.flip()
start.any_key()#temporary key to start system.
def any_key(wait):
waiting = True
while waiting:#a loop is used for the start screen until an action is done.
wait.clock.tick(FPS)#allows animations to
for event in pg.event.get():
if event.type == pg.QUIT:#if the 'x' button is pressed during the start screen.
waiting = False
wait.running = False#stops the main loop.
if event.type == pg.KEYUP:#if any key is released.
waiting = False
def over_screen(over):#displayed when the game ends.
if not over.running:
return#skips the over screen when 'x' button is pressed.
over.screen.fill(BGCOLOR)
over.draw_text('GAME OVER',48,WHITE,WIDTH/2,HEIGHT /4)
def draw_text(self, text, size, color, x, y):
font = pg.font.Font(self.font_name, size)#selects the chosen font.
text_surface = font.render(text, True, color)#creates the text with anti aliasing and the color chosen.
text_rect = text_surface.get_rect()
text_rect.midtop = (x, y)#position of text.
self.screen.blit(text_surface, text_rect)#renders text on screen.
g = Game()
g.start_screen()
while g.running:#the main loop.
g.new()
g.over_screen()
pg.quit()#closes the window.
sprites:
#Sprite class
import random
import pygame as pg
from settings import *
vec = pg.math.Vector2 #creates a 2D Vector which stores the x an y cordinates for the sprites.
class Player(pg.sprite.Sprite):
def __init__(self, game):#create initialise the properties of the sprite.
self.game = game#reference to variable in game class.
pg.sprite.Sprite.__init__(self)#provides functions for the sprite in other functions.
self.image = pg.Surface((PLAYER_WIDTH,PLAYER_HEIGHT))#creates a square for the Player to be used as a hitbox.
self.image.fill(GREEN)#place holder for the player.
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2),(HEIGHT/4)# allows you to move the character.
self.pos = vec(WIDTH/2,HEIGHT/2)#the center of the sprite.
self.velocity = vec(0,0)#the speed of the player.
self.acceleration = vec(0,0)#allows for the change in speed.
self.facing = 0 #direction the player is looking.
self.current = 0#current direction facing.
self.PLAYER_HEALTH = 1000
self.bullet_list = []
def jump(self):
hits = pg.sprite.spritecollide(self, self.game.platforms, False)
if hits:#only able to jump when colliding with platform.
self.velocity.y += -PLAYER_JUMP
collide = hits = pg.sprite.spritecollide(self, self.game.obstacles, False)
if collide:
self.velocity.y += -PLAYER_JUMP
def shoot(self):
#if game.cooldown > 400:
#cooldown = 0
self.bullet = Bullet(self,self.current,self.rect.centerx, self.rect.top)
self.bullet_list.append(self.bullet)
for bullet in self.bullet_list:
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
self.game.all_sprites.add(self.bullet)
self.game.bullets.add(self.bullet)
#self.bullet = Bullet(self.current,self.rect.centerx, self.rect.top)#creates bullet postioned in center.
#self.game.all_sprites.add(self.bullet)
#self.game.bullets.add(self.bullet)
def update(self):
self.acceleration = vec(0,PLAYER_GRAV)#resets the position of player when not moving.
keys = pg.key.get_pressed()#inputs a pressed key.
if keys[pg.K_LEFT]:
self.acceleration.x = -PLAYER_ACC
self.facing = -1
self.current = self.facing
if keys[pg.K_RIGHT]:
self.acceleration.x = PLAYER_ACC
self.facing = 1
self.current = self.facing
if self.acceleration.x == 0:#if standing, the previous direction is saved
self.facing = self.current
#print(self.current)
#friction.
self.acceleration.x += self.velocity.x * PLAYER_FRICTION
#equation for displacment.
self.velocity += self.acceleration
self.pos += self.velocity + 0.5 * self.acceleration#moves thes players position to the new x,y co-ordinate.
#boundaries of screen.
if self.rect.right > WIDTH:
self.pos.x = WIDTH -(PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
self.velocity.x = 0 #avoids player sticking to walls by capping speed at boundaries.
self.acceleration.x = 0 #reduces the amount of 'jitter' when trying to move past boundaries.
if self.rect.left < 0 :
self.pos.x = (PLAYER_WIDTH * 0.5)#avoids overlapping the boundary.
# have to add half the player width to avoid player going into walls.
self.velocity.x = 0 #avoids player sticking to walls by stopping player at boundaries.
self.rect.midbottom = self.pos#tracks the position of the players center.
class Platform(pg.sprite.Sprite,):
def __init__(self, x, y, w, h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLACK)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
class Bullet(pg.sprite.Sprite):
damage = 25
def __init__(self,player,current, x, y):
self.player = player#allows the bullet class to use player variables.
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((20,10))
self.image.fill(LBLUE)
self.rect = self.image.get_rect()
self.rect.right = x
self.rect.centery = y + (PLAYER_HEIGHT/2)
#self.damage = 25
self.velocity = vec(0,0)
if current == -1:#when looking left.
self.velocity = vec(-10,0)
if current == 1:#when looking right.
self.velocity = vec(10,0)
def update(self):
self.rect.right += self.velocity.x
#remove when moves of off screen.
if self.rect.right > WIDTH:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
if self.rect.left <0:
self.kill()
for bullet_amount in self.player.bullet_list:
self.player.bullet_list.pop(self.player.bullet_list.index(bullet_amount))
#print(self.rect.x)
settings:
#settings
TITLE = "downpour"
WIDTH = 900
HEIGHT = 500
FPS = 60
FONT_NAME = 'Ariel'
#platforms
PLATFORM_LIST = [(0, HEIGHT - 50, WIDTH, 50),
(WIDTH -225 ,HEIGHT * 3/4 -50,200, 40),#(x,y,width,height)of the platforms.
(0 +25 ,HEIGHT * 3/4 -50,200, 40),
(0 +350,HEIGHT * 3/4 -150,200, 40)]
OBSTACLE_LIST = [(WIDTH/2,HEIGHT -50-50,100,50),(WIDTH/3,HEIGHT -50-50,100,50),(WIDTH/2,HEIGHT -50-50,100,50)]
#player properties
PLAYER_WIDTH = 50
PLAYER_HEIGHT = 50
PLAYER_ACC = 0.55
PLAYER_FRICTION = -0.05
PLAYER_GRAV = 0.8
PLAYER_JUMP = 15
#colors defines
WHITE = (255,255,255)
BLACK = (0,0,0)
GREY = (211,211,211)
RED = (255,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
LBLUE = (132,112,255)
BGCOLOR = LBLUE
The problem I have encountered is with spawning a new box after destroying one of the multiple boxes. A box can be destroyed by depleting its health through shooting at it.
Lets say I have 3 boxes: A,B and C. when I try to destroy B or C, box A is the one that is destroyed and respawned.
I feel like it's an obvious answer...
code relating to the obstacle:
creating the class:
class Obstacle(pg.sprite.Sprite):
def __init__(self,x,y,w,h):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((w,h))
self.image.fill(BLUE)
self.rect = self.image.get_rect()
self.rect.x = x
self.rect.y = y
self.obs_health = 100
adding it to a Sprite group:
for obs in OBSTACLE_LIST:
new.obstacle = Obstacle(*obs)
new.all_sprites.add(new.obstacle)
new.obstacles.add(new.obstacle)
collisions:
collide = pg.sprite.groupcollide(update.bullets,update.obstacles,True,False)
if collide:
update.obstacle.obs_health = update.obstacle.obs_health - bullet.damage
if update.obstacle.obs_health == 0:
update.obstacle.kill()
spawning a new obstacle:
while len(update.obstacles) < 3:
width = random.randrange(50, 100)
update.obstacle = Obstacle(random.randrange(0, WIDTH - width),HEIGHT-100,100,50)
update.obstacles.add(update.obstacle)
update.all_sprites.add(update.obstacle)
First of all, for all instance methods, it would help the reader if you used the name self instead of all the custom names you're using such as new or update for the first argument.
After that rewrite, you code will look like follows:
collide = pg.sprite.groupcollide(self.bullets,self.obstacles,True,False)
if collide:
self.obstacle.obs_health = self.obstacle.obs_health - bullet.damage
if self.obstacle.obs_health == 0:
self.obstacle.kill()
Now ask yourself, why does the program know that the self.obstacle is the one that collided? Should self.obstacle even exist? It looks like self.obstacle was just used a temporary local variable upon creation of the Game class to add Obstacle's to self.obstacles.
If so just use a local variable as follows:
for obs in OBSTACLE_LIST:
obstacle = Obstacle(*obs)
self.all_sprites.add(obstacle)
self.obstacles.add(obstacle)
At this point, hopefully the error message will make it clear that referencing self.obstacle isn't going to work. pg.sprite.groupcollide returns you a Sprite_dict, so you need to extract the obstacle from collide to figure out what has collided.
#KentShikama Thanks for pointing that out.
I have fixed the issue by using the dictionary.
for obstacles, bullets in collide.items():
obstacles.obs_health = obstacles.obs_health - bullet.damage
if obstacles.obs_health == 0:
obstacles.kill()
In simple words my spawning system doesnt work
I am making a dodge the blocks game and I want 1 out of 5 spawnpoints to be empty so that the player can dodge the others. Of course that spawnpoint is random. I tried changing the numbers and looking at some of my older and similar games but nothing works
EDIT: I posted the other 2 files so that you can run the game. I dont think that they are part of the problem since they don't contain anything related to the spawning process
Here my main file:
import pygame as pg
import random
from sprites import *
from game_options import *
class Game:
def __init__(self):
pg.init()
pg.mixer.init()
self.screen = pg.display.set_mode((WIDTH, HEIGHT))
pg.display.set_caption("BRUH")
self.clock = pg.time.Clock()
self.running = True
def new(self):
self.SPAWNENEMIES = 1
self.my_event = pg.event.Event(self.SPAWNENEMIES)
pg.event.post(self.my_event)
pg.time.set_timer(self.SPAWNENEMIES, 3000)
self.spawnpoint1 = 20, -80
self.spawnpoint2 = 140, -80
self.spawnpoint3 = 260, -80
self.spawnpoint4 = 380, -80
self.spawnpoint5 = 500, -80
self.spawnpoints = (self.spawnpoint1,self.spawnpoint2,self.spawnpoint3,self.spawnpoint4,self.spawnpoint5)
self.all_sprites = pg.sprite.Group()
self.blocks = pg.sprite.Group()
self.player = Player()
self.all_sprites.add(self.player)
self.all_sprites.add(self.blocks)
g.run()
def run(self):
self.running = True
while self.running:
self.clock.tick(FPS)
self.events()
self.update()
self.draw()
def update(self):
self.all_sprites.update()
def events(self):
for event in pg.event.get():
if event.type == pg.QUIT:
self.running = False
if event.type == self.SPAWNENEMIES:
num = random.randint(0,len(self.spawnpoints))
#print(num)
for i in range(5):
if num != i:
print(i)
self.block = Block(self.spawnpoints[i])
self.blocks.add(self.block)
self.all_sprites.add(self.blocks)
dead_blocks = pg.sprite.spritecollide(self.player, self.blocks, True)
# if dead_blocks:
# self.running = False
def draw(self):
self.screen.fill(MY_RED)
self.all_sprites.draw(self.screen)
pg.display.flip()
g = Game()
while g.running:
g.new()
g.quit()
Here is game_options.py:
WIDTH = 580
HEIGHT = 800
FPS = 30
# Simple colors
WHITE = (255,255,255)
BLACK = (0,0,0)
GREEN = (0,255,0)
BLUE = (0,0,255)
RED = (255,0,0)
MY_RED = (255, 67, 67)
GREY = (108,106,106)
and the sprites.py
import pygame as pg
from game_options import *
class Player(pg.sprite.Sprite):
def __init__(self):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((70,70))
self.image.fill(WHITE)
self.rect = self.image.get_rect()
self.rect.center = (WIDTH/2, HEIGHT - 100)
self.speedx = 0
def update(self):
keystate = pg.key.get_pressed()
if keystate[pg.K_LEFT]:
self.speedx += -30
if keystate[pg.K_RIGHT]:
self.speedx += 30
self.rect.x = self.speedx
class Block(pg.sprite.Sprite):
def __init__(self,position):
pg.sprite.Sprite.__init__(self)
self.image = pg.Surface((100,70))
self.image.fill(GREY)
self.rect = self.image.get_rect()
self.rect.center = position
self.speedy = 20
def update(self):
self.rect.y += self.speedy
if self.rect.x > HEIGHT:
self.kill()
I expected to have 1 out of 5 spawnpoints empty but for some reason when I run the game, the first "wave" of sprites always has no empty space. Meanwhile the game continues with the expected results. No error messages
Any help would be appreciated
the first "wave" of sprites always has no empty space
This is due to:
self.SPAWNENEMIES = 1
Change it to:
self.SPAWNENEMIES = pg.USEREVENT
The id of the event is an integer. The pygame event system use a set of integers to identify ordinary actions (mouse motion or button clicks, key press and so on). As the docs says:
User defined events should have a value in the inclusive range of USEREVENT to NUMEVENTS - 1.
You should not define self.SPAWNENEMIES equal to 1, because 1 is reserved another type of event (not sure which one actually), this creates confusion and unexpected behaviour.
Seems that in your case yourv event in fact is posted multiple times, especially at the beginning, so you have two waves superimposed. Unless by chance both waves have the missing block at the same position, you'll see 5 blocks.
Another thing you should fix is:
num = random.randint(0,len(self.spawnpoints))
it should be:
num = random.randint(0,len(self.spawnpoints)-1)
or, alternatively:
num = random.randrange(0,len(self.spawnpoints))
The function random.randint:
Return a random integer N such that a <= N <= b. Alias for randrange(a, b+1).
The endpoint is inclusive.
You have 5 blocks, whose index goes from 0 to 4. When random.randint returns 5, no one of them is removed from the following spawning loop.
I'm not familiar with pygame, but i don't see where you reset the blocks once they pass through the dodging block. I'd guess that everytime you are adding blocks over the existing blocks and they are redrawn on top of the old ones, therefore it's not easily clear that there're bazillion of blocks raining down on each run.
And adding a print(self.blocks) puts out this, proving my guess :D
...
<Group(15 sprites)>
<Group(19 sprites)>
<Group(23 sprites)>
...
I made the game like this with sprites to make a platform game.
It was fine before but now it's lagging, so when the gravity is applied it moves by one and looks unnatural.
It was fine at first, when the platforms were rectangles.
I changed the platforms and also inserted a list of enemies as sprites.
It does work fine but it is very laggy and it is very slow.
I don't like how the character is "stuttering".
What can I do?
import pygame, sys
from pygame.locals import *
pygame.init()
class Poppy(pygame.sprite.Sprite):
def __init__(self): #making the player
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('POPPY.png')
self.rect = self.image.get_rect()
self.grav = .5
self.y_vel = 0
self.jumping = False
def jump_update(self): #checking the jumps
if self.jumping:
self.y_vel += self.grav
self.rect.y += self.y_vel
def jump(self): #the jump initializer
if not self.jumping:
self.y_vel = -50
self.jumping = True
def keys(self): #the keys
key = pygame.key.get_pressed()
dist = 5
if key[pygame.K_RIGHT]: # right key
self.rect.x += dist # move right
elif key[pygame.K_LEFT]: # left key
self.rect.x -= dist
def collide_check(self, platform_list): #check if it hit the ground or the platforms
for blocks in platform_list:
if pygame.sprite.collide_rect(self, blocks) == True:
self.rect.y = blocks.top
if self.rect.y >= 600-self.rect.height: # BALL HITS TOP AND BOTTOM
self.grav = 0
self.y_vel = 0
self.rect.y = 600 - self.rect.height
self.jumping = False
def move(self):
self.rect.x += 0
self.rect.y += 10
def draw(self, surface):
surface.blit(self.image, (self.rect.x, self.rect.y))
class Platform(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('levoneplatform.png')
self.rect = self.image.get_rect()
class Enemy(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('enemy.png')
self.rect = self.image.get_rect()
class Menu():
def setup(self):
background_image = pygame.image.load('menu.png').convert_alpha()
screen.blit(background_image, [0,0])
class LevOne():
def setup(self):
background_image = pygame.image.load('night.png').convert_alpha()
screen.blit(background_image, [0, 0])
platforms_one = [ (200,300),
(50,500),
(550,650),
(300,200),
(120,100)
]
for k,v in platforms_one:
platform = Platform()
enemy = Enemy()
platform.rect.x = k
enemy.rect.x = k
platform.rect.y = v
enemy.rect.y = v - 44
platform_list.add(platform)
enemy_list.add(enemy)
screen = pygame.display.set_mode((800,600))
enemy_list = pygame.sprite.Group()
platform_list = pygame.sprite.Group()
Poppy = Poppy()
Menu = Menu()
LevOne = LevOne()
clock = pygame.time.Clock()
level = 0
while True:
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
elif event.type == KEYDOWN:
if event.key == K_SPACE:
Poppy.jump()
Poppy.jump_update()
Menu.setup()
if event.type == MOUSEBUTTONDOWN:
x,y = pygame.mouse.get_pos()
if x >= 544.86 and x <= 700.86 and y <= 385.02 and y >= 340.03:
level = 1
if level == 1:
first_lives = 5
LevOne.setup()
Poppy.keys()
Poppy.move()
Poppy.draw(screen)
platform_list.draw(screen)
enemy_list.draw(screen)
Poppy.draw(screen)
pygame.display.update()
I notice that you're loading your LevOne background on every iteration, as well as initialising the various sprites. This is a one time operation, and so doing it on every iteration isn't necessary, and is probably causing the stuttering you're experiencing. You should load the background in an __init__ of your LevOne class, along with whatever sprites will be used in the level, then access the background variable when blitting in the setup method.
Instead of this;
class LevOne():
def setup(self):
background_image = pygame.image.load('night.png').convert_alpha()
screen.blit(background_image, [0, 0])
platforms_one = [ (200,300),
(50,500),
(550,650),
(300,200),
(120,100)
]
for k,v in platforms_one:
platform = Platform()
enemy = Enemy()
platform.rect.x = k
enemy.rect.x = k
platform.rect.y = v
enemy.rect.y = v - 44
platform_list.add(platform)
enemy_list.add(enemy)
Try this:
class LevOne():
def __init__(self):
self.background_image = pygame.image.load('night.png').convert_alpha()
platforms_one = [ (200,300),
(50,500),
(550,650),
(300,200),
(120,100)
]
for k,v in platforms_one:
platform = Platform()
enemy = Enemy()
platform.rect.x = k
enemy.rect.x = k
platform.rect.y = v
enemy.rect.y = v - 44
platform_list.add(platform)
enemy_list.add(enemy)
def setup(self):
screen.blit(self.background_image, [0, 0])
The same issue is there for the Menu class I'm noticing. The same change should be made as follows.
class Menu():
def __init__(self):
self.background_image = pygame.image.load('menu.png').convert_alpha()
def setup(self):
screen.blit(self.background_image, [0,0])
I just want to reiterate the reason I believe this is running slowly is that it is having to open a file from the system upon every iteration, Which is a slow operation, and you also keep creating your sprite lists for level one, which only needs to be done once.
i'm making a game about a car trying not to collide with pedestrian car.
I'm trying to add a collision to the user_car(aka Player class) with enemy(aka pedestrian_cars class), but i'm not exactly sure where(while loop?) and how to do it. Some variables maybe be bad but I will fix them later.
My program:
import pygame, random, math, sys
from pygame.locals import *
class Player(pygame.sprite.Sprite):
def __init__(self, starty):
pygame.sprite.Sprite.__init__(self)
# Images
self.aliveImage = pygame.image.load("playercar.png").convert_alpha()
#self.deadImage = pygame.image.load("data/PlayerExplode.png").convert_alpha()
self.image = self.aliveImage
self.rect = self.image.get_rect()
self.rect.x = 200
self.rect.y = starty - self.rect.height
self.speed = 7
self.dead = False
# Explode if you get hit, lose a life
def explode(self):
if not self.dead:
self.dead = True
self.image = self.deadImage
pygame.mixer.stop()
self.channel = self.explodeSound.play()
game.playerShots.empty()
game.enemyShots.empty()
game.wave.mship.empty()
game.lives.update(-1)
class pedestrian_cars(pygame.sprite.Sprite):
def __init__(self, starty,startx):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load("pedcar.png").convert_alpha()
self.rect = self.image.get_rect()
self.rect.y = starty - self.rect.height
self.rect.x = startx - self.rect.width
self.delta_y = 5 # 5
self.gravity = .5 #.5
self.has_spawned = False
def update(self):
self.rect.y += self.delta_y
def spawn(self):
if self.rect.y == 480 or self.has_spawned == False:
self.has_spawned = True
self.rect.x = random.randint(60,300)
self.rect.y = -10
def main():
""" Set up the game and run the main game loop """
pygame.mixer.pre_init(44100, -16, 2, 2048)
pygame.init() # prepare the pygame module for use
surfaceSz = 480 # Desired physical surface size, in pixels.
# Create surface of (width, height), and its window.
main_surface = pygame.display.set_mode((surfaceSz, surfaceSz))
#SPRITES###############################################################
user_car = Player(450)
enemy = pedestrian_cars(10,200)
#SPRITES################################################################
background_image = pygame.image.load("background2.png")
all_sprites = pygame.sprite.Group()
user_car.add(all_sprites)
enemy.add(all_sprites)
clock = pygame.time.Clock()
b1 = "background2.png"
back = pygame.image.load(b1).convert()
back2 = pygame.image.load(b1).convert()
y = 0
screenWidth = 600
screenHeight = 480
#Sound/Music#####################################
pygame.mixer.music.load("stilldre.wav")
pygame.mixer.music.play(-1)
#-################################################
while True:
ev = pygame.event.poll() # look for any event
if ev.type == pygame.QUIT: # window close button clicked?
break # ... leave game loop
sys.exit()
if not user_car.dead:
# Move the player
keys = pygame.key.get_pressed()
if keys[pygame.K_LEFT]:
user_car.rect.x = max(user_car.rect.x - user_car.speed, 116-user_car.rect.width)
elif keys[pygame.K_RIGHT]:
user_car.rect.x = min(user_car.rect.x + user_car.speed, 395-user_car.rect.width)
else:
# Go back to playing after the explosion sound finishes
if not self.channel.get_busy():
self.image = self.aliveImage
self.dead = False
self.rect.x = 200
# Update your game objects and data structures here...
all_sprites.update()
enemy.spawn()
main_surface.fill((0,200,255))
main_surface.blit(background_image, (0, 0))
main_surface.blit(back, (0,y))
main_surface.blit(back2,(0,y-screenHeight))
y = y + 8
if y == screenWidth:
y = 0
## if enemy.alive.x ==
## msElapsed = clock.tick(100)
## pygame.display.flip()
all_sprites.draw(main_surface)
# Now the surface is ready, tell pygame to display it!
pygame.display.flip()
clock.tick(200)
msElapsed = clock.tick(100)
pygame.quit() # once we leave the loop, close the window.
main()
You can simply check if the rects of your objects overlap with colliderect:
while True:
...
if user_car.rect.colliderect(enemy.rect):
do_something()
...