Смена фаз босса pygame

Может кто знающий подскажет или поможет: Босс имеет 3 фазы, 2 из них реализованы так, как надо. В конце второй фазы босс улетает за границу экрана. Далее реализовать мою задумку не получается уже целый день. Попросту не знаю, как прописывать код дальше. Задумка: после того, как босс улетел за границу экрана (в конце второй фазы), через 7 секунд он должен плавно появиться из-за правой нижней части экрана (меняется только X, а Y строго должен быть равен 45% высоты экрана, ибо хитбокс создаётся через topleft). При этом он должен сменить свою текстуру и текстуру своего проджектайла, а также он будет иметь совершенно другую логику стрельбы в игрока. Проблема в том, что делаю я это на двух классах: Boss и условно Boss2, поэтому Boss2 я сделал дочерним элементом Boss, чтобы принять у него оставшееся и максимальное количество хп. С наследованием, вроде как, получилось. Даже босса вывел (не в цикле, а просто ради теста) – он отображается, хоть и без функционала, но хп с прошлых фаз сохраняет. Но вот вопрос в том, как сделать переход между 2 и 3 фазой, ибо вручную вызывать босса на экран – не есть решение.

def end_phase_2(self):
        self.shooting_enabled = False # Останавливаем стрельбу
        self.rect.y -= 10 # Скорость полета за экран
if self.rect.bottom < 0:
        self.rect.y = -self.rect.height # Босс уходит за верхнюю границу экрана

if isinstance(self, Boss2): # Надобность в этом коде я вообще не понял, нужен он или нет, но его яро советует чатгпт
        self.start_phase_3()

Как я наследую класс Boss и его hp и max_hp:

def __init__(self, x, y, images, projectile_image, hp, max_hp):
        super().__init__(x, y, images, projectile_image)
        enemy_images = load_enemy_images()
        self.image = pygame.transform.scale(enemy_images["boss_img"], (700, 500))
        self.rect = self.image.get_rect(topleft=self.rect.topleft)
        self.hp = hp # Устанавливаем здоровье из предыдущей фазы
        self.max_hp = max_hp # Устанавливаем максимальное здоровье из предыдущей фазы
        self.phase = 3

        projectile_images = load_projectile_images()
        self.projectile_image = projectile_images

        self.appear_delay = 7000 # Задержка в 7 секунд
        self.appear_start_time = None # Время начала появления
        self.appear_x = pygame.display.get_surface().get_width() # Начальная координата X для появления
        self.final_x = x # Конечная координата X
        self.appear_speed = 2 # Скорость перемещения босса по X

Как я пытался вывести босса так, как я хочу:

def start_phase_3(self):
        self.appear_start_time = pygame.time.get_ticks()
        self.appear_x = pygame.display.get_surface().get_width() # Начинаем появляться с правой стороны экрана
        self.rect.topleft = (self.appear_x, pygame.display.get_surface().get_height() * 0.45) # Устанавливаем начальное положение босса
        self.phase = 3
        self.shooting_enabled = True # Включаем стрельбу для новой фазы
# boss = PanzerBoss(screen_width * 0.65, screen_height * 0.45, enemy_images, projectile_images["panzer_projectile"], boss.hp, boss.max_hp) - Как вызывал босса в игровом цикле вручную

Ответы (1 шт):

Автор решения: alex
if isinstance(self, Boss2): # Надобность в этом коде я вообще не понял, нужен он или нет, но его яро советует чатгпт
        self.start_phase_3()

вместо этого вам следует переопределить метод end_phase_2 в дочернем классе. В нем вызвать родительский end_phase_2, а затем свою логику. Сейчас ваша структура кода выглядит так

class Boss:
  hp: float
  stage: str
  max_hp: float
  def fire(...):
    ...
  def end_phase_1(self):
    ...
  def end_phase_2(self):
    if isinstance(self, Boss2):
      ...
    ...


class Boss2(Boss):
  # end_phase_1()
  # end_phase_2()
  def end_phase_3(self):
    ...

позвольте ввести еще один класс, который будет отвечать за наблюдение за состоянием босса и выделить классы отвечающие за механику.

class Stage:
  """Описание интерфейса для всех поведений!"""
  name: str

  def __init__(self, boss):
    self._boss = boss

  def fire(self, ...):
    ...

  def start(...):
    ...

  def end(...):
    ...


