Урок 21 SFML еще больше ООП, переход к созданию врага

На этом уроке еще больше переберемся под ООП ,  почистим int main ф-цию от лишних вещей, сделаем главный класс от которого унаследуем класс игрока, а потом и врага.

 

Видеоверсия: http://www.youtube.com/watch?v=R8Uml4XdXIw

Всё-таки я решил удалить лишний код в виде перемещения персонажа по клику мыши, и переменную isSelect, поскольку они характерны для стратегий. Еще вернемся к этому, пока что разберем более подробно тип игры платформер.

После чистки от всяких ненужностей функция int main имеет вид:

Слежение за камеры игроком (переименовал её на более логичное ‘set’) я перенес в функцию update класса игрока

Как видите функция int main уменьшилась в плане количества строк кода, её ещё сильнее можно уменьшить, убрав связанные с созданием и прорисовкой карты строки. Но делать этого не будем пока что. Займемся созданием класса врага.




Что общего между врагом и игроком? Очень многое: жизнь/смерть, здоровье, координаты у обоих есть, ширина,высота,функция обновления(update), столкновение с картой и многое многое другое. Нам ведь не нужно создавать класс врага и прописывать всё тоже самое для него , добавляя ещё больше лишних строк кода? Да, нам этого не надо и мы будем использовать наследование. Наследование даст нам те же переменные и функции классу потомку и мы сможем их использовать, не описывая при этом по новой по сути тоже самое. При этом мы можем переопределять те же функции, если функции родителя нас не устраивают. В частности мы затроним функцию update , поскольку поведение игрока (управление и тд) и врага (поведение врага в игре) совсем различны.

Итак, сейчас мой класс игрока выглядит вот так:

Как видите я убрал функции получения и задания ширины,высоты спрайта, а так же координат х и y (у нас была ф-ция getPlayerCoordinateX() например для взятия координаты “х” у спрайта). В дальнейшем я сделаю эти переменные приватными и мы сможем изменять их значение (или извлекать их значение) при помощи написанных set и get ф-ций.

Безусловно, вы можете использовать стандартные sfml функции для взятия, например координат (sprite.getPosition().x;) , или высоты (sprite.getTextureRect().height;), в дальнейшем я буду делать так же.

Итак, у нас куча объектов и они имеют общие черты, поэтому создадим один общий для них класс, от которого будем наследоваться. В этом классе и будут содержаться эти общие черты.

Функция управления персонажем control() ,безусловно, останется в классе игрока. У всех объектов: врагов, пулей, игрока и прочих – своя игровая логика и функция update для каждого из объектов будет разной.

Итак, создадим класс Entity , в котором и будут храниться общие поля и методы. Кстати, практически всю инициализацию мы будем делать в этом классе и нам не придется повторять строки инициализации в других конструкторах (врага, игрока и тд).

Класс Entity выглядит вот так:

Вот они – общие переменные в public. Все они будут доступны для других дочерних классов , наследуемых от Entity. Появилась переменная moveTimer, её будем потом использовать для реализации врага, или двигающейся платформы (которая каждые 3 сек меняет направление, например).

Важный момент с изображением (которое передается первым параметром в конструкторе) – мы передаем значение по ссылке. В функции main будем создавать изображения, например вот так:

И потом заряжать их в конструктор дочернего класса,в конструкторе дочернего класса оно передастся в конструктор класса родителя, а уже в конструкторе класса родителя (то есть Entity) оно закидывается в текстуру, затем в спрайт. Если бы мы передавали не по ссылке – каждый раз при создании нового врага создавалась бы копия этого изображения, что раздувало бы нам память. В нашем же случае, получается, что мы создаем изображение всего 1 раз.

Далее в конструкторе Entity принимаются такие параметры как: координаты появления объекта, ширина и высота объекта, и имя. Зачем нужно имя? Так мы сможем создавать различных врагов с разными изображениями и поведением и все они будут экземплярами класса Enemy. То есть нам не нужно делать отдельно класс HardEnemy, EasyEnemy, BossEnemy, и тд. Мы даём имя врагу и в зависимости от его имени реализуем внутри класса логику для этого врага. Аналогично с игроком – может быть два игрока и два имени – “Player1″ и “Player2″. Вобщем после всех этих параметров в конструкторе идет инициализация переменных. Эти переменные будут инициализированы сразу для всех дочерних классов при создании их экземпляров.




После создания класса Entity мы сможем удалить часть класса Player , поскольку многое уже перешло в Entity.

Класс Player выглядит теперь так:

