Урок 28 SFML C++ Стрельба (с клавиатуры), пули

На этом уроке научим персонажа стрелять. Для начала рассмотрим какой может быть стрельба. Герой может стрелять пулями (“очередями”, прерывистая стрельба) или лазером (сплошная, постоянная стрельба). Сегодня будем работать с пулями, так как они пользуются наибольшим спросом. Давайте разберемся – куда же может лететь пуля и по какой траектории?

Видеоверсия: http://youtu.be/Db04mWPtooM

Пуля мало чем отличается от платформы или врага, ну а чем? Летит себе со скоростью при появлении и исчезает/умирает при столкновении с чем-либо. Просто нужно дать ей начальную скорость и направление, и прописать, что если она столкнулась со стенкой (или врагом, но это позже), то её life = false.

Пуля может лететь сонаправлено взгляду (или движению) персонажа, то есть в ту сторону, в которую он смотрит (двигается). А может лететь и в сторону курсора (то есть прицела), а её направление может и управляться с клавиатуры , то есть если персонаж идёт вправо, он может стрельнуть влево (типа спиной вперед).

Давайте рассмотрим случай, когда выстрел происходит с помощью клавиатуры в сторону движения/взгляда персонажа. Мы будем стрелять вверх, влево, вправо. Так же сделаем пример стрельбы по диагонали – вправо вверх. Можно стрелять в место клика курсора и это тема следующего урока, но вы же знаете, что достаточно склеить знания из этого урока и урока №18 (только вместо спрайта героя из того урока будет пуля).

Есть у нас два пути. Первый – добавление переменной направления пули для героя (класса Player). Оно (направление) будет совпадать с направлением движения героя, но вы можете сделать так, чтобы не совпадало (просто надо реализовывать лишнее управление для прицеливания в другую сторону) и это будет второй путь. Мы пойдем по первому и обойдем создание переменной направления для общего развития, так скажем. Что я имел ввиду – узнаете позже, давайте сейчас создадим класс пули.

Пули (Bullets):

Создадим класс пули, он похож на класс платформы или врага, поэтому так же будет унаследован он Entity и при этом храним всё в том же списке entities.

Ничего нового не произошло. Только лишь в конструкторе последним параметром мы передаём состояние персонажа state. Помните, у игрока есть состояние и оно перечисляемого типа: enum { left, right, up, down, jump, stay } state; Мы передаём  это состояние в класс пули, чтобы знать в какую же сторону ей лететь при создании, принимаем это состояние типом int и таким образом сможем обращаться к индексу этого состояния. left = нулевой индекс, stay имеет индекс 5.

Строка if (x <= 0) x = 1 спасает пулю от вылета за предел карты, например когда фпс игры неожиданно просядет и игра пропустит кадр с проверкой на столкновение пули с solid объектом, но тут же пуля вернется как бы назад. Такую перестраховку сделали пока с левым и верхним краями карты, для демонстрации. В целом мы ведь не знаем размеры своей карты по ширине и высоте, пока игру до конца не допилим.

Класс пули сделали, теперь надо найти для нее картинку. Я буду использовать вот такую для теста:bullet

маленькая пулька 16*16. Сделаю маску по черному цвету для неё, но лучше иметь прозрачную пульку – это минус одна строка кода и картинка весит меньше. Нам для урока и теста сойдёт вот эта :)

Загрузим для нашей пули картинку там же , где и для остальных изображений (у меня прям сразу после картинки платформы) :

Теперь нужно создать пулю – занести её в список. Да и всё, потому что объекты списка entities уже обновляются, рисуются и умирают. Пульки будем создавать в событиях. Когда нажимаете на клавишу выльется целая куча пуль, поэтому нужно обрабатывать в цикле событий.

Первый вариант – в функции control() класса Player описать нажатие кнопки стрельбы (создаем bool переменную isShoot и активируем её, если нажали пробел например). И потом в цикле событий впилить вот такой код:

В цикле событий именно потому, что если вы напишите “если нажат пробел, то стрельнуть” вне цикла событий, то пули будут лететь не успеете посчитать :)) Кстати если нужно , чтобы персонаж стрелял постоянно (а такое тоже бывает) , то просто убрать условие для проверки на нажатие пробела.

