Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

점프와 몹 밟기

이번 파트에서는 점프하여 몬스터를 찌르는 기능을 추가하겠습니다. 다음 단원에서는 몬스터가 플레이어를 바닥에 내리치면 플레이어가 죽도록 만들겠습니다.

더 자세한 내용은, C# API와 GDScript의 차이점 페이지를 참고하세요.

애니메이션 제어하기

물리 바디는 레이어와 마스크라는 두 가지 상호 보완적인 속성에 접근할 수 있습니다. 레이어는 오브젝트가 어떤 물리 레이어에 있는지 정의합니다.

마스크는 바디가 듣고 감지할 레이어를 제어합니다. 이는 콜리전 감지에 영향을 줍니다. 두 바디가 상호작용하려면 적어도 하나는 다른 바디에 해당하는 마스크가 있어야 합니다.

헷갈리신다면 걱정하지 마세요. 곧 세 가지 예를 보여드리겠습니다.

중요한 점은 레이어와 마스크를 사용하여 물리 상호작용을 필터링하고 성능을 제어하며 코드에서 추가 조건의 필요성을 제거할 수 있다는 점입니다.

기본적으로 모든 피직스 바디와 영역은 레이어와 마스크 모두 1 로 설정됩니다. 이는 모두 서로 충돌한다는 뜻입니다.

물리 레이어는 숫자로 표시되지만, 이름을 지정하여 무엇이 무엇인지 추적할 수 있습니다.

레이어 이름 설정하기

물리 레이어에 이름을 지정해 봅시다. 프로젝트 -> 프로젝트 설정으로 이동합니다.

image0

왼쪽 메뉴에서 레이어 이름 -> 3D 물리로 이동합니다. 오른쪽에 각 레이어 옆에 필드가 있는 레이어 목록을 볼 수 있습니다. 거기에서 이름을 설정할 수 있습니다. 처음 세 개의 레이어 이름을 각각 player, enemies, world로 지정합니다.

image1

이제 물리 노드에 할당할 수 있습니다.

레이어와 마스크 할당하기

Main 씬에서 Ground 노드를 선택합니다. 인스펙터에서 콜리전 섹션을 펼칩니다. 여기에서 노드의 레이어와 마스크를 버튼 격자로 볼 수 있습니다.

image2

땅은 세계의 일부이므로, 세 번째 레이어의 일부가 되기를 원합니다. 불이 켜진 버튼을 클릭하여 첫 번째 Layer끄고 세 번째 레이어를 켜세요. 그런 다음 Mask를 클릭하여 끄기를 선택합니다.

image3

앞서 언급했듯이 Mask 속성은 노드가 다른 물리 오브젝트와의 상호작용을 수신할 수 있게 해주지만, 콜리전이 일어날 필요는 없습니다. Ground는 아무것도 들을 필요가 없으며 크리처의 낙하를 방지하기 위해 존재할 뿐입니다.

속성 오른쪽에 "..." 버튼을 클릭해서 이름 붙은 확인 상자의 리스트를 볼 수 있음을 참고하세요.

image4

다음으로 PlayerMob입니다. 파일시스템 독에서 파일을 더블 클릭하여 player.tscn을 엽니다.

Player 노드를 선택하고 콜리전 -> 마스크를 "enemies"와 "world" 전부로 설정합니다. 첫 번째 레이어는 "player" 레이어이기 때문에 기본 Layer 속성은 그대로 놔둬도 됩니다.

image5

그리고, mob.tscn 을 더블 클릭하고 Mob 노드를 선택하여 Mob 씬을 엽니다.

콜리전 -> 레이어를 "enemies"로 설정하고 콜리전 -> 마스크를 설정 취소하여 마스크를 비게 합니다.

|image1|

이 설정들은 몬스터들이 서로를 통과하여 움직임을 의미합니다. 만약 몬스터들이 서로 충돌하고 미끄러지길 원하신다면 "enemies" 마스크를 세요.

