Использование KinematicBody2D¶
Введение¶
Godot предлагает несколько объектов столкновения для обеспечения как обнаружения столкновения, так и реакций. Решить, какой из них лучше использовать в Вашем проекте, может быть затруднительно. Избежать проблем и упростить разработку можно, если понять, как работает каждый из них и каковы его достоинства и недостатки. В этом учебном пособии мы рассмотрим узел KinematicBody2D и покажем несколько примеров его использования.
Примечание
Этот документ предполагает, что вы знакомы с различными физическими телами Godot. Сначала прочитайте Введение в физику.
Что такое кинематическое тело?¶
KinematicBody2D
предназначен для реализации тел, которые управляются с помощью кода. Кинематические тела обнаруживают столкновения с другими телами при движении, но на них не влияют свойства физики движка, такие, как сила тяжести или трение. Это значит, что Вы должны написать код, чтобы определить их поведение, а так же то, что у Вас есть более точный контроль над тем, как они движутся и реагируют.
Совет
На KinematicBody2D могут влиять сила тяжести и другие силы, но Вы должны рассчитать перемещение в коде. Физический движок не будет перемещать KinematicBody2D сам.
Движение и столкновения¶
При перемещении KinematicBody2D
не следует устанавливать его свойство position
напрямую. Вместо этого используются методы move_and_collide()
или move_and_slide()
. Эти методы перемещают тело вдоль заданного вектора и мгновенно останавливаются, если обнаруживается столкновение с другим телом. После столкновения KinematicBody2D любая реакция на столкновение должна быть закодирована вручную.
Предупреждение
Вы должны перемещать кинематическое тело только в обратном вызове _physics_process()
.
Два метода перемещения служат разным целям, и далее в этом учебном пособии Вы увидите примеры их работы.
move_and_collide
¶
Этот метод принимает один параметр: Vector2, указывающий относительное перемещение тела. Обычно это вектор скорости, умноженный на временной интервал кадра (delta
). Если движок обнаружит столкновение где-либо вдоль этого вектора, тело немедленно прекратит движение. Если это произойдет, метод вернет объект KinematicCollision2D.
KinematicCollision2D
— это объект, содержащий данные о столкновении и сталкивающемся объекте. С помощью этих данных можно вычислить реакцию на столкновение.
move_and_slide
¶
Метод move_and_slide()
предназначен для упрощения реализации реакции на столкновение в типовом случае, когда требуется, чтобы одно тело скользило по другому. Это особенно полезно в платформерах, или играх с видом сверху, например.
Совет
move_and_slide()
автоматически вычисляет перемещение на основе кадров, используя delta
. Не умножайте вектор скорости на``delta`` перед передачей его в move_and_slide()
.
В дополнение к вектору скорости move_and_slide()
принимает ряд других параметров, позволяющих настроить поведение скольжения:
up_direction
— значение по умолчанию:Vector2( 0, 0 )
Этот параметр позволяет определить, какие поверхности движок должен считать полом. Установка этого параметра позволяет использовать методы
is_on_floor()
,is_on_wall()
иis_on_ceiling()
для определения типа поверхности, с которой контактирует тело. Значение по умолчанию означает, что все поверхности считаются стенами.stop_on_slope
— значение по умолчанию:false
Этот параметр предотвращает скольжение тела по склонам, когда оно стоит.
max_slides
— значение по умолчанию:4
Этот параметр задаёт максимальное число столкновений перед тем, как тело остановится. Слишком маленькое значение может полностью остановить перемещение.
floor_max_angle
— значение по умолчанию:0.785398
(в радианах, эквивалентно45
градусам)Этот параметр - максимальный угол, после которого поверхность перестает считаться "полом"
infinite_inertia
— значение по умолчанию:true
Если этот параметр true
, то тело может толкать узлы RigidBody2D, игнорируя их массу, но без обнаружения столкновений с ними. Если этот параметр false
, тело столкнется с твёрдым телом и остановится.
move_and_slide_with_snap
¶
Этот метод добавляет некоторые дополнительный функционал к move_and_slide()
, добавляя параметр snap
. Пока этот вектор находится в контакте с землей, тело будет оставаться прикрепленным к поверхности. Обратите внимание — это означает, что Вы должны отключить привязывание, например, когда прыгаете. Для этого можно установить snap
в Vector2.ZERO
или использовать move_and_slide()
.
Определение столкновений¶
При использовании move_and_collide()
функция напрямую возвращает KinematicCollision2D
, и это можно использовать в коде.
При использовании move_and_slide()
возможно возникновение нескольких столкновений, пока вычисляется ответ для скольжения. Для обработки этих столкновений используйте get_slide_count()
и get_slide_collision()
:
# Using move_and_collide.
var collision = move_and_collide(velocity * delta)
if collision:
print("I collided with ", collision.collider.name)
# Using move_and_slide.
velocity = move_and_slide(velocity)
for i in get_slide_count():
var collision = get_slide_collision(i)
print("I collided with ", collision.collider.name)
// Using MoveAndCollide.
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
GD.Print("I collided with ", ((Node)collision.Collider).Name);
}
// Using MoveAndSlide.
velocity = MoveAndSlide(velocity);
for (int i = 0; i < GetSlideCount(); i++)
{
var collision = GetSlideCollision(i);
GD.Print("I collided with ", ((Node)collision.Collider).Name);
}
Примечание
get_slide_count() считает только те моменты, когда тело сталкивалось и изменяло направление.
Подробные сведения о том, какие данные о столкновениях возвращаются, см. в разделе KinematicCollision2D.
Какой метод перемещения следует использовать?¶
Основной вопрос от новых пользователей Godot: «Как определить, какую функцию движения использовать?» Часто ответ заключается в использовании move_and_slide()
, потому что это «проще», но это не обязательно так. Один из способов думать об этом заключается в том, что move_and_slide()
является частным случаем, а move_and_collide()
— более общим. Например, следующие два фрагмента кода приводят к одной и той же реакции на столкновение:
# using move_and_collide
var collision = move_and_collide(velocity * delta)
if collision:
velocity = velocity.slide(collision.normal)
# using move_and_slide
velocity = move_and_slide(velocity)
// using MoveAndCollide
var collision = MoveAndCollide(velocity * delta);
if (collision != null)
{
velocity = velocity.Slide(collision.Normal);
}
// using MoveAndSlide
velocity = MoveAndSlide(velocity);
Все, что Вы делаете с помощью move_and_slide()
, также может быть сделано с помощью move_and_collide()
, но это может потребовать немного больше кода. Однако, как мы увидим в примерах ниже, есть случаи, когда move_and_slide()
не предоставляет возможности реализовать реакцию, которая нам нужна.
В приведенном выше примере мы сохраняем скорость, которую возвращает move_and_slide()
, обратно в переменную velocity
. Это нужно потому, что при столкновении персонажа с окружением функция внутри пересчитывает скорость, чтобы отразить замедление.
Например, если Ваш персонаж упал на пол, Вы не хотите, чтобы он накапливал вертикальную скорость из-за эффекта гравитации. Вместо этого необходимо, чтобы его вертикальная скорость была обнулена.
move_and_slide()
также может пересчитывать скорость кинематического тела несколько раз в цикле, так как для создания плавного движения он перемещает персонажа и сталкивается до пяти раз по умолчанию. В конце процесса функция возвращает новую скорость персонажа, которую можно сохранить в нашей переменной velocity
и использовать в следующем кадре.
Примеры¶
Чтобы увидеть эти примеры в действии, загрузите образец проекта: using_kinematic2d.zip
.
Перемещение и стены¶
Если Вы загрузили образец проекта, то этот пример находится в файле «BasicMovent.tscn».
В этом примере добавьте KinematicBody2D
с двумя дочерними элементами: Sprite
и CollisionShape2D
. Используйте иконку Godot «icon.png» в качестве текстуры Sprite (перетащите её из дока файловой системы в свойство Texture
нашего Sprite
). В свойстве Shape
в CollisionShape2D
выберите «Новый RectangleShape2D» и установите размер прямоугольника, чтобы он заполнил всё изображение спрайта.
Примечание
Посмотрите Перемещение в 2D пространстве примеры реализации 2D схем движения.
Присоедините скрипт к KinematicBody2D и добавьте следующий код:
extends KinematicBody2D
var speed = 250
var velocity = Vector2()
func get_input():
# Detect up/down/left/right keystate and only move when pressed.
velocity = Vector2()
if Input.is_action_pressed('ui_right'):
velocity.x += 1
if Input.is_action_pressed('ui_left'):
velocity.x -= 1
if Input.is_action_pressed('ui_down'):
velocity.y += 1
if Input.is_action_pressed('ui_up'):
velocity.y -= 1
velocity = velocity.normalized() * speed
func _physics_process(delta):
get_input()
move_and_collide(velocity * delta)
using Godot;
using System;
public class KBExample : KinematicBody2D
{
public int Speed = 250;
private Vector2 _velocity = new Vector2();
public void GetInput()
{
// Detect up/down/left/right keystate and only move when pressed
_velocity = new Vector2();
if (Input.IsActionPressed("ui_right"))
_velocity.x += 1;
if (Input.IsActionPressed("ui_left"))
_velocity.x -= 1;
if (Input.IsActionPressed("ui_down"))
_velocity.y += 1;
if (Input.IsActionPressed("ui_up"))
_velocity.y -= 1;
_velocity = _velocity.Normalized() * Speed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
MoveAndCollide(_velocity * delta);
}
}
Запустите сцену и Вы увидите, что move_and_collide()
работает, как ожидалось, перемещая тело вдоль вектора скорости. Теперь давайте посмотрим, что происходит, когда Вы добавляете некоторые препятствия. Добавьте StaticBody2D с прямоугольной формой столкновения. Для визуализации Вы можете использовать спрайт, Polygon2D, или включить «Видимые Формы Столкновения» из меню «Отладка».
Запустите сцену еще раз и попытайтесь двигаться в препятствие. Вы увидите, что KinematicBody2D
не может проникнуть в него. Теперь попробуйте двигаться в препятствие под углом, и обнаружите, что препятствие действует как клей — ощущение, что тело застревает.
Это происходит из-за отсутствия реакции на столкновение. move_and_collide()
останавливает движение тела при столкновении. Нам нужно кодировать любую реакцию, которую мы хотим получить после столкновения.
Попробуйте изменить функцию на move_and_slide(velocity)
и запустить снова. Обратите внимание, что из расчета скорости была удалена delta
.
move_and_slide()
обеспечивает реакцию на столкновения по умолчанию при скольжении тела вдоль объекта столкновения. Это применимо для большого количества типов игр, и может быть достаточным, чтобы получить то поведение, которое Вы хотите.
Отскок/отражение¶
Что, если Вам не нужна реакция на скользящее столкновение? К примеру («BounceandCollide.tscn» в проекте с примерами), у нас есть персонаж, стреляющий пулями, и мы хотим, чтобы пули отскакивали от стен.
В этом примере используются три сцены. Основная сцена содержит персонажа и стены. Bullet и Wall — это отдельные сцены, чтобы их можно было инстанцировать.
Персонаж управляется клавишами w и s для движения вперёд и назад. Для прицеливания используется указатель мыши. Ниже приведен код персонажа с использованием move_and_slide()
:
extends KinematicBody2D
var Bullet = preload("res://Bullet.tscn")
var speed = 200
var velocity = Vector2()
func get_input():
# Add these actions in Project Settings -> Input Map.
velocity = Vector2()
if Input.is_action_pressed('backward'):
velocity = Vector2(-speed/3, 0).rotated(rotation)
if Input.is_action_pressed('forward'):
velocity = Vector2(speed, 0).rotated(rotation)
if Input.is_action_just_pressed('mouse_click'):
shoot()
func shoot():
# "Muzzle" is a Position2D placed at the barrel of the gun.
var b = Bullet.instance()
b.start($Muzzle.global_position, rotation)
get_parent().add_child(b)
func _physics_process(delta):
get_input()
var dir = get_global_mouse_position() - global_position
# Don't move if too close to the mouse pointer.
if dir.length() > 5:
rotation = dir.angle()
velocity = move_and_slide(velocity)
using Godot;
using System;
public class KBExample : KinematicBody2D
{
private PackedScene _bullet = (PackedScene)GD.Load("res://Bullet.tscn");
public int Speed = 200;
private Vector2 _velocity = new Vector2();
public void GetInput()
{
// add these actions in Project Settings -> Input Map
_velocity = new Vector2();
if (Input.IsActionPressed("backward"))
{
_velocity = new Vector2(-Speed/3, 0).Rotated(Rotation);
}
if (Input.IsActionPressed("forward"))
{
_velocity = new Vector2(Speed, 0).Rotated(Rotation);
}
if (Input.IsActionPressed("mouse_click"))
{
Shoot();
}
}
public void Shoot()
{
// "Muzzle" is a Position2D placed at the barrel of the gun
var b = (Bullet)_bullet.Instance();
b.Start(GetNode<Node2D>("Muzzle").GlobalPosition, Rotation);
GetParent().AddChild(b);
}
public override void _PhysicsProcess(float delta)
{
GetInput();
var dir = GetGlobalMousePosition() - GlobalPosition;
// Don't move if too close to the mouse pointer
if (dir.Length() > 5)
{
Rotation = dir.Angle();
_velocity = MoveAndSlide(_velocity);
}
}
}
И код для пули:
extends KinematicBody2D
var speed = 750
var velocity = Vector2()
func start(pos, dir):
rotation = dir
position = pos
velocity = Vector2(speed, 0).rotated(rotation)
func _physics_process(delta):
var collision = move_and_collide(velocity * delta)
if collision:
velocity = velocity.bounce(collision.normal)
if collision.collider.has_method("hit"):
collision.collider.hit()
func _on_VisibilityNotifier2D_screen_exited():
queue_free()
using Godot;
using System;
public class Bullet : KinematicBody2D
{
public int Speed = 750;
private Vector2 _velocity = new Vector2();
public void Start(Vector2 pos, float dir)
{
Rotation = dir;
Position = pos;
_velocity = new Vector2(speed, 0).Rotated(Rotation);
}
public override void _PhysicsProcess(float delta)
{
var collision = MoveAndCollide(_velocity * delta);
if (collision != null)
{
_velocity = _velocity.Bounce(collision.Normal);
if (collision.Collider.HasMethod("Hit"))
{
collision.Collider.Call("Hit");
}
}
}
public void OnVisibilityNotifier2DScreenExited()
{
QueueFree();
}
}
Работа происходит в _physics_process()
. Вызов move_and_collide()
, если были столкновения, возвращает объект KinematicCollision2D
; в противном случае возвращается значение Nil
.
Если есть столкновение, мы используем его normal
для отражения velocity
пули с помощью метода Vector2.bounce()
.
Если у сталкивающегося объекта (collider
) есть метод hit
, мы также вызываем и его. В проекте примера мы добавили эффект цветной вспышки на стене, чтобы это продемонстрировать.
Перемещения в платформере¶
Попробуем еще один популярный пример: 2D платформер. move_and_slide()
идеально подходит для быстрого получения функционала прыжка и бега персонажа. Если Вы загрузили проект с примерами, то можете найти его в «Platformer.tscn».
В этом примере предполагается, что уровень создан из объектов StaticBody2D
. Они могут быть любой формы и размера. В проекте примера мы используем Polygon2D для создания форм платформ.
Вот код для тела игрока:
extends KinematicBody2D
export (int) var run_speed = 100
export (int) var jump_speed = -400
export (int) var gravity = 1200
var velocity = Vector2()
var jumping = false
func get_input():
velocity.x = 0
var right = Input.is_action_pressed('ui_right')
var left = Input.is_action_pressed('ui_left')
var jump = Input.is_action_just_pressed('ui_select')
if jump and is_on_floor():
jumping = true
velocity.y = jump_speed
if right:
velocity.x += run_speed
if left:
velocity.x -= run_speed
func _physics_process(delta):
get_input()
velocity.y += gravity * delta
if jumping and is_on_floor():
jumping = false
velocity = move_and_slide(velocity, Vector2(0, -1))
using Godot;
using System;
public class KBExample : KinematicBody2D
{
[Export] public int RunSpeed = 100;
[Export] public int JumpSpeed = -400;
[Export] public int Gravity = 1200;
Vector2 velocity = new Vector2();
bool jumping = false;
public void GetInput()
{
velocity.x = 0;
bool right = Input.IsActionPressed("ui_right");
bool left = Input.IsActionPressed("ui_left");
bool jump = Input.IsActionPressed("ui_select");
if (jump && IsOnFloor())
{
jumping = true;
velocity.y = JumpSpeed;
}
if (right)
velocity.x += RunSpeed;
if (left)
velocity.x -= RunSpeed;
}
public override void _PhysicsProcess(float delta)
{
GetInput();
velocity.y += Gravity * delta;
if (jumping && IsOnFloor())
jumping = false;
velocity = MoveAndSlide(velocity, new Vector2(0, -1));
}
}
При использовании move_and_slide()
функция возвращает вектор, представляющий движение, оставшееся после скользящего столкновения. Сохраняя это значение обратно в velocity``персонажа, мы можем плавно двигаться вверх и вниз. Попробуйте удалить ``velocity =
и посмотреть, что произойдет, когда Вы этого не делаете.
Также обратите внимание, что мы добавили Vector2 (0, -1)
в качестве нормали к полу. Этот вектор указывает прямо вверх. В результате, если персонаж сталкивается с объектом, имеющим эту нормаль, объект будет считаться полом.
Использование нормали пола позволяет нам реализовать прыжки, используя is_on_floor()
. Эта функция возвращает значение true
только после такого столкновения move_and_slide()
, при котором нормаль тела столкновения находится в пределах 45 градусов от заданного вектора пола. Можно управлять максимальным углом, задав floor_max_angle
.
Этот угол также позволяет реализовать другие функции, такие как прыжки от стены, используя, например, is_on_wall()
.