class GroundItem(pygame.sprite.Sprite): sprites_group: pygame.sprite.Group # Канал для звуков sounds_channel = pygame.mixer.Channel(1) # Звуки SOUNDS = { "meat": load_sound("assets/audio/sfx/items/meat_sound_1.mp3"), "money": load_sound("assets/audio/sfx/items/money_sound.mp3"), } # Изображения size = (int(TILE_SIZE * 0.6), ) * 2 IMAGES = { "meat": load_image("assets/sprites/items/meat.png", size), "money": load_image("assets/sprites/items/money.png", size), } 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 FireSpell(Spell): """Огненное заклинание Обычное Эффективно против: Зомби, Древних магов Неэффективно против: Демонов, Огненных магов""" damage = 50 spell_type = Spell.FIRE mana_cost = 60 UPDATE_TIME = 40 speed = TILE_SIZE * 0.22 acceleration = 2 action_time = 0 size = (TILE_SIZE // 4 * 3, ) * 2 frames = cut_sheet(load_image("assets/sprites/spells/fire_laser.png"), 6, 1, size) frames += cut_sheet(load_image("assets/sprites/spells/fire_explosion.png"), 7, 9, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/cast_sound_2.ogg") SPELL_SOUNDS = ( load_sound("assets/audio/sfx/spells/spell_sound_1.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_3.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_4.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_5.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_14.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_24.ogg"), )
class CallZombiesSpell(Spell): """ Заклинание спавна монстров (2-4). Самой реализации спавна тут нет, это просто спецэффект (в данном случае звуки) """ # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/call_zombies.ogg", volume=DEFAULT_SOUNDS_VOLUME * 1.5)
class TeleportSpell(Spell): """Заклинание телепортации Переносит игрока в позицию прицела""" spell_type = Spell.TELEPORT damage = "---" mana_cost = 250 UPDATE_TIME = 40 speed = TILE_SIZE * 3 acceleration = 0 damage_frame = 2 action_time = 0 size = (TILE_SIZE // 4 * 7, ) * 2 frames = cut_sheet(load_image("assets/sprites/tiles/EMPTY.png"), 1, 1, size) frames += cut_sheet(load_image("assets/sprites/spells/teleport_puf.png"), 8, 1, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/teleport_sound.ogg") SPELL_SOUNDS = (load_sound("assets/audio/sfx/spells/teleport_sound.ogg"), ) def __init__(self, subject_x: float, subject_y: float, object_x: float, object_y: float, extra_damage: float, object_group, *groups): super().__init__(subject_x, subject_y, object_x, object_y, extra_damage, object_group, *groups) self.start_sprite = pygame.sprite.Sprite() self.start_sprite.start_position = None self.start_sprite.point = None self.start_sprite.image = TeleportSpell.frames[0][0] self.start_sprite.rect = self.start_sprite.image.get_rect() self.start_sprite.rect.center = object_group[0].rect.center
class PoisonSpell(Spell): """Заклинание отравления Отравляет противника, постепенно нанося урон Эффективно против: Огненных магов Неэффективно против: Зеленых слизеней""" spell_type = Spell.POISON damage = 10 action_time = 10 extra_damage = Entity.POISON_DAMAGE * action_time mana_cost = 50 UPDATE_TIME = 40 speed = TILE_SIZE * 0.2 acceleration = 0.5 size = (TILE_SIZE // 4 * 3, ) * 2 frames = cut_sheet(load_image("assets/sprites/spells/poison_laser.png"), 7, 1, size) frames += cut_sheet( load_image("assets/sprites/spells/poison_explosion.png"), 11, 1, size) frames += cut_sheet( load_image("assets/sprites/spells/poison_explosion_1.png"), 25, 1, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/cast_sound_3.ogg") SPELL_SOUNDS = ( load_sound("assets/audio/sfx/spells/spell_sound_1.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_12.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_13.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_17.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_23.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_24.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_26.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_27.ogg"), )
class VoidSpell(Spell): """Заклинание пустоты Урон по площади Эффективно против: Грязных слизеней Неэффективно против: Древних магов""" damage = 60 spell_type = Spell.VOID mana_cost = 100 speed = TILE_SIZE * 0.24 acceleration = 3 UPDATE_TIME = 40 damage_frame = 2 action_time = 0 size = (TILE_SIZE * 3, ) * 2 frames = cut_sheet(load_image("assets/sprites/spells/void_laser.png"), 10, 1) frames += cut_sheet(load_image("assets/sprites/spells/void_explosion.png"), 12, 2, size) frames += cut_sheet( load_image("assets/sprites/spells/void_explosions.png"), 10, 5, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/cast_sound_5.ogg") SPELL_SOUNDS = ( load_sound("assets/audio/sfx/spells/spell_sound_2.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_5.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_14.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_15.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_16.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_17.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_18.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_25.ogg"), )
class Torch(pygame.sprite.Sprite): frames = cut_sheet(load_image("assets/sprites/tiles/TORCH.png"), 8, 1, (round(TILE_SIZE / 4 * 3), ) * 2)[0] # Канал для звуков sounds_channel = pygame.mixer.Channel(0) min_distance_to_player = 100 update_sounds_channel = 0 # Звуки BURNING_SOUND = load_sound("assets/audio/sfx/world/torch_sound.mp3") BURNING_SOUND.set_volume(DEFAULT_SOUNDS_VOLUME) def __init__(self, x: float, y: float, *groups): super().__init__(*groups) self.image = Torch.frames[randint(0, len(Torch.frames) - 1)] self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) self.cur_frame = 0 self.update_time = pygame.time.get_ticks() def update(self, player=None) -> None: ticks = pygame.time.get_ticks() # Через каждые назначенные промежутки времени обновляем минимальное расстояние до игрока if ticks - Torch.update_sounds_channel > 100: Torch.update_sounds_channel = ticks + randint(-20, 20) Torch.min_distance_to_player = 100 return if ticks - self.update_time > 100: self.update_time = pygame.time.get_ticks() while 1: n = randint(0, len(self.frames) - 1) if n != self.cur_frame: self.cur_frame = n break self.image = Torch.frames[self.cur_frame] # Вычисляем расстояние до игрока и ставим с этим коэффиццентом громкость звука # Так он будет меняться, когда мы подходим к факелам и отходим distance_to_player = dist(player.rect.center, self.rect.center) Torch.min_distance_to_player = min(max(distance_to_player, 0.000001), Torch.min_distance_to_player) self.BURNING_SOUND.set_volume( min( DEFAULT_SOUNDS_VOLUME / (Torch.min_distance_to_player / TILE_SIZE) * 3, 1.2)) if not self.sounds_channel.get_busy(): self.sounds_channel.play(self.BURNING_SOUND)
class IceSpell(Spell): """Ледяное заклинание На время замедляет противника (включая перезарядку заклинаний) Эффективно против: Демонов Неэффективно против: Грязных слизней""" damage = 25 spell_type = Spell.ICE mana_cost = 40 UPDATE_TIME = 40 speed = TILE_SIZE * 0.3 acceleration = 4 action_time = 500 size = (TILE_SIZE // 4 * 3, ) * 2 frames = cut_sheet(load_image("assets/sprites/spells/ice_laser.png"), 30, 1, size) frames += cut_sheet(load_image("assets/sprites/spells/ice_explosion.png"), 28, 1, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/cast_sound_1.ogg") SPELL_SOUNDS = ( load_sound("assets/audio/sfx/spells/spell_sound_2.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_12.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_18.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_19.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_20.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_21.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_22.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_26.ogg"), )
class Button(pygame.sprite.Sprite): """Класс, представляющий кнопку в UI элементах""" # Типы событий PRESS_TYPE = pygame.USEREVENT + 1 HOVER_TYPE = pygame.USEREVENT + 2 # Звук при наведении HOVER_SOUND = load_sound("assets/audio/sfx/UI/button_hover.wav") 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) def update(self, scope_position: tuple, was_click: bool, *args) -> None: """ Метод обновляет состояние кнопки. Если что-то произошло, то вызывается соответствующий метод, и меняется sprite (если нужно) :param scope_position: Кортеж координат курсора или прицела, если подключён джойстик :param was_click: Было ли произведено нажатие """ # Проверяем наличие колизии курсора с кнопкой if self.rect.collidepoint(*scope_position): # Добавляем нужное событие в конец списка событий if was_click: pygame.event.post(self.PRESS_EVENT) else: # Если звук наведения не был воспроизведён, то он воспроизводится if not self.was_sound_played: Button.HOVER_SOUND.play() self.was_sound_played = True pygame.event.post(self.HOVER_EVENT) # Меняем изображение self.image = self.hover_image else: self.image = self.base_image # Т.к. на кнопку наведения нет, то сбрасываем свойство self.was_sound_played = False
class Door(pygame.sprite.Sprite): frames = [load_tile('DOOR.png'), load_tile('EMPTY.png')] # Канал для звуков sounds_channel = pygame.mixer.Channel(0) min_distance_to_player = 100 update_sounds_channel = 0 # Звуки OPEN_SOUND = load_sound("assets/audio/sfx/world/door_open.mp3") CLOSE_SOUND = load_sound("assets/audio/sfx/world/door_close.mp3") def __init__(self, x: float, y: float, *groups): super().__init__(*groups) self.image = Door.frames[0] self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) self.collider = Collider(x, y) self.opened = False def update(self, player=None, enemies_group=None, player_group=None) -> None: ticks = pygame.time.get_ticks() if ticks - Door.update_sounds_channel > 100: # каждый назначенный промежуток времени сбрасываем минимальную дистанцию до игрока Door.update_sounds_channel = ticks Door.min_distance_to_player = 100 return collide = pygame.sprite.spritecollide # Если дверь была закрыта, а теперь кто-то с ней соприкасается, открываем и издаём звук if not self.opened and (collide(self, enemies_group, False) or collide(self, player_group, False)): dx, dy = player.rect.centerx - self.rect.centerx, player.rect.centery - self.rect.centery Door.min_distance_to_player = min( max((dx**2 + dy**2)**0.5, 0.000001), Door.min_distance_to_player) volume = min( DEFAULT_SOUNDS_VOLUME / (Door.min_distance_to_player / TILE_SIZE) * 10, 1.2) self.OPEN_SOUND.set_volume(volume) if self.sounds_channel.get_busy(): self.sounds_channel.stop() self.sounds_channel.play(self.OPEN_SOUND) self.opened = True self.image = Door.frames[1] # Если дверь была открыта, а сейчас никто с ней не соприкасается, # закрываем и издаем соответсвующий звук elif self.opened and not (collide(self, enemies_group, False) or collide(self, player_group, False)): dx, dy = player.rect.centerx - self.rect.centerx, player.rect.centery - self.rect.centery Door.min_distance_to_player = min( max((dx**2 + dy**2)**0.5, 0.000001), Door.min_distance_to_player) volume = min( DEFAULT_SOUNDS_VOLUME / (Door.min_distance_to_player / TILE_SIZE) * 10, 1.2) self.CLOSE_SOUND.set_volume(volume) if self.sounds_channel.get_busy(): self.sounds_channel.stop() self.sounds_channel.play(self.CLOSE_SOUND) self.opened = False self.image = Door.frames[0]
class FlashSpell(Spell): """Заклинание молнии Бьёт через стены Эффективно против: Зеленых слизеней Неэффективно против: Зомби""" damage = 50 spell_type = Spell.FLASH mana_cost = 150 UPDATE_TIME = 60 speed = TILE_SIZE * 3 acceleration = 1 damage_frame = 2 action_time = 0 size = (TILE_SIZE // 2 * 2, TILE_SIZE // 2 * 5) frames = cut_sheet(load_image("assets/sprites/tiles/EMPTY.png"), 1, 1, size) frames += cut_sheet(load_image("assets/sprites/spells/light.png"), 15, 1, size) # Канал для звуков sounds_channel = pygame.mixer.Channel(3) # Звуки CAST_SOUND = load_sound("assets/audio/sfx/spells/cast_sound_4.ogg") SPELL_SOUNDS = ( load_sound("assets/audio/sfx/spells/spell_sound_3.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_4.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_5.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_6.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_7.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_8.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_9.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_10.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_11.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_14.ogg"), load_sound("assets/audio/sfx/spells/spell_sound_26.ogg"), )