Создание шутера от первого лица в Godot Engine Часть 2

Создание шутера от первого лица в Godot Engine Часть 2


Обpор второй части

В этой части мы дадим игроку оружие.

../../../_images/PartTwoFinished.png

К концу урока у вас будет игрок, который может стрелять из пистолета, винтовки и атаковать с помощью ножа. Теперь у игрока также будут анимации с переходами, а оружие сможет взаимодействовать с объектами в окружающей среде.

Note

Предполагается, что вы закончили часть 1, прежде чем перейти к этому уроку.

Начнем !

Создание системы обработки анимаций

Сначала нам нужен способ обработки изменяющихся анимаций. Откройте Player.tscn и выберете узел AnimationPlayer (Player->``Rotation_helper``->``Model``->``AnimationPlayer``).

Создайте новый скрипт AnimationPlayer_Manager.gd и закрепите на  AnimationPlayer.

Добавьте этот код в AnimationPlayer_Manager.gd:

# Structure -> Animation name :[Connecting Animation states]
var states = {
"Idle_unarmed":["Knife_equip", "Pistol_equip", "Rifle_equip", "Idle_unarmed"],

"Pistol_equip":["Pistol_idle"],
"Pistol_fire":["Pistol_idle"],
"Pistol_idle":["Pistol_fire", "Pistol_reload", "Pistol_unequip", "Pistol_idle"],
"Pistol_reload":["Pistol_idle"],
"Pistol_unequip":["Idle_unarmed"],

"Rifle_equip":["Rifle_idle"],
"Rifle_fire":["Rifle_idle"],
"Rifle_idle":["Rifle_fire", "Rifle_reload", "Rifle_unequip", "Rifle_idle"],
"Rifle_reload":["Rifle_idle"],
"Rifle_unequip":["Idle_unarmed"],

"Knife_equip":["Knife_idle"],
"Knife_fire":["Knife_idle"],
"Knife_idle":["Knife_fire", "Knife_unequip", "Knife_idle"],
"Knife_unequip":["Idle_unarmed"],
}

var animation_speeds = {
"Idle_unarmed":1,

"Pistol_equip":1.4,
"Pistol_fire":1.8,
"Pistol_idle":1,
"Pistol_reload":1,
"Pistol_unequip":1.4,

"Rifle_equip":2,
"Rifle_fire":6,
"Rifle_idle":1,
"Rifle_reload":1.45,
"Rifle_unequip":2,

"Knife_equip":1,
"Knife_fire":1.35,
"Knife_idle":1,
"Knife_unequip":1,
}

var current_state = null
var callback_function = null

func _ready():
    set_animation("Idle_unarmed")
    connect("animation_finished", self, "animation_ended")



func set_animation(animation_name):
    if animation_name == current_state:
        print ("AnimationPlayer_Manager.gd -- WARNING: animation is already ", animation_name)
        return true

    if has_animation(animation_name) == true:
        if current_state != null:
            var possible_animations = states[current_state]
            if animation_name in possible_animations:
                current_state = animation_name
                play(animation_name, -1, animation_speeds[animation_name])
                return true
            else:
                print ("AnimationPlayer_Manager.gd -- WARNING: Cannot change to ", animation_name, " from ", current_state)
                return false
        else:
            current_state = animation_name
            play(animation_name, -1, animation_speeds[animation_name])
            return true
    return false


func animation_ended(anim_name):
    # UNARMED переход
    if current_state == "Idle_unarmed":
        pass
    # KNIFE переход
    elif current_state == "Knife_equip":
        set_animation("Knife_idle")
    elif current_state == "Knife_idle":
        pass
    elif current_state == "Knife_fire":
        set_animation("Knife_idle")
    elif current_state == "Knife_unequip":
        set_animation("Idle_unarmed")
    # PISTOL transitions
    elif current_state == "Pistol_equip":
        set_animation("Pistol_idle")
    elif current_state == "Pistol_idle":
        pass
    elif current_state == "Pistol_fire":
        set_animation("Pistol_idle")
    elif current_state == "Pistol_unequip":
        set_animation("Idle_unarmed")
    elif current_state == "Pistol_reload":
        set_animation("Pistol_idle")
    # RIFLE transitions
    elif current_state == "Rifle_equip":
        set_animation("Rifle_idle")
    elif current_state == "Rifle_idle":
        pass;
    elif current_state == "Rifle_fire":
        set_animation("Rifle_idle")
    elif current_state == "Rifle_unequip":
        set_animation("Idle_unarmed")
    elif current_state == "Rifle_reload":
        set_animation("Rifle_idle")

