Beispiel #1
0
    def __init__(self, x: float, y: float, damage: float, *groups, color=(255, 255, 255)):
        super().__init__(*groups)
        self.font = load_game_font(min(round(24 + abs(damage) / 3), 64))
        self.last_update_time = 0

        self.damage = abs(round(damage))
        self.alpha = 500
        self.image = self.font.render(str(self.damage), True, color).convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.center = x + randint(-TILE_SIZE // 1.5, TILE_SIZE // 1.5), y + randint(-20, 20)
    def __init__(self,
                 position: tuple,
                 text: str,
                 text_size: int,
                 base_button_filename="button.png",
                 hover_button_filename="button_hover.png",
                 *args):
        super().__init__(*args)

        # События, которые будут вызываться PyGame внутри update
        # (с помощью sender_text будет определено какая кнопка нажата)
        self.PRESS_EVENT = pygame.event.Event(Button.PRESS_TYPE,
                                              {"sender_text": text})
        self.HOVER_EVENT = pygame.event.Event(Button.HOVER_TYPE,
                                              {"sender_text": text})
        # Свойство, чтобы при наведении звук воспроизводился только один раз
        self.was_sound_played = False
        # Текст
        self.text = text
        self.font = load_game_font(text_size)
        # Базовое изображение
        self.text_surface = self.font.render(text, True, pygame.Color("white"))
        self.base_image = load_image(
            f"assets/sprites/UI/components/{base_button_filename}")
        self.base_image.blit(
            self.text_surface,
            self.text_surface.get_rect(
                center=self.base_image.get_rect().center))
        # Изображение при наведении
        self.hover_image = load_image(
            f"assets/sprites/UI/components/{hover_button_filename}")
        self.hover_image.blit(
            self.text_surface,
            self.text_surface.get_rect(
                center=self.hover_image.get_rect().center))
        # Текущее изображение
        self.image = self.base_image
        self.rect = self.image.get_rect()
        # Двигаем кнопку, но с учётом размера
        self.rect = self.rect.move(position[0] - self.rect.width / 2,
                                   position[1] - self.rect.height / 2)
class Message(pygame.sprite.Sprite):
    """
    Класс, представляющий текстовое сообщение появляющиеся при сталкивании
    с каким-либо объектом. Сами колизии и параметр времени последнего столкновения
    обрабатываются вне класса
    """
    font = load_game_font(32)
    # Время отрисовки на экране
    DRAWING_TIME = 1000
    # Время угасания, заимствующееся из времени отрисовки
    # (равное количество ставить не стоит)
    FADING_TIME = 500

    def __init__(self, screen: pygame.surface.Surface, text: str, height: int):
        super().__init__()
        # Изображение
        self.image = self.font.render(text, True,
                                      (255, 244, 79)).convert_alpha()
        self.rect = self.image.get_rect()
        self.rect.center = screen.get_width() // 2, int(height)
        # Последнее время столкновения
        self.last_collide_time = -self.DRAWING_TIME

    def draw(self, screen):
        # Время, прошедшее с последнего вызова отрисовки
        past_time = pygame.time.get_ticks() - self.last_collide_time
        # Учёт времени, для отрисовки сообщения
        if past_time <= Message.DRAWING_TIME:
            # Обработка эффекта затухании, по мере удаления от объекта
            if past_time >= Message.DRAWING_TIME - Message.FADING_TIME:
                # Коэффицент прозрачности, вычисляющийся из времени,
                # прошедшего с последнего вызова, и времени угасания сообщения
                k = (past_time - Message.DRAWING_TIME +
                     Message.FADING_TIME) / Message.FADING_TIME
                self.image.set_alpha(255 - round(255 * k))
            else:
                self.image.set_alpha(255)

            screen.blit(self.image, self.rect.topleft)
 def __init__(self, text: str, text_size: int, position: tuple):
     self.font = load_game_font(text_size)  # шрифт
     self.image = self.background_image  # фон
     indent = 50  # Отступ
     text = text.strip()
     # Высчитывание размера для фона
     size = (max(int(text_size * 0.38 * max(map(len, text.split('\n')))),
                 300),
             round(indent +
                   len(text.split('\n')) * self.font.get_height() * 0.9))
     # Отмасштабированый фон с текстурой для красоты
     self.image = scale_frame(self.image, size, indent)
     self.rect = self.image.get_rect()
     self.rect.center = position  # местоположение
     # Текст для диалога
     self.texts = text.split('\n')
     # Так нужно для вывода сразу нескольких строк
     self.text_surfaces = [
         self.font.render(part.strip(), True, (255, 255, 255))
         for part in self.texts
     ]
     # Флаг для отрисовки (если True, то диалог рисуется)
     self.need_to_draw = True
Beispiel #5
0
    def __init__(self, item_type: str, count: int, x: float, y: float,
                 all_sprites, *groups):
        super().__init__(all_sprites, GroundItem.sprites_group, *groups)

        self.type = item_type  # тип предмета
        self.count = abs(int(count))

        if not self.count:
            self.kill()

        self.image, self.sound = GroundItem.IMAGES[
            self.type], GroundItem.SOUNDS[self.type]
        self.sound.set_volume(DEFAULT_SOUNDS_VOLUME * 3)
        self.image = self.image.copy()
        self.rect = self.image.get_rect()
        self.rect.center = x, y
        self.collider = Collider(x, y)

        for other in pygame.sprite.spritecollide(self,
                                                 GroundItem.sprites_group,
                                                 False):
            other: GroundItem
            if self.type == other.type and other != self:
                self.count = self.count + other.count
                other.kill()

        font = load_game_font(32)

        if self.count > 1:
            count_text = font.render(str(self.count), True, (255, 255, 255))
            rect = count_text.get_rect()
            rect.center = self.rect.right - rect.w // 2, self.rect.bottom - rect.h // 2
            self.image.blit(
                count_text,
                count_text.get_rect(
                    bottomright=self.image.get_rect().bottomright))
class PlayerIcon:
    """
    Класс, представляющий UI элемент с отображением данных об игроке
    или компаньёне
    """
    # В этом случае фонт всегда будет общий у всех, поэтому это атрибут класса
    font = load_game_font(32)
    # Изображение с иконкой игрока
    PLAYER_FACE = pygame.transform.scale2x(
        load_image('assets/sprites/UI/icons/player_face.png'))
    # Изображение с иконкой помошника
    ASSISTANT_FACE = pygame.transform.scale2x(
        load_image('assets/sprites/UI/icons/assistant_face.png'))
    # Рамка вокруг иконки
    FRAME = load_image('assets/sprites/UI/icons/player_icon_frame.png')
    # Иконка яда
    size = (40, 40)
    POISON_ICON = load_image('assets/sprites/UI/icons/poison_icon.png', size)

    def __init__(self, player_or_assistant):
        # Ссылка на игрока (или асистента) для получение необходимоых
        # параметров, таких как: здоровье, мана и т.п.
        self.player_or_assistant = player_or_assistant

    def draw(self,
             screen: pygame.surface.Surface,
             position=(0, 0),
             size_coefficient=1):
        """
        Рисует UI элемент на экране screen
        :param screen: Экран для отрисовки
        :param position: позиция отрисовки от левого верхнего угла экрана
        :param size_coefficient: Коэффицент размера иконки
        """
        # Позиция
        x1, y1 = (0, 0)
        # Пустое изображение всей иконки, куда будут отрисовываться части ниже
        image = pygame.surface.Surface(self.FRAME.get_size(), pygame.SRCALPHA)
        # Высчитывание длинны полосы здоровья
        health_length = round(264 * (self.player_or_assistant.health /
                                     self.player_or_assistant.full_health) +
                              0.5)
        # Поверхность со здоровьем
        health_line = pygame.surface.Surface((health_length, 24))
        health_line.fill((255, 30, 30))
        # Отрисовка полоски со здоровьем и количества здоровья
        image.blit(health_line, (x1 + 132, y1 + 12))
        image.blit(
            self.font.render(
                f'{round(self.player_or_assistant.health + 0.5)}/' +
                f'{self.player_or_assistant.full_health}', True,
                (255, 255, 255)), (x1 + 220, y1 + 10))
        # Высчитывание длинны полосы маны
        mana_length = round(264 * (self.player_or_assistant.mana /
                                   self.player_or_assistant.full_mana) + 0.5)
        # Поверхность с маной
        mana_line = pygame.surface.Surface((mana_length, 24))
        mana_line.fill((30, 30, 255))
        # Отрисовка полоски с маной и количества маны
        image.blit(mana_line, (x1 + 132, y1 + 52))
        image.blit(
            self.font.render(
                f'{round(self.player_or_assistant.mana + 0.5)}/' +
                f'{self.player_or_assistant.full_mana}', True,
                (255, 255, 255)), (x1 + 220, y1 + 50))
        # Если текущая иконка относится к игроку
        if self.player_or_assistant.__class__.__name__ == 'Player':
            screen.blit(
                self.font.render(f'{round(self.player_or_assistant.money)}',
                                 True, (255, 255, 30)),
                (self.FRAME.get_width() + 20, 20))
            image.blit(self.PLAYER_FACE, (x1 + 25, y1 + 20))
        else:
            image.blit(self.ASSISTANT_FACE, (x1 + 25, y1 + 20))
        # Отрисовка фона (рамки) на иконку
        image.blit(self.FRAME, (x1, y1))
        # Отрисовка пустого текста на иконке
        # (для смещения, т.е. по сути это декоративный эффект)
        text_surface = self.font.render('', True, (255, 255, 255))
        image.blit(text_surface, (x1 + 8, y1 + 14))
        # Вывод всей иклггки на экран с учётом коэффицента размера
        screen.blit(
            pygame.transform.scale(
                image, (int(self.FRAME.get_width() * size_coefficient),
                        int(self.FRAME.get_height() * size_coefficient))),
            position)
class SpellContainer:
    """Класс представляет UI элемент с отображением данных о заклинании"""

    # В этом случае шрифт всегда будет общий у всех, поэтому это атрибут класса
    font = load_game_font(32)
    mini_font = load_game_font(16)
    # Задержка курсора на иконке перед показом рамки
    delay_time = 35
    size = (39, 39)  # размер для иконок ниже
    # Иконки кнопок джойстика, чтобы отображать кнопки для вызова заклинаний
    JOYSTICK_ICONS = {
        "o":
        load_image("assets/sprites/UI/icons/joystick_o.png", size),
        "x":
        load_image("assets/sprites/UI/icons/joystick_x.png", size),
        "triangle":
        load_image("assets/sprites/UI/icons/joystick_triangle.png", size),
        "square":
        load_image("assets/sprites/UI/icons/joystick_square.png", size),
        "L1":
        load_image("assets/sprites/UI/icons/joystick_L1.png", size),
        "L2":
        load_image("assets/sprites/UI/icons/joystick_L2.png", size),
    }
    # Поверхность, которая отображается, если заклинание недоступно
    # (т.е. эффект замедления)
    LOCKED = pygame.surface.Surface((20, 20)).convert_alpha()
    LOCKED.fill((0, 0, 0, 180))
    # Рамка (фон) вокруг иконки с заклинанием
    FRAME = load_image('assets/sprites/UI/icons/spell_icon_frame.png')

    def __init__(self, icon_filename: str, spell_class, player):
        # Иконка заклинания
        self.spell_icon = load_image(
            f"assets/sprites/UI/icons/{icon_filename}")
        self.rect = self.spell_icon.get_rect()
        self.w, self.h = self.spell_icon.get_size()  # размер иконки
        # Картинка затемнения
        self.locked = pygame.transform.scale(self.LOCKED, (self.w, self.h))
        self.mana_cost = spell_class.mana_cost  # Стоимость заклинания для игрока
        # ссылка на игрока для получение параметров, связанных с заклинаниями
        self.player = player
        # Информация, которая будет показана в рамке при наведении
        self.information = f'''{spell_class.__doc__}

        Урон: {spell_class.damage}{f' + {spell_class.extra_damage}' if spell_class.__name__ == 'PoisonSpell' else ''}
        {'Время действия: ' + str(spell_class.action_time) + ' c' 
        if spell_class.__name__ in ('IceSpell', 'PoisonSpell') else 'Мгновенное действие'}
        Затраты маны: {spell_class.mana_cost}'''.strip()
        # Диалоговое окно для вывода информации при наведении
        self.massage_box = MessageBox(self.information, 30, (0, 0))
        # время наведения, нужное для определение того, когда надо
        # отрисовать окно с информацией
        self.hover_time = 0

    def draw(self, screen: pygame.surface.Surface, position: tuple,
             is_joystick: bool, spell_key: str):
        """
        Рисует UI элемент на экране screen
        :param screen: Экран для отрисовки
        :param position: Позиция отрисовки
        :param is_joystick: Подключен ли джойстик
        :param spell_key: Строка, представляющая либо ключ для вывода иконки
        для джойстика, либо текст для вывода возле иконки заклинания
        """
        x1, y1 = position  # координаты для отрисовки
        pos = (x1 + 2, y1 + 18)  # учёт отступа по размерам края рамки
        self.rect.topleft = pos
        # Иконка заклинания
        screen.blit(self.spell_icon, pos)
        # Отрисовка затемнения, елси заклинание сейчас недоступно
        if self.player.mana < self.mana_cost or \
                pygame.time.get_ticks() - self.player.shoot_last_time < self.player.between_shoots_range:
            screen.blit(self.locked, pos)
        # Отрисовка рамки вокруг иконки заклинания
        screen.blit(self.FRAME, position)
        # Смещение между иконкой заклинания и кнопкой для переключения
        pos = (x1 + 5, y1 + 14)
        # Если подключён джойстик, то рисуется специальная иконка элемента,
        # которая активирует заклинание
        if is_joystick:
            screen.blit(SpellContainer.JOYSTICK_ICONS[spell_key], pos)
        # Иначе просто текст кнопки с клавиатуры
        else:
            button_text = SpellContainer.font.render(spell_key, True,
                                                     (255, 255, 255))
            screen.blit(button_text, pos)
        # При наведении курсора на заклинание, рисуется табличка с информацией
        if self.rect.collidepoint(*self.player.scope.rect.center):
            # Если время наведения на иконку с заклинанием привысело порог, то
            # информация выводится
            if self.hover_time == self.delay_time:
                # Смещение окошка в сторону прицела и отрисовка
                self.massage_box.rect.bottomleft = self.player.scope.rect.center
                self.massage_box.draw(screen)
            else:
                self.hover_time += 1
        elif self.hover_time:
            self.hover_time = 0
        # Отрисовка цены маны за заклинание в правом нижнем углу
        pos = (x1 + self.h - 6, y1 + self.w - 2)  # позиция
        cost_text = SpellContainer.mini_font.render(str(self.mana_cost), True,
                                                    (255, 255, 255))  # цена
        screen.blit(cost_text, pos)
Beispiel #8
0
def execute(screen: pygame.surface.Surface,
            money: int,
            count_of_alive_assistants: int,
            is_win=False):
    """
    Функция запускает конечной экран (либо смерти, либо победы)
    :param screen: Экран на котором надо отрисовывать менюв
    :param is_win: Флаг, выиграл ли игрок
    :param money: Количество собранных игроком и асистентом денег
    :param count_of_alive_assistants: Количетсво всех живых осистентов к концу игры
    игры
    """
    is_open = True
    # Фоновое изображение для всего экрана
    if is_win:
        # Фоновая музыка при победе
        pygame.mixer.music.load("assets/audio/music/win_screen_BG.ogg")
        pygame.mixer.music.play(-1)
        animated_background = AnimatedBackground(
            "win_{0}.png", "assets/sprites/UI/backgrounds/triumph_screen", 1,
            8, 80, screen.get_size())
        # Картигка с заголовком победы
        title_you_win = load_image('assets/sprites/UI/you_win.png')
        you_win_rect = title_you_win.get_rect()
        you_win_rect.center = screen.get_rect().centerx, int(
            screen.get_rect().centery * 0.7)
    else:
        # Фоновая музыка при проигрыше
        pygame.mixer.music.load("assets/audio/music/fail_screen_BG.mp3")
        pygame.mixer.music.play(-1)
        # Высчитывание размера для фона и сам фон
        size = screen.get_width() // 3, screen.get_height() // 3
        animated_background = AnimatedBackground(
            "death_{0}.png",
            "assets/sprites/UI/backgrounds/fail_screen",
            1,
            23,
            140,
            size,
            scale_2n=True)
    # Лого игры
    logo = LogoImage((screen.get_width() * 0.5, screen.get_height() * 0.1))
    # Изображение курсора
    cursor_image = load_image("assets/sprites/UI/icons/cursor.png")
    # Получение джойстика (если есть) и определение начальной позиции курсора
    if check_any_joystick():
        joystick = get_joystick()
        cursor_x, cursor_y = screen.get_rect().center
    else:
        joystick = None
        # Т.к. джойстика нет позиция будет сразу переопределна далее,
        # поэтому тут начальная позиция не задаётся
        cursor_x, cursor_y = 0, 0
    # Т.к. игрок завершил игру, то файл с сохранением будет перезаписан
    if os.path.isfile("data/save.txt"):
        with open('data/save.txt', 'r+', encoding="utf-8") as file:
            file.truncate(0)
    # Кортеж с текстом который надо вывести (каждый элемент на новой строке)
    texts = (f"Деньги собранные игроком вместе с асистентом: {money}",
             f"Количество живых асистентов: {count_of_alive_assistants}")
    # Шрифт для поверхностей ниже
    title_font = load_game_font(64)
    # Поверхности с одним и тем же текстом, но разный цвет делает крассивый эффект
    text_surfaces_yellow = [
        title_font.render(part.strip(), True, (255, 184, 50)) for part in texts
    ]
    text_surfaces_red = [
        title_font.render(part.strip(), True, (179, 64, 16)) for part in texts
    ]
    # Смещение между наложенными поверхностями для красивого эффекта
    surfaces_offset = 3
    margin = title_font.get_height() * 0.9  # отступ между двумя поверхностями
    # События, которые активируют закрытие экрана с концном
    QUITING_EVENTS = (
        pygame.QUIT,
        pygame.MOUSEBUTTONUP,
        pygame.KEYDOWN,
    )
    # Цикл меню
    while is_open:
        # Обработка событий
        for event in pygame.event.get():
            if event.type in QUITING_EVENTS:
                is_open = False
                break
        # Обновление позиции курсора
        if joystick is not None:
            # Проверка на выход
            if joystick.get_button(CONTROLS["JOYSTICK_UI_CLICK"]):
                break
            # Значение осей на левом стике
            axis_x, axis_y = joystick.get_axis(0), joystick.get_axis(1)
            # Перемещение курсора при движении оси
            if abs(axis_x) >= JOYSTICK_SENSITIVITY:
                cursor_x += JOYSTICK_CURSOR_SPEED * axis_x
            if abs(axis_y) >= JOYSTICK_SENSITIVITY:
                cursor_y += JOYSTICK_CURSOR_SPEED * axis_y
        else:
            cursor_x, cursor_y = pygame.mouse.get_pos()
        # На экране проигрыша есть фон, которого нет на экране победы
        if not is_win:
            screen.fill((31, 30, 36))
        # Вывод текущего кадра фонового изображения
        animated_background.update()
        screen.blit(
            animated_background.image,
            animated_background.image.get_rect(
                center=screen.get_rect().center))
        # Вывод картинки победного заголовка, если игрок выиграл
        if is_win:
            # Анализатор может ругаться, но если is_win истина, то
            # переменные 100% объявлены выше
            screen.blit(title_you_win, you_win_rect)
        # следущая позиция по y (будет нужно при вычислении смещения)
        next_y = 20
        # Вывод красного текста
        for text_surface in text_surfaces_red:
            y_pos = screen.get_height() * 0.6 + next_y
            screen.blit(
                text_surface,
                text_surface.get_rect(midtop=(screen.get_rect().centerx +
                                              surfaces_offset,
                                              y_pos + surfaces_offset)))
            next_y += margin
        next_y = 20
        # Вывод жёлтого текста
        for text_surface in text_surfaces_yellow:
            y_pos = screen.get_height() * 0.6 + next_y
            screen.blit(
                text_surface,
                text_surface.get_rect(midtop=(screen.get_rect().centerx,
                                              y_pos)))
            next_y += margin
        # Вывод логотипа игры
        screen.blit(logo.image, logo.rect.topleft)
        # Вывод изображения курсора
        screen.blit(cursor_image, (cursor_x, cursor_y))

        # Обновление состояния джойстика
        joystick = get_joystick() if check_any_joystick() else None
        pygame.display.flip()
Beispiel #9
0
def play(screen: pygame.surface.Surface,
         level_number: int = 1,
         user_seed: str = None) -> int:
    """
    Функция запуска игрового процесса
    :param screen: Экран для отрисовки
    :param level_number: Номер текущего уровня
    :param user_seed: Сид карты. Если он есть, по нему создаются уровни,
    раставляются враги и прочее
    :return: Код завершения игры (значения описаны в main.py)
    """
    # Псевдо загрузочный экран (для красоты)
    loading_screen(screen)
    # Размеры экрана
    screen_width, screen_height = screen.get_size()
    # Группа со всеми спрайтами
    all_sprites = pygame.sprite.Group()
    # Группа со спрайтами тайлов пола
    tiles_group = pygame.sprite.Group()
    # Группа со спрайтами ящиков и бочек
    # (они отдельно, т.к. это разрушаемые объекты)
    furniture_group = pygame.sprite.Group()
    # Группа со спрайтами преград (т.е. все физические объекты)
    collidable_tiles_group = pygame.sprite.Group()
    # Группа со спрайтами врагов
    enemies_group = pygame.sprite.Group()
    # Группа со спрайтами дверей
    doors_group = pygame.sprite.Group()
    # Группа со спрайтами факелов
    torches_group = pygame.sprite.Group()
    # Группа со спрайтом конца уровня (т.е. с лестницой перехода вниз)
    end_of_level = pygame.sprite.Group()
    # Группа с предметаими, которые находятся на полу
    GroundItem.sprites_group = pygame.sprite.Group()
    # Группа с сундуками
    Chest.chest_group = pygame.sprite.Group()

    is_open = True
    # Поверхность для эффекта затемнения
    transparent_grey = pygame.surface.Surface((screen_width, screen_height),
                                              pygame.SRCALPHA).convert_alpha()
    clock = pygame.time.Clock()  # Часы
    current_seed = user_seed  # текущий сид
    # Создаем уровень с помощью функции из generation_map
    level, level_seed = generate_new_level(
        current_seed.split('\n')[0].split() if current_seed else 0)
    # Игрок (None, т.к. будет переопределён либо при инициализации, либо при по)
    player = None
    if current_seed:
        data = current_seed.split('\n')  # Данные из сида
        # Получение данных об игроке из сида и создание игрока
        _, _, player_level, health, mana, money = data[3].split()[:-1]
        player = Player(0, 0, player_level, all_sprites, health, mana, money)
        # Получение данных об асистентах игрока
        for n in range(int(data[3].split()[-1])):
            # Получениев параметров асистента и его создание
            x1, y1, health, mana, *name = data[4 + n].split()
            assistant = PlayerAssistant(0, 0, player, all_sprites, health,
                                        mana, name)
            # Добавление асистента
            player.add_assistant(assistant)
    # Необходимые аргументы для инициализации уровня
    args = (level, level_number, all_sprites, tiles_group, furniture_group,
            collidable_tiles_group,
            enemies_group, doors_group, torches_group, end_of_level,
            current_seed.split('\n')[1].split() if current_seed else [],
            current_seed.split('\n')[2].split() if current_seed else [],
            player)
    # Инициализация уровня и получение данных об игроке и частях сида
    player, monsters_seed, boxes_seed = initialise_level(*args)
    if current_seed:
        # Если сид был передан, сдвигаем игрока на расстояние от начала уровня (лестницы)
        # Которое было записано в сид
        x_from_start, y_from_start = map(
            float,
            current_seed.split('\n')[3].split()[:2])
        player.rect.center = player.rect.centerx + x_from_start, player.rect.centery + y_from_start
    # Смещение всех асистентов игрока
    for assistant in player.assistants:
        assistant.rect.center = player.rect.center
    # Обновление и сохранение сида после инициализации уровня
    current_seed = '\n'.join([
        ' '.join(level_seed), ' '.join(monsters_seed), ' '.join(boxes_seed),
        str(player),
        str(level_number)
    ])
    save(current_seed)
    camera = Camera(screen.get_size())  # камера
    # Инициализация начальной позиции прицела игрока
    player.scope.init_scope_position((screen_width * 0.5, screen_height * 0.5))
    # Шрифт для вывода фпс в левом верхнем углу
    fps_font = load_game_font(48)
    # Иконка рядом с номером уровня (в правом верхнем углу)
    level_number_icon = load_tile('DOWNSTAIRS.png')
    # Иконка рядом с количеством врагов на уровне (в правом верхнем углу)
    monster_number_icon = load_image(
        'assets/sprites/UI/icons/monster_number.png', (TILE_SIZE, ) * 2)
    # Шрифт для вывода номера уровня и количества врагов
    level_and_enemies_font = load_game_font(64)
    # Сообщение, которое будет появлятся при приближении игрока к сундуку
    chest_title = Message(screen, 'Нажмите Е (или L2), чтобы открыть сундук',
                          screen.get_height() * 0.1)
    # Сообщение, которое будет появлятся при приближении игрока к спуску вниз
    downstairs_title = Message(
        screen, 'Нажмите Е (или L2), чтобы перейти на следующий уровень',
        screen.get_height() * 0.1)
    # Иконки для отображения иконок (контейнеров) с заклинаниями внизу экрана
    spells_containers = (
        SpellContainer("fire_spell.png", FireSpell, player),
        SpellContainer("ice_spell.png", IceSpell, player),
        SpellContainer("poison_spell.png", PoisonSpell, player),
        SpellContainer("void_spell.png", VoidSpell, player),
        SpellContainer("light_spell.png", FlashSpell, player),
        SpellContainer("teleport_spell.png", TeleportSpell, player),
    )
    # Панель с иконкой и информацией об игроке в левом верхнем углу
    player_icon = PlayerIcon(player)
    # Высота для высчитывания позиции отрисовки иконки асистента
    assistants_height = 180
    # Отступ для вывода иконки игрока и его ассистентов
    indent = 20
    # Фоновая музыка
    pygame.mixer.music.load("assets/audio/music/game_bg.ogg")
    pygame.mixer.music.play(-1)
    pygame.mixer.music.set_volume(DEFAULT_MUSIC_VOLUME)
    # Установка событий, обрабатываемых pygame, чтобы не тратить
    # время на обработку ненужных событий (это относится ко всей игре в целом,
    # где обрабатываются события)
    pygame.event.set_allowed((
        pygame.QUIT,
        pygame.MOUSEBUTTONUP,
        pygame.KEYDOWN,
    ))
    # Игровой цикл
    while is_open:
        was_pause_activated = False  # была ли активирована пауза
        keys = pygame.key.get_pressed()  # нажатые клавиши
        buttons = pygame.mouse.get_pressed(5)  # нажатые кнопки мыши
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                is_open = False
            if event.type == pygame.KEYDOWN:
                if event.key == CONTROLS["KEYBOARD_PAUSE"]:
                    was_pause_activated = True
        # Провверка использования заклинаний с джойстика
        if player.joystick:
            if player.joystick.get_button(CONTROLS["JOYSTICK_UI_PAUSE"]):
                was_pause_activated = True
            if player.joystick.get_button(CONTROLS["JOYSTICK_SPELL_FIRE"]):
                player.shoot('fire', enemies_group)
            if player.joystick.get_button(CONTROLS["JOYSTICK_SPELL_ICE"]):
                player.shoot('ice', enemies_group)
            if player.joystick.get_button(CONTROLS["JOYSTICK_SPELL_LIGHT"]):
                player.shoot('flash', enemies_group)
            if player.joystick.get_button(CONTROLS["JOYSTICK_SPELL_POISON"]):
                player.shoot('poison', enemies_group)
            if player.joystick.get_button(CONTROLS["JOYSTICK_SPELL_VOID"]):
                player.shoot('void', enemies_group)
            # Используется ось, т.к. назначен триггер R2
            if player.joystick.get_axis(CONTROLS["JOYSTICK_SPELL_TELEPORT"]
                                        ) > JOYSTICK_SENSITIVITY:
                player.shoot('teleport', tiles_group)
        # Иначе ввод с клавиатуры
        else:
            if keys[CONTROLS["KEYBOARD_SPELL_FIRE"]] or buttons[
                    CONTROLS["MOUSE_SPELL_FIRE"]]:
                player.shoot('fire', enemies_group)
            if keys[CONTROLS["KEYBOARD_SPELL_ICE"]] or buttons[
                    CONTROLS["MOUSE_SPELL_ICE"]]:
                player.shoot('ice', enemies_group)
            if keys[CONTROLS["KEYBOARD_SPELL_LIGHT"]] or buttons[
                    CONTROLS["MOUSE_SPELL_LIGHT"]]:
                player.shoot('flash', enemies_group)
            if keys[CONTROLS["KEYBOARD_SPELL_POISON"]] or buttons[
                    CONTROLS["MOUSE_SPELL_POISON"]]:
                player.shoot('poison', enemies_group)
            if keys[CONTROLS["KEYBOARD_SPELL_VOID"]]:
                player.shoot('void', enemies_group)
            if keys[CONTROLS["KEYBOARD_SPELL_TELEPORT"]]:
                player.shoot('teleport', tiles_group)
        # Обработка паузы
        if was_pause_activated:
            # # Остановка звуков и музыки
            pygame.mixer.pause()
            pygame.mixer.music.pause()
            # Запуск меню паузы
            code = game_menu.execute(screen)
            if code == 1:
                # Псевдо экран загрузки перед следующими действиями (для красоты)
                loading_screen(screen)
                # Очищаем все группы со спрайтами
                all_sprites.empty()
                tiles_group.empty()
                furniture_group.empty()
                collidable_tiles_group.empty()
                enemies_group.empty()
                doors_group.empty()
                torches_group.empty()
                end_of_level.empty()
                Chest.chest_group.empty()
                GroundItem.sprites_group.empty()
                Entity.damages_group.empty()
                # Сохранение данных перед выходом
                save('')
                return 2
            if code is not None:
                # Ставим экран загрузки перед следующими действиями
                loading_screen(screen)
                # Очищаем все группы со спрайтами
                all_sprites.empty()
                tiles_group.empty()
                furniture_group.empty()
                collidable_tiles_group.empty()
                enemies_group.empty()
                doors_group.empty()
                torches_group.empty()
                end_of_level.empty()
                Chest.chest_group.empty()
                GroundItem.sprites_group.empty()
                Entity.damages_group.empty()
                # Сохранение данных перед выходом
                if player.alive:
                    current_seed = '\n'.join([
                        ' '.join(level_seed), ' '.join(monsters_seed),
                        ' '.join(boxes_seed),
                        str(player),
                        str(level_number)
                    ])
                    save(current_seed)
                else:
                    save('')
                return -1
            # Возвращение звука и мызыки так, как было до паузы
            pygame.mixer.unpause()
            pygame.mixer.music.unpause()
        screen.fill(BACKGROUND_COLOR)  # Очистка экрана
        player.update()  # Обновление игрока
        # Если игрок умер, то открывается экран конца игры
        if player.destroyed:
            # Остановка звуков и музыки
            pygame.mixer.pause()
            pygame.mixer.music.pause()
            # Подсчёт количества живых асистентов у игрока (для вывода статистики)
            count_of_alive_assistants = 0
            for sprite in player.assistants.sprites():
                sprite: PlayerAssistant
                # Если асистент живой увеличиваем счётчик
                count_of_alive_assistants += int(sprite.alive)
            # Запуск экрана с концом
            end_screen.execute(screen, player.money, count_of_alive_assistants)
            return -1
        # Проверка на столкновение с любым сундуком
        if pygame.sprite.spritecollideany(player, Chest.chest_group):
            # Обновление времени столкновения с сундуком для
            # красивой отрисовки сообщения
            chest_title.last_collide_time = pygame.time.get_ticks()
            # Проверка на использование (с джойстика или клавиатуры)
            if ((player.joystick and player.joystick.get_axis(
                    CONTROLS['JOYSTICK_USE']) > JOYSTICK_SENSITIVITY)
                    or (keys[CONTROLS['KEYBOARD_USE']])):
                pygame.sprite.spritecollide(player, Chest.chest_group,
                                            False)[0].open()

        enemies_group.update(player)  # обновление врагов
        player.assistants.update(enemies_group)  # обновление асистентов
        Entity.spells_group.update()  # обновление заклинаний
        # Обновление факелов (для звука огня по расстоянию до факела)
        torches_group.update(player)
        # Обновление всех дверей
        doors_group.update(player, enemies_group,
                           [player] + list(player.assistants))
        Chest.chest_group.update()  # обновление сундуков
        Entity.damages_group.update()  # обновление текста с выводом урона
        # Проверка перехода на следующий уровень, при соприкосновении с лестницой вниз
        if pygame.sprite.spritecollideany(player.collider, end_of_level):
            # Обновление времени столкновения с лестницой вниз для
            # красивой отрисовки сообщения
            downstairs_title.last_collide_time = pygame.time.get_ticks()
            if (keys[pygame.K_e]
                    or (player.joystick and player.joystick.get_axis(
                        CONTROLS['JOYSTICK_USE']) > JOYSTICK_SENSITIVITY)):
                # Затухание музыки и звуком
                pygame.mixer.fadeout(1000)
                pygame.mixer.music.fadeout(1000)
                # Псевдо загрузочный экран для красоты
                loading_screen(screen)
                # Если игрок прошёл 10 уровней, то это победа
                if level_number == 10:
                    # Подсчёт количества живых асистентов у игрока (для вывода статистики)
                    count_of_alive_assistants = 0
                    for sprite in player.assistants.sprites():
                        sprite: PlayerAssistant
                        # Если асистент живой увеличиваем счётчик
                        count_of_alive_assistants += int(sprite.alive)
                    # Победный экран
                    end_screen.execute(screen,
                                       player.money,
                                       count_of_alive_assistants,
                                       is_win=True)
                    return -1
                # Иначе перезагружаются параметры для нового уровня
                else:
                    # Очистка всех групп со спрайтами
                    all_sprites.empty()
                    tiles_group.empty()
                    furniture_group.empty()
                    collidable_tiles_group.empty()
                    enemies_group.empty()
                    doors_group.empty()
                    torches_group.empty()
                    end_of_level.empty()
                    Chest.chest_group.empty()
                    GroundItem.sprites_group.empty()
                    Entity.damages_group.empty()
                    level_number += 1  # увеличение номер уровня
                    # Создание целиком нового уровень функцией из generation_map
                    level, level_seed = generate_new_level(0)
                    # Необходимые аргументы для инициализации уровня
                    args = (level, level_number, all_sprites, tiles_group,
                            furniture_group, collidable_tiles_group,
                            enemies_group, doors_group, torches_group,
                            end_of_level, [], [])
                    # Инициализация уровня и получение данных об игроке и частях сида
                    player, monsters_seed, boxes_seed = initialise_level(
                        *args, player=player)
                    # Добавление игрока и асистентов
                    all_sprites.add(player)
                    all_sprites.add(player.assistants)
                    # Смещение асистентов к игроку
                    for assistant in player.assistants:
                        assistant.rect.center = player.rect.center
                    # Изменение текущего сида и файла сохранения
                    current_seed = '\n'.join([
                        ' '.join(level_seed), ' '.join(monsters_seed),
                        ' '.join(boxes_seed),
                        str(player),
                        str(level_number)
                    ])
                    save(current_seed)
                    # Установка начальной позиции приуела
                    player.scope.init_scope_position(
                        (screen_width * 0.5, screen_height * 0.5))
                    # Иконки для отображения иконок (контейнеров) с заклинаниями внизу экрана
                    spells_containers = (
                        SpellContainer("fire_spell.png", FireSpell, player),
                        SpellContainer("ice_spell.png", IceSpell, player),
                        SpellContainer("poison_spell.png", PoisonSpell,
                                       player),
                        SpellContainer("void_spell.png", VoidSpell, player),
                        SpellContainer("light_spell.png", FlashSpell, player),
                        SpellContainer("teleport_spell.png", TeleportSpell,
                                       player),
                    )
                    # Включение музыки после обновления параметров
                    pygame.mixer.music.play(-1)
                    continue
        # Применение смещения камеры относительно игрока
        camera.update(player)
        for sprite in all_sprites:
            camera.apply(sprite)
        # Отрисовка спрайтов в определённом порядке,
        # чтобы они не перекрывали друг друга
        tiles_group.draw(screen)  # тайлы пола
        torches_group.draw(screen)  # факеда
        # Сундуки
        for chest in Chest.chest_group:
            chest.draw_back_image(screen)
        # предметы на земле (мясо и деньги)
        GroundItem.sprites_group.draw(screen)
        collidable_tiles_group.draw(
            screen)  # физические объекты не являющиеся стенами
        doors_group.draw(screen)  # двери
        enemies_group.draw(screen)  # враги
        player.assistants.draw(screen)  # асистенты
        # Шкалы здоровья у асистентов
        for assistant in player.assistants:
            assistant.draw_health_bar(screen)
        player.draw(screen)  # игрок
        Entity.spells_group.draw(screen)  # заклинания
        player.draw_health_bar(screen)  # шкала здоровья у игрока
        # Шкала здоровья у врагов
        for enemy in enemies_group:
            enemy.draw_health_bar(screen)
        Entity.damages_group.draw(screen)  # текст с уроном
        chest_title.draw(screen)  # сообщение по мере приближении к сундуку
        # сообщение по мере приближении к лестнице вниз
        downstairs_title.draw(screen)
        # Значения для определения того, какие иконки текст,
        # нужно отображать на иконках с заклинаниями (нужно, чтобы игроку было
        # проще привыкнуть к управлению)
        is_joystick = player.joystick is not None
        if is_joystick:
            spell_args = ("o", "x", "triangle", "square", "L1", "L2")
        else:
            spell_args = ("1", "2", "3", "4", "5", "Space")
        # Контейнеры с заклинаниями
        for i in range(len(spells_containers) - 1, -1, -1):
            pos = (screen_width * (0.375 + 0.05 * i), screen_height * 0.9)
            spells_containers[i].draw(screen, pos, is_joystick, spell_args[i])
        # Панель с игроком в левом верхнем углу
        player_icon.draw(screen, (indent, indent))
        # Иконоки у асистентов
        for number_of_assistant, assistant in enumerate(player.assistants):
            if not assistant.icon:
                assistant.icon = PlayerIcon(assistant)
            assistant.icon.draw(
                screen,
                (indent + 20, assistants_height + number_of_assistant * 80),
                0.5)
        # фпс
        fps_text = fps_font.render(str(round(clock.get_fps())), True,
                                   (100, 255, 100))
        screen.blit(fps_text, (2, 2))
        # Иконка и количество врагов на уровне
        monster_number_text = level_and_enemies_font.render(
            str(len(enemies_group)), True, (255, 255, 255))
        screen.blit(monster_number_icon, (screen_width - 70, 80))
        screen.blit(monster_number_text, (screen_width - 120, 80))
        # Иконка и номер уровня
        level_number_text = level_and_enemies_font.render(
            str(level_number), True, (255, 255, 255))
        screen.blit(level_number_icon, (screen_width - 70, 10))
        screen.blit(level_number_text, (screen_width - 120, 10))
        # Прицел игрока
        player.scope.draw(screen)

        clock.tick(FPS)
        pygame.display.flip()

    # Запись сохранения после закрытия игры
    save(current_seed)
    return 0
Beispiel #10
0
class Entity(pygame.sprite.Sprite):
    """
    Класс, отвечающий за предстовление базовой сущности в игре
    """

    # Группа со спрайтами, которые считаются физическими объектами
    # общими для всех сущностей.
    collisions_group: pygame.sprite.Group
    all_sprites: pygame.sprite.Group
    player = None

    # Группа со всеми сущностями (экземплярами этого класса)
    # Нужна в основном для коллизий между существами
    entities_group = pygame.sprite.Group()
    damages_group = pygame.sprite.Group()
    spells_group = pygame.sprite.Group()

    default_speed = TILE_SIZE * 0.2

    WAITING_TIME = 2000
    UPDATE_TIME = 120
    HEALTH_LINE_WIDTH = 10
    HEALTH_LINE_TIME = 5000

    POISON_DAMAGE = 5
    BETWEEN_POISON_DAMAGE = 1000

    size = (int(TILE_SIZE),) * 2
    sleeping_frames = cut_sheet(load_image('assets/sprites/enemies/sleep_icon_spritesheet.png'), 4, 1, size)
    poison_frames = cut_sheet(load_image('assets/sprites/spells/poison_static.png'), 5, 1, size)[0]

    small_font = load_game_font(15)
    font = load_game_font(24)

    def __init__(self, x: float, y: float, *args):
        # Конструктор класса Sprite
        super().__init__(*((Entity.entities_group,) + args))
        self.alive = True

        # Изображение
        self.cur_frame = 0
        self.image = self.__class__.frames[0][self.cur_frame]
        self.last_update = pygame.time.get_ticks()
        self.width, self.height = self.image.get_size()

        self.last_damage_time = -Entity.HEALTH_LINE_TIME
        self.last_poison_damage = 0
        self.sleeping_time = None
        self.cur_sleeping_frame = 0
        self.cur_poison_frame = 0
        self.poison_static_time = 0
        self.ice_buff = 0
        self.poison_buff = 0

        self.start_position = x, y
        self.point = None
        self.speed = 0.0001

        self.rect = self.image.get_rect()
        self.rect.center = x, y
        self.collider = Collider(*self.rect.center)

        # Скорость
        self.dx = self.dy = 0

        # Направление взгляда
        self.look_direction_x = 0
        self.look_direction_y = 1

    def update(self) -> None:
        if self.ice_buff:
            self.ice_buff -= 1
            self.speed = self.__class__.default_speed * 0.2
        else:
            self.speed = self.__class__.default_speed

        ticks = pygame.time.get_ticks()
        if self.poison_buff and ticks - self.last_poison_damage > Entity.BETWEEN_POISON_DAMAGE:
            self.last_poison_damage = ticks
            self.poison_buff -= 1
            self.get_damage(Entity.POISON_DAMAGE, 'poison')

    def move(self, dx, dy):
        """
        Метод передвижения. Сущность сдвинется на указанные параметры,
        если там свободно.
        :param dx: Изменение координаты по Х
        :param dy: Изменение координаты по Y
        """
        if not self.alive:
            return
        # Координаты до движения
        pos = self.rect.x, self.rect.y
        # Перемещение по x и обновление столкновений
        self.rect.x = round(self.rect.x + dx)
        self.collider.update(*self.rect.center)

        # Если сущность врезалась во что-то, возвращаем к исходному x
        if pygame.sprite.spritecollide(self.collider, Entity.collisions_group, False):
            self.rect.x = pos[0]
            self.dx = 0
            if self.__class__.__name__ == 'Player':
                self.dash_force_x *= 0.8
                self.dash_direction_x = self.look_direction_x
            elif self.__class__.__name__ == 'PlayerAssistant':
                self.point = None
        # Перемещение по y и обновление столкновений
        self.rect.y = round(self.rect.y + dy)
        self.collider.update(*self.rect.center)

        # Если сущность врезалась во что-то, возвращаем к исходному y
        if pygame.sprite.spritecollide(self.collider, Entity.collisions_group, False):
            self.rect.y = pos[1]
            self.dy = 0
            if self.__class__.__name__ == 'Player':
                self.dash_force_y *= 0.8
                self.dash_direction_y = self.look_direction_y
            elif self.__class__.__name__ == 'PlayerAssistant':
                self.point = None

    def update_frame_state(self, n=0):
        """
        Воспроизводит звук и сменяет кадр анимации

        :param n: если есть, сдвигает номер анимации (стояние вместо движения)
        
        """
        tick = pygame.time.get_ticks()
        if tick - self.last_update > self.UPDATE_TIME:
            self.last_update = tick

            if not self.alive:
                self.cur_frame = self.cur_frame + 1
                if self.cur_frame >= len(self.__class__.death_frames) - 1:
                    if self.__class__.__name__ == 'Player':
                        self.destroyed = True
                    else:
                        for group in self.groups():
                            group.remove(self)
                if self.cur_frame < len(self.__class__.death_frames):
                    self.image = self.__class__.death_frames[self.cur_frame]
                return

            look = self.__class__.look_directions[self.look_direction_x, self.look_direction_y]
            look += n
            self.cur_frame = (self.cur_frame + 1) % len(self.__class__.frames[look])
            self.image = self.__class__.frames[look][self.cur_frame]
            if (self.__class__.__name__ != 'Player' and DEFAULT_SOUNDS_VOLUME * 200 / self.distance_to_player > 0.1
                    and (look < 2 or 'Slime' in self.__class__.__name__ or 'Demon' in self.__class__.__name__)
                    and not self.sounds_channel.get_busy()):
                self.FOOTSTEP_SOUND.set_volume(min(DEFAULT_SOUNDS_VOLUME / (self.distance_to_player / TILE_SIZE) * 3, 1))
                self.sounds_channel.play(self.FOOTSTEP_SOUND)

    def draw_health_bar(self, screen):
        """
        Отрисовка полоски здоровья и отрисовка знака сна (Z-Z-Z).
        :param screen: Экран
        """
        line_width = Entity.HEALTH_LINE_WIDTH  # длинна полоски здоровья
        x, y = self.rect.center  # текущая позиция сущности
        width, height = self.rect.size  # текущие размеры сущности
        # Позиция над сущностью
        x1, y1 = x - width * 0.5, y - height * 0.5
        # При получении урона или наведении прицелом выводиться шкала здоровья
        if pygame.time.get_ticks() - self.last_damage_time < Entity.HEALTH_LINE_TIME or \
                pygame.sprite.collide_rect(self, Entity.player.scope):
            # Обводка вокруг шкалы здоровья
            pygame.draw.rect(screen, 'dark grey', (x1 - 1, y1 - 10 - 1, width + 2, line_width + 2))
            # Длинная полоски здоровья
            health_length = width * max(self.health, 0) / self.full_health
            # Для игрока и его помошника зелёный цвет, для остальных красный
            color = '#00b300' if self.__class__.__name__ in ('Player', 'PlayerAssistant') else 'red'
            # Сама полоска здоровья
            pygame.draw.rect(screen, color, (x1, y1 - 10, health_length, line_width))
            # Текст с текущем здоровьем
            health_text = f'{round(self.health + 0.5)}/{self.full_health}'
            health = self.small_font.render(health_text, True, (255, 255, 255))
            # Отцентровка текста по середине
            rect = health.get_rect()
            rect.center = (x1 + width // 2, y1 - 5)
            # Вывод на экран
            screen.blit(health, rect.topleft)
        # Для всех сущностей кроме игрока выводиться их название
        if self.__class__.__name__ not in ('Player',):
            name = self.font.render(self.name, True, (255, 255, 255))
            rect = name.get_rect()
            rect.center = (x1 + width // 2, y1 - 12 - line_width)
            screen.blit(name, rect.topleft)

        if not self.alive:
            return
        # Отрисовка эффетка яда и обновление параметров
        ticks = pygame.time.get_ticks()
        if self.poison_buff:
            if ticks - self.poison_static_time > Entity.UPDATE_TIME:
                self.poison_static_time = ticks
                self.cur_poison_frame = (self.cur_poison_frame + 1) % len(Entity.poison_frames)
            screen.blit(Entity.poison_frames[self.cur_poison_frame], (self.rect.x, self.rect.y))
        # Отрисовка анимации сна и обновление параметров
        if self.__class__.__name__ not in ('Player',) and not self.target_observed:
            if not self.sleeping_time or ticks - self.sleeping_time >= 250:
                self.cur_sleeping_frame = (self.cur_sleeping_frame + 1) % len(self.sleeping_frames[0])
                self.sleeping_time = ticks
            screen.blit(self.sleeping_frames[0][self.cur_sleeping_frame], (self.rect.centerx + 10, self.rect.y - 35))

    def get_damage(self, damage, spell_type='', action_time=0):
        """
        Получение дамага

        :param damage: Столько здоровья надо отнять
        :param spell_type: Тип урона, чтоб узнавать, на кого он действует сильнее
        :param action_time: Время действия (для льда и отравления)
        
        """

        if not self.alive:
            return
        if spell_type == 'ice':
            self.ice_buff += action_time
        if spell_type == 'poison' and damage >= 5:
            self.poison_buff += action_time

        if damage >= 0:
            look = self.__class__.look_directions[self.look_direction_x, self.look_direction_y]
            self.image = self.get_damage_frames[look]
            self.last_update = pygame.time.get_ticks() + 75
            self.last_damage_time = pygame.time.get_ticks()

        if (self.__class__.__name__ == 'Demon' and spell_type == 'ice' or
                self.__class__.__name__ == 'GreenSlime' and spell_type == 'flash' or
                self.__class__.__name__ == 'DirtySlime' and spell_type == 'void' or
                self.__class__.__name__ == 'Zombie' and spell_type == 'fire' or
                self.__class__.__name__ == 'FireWizard' and spell_type == 'poison' or
                self.__class__.__name__ == 'VoidWizard' and spell_type == 'fire'):
            damage *= 2

        if (self.__class__.__name__ == 'Demon' and spell_type == 'fire' or
                self.__class__.__name__ == 'GreenSlime' and spell_type == 'poison' or
                self.__class__.__name__ == 'DirtySlime' and spell_type == 'ice' or
                self.__class__.__name__ == 'Zombie' and spell_type == 'flash' or
                self.__class__.__name__ == 'FireWizard' and spell_type == 'fire' or
                self.__class__.__name__ == 'VoidWizard' and spell_type == 'void'):
            damage *= 0.25

        damage *= 1000
        damage += randint(-abs(round(-damage * 0.2)), abs(round(damage * 0.2)))
        damage /= 1000

        x, y = self.rect.midtop
        colors = {
            'poison': (100, 230, 125),
            'ice':    (66, 170, 255),
            'void':   (148, 0, 211),
            'flash':  (255, 255, 0),
            'fire':   (226, 88, 34),
        }
        ReceivingDamage(x, y, damage, Entity.all_sprites, Entity.damages_group, color=colors[spell_type])

        self.health = min(self.health - damage, self.full_health)
        if self.health <= 0:
            self.health = 0
            self.death()

    def set_first_frame(self):
        """Установка первого спрайта"""
        tick = pygame.time.get_ticks()
        if tick - self.last_update > self.UPDATE_TIME:
            self.last_update = tick

            look = self.__class__.look_directions[self.look_direction_x, self.look_direction_y]
            self.cur_frame = 0
            self.image = self.__class__.frames[look][self.cur_frame]

    @staticmethod
    def set_global_groups(collisions_group: pygame.sprite.Group, all_sprites: pygame.sprite.Group):
        """
        Метод устанавливает группу со спрайтами, которые будут считаться
        физическими объектами для всех сущностей на уровне.
        (Кроме индивидуальных спрайтов у конкретных объектов,
        например у врагов будет отдельное взаимодействие с игроком).
        Метод нужен при инициализации
        :param collisions_group: Новая группа
        :param all_sprites: Группа всех спрайтов
        """
        Entity.collisions_group = collisions_group
        Entity.all_sprites = all_sprites