Монстры

Для начала, я прописал референсы для монстры. Они хранятся в папке graphic/monsters. Также я добавил звуки ударов и закинул их в папку audio/attack Далее, в setting.py прописаны их входные данные:

monster_data = {
    'axalot': {'health': 200, 'exp': 400, 'damage': 40, 'attack_type': 'slash', 'attack_sound': '../audio/attack/slash.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 300},
    'lizard': {'health': 50, 'exp': 100, 'damage': 15,'attack_type': 'claw',  'attack_sound': '../audio/attack/claw.wav', 'speed': 2, 'resistance': 3, 'attack_radius': 100, 'notice_radius': 400},
    'snake': {'health': 100,'exp':100,'damage': 10,'attack_type': 'claw', 'attack_sound': '../audio/attack/claw.wav', 'speed': 4, 'resistance': 3, 'attack_radius': 80, 'notice_radius': 350},
    'spirit': {'health': 150,'exp':200,'damage': 15,'attack_type': 'claw', 'attack_sound': '../audio/attack/claw.wav', 'speed': 3, 'resistance': 3, 'attack_radius': 100, 'notice_radius': 400}}

Пробежимся по параметрам:

  • health — здоровье монстра

  • exp — сколько очков за смерть монстра

  • damage — какой урон он нанесёт герою

  • attack_type — тип атаки

  • attack_sound — звук удара

  • speed — скорость зверька

  • resistance — на сколько монстр отлетит после нашего удара

  • attack_radius — радиус, с которого монстр опасен и может ударить

  • notice_radius — радиус зрения монстра

Далее, создадим новый файл сущностей (entity.py) и добавим туда супер демона с наследованием групп:

import pygame

class Entity(pygame.sprite.Sprite):
    def __init__(self, groups):
        super().__init__(groups)

Скопируем методы move и collision из player.py. Теперь удаляем из player.py эти методы и будем ссылаться в классе не на pygame.sprite.Sprite, а на Entity (не забывайте импортировать файл). Эти небольшие танцы с бубном нужны для того, чтобы не переписывать каждый раз правила движений для нашего героя и монстров. Все они — одинаковые сущности. Также я перенёс демонов скорости анимации, фрейма и определения вектора скорости. Когда всё сделаете, перепроверьте, что всё работает.

Затем, наконец, создадим файл enemy.py:

Перейдём к настройке уровня. Для начала, сделаем так, чтобы наш герой спаунился там, где надо (зелёный квадрат на карте). Для этого, нам нужно импортировать новый csv-файл из уже созданной карты 'entities': import_csv_layout('../map/map_Spawn.csv'). И пропишем в методе creat_map новый объект:

Результат вас удивит:

Герои Хайрула размножились. Проблема в том, что я прорисовывал карту разными наборами элементов. Не повторяйте моих ошибок и давайте всё исправлять. Дело в том, что номер тайла автоматически рассчитывается программой Tiles, а я указал, разные тайлы и номера задублировались, так как у меня был отдельный файл Bebs.png для спауна врагов и Link_and_block.png для Линка и блоков-стен. Теперь я объединил два набора тайлов и присвоил линку номер 16. Результат:

Линк на своём законном месте. Давайте заспаумим врагов, добавив лишь один else:

Монстры отобразились, но теперь нужно отобразить их верно. Работаем с файлом enemy.py:

Тут мы делаем всё ровно также, как и ранее, но если в файле level.py мы внесём имя любого монстра, то получим картинку монстра на карте:

Осталось только перебрать монстров по их номерам тайлов на карте:

Теперь, добавим аргумент obstacle_sprites в нашу конструкцию, чтобы монстры могли взаимодействовать с Линком. Далее, создадим update-метод для файла enemy.py:

Далее, нужно прописать несколько статусов для наших плохишей. Добавим их в демонов данного файла:

Тут мы ссылаемся на файл settings.py и перехватываем все параметры монстров оттуда. Теперь наша задача прописать метод определения дистанции до объекта. Я думал, что эта задача непроста, так как координаты объекта рассчитываются с верхнего левого угла, у них есть свои векторы (скорости), да ещё и нужна нормализация для предотвращения "диагонального чита" (как это было у Линка). Собственно весь метод:

Я искренне не ожидал, что это так просто. По сути, все сложные методы вычисления Евклидовой величины по поиску дистанции мы переложили на функцию magnitude(), а с читерской функцией normalize() вы уже знакомы. И зачем я учил математику? Далее, пропишем метод определения статуса монстра по отношению к Линку:

Тут мы отсекаем изнутри во вне "окружности" зрения (близко — атака, средняя дистанция — преследование, далеко — idle), но чтобы оно заработало, нам нужно обновлять данные в файле level.py:

Тут самая интересная строка — строка прорисовывания спрайтов для врага. Тут можно как в анекдоте: "Потерялся атрибут? Ничего страшного! Всегда есть метод hasattr". Далее, в run-методе пропишем отрисовку спрайтов врага:

Теперь мы сможем замкнуть врага на игрока, а игрока на уровень. Для этого пропишем новый метод в enemy.py:

Теперь, у нас есть способ получения методов, но мы с ними не взаимодействуем. Исправим это новым методом:

В этом методе всё так же, как было ранее, но не забудьте закинуть его вызов в enemy_update-функцию командой self.actions(player). Тетерь пропишем анимацию. Она полностью аналогична анимации Линка:

Также, добавьте animate в update-функцию. Теперь можно получить ачивку: "Собрал всех чушпанов с района":

Но есть проблема. Они атакуют несчастного Линка толпой без остановки. Это нужно исправить, а значит время нового метода и нового кулдауна. Сначала я добавил нового демона self.can_attack = True. Это флаг, который будет указывать на то, что монстр может пнуть Линка. Соответственно, нужно подправить условие атаки и помимо дистанции, указать данный флаг. Если вы добавили флаг на True, то обязательно сразу нужно прописать ситуацию, когда он будет опускаться (положение False). Запишем этот пункт в методе анимации:

Немного объясню происходящее. Анимация атаки не должна прерывать анимацию перехода и если мы завершили весь цикл из переходов от картинки к картинке, то только тогда можно менять флаг на опущенное состояние. Простыми словами, все враги могут ударить нас только 1 раз, так как флаг не поднимается обратно. Поднимать тот самый флаг мы будем по кулдауну через паузу. То есть, я добавлю два демона, которые будут обозначать время атаки и кулдаун после атаки:

Теперь пропишем сам метод кулдауна по вычислению разницы текущего времени и времени задержки:

Тут самое главное, не забыть про место старта времени, то есть про установку времени на момент атаки:

После этого, не забудьте закинуть метод в update-метод. Результат:

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

Last updated