Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Здравствуйте, это продолжение предыдущей статьи о создании игрового персонажа в GodotEngine. Я наконец понял, как реализовать некоторые механики, такие как второй прыжок в воздухе, карабканье по, и прыжок от стены. Первая часть была более простой по насыщенности, так как с чего-то же нужно было начинать, чтобы потом доработать или переделать.
Для начала я решил собрать весь предыдущий код, чтобы те, кто использовали информацию из предыдущей статьи поняли, как я представлял себе программу полностью
Надеюсь тем, кто читал предыдущую статью стало примерно понятно, как всё работает. Теперь вернёмся к разработке.
Машина состояний(в моём понимании) — часть программы, что определяет состояние чего либо: в воздухе, на полу, на потолке, или на стене, а также определяет что должно происходить с персонажем в том или ином месте. В GodotEngine есть такая вещь как enum, что создаёт перечисление, где каждый элемент является, заданной в коде, константой. Думаю лучше покажу это на примере:
Данный код можно смело положить в самое начало скрипта игрового персонажа и держать в голове, что он существует. Следом инициализируем переменную в нужном месте var current_state: int = States.IN_AIR, которая равна нулю, если использовать print. Далее нужно как-то определять что игрок в текущем состоянии будет делать. Думаю многим опытным разработчикам пришедшим из C++ знакома конструкция switch () {case:}. В GDScript есть похожая адаптированная конструкция, хотя и switch также есть в планах у разработчиков. Конструкция называется match. Думаю будет правильнее показать данную конструкцию в деле, так как рассказывать будет сложнее, чем показывать:
Но мы до сих пор не меняем состояния. Нужно создать отдельную функцию, которую будем вызывать перед match-ем, чтобы изменять переменную current_state которую стоит добавить в код к остальным переменным. А функцию назовём update_state().
Теперь, когда машина состояний готова, мы можем добавлять уйму функций. В том числе и добавить анимации к персонажу… Даже не так… Мы можем добавить тонну анимаций персонажу. Система стала модульной. Но на этом мы не закончили с кодом. Я сказал в начале, что покажу, как делать дополнительный прыжок в воздухе, карабканье по, и прыжок от стены. Начнём по порядку.
Во-первых, добавьте вызов прыжка в состоянии States.IN_AIR в наш match, который мы чуток доработаем.
Вот код нашего прыжка, который я исправил:
В комментариях к коду в принципе сказано, как я переделал программу, надеюсь вы понимаете мои слова там. Но фактически этих исправлений хватит чтобы изменить механику прыжка и улучшить до двойного. На то чтобы изобрести следующие методы мне потребовалась пара месяцев. Я их написал фактически позавчера, 1 октября 2020 года.
К нашему сожалению, Нормаль стены GodotEngine не позволяет узнать, из чего следует, что нам придётся создать небольшой костыль. Для начала я сделаю сноску имеющихся на данный момент переменных, чтобы можно было проще сказать что изменилось.
Теперь нужно определять по какой стене игрок карабкается.
Вот дерево сцены, что вам стоит подготовить для реализации определителя стороны стены
Разместите 2 Area2D по бокам персонажа и CollisionShape2D обоих не должны пересекаться с персонажем. Подпишите соответственно объекты WallLeft/WallRight и присоедините сигналы _on_body_endered и _on_body_exited к единственному скрипту персонажа. Вот код который нужен чтобы определять стены(Добавить в самый конец скрипта):
Приступим к методу карабканья. В коде всё будет сказано за меня
И нужно переписать управление move_character() для того, чтобы можно было не просто держаться, а карабкаться вверх вниз, благо у нас есть direction
И исправляем наш _physics_process()
Теперь персонаж должен уметь карабкаться по стенам.
Теперь реализуем прыжок от стены.
Добавляем вызов этого метода в наш match -> States.ON_WALL и мы присоединили наш метод к остальной части _physics_process().
В данной статье я показал реализацию относительно сложных механик(для начинающих) в GodotEngine. Но это ещё не последняя часть серии статей, поэтому попрошу тех, кто знает как реализовать мною показанные методы в этой статье лучше, писать о них в комментарии. Я, да и многие читающие, будем благодарны за качественные и быстрые решения.
Для начала я решил собрать весь предыдущий код, чтобы те, кто использовали информацию из предыдущей статьи поняли, как я представлял себе программу полностью
extends KinematicBody2D
# Константы
const GRAVITY: int = 40
const MOVE_SPEED: int = 120 # Скорость перемещения персонажа в пикселях
const JUMP_POWER: int = 80 # Скорость прыжка
# Переменные
var velocity: Vector2 = Vector2.ZERO
func _physics_process(_delta: float) -> void:
# Ниже вставлять вызовы функций перемещения
move_character() # Перемещение персонажа
jump()
# Ниже можно ничего не трогать
self.velocity.y += GRAVITY
self.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))
func move_character() -> void:
var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
self.velocity.x = direction * MOVE_SPEED
func jump() -> void:
if self.is_on_floor():
if Input.is_action_pressed("ui_accept"): # Я вспомнил про событие ui_accept
# Оно вмещает в себя нажатие прыжка
self.velocity.y -= JUMP_POWER
Надеюсь тем, кто читал предыдущую статью стало примерно понятно, как всё работает. Теперь вернёмся к разработке.
Машина состояний
Машина состояний(в моём понимании) — часть программы, что определяет состояние чего либо: в воздухе, на полу, на потолке, или на стене, а также определяет что должно происходить с персонажем в том или ином месте. В GodotEngine есть такая вещь как enum, что создаёт перечисление, где каждый элемент является, заданной в коде, константой. Думаю лучше покажу это на примере:
enum States { # Создаётся перечисление States, к константам которого можно обращаться через States.IN_AIR, States.ON_FLOOR...
IN_AIR, # В воздухе
ON_FLOOR, # На полу
ON_WALL # На стене
}
Данный код можно смело положить в самое начало скрипта игрового персонажа и держать в голове, что он существует. Следом инициализируем переменную в нужном месте var current_state: int = States.IN_AIR, которая равна нулю, если использовать print. Далее нужно как-то определять что игрок в текущем состоянии будет делать. Думаю многим опытным разработчикам пришедшим из C++ знакома конструкция switch () {case:}. В GDScript есть похожая адаптированная конструкция, хотя и switch также есть в планах у разработчиков. Конструкция называется match. Думаю будет правильнее показать данную конструкцию в деле, так как рассказывать будет сложнее, чем показывать:
func _physics_process(_delta: float) -> void:
# Ниже функции перемещения
match (self.current_state):
States.IN_AIR:
# Вызов методов что доступны в воздухе.
self.move_character()
States.ON_FLOOR:
# Вызов методов, что доступны на земле.
self.move_character()
self.jump()
States.ON_WALL:
# вызов методов, что доступны, если мы упремся лицом в стену. Пока кроме перемещения ничего нет.
self.move_character()
# Ниже будет остальной код
Но мы до сих пор не меняем состояния. Нужно создать отдельную функцию, которую будем вызывать перед match-ем, чтобы изменять переменную current_state которую стоит добавить в код к остальным переменным. А функцию назовём update_state().
func update_state() -> void:
# Тут всё зависит от запланированных разработчиком возможностей персонажа.
if self.is_on_floor():
self.current_state = self.States.ON_FLOOR
elif self.is_on_wall() and !self.is_on_floor():
# Когда персонаж только на стене.
self.current_state = self.States.ON_WALL
elif self.is_on_wall() and self.is_on_floor():
# Ситуация угла. Будем в данном случае на стене.
self.current_state = self.States.ON_WALL
else: # Во всех других случаях будем в воздухе
self.current_state = self.states.IN_AIR
Теперь, когда машина состояний готова, мы можем добавлять уйму функций. В том числе и добавить анимации к персонажу… Даже не так… Мы можем добавить тонну анимаций персонажу. Система стала модульной. Но на этом мы не закончили с кодом. Я сказал в начале, что покажу, как делать дополнительный прыжок в воздухе, карабканье по, и прыжок от стены. Начнём по порядку.
Дополнительный прыжок в воздухе
Во-первых, добавьте вызов прыжка в состоянии States.IN_AIR в наш match, который мы чуток доработаем.
Вот код нашего прыжка, который я исправил:
func jump() -> void:
# Старую проверку в мусор. Мы сделаем её позже.
if Input.is_action_pressed("ui_accept"): # Назначаем в настройках событие
if self.current_state == self.States.ON_FLOOR:
# Как раньше, но добавляем проверку через текущее_состояние
self.velocity.y -= JUMP_POWER
elif (self.current_state == self.States.IN_AIR or self.current_state == self.States.ON_WALL)
and self.second_jump == true:
# Тут проверяем на другие состояния и можем ли мы вообще прыгнуть второй раз
self.velocity.y = -JUMP_POWER
# Сбрасываем накопленное ускорение падения и совершаем прыжок
self.second_jump = false
# Не забудьте добавить var second_jump: bool = true в самый верх. и в update_state()
# Добавьте после if self.is_on_floor(): self.second_jump = true # чтобы сбрасывать состояние прыжка после приземления на пол.
В комментариях к коду в принципе сказано, как я переделал программу, надеюсь вы понимаете мои слова там. Но фактически этих исправлений хватит чтобы изменить механику прыжка и улучшить до двойного. На то чтобы изобрести следующие методы мне потребовалась пара месяцев. Я их написал фактически позавчера, 1 октября 2020 года.
Карабканье по стенам
К нашему сожалению, Нормаль стены GodotEngine не позволяет узнать, из чего следует, что нам придётся создать небольшой костыль. Для начала я сделаю сноску имеющихся на данный момент переменных, чтобы можно было проще сказать что изменилось.
extends KinematicBody2D
# Сигналы
signal timer_ended # нужно чтобы заставить работать yield в wall_jump, что основан на обмане управления.
# Константы
const GRAVITY: int = 40
const MOVE_SPEED: int = 120 # Скорость перемещения персонажа в пикселях
const JUMP_POWER: int = 80 # Скорость прыжка
const WALL_JUMP_POWER: int = 60 # Сила прыжка от стены. Нужно для соответственной функции
const CLIMB_SPEED: int = 30 # Скорость вскарабкивания
# Переменные
var velocity: Vector2 = Vector2.ZERO
var second_jump: bool = true
var climbing: bool = false # Нужно чтобы определять, карабкается ли игрок по стене, или нет.
var timer_working: bool = false
var is_wall_jump: bool = false # Нужно, чтобы определить, а от стены ли мы прыгаем
var left_pressed: bool = false # Для искусственного зажатия кнопки влево
var right_pressed: bool = false # Для искусственного зажатия кнопки вправо
var current_state: int = States.IN_AIR
var timer: float = 0 # счётчик таймера, что будет встроен в _process(delta: float)
var walls = [false, false, false] # определения стен и потолка. Нулевой и второй - стены. Первый - потолок.
# Пока нужны только нулевой и второй
# Перечисления
enum States {
IN_AIR, # В воздухе
ON_FLOOR, # На полу
ON_WALL # На стене
}
# И я сделаю чуть больше чем сказал, добавив метод _process() с самодельным таймером
func _process(delta: float):
if timer_working:
timer -= delta
if timer <= 0:
emit_signal("timer_ended")
timer = 0
Теперь нужно определять по какой стене игрок карабкается.
Вот дерево сцены, что вам стоит подготовить для реализации определителя стороны стены
Разместите 2 Area2D по бокам персонажа и CollisionShape2D обоих не должны пересекаться с персонажем. Подпишите соответственно объекты WallLeft/WallRight и присоедините сигналы _on_body_endered и _on_body_exited к единственному скрипту персонажа. Вот код который нужен чтобы определять стены(Добавить в самый конец скрипта):
# Надеюсь тут всё интуитивно понятно
# Если нет, то комментарии вам в помощь
func _on_WallRight_body_entered(_body):
if (_body.name != self.name):
self.walls[0] = true # Если засечённый объект не мы, объект слева - стена
func _on_WallRight_body_exited(_body):
self.walls[0] = false # Когда тело вышло из коллизии другого объекта - стены слева нет
func _on_WallLeft_body_entered(_body):
if (_body.name != self.name):
self.walls[2] = true # Если засечённый объект не мы, объект справа - стена
func _on_WallLeft_body_exited(_body):
self.walls[2] = false # Когда тело вышло из коллизии другого объекта - стены справа нет
Приступим к методу карабканья. В коде всё будет сказано за меня
func climbing() -> void:
if (self.walls[0] or self.walls[2]): # Если стена слева или стена справа есть
# Создайте новый action в настройках и назовите ui_climb. Об этом я уже говорил в первой части.
self.climbing = Input.is_action_pressed("ui_climb")
else:
self.climbing = false
И нужно переписать управление move_character() для того, чтобы можно было не просто держаться, а карабкаться вверх вниз, благо у нас есть direction
func move_character() -> void:
var direction: float = Input.get_action_strength("ui_right") - Input.get_action_strength("ui_left")
if !self.climbing:
self.velocity.x = direction * MOVE_SPEED
else:
self.velocity.y = direction * CLIMB_SPEED
И исправляем наш _physics_process()
func _physics_process(_delta: float) -> void:
# Ниже функции перемещения
match (self.current_state):
States.IN_AIR:
self.move_character()
States.ON_FLOOR:
self.move_character()
self.jump()
States.ON_WALL:
self.move_character()
# Ниже можно ничего не трогать
if !self.climbing:
self.velocity.y += GRAVITY
self.velocity = self.move_and_slide(self.velocity, Vector2(0, -1))
Теперь персонаж должен уметь карабкаться по стенам.
Прыжок от стены
Теперь реализуем прыжок от стены.
func wall_jump() -> void:
if Input.is_action_just_pressed("ui_accept") and Input.is_action_pressed("ui_climb"):
# Если нажата 1 раз кнопка прыжка и зажата кнопка карабканья
self.is_wall_jump = true # Мы прыгаем от стены = да
self.velocity.y = -JUMP_POWER # Изменяем ускорение до -JUMP_POWER
if walls[0]: # Если стена слева
self.timer = 0.5 # Установить self.timer на 0.5 секунды
self.timer_enabled = true # Включаем таймер
self.left_pressed = true # ставим переменную left_pressed на да
yield(self, "timer_ended") # Дожидаемся срабатывания сигнала timer_ended
self.left_pressed = false # отпускаем left_pressed
if walls[2]: # Если стена справа
self.timer = 0.5 # Установить self.timer на 0.5 секунды
self.timer_enabled = true # Включаем таймер
self.right_pressed = true # ставим переменную right_pressed на да
yield(self, "timer_ended") # Дожидаемся срабатывания сигнала timer_ended
self.right_pressed = false # отпускаем right_pressed
self.is_wall_jump = false # Прыгнули. Больше не на стене
Добавляем вызов этого метода в наш match -> States.ON_WALL и мы присоединили наш метод к остальной части _physics_process().
Заключение
В данной статье я показал реализацию относительно сложных механик(для начинающих) в GodotEngine. Но это ещё не последняя часть серии статей, поэтому попрошу тех, кто знает как реализовать мною показанные методы в этой статье лучше, писать о них в комментарии. Я, да и многие читающие, будем благодарны за качественные и быстрые решения.