func animation_callback():
    if callback_function == null:
        print ("AnimationPlayer_Manager.gd -- WARNING: No callback function for the animation to call!")
    else:
        callback_function.call_func()



Давайте разберем, что этот скрипт делает:

Начнем с глобальных переменных:

  • states: Словарь для хранения наших анимационных состояний. (Дальнейшее объяснение ниже)
  • animation_speeds: Словарь для хранения всех скоростей, на которых мы хотим проигрывать анимации.
  • current_state: Переменная для хранения состояния анимации, в котором мы сейчас находимся.
  • callback_function: Переменная для удержания функции обратного вызова. (Дальнейшее объяснение ниже)

Если вы знакомы со state machine, вы, возможно, заметили, что состояния структурированы как базовый state machine. Примерно так настраиваются состояния:

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

Если мы попытаемся перейти к состоянию, которое не включено в словари, то мы получаем предупреждающее сообщение, и анимация не изменяется. Мы также автоматически перейдем из некоторых состояний в другие, это будет объяснено далее в animation_ended

Note

Ради этого простого руководства мы не используем «правильный» state machine. Если вам интересно узнать больше об этом аспекте, см. Следующие статьи:
(Python) https://dev.to/karn/building-a-simple-state-machine-in-python
(C#) https://www.codeproject.com/Articles/489136/UnderstandingplusandplusImplementingplusStateplusP
(статья на Wiki) https://en.wikipedia.org/wiki/Finite-state_machine
В следующих частях этой серии уроков мы можем пересмотреть этот скрипт, включив в него правильный state machine.

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

Note

Обратите внимание, что все анимации стрельбы быстрее, чем их нормальная скорость. Не забудьте это!

current_state будет содержать имя состояния анимации, в котором мы сейчас находимся.

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

Рассмотрение функции _ready.

Сначала мы устанавливаем анимацию в Idle_unarmed, используя функцию set_animation, поэтому мы обязательно начинаем с этой анимации. Затем мы подключаем сигнал nimation_finished к этому скрипту и назначаем его для вызова анимации.

Теперь set_animation.

set_animation проверяет, можем ли мы перейти к уже пройденному состоянию анимации, и если можем, то переходим. Другими словами, если состояние анимации, в котором мы находимся в настоящее время, имеет переданное имя состояния в states, то мы перейдем к этой анимации.

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

Затем мы проверяем, имеет ли AnimationPlayer прошедшую анимацию, используя has_animation. Если это не так, мы возвращаем false.

Затем мы проверяем, установлено current_state или нет. Если current_state в настоящее время не задано,то мы устанавливаем current_state в прошедшую анимацию и говорим AnimationPlayer, чтобы он начал воспроизведение со временем смешивания -1 и скоростью, установленной в animation_speeds, а затем мы возвращаем true.

Если у нас есть состояние в current_state, то мы получаем все возможные состояния, к которым мы можем перейти. Если имя анимации находится в массиве возможных переходов, то мы устанавливаем current_stateв прошедшую анимацию, сообщаем AnimationPlayer, чтобы он воспроизвел анимацию со временем смешивания -1 с установленной скоростью в animation_speeds, а затем мы возвращаем true.

Давайте посмотрим на animation_ended.

animation_ended - это функция, которая будет вызываться AnimationPlayer, когда выполнена анимация.

По окончанию определенных состояний нам может потребоваться переход в другое. Чтобы справиться с этим, мы проверяем все возможные состояния анимации. Если нам нужно, мы переходим в другое.

Warning

Если вы используете свои собственные анимированные модели, убедитесь, что ни одна из анимаций не настроена на цикл. Циклические анимации не отправляютanimation_finished , когда они достигают конца.

Note

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

Наконец, у нас есть animation_callback. Эта функция будет вызываться функцией track в нашей анимации. Если у нас есть FuncRef, назначенная callback_function, то мы вызываем это переданное в функции. Если у нас нет функции FuncRef, назначенной callback_function, мы выводим предупреждение на консоль.

Tip

Попробуйте запустить Testing_Area.tscn, чтобы убедиться, что нет проблем с запуском. Если игра работает, но ничего не изменилось, все правильно.

Подготовка анимаций

Теперь, когда у нас есть рабочий менеджер анимации, нам нужно вызвать его из нашего скрипта игрока. Однако, до этого нам нужно установить некоторые вызовы анимации при стрельбе.

Откройте Player.tscn, если вы не открыли его, и перейдте к узлу AnimationPlayer (Player->``Rotation_helper``->``Model``->``AnimationPlayer``).

Нам нужно привязать функию track к трем из наших анимаций: стрельбы для пистолета, винтовки и ножа. Начнем с пистолета. Нажмите на раскрывающийся список анимации и выберите «Pistol_fire».

Теперь прокрутите вниз до самой нижней части списка дорожек анимации. Заключительный пункт в списке должен быть Armature/Skeleton:Left_UpperPointer. Теперь щелкните значок «плюс» в нижней строке окна анимации.

../../../_images/AnimationPlayerAddTrack.png

Это вызовет окно с тремя вариантами. Мы хотим добавить функцию callback track, поэтому щелкните опцию «Add Call Func Track». Это откроет окно, отображающее все дерево узлов. Перейдите к AnimationPlayer, выберите его и нажмите OK.

../../../_images/AnimationPlayerCallFuncTrack.png

Теперь в нижней части списка анимационных дорожек у вас будет зеленый трек «AnimationPlayer». Теперь нам нужно добавить точку, где мы хоти вызвать нашу функцию. Скрольте временную шкалу, пока вы не достигнете точки, где дуло только начинает мигать.

Note

Временная шкала - это окно, в котором хранятся все точки нашей анимации. Каждая из маленьких точек представляет собой данные анимации.
Скроллинг временной шкалы означает перемещение себя через анимацию. Поэтому, когда мы говорим «скрольте временную шкалу, пока не достигнете точки», мы подразумеваем переход через окно анимации, пока вы не достигнете точки на временной шкале.
Кроме того, дуло пистолета - это конечная точка, где выходит пуля. Молниеносная вспышка - это вспышка света, которая выходит из дула при выстреле пули. Дуло также иногда упоминается как ствол пистолета.

Tip

Для более тонкого управления при очистке временной шкалы нажмите Control и прокрутите вперед, чтобы увеличить масштаб. Прокрутка назад его уменьшит.
Вы также можете изменить способ сглаживания временной шкалы, изменив значение Step (s) на lower/higher.

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

../../../_images/AnimationPlayerAddPoint.png

Теперь нам нужно сделать есть еще одну вещь, прежде чем мы закончим с пистолетом. Выберите кнопку «enable editing of individual keys» в правом углу окна анимации.

../../../_images/AnimationPlayerEditPoints.png

Как только вы нажмете на это, откроется новое окно с правой стороны. Теперь нажмите на зеленую точку на дорожке AnimationPlayer. Это откроет информацию, связанную с этой точкой на временной шкале. В поле пустого имени введите «animation_callback» и нажмите enter.

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

Warning

Не забудьте снова нажать кнопку «enable editing of individual keys», чтобы отключить возможность редактирования отдельных клавиш, ведь вы можете случайно изменить одну из дорожек!

Давайте повторим процесс для стрельбы из винтовки и ножей!

Note

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

Перейдите в анимацию «Rifle_fire» из раскрывающегося списка. Добавьте функцию track, когда вы достигнете нижней части списка дорожек, щелкнув значок «Маленький плюс» в нижней части экрана. Найдите точку, где дуло только начинает мигать, и нажмите маленький зеленый символ плюс, чтобы добавить функцию в эту позицию.

Затем нажмите кнопку «enable editing of individual keys» и плюс в нижней правой части окна. Выберите вновь созданную функцию, поместите «animation_callback» в поле имени и нажмите enter. Нажмите кнопку «enable editing of individual keys» еще раз, чтобы отключить редактирование отдельных клавиш.

Теперь нам просто нужно применить функцию track к анимации ножа. Выберите анимацию «Knife_fire» и прокрутите до нижней части дорожек. Нажмите символ «плюс» внизу окна анимации и добавьте функцию track. Затем поставьте точку примерно на первой трети анимации.

Note

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

Теперь проделайте все тоже самое, что и в предыдущие разы.

Tip

Обязательно сохраните свою работу!

Мы почти готовы добавить возможность стрелять! Нам просто нужно настроить одну последнюю сцену: сцену для нашей пули.
Именно этим мы и займемся в следующем уроке !

Переведено отсюда.


Report Page