Другой вариант – обработать нажатие клавиши P (пусть это будет ещё одна кнопка стрельбы) в цикле событий с помощью sfml функции:

Первый вариант лучше с точки зрения логики приложения (потому что управление у нас уже описано ранее, зачем описывать где то ещё?). Вобще в int main и main.cpp в целом получилось значительное количество нагромождений, лучше будет – если вы всё это рассортируете и уменьшите int main ().




Пример со стрельбой в 4 направления готов.
Давайте стрельнем по диагонали вправо вверх:

Пойдем по пути добавления еще одного enum state состояния right_Top, которое будет активировано , если нажаты клавиши вправо+вверх. и в control() класса игрока допишем:

Хотелось бы отметить, что нам пришлось добавить одно состояние right_top из-за текущей логики в update персонажа. Если требуется большое количество состояний как сейчас, то нужно отделить направление персонажа и его состояние, то есть просто сделать состояние “walk”, а куда он идёт уже решает другая переменная. Я не планировал, что лисёнок озвереет и будет стрелять, поэтому по привычке с enum использовал switch (частая связка , особенно в java). Вобщем допиливать еще одну переменную ради этого сейчас не будем, просто знайте. Кстати говоря, если наш герой всё таки будет стрелять вправо вверх и при этом должен стоять на месте, то это полюбому придется реализовывать, потому что сейчас он стреляет вправо вверх и при этом подпрыгивает , т.к срабатывает ещё и состояние jump. Наши прошлые состояния можно оставить в том случае, если мы будем стрелять лишь в два направления (вправо или влево), или же по клику курсора мыши. Как видите – чем толще игра , тем больше нужно вводить таких вот логических ограничений. Отступление получилось слишком большим, но я надеюсь вы поняли, что я хотел до вас донести.

Вправо вверх в платформере идти мы не можем, во всяком случае пока в нашей игре такого не предвидится , поэтому при этом состоянии мы просто будем идти вправо:

Индекс этого состояния равен [6] шести , поэтому в update у пули в переключателе направлений допишем вот такую штуку:

т.е пуля полетит вправо вверх, если нажата клавиша вправо и вверх. Аналогичным образом можно сделать и для других направлений.

Суть урока – научиться стрелять, а такие тонкости продумываете сами или спрашивайте на форуме.

На следующем уроке стрельнем в сторону клика курсора.

 

main.cpp урока

 

 

 

 

 

 

 

 

 

 

Буду благодарен, если поделитесь:
SFML вопросы, прошу, задавайте на форуме.
  1. Спасибо, Павел! То, что надо 😉 А можно надеяться увидеть урок по созданию меню игры? А заодно по разворачиванию её на весь экран. Хотя, как мне кажется, на весь экран развернуть не тянет на отдельный урок.

    1. Можно надеяться, такой урок планируется в будущем (только когда это будет – не знаю.. )
      насчет развернуть на весь экран это как пример доп параметра рассматривалось
      вот тут
      https://kychka-pc.ru/sfml/urok-2-razbor-testovogo-koda-osnovnoj-princip-raboty-sfml-obyazatelnye-funkcii-sfml.html

      про fullscreen пара слов поищи в уроке

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

  2. На размышление

    Я начал анализировать библиотеку SFML, и наткнуля вот на какую фигню:

    x и y можно найти вот так

    w и h можно найти вот так

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

  3. Верно.

    float x = sprite.setPosition().x; // возвращает положение спрайта по X
    float y = sprite.getPosition().y; // возвращает положение спрайта по Y
    Можно было не писать float x и float y а просто закинуть в Vector2f = sprite.getPosition();
    А с помощью image.getSize ты получишь размер изображения, а не спрайта.
    Размер спрайта можно узнать и с помощью sprite.getTextureRect().width – как то примерно так, писал по памяти.
    просто работу с векторами в уроках не рассматривали и людям проще вникнуть для начала именно таким образом, моё мнение такое.

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