class Stage1(Stage):
  """Первая реализация интерфейса поведений босса."""
  
  name = '1'  

  def fire(self, ...):
    ...

  def start(...):
    ...

  def end(...):
    boss = self._boss
    boss.shooting_enabled = False # Останавливаем стрельбу
    boss.rect.y -= 10 # Скорость полета за экран
    if boss.rect.bottom < 0:
      boss.rect.y = -boss.rect.height # Босс уходит за верхнюю границу экрана

class Stage2(Stage1):
  """Вторая реализация интерфейса поведений босса."""
  
  name = '2'  

  def start(...):
    ...

  def end(...):
    self.boss.visible = False

class Stage3(Stage):
  """Третья реализация интерфейса поведений босса."""
  
  name = '3'  

  def fire(self, ...):
    """своя реализация стрельбы"""
    ...

  def start(...):
    from threading import Thread
    Thread(target=self.thread).start()
  
  def thread(...):
    sleep(7)  # так как мы в потоке, то игра продолжить быть интерактивной
    boss = self._boss
    boss.appear_start_time = pygame.time.get_ticks()
    boss.appear_x = pygame.display.get_surface().get_width() # Начинаем появляться с правой стороны экрана
    boss.rect.topleft = (boss.appear_x, pygame.display.get_surface().get_height() * 0.45) # Устанавливаем начальное положение босса
    boss.shooting_enabled = True # Включаем стрельбу для новой фазы
    boss.visible = True # отрисовываем босса

  def end(...):
    ...


class Boss:
  hp: float
  stage: Stage
  _stages: (Stage1, Stage2, Stage3)
  max_hp: float
  
  def __init__(self, ...):
    ...
  
  def begin(...):
    self.stage = next(self._stages)
    self.stage.start()
    return self

  def end(...):
    self.stage.end()
    return self
  
  def __iter__(self):
    self._stages = iter(x(self) for x in self._stages)
    return self.begin()
  
  def __next__(self):
    self.end()
    return self.begin()
  
  def fire(self, ...):
    self.stage.fire(...)

Класс Boss меняет состояния по протоколу итератора.

boss = Boss()
_ = iter(boss)
print(boss.stage.name)
_ = next(boss)
print(boss.stage.name)

у третьей стратегии метод start запускает поток, который засыпает на 7 секунд. дальше можете отрисовывать босса.

Вместо потока, вам также доступен вариант с таймером pygame

...


class Stage3(Stage):
  """Третья реализация интерфейса поведений босса."""
  
  name = '3'
  
  def __init__(self, boss):
    boss.hp = boss.max_hp = boss.max_hp * 2

  def fire(self, ...):
    """своя реализация стрельбы"""
    ...

  def start(...):
    boss = self._boss
    boss.appear_start_time = pygame.time.get_ticks()
    boss.appear_x = pygame.display.get_surface().get_width() # Начинаем появляться с правой стороны экрана
    boss.rect.topleft = (boss.appear_x, pygame.display.get_surface().get_height() * 0.45) # Устанавливаем начальное положение босса
    boss.shooting_enabled = True # Включаем стрельбу для новой фазы
    boss.visible = True # отрисовываем босса

  def end(...):
    ...

...
import pygame

# Инициализация Pygame
pygame.init()

# Настройка экрана и времени
screen = pygame.display.set_mode((800, 600))
clock = pygame.time.Clock()

boss_visible = False
boss_timer = 0
boss_delay = 3000  # 3 секунды

# Основной игровой цикл
running = True

boss = iter(Boss())

boss_timer = 0
boss_delay = 7_000

while running:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False

    # Логика игры
    if boss_timer > 0:
      boss_timer = pygame.time.get_ticks()
      if boss_timer >= boss_delay:
        boss.begin()
        boss_timer = 0

    if boss.hp == 0:
      try:
        boss.end()
        if boss.stage.name == '2':
          boss_timer = pygame.time.get_ticks()
          _boss_delay = boss_timer + boss_delay
        else:
          boss.begin()
      except StopIteration:
        # босс повержен
        raise

    if not boss_visible:
        # Начинаем отсчет времени
        boss_timer += clock.get_time()
        if boss_timer >= boss_delay:
            boss_visible = True
            boss_timer = 0

    # Отрисовка
    screen.fill((0, 0, 0))
    if boss_visible:
        # Отрисовать босса
        pygame.draw.rect(screen, (255, 0, 0), (400, 300, 50, 50))  # Пример босса
    pygame.display.flip()
    clock.tick(60)

pygame.quit()

как то так

→ Ссылка