Проблема с шутером Pygame

В моей игре возникает странная проблема: при запуске игры пуля выпущена без причины, а отображаемые боеприпасы переходят от 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()

Спасибо

  • Переписал части кода, которые, по моему мнению, вызывали проблему.
    • Я думал, что смогу уловить проблему
  • Посоветовался с ИИ, но это не помогло.
    • Я думал, что ИИ сможет уловить проблему

На самом деле в результате сначала ничего не вышло, а потом вышло еще хуже.

Ваш Player.update вызывается 60 раз в секунду. КАЖДЫЙ раз, когда он вызывается, если пробел нажат, вы стреляете. Быстрое нажатие клавиши пробела занимает 50 или 100 мс. За 100 мс вы обновитесь 6 раз и выпустите 6 пуль. Вам нужно «обнаружение края», чтобы решить, когда пробел перемещается сверху вниз и обратно.

Tim Roberts 15.06.2024 20:49

используйте event.type == pygame.KEYDOWN вместо keys[pg.K_SPACE]:, чтобы проверить однократное нажатие. ИЛИ используйте pygame.time, чтобы отсчитать время с момента последнего выстрела и контролировать скорость съемки. Вы также можете учитывать только активные пули для выстрела, например. 6 пуль за раз

furas 15.06.2024 21:38
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
2
53
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В этом нет ничего странного. Программа работает очень быстро, и когда вы удерживаете нажатой клавишу, она может запускаться 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()

Спасибо, я просто не знал, что в этом проблема

CoolBeans64 16.06.2024 05:49

Другие вопросы по теме