취미로 게임 개발에 입문한 사람들이 가장 처음 만나는 최대의 장벽이 바로 이번 글에서 설명할 “인스턴스”입니다.
정식 입문서에는 이걸 설명하기 위해서 “객체지향 프로그래밍”과 객체에 대한 설명, 객체 지향의 특성 4가지인 추상화 상속 다형성 캡슐화....
그만 알아보도록 하죠.
실은 해보면 정말 쉬운겁니다.
우리는 취미로 게임을 만드는 거지, 이걸 업으로 삼을 사람들은 아니잖아요?
이번 글에서 할 일은 단 하나! 클릭 할 때마다, 획득한 코인이 “뿅!” 나타났다 사라지는 것을 구현할 것입니다.
지난번 프로젝트에서 이어서 진행됩니다.
■ 코인을 표시해줄 전용 라벨을 만들자!
기존의 작업하던 프로젝트에서 이어서 작업을 하지만, 이번에는 그 시작 지점이 조금 다릅니다.
프로젝트를 진행중인 node_2D 옆에 있는 + 아이콘을 누르면, 마치 “새 문서”와 같이 빈 노드가 새로 생성될 것입니다.

완전히 새로운 창이 뜨는데, 이번에는 “2D 씬”이 아닌 오직 “라벨”만으로 이루어진 노드를 만들어 줍시다.

다른 노드 > 이후는 이전에 라벨을 추가하는 것과 완벽하게 같은 방법입니다.
이렇게 생성된 라벨의 text에는 “코인획득라벨”이라고 임시로 입력해 줍니다.
이번에는 라벨을 조금만 더 꾸며 보도록 합시다.
라벨의 우측 설정창 (인스펙터)에서
Control > Theme Overrides > 에 있는 모든 항목을 펼쳐서 아래와 같이 (또는 직접 만져보면서 보기 좋게) 세팅해 봅시다.
Colors > Font Outline Color : 체크
Constants > Outline size : 체크, 5
Fonts Sizes > Font Size : 체크, 20
※ 기존 프로그래머를 위한 조언 (초보자는 몰라도 전혀 문제 없어요!)
이 부분도 코드 작성을 통해서 수정할 수 있습니다만, 인스턴스를 오버라이드 하는 방식으로 구현된 부분이라,
기존에 호출하던 방식 $Label.font_size = 20 과 같은 방식으로는 수정할 수 없습니다.
주로 전용 폰트 스타일을 새로 overrides 시킨 뒤 그 값을 수정하거나, $Label.set("theme_override_font_sizes/font_size", 30) 과 같은 귀찮은 방식으로 설정해야 합니다.
그러니 그냥 쉽게 인스펙터 수정을 이용하세요 ^^

적당히 그럴 듯 하게 만들어진 라벨.
물론 나중에 얼마든지 수정할 수 있으니까, 지금은 불만족스럽더라도 일단은 결과물을 보자구요!
저장을 하도록 합시다. 그냥 label.tscn 으로 저장하셔도 되고, 자신만의 이름을 붙히셔도 상관 없습니다.
이제 상단의 node_2D 탭을 눌러서 기존의 작업물로 돌아와 봅시다.
■ 인스턴스를 만들자!
약간의 짧은 글로 인스턴스의 구조를 설명 드리겠습니다.
우리는 위에서 만든 라벨을 “클릭 할 때 마다 반복해서 화면에 띄워줄 것”입니다.
그러기 위해서는 위 라벨의 “설계도”를 누군가에게 맡기고,
필요할 때마다 그 “누군가”를 불러서 “라벨 주세요!” 외치면 됩니다.
그리고 받은 라벨을 원하는 위치에 띄워주면 완성이 되는 거죠.
설계도를 받을 사람은 “감자” 라고 합시다. 설계도를 저장해야 하니 이는 “변수”로 만들어져야 겠죠.

