Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!

Приветствую, человеки и прочие расы фентезийного болота. В первой статье мы познакомились с движком Godot, сделали простенькую карту и настроили управление персонажем.

Настало время сделать физику еще более беспощадной стервой, а так же оживить нашего персонажа при помощи анимации. А еще мы добавим любимое развлечение графа Дракулы - колья!

Пока что у нас есть вот это нечто...
Пока что у нас есть вот это нечто...

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

Этот гад способен менять свое направление прямо в воздухе и к примеру развернутся на пол пути к пропасти. В каких то платформерах это нормально и является частью игровой механики, но у нас игрок будет СТРАДАТЬ!

"Игрок может двигаться по оси X (влево-вправо) только находясь на земле"
"Игрок может двигаться по оси X (влево-вправо) только находясь на земле"

Мы внесем правки в нашу функцию передвижения влево-вправо. На данный момент функция активируется если нажата одна из кнопок направления. Добавляем условие, что игрок может передвигаться в этих направлениях только находясь на земле.

func handle_acceleration (input_axis, delta):

if is_on_floor() and input_axis !=0 :

velocity.x = move_toward(velocity.x, SPEED * input_axis, ACCELERATION * delta)

Теперь если игрок неверно рассчитал расстояние для прыжка - сменить направление полета в воздухе он уже не сможет... 
Теперь если игрок неверно рассчитал расстояние для прыжка - сменить направление полета в воздухе он уже не сможет... 

Казалось бы, на этом можно закончить, но есть нюанс.

Игроку нужно сначала взять хотя бы минимальный разбег в нужную сторону, чтобы избежать прыжка на одном месте. Грубо говоря, он должен сперва нажать клавишу "ВПРАВО", а уже потом "ПРОБЕЛ" иначе не сработает. Это неудобно, особенно когда нам нужно запрыгнуть на выступ находясь у стены. Дело поправимое, залазим обратно в эту же функцию и дополняем ее.

func handle_acceleration (input_axis, delta):

if is_on_floor() and input_axis !=0 :

velocity.x = move_toward(velocity.x, SPEED * input_axis, ACCELERATION * delta)

elif not is_on_floor() and input_axis != 0 and velocity.x == 0:

velocity.x = move_toward(velocity.x, SPEED * input_axis, ACCELERATION * 10 * delta)

Понимаю, выглядит сложновато, потому переведу на человечий.

Передвижение влево-вправо доступно только при нахождении на земле. Если игрок нажал сначала "ПРОБЕЛ", то произойдет прыжок на месте. Сдвинуться по оси X (влево-вправо) он уже не сможет, так как находится не на земле.

Нам нужно определить, находится ли персонаж в состоянии прыжка - это not_is_on_floor()

Раз происходит прыжок на месте, то ось X остается неизменной - это velocity.x == 0

Наша попытка передвижения вызывается нажатием клавиши направления - это input_axis !=0

Если все три условия соблюдены, то мы позволяем совершить передвижение по оси X, однако произойдет оно только один раз и только в одном из выбранных направлений. Все потому, что в следующем кадре значение velocity.x уже не будет равно нулю и данное условие перестает работать. Чтобы этот одноразовый рывок имел смысл, нам нужно увеличить значение ACCELERATION. Можем сделать для этого отдельную переменную, но я обошелся простым умножением на 10.

Что мы получили по итогу?

Прыжок с места является коротким - этого хватит чтобы запрыгнуть на выступ или преодолеть небольшое препятствие. Однако наличие разбега способно увеличить расстояние прыжка. Но какой бы прыжок не выбрал игрок - изменить направление полета в воздухе он уже не сможет.

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

Самое популярное препятствие в платформерах - это пики точеные!

Рисуем пики 16 х 16 в любом удобном графическом редакторе...
Рисуем пики 16 х 16 в любом удобном графическом редакторе...
Путь: Рендеринг - Текстуры - Default Texture Filter;
Путь: Рендеринг - Текстуры - Default Texture Filter;

Нарисовав препятствие - загружаем его в ресурсы проекта. Создаем новую сцену "Spikes" и добавляем дочерний узел "Sprite2D". Как подцеплять к нему текстуру мы проходили в первой статье - советую почитать если еще незнаком. Чтобы наша картинка не замылилась ввиду ее пиксельного стиля - поменяем текстурный фильтр на Nearest.

Узел "Area2D" и раздел "Collision"...
Узел "Area2D" и раздел "Collision"...

Area2D - куда тебя ведать?

Создаем еще одну сцену (не узел) и за ее основу берем "Area2D" (вот это уже узел). Данная штука нужна чтобы обнаруживать пересечения объектов. У многих объектов есть такой раздел как "Collision", а в нем слои и маски.

Для того, чтобы было проще понять - объясню на примере:

Слой - это область, где данный объект МОГУТ увидеть другие объекты и взаимодействовать с ним.

Маска - это область, где данный объект МОЖЕТ увидеть другие объекты и взаимодействовать с ними.

Например, мы поставим объект "StaticBody2D" из которого состоит наша карта на первый слой. "CharterBody2D" который является нашим игроком мы поместим на второй слой.

Если у "CharterBody2D" в разделе маска мы укажем второй слой вместо первого, на котором находится "StaticBody2D" - наш персонаж его просто не увидит. А это значит, что игрок просто провалится в пол.

Объект World находится на первом слое, объект Player на втором. Маска Player видит объекты лишь на втором слое.
Объект World находится на первом слое, объект Player на втором. Маска Player видит объекты лишь на втором слое.
Из-за этого зоны столкновения у объектов Player и World не взаимодействуют и мы проваливаемся вниз.  
Из-за этого зоны столкновения у объектов Player и World не взаимодействуют и мы проваливаемся вниз.  
Если выставить у Player маску первого слоя - он начнет его видеть и взаимодействовать с ним. По этой причине мы вновь твердо стоим на поверхности.
Если выставить у Player маску первого слоя - он начнет его видеть и взаимодействовать с ним. По этой причине мы вновь твердо стоим на поверхности.

