Движение игрока с помощью кода

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

Нажмите правой кнопкой мыши на узел Player и выберите Attach Script, чтобы добавить к нему новый скрипт. Во всплывающем окне настройте Template на Empty перед тем, как нажать кнопку Create.

image0

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

extends KinematicBody

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75

var velocity = Vector3.ZERO

Это свойства, характерные для тела в движении. velocity - это 3D-вектор, совмещающий в себе скорость с направлением. Здесь мы определяем его, как свойство, потому что хотим обновить и использовать повторно его значения в кадре.

Примечание

Значения довольно сильно отличаются от 2D-кода, потому что расстояние здесь измеряется в метрах. В то время как в 2D тысяча единиц (пикселей) может соответствовать только половине ширины Вашего экрана, в 3D же это будет равняться километру.

А сейчас давайте напишем код к движению. Мы начинаем с вычисления ввода вектора направления, используя глобальный объект Input в _physics_process().

func _physics_process(delta):
    # We create a local variable to store the input direction.
    var direction = Vector3.ZERO

    # We check for each move input and update the direction accordingly.
    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        # Notice how we are working with the vector's x and z axes.
        # In 3D, the XZ plane is the ground plane.
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

Здесь мы собираемся сделать расчет, используя виртуальную функцию _physics_process(). Так же как и _physics_process(), она позволяет Вам обновлять узел в каждом кадре, но она создана специально для кодирования в области физики, как, например, движения кинематического или негибкого тела.

См.также

Для получения дополнительной информации о разнице между _process() и _physics_process(), прочитайте Idle and Physics Processing.

Сперва инициализируем переменную direction со значением Vector3.ZERO. После этого мы проверяем, использует ли игрок один или больше вводов move_* и обновляем x и z векторов. Они соответствуют осям опорной плоскости.

Эти четыре условия дают нам восемь возможных вариантов и восемь возможных направлений.

В случае, если игрок нажмет, например, обе клавиши W и D одновременно, вектор будет иметь длину около 1.4. Но если он нажимает одну клавишу, то вектор будет иметь длину 1. Мы хотим, чтобы длина вектора была неизменной. Чтобы сделать это, мы можем вызвать его метод normalize().

#func _physics_process(delta):
    #...

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

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

В этом случае, мы также получаем узел Pivot и вызываем его метод look_at(). Этот метод занимает место в пространстве так, чтобы можно было рассмотреть глобальные координаты и верхнее направление. В данном случае мы можем использовать константу Vector3.UP.

Примечание

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

В 3D свойством, содержащим позицию узла, является translation. Добавив к нему direction, мы получим позицию, которая находится на расстоянии одного метра от игрока.

Затем мы обновляем скорость. Мы должны вычислить скорость земли и скорость падения отдельно. Обязательно вернитесь на одну вкладку назад, чтобы строки находились внутри функции _physics_process(), но вне условия, которое мы только что написали.

func _physics_process(delta):
    #...
    if direction != Vector3.ZERO:
        #...

    # Ground velocity
    velocity.x = direction.x * speed
    velocity.z = direction.z * speed
    # Vertical velocity
    velocity.y -= fall_acceleration * delta
    # Moving the character
    velocity = move_and_slide(velocity, Vector3.UP)

Для вертикальной скорости мы вычитаем ускорение свободного падения, умноженное на дельту времени, в каждом кадре. Обратите внимание на использование оператора -=, который является сокращением variable = variable - ....

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

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

В последней строке мы вызываем KinematicBody.move_and_slide(). Это эффективный метод класса KinematicBody, который позволяет Вам плавно передвигать персонажа. Если он ударяется об стену в середине движения, то движок попытается сгладить действие для Вас.

Эта функция подразумевает два параметра: нашу скорость и направление вверх. Она двигает персонажа и возвращает остаточную скорость после столкновений. При ударении об пол или стену функция сократит или сбросит скорость по направлению от Вас. В нашем случае хранение возвращаемого значения функции предотвращает персонажа от накопления вертикального импульса, который в противном случае станет таким большим, что персонаж сможет передвигаться через плиты пола через некоторое время.

Вот и весь код, необходимый для перемещения персонажа по полу.

Для сравнения вот так выглядит завершенный код Player.gd.

extends KinematicBody

# How fast the player moves in meters per second.
export var speed = 14
# The downward acceleration when in the air, in meters per second squared.
export var fall_acceleration = 75

var velocity = Vector3.ZERO


func _physics_process(delta):
    var direction = Vector3.ZERO

    if Input.is_action_pressed("move_right"):
        direction.x += 1
    if Input.is_action_pressed("move_left"):
        direction.x -= 1
    if Input.is_action_pressed("move_back"):
        direction.z += 1
    if Input.is_action_pressed("move_forward"):
        direction.z -= 1

    if direction != Vector3.ZERO:
        direction = direction.normalized()
        $Pivot.look_at(translation + direction, Vector3.UP)

    velocity.x = direction.x * speed
    velocity.z = direction.z * speed
    velocity.y -= fall_acceleration * delta
    velocity = move_and_slide(velocity, Vector3.UP)

Проверка движения нашего игрока

Мы поместим нашего игрока на сцену Main, чтобы протестировать её. Чтобы это сделать, нам необходимо инстанцировать игрока и затем добавить камеру. В отличии от 2D, в 3D Вы ничего не увидите, если в Вашем окне просмотра нет камеры, указывающей на что-то.

Сохраните Вашу сцену Player и откройте сцену Main. Вы можете нажать на вкладку Main вверху редактора и сделать это.

image1

Если Вы закрыли сцену до того, как сделать это, направьтесь в панель FileSystem и двойным нажатием щелкните Main.tscn, чтобы снова её открыть.

Для инстанцирования Player нажмите правой кнопкой мыши на узел Main и выберите Instance Child Scene.

image2

Во всплывающем окне двойным щелчком нажмите на Player.tscn. Персонаж должен появиться в центре окна просмотра.

Добавление камеры

Теперь давайте добавим камеру. Точно так же, как мы сделали с Player Pivot, мы создадим основной каркас. Снова нажмите правой кнопкой мыши на узел Main и выберите в этот раз Add Child Node. Создайте новый Position3D, назовите его CameraPivot и добавьте узел Camera, как его дочерний. Ваше дерево сцены должно выглядеть таким образом.

image3

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

image4

Мы будем использовать Pivot для поворота камеры, как будто она находится на кране. Сначала давайте разделим 3D-вид, чтобы иметь возможность свободно перемещаться по сцене и видеть то, что видит камера.

На панели инструментов прямо над вьюпортом нажмите View, затем 2 Viewports. Вы также можете нажать Ctrl + 2 (Cmd + 2 на macOS).

image5

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

image6

На виде сверху переместите камеру примерно на 19 единиц по оси Z (синяя).

image7

Here's where the magic happens. Select the CameraPivot and rotate it -45 degrees around the X axis (using the red circle). You'll see the camera move as if it was attached to a crane.

image8

Вы можете запустить сцену, нажав F6 и нажимая клавиши со стрелками перемещать персонаж.

image9

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

Снова выберите Camera и в Inspector установите Projection на Orthogonal и Size на 19. Теперь персонаж должен выглядеть более плоским и поверхность должна заполнять задний план.

image10

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