В моей игре возникает странная проблема: при запуске игры пуля выпущена без причины, а отображаемые боеприпасы переходят от 0/20, что уже неправильно, и переходят к 0/0 без какого-либо вмешательства пользователя???
Я включил минимальную повторную реализацию исходного кода, содержащую только проблемные области.
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()
Спасибо
На самом деле в результате сначала ничего не вышло, а потом вышло еще хуже.
используйте event.type == pygame.KEYDOWN вместо keys[pg.K_SPACE]:, чтобы проверить однократное нажатие. ИЛИ используйте pygame.time, чтобы отсчитать время с момента последнего выстрела и контролировать скорость съемки. Вы также можете учитывать только активные пули для выстрела, например. 6 пуль за раз






В этом нет ничего странного. Программа работает очень быстро, и когда вы удерживаете нажатой клавишу, она может запускаться Player.update() много раз и стрелять множеством пуль.
Если вам нужен один выстрел при одном нажатии клавиши, вам следует использовать evenKEYDOWN (или KEYUP), потому что он генерирует только одно событие - и не имеет значения, как долго вы держите кнопку нажатой.
(Это также может быть полезно, если вы хотите использовать мышь, чтобы щелкнуть какой-либо элемент только один раз — например, нажать кнопку на экране — для этого может потребоваться eventMOUSEBUTTONDOWNMOUSEBUTTONUP)
Здесь я добавляю функцию handle_event(event) в Player и выполняю ее в цикле for event.
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
Полный рабочий код:
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()
Но некоторые игры позволяют выпускать много пуль, пока вы удерживаете нажатой клавишу, и для этого может потребоваться отсчитывать время между выстрелами, чтобы выстрелить меньше пуль за короткое время.
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()
Возможно, потребуется добавить его к fire_bullet() и вычислить self.next_bullet, только если в магазине была пуля.
Этот метод позволяет изменить self.bullet_delay на более быстрое оружие. "Бонусы".
Полный рабочий код:
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()
Спасибо, я просто не знал, что в этом проблема
Ваш
Player.updateвызывается 60 раз в секунду. КАЖДЫЙ раз, когда он вызывается, если пробел нажат, вы стреляете. Быстрое нажатие клавиши пробела занимает 50 или 100 мс. За 100 мс вы обновитесь 6 раз и выпустите 6 пуль. Вам нужно «обнаружение края», чтобы решить, когда пробел перемещается сверху вниз и обратно.