Теперь, когда мы знаем для чего нужен данный узел - пора сделать точеные пики действительно опасными. Заходим в сцену и выбрав узел Area2D переименовываем в "HazardArea". В разделе "Collision" выставляем третий слой, а вот маску оставляем пустой. Наши пики никого не должны искать - они сами выступают объектом поиска.

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!

Теперь переходим в сцену Spikes и нажимаем на кнопку добавления дочерней сцены. Она находится рядом с кнопкой добавления узла. Выбираем нашу сцену "HazardArea2D". Добавляем для "HazardArea2D" два дочерних узла "CollisionPolygon", определяя область столкновения.

Кнопка справа от "+"...
Кнопка справа от "+"...
Можно создать область для одной пики и просто скопировать ее...
Можно создать область для одной пики и просто скопировать ее...

Теперь мы переходим в сцену "Player" и создаем дочерний узел "Area2D", называем его "HazardDetecter". Уже к этой области привязываем дочерний"CollisionShape". В узле "HazardDetecter" выбираем раздел "Collision" и оставляем графу слоя пустой. Но в разделе маски и выбираем третий слой - именно в нем мы будем искать область опасности "HazardArea".

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!
Графа слоя - пустая, графа маски - выставлена на третий слой...
Графа слоя - пустая, графа маски - выставлена на третий слой...

Осталось настроить сигналы... вы еще не знакомы с ними?

У многих узлов есть доступные сигналы и узел Area2D - не исключение. Посмотреть доступные сигналы можно в колонке справа, перейдя в раздел "Узел". Нас в первую очередь интересует сигнал "area_entered", что в переводе на человеческий означает "Обнаружена область".

В графе маска указан третий слой и именно на нем находится "HazardArea". Когда наш узел "HazardDetecter" обнаружит другой узел типа Area2D - сигнал сработает. Но что нам делать с полученным сигналом?

Сделаем простенькую систему смерти и возрождения!

Свет, камера - мотор!
Свет, камера - мотор!

Для начала добавим камеру, которая будет следовать за нашим персонажем. В разделе "PositionSmoothing" выставим "Speed" на 5 пикселей в секунду. Эта небольшая задержка сделает движение камеры более плавным.

Теперь нам потребуется зафиксировать точку для возрождения. Ей будет выступать стартовая позиция с которой мы начинаем игру. Создаем переменную стартовой позиции и при запуске игры закладываем в нее координаты начального положения игрока.

@onready var starting_position = global_position

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!

Теперь переходим в узел "HazardDetecter" к разделу сигналов и ищем "entered_area". Нажимаем правой кнопкой мыши, тыкаем присоединить и выбираем наш объект "Player".

Это создаст в скрипте новую функцию, под которой мы пишем всего одну строку.

global_position = starting_position

Присоединение сигнала к узлу...
Присоединение сигнала к узлу...
"Если будет обнаружена Area2D - позиция игрока вернется на стартовую"
"Если будет обнаружена Area2D - позиция игрока вернется на стартовую"

Остается только расставить наши точеные пики по карте и мы можем смело отлетать на стартовую точку при столкновении с ними!

Вот они слева направо - пик, кол, штык, клык и острие...
Вот они слева направо - пик, кол, штык, клык и острие...
Разбежавшись прыгну со скалы...
Разбежавшись прыгну со скалы...
Вот я был и вот я у респауна...
Вот я был и вот я у респауна...

Остался чисто косметический момент, а именно - анимация персонажа. 2D сеты анимации вы можете легко откапать на просторах интернета, я же расскажу вам как их правильно настроить.

Заменяем узел спрайта на "AnimatedSprite2D" и в разделе "Sprite Frames" создаем новый. Перед вами откроется окно кадровой анимации, куда вы и будете загружать кадры.

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!
Стой, прыгай и беги...
Стой, прыгай и беги...

Создаем три анимации - "idle", "jump" и "run". Первые две состоят из одиночных спрайтов, так как это статичные позиции. А вот для бега я решил добавить три спрайта. В окошке с FPS мы устанавливаем частоту кадров при анимации, в нашем случае поставили 12.

Теперь переносим зажав ctrl узел "AnimatedSprite2D" в наш код в виде переменной и создаем функцию update_animation.

func update_animation (input_axis, delta):
if velocity.x == 0:
animated_sprite_2d.play("idle")
if input_axis !=0:
animated_sprite_2d.flip_h = (input_axis < 0)
animated_sprite_2d.play("run")
if not is_on_floor():
animated_sprite_2d.play("jump")

Снова переведу для вас на человечий язык:

Если мы стоим на месте - запустить анимацию стоячего положения;

Если нажата одна из кнопок движения, то мы проверяем направление. Если нажата кнопка "влево" - то зеркально разворачиваем объект анимации. Проигрываем анимацию бега.

Если мы не на земле - запускаем анимацию прыжка.

Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!
Добавьте функцию в список под phisics_process
Добавьте функцию в список под phisics_process
Теперь наш персонаж действительно ожил...
Теперь наш персонаж действительно ожил...

Наконец-то мы получили подобие игры!

У нас есть анимированный персонаж подчиняющийся законам физики, карта по которой он передвигается и даже опасности, которые могут его убить и отправить на старт.

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

Продолжим доводить данный проект до ума в следующей статье! А пока что подписывайся на мой блог и оставь пару комментариев. Тебе не сложно - а мне поможет!

По традиции гоблин от нейросети в конце!
По традиции гоблин от нейросети в конце!
88
Начать дискуссию