KinematicBody2D 사용하기

소개

Godot은 충돌 감지와 반응성을 모두 제공하기 위해 많은 충돌 물체를 제공합니다. 프로젝트에 사용할 항목을 결정하는 것은 혼란스러울 수 있습니다. 각각의 작동 방식 및 장단점이 무엇인지 이해한다면 문제를 피하고 개발을 간소화할 수 있습니다. 이 튜토리얼에서는:ref:`KinematicBody2D <class_KinematicBody2D> 노드를 살펴보고 사용 방법에 대한 몇 가지 예를 보여 드리겠습니다.

주석

이 문서는 당신이 Godot의 다양한 물리학 body들에 대해 잘 알고 있다고 가정합니다. 먼저 :ref:`doc_physics_introduction`를 읽어주세요.

kinematic body란 무엇인가?

KinematicBody2D is for implementing bodies that are to be controlled via code. They detect collisions with other bodies when moving, but are not affected by engine physics properties, like gravity or friction. While this means that you have to write some code to create their behavior, it also means you have more precise control over how they move and react.

참고

`KinematicBody2D`는 중력과 다른 힘에 의해 영향을 받을 수 있지만 코드로 움직임을 계산해야 합니다. 물리 엔진은 `KinematicBody2D`를 움직이지 않습니다.

Movement and collision

KinematicBody2D``를 옮길 ``position``속성을 직접 설정해서는 됩니다. 대신 ``move_and_collide() 또는 move_and_slide() 매서드를 사용해야 합니다. 이러한 방법은 주어진 벡터를 따라 body를 움직이며 충돌이 감지될 경우 즉시 중지됩니다. KineticBody2D가 충돌한 후에는 *충돌 반응*을 수동으로 코딩해야 합니다.

경고

Kinematic body의 움직임은 _physics_process() 콜백 함수 로만 이루어져야 합니다.

The two movement methods serve different purposes, and later in this tutorial, you'll see examples of how they work.

move_and_collide

이 메서드는 하나의 매개 변수를 가집니다: body의 상대적인 움직임을 나타내는 Vector2. 일반적으로 이것은 속도 벡터에 프레임 타임스탬프(delta)를 곱한 것입니다. 엔진이 이 벡터를 따라 어느 곳에서든 충돌을 감지하면 body는 즉시 작동을 멈춥니다. 이 경우 메서드는 KinematicCollision2D 개체를 반환합니다.

KinematicCollision2D is an object containing data about the collision and the colliding object. Using this data, you can calculate your collision response.

move_and_slide

move_and_slide() 방법은 하나의 body가 다른 하나의 body를 따라 미끄러지도록 하려는 일반적인 경우에서 충돌 반응을 단순화하기 위한 것입니다. 이것은 예를 들어 플랫포머나 하향식 게임에서 특히 유용합니다.

참고

move_and_slide()``는 ``delta``를 사용하여 프레임 기반 이동을 자동으로 계산합니다. 속도 벡터를 ``move_and_slide()``에 전달하기 전에 ``delta 로 곱하지 마십시오.

속도 벡터 외에도 ``move_and_slide()``에는 여러 가지 다른 매개 변수가 포함되어 있어 슬라이드 동작을 지정할 수 있습니다:

  • floor_normal - default value: Vector2( 0, 0 )

    이 매개 변수를 사용하면 엔진이 바닥으로 간주해야 하는 표면을 정의할 수 있습니다. 이를 설정하면 is_on_floor(), is_on_wall()`, is_on_ceiling() 매서드를 사용하여 신체와 접촉하는 표면의 유형을 탐지할 수 있습니다. 기본값으론 모든 표면이 벽으로 간주됩니다.

  • slope_stop_min_velocity - default value: 5

    이것은 경사면에 서 있을 때의 최소 속도입니다. 이렇게 하면 정지 상태에서 body가 비탈 아래로 미끄러지는 것을 방지할 수 있습니다.

  • max_bounces - default value: 4

    이것은 body가 움직임을 멈추기 전의 최대 충돌 횟수입니다. 이 값을 너무 낮게 설정하면 이동이 완전히 차단될 수 있습니다.

  • floor_max_angle - default value: 0.785398 (라디안으론, 45 도와 같다)

    이것은 지표면이 더 이상 "바닥"으로 간주되지 않는 최대 각도입니다.

move_and_slide_with_snap

이 방법은 snap 매개 변수를 추가하여 move_and_slide()``에 가지 기능을 더합니다. 벡터가 지면과 접촉하는 body는 표면에 부착된 상태를 유지합니다. 이는 점프할 스냅을 비활성화해야 함을 의미합니다. ``snap``을``Vector2(0, 0) 로 설정하거나 move_and_slide() 를 대신 사용하여 이 작업을 수행할 수 있습니다.

사용해야할 이동 매서드는 무엇입니까?

A common question from new Godot users is: "How do you decide which movement function to use?" Often, the response is to use move_and_slide() because it's "simpler", but this is not necessarily the case. One way to think of it is that move_and_slide() is a special case, and move_and_collide() is more general. For example, the following two code snippets result in the same collision response:

../../_images/k2d_compare.gif
# 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()``가 당신이 원하는 대답을 제공하지 않는 경우도 있습니다.

예시

예제가 실행되는것을 보기 위해선, 샘플 프로젝트를 다운로드하십시오: using_kinematic2d.zip.

이동 및 벽

If you've downloaded the sample project, this example is in "BasicMovement.tscn".

