물리 소개

게임 개발 중 종종 게임의 두 물체가 교차하거나 접촉할 때를 알아야 합니다. 이를 충돌 감지 라고 합니다. 충돌이 감지될 때, 일반적이라면 무언가가 발생하길 원할겁니다. 이를 충돌 응답이라고 합니다.

Godot는 충돌 감지와 반응을 모두 제공하기 위해 2D와 3D로 구성된 다수의 충돌 객체를 제공합니다. 프로젝트에 이 중 어떠한 것을 사용할지 결정하려는 것은 혼란스러울 수 있습니다. 하지만 각각의 작동 방식과 장단점을 이해한다면 이러한 문제를 피하고 개발을 간소화할 수 있습니다.

이 가이드에서 다음 내용을 배울 것입니다:

  • Godot의 4가지 충돌 객체 타입
  • 각 충돌 객체의 작동 방식
  • 한 유형을 다른 유형보다 선택해야 하는 시기 및 이유

주석

이 문서의 예제에서는 2D 객체를 사용합니다. 모든 2D 객체와 충돌 모양은 3D에서 직접적으로 동등하며 대부분의 경우 거의 동일한 방식으로 작동합니다.

충돌 객체

Godot는 CollisionObject2D에서 확장된 4가지 종류의 물리 바디(body)를 제공합니다:

  • Area2D
    Area2D 노드는 감지(detection)영향력(influence)을 제공합니다. 그들은 객체들이 언제 겹치는지를 감지할 수 있고, 바디가 들어가거나 나올 때 시그널을 보낼 수 있습니다. Area2D는 또한 정의된 영역에서 중력이나 제동과 같은 물리적 특성을 치환하는데 사용될 수도 있습니다.

나머지 3개의 body는 PhysicsBody2D에서 확장됩니다:

  • StaticBody2D
    정적 바디는 물리 엔진에 의해 움직이지 않는 물체입니다. 그것은 충돌 감지에는 관여하지만, 충돌에 대응하여 움직이지는 않습니다. 이것들은 환경의 일부이거나 동적 동작이 필요하지 않은 객체에 가장 많이 사용됩니다.
  • RigidBody2D
    이것은 시뮬레이션된 2D 물리를 구현하는 노드입니다. RigidBody2D를 직접적으로 제어하지는 않지만, 그대신 (중력, 충격 등) 힘을 가하며 물리 엔진은 그것의 결과적인 움직임을 계산합니다. 리지드 바디 사용에 대해 더 알아보기.
  • KinematicBody2D
    충돌 감지 기능을 제공하지만, 물리는 제공하지 않는 body입니다. 모든 이동 및 충돌 반응은 코드로 구현되어야 합니다.

충돌 모양

물리 바디는 원하는 수의 Shape2D 객체를 자식으로 수용할 수 있습니다. 이러한 모양은 객체의 충돌 경계를 정의하고 다른 객체와의 접촉을 감지하는 데 사용됩니다.

주석

충돌을 감지하기 위해, 최소한 하나 이상의 Shape2D가 객체에 할당되어야 합니다.

모양을 지정하는 가장 일반적인 방법은 객체의 자식으로 CollisionShape2D또는 CollisionPolygon2D를 추가하는 것입니다. 이러한 노드들은 편집기 작업 공간에서 직접 모양을 그릴 수 있도록 합니다.

중요

편집기에서 충돌 모양을 변경하지 않도록 주의하십시오. 인스펙터의 "Scale" 속성은 (1, 1)로 유지되어야 합니다. 충돌 모양을 변경할 때는 항상 Node2D 크기 핸들이 아닌 크기 핸들을 사용해야 합니다. 도형의 크기를 조절하는 것은 예기치 않은 충돌 행동이 일어날 수 있습니다.

../../_images/player_coll_shape1.png

물리 프로세스 콜백

물리 엔진은 성능을 향상시키기 위해 여러 개의 스레드를 생성할 수 있으므로, 물리 작업을 처리하는 데 최대 프레임까지 사용할 수 있습니다. 이로 인해, 위치(position) 또는 선형 속도(linear velocity)와 같은 바디의 상태 변수의 값이 현재 프레임에 정확하지 않을 수 있습니다.

