Движение игрока с помощью кода¶
Пришло время для написания кода! Мы собираемся использовать действия ввода, который мы уже создали в предыдущих частях, чтобы персонаж двигался.
Нажмите правой кнопкой мыши на узел Player и выберите Attach Script, чтобы добавить к нему новый скрипт. Во всплывающем окне настройте Template на Empty перед тем, как нажать кнопку Create.
Давайте начнем со свойств класса. Мы собираемся определить скорость движения, ускорение свободного падения, представляющее гравитацию, и скорость, которую мы будем использовать для движений персонажа.
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
public class Player : KinematicBody
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _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
public override void _PhysicsProcess(float 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.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("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 += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
}
Здесь мы собираемся сделать расчет, используя виртуальную функцию _physics_process()
. Так же как и _physics_process()
, она позволяет Вам обновлять узел в каждом кадре, но она создана специально для кодирования в области физики, как, например, движения кинематического или негибкого тела.
См.также
Для получения дополнительной информации о разнице между _process()
и _physics_process()
, прочитайте Idle and Physics Processing.
We start by initializing a direction
variable to Vector3.ZERO
. Then, we
check if the player is pressing one or more of the move_*
inputs and update
the vector's x
and z
components accordingly. These correspond to the
ground plane's axes.
Эти четыре условия дают нам восемь возможных вариантов и восемь возможных направлений.
В случае, если игрок нажмет, например, обе клавиши W и D одновременно, вектор будет иметь длину около 1.4
. Но если он нажимает одну клавишу, то вектор будет иметь длину 1
. Мы хотим, чтобы длина вектора была неизменной. Чтобы сделать это, мы можем вызвать его метод normalize()
.
#func _physics_process(delta):
#...
if direction != Vector3.ZERO:
direction = direction.normalized()
$Pivot.look_at(translation + direction, Vector3.UP)
public override void _PhysicsProcess(float delta)
{
// ...
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
}
Here, we only normalize the vector if the direction has a length greater than zero, which means the player is pressing a direction key.
In this case, we also get the Pivot node and call its look_at()
method.
This method takes a position in space to look at in global coordinates and the
up direction. In this case, we can use the Vector3.UP
constant.
Примечание
Такие локальные координаты узла, как, например, translation
, схожи с их родительскими константами. Глобальные координаты схожи с главными осями мира, которые вы наоборот можете видеть в окне просмотра.
In 3D, the property that contains a node's position is translation
. By
adding the direction
to it, we get a position to look at that's one meter
away from the Player.
Затем мы обновляем скорость. Мы должны вычислить скорость земли и скорость падения отдельно. Обязательно вернитесь на одну вкладку назад, чтобы строки находились внутри функции _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)
public override void _PhysicsProcess(float delta)
{
// ...
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_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)
public class Player : KinematicBody
{
// How fast the player moves in meters per second.
[Export]
public int Speed = 14;
// The downward acceleration when in the air, in meters per second squared.
[Export]
public int FallAcceleration = 75;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float 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.IsActionPressed("move_right"))
{
direction.x += 1f;
}
if (Input.IsActionPressed("move_left"))
{
direction.x -= 1f;
}
if (Input.IsActionPressed("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 += 1f;
}
if (Input.IsActionPressed("move_forward"))
{
direction.z -= 1f;
}
if (direction != Vector3.Zero)
{
direction = direction.Normalized();
GetNode<Spatial>("Pivot").LookAt(Translation + direction, Vector3.Up);
}
// Ground velocity
_velocity.x = direction.x * Speed;
_velocity.z = direction.z * Speed;
// Vertical velocity
_velocity.y -= FallAcceleration * delta;
// Moving the character
_velocity = MoveAndSlide(_velocity, Vector3.Up);
}
}
Проверка движения нашего игрока¶
Мы поместим нашего игрока на сцену Main, чтобы протестировать её. Чтобы это сделать, нам необходимо инстанцировать игрока и затем добавить камеру. В отличии от 2D, в 3D Вы ничего не увидите, если в Вашем окне просмотра нет камеры, указывающей на что-то.
Сохраните Вашу сцену Player и откройте сцену Main. Вы можете нажать на вкладку Main вверху редактора и сделать это.
Если Вы закрыли сцену до того, как сделать это, направьтесь в панель FileSystem и двойным нажатием щелкните Main.tscn
, чтобы снова её открыть.
Для инстанцирования Player нажмите правой кнопкой мыши на узел Main и выберите Instance Child Scene.
Во всплывающем окне двойным щелчком нажмите на Player.tscn. Персонаж должен появиться в центре окна просмотра.
Adding a camera¶
Теперь давайте добавим камеру. Точно так же, как мы сделали с Player Pivot, мы создадим основной каркас. Снова нажмите правой кнопкой мыши на узел Main и выберите в этот раз Add Child Node. Создайте новый Position3D, назовите его CameraPivot и добавьте узел Camera, как его дочерний. Ваше дерево сцены должно выглядеть таким образом.
Обратите внимание на флажок Preview, который появляется в левом верхнем углу, когда выбран параметр Camera. Вы можете нажать на него, чтобы просмотреть проекцию камеры в игре.
Мы будем использовать Pivot для поворота камеры, как будто она находится на кране. Сначала давайте разделим 3D-вид, чтобы иметь возможность свободно перемещаться по сцене и видеть то, что видит камера.
На панели инструментов прямо над вьюпортом нажмите View, затем 2 Viewports. Вы также можете нажать Ctrl + 2 (Cmd + 2 на macOS).
В нижнем представлении выберите Camera и включите предварительный просмотр камеры, нажав на флажок.
На виде сверху переместите камеру примерно на 19
единиц по оси Z (синяя).
Вот где происходит волшебство. Выберите CameraPivot и поверните его на 45
градусов вокруг оси X (с помощью красного круга). Вы увидите, что камера движется, как будто она прикреплена к крану.
Вы можете запустить сцену, нажав F6 и нажимая клавиши со стрелками перемещать персонаж.
Мы можем видеть пустое пространство вокруг персонажа из-за перспективы. В этой игре мы наоборот собираемся использовать орфографическую проекцию, чтобы лучше структурировать область геймплея и упростить для игрока считывание дистанций.
Снова выберите Camera и в Inspector установите Projection на Orthogonal и Size на 19
. Теперь персонаж должен выглядеть более плоским и поверхность должна заполнять задний план.
Таким образом, у нас есть и движение игрока, и вид. Далее мы будем работать над монстрами.