Godot 4. Улучшаем управление и анимацию игрока.

Предисловие.

У нас есть игра с 2-мя уровнями, которая выглядит максимально бледно, что ж, в этом уроке исправим эту проблему, добавив:

  • Анимированный спрайт
  • Более качественное управление игроком

Подготовка.

Скачайте спрайт персонажа (player.png).

И закиньте его в проект на Godot.

Теперь, уберем мыло, зайдите в настройки проекта.

Выберите Рендериг->Текстуры, там у Default Texture Filter выберите Nearest. Теперь, все ресурсы будут загружаться без размытия.

Включите расширенные настройки, измените разрешение игры на 320×240, и разрешение окна на 1280×720, где:

  • разрешение игры — разрешение в котором рендерится игра, и размер области видимости камеры.
  • разрешение окна — разрешения окна с игрой, в нем разрешение игры растягивается до разрешения окна.

Подготовка завершена, теперь добавим анимацию игроку.

Настраиваем анимации игроку

В сцене игрока, удалите ноду Sprite2D, и добавьте AnimatedSprite2D.

В инспекторе, создайте SpriteFrames и нажмите на него

Внизу, у вас появится редактор анимаций.

  1. Список анимаций
  2. Кнопка добавить новую анимацию в список (1)
  3. Кнопки проигрывания анимации
  4. Добавить кадры анимации из спрайтшита (сетки с кадрами)
  5. Длительность 1-го кадра анимации
  6. Список кадров выбранной выбранной анимации.

Ок, добавьте анимации:

  • idle — бездействие.
  • run — бег
  • jump — прыжок

Нажатием кнопки документа с плюсом (2).

Далее, нажмите на кнопку в форме сетки (4). Там выберите спрайт player.png скачанный ранее.

idle — анимация

Укажите количество кадров:

  • по горизонтали — 2
  • по вертикали — 3

Выбираете кадр, и нажимаете Добавить кадров: 1

То же самое сделайте для других анимаций, по кадрам показанных на картинках.

run — анимация
jump — анимация

Теперь, будем переключать анимации через код игрока.

Улучшаем логику игрока

Откройте код игрока, и замените код на следующее:

extends CharacterBody2D

const SPEED = 200.0
const JUMP_VELOCITY = -200.0
const GRAVITY = 500.0
const ACCELERATION = 900.0
const FRICTION = 1000.0

#положили ноду AnimatedSprite2D в переменную sprite
@onready var sprite = $AnimatedSprite2D

func _physics_process(delta):
	
	if not is_on_floor():
		velocity.y += GRAVITY*delta
	
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = JUMP_VELOCITY
	
	var direction = 0
	if Input.is_action_pressed("move_left"):
		direction += 1
	if Input.is_action_pressed("move_right"):
		direction -= 1
		
	if(direction != 0):
#Если игрок двигается влево или вправо, то применяем ускорение
		apply_acceleration(direction, delta)
		
	else:
#Иначе трение, или другими словами, ускорение направленной в 0
		apply_friction(direction, delta)
	#обновляем анимацию в зависимости от направления.
	update_animations(direction)
	
	move_and_slide()

func apply_acceleration(direction, delta):
#Увеличиваем скорость velocity.x с ускорением ACCELERATION до скорости SPEED
	velocity.x = move_toward(velocity.x, SPEED*direction, ACCELERATION*delta)
	
func apply_friction(direction, delta):
#Уменьшаем скорость velocity.x с ускорением FRICTION до скорости 0
	velocity.x = move_toward(velocity.x, 0, FRICTION*delta)
		
func update_animations(direction):
#Если игрок стоит на месте
	if(direction == 0):
#Проигрываем анимацию бездействия
		sprite.play("idle")
	else:
#Если двигается, проигрываем анимацию ходьбы и поворачиваем спрайт в направлении движения
		sprite.play("run")
		sprite.flip_h = direction > 0
	#Если игрок в воздухе, проигрываем анимаю прыжка
	if(!is_on_floor()):
		sprite.play("jump")

Весь код я кратко объяснил в комментариях, а теперь подробнее.