Класс игрока на данный момент самый большой класс. Не удивительно, ведь последние 20 уроков мы только и делали, что прокачивали игрока:))

Всего две переменные имеет Игрок, этих переменных нет у других. Это очки игрока и состояние игрока. Причем состояние, возможно, придется перенести в будущем в Entity, ведь у нас могут быть крутые и не тупые враги, имеющие свою логику. Плюс каждому врагу проще будет давать анимацию, если он будет иметь эти состояния. Пока оставим так и не будем усложнять сейчас задачу.

В параметрах конструктора игрока мы имеем все то же изображение (именно то, которое загрузили один раз в int main), координаты и размеры будущего спрайта. Затем мы передаем их в базовый класс (класс родитель) Entity. Там уже эти переменные инициализируются и изображение запиливается в спрайт.
В самом конструкторе, внутри, для первого игрока делаем прямоугольник спрайта. Ведь если у нас будет второй игрок – у него может быть другой спрайт и другие размеры. Вот это и прописали. (4 и 19 в самом начале, это координаты в картинке, с которых мы начинаем рисовать). Я взял другой спрайт вместо того льва, теперь у меня спрайт знаменитого Лисёнка из Соника. Вот он:

спрайт героя
спрайт героя

 

С этим понятно, теперь разберем функции игрока или как ещё говорят – методы.

в control() происходит управление, (я убрал пока анимацию и увеличил высоту прыжка). Эту функцию мы потом вызываем в update, что делает её всегда активной.

Далее идёт та же функция обработки коллизий (столкновений) с картой.

Затем функция update, в которой мы вызываем эти функции. Именно функцию update мы вызываем в функции int main() внутри цикла “пока открыто окно”.

Теперь остался враг. У врага тоже много общего между игроком. Даже конструктор такой же. Смотрим как враг выглядит:

Многое похоже, а местами и тоже самое. Смотрите, в конструкторе я даю начальную скорость врагу с именем “EasyEnemy”, это будет тупой враг, который меняет своё направление влево-вправо.

Реализация того самого влево-вправо может быть разной. Сейчас я сделал так, что враг изменяет свое направление , когда столкнется со стеной. Это видно в функции checkCollisionWithMap – при столкновении с правой стеной он едет влево, с левой – едет вправо. А sprint,scale(-1,1) это отражение спрайта по горизонтали в момент столкновения со стеной. Эту функцию можно использовать в таких случаях (то есть когда она 1 раз выполняется при определенном условии). Но если напишете её в update – спрайт будет влево вправо быстро танцевать, а значит эта функция тут не подойдет. Мы знаем из первых уроков, что отражать спрайт можно при помощи setTextureRect.

Ну а в функции update мы реализуем (пока что простую) логику для врага. Кстати вы видите там в комментарии есть второй вариант реализации движения влево-вправо врага , а именно по таймеру. То есть он будет менять свое направление через заданный промежуток времени. Можно делать и так тоже.

Все, теперь у нас есть три класса и когда мы создадим, например, пули или ещё что – они так же будут дочерними от класса Entity.

Давайте загрузим изображение для нашего врага:

shamaich sfml
shamaich sfml by kychka-pc.ru

Закинем наши новые два изображения в ту же папку images , что и ранее.

теперь в функции int main до цикла пока открыто окно, напишем:

Мы загрузили два изображения. Теперь необходимо их передать в конструктор класса по ссылке. Если передать по значению, то в памяти выделится место под ещё два таких изображения.

Сразу ниже создадим игрока и врага:

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

И осталось только нарисовать наши объекты:

 

Я изменил карту в map.h на такую:

 

Более подходит для нашего полигона испытаний.

Результат работы программы:

урок 21 sfml
урок 21 sfml

 

 

Мы знаем, что врагов может быть много, что же нам, каждый раз создавать новый объект в int main еще и с разным именем? Враги должны быть динамическими, об этом мы поговорим на следующих уроках. Взаимодействие с врагом так же рассмотрим на следующих уроках.

Вопросы на форум (В соответствующий раздел по sfml – Simple and fast multimedia library)

Кстати, можете раскидать классы по разным файлам

Код урока:

main.cpp

 

map.h

 

 

 

Буду благодарен, если поделитесь:
SFML вопросы, прошу, задавайте на форуме.
  1. Павел здравствуйте. Подскажите пожалуйста. Если персонаж приседает, чтобы пройти под препятствием. Как сделать чтобы размер его прямоугольника уменьшился и как изменить столкновения с препятствием, если он вдруг встанет под препятствиями?

Добавить комментарий