이러한 부정확성을 피하기 위해, 바디의 속성에 접근해야 하는 모든 코드는 각 물리 단계 전에 일정한 프레임률 (기본 초당 60회)로 호출되는 Node._physics_process() 콜백에서 실행되어야 합니다.

충돌 레이어와 마스크

가장 강력하지만 자주 오해되는 충돌 기능로 충돌 레이어 시스템이 있습니다. 이 시스템을 사용하면 다양한 객체 간의 복잡한 상호 작용을 구축할 수 있습니다. 주요 개념은 레이어마스크입니다. 각 CollisionObject2D 에는 서로 상호 작용할 수 있는 20개의 서로 다른 물리 레이어가 있습니다.

각 속성을 차례대로 살펴보겠습니다:

  • collision_layer
    이것은 객체가 나타나는 레이어를 형성합니다. 기본적으로 모든 바디는 레이어 ``1``에 있습니다.
  • collision_mask
    이것은 바디가 충돌을 위해 스캔하는 레이어를 뜻합니다. 객체가 마스크 레이어 중 하나에 있지 않으면, 바디는 이를 무시합니다. 기본적으로 모든 바디는 레이어 1을 스캔합니다.

이러한 속성은 코드를 통해, 혹은 인스펙터에서 편집하여 구성할 수 있습니다.

각 레이어를 사용하는 대상을 추적하는 것은 어려울 수 있으므로, 사용 중인 레이어에 이름을 지정하는 것이 유용할 수 있습니다. 이름은 프로젝트 설정 -> Layer Names에서 지정할 수 있습니다.

../../_images/physics_layer_names.png

예시:

당신의 게임에는 4가지의 노드 타입이 있습니다: 벽, 플레이어, 적, 코인. 플레이어와 적 모두 벽과 충돌해야 합니다. 플레이어 노드는 적과 코인의 충돌을 모두 감지해야 하지만, 적과 코인은 서로 무시해야 합니다.

레이어 1-4를 "walls", "player", "enemies", 그리고 "coins"으로 이름을 지정하는 걸로 시작하고, "Layer" 속성을 사용하여 각 노드 타입을 각각의 레이어에 배치합니다. 그런 다음 각 노드가 상호 작용할 레이어를 선택하여 각 노드의 "Mask" 속성을 설정합니다. 예를 들어, 플레이어의 설정은 다음과 같습니다:

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

Area2D

Area 노드는 감지영향력을 제공합니다. 그들은 물체가 중복되는 때를 감지하고 바디가 들어가거나 나올 때 시그널을 방출할 수 있습니다. Area는 정의된 영역에서 중력 또는 제동과 같은 물리적 특성을 치환하는 데 사용될 수도 있습니다.

Area2D에는 3가지 주요 용도가 있습니다:

  • 특정 지역에서 (중력과 같은) 물리적 매개변수를 오버라이딩 합니다.
  • 다른 바디가 특정 지역에 들어오거나 나가는 때를 감지하거나 현재 지역에 있는 바디가 무엇인 지를 탐지합니다.
  • 다른 영역들이 겹치는지 확인합니다.

기본적으로, 영역은 마우스 및 터치스크린 입력도 받습니다.

StaticBody2D

정적 바디는 물리 엔진에 의해 움직이지 않는 물체입니다. 그것은 충돌 감지에는 참여하지만 충돌에 대응하여 움직이지 않습니다. 그러나, constant_linear_velocityconstant_angular_velocity의 특성을 이용하여 마치 움직이는 것처럼 충돌하는 바디에 움직임이나 회전을 전달할 수 있습니다.

StaticBody2D 노드는 환경에 속하거나 동적 동작을 수행할 필요가 없는 객체에 가장 많이 사용됩니다.

StaticBody2D 이용 예시:

  • 플랫폼 (이동 플랫폼 포함)
  • 컨베이어 벨트
  • 벽 및 기타 장애물

RigidBody2D

이것은 시뮬레이션 된 2D 물리를 구현하는 노드입니다. 당신은 RigidBody2D를 직접 제어하지는 않습니다. 대신 힘을 가하게 되면 물리 엔진은 다른 물체와의 충돌을 포함한 결과 움직임과 충돌, 회전 등의 충돌 반응을 계산합니다.

