모든 기록은 다 까먹은 멍청한 미래의 나를 위한거라서 완전한 튜토리얼은 될 수 없음을 전제로 시작한다.
Godot은 2D, 3D가 다 가능하고 내 최종 목표는 3D이지만, 배워나가는 단계에서는 보다 간단한 2D예제, 그중에서도 근본중의 근본 Pong게임으로 시작해보자. 사실, 제일 간단한 게임이기 때문에 첫 게임으로 만들어보기 좋다. Pong게임은 초기 비디오 게임으로 탁구처럼 다음과 같이 공을 튕기며 점수를 내는 게임이다.

유튜브에도 강좌가 많으며, 여기서는 Learn Godot by creating Pong (https://www.youtube.com/watch?v=kr1BoEbuveI) 을 주로 참고하여 내용을 재구성 할 것이다.
게임엔진의 기본구조 이해
물리학과 출신의 고질병인데, 무작정 따라하기전에 기본 원리같은걸 짚고 넘어가야한다. 게임엔진은 제작 툴로서 다양한 기능을 제공하지만, 게임을 구동하는 런타임 엔진으로서 기본적인 동작은 유니티든, 언리얼이든 크게 다르지 않다.
게임엔진은 기본적으로 무한루프를 돌며 원하는 초당 프레임(fps) 이상을 화면에 렌더링한다. 보통 30fps에서 60fps겠지만, 성능이 좋다면 이를 넘어서도 상관은 없다. 높을수록 더 부드러운 화면을 보게 될 뿐이니까. 기본적인 흐름은 다음 순서도와 같을 것이다.

게임이 시작되면 각종 이미지, 사운드등 리소스를 로드하고 입력장치를 체크하며 초기화를 수행한다. 그 다음에는 앞서 말한 것과 같이 무한루프로 진입해 매 프레임을 프레임 레이트에 맞게 화면에 그려준다. 프레임을 그려주기전에, 유저의 입력이 있는지 체크하고 이에따라 플레이어의 이동위치등을 계산한다. 적이나 각종 환경의 물체들에도 필요한 계산을 하는데, 필요하다면 물리엔진을 이용한다. 물리엔진은 현실의 물리법칙에 의한 움직임을 게임내에서 시뮬레이션 하는데 이용된다. 이처럼 모든 움직임을 계산했다면, 충돌 체크를 한다. 총알에 부딪혔는지, 적과 부딪혔는지 등등을 계산한다. 충돌 영역은 눈에 보이는 이미지와는 별도로 계산이 간편한 박스나 큐브, 원, 구등으로 설정해서 계산하게된다. 충돌로 에너지가 닳수도 있고, 죽을 수도 있으며, 적을 죽이거나 물체를 박살낼 수도 있을 것이다. 충돌도 물리적 접촉이므로 필요하다면 물리엔진을 이용해 계산한다. 이러한 모든 계산이 끝나면, 드디어 한 프레임, 화면을 그려줄 수 있는 것이다.
게임의 FPS(Frame Per Second)는 보통 30fps, 60fps 또는 그 이상이다. 그러니까 매초, 위의 계산과정을 30번, 60번 또는 그 이상 수행하게 된다. 게임에 따라 엄청난 양의 계산을 하게 되므로 성능을 위해 최대한 적은 계산을 이용하도록 다양한 최적화가 사용된다. 3D게임의 경우 물체를 그리고 광원에 따른 렌더링에 엄청난 계산량이 필요하다. 대부분 그래픽 카드에 의해 처리되는데, 그래픽 카드의 발전은 이러한 3D 계산들을 위한 것이라고 해도 과언이 아니다. 계산량이 많은 또하나의 부분은 물리엔진이다. 현실의 물리법칙을 시뮬레이션 하려면 복잡한 계산식을 사용해야 하기 때문에 많이 사용할 수록 부하가 걸린다. 또한, 물리계산은 주로 CPU를 통해 이루어진다. 그래서 보통, 화면이 60fps이상 올라가더라도 물리계산은 60fps로만 하는 식으로 높은 fps에서도 계산량이 더 늘어나지 않도록 화면 렌더링과 분리해 사용한다.
Godot 게임의 기본 구성요소 : Node 와 Scene
Godot에서 모든 요소는 Node Tree로 구성된다. 에디터에서 노드트리는 다음과 같이 Scene 패널에서 보여진다.

Unity, Unreal에서 게임 오브젝트나 액터라고 불리는 것들도 모두 노드이고, 거기에 추가되는 컴포넌트들도 모두 노드로 제공된다. 다른 게임엔진 사용자는 혼란스러울수 있으나, 오히려 간단해진 개념으로 생각하면 좋겠다.
Godot에서 씬(Scene)은 다른 엔진들에서처럼 하나의 레벨만을 의미하지 않는다. 씬은 Godot에서 노드의 그룹일 뿐이다. 이게 무슨말이냐면, 노드들이 모여서 하나의 레벨 씬을 구성하기도 하지만, 다른 엔진에서 액터나 프리팹으로 불리는 것들도 결국 컴포넌트와 오브젝트들의 그룹이고, Godot에서 컴포넌트나 오브젝트가 다 Node이므로, 이 액터나 프리팹들도 Node의 그룹으로 만들어진 Scene이 된다.
혼란스럽다면 간단한 실제 예를 들어보자.
새로운 씬 만들기
노드를 추가하려면 씬을 먼저 만들어야 한다. 새로운 씬은 메뉴의 Scene>New Scene을 이용하거나, 뷰포트 상단의 탭을 이용할 수 있다. 아래 이미지를 참고하자.

Pong게임의 경우, 여러 레벨이 존재하지 않고 단 하나의 레벨 씬만 필요로 한다. 새로 생성한 씬을 Pong게임의 레벨 씬으로 만들어 보자. 씬은 노드의 그룹이라고 했으므로 노드를 추가해야 한다. 노드가 하나도 없다면 노드 트리에 다음과 같은 화면이 보일 것이다.

새로운 씬은 노드트리의 최상위 노드인 루트노드를 필요로 한다. 위 이미지와 같이 보이는 것은 루트노드로 기본적으로 2D Scene, 3D Scene, UI 에 대한걸 바로 생성 가능하도록 제공하는 것이다. 굳이 이걸 선택 안하고 다른 노드를 루트노드로 선택 할 수 있는데, 위 이미지에 빨간 선으로 표시한 ‘+’를 클릭하거나, “Other Node”를 선택하면 된다. 그러면, 다음과 같이 사용가능한 노드 목록 다이얼로그가 뜬다.

사용가능한 노드는 그 수가 매우 많으므로, 노드 이름을 아는 경우 위 다이얼로그에서 Search 부분에 직접 입력해서 찾아도 된다. 노드를 선택하면, 아래 Description에는 간략한 설명이 표시되는걸 볼 수 있다. 여기서는 가장 기본적인 ‘Node’를 선택해보자. 더블 클릭을 하거나, ‘Create’버튼을 누르면 생성된다.
눈치 챘을지 모르겠지만, Godot을 배운다는건 이 수많은 노드중에서 내가 필요한 노드를 찾고 사용법을 익히는 것이다. 노드는 사실, Godot이 제공하는 하나의 클래스이다. 위 다이얼로그에서 간단한 설명을 볼 수 있지만, 공식 문서에서 자세한 내용을 참고할 수 있다. 예를들어, 위에서 추가한 ‘Node’노드의 경우, 다음 링크에서 찾아볼 수 있다. https://docs.godotengine.org/en/stable/classes/class_node.html
추가한 노드의 이름을 “Pong_Level”로 바꿔서 저장해보자. 노드 트리에 해당 노드를 더블클릭 하거나, 노드를 선택하고 F2를 눌러 이름을 바꿀 수있다. 선택한 노드에 마우스 우클릭을해서 컨텍스트 메뉴를 띄운 후, ‘Rename’을 선택해도 된다.

Ctrl+S를 누르거나 메뉴에서 Scene>Save Scene을 선택해 씬을 저장할 수 있다. 저장시 다음과 같은 다이얼로그가 뜬다.

Godot에서 프로젝트 루트 디렉토리가 “res://”이다. 만약 폴더를 만들고 싶다면, 상단 오른쪽에 ‘Create Folder’를 이용할 수 있다. 아래 파일이름을 보면, 디폴트로 루트노드의 이름이 그대로 파일이름에 들어가는 것을 볼 수 있다. 이대로 저장해보자. 확장자는 보이듯이 tscn으로 저장된다. 이렇게 저장된 씬은 나중에 에디터의 FileSystem 브라우저에서 선택하여 불러올 수 있다.
외부 리소스 임포트하기
Pong은 매우 단순한 모양의 게임이라서 딱히 외부 리소스까지도 필요하지는 않다. 그냥 내부적으로 사각형과 원만 그려주면 구현가능하다. 하지만, 일반적으로는 외부 리소스를 임포트 해오는게 필요하므로, 처음에 언급한 Learn Godot by create Pong 에서 제공하는 리소스로 설명해 보겠다.
먼저, 해당 리소스는 github의 다음 위치에서 다운로드가 가능하다. https://github.com/clear-code-projects/GodotPong/blob/master/Assets.zip 링크의 github에 들어가보면, 오른쪽 아래부분에 Download 버튼이 보일 것이다. 다운로드를 받자. 다운받은 Assets.zip 압축을 풀면, Assets 폴더와 그안에 리소스 파일 몇개가 들어있다.
Godot에서 리소스를 포함한 모든 파일들은 프로젝트 루트에 존재한다. 리소스를 임포트 해오는데, 특별한 변환이 필요치 않으므로, 프로젝트 디렉토리에 원하는 리소스를 복사만 하면 임포트가 가능하다. 프로젝트 위치를 탐색기에서 여는 방법은 FileSystem에서 루트인 “res://”를 우클릭해서 뜨는 컨텍스트 메뉴의 “Open in File Manager”를 실행하는 것이다.

이렇게 오픈한 프로젝트 루트 디렉토리에 다운받아 압축을 푼 Assets 폴더를 복사해주면, Godot 에디터에서 바로 적용되는걸 확인 할 수 있다.

두번째 방법은, Assets폴더를 바로 Godot 에디터의 FileSystem으로 드래그 하는 방법이다.

Player Scene 만들기
앞에서 얘기했듯, 다른 엔진에서 prefab이나 액터와 같은 것들도 Godot에선 Scene에 해당한다. Pong게임에서 플레이어에 해당하는 막대를 새로운 씬을 생성해 만들어보자.
Pong 게임은 막대를 움직여 공을 튕겨내야 하는 게임이다. 로직이 간단하기 때문에 직접 충돌체크를 하고, 공이 어디로 튈지 계산할 수도 있다. 아마도 오래전 게임은 그렇게 만들어 졌겠지. 여기서 그럴필요는 없다. 우리는 게임엔진을 사용하기 때문에 알아서 충돌체크를 해줄거고, 물리엔진을 이용해서 공을 튕겨내 줄 것이다.
새로운 씬을 만들 때, 씬의 가장 기본적인 특징을 포함하는 루트노드를 선택하게 된다. 여기서는 KinematicBody2D를 이용할 것이다. 게임엔진과 무관하게 물리엔진은 거의 비슷하게 동작한다. 물리학에서 물체는 RigidBody로 다룬다. 형태가 변형되지 않는 이론적인 모델인건데, 게임에서도 RigidBody라고 사용한다. Godot에서 2D 버전으로는 RigidBody2D 노드가 있다. RigidBody로 정의된 물체는 작용 반작용 법칙에 의해 충돌시, 서로 튕겨져 나간다. 하지만, 게임에서 모든 물체가 이렇게 작용하면, 벽이 밀려나고 발판이 움직일 수도 있다. 그래서 위치가 고정된 물체로 충돌시 상대만 튕겨져 나가는걸 Kinematic Body라고 정의해 사용한다. Godot 2D 버전으로는 KinematicBody2D 이다. Pong에서 플레이어 막대는 위아래로만 움직이고, 공과 충돌해도 공만 튕겨내기 때문에 바로 이 KinematicBody2D를 루트 노드로 사용한다. 새로운 씬을 만들고 루트노드로 KinematicBody2D를 추가해보자. 앞에서 했던 것처럼 노드의 이름도 “PlayerStick”으로 바꿔준다.
KinematicBody2D 노드를 추가하면, 옆에 노란색 경고 아이콘이 뜰 것이다.

경고문구를 보면, shape이 없기 때문에 다른 오브젝트와 충돌 또는 상호작용을 할 수 없다고 나온다. 그러면서 이를 해결하기 위해 CollisionShape2D나 CollisionPolygon2D 를 자식으로 추가해 shape을 정의하라고 한다.
이부분이 다른 엔진과의 좀 다른 점일 수 있다. 다른 엔진에선 오브젝트 생성시 기본적으로 필요한게 다 딸려올 것이지만, Godot에서는 개발자가 필요한 노드를 다 추가해 줘야한다. 물론, 다른 엔진에서도 필요한경우, 컴포넌트를 직접 추가하기도 한다.
힌트에 나온대로 CollisionShape2D를 추가하자. 자식 노드의 추가는 원하는 부모 노드를 선택한 후, 위의 ‘+’버튼을 누르거나 ‘Ctrl+A’ 단축키를 이용해 가능하다.

CollisionShape2D를 추가했으나, 여기에도 노란색 경고 아이콘이 뜨는걸 볼 수 있다.

경고 문구를 보면, shape 리소스를 생성해야 한다고 나온다. CollisionShape2D를 선택한 상태에서 오른쪽 Inspector를 살펴보자.

Inspector에는 선택한 노드의 속성들이 표시된다. Godot 문서에서 CollsionShape2D를 살펴보면, 다음과 같은 상속관계를 볼 수 잇다.

CollisionShape2D 노드는 실제로 하나의 클래스이고, 위와 같은 상속관계를 갖고 있다는 얘기이다. 위에서 살펴본 Inspector와 비교해보면, 상속받은 각 노드(또는 클래스)가 나열되어 있고, 그안에 그 노드의 속성들이 있다는걸 볼 수 있다.
다시, 작업으로 돌아와서 경고문구에서 말하는 shape 속성을 설정해보자. 현재는 empty로 되어 있어 경고 문구가 뜨는 것이다. 이 shape은 실제로 충돌 영역을 지정하고 충돌 계산에 사용된다. empty를 클릭해서 드롭다운 박스를 열면, 여러가지가 나오는데 여기에서 우리는 스틱 형태이므로 RectangleShape2D를 선택한다.

이렇게 선택하면, 뷰포트에 rectangle 형태가 보이는 걸 알 수 있다. 이것이 Collision rectangle이 된다. 화면에 보이는 컨트롤 포인트를 드래그해서 이동하거나 형태를 변형가능하다.

또한, 정확한 값을 이용하여 속성값을 직접 입력 할 수도 있다. shape 속성의 선택된 RectangleShape2D를 클릭하면, 숨겨져있던 Extends 속성이 나타난다. 이를 이용해 사이즈를 조절할 수 있다.

값을 변경하는 경우, 속성필드에 동그란 화살표가 생기는데, 이걸 누르면 값을 초기화 할 수 있다.
경고 문구는 없어졌지만, Collision shape은 충돌 계산을 위한 것일 뿐, 플레이어의 눈에 보이는게 아니다. 에디터 오른쪽 상단에 있는 Play Scene 아이콘을 클릭해보자. 단축키 F6을 눌러도 된다.

실행이 되지만, 아무것도 보이지 않는걸 알 수 있다. 화면에 어떻게 보일지 필요한 자식 노드를 추가해보자. 앞에서 추가한 에셋의 이미지를 사용할 수도 있고, 간단한 사각형 형태이기 때문에 추가 리소스없이 메쉬를 이용할 수도 있다. 여기서는 이왕 추가한 에셋의 이미지를 이용해보자. 보통 2D게임에 사용되는 이미지는 스프라이트 이미지로 구성된다. 스프라이트(Sprite)라는 이미지 형태는 게임에서 메모리에 로딩해 사용하기 쉽게하기위해 하나의 큰 이미지에 캐릭터의 여러 모습이나 형태를 모아놓거나, 특수효과 애니메이션 프레임을 모아놓은 이미지이다. 다음 이미지는 구글링으로 검색한 스프라이트 시트의 예이다.

게임에서는 스프라이트 시트를 메모리에 올려놓고 필요한 부분만 읽어와서 상태에 맞는 이미지를 사용하게 된다.
지금 만드는 Pong에서는 딱히 애니메이션을 쓸일은 없다. 루트 노드인 PlayerStick에 Sprite노드를 추가한 후, Inspector의 Texture 속성에 이미지를 추가해주면 된다. 이미지를 추가하는 방법은 왼쪽 아래 FileSystem에서 이미지를 드래그해서 Texture 속성에 드롭 해주는 방법이 있다.

그림과같이, Paddle.png 이미지 파일을 Texture 부분에 드래그앤 드롭 해보자.
또는, Inspector의 Texture속성에서 드롭다운 메뉴를 연 후, Load를 선택해 뜨는 파일 탐색 다이얼로그에서 파일을 직접 선택해줘도 된다.

이제, 처음에 만든 Collision Rectangle을 이미지에 맞게 조정해준다. 편집할 때 Sprite와 CollisionShape2D가 겹쳐서 헷갈리는 경우에는 노드트리에서 눈 아이콘을 클릭해 안보이게 하거나, 노드를 위아래로 드래그 해서 Z-order를 변경해놓고 작업이 가능하다.

이렇게 여러개의 노드가 추가되면, 뷰포트에서 선택시 자식 노드들이 따로 선택되어 따로따로 놀게 된다. 이를 방지하기 위해, 작업이 완료되면 자식노드가 별도로 선택되지 않게 묶어줘야한다. 루트노드인 PlayerStick 노드를 선택한 상태에서 뷰포트 툴바에 있는 다음 아이콘을 클릭하자.

이렇게 하면, 트리뷰의 노드에 동일한 표시가 생기고 마우스로 클릭시 자식노드가 선택되지 않고 루트노드가 선택되어 다같이 움직이게 된다.
레벨 씬에 플레이어 스틱 씬 추가하기
이렇게 PlayerStick 씬을 별도로 만들었다. 이걸 처음에만든 Pong Level 씬에 추가해보자. 우선 FileSystem에서 저장했던 Pong Level 씬을 더블클릭하여 열어보자. 이미 열려있다면, 뷰포트 상단의 탭으로 이동이 가능하다.

씬에 다른 씬을 노드로 추가할 때는 기존 노드 추가방식과는 좀 다르다. 씬은 하나의 템플릿 같은 것으로 다른 씬에 추가할 때는 인스턴스로 생성하여 추가하여야 한다. 인스턴스로 추가되는 이유는 적이나 총알 같은 경우, 같은 씬이 얼마든지 여러개가 추가될 수 있는데, 각각 독립적으로 움직여야 하기 때문이다. 즉, 인스턴스로 생성한 씬의 값을 변경하더라도 다른 인스턴스에는 영향을 끼치지 않는다. 씬의 인스턴스를 생성하기 위해서는 씬의 트리노드에서 고리모양의 Instance Child Scene 아이콘을 클릭한다.

아이콘을 클릭하면, 프로젝트에 생성된 씬 목록이 뜨게 된다. 여기서 원하는 씬을 선택하면 된다. 지금 만드는 Pong게임에서는 PlayerStick을 선택한다.

이미지에 여기서 다루지 않은 씬들이 보이는데, 이것들은 이전에 만들었던 씬들이니 무시해도 된다.
실행 및 시작씬 설정
Pong 게임 만들기 1탄의 마지막으로 지금까지 만든걸 실행해보자. 앞에서 PlayerStick 씬을 실행할 때 F6으로 실행했던건 현재 씬을 실행하는 거였다. 전체 게임을 실행하려면, 오른쪽 위의 Play 아이콘을 클릭하거나 F5를 누르면 된다.

아마도, 다음과 같은 다이얼로그가 뜰 것이다.

게임을 실행시에는 여러 씬들중에 최초 실행되어야 하는 씬이 필요하므로 선택하라는 창이다. Select를 클릭한다.

프로젝트 디렉토리의 파일 탐색 다이얼로그가 뜬다. 여기서 최소 실행할 씬을 선택하면 되는데, Pong Level 씬을 선택한다. 시작씬을 선택하면, 게임이 실행되는 것을 볼 수 있다. 비록, 플레이어 막대기 하나를 제외하고 아무것도 없겠지만 ㅋㅋㅋ
게임의 시작씬은 프로젝트 세팅에서 확인하거나 변경이 가능하다. 메뉴의 Project>Project Settings… 를 선택해보자.

General 탭의 Application>Run 항목을 보면, Main Scene에 방금 선택한 Pong Level이 설정되어 있는 것을 볼 수 있다. 여기서 재설정을 하거나 초기화가 가능하다.
To be continue…
처음이니까 정말 자잘한거 까지 설명하며 여기까지 왔다. 겨우 플레이어 막대기 하나 생성했지만, 씬과 노드에 대해 감을 잡을수 있는 내용이었다고 생각한다. 다음엔 나머지 화면 구성을 완료한 다음, 게임이 동작하도록 스크립트까지 작성해보자. 2편 아니면 3편까지 가면 완성될 것으로 예상된다. 그럼이만.