참고

몹은 XZ 평면에서만 움직이기 때문에 "world" 레이어에 마스크하지 않아도 됩니다. 우리는 이들에 의도적으로 아무 중력도 적용하지 않습니다.

점프

점프 기능은 단 두 줄의 코드만 있으면 됩니다. Player 스크립트를 엽니다. 우리는 점프를 코딩하기 위해 점프의 강도를 조절할 값과 ``_physics_process()``를 업데이트해줘야 합니다.

스크립트 최상단의 fall_acceleration이 정의된 줄 밑에 ``jump_impulse``를 추가합니다.

#...
# Vertical impulse applied to the character upon jumping in meters per second.
@export var jump_impulse = 20

_physics_process()``안의 ``move_and_slide() 위에 다음 코드를 추가합니다.

func _physics_process(delta):
    #...

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        target_velocity.y = jump_impulse

    #...

점프에는 이것만 있으면 됩니다!

is_on_floor() 메서드는 CharacterBody3D 클래스의 도구입니다. 이것은 현재 프레임에 바디가 바닥과 충돌했다면 ``true``(참)을 반환합니다. 그렇기 때문에 우리는 *Player*에 중력을 적용합니다. 그러면 우리는 몬스터같이 바닥 위에 떠다니는 대신 바닥과 접축할 수 있습니다.

만약 캐릭터가 바닥 위에 있고 플레이어가 "jump"를 누른다면 우리는 캐릭터에 많은 수직 속도를 부여합니다. 게임 안에서는, 당신은 이렇게 조작이 즉시 반응하고 일시적인 속도 부스트를 받기를 원합니다. 비현실적이지만, 감각은 좋으니까요.

Y축이 위쪽으로 양수인 것을 알 수 있습니다. 이는 Y축이 아래쪽으로 양수인 2D와 다릅니다.

몬스터를 짓밟기

다음으로 스쿼시 메카닉을 추가해 보겠습니다. 캐릭터가 몬스터를 튕기면서 동시에 몬스터를 처치하도록 만들겠습니다.

몬스터와의 콜리전을 감지하고 바닥과의 콜리전과 구분해야 합니다. 이를 위해 Godot의 group 태깅 기능을 사용할 수 있습니다.

mob.tscn 씬을 다시 열고 Mob 노드를 선택합니다. 오른쪽의 노드 독으로 이동하여 시그널 목록을 확인합니다. 노드 독에는 두 개의 탭이 있습니다: 이미 사용 중인 시그널과 노드에 태그를 할당할 수 있는 그룹입니다.

|image7|

이름 필드에 "mob"을 입력하고 확인 버튼을 클릭하세요.

|image8|

이제 "mob" 그룹이 씬 그룹 섹션에 표시됩니다.

|image9|

독에 아이콘이 표시되어 노드가 하나 이상의 그룹에 속해 있음을 나타냅니다.

|image10|

이제 코드의 그룹을 사용하여 몬스터와의 콜리전과 바닥과의 콜리전을 구분할 수 있습니다.

스쿼시 메카닉 코딩하기

플레이어 스크립트로 돌아가서 스쿼시 및 바운스를 코딩하세요.

스크립트의 맨 위에는 또 다른 속성인 bounce_impulse가 필요합니다. 적을 격파할 때 캐릭터가 점프할 때처럼 높이 올라가는 것은 원하지 않습니다.

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
@export var bounce_impulse = 16

그런 다음 위에서 _physics_process()에 추가한 Jumping 코드 블록 뒤에 다음 루프를 추가합니다. Godot는 move_and_slide()를 사용하여 캐릭터의 움직임을 부드럽게 하기 위해 몸을 여러 번 연속으로 움직이게 합니다. 따라서 발생했을 수 있는 모든 콜리전을 반복해야 합니다.

루프를 반복할 때마다 몹에 착지했는지 확인합니다. 그렇다면 몹을 죽이고 바운스합니다.