인스펙터에서 설정할 수 있는 "Mass", "Friction", 또는 "Bounce"와 같은 특성을 통해 리지드 바디의 동작을 수정할 수 있습니다.

바디의 행동은 또한 프로젝트 설정 -> Physics에서 설정한 월드 속성의 영향을 받거나 전역 물리 특성을 오버라이딩 하는 Area2D를 입력한 것에 영향을 받습니다.

리지드 바디가 쉬는 상태이고 한동안 움직이지 않으면, 잠들게 됩니다. 잠든 바디는 정적 바디처럼 작용하며, 그 힘은 물리 엔진에 의해 계산되지 않습니다. 충돌 또는 코드를 통해 힘이 가해지면 바디가 깨어나게 됩니다.

리지드 바디 모드

리지드 바디는 다음 네 가지 모드 중 하나로 설정할 수 있습니다:

  • Rigid - 이 바디는 물리적 물체처럼 작동합니다. 그것은 다른 바디들과 충돌하고 그것에 적용되는 힘에 반응합니다. 기본 모드입니다.
  • Static - 이 바디는 StaticBody2D처럼 동작하며 움직이지 않습니다.
  • Character - "Rigid" 모드와 유사하지만 바디를 회전할 수는 없습니다.
  • Kinematic - 이 바디는 KinematicBody2D처럼 작동하며 코드를 통해 움직여야 한다.

RigidBody2D 사용하기

리지드 바디를 사용하는 것의 이점 중 하나는 코드를 쓰지 않고도 많은 행동을 "자유롭게" 할 수 있다는 것입니다. 예를 들어, 떨어지는 블록으로 "앵그리 버드"-스타일의 게임을 만드는 경우, RigidBody2D들을 만들고 해당 속성을 조정하기만 하면 됩니다. 쌓기, 낙하 및 튕김은 물리 엔진에 의해 자동으로 계산됩니다.

하지만, 만약 여러분이 바디를 어느 정도 통제하고 싶다면, 조심히 다루어야 합니다 - position, linear_velocity 또는 리지드 바디의 다른 물리적 특성들을 바꾸면 예상치 못한 행동을 초래할 수 있습니다. 물리학 관련 속성을 변경해야 하는 경우 _physics_process() 대신 _integrate_forces() 콜백을 사용해야 합니다. 이 콜백에서는 안전하게 속성을 변경하고 물리 엔진과 동기화할 수 있는 Physics2DDirectBodyState에 접근할 수 있습니다.

예를 들어, 다음은 "소행성" 스타일 우주선의 코드입니다:

extends RigidBody2D

