2강노드와 씬 — Godot의 사고방식
기능 한 조각을 쌓아 부품을 만들고, 부품으로 게임을 만든다.
이 강의 목차 (6)
- 노드 = 기능 한 조각, 씬 = 노드를 조립한 부품 이라는 구조를 이해한다.
- 화면에 캐릭터 그림을 올린다.
- 만든 것을 씬으로 저장해 여러 개 찍어낸다.
Godot에는 단 하나의 사고방식만 있다#
Godot의 거의 모든 것은 이 문장으로 설명됩니다.
작은 기능(노드)을 나무처럼 쌓아 부품(씬)을 만들고, 그 부품을 다시 부품처럼 쓴다.
이게 전부입니다. 3강 이후로 배우는 물리·충돌·카메라·UI·네트워크가 전부 이 구조 위에 얹힙니다.
노드 — 기능 한 조각#
노드(Node) 는 "한 가지 일만 하는 부품"입니다. 예를 들면,
| 노드 | 하는 일 |
|---|---|
Node2D |
위치·회전·크기를 가진다. 그리지는 않는다 |
Sprite2D |
이미지 한 장을 그린다 |
AnimatedSprite2D |
여러 장을 순서대로 그린다 |
CollisionShape2D |
충돌 판정용 도형을 가진다 |
Camera2D |
화면이 어디를 비출지 정한다 |
Timer |
시간을 재고 끝나면 알린다 |
AudioStreamPlayer2D |
소리를 낸다 |
Label |
글자를 표시한다 |
노드 하나하나는 놀랄 만큼 단순합니다. Sprite2D는 이미지를 그릴 뿐, 움직이지도 부딪히지도
않습니다. 움직이려면 코드가 필요하고, 부딪히려면 충돌용 노드를 옆에 붙여야 합니다.
이게 처음에는 불편해 보이지만, 덕분에 필요한 기능만 골라 조립할 수 있습니다.
트리 — 부모와 자식#
노드는 항상 나무(tree) 구조로 놓입니다. 위가 부모, 아래가 자식입니다.
Player (Node2D) ← 위치를 담당
├─ Sprite (Sprite2D) ← 그림
├─ Shape (CollisionShape2D)← 충돌 도형
├─ Gun (Node2D) ← 총구 위치
│ └─ Muzzle (Sprite2D) ← 총구 불꽃
└─ Shoot (Timer) ← 연사 간격여기서 가장 중요한 규칙 하나를 지금 외우고 갑니다.
자식의 위치는 부모를 기준으로 한 상대 위치입니다.
Player를 오른쪽으로 100 옮기면 Sprite, Shape, Gun, Muzzle이 전부 같이 100 갑니다.
Player를 45도 돌리면 자식들도 다 같이 45도 돕니다.
그래서 "캐릭터를 움직인다"는 건 Player 하나만 움직이면 되는 일이 됩니다.
Player는 회전하는 접시고 Sprite, Gun은 그 위에 놓인 컵입니다.
접시를 돌리면 컵의 "접시 위 위치"는 그대로인데 방바닥 기준 위치는 바뀝니다.
5강에서 이걸 로컬 좌표 / 글로벌 좌표라고 부르게 됩니다.
씬 — 조립한 부품을 저장한 것#
노드를 다 조립했으면 씬(Scene) 으로 저장합니다. .tscn 파일이 하나 생깁니다.
여기서 Godot 초보가 가장 많이 헷갈리는 지점입니다.
다른 엔진이나 영상 쪽에서 "씬"은 보통 화면 하나를 뜻합니다. Godot에서 씬은 재사용 가능한 부품입니다.
- 타이틀 화면 → 씬
- 스테이지 1 → 씬
- 적 슬라임 한 마리 → 씬
- 총알 한 발 → 씬
- 체력바 → 씬
크기와 상관없이 "묶어서 재사용하고 싶은 단위"면 전부 씬입니다.
한 번 씬으로 저장하면, 다른 씬 안에 부품처럼 끼워 넣을 수 있습니다.
Enemy.tscn을 만들어 두면 스테이지에 30마리를 찍어 넣어도 원본을 고치는 순간 30마리가
동시에 바뀝니다. 이걸 인스턴스(instance) 라고 부릅니다.
프로그래밍을 안다면 이렇게 보면 정확합니다.
Enemy.tscn은 설계도(클래스), 스테이지에 놓인 슬라임 30마리는 인스턴스입니다.
설계도를 고치면 인스턴스가 전부 따라 바뀌고, 인스턴스 하나만 따로 값을 바꿀 수도 있습니다.
실습 — 화면에 캐릭터 올리기#
1) 이미지 준비#
Godot 프로젝트에 기본으로 들어 있는 icon.svg를 그냥 쓰겠습니다.
직접 만든 PNG가 있다면 프로젝트 폴더 안으로 복사하면 자동으로 인식됩니다.
2) 노드 만들기#
- 씬 독에서
main.tscn을 엽니다. - 루트에
Node2D가 없다면+로 Node2D를 추가하고 이름을Main으로 바꿉니다. Main을 선택한 상태에서+→ Sprite2D 추가.Sprite2D를 선택하고 우측 인스펙터의 Texture 칸에, 파일시스템 독의icon.svg를 끌어다 놓습니다.
화면 가운데에 Godot 로봇 아이콘이 나타납니다. F5로 실행하면 게임 창에도 보입니다.
검은 화면이 아니라 그림이 보인다면 성공입니다. 아직 코드는 0줄입니다. 노드를 놓고 이미지를 지정했을 뿐입니다.
3) 부품으로 묶어서 씬 만들기#
이제 이걸 "플레이어"라는 부품으로 독립시킵니다.
Sprite2D를 선택하고Ctrl+A(또는 우클릭 → 자식 노드 추가)로 상위에Node2D를 하나 더 만들지 말고, 씬 독에서+→Node2D를 새로 추가한 뒤 이름을Player로 바꿉니다.Sprite2D를 마우스로 끌어Player안으로 넣습니다.Player를 우클릭 → Save Branch as Scene (브랜치를 씬으로 저장)- 이름은
player.tscn.
이제 main.tscn의 Player는 다른 파일이 된 부품입니다. 아이콘 옆에 필름 모양 표시가 붙습니다.
4) 여러 개 찍어보기#
파일시스템 독의 player.tscn을 뷰포트로 끌어다 놓으면 하나가 더 생깁니다.
서너 개 놓고 위치를 흩어 보세요.
이제 player.tscn을 열어서 스프라이트 크기를 바꿔보면, 놓아둔 전부가 함께 바뀝니다.
이것이 씬을 부품으로 쓰는 이유입니다.
첫 스크립트 — 노드에 행동을 붙이기#
노드는 스스로 움직이지 않습니다. 스크립트를 붙여야 행동이 생깁니다.
player.tscn을 엽니다.- 루트
Player노드를 선택하고 씬 독 우측 상단의 스크립트 붙이기(두루마리+) 아이콘 클릭. - 경로는
res://player.gd, 템플릿은Node: Default로 두고 생성.
기본 코드가 이렇게 나옵니다.
extends Node2D
func _ready() -> void:
pass
func _process(delta: float) -> void:
pass한 줄씩 뜻을 봅니다.
extends Node2D— 이 스크립트는 Node2D의 능력을 물려받는다. 그래서position같은 Node2D의 속성을 그대로 쓸 수 있습니다.func _ready()— 이 노드가 화면에 처음 등장할 때 딱 한 번 호출됩니다. 초기화 자리입니다.func _process(delta)— 매 프레임마다 호출됩니다. 1초에 60번쯤 불립니다.pass— "아무것도 안 함". 내용을 채우면 지웁니다.
_ready, _process 처럼 앞에 밑줄이 붙은 함수는 여러분이 부르는 게 아니라 엔진이 알아서
불러주는 약속된 이름입니다. 콜백이라고 부릅니다.
철자를 하나라도 틀리면(_Process, _proces) 조용히 아무 일도 일어나지 않습니다.
"코드를 썼는데 아무 반응이 없다"의 원인 1위입니다.
이제 내용을 채웁니다.
extends Node2D
func _ready() -> void:
print("플레이어 등장:", name)
func _process(delta: float) -> void:
position.x += 60 * deltaF5로 실행하면 캐릭터가 오른쪽으로 천천히 흘러갑니다.
main.tscn에 여러 개 놓아뒀다면 전부 같이 움직입니다. 부품 하나를 고쳤는데 전부 바뀐 겁니다.
60 * delta 가 왜 그냥 60이 아닌지는 4강에서 본격적으로 다룹니다.
지금은 "초당 60픽셀"이라고만 읽어두세요.
60을200으로 → 얼마나 빨라지나요?position.x를position.y로 → 어느 방향으로 가나요? (위? 아래?)rotation += 2 * delta한 줄을 추가 → 무엇이 도나요?main.tscn에 놓인 플레이어 중 하나만 선택해 인스펙터에서Scale을 2로 바꿔보세요. 나머지는 그대로인가요? (인스턴스 개별 수정)
- 개발자 —
extends가 곧 그 스크립트가 붙을 수 있는 노드 타입을 정합니다.Sprite2D용 스크립트를Node2D에 붙이면 실행 시점에 터집니다. - 기획자 — 앞으로 "적 밸런스를 바꿔주세요"는 씬 하나를 고치는 일입니다. 이 구조를 알면 "이건 30군데 고쳐야 해서 비싸요"라는 말이 사실인지 아닌지 판단됩니다.
- 디자이너 — 스프라이트의 원점(피벗) 은 기본이 이미지 정중앙입니다.
발밑을 기준으로 두고 싶다면
Sprite2D의Offset을 조정하거나, 이미지 자체를 그렇게 만드세요. 5강에서 왜 중요한지 다룹니다.
"Invalid call. Nonexistent function 'xxx'"
→ 그 노드 타입에 없는 함수를 불렀습니다. extends 줄이 맞는지 먼저 보세요.
"들여쓰기 오류(Indentation)"
→ GDScript는 탭/스페이스 혼용을 싫어합니다. Godot 에디터 기본은 탭입니다.
바깥에서 복붙한 코드가 스페이스면 섞입니다. 에디터에서 Ctrl+A 후 재정렬하세요.
"스크립트를 붙였는데 아무 일도 없다"
→ ① 함수 이름 오타(_process), ② 그 노드가 씬 트리에 실제로 들어 있지 않음,
③ 실행 중인 메인 씬이 다른 파일. 이 셋 중 하나입니다.
- 노드는 기능 한 조각, 씬은 노드를 조립한 부품이다 — 라고 말할 수 있다
- 부모를 움직이면 자식이 따라온다는 걸 눈으로 봤다
-
player.tscn을 만들어 여러 개 찍어봤다 - 원본 씬을 고치면 전부 바뀌는 걸 확인했다
다음 강의 — 이제 P1로 들어갑니다. _process가 1초에 몇 번 불리는지,
그 사이에 엔진이 정확히 무슨 일을 하는지 한 프레임을 해부합니다.