이 코드를 사용하면 주어진 프레임에서 콜리전이 발생하지 않으면 루프가 실행되지 않습니다.

func _physics_process(delta):
    #...

    # Iterate through all collisions that occurred this frame
    for index in range(get_slide_collision_count()):
        # We get one of the collisions with the player
        var collision = get_slide_collision(index)

        # If there are duplicate collisions with a mob in a single frame
        # the mob will be deleted after the first collision, and a second call to
        # get_collider will return null, leading to a null pointer when calling
        # collision.get_collider().is_in_group("mob").
        # This block of code prevents processing duplicate collisions.
        if collision.get_collider() == null:
            continue

        # If the collider is with a mob
        if collision.get_collider().is_in_group("mob"):
            var mob = collision.get_collider()
            # we check that we are hitting it from above.
            if Vector3.UP.dot(collision.get_normal()) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                target_velocity.y = bounce_impulse
                # Prevent further duplicate calls.
                break

아무 많은 새로운 기능들이 있습니다. 다음은 여기에 관한 추가적인 정보들입니다.

get_slide_collision_count()``와 get_slide_collision() 함수는 모두 CharacterBody3D 클래스에서 제공되며 ``move_and_slide()``와 관련되어 있습니다.

get_slide_collision() 은 어디에, 어떻게 콜리전이 발생하는지에 대한 정보를 가지고 있는 KinematicCollision3D 오브젝트를 반환합니다. 예를 들면, 우리는 collision.get_collider().is_in_group("mob")``에 대해``is_in_group()``을 호출하여 "mob"과의 충돌을 검사하기 위해``get_collider 속성을 사용합니다.

참고

is_in_group() 메서드는 매 :ref:`Node<class_Node>`마다 사용 가능합니다.

우리가 괴물 위에 착륙하는 것을 확인하려면, 벡터 곱 연산을 사용합니다: Vector3.UP.dot (collision.get_normal) > 0.1. 콜리전 법선은 콜리전이 발생한 평면에 수직인 3D 벡터입니다. 곱 연산은 위 방향과 비교를 할 수 있게 합니다.

곱 연산의 결과가 ``0``보다 크다면 두 벡터는 90도보다 작다는 것 입니다. 따라서 값이``0.1``보다 크다는 것은 대충 우리가 괴물의 위에 있다는 것을 말해줍니다.

밟기와 튕기기 로직을 처리하고난 후, ``mob.squash()``가 중복되어 호출되는 것을 방지하고자``break``문 을 통하여 사전에 루프를 종료합시다. 그렇지 않으면 이것은 하나의 kill 이벤트에 대해 여러번 스코어를 적용하는 것과 같은 의도하지 않는 버그를 불러옵니다.

저희는 정의되지 않은 함수 ``mob.squash()```을 호출하고 있습니다, 그래서 Mob 클래스에 추가해야합니다.

파일시스템 독에서 mob.gd 스크립트를 더블 클릭하여 엽니다. 스크립트의 상단에 ``squashed``라고 하는 새로운 시그널을 정의하겠습니다. 그리고 몹을 파괴하는 스크립트의 하단부에 시그널을 방출하는 밟기 함수를 추가합니다.

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    squashed.emit()
    queue_free()

참고

C#을 사용할 때 Godot는 EventHandler로 모든 시그널을 끝내기 위해 자동으로 적절한 이벤트를 생성합니다. C# 시그널을 참조하세요.

다음 강의에서는 스코어에 점수를 추가 하기 위한 시그널을 사용하겠습니다.

그렇게하여 여러분은 점프를 함으로써 괴물들을 죽일 수 있게 됩니다. 키보드:F5`를 눌러 ``main.tscn` 을 메인 씬으로 설정하고 게임을 진행할 수 있습니다.

그러나, 아직 플레이어가 죽지 않습니다. 이것은 다음 파트에서 다루도록 하겠습니다.