Для начала, я прописал референсы для монстры. Они хранятся в папке graphic/monsters. Также я добавил звуки ударов и закинул их в папку audio/attack Далее, в setting.py прописаны их входные данные:
Скопируем методы 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-метод. Результат:
Мы не закончили работу с монстрами, но давайте оставлю бэкап проекта сейчас и в следующей части создадим методы взаимодействия нас с монстрами и монстров с нами.
import pygame
from settings import *
from entity import Entity
from support import *
class Enemy(Entity):
def __init__(self, monster_name, pos, groups):
super().__init__(groups)
self.sprite_type = 'enemy' #новый тип спрайтов — враги
self.import_graphics(monster_name) #обращаемся к новой функции перебора картинок
self.status = 'idle' #установим базовый статус
self.image = self.animations[self.status][self.frame_index] #перебираем номер фрейма в папке из функции ниже
self.rect = self.image.get_rect(topleft = pos) #традиционная отрисовка
def import_graphics(self, monster_name):
self.animations = {'idle': [], 'move': [], 'attack': []} #перебираем возможные варианты анимаций в папках
main_path = f'../graphic/monsters/{monster_name}/' #обращаемся к монстру по имени :)
for animation in self.animations.keys(): #перебираем все картинки
self.animations[animation] = import_folder(main_path + animation) #перебор благодаря support-файлу
else:
if col == '0':
monster_name = 'axolot'
elif col == '4':
monster_name = 'lizard'
elif col == '8':
monster_name = 'snake'
else:
monster_name = 'spirit'
Enemy(monster_name, (x, y), [self.visible_sprites])
def enemy_update(self, player):
enemy_sprites = [sprite for sprite in self.sprites() if hasattr(sprite,'sprite_type') and sprite.sprite_type == 'enemy']
for enemy in enemy_sprites:
enemy.enemy_update(player)