For this example, add a KinematicBody2D with two children: a Sprite and a CollisionShape2D. Use the Godot "icon.png" as the Sprite's texture (drag it from the Filesystem dock to the Texture property of the Sprite). In the CollisionShape2D's Shape property, select "New RectangleShape2D" and size the rectangle to fit over the sprite image.

주석

2D 이동 계획을 구현하는 예는 :ref:`doc_2d_movement`을 참조하시오.

KineticBody2D에 스크립트를 연결하고 다음 코드를 추가합니다:

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;
        }
    }

    public override void _PhysicsProcess(float delta)
    {
        getInput();
        MoveAndCollide(velocity * delta);
    }
}

이 장면을 보면 ``move_and_collide()``가 예상대로 작동하면서 body가 속도 벡터를 따라 움직이는 것을 볼 수 있습니다. 이제 장애물을 추가하면 어떻게 되는지 봅시다. 직사각형 충돌 모양으로 :ref:`StaticBody2D <class_StaticBody2D>`를 추가합니다. 가시성을 위해 "Debug" 메뉴에서 스프라이트, Polygon2D를 사용하거나 "Visible Collision Shapes"를 켤 수 있습니다.

장면을 다시 실행하고 장애물 안으로 들어가 보십시오. 당신은 ``KinematicBody2D``가 장애물을 통과할 수 없다는 것을 알게 될 것입니다. 하지만, 한 각도로 장애물 안으로 들어가 보세요. 그러면 장애물이 접착제처럼 작용한다는 것을 알 수 있을 것입니다 - 마치 body가 달라붙는 것 같은 느낌이 듭니다.

이 문제는 *충돌 대응*이 없기 때문에 발생합니다. ``move_and_collide()``는 충돌이 일어날 때 몸의 움직임을 멈춥니다. 우리는 충돌 로부터 우리가 원하는 어떤 대응도 코드화 할 필요가 있습니다.

함수를 ``move_and_slide(velocity)``로 변경하고 다시 실행해 보십시오. 속도 계산에서 ``delta``를 제거했습니다.

move_and_slide() 는 충돌 물체를 따라 몸을 미끄러뜨리는 기본 충돌 반응을 제공합니다. 이것은 많은 게임 유형에 유용하며, 여러분이 원하는 행동을 하기 위해 필요한 모든 것일 수 있습니다.

Bouncing/reflecting

슬라이딩 충돌 반응을 원하지 않을 경우 어떻게 해야 할까요? 이 예시(샘플 프로젝트의 "BounceandCollide.tscn") 에서, 우리는 총알을 쏘는 캐릭터가 있고 총알이 벽에서 튀어 나오길 바랍니다.

이 예시에서는 세 장면을 사용합니다. 메인 장면에는 플레이어와 벽이 포함됩니다. 총알과 벽은 각각 다른 장면이기 때문에 예시로 들 수 있습니다.

플레이어는 앞뒤로 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(((Node2D)GetNode("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 collsion = MoveAndCollide(_velocity * delta);
        if (collsion != null)
        {
            _velocity = _velocity.Bounce(collsion.Normal);
            if (collsion.Collider.HasMethod("Hit"))
            {
                collsion.Collider.Hit();
            }
        }
    }

    public void OnVisibilityNotifier2DScreenExited()
    {
        QueueFree();
    }
}

The action happens in _physics_process(). After using move_and_collide(), if a collision occurs, a KinematicCollision2D object is returned (otherwise, the return is Nil).

만약 다시 충돌이 일어난다면, 우리는 충돌의 normal``을 총알의 ``velocity``를  ``Vector2.bounce() 메서드로 반영하는 데 사용합니다.

충돌물체(collider)가 hit 메서드를 갖고 있다면, 우리도 그것을 호출한다. 예제 프로젝트에서는, 이를 시연하기 위해 벽에 깜박이는 색 효과를 추가했습니다.

../../_images/k2d_bullet_bounce.gif

Platformer movement

인기 있는 예를 하나 더 들어 보겠습니다: 2D 플랫포머입니다. ``move_and_slide()``는 기능성 캐릭터 제어기를 신속하게 작동하는 데 이상적입니다. 샘플 프로젝트를 다운로드한 경우, "Platformer.tscn"에서 찾을 수 있습니다.

이 예에서는, ``StaticBody2D``로 구성된 레벨이 있다고 가정합니다. 그들은 어떤 모양이나 크기가 될 수 있습니다. 샘플 프로젝트에서는, 플랫폼 모양을 만들기 위해 :ref:`Polygon2D <class_Polygon2D>`를 사용하고 있습니다.

플레이어 body의 코드는 다음과 같습니다:

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));
    }
}
../../_images/k2d_platform.gif

When using move_and_slide(), the function returns a vector representing the movement that remained after the slide collision occurred. Setting that value back to the character's velocity allows us to smoothly move up and down slopes. Try removing velocity = and see what happens if you don't do this.

또한 우리는 ``Vector2(0, -1)``를 바닥의 정상으로 추가했습니다. 이것은 바로 위쪽을 가리키는 벡터입니다. 즉, 캐릭터가 이와 같은 정상 상태의 개체와 충돌하면 바닥으로 간주됩니다.

바닥을 정상적으로 사용하면 is_on_floor()``을 사용함으로써 점프 작업을 있습니다. 함수는 충돌 본체의 정상이 주어진 바닥 벡터에서 45도 이내인 ``move_and_slide() 충돌 후에만 true 를 반환합니다.

This also allows you to implement other features (like wall jumps) using is_on_wall(), for example.