My game is experiencing a weird issue when the game starts a bullet is fired for no reason and the ammo that is displayed goes from 0/20 which is already wrong and goes to 0/0 without any user input???
I have included a minimal reimplementation of the original code only containing the problematic areas
import pygame as pg
import math
# Constants
WIN_LEN = 800
WIN_HEIGHT = 600
PLAYER_SIZE = 50
PLAYER_SPEED = 300
BULLET_SPEED = 700
BULLET_RADIUS = 5
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.rect = pg.Rect(pos[0] - PLAYER_SIZE // 2, pos[1] - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE)
self.position = pg.Vector2(pos)
self.angle = 0
self.ammo = 20
self.magazine = 10
self.bullets = []
def update(self, keys, dt):
if keys[pg.K_SPACE]:
self.fire_bullet()
cursor_pos = pg.mouse.get_pos()
dx, dy = cursor_pos[0] - self.position.x, cursor_pos[1] - self.position.y
self.angle = math.degrees(math.atan2(dy, dx))
self.rect.center = self.position
def fire_bullet(self):
if self.ammo > 0 and len(self.bullets) < self.magazine:
self.ammo -= 1
self.bullets.append(Bullet(self.position, self.angle))
else:
print("No ammo to fire.")
def draw_ui(self, screen):
font = pg.font.Font(None, 36)
text = font.render(f"Ammo: {self.ammo}/{len(self.bullets)}", True, WHITE)
screen.blit(text, (10, 10))
class Bullet(pg.sprite.Sprite):
def __init__(self, position, angle):
super().__init__()
self.rect = pg.Rect(position[0], position[1], BULLET_RADIUS * 2, BULLET_RADIUS * 2)
self.position = pg.Vector2(position)
self.velocity = pg.Vector2(math.cos(math.radians(angle)), math.sin(math.radians(angle))) * BULLET_SPEED
def update(self, dt):
self.position += self.velocity * dt
self.rect.center = self.position
def draw(self, screen):
pg.draw.circle(screen, WHITE, (int(self.position.x), int(self.position.y)), BULLET_RADIUS)
def main():
pg.init()
screen = pg.display.set_mode((WIN_LEN, WIN_HEIGHT))
clock = pg.time.Clock()
running = True
player = Player((WIN_LEN // 2, WIN_HEIGHT // 2))
all_sprites = pg.sprite.Group(player)
while running:
dt = clock.tick(60) / 1000
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
keys = pg.key.get_pressed()
all_sprites.update(keys, dt)
screen.fill((0, 0, 0)) # Clear screen
for bullet in player.bullets:
bullet.update(dt)
bullet.draw(screen)
pg.draw.rect(screen, BLUE, player.rect) # Draw player
player.draw_ui(screen)
pg.display.flip()
pg.quit()
if __name__ == "__main__":
main()
Thanks
What actually resulted at first was nothing and then came to something even worse
It is nothing weird. Program runs very fast and when you keep pressed key then it may run Player.update()
many times and it may shoot many bullets.
If you want one shot on one keypress then you should use even
KEYDOWN
(or KEYUP
) because it generates only one event - and it doesn't matter how long you keep button pressed.
(It can be also useful when you want to use mouse to click some element only once - e.g. press button on screen - and this may need event
MOUSEBUTTONDOWN
MOUSEBUTTONUP
)
Here I add function handle_event(event)
in Player
and I execute it in for event
loop.
class Player(pg.sprite.Sprite):
# ... code ...
def update(self, keys, dt):
#if keys[pg.K_SPACE]:
# self.fire_bullet()
cursor_pos = pg.mouse.get_pos()
dx, dy = cursor_pos[0] - self.position.x, cursor_pos[1] - self.position.y
self.angle = math.degrees(math.atan2(dy, dx))
self.rect.center = self.position
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.fire_bullet()
# ... code ...
while running:
dt = clock.tick(60) / 1000
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
player.handle_event(event) # <-- execute in event loop
Full working code:
import pygame as pg
import math
# Constants
WIN_LEN = 800
WIN_HEIGHT = 600
PLAYER_SIZE = 50
PLAYER_SPEED = 300
BULLET_SPEED = 700
BULLET_RADIUS = 5
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.rect = pg.Rect(pos[0] - PLAYER_SIZE // 2, pos[1] - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE)
self.position = pg.Vector2(pos)
self.angle = 0
self.ammo = 20
self.magazine = 10
self.bullets = []
def update(self, keys, dt):
#if keys[pg.K_SPACE]:
# self.fire_bullet()
cursor_pos = pg.mouse.get_pos()
dx, dy = cursor_pos[0] - self.position.x, cursor_pos[1] - self.position.y
self.angle = math.degrees(math.atan2(dy, dx))
self.rect.center = self.position
def handle_event(self, event):
if event.type == pg.KEYDOWN:
if event.key == pg.K_SPACE:
self.fire_bullet()
def fire_bullet(self):
if self.ammo > 0 and len(self.bullets) < self.magazine:
self.ammo -= 1
self.bullets.append(Bullet(self.position, self.angle))
else:
print("No ammo to fire.")
def draw_ui(self, screen):
font = pg.font.Font(None, 36)
text = font.render(f"Ammo: {self.ammo}/{len(self.bullets)}", True, WHITE)
screen.blit(text, (10, 10))
class Bullet(pg.sprite.Sprite):
def __init__(self, position, angle):
super().__init__()
self.rect = pg.Rect(position[0], position[1], BULLET_RADIUS * 2, BULLET_RADIUS * 2)
self.position = pg.Vector2(position)
self.velocity = pg.Vector2(math.cos(math.radians(angle)), math.sin(math.radians(angle))) * BULLET_SPEED
def update(self, dt):
self.position += self.velocity * dt
self.rect.center = self.position
def draw(self, screen):
pg.draw.circle(screen, WHITE, (int(self.position.x), int(self.position.y)), BULLET_RADIUS)
def main():
pg.init()
screen = pg.display.set_mode((WIN_LEN, WIN_HEIGHT))
clock = pg.time.Clock()
running = True
player = Player((WIN_LEN // 2, WIN_HEIGHT // 2))
all_sprites = pg.sprite.Group(player)
while running:
dt = clock.tick(60) / 1000
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
player.handle_event(event)
keys = pg.key.get_pressed()
all_sprites.update(keys, dt)
screen.fill((0, 0, 0)) # Clear screen
for bullet in player.bullets:
bullet.update(dt)
bullet.draw(screen)
pg.draw.rect(screen, BLUE, player.rect) # Draw player
player.draw_ui(screen)
pg.display.flip()
pg.quit()
if __name__ == "__main__":
main()
But some games allow to fire many bullets when you keep pressed key - and this may need to count time between shots to shoot less bullets in short time.
class Player(pg.sprite.Sprite):
def __init__(self, pos):
# ... code ...
self.bullet_delay = 500
self.next_bullet = pg.time.get_ticks()
def update(self, keys, dt):
if keys[pg.K_SPACE]:
# check if I can fire next bullet
if self.next_bullet <= pg.time.get_ticks():
# calculate time for next bullet
self.next_bullet = pg.time.get_ticks() + self.bullet_delay
self.fire_bullet()
It may need to add it to fire_bullet()
and calculate self.next_bullet
only if there was bullet in magazine.
This method allows to change self.bullet_delay
to have faster gun. "PowerUps".
Full working code:
import pygame as pg
import math
# Constants
WIN_LEN = 800
WIN_HEIGHT = 600
PLAYER_SIZE = 50
PLAYER_SPEED = 300
BULLET_SPEED = 700
BULLET_RADIUS = 5
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
class Player(pg.sprite.Sprite):
def __init__(self, pos):
super().__init__()
self.rect = pg.Rect(pos[0] - PLAYER_SIZE // 2, pos[1] - PLAYER_SIZE // 2, PLAYER_SIZE, PLAYER_SIZE)
self.position = pg.Vector2(pos)
self.angle = 0
self.ammo = 20
self.magazine = 10
self.bullets = []
self.bullet_delay = 500
self.next_bullet = pg.time.get_ticks()
def update(self, keys, dt):
if keys[pg.K_SPACE]:
if self.next_bullet <= pg.time.get_ticks():
self.next_bullet = pg.time.get_ticks() + self.bullet_delay
self.fire_bullet()
cursor_pos = pg.mouse.get_pos()
dx, dy = cursor_pos[0] - self.position.x, cursor_pos[1] - self.position.y
self.angle = math.degrees(math.atan2(dy, dx))
self.rect.center = self.position
def fire_bullet(self):
if self.ammo > 0 and len(self.bullets) < self.magazine:
self.ammo -= 1
self.bullets.append(Bullet(self.position, self.angle))
else:
print("No ammo to fire.")
def draw_ui(self, screen):
font = pg.font.Font(None, 36)
text = font.render(f"Ammo: {self.ammo}/{len(self.bullets)}", True, WHITE)
screen.blit(text, (10, 10))
class Bullet(pg.sprite.Sprite):
def __init__(self, position, angle):
super().__init__()
self.rect = pg.Rect(position[0], position[1], BULLET_RADIUS * 2, BULLET_RADIUS * 2)
self.position = pg.Vector2(position)
self.velocity = pg.Vector2(math.cos(math.radians(angle)), math.sin(math.radians(angle))) * BULLET_SPEED
def update(self, dt):
self.position += self.velocity * dt
self.rect.center = self.position
def draw(self, screen):
pg.draw.circle(screen, WHITE, (int(self.position.x), int(self.position.y)), BULLET_RADIUS)
def main():
pg.init()
screen = pg.display.set_mode((WIN_LEN, WIN_HEIGHT))
clock = pg.time.Clock()
running = True
player = Player((WIN_LEN // 2, WIN_HEIGHT // 2))
all_sprites = pg.sprite.Group(player)
while running:
dt = clock.tick(60) / 1000
for event in pg.event.get():
if event.type == pg.QUIT:
running = False
#player.handle_event(event)
keys = pg.key.get_pressed()
all_sprites.update(keys, dt)
screen.fill((0, 0, 0)) # Clear screen
for bullet in player.bullets:
bullet.update(dt)
bullet.draw(screen)
pg.draw.rect(screen, BLUE, player.rect) # Draw player
player.draw_ui(screen)
pg.display.flip()
pg.quit()
if __name__ == "__main__":
main()