This question already exists:
How to set limit of bullets on pygame [closed]
Closed 9 years ago.
Here's the code, please help with detailed instruction. I need to know how to set limits on shooting bullets. If you could show me how to do it with my code that would be great. I'm including the player class, bullet class, main loop and shooting mech.
# This class represents the Player
class Player(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([20,20])
self.image.fill(red)
self.rect = self.image.get_rect()
# This class represents the bullet
class Bullet(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([10, 4])
self.image.fill(black)
self.rect = self.image.get_rect()
class Shoot(pygame.sprite.Sprite):
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([10, 4])
self.image.fill(black)
self.rect = self.image.get_rect()
# Initialize Pygame
pygame.init()
# Set the height and width of the screen
screen_width=700
screen_height=400
screen=pygame.display.set_mode([screen_width,screen_height])
# This is a list of every sprite. All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
# List of each bullet
bullet_list = pygame.sprite.Group()
shoot_list = pygame.sprite.Group()
# Create a red player block
player = Player()
all_sprites_list.add(player)
#Loop until the user clicks the close button.
done=False
# Used to manage how fast the screen updates
clock=pygame.time.Clock()
score = 0
player.rect.y=370
bullet_count=0
# -------- Main Program Loop -----------
while done==False:
# ALL EVENT PROCESSING SHOULD GO BELOW THIS COMMENT
for event in pygame.event.get(): # User did something
if event.type == pygame.QUIT: # If user clicked close
done=True # Flag that we are done so we exit this loop
if event.type == KEYDOWN:
if event.key == K_LEFT:
bullet = Bullet()
bullet.rect.x = player.rect.x
bullet.rect.y = player.rect.y
all_sprites_list.add(bullet)
bullet_list.add(bullet)
if event.key == K_RIGHT:
shoot = Shoot()
shoot.rect.x = player.rect.x
shoot.rect.y = player.rect.y
all_sprites_list.add(shoot)
shoot_list.add(shoot)
# ALL EVENT PROCESSING SHOULD GO ABOVE THIS COMMENT
# ALL GAME LOGIC SHOULD GO BELOW THIS COMMENT
# Calculate mechanics for each bullet
bulletCounter = 0
for bullet in bullet_list:
bullet.rect.x -= 5
for shoot in shoot_list:
shoot.rect.x -= -5
# Remove the bullet if it flies up off the screen
if bullet.rect.y < -10:
bullet_list.remove(bullet)
all_sprites_list.remove(bullet)
# Get the current mouse position. This returns the position
# as a list of two numbers.
pos = pygame.mouse.get_pos()
# Set the player x position to the mouse x position
player.rect.x=pos[0]
# ALL GAME LOGIC SHOULD GO ABOVE THIS COMMENT
# ALL CODE TO DRAW SHOULD GO BELOW THIS COMMENT
# Clear the screen
screen.fill(white)
# Draw all the spites
all_sprites_list.draw(screen)
# ALL CODE TO DRAW SHOULD GO ABOVE THIS COMMENT
# Limit to 20 frames per second
clock.tick(20)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
pygame.quit()
You can create a gun class which have the bullet count as a property and shooting will be a function of the class.
class Gun(object):
def __init__(self):
self.bullet_count = 5
def __shoot(self, bullet_sprite, bullet_list):
if self.bullet_count > 0:
self.bullet_count -= 1
bullet_sprite.rect.x = player.rect.x
bullet_sprite.rect.y = player.rect.y
all_sprites_list.add(bullet_sprite)
bullet_list.add(bullet_sprite)
def shoot_left(self, bullet_list):
self.__shoot(Bullet(), bullet_list)
def shoot_right(self, bullet_list):
self.__shoot(Shoot(), bullet_list)
-------------------------------------------------------
Then, before your enter your while-loop you would create an instance of the gun class and use it as follows:
if event.type == pygame.KEYDOWN and gun.bullet_count > 0:
print "KEY DOWN"
if event.key == K_LEFT:
gun.shoot_left(bullet_list)
if event.key == K_RIGHT:
gun.shoot_right(shoot_list)
Related
import pygame, sys
start = True
class Player(pygame.sprite.Sprite):
def __init__(self, pos_x, pos_y):
super().__init__()
self.attack_animation = False
self.sprites = []
self.sprites.append(pygame.image.load('crossHair.png'))
self.sprites.append(pygame.image.load('crossHair_2.png'))
self.sprites.append(pygame.image.load('crossHair_3.png'))
self.sprites.append(pygame.image.load('crossHair_4.png'))
self.current_sprite = 0
self.image = self.sprites[self.current_sprite]
self.image.set_colorkey('white')
for items in self.sprites:
items.set_colorkey('white')
self.rect = self.image.get_rect()
self.rect.topleft = [pos_x,pos_y]
def attack(self):
self.attack_animation = True
self.image.set_colorkey('white')
def update(self,speed):
self.image.set_colorkey('white')
self.current_sprite += speed
if int(self.current_sprite) >= len(self.sprites):
self.attack_animation = False
self.current_sprite = 0
self.image = self.sprites[int(self.current_sprite)]
# General setup
pygame.init()
clock = pygame.time.Clock()
# Game Screen
screen_width = 400
screen_height = 400
mouse = pygame.mouse.get_pos()
screen = pygame.display.set_mode((screen_width,screen_height))
pygame.display.set_caption("Sprite Animation")
# Creating the sprites and groups
moving_sprites = pygame.sprite.Group()
while True:
globals()['mouse'] = pygame.mo[![now this is the problem][1]][1]use.get_pos()
player = Player(mouse[0],mouse[1])
moving_sprites.add(player)
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
sys.exit()
if event.type == pygame.KEYDOWN:
player.attack()
# Drawing
screen.fill((0,0,0))
screen.set_colorkey('white')
moving_sprites.draw(screen)
moving_sprites.update(0.04)
pygame.display.flip()
clock.tick(120)
this is creatiing my player / crossHair every 120th of a second and it leaves behind it's last sprite
now the problem with that is it leaves behind my last sprite and if i put
moving_sprites = pygame.sprite.Group()
in while loop then it won't animate anybody can solve this pls answer my question...
This is not the way to animate sprites. Creating a new sprite every frame is bad practice and a waste of performance since all objects have to be recreated. Even more you load the images in the constructor of the Player class. If you do this every frame, then every frame the images have to be loaded from the volume and decoded.
Create the sprite once before the application loop and change the attributes of the sprite object in the loop. This will give you the best performance:
class Player(pygame.sprite.Sprite):
# [...]
def set_pos(self, pos_x, pos_y):
self.rect.topleft = (pos_x, pos_y)
moving_sprites = pygame.sprite.Group()
player = Player(0, 0)
moving_sprites.add(player)
while True:
player.set_pos(*pygame.mouse.get_pos())
# [...]
This question already has answers here:
Why is nothing drawn in PyGame at all?
(2 answers)
Closed 1 year ago.
Below is the code... Bullet class is defined first, class is called in _fire_bullet, _update_screen, and _check_KEYDOWN. When spacebar is pressed the event handler should take in the event key, call _fire_bullet function where a bullet object is created and added to sprite list. Then fired and moves across screen. As of now its going to move the wrong direction but I will correct that issue easily when I can actually see the bullet.
#make a pygame window with a blue background
import pygame
import sys
from pygame.sprite import Sprite
class Bullet(Sprite):
""" A class to manage bullets fired from the ship """
def __init__(self, game):
""" create a bullet object at the ships current position """
super().__init__()
self.screen = game.screen
self.bullet_speed = 1.0
self.bullet_width = 300
self.bullet_height = 150
self.bullet_color = (0, 200, 200)
#create a bullet at rect (0,0) and the set the correct position
self.rect = pygame.Rect(0, 0, self.bullet_width, self.bullet_height)
self.rect.midright = game.rect.midright
#store the bullets position as a decimal value
self.y = float(self.rect.y)
self.x = float(self.rect.x)
def update(self):
""" move the bullet up the screen """
#update the decimal position of the bullet.
self.y -= self.bullet_speed
self.rect.x = self.x
#uipdate the rect position
self.rect.y = self.y
def draw_bullet(self):
""" draw the bullet to the screen """
pygame.draw.rect(self.screen, self.bullet_color, self.rect)
class Game:
""" a class the creates a window with a blue screen """
def __init__(self):
pygame.init()
#--------------------------------------------------------------------------------------------
#screen size, color, caption
self.screen = pygame.display.set_mode((1200,800)) #create attribute to hold display settings
self.bg_color = (250,250,250) #create attribute to hold RGB color (blue)
pygame.display.set_caption("Blue Screen")
#--------------------------------------------------------------------------------------------
#--------------------------------------------------------------------------------------------
#tank drawing
self.screen_rect = self.screen.get_rect() #get the screen rect dim
self.image = pygame.image.load('images/tank.bmp') #load the image from directory
self.rect = self.image.get_rect() #get the image rect dim
self.rect.center = self.screen_rect.center #store the screens center x/y coord
#tank movement
self.tank_moving_left = False
self.tank_moving_right = False
self.tank_moving_up = False
self.tank_moving_down = False
self.tank_speed = 1 #tank pixels
self.direction_right = self.image #holds right image
self.direction_left = pygame.transform.flip(self.image, True, False) #holds left image
#--------------------------------------------------------------------------------------------
self.bullets = pygame.sprite.Group()
def move(self):
""" move tnak tank_speed based on direction of movement (key pressed)
also detect collision """
if self.tank_moving_right and self.rect.right < self.screen_rect.right:
self.rect.x += self.tank_speed
if self.tank_moving_left and self.rect.left > self.screen_rect.left:
self.rect.x -= self.tank_speed
if self.tank_moving_down and self.rect.bottom < self.screen_rect.bottom:
self.rect.y += self.tank_speed
if self.tank_moving_up and self.rect.top > self.screen_rect.top:
self.rect.y -= self.tank_speed
def blitme(self):
""" draw the image of the tank """
self.screen.blit(self.image, self.rect)
def _update_screen(self):
""" update screen """
self.screen.fill(self.bg_color)
self.blitme()
pygame.display.flip()
for bullet in self.bullets.sprites():
bullet.draw_bullet()
print(bullet.rect.midright)
def _check_KEYDOWN(self, event):
""" when key is press either quit, or move direction of arrow pressed and flip image """
if event.key == pygame.K_q:
sys.exit()
elif event.key == pygame.K_RIGHT:
self.tank_moving_right = True
self.image = self.direction_right
elif event.key == pygame.K_LEFT:
self.tank_moving_left = True
self.image = self.direction_left
elif event.key == pygame.K_UP:
self.tank_moving_up = True
elif event.key == pygame.K_DOWN:
self.tank_moving_down = True
elif event.key == pygame.K_SPACE:
self._fire_bullet()
print(1)
def _check_KEYUP(self, event):
""" when key is let go stop moving """
if event.key == pygame.K_RIGHT:
self.tank_moving_right = False
elif event.key == pygame.K_LEFT:
self.tank_moving_left = False
elif event.key == pygame.K_UP:
self.tank_moving_up = False
elif event.key == pygame.K_DOWN:
self.tank_moving_down = False
def _fire_bullet(self):
""" create a bullet and add it to the bullets group """
new_bullet = Bullet(self)
self.bullets.add(new_bullet)
def run_game(self):
""" loops the game/ updates screen/ checks for key clicks"""
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
self._check_KEYDOWN(event)
elif event.type == pygame.KEYUP:
self._check_KEYUP(event)
self.move()
self.bullets.update()
self._update_screen()
if __name__ == '__main__':
a = Game()
a.run_game()
Your _update_screen() function is drawing your bullets after updating the display. The bullets then get overwritten next time _update_screen() is called when you fill the screen with the background color.
If you reorder your screen update function as follows you should be able to see your bullets:
def _update_screen(self):
""" update screen """
self.screen.fill(self.bg_color)
self.blitme()
for bullet in self.bullets.sprites():
bullet.draw_bullet()
print(bullet.rect.midright)
pygame.display.flip()
Also, you can create an image for your bullet by changing your initialization function:
…
#create a bullet at rect (0,0) and the set the correct position
self.image = pygame.Surface((self.bullet_width, self.bullet_height))
self.image.fill(self.bullet_color)
self.rect = self.image.get_rect()
Then you won't need a draw_bullet() function, you can replace the drawing of individual bullets in _update_screen() with self.bullets.draw(self.screen)
So im trying to make a local multiplayer game, i have created the player class which works fine so far.
Now im trying to do a bullet class to create different types of shots for the player's spaceships, however upon calling my bullet, it only gets drawn to the screen where the player was at the start of the game and it doesnt move (so far just trying to program it to move)
Here is the code:
import pygame
pygame.init()
#Display screen
screen = pygame.display.set_mode((800, 600))
pygame.display.set_caption("Space Arena")
background = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\background.png').convert_alpha()
playerImg = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player.png').convert_alpha()
player2Img = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\Player2.png').convert_alpha()
regular_bullet = pygame.image.load(r'C:\Users\Bruno\Documents\PythonProjects\Pygame\Multiplayer\Images\bullet.png').convert_alpha()
class Player:
def __init__(self, image, x, y, isPlayer1, isPlayer2):
self.image = image
self.x = x
self.y = y
self.isPlayer1 = isPlayer1
self.isPlayer2 = isPlayer2
def Move(self):
key_states = pygame.key.get_pressed()
x_change = 0.2
y_change = 0.2
if self.isPlayer1:
if key_states[pygame.K_a]:
self.x += -x_change
if key_states[pygame.K_d]:
self.x += x_change
if key_states[pygame.K_w]:
self.y += -y_change
if key_states[pygame.K_s]:
self.y += y_change
elif self.isPlayer2:
if key_states[pygame.K_LEFT]:
self.x += -x_change
if key_states[pygame.K_RIGHT]:
self.x += x_change
if key_states[pygame.K_UP]:
self.y += -y_change
if key_states[pygame.K_DOWN]:
self.y += y_change
def draw(self, screen):
screen.blit(self.image,(self.x,self.y))
def ColorPlayer(self ,image):
if self.isPlayer1:
self.image.fill((190,0,0,100), special_flags = pygame.BLEND_ADD)
if self.isPlayer2:
self.image.fill((0,0,190,100), special_flags = pygame.BLEND_ADD)
class Bullet():
def __init__(self, image, bulletx, bullety):
self.image = image
self.bulletx = bulletx
self.bullety = bullety
self.bullet_state = "ready"
def draw(self, screen):
if self.bullet_state == "fire":
screen.blit(self.image,(self.bulletx,self.bullety))
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
if self.bulletx > 800:
self.bullet_state = "ready"
def redrawWindow(screen, player, player2, background, player_bullet):
screen.fill((255,255,255))
screen.blit(background,(0,0))
player.draw(screen)
player2.draw(screen)
player_bullet.draw(screen)
pygame.display.update()
def Main():
done = False
player = Player(playerImg,200,200,True,False)
player2 = Player(player2Img,600,200,False,True)
player1_bullet = Bullet(regular_bullet, player.x, player.y)
while not done:
player.Move()
player2.Move()
player.ColorPlayer(playerImg)
player2.ColorPlayer(player2Img)
redrawWindow(screen, player, player2, background, player1_bullet)
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.shoot()
Main()
It looks like shoot is effectively the "update" function for your bullet, and the code inside would need to be called every frame (like with player.Move()) for the bullet to keep moving after it's fired.
The only code you have that changes the bullet position is this bit inside the shoot method.
def shoot(self):
change_x = 0.9
if self.bullet_state == "fire":
self.bulletx += change_x
Nothing else moves it. You need to have a move function that adjusts it position and call that each frame. In fact your shoot function is a bit odd it looks more like what the move function would look like. Perhaps rename it to move() and call it each frame?
That function is a bit unusual for a shoot() function, since it moves an existing bullet instead of firing a new one. Normally a shoot method would be expected to be a method in the player and to create a new bullet. It does not really make sense for a bullet instance function to shoot and to create a new bullet. Also you seem to have coded it so that there is a single bullet that goes to 800 and then can be re-fired, but you do not have anything that resets the bullets position back to where the player is.
It might make more sense for the 'ready' and 'fire' state to be a state of the player since the player is the one that can or cannot fire. Also the shoot() method is more commonly in the player class and when it fires (the player is in state 'ready') it creates a new Bullet instance at the players position.
The bullet move() method would be called each frame just like the player move() functions are. Normally there would be more than one bullet allowed at a time and so you would keep a list of the and have to iterate through the list of bullets moving them along (and doing collision checking to see if they hit anything). If a bullet goes off the screen or hits something then you remove the bullet from the bullet list.
You have to change 2 things.
The method Bullet.shoot() has to be continuously invoked in the main application loop. Note, this method updates the bullet dependent on its state. If the state of the bullet is "fire" then the bullet moves. Else it stands still.
Update the position of the bullet when it is fired. The bullet always has to start at the position of the player:
def Main():
# [...]
while not done:
# [...]
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_SPACE:
if player1_bullet.bullet_state is "ready":
player1_bullet.bullet_state = "fire"
player1_bullet.bulletx = player.x
player1_bullet.bullety = player.y
player1_bullet.shoot()
I'm trying to build a game something like, balls are coming down randomly and the boy in the bottom catch the ball. I made a row of balls on top in a random manner, but I'm not sure how to make them appear randomly and fall down individually.
baseball_game.py
import sys
import pygame
from pygame.sprite import Group
from settings import Settings
from boy import Boy
from ball import Ball
import game_functions as gf
def run_game():
# Initialize pygame, settings, and screen object.
pygame.init()
bg_settings = Settings()
screen = pygame.display.set_mode(
(bg_settings.screen_width, bg_settings.screen_height))
pygame.display.set_caption("Catch the Baseball!")
# Make a boy
boy = Boy(bg_settings, screen)
balls = Group()
# Create the fleet of aliens.
gf.create_fleet(bg_settings, screen, boy, balls)
# Make a ball
ball = Ball(bg_settings, screen)
# Set the background color.
bg_color = (217, 208, 187)
# Start the main loop for the game.
while True:
gf.check_events(boy)
boy.update()
gf.update_balls(balls)
gf.update_screen(bg_settings, screen, boy, balls)
run_game()
ball.py
import pygame
from pygame.sprite import Sprite
import random
class Ball(Sprite):
"""A class to represent a single ball."""
def __init__(self, bg_settings, screen):
"""Initalize the ball and set its starting position."""
super(Ball, self).__init__()
self.screen = screen
self.bg_settings = bg_settings
# Load the ball image and set its rect attribute.
self.image = pygame.image.load('images/ball.png')
self.rect = self.image.get_rect()
# Start each new ball.
self.rect.x = random.randint(-10, 40)
self.rect.y = random.randint(-10, 40)
# Store the ball's exact position.
self.y = float(self.rect.y)
def update(self):
"""Move the ball down."""
self.y += self.bg_settings.ball_speed_factor
self.rect.y = self.y
def blitme(self):
"""Draw the ball at its current location."""
self.screen.blit(self.image, self.rect)
game_functions.py
import sys
import pygame
from ball import Ball
from random import randint
random_number = randint(-15, 39)
def check_events(boy):
"""Respond to keypresses and mouse events."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, boy)
elif event.type == pygame.KEYUP:
check_keyup_events(event, boy)
def check_keydown_events(event, boy):
"""Respond to keypresses."""
if event.key == pygame.K_RIGHT:
boy.moving_right = True
elif event.key == pygame.K_LEFT:
boy.moving_left = True
elif event.key == pygame.K_q:
sys.exit()
def check_keyup_events(event, boy):
"""Respond to key releases."""
if event.key == pygame.K_RIGHT:
boy.moving_right = False
elif event.key == pygame.K_LEFT:
boy.moving_left = False
def update_screen(bg_settings, screen, boy, balls):
"""Update images on the screen and flip to the new screen."""
# Redraw the screen during each pass through the loop.
screen.fill(bg_settings.bg_color)
boy.blitme()
balls.draw(screen)
# Make the most recently drawn screen visible.
pygame.display.flip()
def get_number_balls_x(bg_settings, ball_width):
"""Determine the number of aliens that fit in a row."""
available_space_x = bg_settings.screen_width - 2 * ball_width
number_balls_x = int(available_space_x / (2 * ball_width))
return number_balls_x
def create_ball(bg_settings, screen, balls, ball_number):
"""Create a ball and place it in the row."""
ball = Ball(bg_settings, screen)
ball_width = ball.rect.width
ball.x = ball_width + 2 * ball_width * ball_number
ball.rect.x = ball.x
balls.add(ball)
def create_fleet(bg_settings, screen, boy, balls):
ball = Ball(bg_settings, screen)
number_balls_x = get_number_balls_x(bg_settings, ball.rect.width)
for ball_number in range(number_balls_x):
create_ball(bg_settings, screen, balls, ball_number)
def update_balls(balls):
"""Update the positions of all balls in the fleet."""
balls.update()
So the first row of balls are randomly placed on top, but how do I make it to appear not all at once, and falling down separately?
Since you mention that you want the balls to fall down individually, we can think of each ball having two states: "falling" and "not falling". We can adjust the update and __init__ functions for ball.py accordingly:
class Ball(Sprite):
def __init__(self, bg_settings, screen)
# ... all your other properties
self.is_falling = False # the ball is not falling when first created
# ...
def update(self):
if self.is_falling:
self.y += self.bg_settings.ball_speed_factor
self.rect.y = self.y
Now we just need some way of triggering the falling property in each ball randomly. In game_functions.py, you have a method that updates the balls. We could add some function there to randomly decide whether we want a ball to drop, and which ball we want to drop:
from random import random
# ...
def update_balls(self):
if random() < 0.4: # 40% percent chance of making a ball drop
ball_index = randint(0, len(balls)-1)
balls[ball_index].is_falling = True
balls.update()
Make a variable called RNG and set it to generate random numbers between any range let's say between -50 and 50. Then you can make an array called RNG_CASES[] and give them any random constant value in the range that you set for the RNG. Finally, make a conditional structure:
if(RNG in RNG_CASES[]):
fall();
else:
pass; #(pass not needed, only for pseudocode).
You could just add new Ball instances to the group if some condition is True (if random.randrange(100) < 2: in the example below). If there should be a maximum number of balls, add and len(balls) < max_balls:.
Also, don't forget to kill the sprites when they leave the screen.
import random
import pygame
from pygame.sprite import Sprite
class Ball(Sprite):
"""A class to represent a single ball."""
def __init__(self, bg_settings, screen):
"""Initalize the ball and set its starting position."""
super(Ball, self).__init__()
self.screen = screen
self.bg_settings = bg_settings
self.image = pygame.Surface((30, 30))
self.image.fill((100, 200, 0))
self.rect = self.image.get_rect()
# Start each new ball.
self.rect.x = random.randint(-10, self.bg_settings.screen_width)
self.rect.y = random.randint(-100, -40)
# Store the ball's exact position.
self.y = float(self.rect.y)
def update(self):
"""Move the ball down."""
self.y += 1
self.rect.y = self.y
if self.rect.top > self.bg_settings.screen_height:
self.kill()
class Settings:
screen_width = 640
screen_height = 480
bg_color = pygame.Color('gray12')
def run_game():
pygame.init()
clock = pygame.time.Clock()
bg_settings = Settings()
screen = pygame.display.set_mode(
(bg_settings.screen_width, bg_settings.screen_height))
balls = pygame.sprite.Group()
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
return
balls.update()
if random.randrange(100) < 2:
balls.add(Ball(bg_settings, screen))
screen.fill(bg_settings.bg_color)
balls.draw(screen)
pygame.display.flip()
clock.tick(60)
run_game()
I'm working on my first project in Python / Pygame, which is a shooter-style game. However, when I create multiple instances of my Bullet sprite and add them to the sprite group, only the most recent instance is shown on the screen. That is, only one bullet is showing at any given time.
I think Lines 175-180 or within the Bullet class are causing the problem.
My code:
import pygame, random , sys , time
from pygame.locals import *
# Screen dimensions
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
# Global constants
WHITE = (255, 255, 255)
BLACK = ( 0, 0, 0)
LIGHTBLUE = ( 0, 0, 155)
FPS = 60
class Player(pygame.sprite.Sprite):
# set speed vector of the player
change_x = 0
change_y = 0
moverate = 5
# Constructor. Pass in x and y position
def __init__(self, x, y):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
# Create player image
self.image = pygame.image.load('player.png')
self.image.set_colorkey(WHITE)
# Set a referance to the image rect.
self.rect = self.image.get_rect()
self.rect.centerx = x
self.rect.y = y
def changespeed(self, x, y):
""" Change the speed of the player"""
self.change_x += x
self.change_y += y
def update(self):
""" Move the player. """
# Move left/right
self.rect.x += self.change_x
# Move up/down
self.rect.y += self.change_y
def stop(self):
""" Called when the user lets off the keyboard."""
self.change_x = 0
self.change_y = 0
self.image = pygame.image.load('player.png')
self.image.set_colorkey(WHITE)
class Enemy(pygame.sprite.Sprite):
""" This class represents the enemy sprites."""
minmoverate = 1
maxmoverate = 8
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('enemyShip.png')
self.image = pygame.transform.scale(self.image, (50, 50))
self.image.set_colorkey(WHITE)
self.rect = self.image.get_rect()
def reset_pos(self):
""" Reset position to the top of the screen, at a random x location.
Called by update() or the main program loop if there is a collision."""
self.rect.y = - ( SCREEN_HEIGHT / 4)
self.rect.x = random.randrange(SCREEN_WIDTH)
def update(self):
""" Move the enemies. """
# Move down, at some speed
self.rect.y += 2
# Move left and right, at some speed
self.rect.x += 0
# If enemy is too far down, reset to top of screen
if self.rect.y > SCREEN_HEIGHT:
self.reset_pos()
class Bullet(pygame.sprite.Sprite):
""" This class represents the bullet. """
def __init__(self):
# Call the parent class (Sprite) constructor
pygame.sprite.Sprite.__init__(self)
self.image = pygame.Surface([8, 20])
self.image.fill(LIGHTBLUE)
self.rect = self.image.get_rect()
def update(self):
""" Move the bullet. """
self.rect.y -= 10
class Game(object):
""" This class represents an instance of the game. If we need to
rest the game we'd just need to create a new instance of this class."""
# --- Class attributes.
# Sprite lists
enemy_list = None
bullet_list = None
all_sprites_list = None
# --- Class methods
# Set up the game
def __init__(self):
self.score = 0
self.game_over = False
# Create sprite lists
self.enemy_list = pygame.sprite.Group()
self.bullet_list = pygame.sprite.Group()
self.all_sprites_list = pygame.sprite.Group()
# Create the starting enemy ships
for i in range(15):
enemy = Enemy()
enemy.rect.x = random.randrange(SCREEN_WIDTH)
enemy.rect.y = random.randrange(-300, 20)
self.enemy_list.add(enemy)
self.all_sprites_list.add(enemy)
# Create the player
self.player = Player(SCREEN_WIDTH / 2, SCREEN_HEIGHT - (SCREEN_HEIGHT / 6))
self.all_sprites_list.add(self.player)
def process_events(self):
""" Process all of the events. Return "True" if we need to close the window."""
for event in pygame.event.get():
if event.type == pygame.QUIT:
return True
elif event.type == KEYDOWN:
if event.key == K_ESCAPE:
return True
elif event.key == K_RETURN:
if self.game_over:
self.__init__()
elif event.key in (K_RIGHT ,K_d):
self.player.changespeed( self.moverate ,0)
elif event.key in (K_LEFT ,K_a):
self.player.changespeed( -self.moverate ,0)
elif event.key in (K_UP , K_w):
self.player.changespeed(0, -self.moverate)
elif event.key in (K_DOWN , K_s):
self.player.changespeed(0, self.moverate)
elif event.key == K_SPACE: # Fire bullet
bullet = Bullet(0
# Set bullet so it is where the player is
bullet.rect.centerx = self.player.rect.centerx
bullet.rect.y = self.player.rect.y
# Add bullet to lists
self.all_sprites_list.add(bullet)
self.bullet_list.add(bullet)
elif event.type == KEYUP:
if event.key in (K_RIGHT ,K_d):
self.player.changespeed( -self.moverate ,0)
elif event.key in (K_LEFT ,K_a):
self.player.changespeed( self.moverate ,0)
elif event.key in (K_UP , K_w):
self.player.changespeed(0, self.moverate)
elif event.key in (K_DOWN , K_s):
self.player.changespeed(0, -self.moverate)
def run_logic(self):
""" This method is run each time through the frame.
It updates positions and checks for collisions."""
enemy = Enemy()
if not self.game_over:
# Move all the sprites
self.all_sprites_list.update()
if len(self.all_sprites_list) < 17:
self.enemy_list.add(enemy)
self.all_sprites_list.add(enemy)
enemy.rect.x = random.randrange(SCREEN_WIDTH)
enemy.rect.y = random.randrange(-100, -50)
# Bullet Mechanics
for bullet in self.bullet_list:
# See if the bullets has collided with anything.
self.enemy_hit_list = pygame.sprite.spritecollide(bullet, self.enemy_list, True)
# For each enemy hit, remove bullet and enemy and add to score
for enemy in self.enemy_hit_list:
self.bullet_list.remove(bullet)
self.all_sprites_list.remove(bullet)
self.score += 1
# Remove the bullet if it flies up off the screen
if bullet.rect.y < -10:
self.bullet_list.remove(bullet)
self.all_sprites_list.remove(bullet)
# Player Mechanics
for enemy in self.enemy_list:
# See if player has collided with anything.
self.player_hit_list = pygame.sprite.spritecollide(self.player, self.enemy_list, True)
if len(self.player_hit_list) == 1:
# If player is hit, show game over.
self.game_over = True
def display_frame(self, screen):
""" Display everything to the screen for the game. """
screen.fill(BLACK)
if self.game_over:
# font = pygame.font.Font("Serif:, 25)
font = pygame.font.SysFont("serif", 25)
text = font.render("Game Over! You scored " + str(self.score) +" points, press Enter to restart", True, WHITE)
center_x = (SCREEN_WIDTH // 2) - (text.get_width() // 2)
center_y = (SCREEN_HEIGHT // 2) - (text.get_height() // 2)
screen.blit(text, [center_x, center_y])
if not self.game_over:
self.all_sprites_list.draw(screen)
pygame.display.flip()
def main():
""" Main program function. """
# Initialize Pygame and set up the window
pygame.init()
size = [SCREEN_WIDTH, SCREEN_HEIGHT]
screen = pygame.display.set_mode(size)
screen_rect = screen.get_rect()
pygame.display.set_caption("My Game")
pygame.mouse.set_visible(False)
# Create our objects and set the data
done = False
clock = pygame.time.Clock()
# Create an instance of the Game class
game = Game()
# Main game loop
while not done:
# Process events (keystrokes, mouse clicks, etc)
done = game.process_events()
# Update object positions, check for collisions
game.run_logic()
# Draw the current frame
game.display_frame(screen)
# Pause for the next frame
clock.tick(FPS)
# Close window and exit
pygame.quit()
# Call the main function, start up the game
if __name__ == "__main__":
main()
The problem is that you're only ever creating a single Bullet instance, which you're storing in the Game.bullet class variable. Whenever you shoot, the code moves that single bullet to the player's position, and it updates from there.
You probably want to create a new bullet for every shot. Use something like this in process_events:
elif event.key == K_SPACE: # Fire bullet
bullet = Bullet()
bullet.rect.centerx = self.player.rect.centerx
bullet.rect.y = self.player.rect.y
# Add bullet to lists
all_sprites_list.add(self.bullet)
bullet_list.add(self.bullet)
That creates a new bullet every time the space bar is pressed. If your game design specifies a limit to the number of bullets that can be "active" at once, you may need some more complex logic (or if the performance cost of creating and destroying lots of instances is too much you could write some object caching code), but this should get you on the right track.
Now, the code above doesn't do anything to remove the bullets later, so you'll probably need to handle that too or your game will bog down from the calculations involving off-screen bullets. I'd suggest putting some logic in Bullet.update() that calls self.kill() if the bullet is off the screen (or whenever makes sense for your game). The instance will be garbage collected automatically when there are no more references to it.
I'd also suggest getting rid of all of the class variables of you Game class, which are either unnecessary (they get shadowed by instance variables that are created in __init__), or broken (like bullet).