Мы добавили константы ACCELERATION и FRICTION

const ACCELERATION = 900.0
const FRICTION = 1000.0

которые отвечают, за ускорение и силу трения (ускорение в 0). Они больше чем SPEED, из-за того что они будут потом умножаться на delta.

Далее onready

@onready var sprite = $AnimatedSprite2D

Эта запись аналогична следующей:

var sprite
func _ready():
  sprite = $AnimatedSprite2D

Т.е. присваивает значение переменной в методе _ready. Запись с onready оказалась короче и проще, поэтому её и используем.

Дальше идут функции, начнем с update_animations

func update_animations(direction):
#Если игрок стоит на месте
	if(direction == 0):
#Проигрываем анимацию бездействия
		sprite.play("idle")
	else:
#Если двигается, проигрываем анимацию ходьбы и поворачиваем спрайт в направлении движения
		sprite.play("run")
		sprite.flip_h = direction > 0
	#Если игрок в воздухе, проигрываем анимаю прыжка
	if(!is_on_floor()):
		sprite.play("jump")
  • Если игрок стоит на месте, то проигрываем анимацию idle, проигрывание анимации работает с помощью функции play объекта AnimatedSprite.
  • Если игрок двигается, то проигрываем анимацию движения run
  • В зависимости от направления, отражаем спрайт по горизонтали flip_h
  • Если игрок в воздухе, проигрываем анимацию прыжка jump

Далее, в apply_acceleration, которая вызывается если игрок двигается.

func apply_acceleration(direction, delta):
#Увеличиваем скорость velocity.x с ускорением ACCELERATION до скорости SPEED
	velocity.x = move_toward(velocity.x, SPEED*direction, ACCELERATION*delta)

С помощью move_toward применяем ускорение к скорости по следующему шаблону

move_toward(начальное_значение, конечное_значение, шаг)

Таким образом, функция apply_acceleration плавно увеличивает скорость игрока от текущей velocity.x до SPEED с ускорением ACCELERATION

Так же работает и apply_friction, который вызывается при бездействии. Только скорость стремится к 0, а ни к SPEED.

Ок, не забудьте подогнать CollisionShape у игрока под размеры спрайта.

Теперь, запустите.

Как видите, управлять игроком стало намного приятней, а еще, у него появилась анимация.

Добавляем Coyote jump.

Бывало у вас бывало так, что вы нажимаете на кнопку прыжка с края платформы а она не срабатывает, и вы падаете в пропасть? Что-ж, в реальности она еще как срабатывает, вы просто в тайминги не попали. Но, в любом случае, это неприятный опыт, который можно пофиксить добавив Coyote jump.

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

Ок, добавьте ноду Timer к игроку, назовите CoyoteTimer. В инспекторе, установите:

  • Wait Time = 0.15s, указывает на сколько засечь таймер
  • One Shot = True, определяет должен-ли повторно запускаться таймер, в нашем случае, не должен
WaitTimer = 0.15s на картинке старые данные

Теперь, откройте код игрока. Присвойте там CoyoteTimer через onready.

@onready var coyoteTimer = $CoyoteTimer

Код прыжка замените на следующее:

if Input.is_action_just_pressed("jump") and (is_on_floor() or coyoteTimer.time_left > 0.0):
  velocity.y = JUMP_VELOCITY

Если нажата кнопка прыжка и, либо игрок на земле, либо coyoteTimer не закончился, тогда прыгаем.

В конце _physics_process добавьте следующее:

  update_animations(direction)
  var was_on_floor = is_on_floor() #Получаем значение is_on_floor до применения скорости
  move_and_slide() #Обновляем is_on_floor и позицию игрока
  if(was_on_floor and !is_on_floor() and velocity.y >= 0): #Если игрок только что оказался в воздухе, и падает вниз, то
    coyoteTimer.start() #Запускаем coyoteTimer

Код, объяснил в комментариях.

Готово, теперь запустите.

Итоговый результат.

Пожалуйста отключи блокировщик рекламы, или внеси сайт в белый список!

Please disable your adblocker or whitelist this site!