def initialise_level(level_map, level, all_sprites, tiles_group, furniture_group, barriers_group, enemies_group, doors_group, torches_group, end_of_level, monster_seed, boxes_seed, player): """ Функция для инициализации уровня Проходит по переданной ей карте уровня и на каждый символ карты создает тайл и что-то на нем, если есть Если передается сид, монстры будут такие, как записано в сиде Разные тайлы пола, предметов на уровне (бочек, коробок) будут всегда одинаковые :param level_map: Уровень :param level: Номер уровня, нужен для расчета сложности :param all_sprites: Группа со всеми спрайтами :param tiles_group: Группа со спрайтами плиток пола :param furniture_group: Группа со спрайтами ящиков на уровне :param barriers_group: Группа для спрайтов с тайлами, сквозь которые нельзя ходить :param enemies_group: Группа врагов :param doors_group: Группа дверей :param torches_group: Группа с факелами :param end_of_level: Группа тайла лестницы вниз, при касании с которым произойдет переход на следующий уровень :param monster_seed: Сид, по которому будут расставлены монстры :param boxes_seed: Сид, по которому будут расставлены бочки :param player: Если он передан, просто перемещаем его на новое место, а иначе создаем нового :return player: Игрок, размещённый в нужном месте :return monster_seed: Враги, размещённые в нужном месте :return boxes_seed: Ящики, размещённые или нет в нужном месте """ new_monster_seed = [] new_boxes_seed = [] level_map = [list(i) for i in level_map] # Установка общих физических объектов для всех сущностей Entity.set_global_groups(barriers_group, all_sprites) # Установка общих физических объектов для всех заклинаний Spell.set_global_collisions_group(barriers_group) Spell.set_global_breaking_group(doors_group, furniture_group) for y in range(len(level_map)): for x in range(len(level_map[y])): if level_map[y][x] in 'PMF.BCrbltT': # объединим те, в которых надо спавнить пол if true_with_chance(CRACKED_FLOOR_CHANCE): Tile(choice(['.0', '.1', '.2', '.3']), x, y, all_sprites, tiles_group) else: Tile('.', x, y, all_sprites, tiles_group) if level_map[y][x] == 'P': # ИГРОК # Помещаем игрока в центр текущего тайла if not player: player = Player(x * TILE_SIZE + TILE_SIZE * 0.5, y * TILE_SIZE + TILE_SIZE * 0.5, level, all_sprites) for _ in range(1): player.add_assistant(PlayerAssistant(-10000, -10000, player, all_sprites)) else: player.start_position = (x + 0.5) * TILE_SIZE, (y + 0.5) * TILE_SIZE player.rect.center = (x + 0.5) * TILE_SIZE, (y + 0.5) * TILE_SIZE Tile(level_map[y][x], x, y, all_sprites, tiles_group) elif level_map[y][x] == 'M': # МОНСТР random_monster(x, y, level, all_sprites, enemies_group, new_monster_seed, monster_seed) elif level_map[y][x] == 'B': # МЕБЕЛЬ (бочки) if boxes_seed: n = int(boxes_seed.pop(0)) else: n = randint(0, 3) index = len(new_boxes_seed) if n == 1: Furniture('B1', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) elif n == 2: Furniture('B2', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) elif n == 3: Furniture('B3', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) new_boxes_seed.append(str(n)) elif level_map[y][x] == 'C': # СУНДУКИ if boxes_seed: n = int(boxes_seed.pop(0)) else: n = [randint(1, 3), 4][true_with_chance(80)] index = len(new_boxes_seed) if n == 4: Chest(x, y, new_boxes_seed, index, all_sprites, barriers_group) elif n == 1: Furniture('B1', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) elif n == 2: Furniture('B2', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) elif n == 3: Furniture('B3', x, y, new_boxes_seed, index, all_sprites, furniture_group, barriers_group) new_boxes_seed.append(str(n)) elif level_map[y][x] in 'ltT': # ДВЕРИ И ФАКЕЛА if level_map[y][x] == 'l': Door(x - 0.5, y, all_sprites, doors_group) elif level_map[y][x] == 't': Door(x, y - 0.5, all_sprites, doors_group) elif level_map[y][x] == 'T': Torch(x + 0.12, y, all_sprites, torches_group) elif level_map[y][x] in '1234567890-=': Tile(level_map[y][x], x, y, all_sprites, barriers_group) elif level_map[y][x] == 'E': Tile('E', x, y, all_sprites, tiles_group, end_of_level) elif level_map[y][x] != ' ': Tile(level_map[y][x], x, y, all_sprites, tiles_group) # вернем игрока и сид монстров return player, new_monster_seed, new_boxes_seed
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