나무·바위를 자유롭게 흩뿌리면서 충돌까지 얻는 유일한 방법.
설치부터 Keep Static Colliders 까지 클릭 순서 그대로 적었다.
왜 이것을 쓰나 —
MultiMeshInstance3D의Populate Surface로 뿌린 것들은 물리 세계에 존재하지 않는다. 캐릭터가 그대로 통과한다. 엔진 내장에는 이를 자동으로 해결하는 버튼이 없고, 클릭만으로 되는 방법은 이것뿐이다. → MultiMeshInstance3D 문서
ProtonScatter 는 "무엇을" 과 "어디에" 를 스스로 만들지 않는다. 씬에 먼저 있어야 한다.
| 있어야 하는 것 | 왜 |
|---|---|
뿌릴 대상 — MeshInstance3D 또는 씬 파일 | Step 5 에서 지정한다 |
바닥 — MeshInstance3D + StaticBody3D + CollisionShape3D | Step 8 의 Project On Colliders 가 레이캐스트로 지면을 찾는다. 콜리전이 없으면 지면에 붙지 못한다 |
🛑 바닥에 콜리전이 없으면 나무가 지면에 붙지 않고 영역 안에 공중부양한다. 바닥
MeshInstance3D를 선택하고 툴바Mesh ▸ Create Collision Shape ▸ Static Body Child ▸ Trimesh로 먼저 만들어 둔다.
충돌까지 원한다면 뿌릴 대상에도 콜리전이 있어야 한다.
두 경로 중 하나를 고른다.
에디터 상단 ▸ AssetLib 탭 ▸ "ProtonScatter" 검색 ▸ Download ▸ Install
git clone --depth 1 https://github.com/HungryProton/scatter.git
# scatter/addons/proton_scatter/ 를 프로젝트의 addons/ 로 복사한다
용량 주의 —
addons/proton_scatter/는 13MB 이고 그중 11MB 가demos/다. 저장소에 넣을 거라면demos/와tests/를 빼면 1.5MB 로 줄어든다.
🛑 단demos/를 빼면 기본 프리셋의ScatterItem이demos/assets/brick.tscn을 가리켜 콘솔에Cannot open file이 한 번 뜬다. Step 5 에서 Path 를 자기 모델로 바꾸면 사라진다.
Project ▸ Project Settings… ▸ Plugins 탭 ▸ ProtonScatter 의 Enabled 를 ✅
활성화되면 ProtonScatter · ProtonScatterItem ·
ProtonScatterShape 노드를 Create New Node 목록에서 검색할 수 있게 된다.
안 보이면 활성화가 안 된 것이다.
Create New Node 에서 "scatter" 로 검색해 세 개를 만든다. 부모-자식 관계가 정해져 있다.
ProtonScatterItem · ProtonScatterShape 는 | 반드시 ProtonScatter 의 자식이어야 한다 |
| 여러 종류를 섞으려면 | ProtonScatterItem 을 여러 개 둔다 (각 Proportion 으로 비율 조절) |
| 영역을 여러 개 쓰려면 | ProtonScatterShape 도 여러 개 둘 수 있다 |
ProtonScatter 를 선택해 인스펙터를 열면 기본 프리셋이 자동 적용된다.
(근거 — stack_panel.gd:73-76 이 just_created 인 스택에
presets/scatter_default.tscn 을 적용한다.)
들어오는 값을 그대로 읽으면 이렇다:
modifier 4개
▸ Create Inside (Random) [Create] amount = 75
▸ Randomize Transforms [Edit] position (0.15, 0.15, 0.15)
rotation (20.0, 360.0, 20.0)
scale (0.1, 0.1, 0.1)
▸ Relax Position [Edit] iterations 3 · offset_step 0.2
consecutive_step_multiplier 0.75
▸ Project On Colliders [Edit] ray_length 5.0 · ray_offset 5.0
remove_points_on_miss false
max_slope 90.0 · collision_mask 1
이 넷이 Step 6~8 에서 만질 대상 전부다. 순서에도 뜻이 있다 — 뿌리고(Create) → 흩고(Randomize) → 겹침을 풀고(Relax) → 지면에 붙인다(Project).
🛑 코드로
ProtonScatter.new()를 하면 스택이 비어 있다. 자동 적용은 에디터 인스펙터 전용이다.
ProtonScatterShape 를 선택하면 인스펙터에 Shape 칸이
<empty> 로 있다. 클릭해 New 에서 모양을 고른다.
| 고를 수 있는 것 | 무엇 | 크기 프로퍼티 |
|---|---|---|
| ProtonScatterBoxShape | 상자 (가장 흔하다) | size — Vector3 |
| ProtonScatterSphereShape | 구 | radius |
| ProtonScatterPathShape | 곡선을 따라 | Curve3D |
| ProtonScatterBaseShape | 다른 모양의 부모 클래스 | (직접 쓰지 않는다) |
뷰포트에 노란 상자가 나타난다. 이 안쪽이 뿌려질 범위다.
ProtonScatterShape 노드를 옮기면 범위도 함께 움직인다.
Negative 를 켜면 | 그 영역을 빼낸다(구멍을 낸다). 길·건물 자리를 비울 때 |
size.y | 높이도 범위에 들어간다. Project On Colliders 가 지면에 붙여 주므로 2 정도면 충분 |
ProtonScatterItem 을 선택하고 인스펙터에서 두 칸을 정한다.
| 값 | 뜻 | Path 에 넣는 것 |
|---|---|---|
| From current scene | 지금 이 씬 안의 노드 | 씬 트리의 노드 (Assign… 버튼으로 고른다) |
| From disk | 다른 씬 파일 | res://…/tree.tscn |
처음이라면 From current scene 이 쉽다. 씬에 이미 놓아 둔 나무를 그대로 가리키면 된다.
Path 줄의 Assign… 을 누르면 Select a Node 창이 뜬다.
씬 트리에서 뿌릴 대상을 고른다.
🛑 콜리전(Step 9)까지 원한다면 고른 노드 아래에
StaticBody3D ▸ CollisionShape3D가 있어야 한다. ProtonScatter 는 그 shape 을 복제한다. 없으면 메시만 뿌려진다.
고르는 순간 뷰포트에 인스턴스가 나타난다.
ProtonScatter 루트를 선택하고 인스펙터 아래쪽 Modifier Stack 을 본다.
Modifier Stack
+ Add modifier
▸ Create Inside (Random) ← 이것을 클릭해 펼친다
▸ Randomize Transforms
▸ Relax Position
▸ Project On Colliders
값을 바꾸면 뷰포트가 즉시 다시 뿌려진다.
| 개수를 정하는 다른 방법 | |
|---|---|
| Create Inside (Poisson) | 개수 대신 radius(최소 간격) 로 정한다. 겹치지 않는 배치가 필요하면 이쪽 |
| Create Inside (Grid) | 격자로 규칙적으로 |
| Create Along Edge (…) | 영역의 가장자리를 따라 — 울타리·가로수 |
Randomize Transforms 를 펼친다. 세 줄이 있고, 각각 무작위 폭이다
(값이 0이면 흩지 않는다).
Rotation 의 Y 가 360 인 것이 핵심이다 — 위 축 기준으로 완전히 무작위로 돌려
같은 나무를 복제한 티를 지운다. X·Z 의 20° 는 살짝 기울여 자연스럽게 만드는 값이다.
| 하고 싶은 것 | 어떻게 |
|---|---|
| 나무가 똑바로만 서게 | Rotation 을 (0, 360, 0) 으로. X·Z 를 0 으로 |
| 바위처럼 아무렇게나 | (360, 360, 360) |
| 크기를 더 다양하게 | Scale 을 (0.3, 0.3, 0.3) 처럼 올린다 |
🛑 회전만 필요하면
Randomize Rotation이라는 별도 modifier 도 있다. 다만 그쪽 기본값은(360, 360, 360)이라 그대로 쓰면 나무가 눕는다. 반드시(0, 360, 0)으로 고쳐서 쓴다.
Relax Position 을 펼친다. 무작위로 뿌리면 나무끼리 겹치는데,
이 modifier 가 서로 밀어내며 간격을 고르게 만든다.
나무가 서로 파고들면 Iterations 를 올리거나 Offset Step 을 키운다.
겹침을 아예 만들지 않으려면 Step 6 에서
Create Inside (Poisson)을 쓰는 편이 더 확실하다.Relax Position은 이미 뿌려진 것을 나중에 미는 방식이다.
인스턴스를 아래로 레이캐스트해 지면에 붙인다.
🛑 바닥 콜리전이 레이어 1 에 없으면 지면에 붙지 않는다. 나무가 공중에 뜨면 여기를 본다.
Ray Length·Ray Offset이 5m 이므로 높낮이 차가 5m 를 넘는 지형에서도 놓친다.
소스 기본값은Remove Points On Miss = true지만 프리셋이false로 덮는다. 직접 modifier 를 추가했을 때는true라서, 바닥을 못 맞히면 인스턴스가 전부 사라진다.
여기가 다른 방법과 갈리는 지점이다. ProtonScatter 루트를 선택하고
인스펙터의 Performance 그룹을 펼친다.
MultiMesh 인스턴스를 채우는 바로 그 루프가, 같은 Transform3D 로
물리 서버에 shape 을 등록한다.
# scatter.gd:448-450
t = item.process_transform(transforms.list[offset + i])
mmi.multimesh.set_instance_transform(i, t) # 렌더
_create_collision(static_body, t) # 물리 — 같은 t
# scatter.gd:609-611 — 끄는 것은 Create Copies(1) 뿐이다
func _create_collision(body: StaticBody3D, t: Transform3D) -> void:
if not keep_static_colliders or render_mode == 1:
return
지원 shape — Sphere · Box · Capsule · Cylinder · ConcavePolygon · ConvexPolygon · HeightMap · SeparationRay.
godot --headless --path . -s res://tests/protonscatter_collision_probe.gd
[A. keep_static_colliders = false] 레이 12발 중 0발 명중 ✅ 콜리전 없음
[B. keep_static_colliders = true] 레이 12발 중 12발 명중 ✅ 콜리전 있음
콜리전 윗면 높이 ≈ 2.27 m
[C. B + source_scale_multiplier = 4] 레이 12발 중 0발 명중 ✅ 실측된 한계
실패 0 건
| # | 제약 | 뜻 |
|---|---|---|
| ① | 씬 트리에 콜리전 노드가 없다 | PhysicsServer3D 에 직접 등록 → Debug ▸ Visible Collision Shapes 에도 안 보인다. 확인은 직접 부딪혀 보는 것뿐 |
| ② | 부모의 scale 이 버려진다 | MeshInstance3D·CollisionShape3D 그 노드 자신의 transform 만 살린다 → 크기는 메시·shape 자체에 넣는다 |
| ③ | Source Scale Multiplier | 키우면 콜리전이 따라오지 않는다(위 실측 C) |
| ④ | collision layer 미설정 | 소스에 body_set_collision_layer 호출이 0회 → 전부 기본 레이어 |
| 무엇을 | 어떻게 |
|---|---|
| 뿌려졌나 | 뷰포트에 보이는가. ProtonScatter 아래 ScatterOutput 이 생겼는가 |
| 드로우콜이 1인가 | F5 로 실행해 모니터를 본다. 에디터 뷰포트 숫자에는 기즈모가 섞인다 |
| 충돌하나 | 🛑 디버그 뷰로는 안 보인다(제약 ①). 캐릭터로 직접 부딪혀 본다 |
사람이 걷지 않아도 판정할 수 있다 — 캐릭터와 똑같은 캡슐로 숲을 가로지르는 직선을 훑는다:
[왼쪽 MultiMesh] 숲 중심까지 4.5 m 를 훑었으나 걸리는 것이 없었다 ✅ 통과
[오른쪽 ProtonScatter] 출발 0.2 m 지점에서 막혔다 ✅ 막힘
실패 0 건
| 증상 | 원인과 해결 |
|---|---|
| Create New Node 에 ProtonScatter 가 없다 | Step 2 의 플러그인 활성화를 안 했다 |
| 아무것도 안 뿌려진다 | ① ScatterItem 의 Path 가 비었다 ② ScatterShape 의 Shape 이 <empty> ③ Modifier Stack 이 비었다(코드로 만든 경우) |
Cannot open file … brick.tscn | 애드온을 demos/ 없이 벤더링했다. Step 5 에서 Path 를 바꾸면 사라진다 |
| 나무가 공중에 뜬다 | Project On Colliders 가 지면을 못 찾는다 → 바닥에 콜리전을 주고 Collision Mask·Ray Length(5m) 확인 |
| 나무가 눕거나 뒤집힌다 | Randomize Transforms ▸ Rotation 의 X·Z 를 0 으로 |
| 나무끼리 겹친다 | Relax Position ▸ Iterations 를 올리거나 Create Inside (Poisson) 으로 |
| 충돌이 안 생긴다 | ① Keep Static Colliders ✅ ② Render Mode = Use Instancing ③ 원본에 StaticBody3D ▸ CollisionShape3D 가 있는가 |
| 콜리전이 안 보인다 | 🛑 정상이다. 노드를 만들지 않으므로 디버그 뷰에 안 나온다 |
| 콜리전 크기가 메시와 다르다 | 부모의 scale 이 버려졌다(제약 ②) |
| 실행 중 배치가 바뀐다 | Enable Updates In Game 을 끈다 |
| 상황 | 답 |
|---|---|
| 자유 배치 + 충돌이 둘 다 필요 | ✅ ProtonScatter — 이것 말고 클릭으로 되는 방법이 없다 |
| 격자에 놓아도 되는 건물·울타리 | GridMap + MeshLibrary (내장, 콜리전 자동) |
| 충돌이 필요 없는 풀·꽃·자갈 | MultiMeshInstance3D + Populate Surface (내장) |
| 부딪힐 나무가 수십 그루뿐 | 그냥 개별 노드. 가장 단순하다 |
이 페이지는 클릭 순서다. 원리·다른 해법·코드는 문서 쪽이 더 자세하다.