본 기사는 https://ziva.sh/blogs/godot-collision-not-working의 한국어 버전입니다 (영어 원문은 Ziva.sh가 2026년 11월 10일에 공개했습니다).
Area2D의 body_entered 시그널이 계속 발동하지 않거나, 플레이어가 바닥을 통과하여 떨어지는 경우라면 아래 7가지 설정을 확인해 주세요. Area2D 관련 항목은 Godot 4.7.2로 테스트했으며, 바닥 항목은 샘플 프로젝트를 통해 재현했습니다. AI에게 원인을 찾게 하고 싶다면, Godot 에디터 내에서 작동하는 AI 에이전트 Ziva가 일부러 고장 낸 씬의 4가지 버그를 1분 34초 만에 모두 찾아내고 수정했습니다. 이때 사용된 것은 validate_physics_setup 도구와 씬 읽기 기능입니다. 해당 테스트는 기사 후반부에 있으며, 손상된 프로젝트도 다운로드할 수 있습니다.
위에서부터 순서대로 확인해 주세요. 각 항목의 수정 방법은 한 줄로 작성되어 있습니다.
| # | 원인 | 수정 방법 |
|---|
| 1 | Area2D의 Mask에 바디(body) 레이어가 포함되어 있지 않음 | Area2D의 Collision > Mask에서 바디 레이어에 체크를 합니다. |
| 2 | CollisionShape2D에 Shape이 없거나 비활성화됨 (Disabled) | Shape에 새로운 RectangleShape2D(또는 유사한 모양)을 설정하고, Disabled 체크를 해제합니다. |
| 3 | Area2D의 Monitoring이 꺼져 있음 | Area2D의 인스펙터에서 Monitoring에 체크합니다. |
| 4 | 시그널이 연결되지 않았거나 잘못된 노드에 연결됨 | body_entered를 해당 메서드를 가진 스크립트의 노드에 연결합니다. |
| 5 | 바디(Body)가 area_entered를 기다리고 있음 | 바디에는 body_entered를, 다른 Area2D에는 area_entered를 사용해야 합니다. |
| 6 | CharacterBody2D가 바닥을 통과하여 떨어짐 | 캐릭터의 Collision > Mask에서 바닥 레이어에 체크합니다. |
| 7 | 바닥이 물리 레이어가 없는 TileMapLayer임 | TileSet에 물리 레이어를 추가하고, 단단한 타일에 충돌 폴리곤(collision polygon)을 그립니다. |
항목 1부터 5까지의 테스트 방법: 헤드리스(headless) Godot 4.7.2 스크립트를 사용하여, 충돌 레이어 2에 있는 CharacterBody2D를, 60 x 60 크기의 직사각형 Shape을 가진 Area2D 사이로 떨어뜨렸습니다. 이때 move_and_slide()를 이용해 물리 프레임당 4픽셀씩 움직이면서 시그널이 발동했는지 기록했습니다. 올바르게 설정된 경우에는 시그널이 발동했지만, 항목 1부터 5까지의 잘못된 설정에서는 어느 것도 발동하지 않았습니다.
Godot의 물리 입문 페이지에서는 두 가지 속성을 다음과 같이 정의하고 있습니다. 레이어(Layer)는 “객체가 존재하는 레이어를 설명합니다”(describes the layers that the object appears in)를 의미하며, 마스크(Mask)는 “바디가 충돌을 검색할 레이어를 설명합니다. 객체가 마스크의 어느 레이어에도 없으면, 바디는 그 객체를 무시합니다”(describes what layers the body will scan for collisions. If an object isn't in one of the mask layers, the body will ignore it.)를 의미합니다. 둘 다 기본값은 레이어 1입니다.
Area2D가 바디를 감지하려면, 바디의 레이어가 Area2D의 마스크에 포함되어 있어야 합니다. 테스트 결과, 마스크가 1인 Area2D는 레이어 2에 있는 바디를 한 번도 감지하지 못했습니다. 반대 방향은 상관이 없었습니다. Area2D의 레이어를 0, 바디의 마스크를 0으로 설정해도 body_entered는 발동했습니다. 따라서 코인이나 트리거가 플레이어에게 반응하지 않을 때는 먼저 트리거의 Mask를 확인해 주세요.
인스펙터에서는 레이어 번호에 체크를 하지만, .tscn 파일이나 코드에서는 같은 값이 비트마스크(bitmask)로 사용됩니다. 레이어 1은 1, 레이어 2는 2, 레이어 3은 4, 레이어 4는 8입니다. 만약 씬에 collision_mask = 4가 설정되어 있다면...
라고 되어 있다면, 스캔하는 것은 레이어 4가 아니라 레이어 3입니다. 코드에서는 set_collision_mask_value()를 사용하면 이 계산을 생략할 수 있습니다.
extends Area2D
func _ready() -> void:
set_collision_mask_value(2, true)
...
테스트에서 Shape이 빈 CollisionShape2D를 자식으로 가진 Area2D는 아무것도 감지하지 못했고, Shape이 설정되어 있어도 Disabled에 체크가 되어 있는 경우도 마찬가지였습니다. CollisionShape2D를 선택하고, 비어있는 Shape 필드를 클릭하여 모양을 선택한 후, 스프라이트에 겹치는 크기로 조정해 주세요.
Monitoring을 끄자 테스트의 Area2D는 아무것도 감지하지 못했습니다. Area2D 레퍼런스에도 body_entered는 "Requires monitoring to be set to true." (monitoring이 true여야 함)라고 쓰여 있습니다. 기본값은 켜져 있으므로, 인스펙터와 이것을 false로 설정하는 스크립트가 없는지 확인해 주세요.
_on_body_entered 함수는 시그널(Signal)이 연결되어 있지 않으면 아무것도 하지 않습니다. 연결이 없는 경우, 테스트에서는 아무것도 발화하지 않았습니다. Signals 독(Inspector 옆의 Signals 탭)에서 연결하거나, 위의 스니펫처럼 코드로 연결해 주세요.
연결 대상도 해당 메서드를 가진 스크립트의 노드여야 합니다. 샘플 씬에서는 코인에서 레벨 루트(스크립트 없음)로의 연결 시 _on_body_entered가 한 번도 실행되지 않았고, Godot는 오류도 출력하지 않았습니다. 독에 연결이 표시되는데 아무 일도 일어나지 않는 경우, 연결 대상을 확인해 주세요.
body_entered는 물리 바디(CharacterBody2D, RigidBody2D, StaticBody2D)와 TileSet에 충돌 모양이 있는 타일맵을 대상으로 발화합니다. area_entered는 다른 Area2D만을 대상으로 발화합니다. 테스트에서는 CharacterBody2D를 대상으로 area_entered를 기다려도 한 번도 발화하지 않았습니다.
이것은 항목 1의 역방향입니다. 캐릭터가 바닥과 충돌하는 것은, 바닥의 레이어가 캐릭터의 마스크에 포함되어 있는 경우에만 가능합니다. 샘플 프로젝트에서는 플레이어가 레이어 2에 있고 collision_mask = 4 (레이어 3)였으며, 바닥은 레이어 1에 있었습니다. 3초 후, 플레이어는 y 560의 바닥보다 훨씬 아래인 y 4327에 있었습니다. 플레이어의 마스크를 1로 설정하자, 플레이어는 y 520에서 바닥 위에 멈추었습니다.
TileMapLayer는 TileSet에 물리 레이어가 있고, 단단한 타일에 충돌 폴리곤이 있을 때까지 충돌을 갖지 않습니다. 절차는 TileMapLayer 가이드(영어)에 있습니다. 이 경우에도 캐릭터의 마스크에 해당 물리 레이어의 충돌 레이어가 포함되어야 합니다.
다음 두 가지는 테스트에서 감지를 방해하지 않았습니다.
move_and_slide()가 아니라 position을 설정하여 바디를 움직이는 경우에도 body_entered는 발화했습니다. 테스트의 바디는 프레임당 4픽셀 이동시켰습니다. 얇은 영역을 건너뛰어 버릴 정도의 큰 이동은 테스트하지 않았습니다.
- 바디가 처음부터 에리어 내부에 있는 경우:
body_entered는 발화했습니다.
채팅 창에서 보이는 것은 붙여넣은 코드뿐이지만, 레이어, 마스크, Shape, 시그널 연결은 씬 파일 안에 있습니다. 점프한 캐릭터가 공중에 떠 있는 상태로 어려움을 겪었던 Godot 포럼 사용자는 "chat gpt just scrambled my code to trash" (ChatGPT에게 코드를 완전히 망가뜨린 것뿐이었다)라고 작성했습니다(Godot Forum). 씬을 읽고 실행할 수 있는 에이전트라면, 충돌 버그에도 더 잘 대처할 수 있는지 확인하고 싶었습니다.
Ziva는 Ziva.sh가 개발했으며, 이 테스트 역시 Ziva.sh가 작성했습니다.
테스트는 2026년 11월 10일에 Linux 환경의 Godot 4.7.2에서 실행했습니다. Ziva는 개발 빌드를 사용했으며, 모델은 기본 Ziva Smart를 사용했고 자동 승인(auto-approve) 기능은 활성화되어 있습니다. 단일 프로젝트에서 한 번만 실행한 결과이므로, 벤치마크라기보다는 하나의 예시로 참고해 주십시오.
우리는 네 가지 버그가 포함된 작은 장면을 만들었습니다. 첫째, 플레이어의 마스크에 바닥(floor) 레이어가 포함되어 있지 않음. 둘째, 코인의 마스크에 플레이어 레이어가 포함되어 있지 않음. 셋째, 코인의 CollisionShape2D에 모양이 없음. 넷째, 코인의 _on_body_entered가 연결되지 않았습니다. 직접 시도해 보고 싶다면, 손상된 프로젝트를 다운로드(ZIP, 2 KB)하십시오. Godot 4.7.2에서 가져와 로드할 수 있습니다. F5를 누르면 플레이어가 바닥을 통과하여 떨어집니다.
프롬프트는 다음과 같습니다. 한 글자도 빠짐없이 전달했습니다 (실제로 사용한 영어 프롬프트와 그 번역입니다).
In res://collision_demo/level.tscn my player falls straight through the floor, and when the player touches the coin nothing happens (it should print "Coin collected" and disappear). Find out why and fix it. Run the scene to check it works.
번역: res://collision_demo/level.tscn에서 플레이어가 바닥을 그대로 통과하여 떨어집니다. 또한, 플레이어가 코인에 닿아도 아무 일도 일어나지 않습니다 (실제로는 "Coin collected"를 출력하고 사라져야 합니다). 원인을 찾아 수정해 주십시오. 장면을 실행하여 정상 작동하는지 확인하십시오.
Ziva가 수행한 작업:
- 장면 트리(Scene Tree)에서
level.tscn과 두 개의 스크립트를 읽었습니다. - Player와 Floor에 대해 validate_physics_setup 함수를 호출했습니다. 반환된 문제는 하나였으며, /root/Player에 대한 `
은 시퀀스, 캐릭터의 노드(Node), 발판의 노드를 받습니다. 각 캐릭터에 대해 충돌 마스크(Collision Mask)가 0이 아닌지, 마스크가 최소한 하나의 발판 충돌 레이어와 중첩되는지, 그리고 모양을 가진 CollisionShape2D 자식이 있는지 확인합니다. 발판이 TileMapLayer인 경우, TileSet이 할당되어 있고 물리 레이어를 가지고 있는지 확인합니다. 결과는 문제점과 제안 목록으로 반환됩니다.
Area2D, Monitoring, 신호 연결(Signal connection)은 확인하지 않습니다. 이번 실행에서 이 도구가 발견한 것은 바닥의 버그뿐이었습니다. 코인의 3가지 버그는 모델이 시퀀스를 읽어 발견한 것입니다. 따라서 큰 시퀀스에서는 이 부분이 Ziva가 올바른 파일을 읽는지 여부에 달려 있습니다.
- 신호 연결 대상 오류(실행 전 수정). 첫 번째 연결은 레벨의 루트를 향하고 있었고, 위 항목 4와 같이 에러 없이 실패하는 경우였습니다. Ziva는 시퀀스를 실행하기 전에 이를 수정했습니다. -
작은 시퀀스 한 개만. 관련된 노드는 3개였고, 버그는 모두 하나의 파일 내에서 보이는 상태였습니다. 여러 시퀀스에 걸친 충돌 버그나, 실행 중에 레이어를 변경하는 코드가 원인인 버그는 아직 Ziva로 테스트하지 않았습니다.
자신의 프로젝트에서 시험해 보려면, Ziva를 설치하고 사람에게 증상을 설명할 때와 같은 방식으로 전달하십시오. 예를 들어, “my player falls through the floor, find out why and fix it, then run the scene.”(플레이어가 바닥을 통과하여 떨어지니, 원인을 찾아서 수정하고 시퀀스를 실행해 줘)와 같이 말합니다.
Godot 4.7.2에서 테스트했을 때, 다음 경우에 body_entered가 발화하지 않았습니다. Area2D의 충돌 마스크에 바디(Body)의 충돌 레이어가 포함되어 있지 않은 경우, CollisionShape2D에 모양이 없거나 비활성화된 경우, Monitoring이 꺼진 경우, 신호가 연결되지 않은 경우, CharacterBody2D에서 body_entered 대신 area_entered를 기다리고 있는 경우입니다. 이 순서로 확인하십시오. Area2D 자체의 레이어와 바디의 마스크는 관계가 없었습니다.
충돌 레이어(Collision Layer)는 객체가 속하는 레이어들의 집합입니다. 충돌 마스크(Collision Mask)는 객체가 충돌을 검색하는 레이어들의 집합입니다. Area2D가 바디를 감지하려면, 바디의 레이어가 Area2D의 마스크에 포함되어 있어야만 합니다. CharacterBody2D가 바닥 위에서 멈추려면, 바닥의 레이어가 캐릭터의 마스크에 포함되어 있어야만 합니다. .tscn 파일과 코드에서는 값은 비트마스크(bitmask)이며, 레이어 1은 1, 레이어 2는 2, 레이어 3은 4, 레이어 4는 8입니다.
먼저 캐릭터의 충돌 마스크를 확인하십시오. 바닥의 충돌 레이어가 포함되어 있어야 합니다. 샘플에서는 플레이어의 마스크가 4(레이어 3)였고, 바닥은 레이어 1에 있었으며, 플레이어는 3초 만에 y 4327까지 떨어졌습니다. 플레이어의 Mask에서 레이어 1에 체크하자 문제가 해결되었습니다. CollisionShape2D에 모양이 없거나, TileMapLayer의 TileSet에 물리 레이어가 없는 경우에도 같은 증상이 나타납니다.
Godot 에디터 내에서 작동하는 AI 에이전트 Ziva는 테스트용으로 고장 난 시퀀스에 심어진 4가지 버그를 1분 34초 만에 모두 발견하고 수정했습니다. 플레이어의 마스크, 코인의 마스크, 충돌 모양 누락, 미연결 신호입니다. validate_physics_setup 도구는 플레이어와 바닥의 마스크 불일치만 지적했고, 나머지 3가지는 Ziva가 시퀀스 파일을 읽어서 발견했습니다. 그 후 Ziva는 시퀀스를 실행하여 'Coin collected'가 출력되는 것을 확인했습니다. 이는 하나의 프로젝트에서 한 번 실행한 결과입니다.
Area2D가 바디를 감지하는 데는 필요하지 않습니다. Godot 4.7.2에서 테스트했을 때, Area2D의 레이어를 0, 바디의 마스크를 0으로 설정했음에도 불구하고, Area2D의 마스크에 바디의 레이어가 포함되어 있었기 때문에 body_entered는 발화했습니다. 상대방의 레이어와 일치해야 하는 것은 감지하는 쪽 객체의 마스크뿐입니다.