Продолжаем изучение Godot на примере 2D платформера! Анимация, настройка камеры и усложнение физики!
Приветствую, человеки и прочие расы фентезийного болота. В первой статье мы познакомились с движком Godot, сделали простенькую карту и настроили управление персонажем.
Настало время сделать физику еще более беспощадной стервой, а так же оживить нашего персонажа при помощи анимации. А еще мы добавим любимое развлечение графа Дракулы - колья!
Наш подопечный уже научился ходить в разные стороны, а так же совершать прыжки. База для любого платформера - это гравитация, а потому нашего болванчика тянет вниз. Но есть проблема...
Этот гад способен менять свое направление прямо в воздухе и к примеру развернутся на пол пути к пропасти. В каких то платформерах это нормально и является частью игровой механики, но у нас игрок будет СТРАДАТЬ!
Мы внесем правки в нашу функцию передвижения влево-вправо. На данный момент функция активируется если нажата одна из кнопок направления. Добавляем условие, что игрок может передвигаться в этих направлениях только находясь на земле.
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.
Что мы получили по итогу?
Прыжок с места является коротким - этого хватит чтобы запрыгнуть на выступ или преодолеть небольшое препятствие. Однако наличие разбега способно увеличить расстояние прыжка. Но какой бы прыжок не выбрал игрок - изменить направление полета в воздухе он уже не сможет.
Для чего обычно вводят такую механику? Для повышения ценности действий игрока. Зная, что нельзя повернуть назад в полете игрок будет куда осторожнее вымерять расстояние для прыжка над опасностью. К слову об опасности...
Самое популярное препятствие в платформерах - это пики точеные!
Нарисовав препятствие - загружаем его в ресурсы проекта. Создаем новую сцену "Spikes" и добавляем дочерний узел "Sprite2D". Как подцеплять к нему текстуру мы проходили в первой статье - советую почитать если еще незнаком. Чтобы наша картинка не замылилась ввиду ее пиксельного стиля - поменяем текстурный фильтр на Nearest.
Area2D - куда тебя ведать?
Создаем еще одну сцену (не узел) и за ее основу берем "Area2D" (вот это уже узел). Данная штука нужна чтобы обнаруживать пересечения объектов. У многих объектов есть такой раздел как "Collision", а в нем слои и маски.
Для того, чтобы было проще понять - объясню на примере:
Слой - это область, где данный объект МОГУТ увидеть другие объекты и взаимодействовать с ним.
Маска - это область, где данный объект МОЖЕТ увидеть другие объекты и взаимодействовать с ними.
Например, мы поставим объект "StaticBody2D" из которого состоит наша карта на первый слой. "CharterBody2D" который является нашим игроком мы поместим на второй слой.
Если у "CharterBody2D" в разделе маска мы укажем второй слой вместо первого, на котором находится "StaticBody2D" - наш персонаж его просто не увидит. А это значит, что игрок просто провалится в пол.
Теперь, когда мы знаем для чего нужен данный узел - пора сделать точеные пики действительно опасными. Заходим в сцену и выбрав узел Area2D переименовываем в "HazardArea". В разделе "Collision" выставляем третий слой, а вот маску оставляем пустой. Наши пики никого не должны искать - они сами выступают объектом поиска.
Теперь переходим в сцену Spikes и нажимаем на кнопку добавления дочерней сцены. Она находится рядом с кнопкой добавления узла. Выбираем нашу сцену "HazardArea2D". Добавляем для "HazardArea2D" два дочерних узла "CollisionPolygon", определяя область столкновения.
Теперь мы переходим в сцену "Player" и создаем дочерний узел "Area2D", называем его "HazardDetecter". Уже к этой области привязываем дочерний"CollisionShape". В узле "HazardDetecter" выбираем раздел "Collision" и оставляем графу слоя пустой. Но в разделе маски и выбираем третий слой - именно в нем мы будем искать область опасности "HazardArea".
Осталось настроить сигналы... вы еще не знакомы с ними?
У многих узлов есть доступные сигналы и узел Area2D - не исключение. Посмотреть доступные сигналы можно в колонке справа, перейдя в раздел "Узел". Нас в первую очередь интересует сигнал "area_entered", что в переводе на человеческий означает "Обнаружена область".
В графе маска указан третий слой и именно на нем находится "HazardArea". Когда наш узел "HazardDetecter" обнаружит другой узел типа Area2D - сигнал сработает. Но что нам делать с полученным сигналом?
Сделаем простенькую систему смерти и возрождения!
Для начала добавим камеру, которая будет следовать за нашим персонажем. В разделе "PositionSmoothing" выставим "Speed" на 5 пикселей в секунду. Эта небольшая задержка сделает движение камеры более плавным.
Теперь нам потребуется зафиксировать точку для возрождения. Ей будет выступать стартовая позиция с которой мы начинаем игру. Создаем переменную стартовой позиции и при запуске игры закладываем в нее координаты начального положения игрока.
@onready var starting_position = global_position
Теперь переходим в узел "HazardDetecter" к разделу сигналов и ищем "entered_area". Нажимаем правой кнопкой мыши, тыкаем присоединить и выбираем наш объект "Player".
Это создаст в скрипте новую функцию, под которой мы пишем всего одну строку.
global_position = starting_position
Остается только расставить наши точеные пики по карте и мы можем смело отлетать на стартовую точку при столкновении с ними!
Остался чисто косметический момент, а именно - анимация персонажа. 2D сеты анимации вы можете легко откапать на просторах интернета, я же расскажу вам как их правильно настроить.
Заменяем узел спрайта на "AnimatedSprite2D" и в разделе "Sprite Frames" создаем новый. Перед вами откроется окно кадровой анимации, куда вы и будете загружать кадры.
Создаем три анимации - "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")
Снова переведу для вас на человечий язык:
Если мы стоим на месте - запустить анимацию стоячего положения;
Если нажата одна из кнопок движения, то мы проверяем направление. Если нажата кнопка "влево" - то зеркально разворачиваем объект анимации. Проигрываем анимацию бега.
Если мы не на земле - запускаем анимацию прыжка.
Наконец-то мы получили подобие игры!
У нас есть анимированный персонаж подчиняющийся законам физики, карта по которой он передвигается и даже опасности, которые могут его убить и отправить на старт.
Надеюсь, таким же новичкам как я данный материал будет понятен, а более опытные гоблины-шаманы не найдут значительных косяков в коде.
Продолжим доводить данный проект до ума в следующей статье! А пока что подписывайся на мой блог и оставь пару комментариев. Тебе не сложно - а мне поможет!