var gamja = preload(”res://label.tscn”)
이제 버튼을 누를 떄 마다 동작을 해야 하니, 기존에 코인 버튼에 연결 되어 있던 func _on_button_pressed(): 함수에 코드를 추가하면 되겠죠?
다음은 “라벨 주세요!”라고 외칠 때 마다, gamja가 설계도에 따라 물건을 만들어 줄 것입니다.
var givelabel = gamja.instantiate()
givelabel 이라는 변수는 감자가 만든 라벨을 받게 되었습니다. 그리고 감자 뒤에 붙은 .instantiate() 이게 바로 설계도에 따라 물건을 양산하라는 의미입니다.
※ 주의 : godot 4.0 이전에는 .instance() 4.0 이후부터는 intantiate() 입니다. 버전에 따라서 아예 호환이 안됩니다!
이제 라벨은 무한하게 양산 될 것입니다. 비록 우리는 볼 수 없지만, 각 라벨에는 각각의 고유 시리얼 넘버라도 달려 있을 것입니다.
이렇게 만들어진 라벨을 실제로 사용할 때는 add_child(객체명)을 사용해 등장 시킬 수 있습니다.
add_child(givelabel)
그리고 이걸 어디에다 출력하냐?
= 라벨의 위치는 마찬가지로 인스펙터에 존재하는 “position” 값을 조절해 주면 됩니다.
대충 현재 마우스 위치를 강제로 받아오게 만들어 보죠.
get_global_mouse_position() 이 함수는 현재의 마우스 위치의 가져와주는 빌트인(고닷에 미리 만들어진) 함수입니다.
givelabel.position = get_global_mouse_position()

저장하고 실행을 해 보면??

라벨이 무한하게 양산되긴 하는데... 화면을 가려서는 안될 것 같긴 합니다 ㅎㅎㅎ
시간이 지나면 자동으로 사라지도록 만들어 보죠!
그리고 그 전에, 지금 이게 인스턴스 핵심의 전부입니다. 호들갑 떤 것에 비해 별로 어렵진 않죠?
여기서 사용하는 데미지 라벨, 생산을 누르면 뽑히는 유닛, 시뮬레이션에서 나오는 건설, 캐릭터가 쓰는 스킬...
사소하게 게임에서 누를 때 뜨는 팝업 창이나, 업적 달성을 표시해 주는 UI 등등,
이 모든 것들이 결국 미리 준비된 노드, 그리고 그걸 인스턴스로 불러내는 것이 불과합니다.
더욱 정확히는 게임이라는 프로그램 전체가 하나의 인스턴스 집합입니다.
■ 인스턴스의 소각
label 탭으로 돌아옵시다.“시간”이 지나면 자동으로 라벨이 사라져야 하니, 시간을 알려줄 무엇인가가 추가되어야 겠죠?
라벨과 버튼을 추가하 듯, 이번에는 Label 에 자식 노드로 Timer 노드를 추가해 줍시다.

그리고 타이머는 라벨이 등장하면 바로 시간을 카운트 하도록,
Timer의 인스펙터에서 “Autostart”에 체크를 해 줍시다. 필요하면 Wait time도 조절해 주셔도 되지만, 일단은 바로 결과를 볼 수 있게 현재의 1초를 그대로 둡시다.
이제 라벨에도 코드를 넣어주기 위해서 Label을 누르고 위에 있는 “문서+”아이콘을 눌러 새로운 스크립트 파일을 만들어 줍시다.
너무나 당연하게 _ready와 _ process 가 추가되는데, 그냥 두셔도 되고, 지워버리셔도 됩니다.
우리는 “시간이 지나면” 이라는 조건이 있고, Timer도 만들어 주었습니다.

timer노드의 timeout() : 시간이 만료되면, 우리가 설정해 놓은 명령을 수행하게 할 수 있습니다.
여기에는 단 한 줄 queue_free()만 적어 줍시다.

다 지워버리고 이것 뿐...
queue_free() 는 우리가 위에서 만든 “인스턴스”를 소각해 버리는 기능입니다.
타이머는 라벨이 등장하는 즉시 시작되고, 타이머가 1초로 설정되어 있으니,
우리가 만든 라벨은, 등장하고 1초 뒤에 사라질 것입니다.
그리고 위에서 적은 것 처럼 “각 라벨에는 각각의 고유 시리얼 넘버라도 달려 있을 것입니다.”
그래서 라벨 사라져! 라고 말하는 것 처럼 보이지만, 고유 넘버 XXXX0001 번 사라져! 이런 식으로 처리할 겁니다.
이걸 쓴다고 모든 라벨이 한순간에 증발하는 일은 절대 없습니다.
F5를 눌러 실행해 보면?
인스턴스가 1초가 경과한 뒤 사라지는 것을 볼 수 있습니다.
※ 굳이 소각해 줘야 하나요? 지난번에 disabled 처럼 visible 의 인스펙터를 바꿔 버려도 되지 않나요?
인스턴스는 실제 라벨처럼 계속 메모리에 남아 있게 됩니다.
오랜시간 쌓인다면, 결코 무시할 수 없는 양의 인스턴스가 우리 눈에는 보이지 않는 메모리에 남게 됩니다.
그러니 필요 없어지는 인스턴스는 꼭 제거해주는 습관을 길러 주세요!
※ timer를 쓰기 싫은 분에게 단 2줄 코드
await get_tree().create_timer(1).timeout
queue_free()
편하게 타이머 쓰기를 추천 드립니다만, 매번 특정 시간마다 호출하고 싶을 때 마다 타이머를 넣기 귀찮으신 분은
await라는 빌트인 함수를 사용하실수도 있습니다. 이 두줄을 label의 _ready 부분에 넣어 주시면 됩니다.
■ 가만 있으면 심심하니 라벨이 떨어지게 해 보자!
첫 애니메이션입니다!
라벨이 아래로 떨어지게 만들어 봅시다.
이건 “실시간으로 처리되어야 하는 것”이기에
func _process(delta):
함수에 등록되어야 하죠. 이런 애니메이션을 위해서 전용 물리 효과 함수
func _physics_process(delta):
라는 것도 있지만, 기본적으로는 같은 작동 방식이니 어떤 걸 써도 상관 없습니다.
피직스는 나중에 벨로시티를 공부할 때 사용해 보도록 하죠.

라벨에 애니메이션을 넣는 것이니, 라벨을 아래로 끌어다 놓으면...?
“.” 라는 문자가 생성됩니다. 이건 루트 노드라서 이렇게 표시되는 건데 전혀 문제 없습니다.
이게 좀 보기 싫으신 분은 그냥 self. 로 입력하셔도 됩니다.
$".".position.y += 3
또는
self.position.y += 3
매 프레임마다 라벨의 위치(position)의 Y축(세로)에 3씩 값을 올린다는 의미를 만들었습니다.
Y 축에 값을 더하면 아래로 내려가니, 반대로 위로 올라가게 만들고 싶다면 빼주시면 됩니다!
self.position.y -= 3
이대로 실행해 보시면...

아래로 추락하는 라벨이 완성되었습니다!
■ 동작을 한 단계 향상시켜 봅시다. delta 사용
우리가 만든 애니메이션은 “프레임”에 의존합니다. 120프레임으로 출력되는 컴퓨터에서는 초고속으로 내려갈 것이고,
반면 30프레임밖에 안나오는 컴퓨터에서는 느려터진 애니메이션이 될 겁니다.
이건 엄청 고전 게임을 요즘 컴에서 구동하면 미친 듯 빠른 속도로 돌아가는 증상을 겪어보신 분들이 계실텐데,
바로 프레임 기반으로 프로그램을 만들었을 때 발생하는 이슈입니다.
거기서 등장하는 것이 delta 값 입니다.
delta는 시간을 프레임으로 나눈 값으로써, 프레임이 낮은 컴퓨터나 높은 컴퓨터, 또는 프레임이 들쑥날쑥하는 컴퓨터에서도
일관적으로 부드러운 애니메이션을 구현할 수 있게 만든 값입니다.
방법은 간단합니다. 그저 움직임에 delta 값을 곱해주기만 하면 됩니다.
func _process(delta):
self.position.y += 3 * delta
실행해 보면 굉장히 느린 것을 알 수 있습니다.
델타 값이 시간을 프레임으로 나눈 값인지라, 매우 작은 소숫점 값입니다.
그럼 간단하게 우리가 원하는 속도가 될 정도로 곱하는 값을 키워주면 끝이죠!
앞의 곱샘 값을 300 정도로 키워 보시면 (대충 100배 하면 됩니다), 괜찮은 속도로 움직일 것입니다.
■ 갑자기 사라지는 것은 어색하다! 서서히 사라지도록 만들자!
이건 라벨이 가지고 있는 다양한 인스펙터 값들을 우리가 이미 사용한 방법을 쓰면 끝입니다.
그리고 색상과 투명도를 조절하는 인스펙터는 modulate 입니다. 그리고 modulate 값 내부에서도 RGBA 의 alpha가 투명도를 의미합니다.
self.modulate.a -= 1 * delta

내려가면서 (투명도가 높아지는) 사라지는 라벨들
너무나 간단하게 해결되었고, 동시에 여러분들이 앞으로 공부할 방향도 느껴질 것입니다.
라벨이나 버튼이나 타이머, 이러한 것들이 가지고 있는 “인스펙터”을 알고, 그 값을 잘 조절할 수 있다면
여러분들이 원하는 많은 것들을 구현할 수 있을 것 같고,
그리고 그것은 거의 맞습니다.
수많은 게임의 구성 요소들은 단지 인스펙터의 값을 클릭할 때 마다 조정하는 것 만으로도 구현해 낼 수 있습니다.
그리고 오늘 학습한 인스턴스까지 활용하면 더욱 엄청난 활용도를 보여줄 것입니다.
■ 최종 목적인 “코인 획득”을 출력하자.
감을 잡으신 분들은 이건 굳이 알려드리지 않아도 혼자서도 하실 수 있을 내용입니다.그저 우리가 만든 “라벨”의 “text”에 coinEff 값을 출력하면 끝이니까요.
이미 만든 라벨에 position을 바꿔 봤으니 그대로 text 값도 바꿔주는 내용만 추가하면 됩니다.

추가되었다는 의미에서 “+” 텍스트도 추가해 주고, coinEff는 숫자니까 str()을 사용해 텍스트로 바꿔주면 끝!

너무나 잘 작동합니다.
■ 응용편 - 다른 동작들도 만들어 봅시다.
움직임의 이해도도 높히기 위해, 라벨이 떨어지는 2가지 방식을 더 만들어 봅시다.
- 중력의 영향을 받는 것 처럼 “점차 가속해서 떨어지는 라벨”
- 클릭하면 톡~ 튕기는 것 처럼 “뛰어 올랐다가 떨어지는 라벨”
낙하하는 물체는 중력 가속도라는 9.8m/s^2 이라는 물리적 관념은 지겨우실 테니...
단순하게 “매 초 속도가 더해진다(가속도)” 라는 것만 기억하시면 됩니다.
우리는 2개의 변수를 추가해 줄 필요가 있죠.
속도(speed)와 가속도(acceleration) 변수 입니다.
var speed = 0 # 물체의 속도 : 처음에는 멈춰 있을 테니, 기본 값은 0
var acceleration = 300 # 매 초마다 속도에 더해질 가속도입니다. 값을 조절하면서 가지고 놀아 보세요.

기존 코드
self.position.y = 300 * delta
에서 떨어지는 속도는 앞에 붙은 300의 값을 수정 했습니다. 그럼 우리가 준비한 speed를 300 대신 넣으면 떨어지는 속도를 설정할 수 있겠죠.
self.position.y = speed * delta
그리고 가속도는 “매 시간 속도에 추가되는 속도” = 속도에 가속도를 매번 (delta) 더해주기만 하면 됩니다.
speed += acceleration * delta

점차 아래로 가속되는 라벨이 완성되었습니다.
이번에는 튕기듯 날아가는 라벨로 한번 더 업그레이드 해 봅시다.
너무 쉽습니다. 그냥 “위쪽 방향으로 초기 속도(Speed)”만 추가해 주면 끝이거든요.
Y 좌표 기준 위쪽으로 튕기기 위해서는 - 값을 가져야 하겠죠?
var speed = 0
여기의 speed를 -150 정도를 입력해 봅시다.
비록 우리가 원하는 동작으로 정확하게 동작하고 있지만,
너무 빨리 사라져 버려서 결과를 확인하기 어렵네요.
그러니 인스턴스를 지워버리는 timer의 wait time을 3초까지 늘려주고,
텍스트를 투명하게 만들어 주는 modultae.a 값을 절반인 0.5로 낮춰줘 봅시다.

이제 위로 튕겼다가 아래로 떨어지는 라벨이 보이게 됩니다.
세로축으로만 움직이는게 조금 별로니, x축으로 관성운동 (그냥 옆으로 튕긴다는 말을 그럴듯하게 적어봤습니다)을 추가해 봅시다.
x축으로 움직이고 가속도 없다? 이건 우리가 처음 라벨이 떨어지게 만들었을 때와 굉장히 비슷하군요.
세로축인 y 대신 가로축인 x 값을 가진 움직임을 추가해 주면 됩니다.
self.position.x += 100 * delta


변수값을 크게 조절해 보면서 움직임의 변화를 보는 것도 좋은 경험이 될 겁니다.
글 읽어 주시는 분들에게 감사 인사 드리며, 이 글은 극 소수의 독자를 대상으로 작성되고 있습니다.
상호주의를 바탕으로 제 글에 찾아와서 댓글과 좋아요까지 눌러주시는 모든 분들께는 감사드리지만,
굳이 관심 없는 주제의 글까지 까지 찾아오지 않으셔도, 감사한 마음만 받겠습니다! 물론 좋아요와 댓글은 좋지만요 ㅋㅋㅋㅋ
여러분들의 즐거운 게임 개발을 응원하며,
다음편에서는 "크리티컬의 구현과 함수에 대한 이해"를 다뤄 볼까합니다.
따라하시는 와중에 막히거나 궁금하신 점은 언제든 댓글 남겨주세요 ^^
《 고도 엔진 입문글 모음 - https://page.onstove.com/indie/global/view/10204130 》
#godot #고도엔진 #입문 #튜토리얼 #게임만들기
촉촉한감자칩
🫡🫡🫡 즐겜을 위해 하루하루 살아가고 있습니다.
미소녀 게임, 건설 경영 게임을 사랑합니다!