var thrust = Vector2(0, 250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        applied_force = thrust.rotated(rotation)
    else:
        applied_force = Vector2()
    var rotation_dir = 0
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_left"):
        rotation_dir -= 1
    applied_torque = rotation_dir * torque
class Spaceship : RigidBody2D
{
    private Vector2 thrust = new Vector2(0, 250);
    private float torque = 20000;

    public override void _IntegrateForces(Physics2DDirectBodyState state)
    {
        if (Input.IsActionPressed("ui_up"))
            SetAppliedForce(thrust.Rotated(Rotation));
        else
            SetAppliedForce(new Vector2());

        var rotationDir = 0;
        if (Input.IsActionPressed("ui_right"))
            rotationDir += 1;
        if (Input.IsActionPressed("ui_left"))
            rotationDir -= 1;
        SetAppliedTorque(rotationDir * torque);
    }
}

linear_velocity 또는 angular_velocity를 직접 설정하는 것이 아니라 바디에 힘을 (thrusttorque) 적용하고 그 결과적인 움직임을 물리 엔진이 계산하도록 한다는 점에 유의해야 합니다.

주석

리지드 바디가 잠에 들 때 _integrate_forces() 함수는 호출되지 않을 것입니다. 이 동작을 오버라이드 하려면 충돌을 만들거나, 충돌을 적용하거나, 힘을 가하거나, can_sleep 속성을 비활성화하여 바디를 깨어 있게 해야 합니다. 이것이 퍼포먼스에 안 좋은 영향을 줄 수 있으니 조심하세요.

접촉 알림

기본적으로 리지드 바디는 접촉면을 추적하지 않는데, 많은 바디가 씬 내에 있을 경우 엄청난 양의 메모리가 요구되기 때문입니다. 접촉 알림를 사용하려면 contacts_reported 속성을 0이 아닌 값으로 설정합니다. 그렇게 하면 접촉 알림은 Physics2DDirectBodyState.get_contact_count()이나 관련된 기능을 통해 얻을 수 있습니다.

시그널을 통한 접촉 모니터링은 contact_monitor 속성을 통해 활성화할 수 있습니다. 사용 가능한 시그널 목록은 RigidBody2D를 참조하십시오.

KinematicBody2D

KinematicBody2D 바디는 다른 물체와의 충돌을 감지하지만 중력이나 마찰과 같은 물리적 성질의 영향을 받지 않습니다. 대신 코드를 통해 사용자가 제어해야 합니다. 물리 엔진은 키네마틱 바디를 움직이지 않습니다.

키네마틱 바디를 옮길 때는 위치를 직접 정해서는 안 됩니다. 대신 move_and_collide() 또는 move_and_slide() 메서드를 사용합니다. 이 메서드들은 주어진 벡터를 따라 바디를 움직이며, 다른 바디와의 충돌이 감지되면 즉시 정지합니다. 바디가 충돌한 후에는 모든 충돌 응답을 수동으로 코딩해야 합니다.

키네마틱 충돌 응답

충돌 후 바디가 튀어 오르거나, 벽을 따라 미끄러지거나, 부딪힌 개체의 속성을 변경하기를 원할 수 있습니다. 충돌 응답을 다루기 위해선 KinematicBody2D을 움직이기 위해 사용된 메서드에 의해 결정됩니다.

move_and_collide

이 함수는 move_and_collide()를 사용할 때 충돌과 충돌 물체에 대한 정보가 포함된 KinematicCollision2D 객체를 반환합니다. 이 정보를 사용하여 응답을 확인할 수 있습니다.

예를 들어, 충돌이 발생한 공간의 지점을 찾으려면 다음과 같이 하면 됩니다:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.position
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
        {
            var collisionPoint = collisionInfo.GetPosition();
        }
    }
}

혹은 충돌 개체가 튀어나오도록 하려면 다음과 같습니다:

extends KinematicBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.normal)
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
            velocity = velocity.Bounce(collisionInfo.Normal);
    }
}

move_and_slide

슬라이딩은 일반적인 충돌 반응입니다; 플레이어가 탑뷰 게임에서 벽을 따라 움직이거나 플랫폼 안에서 오르내리는 경사를 상상해 보십시오. move_and_collide()를 사용한 후 직접 이를 코딩할 수 있지만 move_and_slide()는 많은 코드를 작성하지 않고 슬라이딩 이동을 실행할 수 있는 편리한 방법입니다.

경고

move_and_slide() 는 자동으로 타임 스텝를 계산에 포함하므로, 속도 벡터에 delta를 곱하면 안 됩니다.

예를 들어, 다음 코드를 사용하여 지면 (경사 포함)을 따라 걸을 수 있고 지면에 있을 때 점프할 수 있는 캐릭터를 만들 수 있습니다:

extends KinematicBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

var velocity = Vector2()

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 is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    velocity = move_and_slide(velocity, Vector2(0, -1))
class Body : KinematicBody2D
{
    private float runSpeed = 350;
    private float jumpSpeed = -1000;
    private float gravity = 2500;

    private Vector2 velocity = new Vector2();

    private void getInput()
    {
        velocity.x = 0;

        var right = Input.IsActionPressed("ui_right");
        var left = Input.IsActionPressed("ui_left");
        var jump = Input.IsActionPressed("ui_select");

        if (IsOnFloor() && jump)
            velocity.y = jumpSpeed;
        if (right)
            velocity.x += runSpeed;
        if (left)
            velocity.x -= runSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += gravity * delta;
    }
}

자세한 코드가 포함된 데모 프로젝트를 포함하여 move_and_slide() 사용에 대한 자세한 내용은 Kinematic character (2D)를 참조하십시오.