I'm writing a pygame shooting game where you have to use space station to shoot enemies flying from the top of the screen to the bottom. However, I noticed that the Y positions of enemy(enemy.pos_y) and bullet(bullet.pos_y) is just the position where they spawn, but this position doesn't change even though the enemy moves in the window of the game (because of the move() function in class Enemy) and also bullet moves (also because of the move() function but in Bullet class) and that's obvious because the Y values are constant (enemy.pos_y=-30 and bullet.pos_y=STATION_HEIGHT - 5. So is there any solution to make those positions' values change (I'm saying value because on screen enemies' and bullets' positions change) and still spawn them in those given positions? This issue stops my further steps, because for example making collision between enemies and bullets is impossible, because it's like they never move, and so they never meet. So how to make detect those objects' position's value's change?
In this picture you can see that enemies' positions' value remains the same: enter image description here
Enemies' class:
import pygame
import math
class Enemy:
def __init__(
self, pos_x: float, pos_y: float, texture: pygame.Surface, speed: float
):
self.pos_x = pos_x
self.pos_y = pos_y
self.texture = texture
self.speed = speed
def move(self):
self.pos_y += self.speed
Bullets' class:
import pygame
class Bullet:
def __init__(
self, pos_x: float, pos_y: float, texture: pygame.Surface, speed: float
):
self.pos_x = pos_x
self.pos_y = pos_y
self.texture = texture
self.speed = speed
def move(self):
self.pos_y -= self.speed
Here I display enemies and bullets (in main loop):
running = True
while running:
all_event = pygame.event.get()
for event in all_event:
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.MOUSEBUTTONDOWN:
shot_sound.play()
# Bullets
bullet = Bullet(
bulletX + station.pos_x,
STATION_HEIGHT + 10,
bullet_texture,
BULLET_SPEED,
)
bullets.append(bullet)
print(f'position X={bullet.pos_x}')
# Enemies move
for enemy in enemies:
for i in range(3):
enemy.move()
for i in range(3):
enemy = Enemy(random.randrange(67, 520), -30, enemy_texture, ENEMY_SPEED)
start_time+=1
if start_time > 200:
enemies.append(enemy)
start_time = 0
The whole code you can find on my Github: enter link description here
ps. I'm sorry if this question and problem's explanation isn't well-written, but this is my first question here on stackoverflow so I'm not really experienced, I'm waiting for questions if something's not understandable.
You actually just print the position of a newly created bullet. If you want to see the position of a moving bullet, you need to move the print statement in the loop that moves the bullets:
while running:
# [...]
for enemy in enemies:
enemy.move()
print(f'enemy position Y={enemy.pos_y}')
# [...]
for bullet in bullets:
bullet.move()
print(f'bullet position Y={bullet.pos_y}')
# [...]
or
while running:
# [...]
for i, enemy in enumerate(enemies):
enemy.move()
print(f'position of enemy {i} Y={enemy.pos_y}')
# [...]
for i, bullet in enumerate(bullets):
bullet.move()
print(f'position of bullet {i} Y={bullet.pos_y}')
# [...]
Related
This question already has answers here:
How to move a sprite according to an angle in Pygame
(3 answers)
calculating direction of the player to shoot pygame
(1 answer)
Moving forward after angle change. Pygame
(1 answer)
Closed 1 year ago.
I wanna know how to shoot a projectile towards the direction the player is looking. I would also like to know how to shoot that projectile from the middle of the player. I have got my bullet class sort of ready but don't know my next step.
here is my code and replit...
https://replit.com/#TahaSSS/game-1#main.py
import pygame
from sys import exit
from random import randint
import math
from pygame.constants import K_LSHIFT, K_SPACE, MOUSEBUTTONDOWN
pygame.init()
pygame.mouse.set_visible(True)
class Player(pygame.sprite.Sprite):
def __init__(self, x , y):
super().__init__()
self.x = x
self.y = y
self.image = pygame.image.load('graphics/Robot 1/robot1_gun.png').convert_alpha()
self.rect = self.image.get_rect()
self.orig_image = pygame.image.load('graphics/Robot 1/robot1_gun.png').convert_alpha()
self.rotate_vel = 1
self.cross_image = pygame.image.load('graphics/crosshair049.png')
def draw(self, surface):
""" Draw on surface """
# blit yourself at your current position
surface.blit(self.image, self.rect)
dir_vec = pygame.math.Vector2()
dir_vec.from_polar((180, -self.rotate_vel))
cross_pos = dir_vec + self.rect.center
cross_x, cross_y = round(cross_pos.x), round(cross_pos.y)
surface.blit(self.cross_image, self.cross_image.get_rect(center = (cross_x, cross_y)))
def movement(self):
key = pygame.key.get_pressed()
self.rect = self.image.get_rect(center = (self.x, self.y))
dist = 3 # distance moved in 1 frame, try changing it to 5
if key[pygame.K_DOWN] or key[pygame.K_s]: # down key
self.y += dist # move down
elif key[pygame.K_UP] or key[pygame.K_w]: # up key
self.y -= dist # move up
if key[pygame.K_RIGHT] or key[pygame.K_d]: # right key
self.x += dist # move right
elif key[pygame.K_LEFT] or key[pygame.K_a]: # left key
self.x -= dist # move left
def rotate(self, surface):
keys = pygame.key.get_pressed()
if keys[K_LSHIFT]:
self.rotate_vel += 5
self.image = pygame.transform.rotate(self.orig_image, self.rotate_vel)
self.rect = self.image.get_rect(center=self.rect.center)
surface.blit(self.image, self.rect)
if keys[K_SPACE]:
self.rotate_vel += -5
self.image = pygame.transform.rotate(self.orig_image, self.rotate_vel)
self.rect = self.image.get_rect(center=self.rect.center)
surface.blit(self.image, self.rect)
def update(self):
self.movement()
self.draw(screen)
self.rotate(screen)
class Bullet(pygame.sprite.Sprite):
def __init__(self, x , y):
super().__init__()
self.bullet_image = pygame.image.load('graphics/weapons/bullets/default_bullet.png')
self.bullet_rect = self.bullet_image.get_rect(center = (x,y))
self.bullet_speed = 15
#screen
clock = pygame.time.Clock()
FPS = 60
screen = pygame.display.set_mode((800, 400))
#player
player_sprite = Player(600, 300)
player = pygame.sprite.GroupSingle()
player.add(player_sprite)
#bullet
bullet_group = pygame.sprite.Group()
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
#screen
screen.fill('grey')
#player sprite funtions
player.update()
clock.tick(FPS)
pygame.display.update()
pygame.sprite.Group.draw() and pygame.sprite.Group.update() are methods which are provided by pygame.sprite.Group.
The former delegates the to the update method of the contained pygame.sprite.Sprites - you have to implement the method. See pygame.sprite.Group.update():
Calls the update() method on all Sprites in the Group [...]
The later uses the image and rect attributes of the contained pygame.sprite.Sprites to draw the objects - you have to ensure that the pygame.sprite.Sprites have the required attributes. See pygame.sprite.Group.draw():
Draws the contained Sprites to the Surface argument. This uses the Sprite.image attribute for the source surface, and Sprite.rect. [...]
A Sprite kan be removed from all Groups and thus delted by calling kill.
Since pygame.Rect is supposed to represent an area on the screen, a pygame.Rect object can only store integral data.
The coordinates for Rect objects are all integers. [...]
The fraction part of the coordinates gets lost when the new position of the object is assigned to the Rect object. If this is done every frame, the position error will accumulate over time.
If you want to store object positions with floating point accuracy, you have to store the location of the object in separate variables respectively attributes and to synchronize the pygame.Rect object. round the coordinates and assign it to the location (e.g. .center) of the rectangle.
Write a Sprite class with an update method that moves the bullet in a certain direction. Destroy the bullet when it goes out of the display:
class Bullet(pygame.sprite.Sprite):
def __init__(self, pos, angle):
super().__init__()
self.image = pygame.image.load('graphics/weapons/bullets/default_bullet.png')
self.image = pygame.transform.rotate(self.image, angle)
self.rect = self.image.get_rect(center = pos)
self.speed = 15
self.pos = pos
self.dir_vec = pygame.math.Vector2()
self.dir_vec.from_polar((self.speed, -angle))
def update(self, screen):
self.pos += self.dir_vec
self.rect.center = round(self.pos.x), round(self.pos.y)
if not screen.get_rect().colliderect(self.rect):
self.kill()
pygame.key.get_pressed() returns a list with the state of each key. If a key is held down, the state for the key is True, otherwise False. Use pygame.key.get_pressed() to evaluate the current state of a button and get continuous movement.
The keyboard events (see pygame.event module) occur only once when the state of a key changes. The KEYDOWN event occurs once every time a key is pressed. KEYUP occurs once every time a key is released. Use the keyboard events for a single action.
Use an keyboard event to generate a new Bullet object (e.g. TAB). Call bullet_group.update(screen) and bullet_group.draw(screen) in the application loop:
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
exit()
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_TAB:
pos = player_sprite.rect.center
dir_vec = pygame.math.Vector2()
new_bullet = Bullet(pos, player_sprite.rotate_vel)
bullet_group.add(new_bullet)
bullet_group.update(screen)
screen.fill('grey')
player.update()
bullet_group.draw(screen)
pygame.display.update()
clock.tick(FPS)
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 have been trying to modify the code from this Tutorial so that after a bullet strikes an enemy the player.png image is shown at position x = 60 and y = 48. But the image does not remain fixed, it just appears and disappears. I don't know exactly where the wrong or missing element is in the code but I suspect that one of the causes is some misuse of my part of the for loop within the draw_reaction function.
My player.png image
My MWE code:
import pygame
import random
# Define some colors
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
# --- Classes
class Block(pygame.sprite.Sprite):
""" This class represents the block. """
def __init__(self, color):
# Call the parent class (Sprite) constructor
super().__init__()
self.image = pygame.Surface([20, 15])
self.image.fill(color)
self.rect = self.image.get_rect()
class Player(pygame.sprite.Sprite):
""" This class represents the Player. """
def __init__(self):
""" Set up the player on creation. """
# Call the parent class (Sprite) constructor
super().__init__()
self.image = pygame.Surface([20, 20])
self.image.fill(RED)
self.rect = self.image.get_rect()
def update(self):
""" Update the player's position. """
# 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
self.rect.x = pos[0]
class Bullet(pygame.sprite.Sprite):
""" This class represents the bullet . """
def __init__(self):
# Call the parent class (Sprite) constructor
super().__init__()
self.image = pygame.Surface([4, 10])
self.image.fill(BLACK)
self.rect = self.image.get_rect()
def update(self):
""" Move the bullet. """
self.rect.y -= 3
# --- Create the window
# 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])
# --- Sprite lists
# This is a list of every sprite. All blocks and the player block as well.
all_sprites_list = pygame.sprite.Group()
# List of each block in the game
block_list = pygame.sprite.Group()
# List of each bullet
bullet_list = pygame.sprite.Group()
# --- Create the sprites
for i in range(0,1,2):
# This represents a block
block = Block(BLUE)
# Set a random location for the block
block.rect.x = random.randrange(screen_width)
block.rect.y = random.randrange(350)
# Add the block to the list of objects
block_list.add(block)
all_sprites_list.add(block)
# Create a red player block
player = Player()
all_sprites_list.add(player)
player_image = pygame.image.load("player.png").convert()
# 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
# -------- Main Program Loop -----------
while not done:
# --- Event Processing
for event in pygame.event.get():
if event.type == pygame.QUIT:
done = True
elif event.type == pygame.MOUSEBUTTONDOWN:
# Fire a bullet if the user clicks the mouse button
bullet = Bullet()
# Set the bullet so it is where the player is
bullet.rect.x = player.rect.x
bullet.rect.y = player.rect.y
# Add the bullet to the lists
all_sprites_list.add(bullet)
bullet_list.add(bullet)
# --- Game logic
# Call the update() method on all the sprites
# Calculate mechanics for each bullet
for bullet in bullet_list:
# See if it hit a block
block_hit_list = pygame.sprite.spritecollide(bullet, block_list, True)
# For each block hit, remove the bullet and add to the score
for block in block_hit_list:
bullet_list.remove(bullet)
all_sprites_list.remove(bullet)
score += 1
print(score)
# Remove the bullet if it flies up off the screen
if bullet.rect.y < -10:
bullet_list.remove(bullet)
all_sprites_list.remove(bullet)
def draw_reaction():
for bullet in bullet_list:
block_hit_list = pygame.sprite.spritecollide(bullet, block_list, True)
for block in block_hit_list:
screen.blit(player_image, [60, 48])
all_sprites_list.update()
# --- Draw a frame
# Clear the screen
screen.fill(WHITE)
draw_reaction()
# Draw all the spites
all_sprites_list.draw(screen)
# Go ahead and update the screen with what we've drawn.
pygame.display.flip()
# --- Limit to 20 frames per second
clock.tick(60)
pygame.quit()
EDIT UPDATE: The game has only one enemy that appears in a random place each time the game starts again.
EDIT UPDATE 2: I know that in most games after a collision the enemy is removed from the screen. This also happens in my code. But for a particular need of mine I need that, as an consequence, an image (player.png) remains fixed constantly after this bullet collision with the enemy.
The problem is caused by the draw_reaction() function only drawing the player_image during the absolute-time the bullet is colliding with a block. So it shows the image for a single frame (One 60th of a second), but then on the next loop the collision is no longer occurring (the bullet is removed), so it is never drawn.
There are a number of ways around this, but I'm not sure of the purpose of showing this bitmap, so maybe they're not as helpful as they should be.
The easiest fix is to probably turn the player image into a sprite, and simply add it to the existing all_sprites_list when the bullet-hit triggers, and then take it away some time later (or how ever it should work).
class PlayerShip( pygame.sprite.Sprite ):
def __init__(self):
super().__init__()
self.image = pygame.image.load( "player.png" ) #.convert()
self.rect = self.image.get_rect()
self.rect.topleft = ( 60, 48 )
Then later on:
player_sprite = PlayerShip()
and
def draw_reaction():
for bullet in bullet_list:
block_hit_list = pygame.sprite.spritecollide(bullet, block_list, True)
for block in block_hit_list:
# TODO: ensure it's not showing already
all_sprites_list.add( player_sprite ) # add the player sprite
break # only add once
EDIT: Code snippets updated based on comment
I'm trying to create a top down shooter, but my enemies won't spawn in. I'm trying to have the enemy move in a diagonal pattern but also be able to bounce off of the wall kind of like a top down bouncing ball. I'm also trying to have a new enemy spawn every 10 seconds, but when I run the program nothing shows up, there is no error either. Can someone please help.
import pygame
pygame.init()
import random
import time
inPlay = True
width=900
height=700
screen = pygame.display.set_mode((width, height))
#enemy
enemyFrequency=10
enemyPause=enemyFrequency
killEnemy=True
#------------------------------#
# classes #
#------------------------------#
######################################################
class Enemy(pygame.sprite.Group):
def __init__(self,ballX,ballY,ballSpeed,picture2=None):
pygame.sprite.Group.__init__(self)
self.ballX=ballX
self.ballY=ballY
self.ballSpeed=ballSpeed
self.image2=pygame.image.load(picture2)
def move(self):
for enemys in self:
self.ballX+=self.ballSpeed
self.ballY+=self.ballSpeed
def decay(self):
for enemys in self:
if enemys.y > height:
self.remove(enemys)
#------------------------------#
# functions #
#------------------------------#
def redraw_game_window():
screen.fill((30, 30, 30))
enemys.draw(screen)
pygame.display.update()
#------------------------------#
# main program #
#------------------------------#
RB=width-player.rect.width
CEILING = 3*player.rect.height # for ship movement
FLOOR = height - player.rect.height #
#enemy
enemies=pygame.sprite.Group()
enemySpeed=3
eX=random.randint(0,RB)
eY=random.randint(-height,0)
enemys=Enemy (eX,eY,enemySpeed,'asteroid.png')
t0 = pygame.time.clock()
dt = 0
enemyCount = 0
clock = pygame.time.Clock()
while inPlay:
redraw_game_window()
for event in pygame.event.get():
if event.type == pygame.QUIT:
inPlay=False
#enemies spawning
if dt < nSeconds:
t1 = time.process_time()
dt = t1 - t0
enemies.add(enemys)
else:
enemyInstance = Enemy()
enemyCount += 1
t0 = time.clock()
dt = 0
enemys.move()
enemys.decay()
clock.tick(30)
pygame.quit()
I hacked your code to the point where it seemed to be doing what the code describes, and what matches your question. There were a lot of things missing in this code, maybe you cut them out before posting to SO to make the code shorter.
The crux of the problem is the handling of the enemy sprites. I don't know if it was by design, but the original code was defining what seemed like a sprite, but based on a sprite group.
So I modified the code based on the idea that enemies should be a global sprite group, and went from there "fixing" things. There was no Player class defined, I added one. I could not follow the timing code, it was using pygame.time.clock() (which is an object) as a time value. It seemed like this code was keeping a delta, and counting time to respawn a new enemy, I had to re-write this bit to get it to work - my apologies.
The other comment I want to make - sprites can define an update() function. This function should handle the position changes and appearance of the sprite. PyGame sprite groups will handle the calling of this function automatically (if it's defined) in the sprite group update() meta-call. This gives the program a clean and simple way to handle sprite animation.
import pygame
pygame.init()
import random
import time
inPlay = True
width=900
height=700
screen = pygame.display.set_mode((width, height))
#enemy
enemies = pygame.sprite.Group() # Holds all enemy sprites
enemyFrequency = 1000 # milliseconds between enemy spawn
killEnemy = True
enemyCount = 0
#------------------------------#
# classes #
#------------------------------#
######################################################
class Player(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load('player.png').convert_alpha()
self.rect = self.image.get_rect()
self.rect.center = (100,100) # TODO - position properly
def update(self):
# TODO
pass
class Enemy(pygame.sprite.Sprite):
def __init__(self, ballX, ballY, ballSpeed, picture2=None):
pygame.sprite.Sprite.__init__(self)
self.ballSpeed = ballSpeed
self.image = pygame.image.load(picture2).convert_alpha()
self.rect = self.image.get_rect()
self.rect.center = ( ballX, ballY )
def update(self):
global enemies # group of all enemy sprites (to which this sprite belongs)
global height # window height
self.rect.x += self.ballSpeed
self.rect.y += self.ballSpeed
# decay the enemy
if ( self.rect.y > height ):
enemies.remove(self) # went off screen, delete it
#------------------------------#
# functions #
#------------------------------#
def redraw_game_window():
screen.fill((30, 30, 30))
enemies.draw(screen)
pygame.display.update()
#------------------------------#
# main program #
#------------------------------#
player = Player()
RB=width-player.rect.width
CEILING = 3*player.rect.height # for ship movement
FLOOR = height - player.rect.height #
# start with 3 enemies
for i in range( 3 ):
enemySpeed = 3
eX=random.randint(0,RB)
eY=random.randint(-height,0)
enemies.add( Enemy(eX,eY,enemySpeed,'asteroid.png') )
clock = pygame.time.Clock()
last_enemy_spawn_time = 0
while inPlay:
time_now = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == pygame.QUIT:
inPlay=False
# is it time to spawn a new enemy?
if ( time_now - last_enemy_spawn_time > enemyFrequency ):
last_enemy_spawn_time = time_now # reset timer
#enemies spawning
eX=random.randint(0,RB)
eY=random.randint(-height,0)
enemies.add( Enemy(eX,eY,enemySpeed,'asteroid.png') )
enemyCount += 1
enemies.update() # call the update() of every sprite
redraw_game_window()
#enemies.decay() -- MOVED INTO Enemy.update()
clock.tick(30)
pygame.quit()
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).