반응형

Path2D노드 사용법에 대해 알아보겠습니다. 

Path2D노드를 사용하면 단순히 움직이는 캐릭터들(Enemy 나 NPC 등)의 단순 이동동작을 최소한의 코딩으로 구현할 수 있는 유용한 노드입니다.

 

1. 적당한 Enemy 노드가 있을 경우, 전체 동작하는 Scene에서 아래와 같이 구성을 하였습니다. Path2D노드는 하위에 항상 PathFollow2D를 자식으로 담고 있어야 합니다. Path2D노드가 포인트를 이용하여 라인을 생성하는 노드라면, PathFollow2D노드는 그 선을 어떤 방식으로 따라가는지를 설정하는 노드입니다.

2. Path2D노드를 다시 선택하면 아래 그림과 같이 에디터 상단에 포인터 추가/삭제/편집할 수 있는 버튼이 활성화됩니다. 적당히 이동 경로를 그려줍니다. 이 중 5번째 아이콘은 경로를 닫아주는 버튼입니다. 라인을 완전히 그리지 말고, 적당히 그린 후 이 버튼을 누르면 라인을 닫으면서 닫힌 도형으로 만들어줍니다.

3. PathFollow2D노드를 선택한 후 Rotate를 체크 해제합니다. (경우에 따라 다릅니다만, 저는 라인을 따라 회전하지 않고, 좌우 평행이동만 하려고 합니다.

4. Enemy.gd스크립트 내부에 아래의 라인을 추가해줍니다. (_process(delta)가 없다면, _physics_process(delta)함수에 추가해주시기 바랍니다.)

# 변수로 선언하여 PathFollow2D를 불러옴
onready var path_follow = get_parent()
var speed = 20 # 움직이는 속도

func _process(delta):	
	path_follow.set_offset(path_follow.get_offset() + speed *delta)
반응형
반응형

오늘은 AnimationTree노드에 대해 알아보겠습니다.

 

처음 캐릭터의 애니메이션을 만들면 AnimationPlayer노드를 많이 사용하는데요, 이 노드만 이용해서 각종 상태를 코드로 컨트롤하려면 좀 힘듭니다. 많이 힘듭니다. 머리는 쥐가나고, 코드는 스파게티가 되고, 뭐 하나라도 추가하려면 완전...

그래서 여기서도 똑똑하신분이 상태 관리하라고 노드를 하나 주셨는데, 그게 바로 AnimationTree노드입니다. AnimationTree노드를 사용하려면 각각의 동작 Animation은 사전에 구현이 되어있어야합니다. 이미 존재하는 Animation을 컨트롤하는 상태관리자라고 보시면 됩니다. 참고로 저는 코로나 격리기간 중에 아래와 같은 게임을 7살 딸과 함께 만드는 중이었습니다. 탑다운 방식이다보니 상하좌우방면의 idle/walk(run)/attack의 애니메이션이 모두 필요한 상태입니다.

1. AnimationTree노드 추가

우선은 해당 노드를 한번 추가해줍니다. 그리고 인스펙터에서 Tree Root에서 "새 AnimationNodeStateMachine"을 선택합니다. 다른 강좌 영상을 봐도 거의 이거 하나만 씁니다. 아직 아래의 작업공간에는 아무것도 나타나지 않습니다. (제 화면의 idle박스와 walk박스는 미리 작업해 놓은 것이므로 무시하세요.)

내부의 분홍색 라인은 카메라 때문에 나타납니다. 무시하세요.

2. Add BlendSpace2D

화면 하단의 AnimationTree작업공간에서 빈 공간에 마우스 우클릭 후 Add BlendSpace2D를 선택/추가해줍니다. 그리고 이름을 idle이라고 바꾸도록 하겠습니다. 동일한 방법으로 하나 더 추가하여 walk도 만듭니다.

3. Animation 노드 연결

메뉴에서 화살표를 선택하고 idle박스에서 walk박스로 드래그해주면 그 방향으로 화살표 선이 나타나 연결됩니다. 동일한 방법으로 반대쪽도 수행해줍니다.

다음으로 idle박스를 선택한 후 빨간색으로 표시된 버튼을 눌러서, idle이 게임 시작의 최초 "시작"상태임을 인식시켜줍니다. 

(ps) 각 화살표를 클릭하면 인스펙터에서 설정 가능한 부분들이 나타납니다. 여기서 다룰 부분이 아니므로, 이번에는 패스하도록 하겠습니다.  

 

4. BlendSpace편집

idle박스의 편집버튼을 누르면 BlendSpace편집창이 나타납니다.

이미지상에 하단부분 클릭도 빼놓지 않고 추가해줍니다.

X축 및 Y축이 -1 ~ +1까지 있는 좌표계가 나타납니다. 이는 마우스, 키보드 등의 입력값을 말합니다. 즉 캐릭터의 현재 위치를 (0, 0)으로 보고, 이를 중심으로 마우스가 어디에서 눌렸는지를 받아들여 이에 대한 반응을 보여줍니다. (키보드일 경우는 중간이 없고 상/하/좌/우만 있겠죠?). 주의할 점은 Y값의 1이 화면에서는 아래쪽을 의미합니다. 즉, 위 그림에서 보이는 것처럼 (0, 1) 지점에 idle_down 애니메이션을 지정해아 합니다.

메뉴 아이콘의 3번째 버튼이 포인트 추가버튼입니다. 선택 후 위의 그림과 같이 마름모꼴의 각 지점을 클릭하면 에니메이션 추가를 할 수 있습니다. 각각의 좌표계에 애니메이션을 아래와 같이 추가합니다. 또한 혼합 버튼을 ...으로 변경합니다.

 

(-1, 0) : idle_left

(+1, 0): idle_right

(0, -1) : idle_top

(0, +1): idle_down

 

여기까지 진행하면 BlendSpace2D지정은 완료가 됩니다.

 

5. 테스트

이번엔 잘 지정이 되었는지 테스트를 해보겠습니다. 메뉴의 위쪽에 경로: root를 선택합니다. 

그럼 애니메이션 박스를 만들었던 화면으로 전환되는데, 여기서 idle버튼을 플레이합니다.(박스 안쪽에 작은 실행버튼을 클릭합니다.) 그리고 다시 편집 버튼을 눌러 아까의 BlendSpace편집 화면으로 들어갑니다.

그리고 마커 버튼을 선택한 다음 마름모 영역 안에서 마우스를 드래그 하면 캐릭터가 해당 방향으로 애니메이션을 전환하는 모습을 볼 수 있습니다.

동일한 방법으로 walk 애니메이션도 추가해 줍니다. (attack애니메이션도 있으면 추가합니다.)

 

6. 스크립트 코딩

그럼 이렇게 만든 애니메이션을 어떻게 사용하는지 보겠습니다. 우리가 사용할 idle애니메이션은 "AnimationTree"노드의 "parameters/playback"이라는 속성에 존재합니다. 

이 속성을 활용하기 위해 anim_mode = anim_tree.get("parameters/playback")을 사용합니다. 그리고 실제 애니메이션을 구현하는 것은 anim_mode.travel 함수입니다. anim_mode.travel("idle")과 같이 사용하면 idle애니메이션을 실행합니다.

extends KinematicBody2D


const SPEED = 120
var direction = Vector2(0,0)
var motion = Vector2(0,0)
onready var anim_tree = $AnimationTree
onready var anim_mode = anim_tree.get("parameters/playback")


func _physics_process(delta):
	controls()
	movement()	
	anim_control()
	
func controls(): 
# 키보드 입력을 받아서 방향을 정하는 함수
	var LEFT = Input.is_action_pressed("ui_left")
	var RIGHT = Input.is_action_pressed("ui_right")
	var UP = Input.is_action_pressed("ui_up")
	var DOWN = Input.is_action_pressed("ui_down")
	
	direction.x = -int(LEFT) + int(RIGHT)
	direction.y = -int(UP) + int(DOWN)
	
	
func movement():
# 방향을 크기 1인 벡터 motion으로 만들고, 이를 이용해 동작을 구현
	motion = direction.normalized()
	move_and_slide(motion * SPEED, Vector2(0,0))

func anim_control():
# 방향에 따라 애니메이션 지정. 애니메이션의 방향 값만 설정해 놓음
	match direction:
		Vector2(-1,0):
			anim_tree.set('parameters/walk/blend_position', Vector2(-1,0))
			anim_tree.set('parameters/idle/blend_position', Vector2(-1,0))
		Vector2(1,0):
			anim_tree.set('parameters/walk/blend_position', Vector2(1,0))
			anim_tree.set('parameters/idle/blend_position', Vector2(1,0))
		Vector2(0,-1):
			anim_tree.set('parameters/walk/blend_position', Vector2(0,-1))
			anim_tree.set('parameters/idle/blend_position', Vector2(0,-1))	
		Vector2(0, 1):
			anim_tree.set('parameters/walk/blend_position', Vector2(0, 1))
			anim_tree.set('parameters/idle/blend_position', Vector2(0, 1))	
	
    # 방향이 설정된 상태에서 애니메이션을 선택
	if motion.length()!=0: # motion의 크기가 0이 아니면 이동중이므로 walk 애니
		anim_mode.travel("walk")
	else:		# motion의 크기가 0이면 멈춤이므로 idle 애니
		anim_mode.travel("idle")

 

이상으로 AnimationTree노드의 BlendSpace2D속성을 이용한 애니메이션 구현을 알아보았습니다. 

 

~~끝~~

반응형
반응형

이미지.zip
0.04MB


1. Display Size를 (400,500)으로 세팅합니다.
2. Borderless를 설정합니다.
3. Per Pixel Transparency를 Allowed, Enabled 로 바꿉니다.

4. 노드 구성
- clock (control)
ㄴ ColorRect (ColorRect)
ㄴ Body (sprite) : 시계 몸체 이미지
ㄴ pivot_h (Node2D)
ㄴ Hour (sprite) : 시침 이미지
ㄴ pivot_m (Node2D)
ㄴ Minute (sprite) : 분침 이미지
ㄴ pivot_s (Node2D)
ㄴ Second (sprite) : 초침 이미지
5. Body는 앱 중앙에 위치시키고, pivot들은 Body의 중앙에, pivot 내부의 sprite이미지들은 위와같이 정렬해줍니다. Godot 엔진에서 이미지를 특정 포인트를 기준으로 돌리기가 어렵습니다. 그래서 모두 가운데를 기준으로 돌리되, sprite이미지를 아예 적당한 거리에 떨어트려놓으면 가운데를 기준으로 돌아가는 효과를 만들 수 있습니다.

6. 스크립팅 (clock.gd)

extends Control var following = false 
var start_pos = Vector2() 

func _ready(): 
	get_tree().get_root().set_transparent_background(true) 
   
func _physics_process(delta): # ESC버튼으로 프로그램 종료 기능 
	if Input.is_action_pressed("ui_cancel"): 
    	get_tree().quit() # 시침/분침/초침의 회전 각도 계산 
    
    $Body/pivot_h.rotation = (OS.get_time()["hour"] + OS.get_time()["minute"] / 60.0) * PI/6 
    $Body/pivot_m.rotation = OS.get_time()["minute"] * PI/30 
    $Body/pivot_s.rotation = OS.get_time()["second"] * PI/30 
    
# 마우스 클릭&드래그로 시계 프로그램 위치 이동 
func _on_clock_gui_input(event): 
	if event is InputEventMouseButton: 
    	if event.get_button_index()==1: 
        	following = !following 
            start_pos = get_local_mouse_position() 
    if following: 
        OS.set_window_position(OS.window_position + get_local_mouse_position() - start_pos)


<결과>

반응형
반응형

GUI에서 레이아웃을 구성할 때  앵커를 이용해서 다양한 종횡비를 처리할 수도 있습니다만, 좀더 효과적으로 내부 요소들을 배치하기 위해서는 컨테이너가 필요합니다.

컨테이너 노드를 사용하면 모든 자식 노드는 자신의 지정된 위치가 무시됩니다. 즉, 컨테이너가 위치를 제어하게 되고, 컨테이너의 크기가 조정되면 모든 자식 노드가 그에 따라 재배치되며 사용된 컨테이너 유형에 따라 동작합니다.

HBoxContainer 가 자식 버튼 의 크기를 조정 하는 예입니다.

Size Flag

컨테이너에 노드를 추가할 때 컨테이너가 각 자식을 처리하는 방식은 주로 Size Flag에 따라 다릅니다 . Size Flag는 컨테이너의 자식노드(컨트롤 노드)의 inspector에서 찾을 수 있습니다.

크기 플래그는 수직 및 수평 크기 조정에 대해 독립적입니다.

  • Fill: 컨트롤 노드가 컨테이너 내의 지정된 영역을 채우도록 합니다. 컨트롤이 확장 되는지 여부에 관계없이 (아래 참조) 이 기능이 켜져 있을 때만 지정된 영역을 채웁니다 (기본값).
  • Expand : 부모 컨테이너에서 x축 또는 y축 전체를 사용하도록 확장합니다. 확장되지 않는 컨트롤은 확장하는 컨트롤에 의해 밀려납니다. 확장 컨트롤 사이에서 컨트롤이 서로 차지하는 공간의 양은 비율 에 따라 결정됩니다 (아래 참조).
  • Shirink Center : 확장할 때(Fill 옵션이 아닐 경우) 확장된 영역의 중앙에 유지하려고 합니다(기본값(Off)일 경우 왼쪽 또는 위쪽에 유지됨).
  • Ratio :  확장된 컨트롤이 서로에 대해 사용 가능한 공간을 차지하는 공간의 비율입니다. "2"가 있는 컨트롤은 "1"이 있는 컨트롤보다 두 배의 사용 가능한 공간을 차지합니다.

컨테이너 유형

Godot는 다양한 용도로 사용되는 몇 가지 컨테이너 유형을 기본적으로 제공합니다:

 

(1) Box Container

HBoxContainer  VBoxContainer 를 통해 자식 컨트롤을 수직 또는 수평으로 정렬합니다 . 지정된 방향의 반대(가로 컨테이너의 경우 수직)에서는 자식을 확장하여 꽉 채웁니다.

이 컨테이너는 Expand 플래그가 설정된 자식 컨트롤노드에 대해 Ratio 속성을 사용하여 사이즈를 조절합니다.

 

(2) Grid Container

그리드 형식으로 자식 컨트롤을 배치합니다(열 수를 지정해야 함). 수직 및 수평 확장 플래그를 모두 사용합니다.

 

(3) Margin Container

자식 컨트롤은 Container의 경계까지만 확장 됩니다. 테마 구성에 따라 여백에 패딩이 추가됩니다.

이 컨테이너에서의 여백은 테마 값이므로 각 컨트롤의 Margin이 아닌, 컨테이너의 Theme Overrides > Constants 섹션에서 편집해야 합니다.

 

(4) Tab Container

TabContainer 를 통해 현재 하나만 표시 되는 여러 자식 컨트롤을 중첩해서 만들 수 있습니다..

 

"현재탭"의 변경은 컨테이너 상단에 있는 탭 클릭을 통해 수행됩니다.

타이틀은 TabContainer API 를 통해 재정의할 수 있지만, 기본적으로 노드 이름에서 생성됩니다.

탭 배치 및 StyleBox 와 같은 설정은 TabContainer 테마 재정의(Theme overrides)에서 수정할 수 있습니다.

 

(5) Split Container

하나 또는 두 개의 자식 컨트롤만 허용하며, 분리선( HSplitContainer 및 VSplitContainer)을 기준으로 각각 배치합니다. 수평 및 수직 플래그 및 Ratio 모두 지정 가능합니다.

분리선을 끌어 두 자식 간의 크기를 변경할 수 있습니다.

 

(6) PanelContainer

StyleBox를 그릴 수 있는 간단한 컨테이너입니다. 내부 컨트롤은 전체영역으로 확장됩니다. (수평 및 수직 Size Flag 사용 가능).

이 컨테이너는 최상위 컨테이너로 사용, 또는 특정 부분에만 배경을 추가하는 등의 용도로 사용할 수 있습니다.

 

(7) ScrollContainer

단일 자식 노드만을 허용합니다. 이 노드가 컨테이너보다 크면 스크롤 막대가 추가됩니다. (수평 및 수직 Size Flag 사용 가능).

마우스 휠과 터치 드래그(터치가 가능한 경우)도 사용 가능합니다.

 

위의 예에서와 같이 ScrollContainer의 일반적인 사용 방법이 바로 VBoxContainer를 자식으로 함께 사용하는 것입니다.

반응형
반응형

Signal

소개

Signal은 옵저버 패턴의 Godot버전입니다. 특정 이벤트가 발생하면 노드는 신호를 보낼 수도 있고, 다른 노드는 그 신호에 응답할 수도 있습니다. 예로, 버튼이 눌려 있는지 매 프레임마다 확인하는 대신 버튼이 눌렸을 때 Signal을 보낼 수 있습니다. 

 

타이머 예

Timer 노드를 이용한 예제를 살펴보겠습니다. Node2D와 두 개의 자식(Timer 및 Sprite )이 있는 새 씬을 만듭니다 . 씬독에서 Node2D의 이름은 TimerExample로 바겠습니다.

Sprite의 텍스처에는 LoadSprite의 Texture 속성 드롭다운 메뉴에서 Godot 아이콘이나 원하는 다른 이미지를 할당합니다. 루트 노드에 스크립트를 첨부하되 아직 코드를 추가하지 마십시오.

씬 트리는 아래와 같이 구성합니다.

Timer 노드의 속성에서 자동 시작 옆에 있는 "켜기" 상자를 선택합니다 . 그러면 씬을 실행할 때 타이머가 자동으로 시작됩니다. 대기 시간 을 1초로 합니다.

Inspector 탭 옆에는 Node 탭이 있습니다. 이 탭을 클릭하면 선택한 노드가 내보낼 수 있는 신호 리스트를 볼 수 있습니다. Timer 노드의 경우 우리가 관심을 가져야할 것은 "timeout" Signal입니다. 이 Signal은 타이머가 0에 도달할 때마다 방출됩니다.

"timeout()" 신호를 클릭하고 신호 패널 하단의 "연결..."을 클릭합니다. 신호 연결 방법을 정의할 수 있는 다음 창이 표시됩니다.

왼쪽에서 씬의 노드를 볼 수 있으며 신호를 "수신"하려는 노드를 선택할 수 있습니다. Timer 노드는 파란색이며 이는 신호를 방출하는 노드임을 시각적으로 나타냅니다. 루트 노드를 선택합니다.

[주의] Signal수신하려는 노드에는 스크립트가 부착되어 있어야 하며 그렇지 않으면 오류 메시지가 표시됩니다.

고급 메뉴를 토글하면 오른쪽에 인수(다른 타입, 임의의 갯수)를 바인딩할 수 있는 것을 볼 수 있습니다. 

창 하단에는 "Receiver Method"이라는 필드가 있습니다. 이것은 사용하려는 대상 노드의 스크립트에 있는 함수의 이름입니다. 기본적으로 Godot는 _on_<노드이름>_<Signal이름> 의 명명 규칙으로 자동 생성 하지만 원하는 경우 변경할 수 있습니다.

"연결"을 클릭하면 스크립트에 함수가 생성됩니다.

extends Node2D


func _on_Timer_timeout():
    pass # Replace with function body.

이제 자동 생성된 코드를 신호가 수신될 때 실행하려는 코드로 바꿀 수 있습니다. Sprite를 깜박이도록 합시다.

extends Node2D


func _on_Timer_timeout():
    # Note: `$`는 `get_node()`의 단축형,
    # `$Sprite`는 `get_node("Sprite")`와 동일 의미.
    $Sprite.visible = !$Sprite.visible

씬을 실행하면 Sprite가 1초마다 깜박이는 것을 볼 수 있습니다. 타이머의 대기 시간 속성을 변경하여 깜빡임 속도를 변경할 수 있습니다.

코드에서 신호 연결

편집기가 아닌 코드에서 신호 연결을 만들 수도 있습니다. 이것은 일반적으로 코드를 통해 노드를 인스턴싱 할 때와 같이 편집기를 사용할 수 없을 때 필요합니다.

먼저 타이머의 "노드" 탭에서 연결을 선택하고 연결 해제를 클릭하여 신호를 연결 해제합니다.

코드에서 연결하기 위해 connect함수를 사용합니다. 실행하면 타이머가 자동 실행되도록 _ready() 함수에 입력하겠습니다 . 함수의 구문은

<노드>.connect(<signal이름>, <대상노드>, <대상함수이름>)

입니다 . 다음은 타이머 연결을 위한 코드입니다.

extends Node2D


func _ready():
    $Timer.connect("timeout", self, "_on_Timer_timeout")


func _on_Timer_timeout():
    $Sprite.visible = !$Sprite.visible

Custom Signal

Godot에서는 직접 생성한 커스텀 시그널을 선언을 사용할 수도 있습니다.

extends Node2D


signal my_signal

일단 선언되면 Custom Signal이 Inspector에 나타나며 다른 Signal과 같은 방식으로 연결할 수 있습니다.

코드를 통해 신호를 내보내려면 다음 emit_signal기능을 사용하세요.

extends Node2D


signal my_signal


func _ready():
    emit_signal("my_signal")

Signal 선언 시 하나 이상의 인수를 선언할 수도 있습니다. 괄호 사이에 인수 이름을 지정합니다.

extends Node


signal my_signal(value, other_value)

메모

Signal의 인수는 에디터의 노드 독에 표시되며, Godot는 이를 사용하여 콜백 함수를 생성합니다. 그러나 신호를 방출할 때 여전히 인수의 갯수를 변경할 수 있습니다. 따라서 올바른 값을 내보내는 것은 사용자에게 달려 있습니다.

값을 전달하려면 emit_signal함수에 두 번째 인수로 추가합니다.

extends Node


signal my_signal(value, other_value)


func _ready():
    emit_signal("my_signal", true, 42)

결론

Godot에 내장된 많은 노드 유형은 이벤트를 감지하는 데 사용할 수 있는 Signal을 제공합니다. 예를 들어, 동전을 나타내는 Area2D 는 플레이어의 물리적 몸체가 CollisionShape에 들어갈 때마다 body_entered신호를 방출하여 플레이어가 수집하는 것을 인식하게 할 수 있습니다.

 

반응형
반응형

스크립팅(계속)

Processing

대부분의 스크립트는 매 프레임에서 처리되어야 합니다. 처리에는 idle(유휴상태) 처리와 physics(물리) 처리의 두 가지 유형이 있습니다.

Idle 처리는 Node._process() 메서드가 스크립트에서 발견되면 활성화됩니다. Node.set_process() 함수로 켜고 끌 수 있습니다. 이 메서드는 프레임이 그려질 때마다 호출됩니다.

func _process(delta):
    # Do something...
    pass

_process() 함수가 호출 빈도는 실행 중인 애플리케이션의 FPS(초당 프레임 수)에 따라 다릅니다. 이 비율은 실행 시점마다, 또는 장치에 따라 다를 수 있습니다.

delta매개변수에는 _process()를 호출한 이전 시점부터 경과된 시간(초)이 부동 소수점 숫자로 포함됩니다.

이 매개변수는 게임의 FPS에 관계없이 항상 동일한 시간이 소요되도록 하는 데 사용할 수 있습니다.

예를 들어, 움직임 속도를 프레임 속도와 상관없이 일정하게 만들기 위해 움직임에 시간 델타를 곱하는 경우가 많습니다.

_physics_process() 함수도 비슷하지만 캐릭터 제어와 같이 각 물리 단계 전에 발생해야 하는 프로세스에 사용해야 합니다. 즉, 좀 더 정확한 간격의 시간에 실행할 필요가 있을 때 이 함수를 사용합니다. 기본적으로 초당 60회입니다. Physics -> Common -> Physics Fps 아래의 Project Settings 메뉴에서 간격을 변경할 수 있습니다.

_process()은 physics와 동기화되지 않습니다. 프레임 속도는 하드웨어 및 게임 최적화 등에 따라 모두 다릅니다. 실행은 single-threaded 게임에서 물리 단계 후에 수행됩니다.

_process() 함수가 작동 중인지 다음 스크립트로 간단히 확인해보겠습니다. 

extends Label

var accum = 0

func _process(delta):
    accum += delta
    text = str(accum) # 'text' is a built-in label property.

매 프레임마다 값이 증가되는 카운터가 표시됩니다. 

그룹

 Godot의 그룹은 다른 소프트웨어에서 볼 수 있는 태그와도 유사한 개념입니다. 노드는 복수의 그룹에 추가할 수 있습니다.  그룹 추가 첫번째 방법은 UI의 Node 패널 아래의 Groups 버튼을 사용하여 지정하는 방법입니다.

그리고 두 번째 방법은 코드에서입니다. 

func _ready():
    add_to_group("enemies")

SceneTree.call_group() 함수는 group 과 액션함수를 연결하는 함수입니다. 아래 코드는, 플레이어가 비밀 기지에 몰래 잠입한 것이 발견되면 모든 적에게 알람을 울리는 예시 코드입니다.

func _on_discovered(): # This is a purely illustrative function.
    get_tree().call_group("enemies", "player_was_discovered")

enemies그룹의 모든 멤버에 대해 player_was_discovered함수를 호출합니다 .

SceneTree.get_nodes_in_group() 함수는 enemies 노드 목록을 가져옵니다.

var enemies = get_tree().get_nodes_in_group("enemies")

SceneTree 클래스에는 장면, 해당 노드 트리 및 노드 그룹과의 상호 작용과 같은 많은 메서드를 제공합니다. 이를 통해 장면전환, 게임 재 로딩, 게임 종료, 일시 중지/해제 등을 쉽게 구현할 수 있습니다.

재정의 가능한 함수

노드에는 아래와 같이 재정의 가능한 함수들이 존재합니다.

func _enter_tree():
    # 노드가 Scene Tree에 들어가면 활성화됩니다.
    # 이 함수가 호출됩니다. 하위 노드는 아직 Scene Tree에 들어가지 않았습니다.
    # 대부분의 경우 _ready()를 사용하는 것이 좋습니다.
    pass

func _ready():
    # _enter_tree이후에 들어갑니다. 모든 하위 노드들이 Scene Tree에 들어갔으며
    # active상태임을 보장합니다.
    pass

func _exit_tree():
    # 노드가 Scene Tree를 나갈 때 이 함수가 호출됩니다.
    # 모든 하위 노드들은 Scene Tree나갔으며 모두 비활성화되었습니다    
    pass

func _process(delta):
    # 매 프레임마다 호출됨
    pass

func _physics_process(delta):
    # 매 물리프레임마다 호출됨
    pass

 

노드 생성

코드에서 노드를 생성하려면 다른 클래스 기반 데이터 유형과 마찬가지로 .new() 메서드를 호출합니다. 

var s
func _ready():
    s = Sprite.new() # 새 sprite노드 생성
    add_child(s) # 이 노드에 자식 노드로 추가.

노드를 삭제하려면 장면 내/외부에 관계없이 free() 함수를 사용합니다.

func _someaction():
    s.free() # 장면에서 노드를 즉시 제거/해제.

노드가 해제되면 모든 자식 노드도 해제됩니다. 이 때문에 노드 수동 삭제가 간단합니다. 기본 노드를 해제하면 하위 트리의 다른 모든 항목이 함께 사라집니다.

Signal 또는 함수 호출로 인해 "Blocked" 노드를 삭제하려는 시도가 의도치 않게 발생할 수 있습니다. 이 경우 게임이 중단되고, 디버거에서 경고를 보내줄 수도 있습니다. 노드를 삭제하는 가장 안전한 방법은 Node.queue_free() 함수를 사용하는 것 입니다. idle 상태에서 노드가 안전하게 지워집니다.

func _someaction():
    s.queue_free() # 씬에서 안전하게 삭제/해제 가능할 경우 수행

씬 인스턴싱 (Instancing Scenes)

코드에서의 씬 인스턴싱은 두단계로 수행됩니다. 첫번째는 씬 로딩입니다.

var scene = load("res://myscene.tscn") # 스크립트가 인스턴싱(수행)되면 불러옴.

preload는 구문 분석 타이밍에 발생하므로 더 편리할 수 있습니다(GDScript만 해당):

var scene = preload("res://myscene.tscn") # 스크립트 구문분석 타이밍에 불러옴

그러나 씬은 아직 노드가 아닙니다. PackedScene 이라는 특수 리소스에 Pack되어 있습니다. 실제 노드를 생성하려면 PackedScene.instance() 함수 를 호출해야 합니다. 그러면 활성 씬에 추가할 수 있는 노드 트리가 반환됩니다.

var node = scene.instance()
add_child(node)

이 2단계 프로세스의 장점은 패킹된 장면을 로드하고 사용할 준비가 된 상태에서, 원하는 만큼 많은 인스턴스를 생성할 수 있다는 것입니다. 이는 활성 씬에서 여러 적, 총알 등을 빠르게 인스턴스화하는 데 유용합니다.

스크립트를 클래스로 등록

Godot에는 "스크립트 클래스" 기능이 있습니다. 파일을 직접 로드하여 명명되지 않은 스크립트에만 액세스할 수 있습니다. 편집기에서 class_name키워드 다음에 클래스 이름을 사용하여 타입으로 등록할 수 있습니다. 그런 다음 쉼표 뒤에 PNG 또는 SVG 의 아이콘 이미지 경로를 추가합니다. (최소 16×16, 32×32 권장). 그러면 노드 또는 리소스 생성 대화 상자에서 추가한 새로운 타입을 찾을 수 있습니다. (에디터 재시작 필요)

extends Node

# Declare the class name here
class_name ScriptName, "res://path/to/optional/icon.svg"

func _ready():
    var this = ScriptName           # reference to the script
    var cppNode = MyCppNode.new()   # new instance of a class named MyCppNode

    cppNode.queue_free()

Godot 3.1참고:

  • GDScript 및 NativeScript(C++ 및 기타 GDNative 기반 언어)만 스크립트 클래스 등록가능
  • GDScript만 스크립트 클래스의 전역 변수 생성가능
반응형
반응형

스크립팅 언어

 

사용 가능한 스크립팅 언어

Godot는 GDNative 기술을 통해 GDScript, C#, VisualScript, C++ 의 4가지 게임플레이 프로그래밍 언어를 공식적으로 제공합니다 . 커뮤니티에서 지원하는 언어로 Python, Lua, Rust 등도 있습니다.

단일 프로젝트에서 여러 언어를 사용할 수 있습니다. 예를 들어 팀에서 GDScript로 게임 플레이 로직을 코딩할 수 있습니다. 작성 속도가 빠르기 때문에 레벨 디자이너가 그래픽 언어 VisualScript로 퀘스트를 스크립팅하도록 하고, C# 또는 C++를 사용하여 복잡한 알고리즘을 구현하고 성능을 극대화할 수 있습니다. 또는 GDScript 또는 C#으로 모든 것을 작성할 수 있습니다. 

어떤 언어를 사용해야 하는가?

GDScript  시작하는 것을 추천합니다. 이 언어는 Godot와 게임 개발자의 요구를 위해 특별히 만들었습니다. 가볍고 간단한 구문을 가지고 있으며 Godot와 가장 긴밀하게 통합됩니다.

C#의 경우 VSCode 또는 Visual Studio 와 같은 외부 코드 편집기가 필요합니다 . C# 지원이 이제 완성되었지만 GDScript에 비해 학습 리소스가 더 적습니다. 그렇기 때문에 이미 C# 언어에 대한 경험이 있는 사용자에게 주로 C#을 권장합니다.

각 언어의 특징과 장단점을 살펴보겠습니다.

GD스크립트

GDScript  Godot를 위해 구축된 객체 지향  명령형 프로그래밍 언어입니다. 게임 개발자가 게임을 코딩하는 시간을 절약할 수 있도록 제작되었습니다. 기능은 다음과 같습니다.

  • 간단한 구문.
  • 빠른 컴파일 및 로딩 시간.
  • 노드, 신호 및 연결된 장면의 추가 정보 등에 대한 코드 자동완성기능(인텔리센스) 및 긴밀한 편집기 통합.
  • 벡터 및 변환 유형이 내장되어 있어 게임의 필수 요소인 선형 대수를 사용시 효율적.
  • 정적 타입지정이 가능하여 효율적으로 다중 스레드를 지원. (Lua나 Python에서 지원하기 어려움)
  • 가비지 수집 기능 없음. 엔진은 대부분의 경우 기본적으로 Reference를 계산하고 메모리를 관리하지만 필요한 경우 메모리를 제어할 수도 있음.
  • 변수는 기본적으로 동적 유형이 있지만 강력한 유형 검사를 위해 Type Hint를 사용할 수도 있음.

GDScript는 들여쓰기를 사용하여 코드 블록을 구성할 때 Python처럼 보이지만 실제로는 같은 방식으로 작동하지 않습니다. Squirrel, Lua 및 Python을 포함한 여러 언어에서 영감을 받았습니다.

[참고]

Python이나 Lua를 직접 사용하지 않는 이유.

Godot 초기에는 Python을 사용한 다음 Lua를 사용했습니다. 두 언어의 통합에는 많은 작업이 필요했고 심각한 제한이 있었습니다. 예를 들어, 스레딩 지원은 Python에서 쉽지 않았습니다. 전용 언어를 개발하는 데 더 많은 작업이 필요하지 않으며 게임 개발자의 요구에 맞게 조정할 수 있습니다. 현재 타사 언어로는 제공하기 어려웠던 성능 최적화 및 기능을 개발하고 있습니다.

.NET / C#

Microsoft의 C# 은 게임 개발자들이 가장 좋아하는 언어로, 마이크로소프트 지원 아래 공식적으로 지원하게 되었습니다. C#은 수많은 라이브러리가 작성된 성숙하고 유연한 언어입니다. 

C#에서 스크립팅하려면 Godot Mono 버전을 사용해야 합니다. Godot 웹사이트의 다운로드 페이지에서 다운로드 할 수 있습니다. Godot는 Mono .NET 런타임을 사용하기 때문에 이론적으로 Godot의 모든 타사 .NET 라이브러리 또는 프레임워크는 물론 F#, Boo 또는 ClojureCLR과 같은 공용 언어 인프라 호환 프로그래밍 언어를 사용할 수 있습니다. 그러나 공식적으로는 C#이 유일한 .NET 지원 언어입니다.

[참고]

GDScript 코드 자체는 컴파일된 C# 또는 C++만큼 빠르게 실행되지 않습니다. 그러나 대부분의 스크립트 코드는 엔진 내부의 C++ 코드에서 빠른 알고리즘으로 작성된 함수를 호출합니다. 많은 경우 GDScript, C# 또는 C++로 게임 플레이 로직을 작성하는 것은 성능에 큰 영향을 미치지 않습니다.

비주얼스크립트(VisualScript)

비주얼 스크립팅 은 블록을 연결하는 그래프 기반의 비주얼 프로그래밍 언어입니다. 게임 디자이너와 아티스트와 같은 프로그래머가 아닌 사람들에게 유용한 도구가 될 수 있습니다.

다른 언어를 사용하여 게임에 특정한 맞춤형 블록을 생성할 수 있습니다. 예를 들어 AI, 퀘스트 또는 대화를 스크립팅합니다. 

완전한 게임을 코딩하는 데 필요한 모든 기본 빌딩 블록을 제공하지만 VisualScript를 이런 방식으로 사용하지 않는 것이 좋습니다. 그것으로 모든 것을 프로그래밍하는 것은 다른 프로그래밍 언어를 사용하는 것에 비해 오히려 느립니다. 자세한 내용은 VisualScript 시작하기 를 참조하십시오 .

 

GDNative를 통한 C 및 C++

마지막으로, GDNative를 이용한 C++ 스크립팅은 Godot를 재컴파일(또는 다시 시작)할 필요가 없습니다.

 

모든 C++ 버전을 사용할 수 있으며 내부 C API Bridge 사용 덕분에, 생성된 공유 라이브러리에 대해 컴파일러 브랜드와 버전을 혼합하는 것이 완벽하게 작동합니다.

이 언어는 성능을 위한 최선의 선택이며 다른 부분은 GDScript 또는 Visual Script로 작성할 수 있으므로 전체 게임에서 사용할 필요는 없습니다. GDNative 인터페이스를 통해 더 많은 언어를 사용할 수 있지만, 공식적으로 지원되는 언어는 C++입니다.

 

씬 스크립팅

아래 부분에서는 버튼과 레이블로 구성된 GUI 씬을 제작합니다. 여기서 버튼을 누르면 레이블이 업데이트됩니다. 

  • 스크립트를 작성하고 노드에 연결합니다.
  • 신호를 통해 UI 요소 연결.
  • 장면의 다른 노드에 액세스할 수 있는 스크립트 작성.

[GDScript 참조문서]

씬 설정

새 프로젝트를 만들어 시작하겠습니다. 씬 탭에서 "Add Child Node"(또는Ctrl + A)를 눌러 아래와 같이 노드를 추가합니다.

  • Panel
    • Label
    • Button

 

2D 편집기에서는 아래의 이미지와 같이 보이도록 크기 및 위치를 조정하고, Inspector 탭에서 텍스트를 작성합니다.

마지막으로 씬 이름을 sayhello.tscn로 하여 저장합니다.

스크립트 추가

패널 노드를 마우스 오른쪽 버튼으로 클릭한 다음 상황에 맞는 메뉴에서 "스크립트 첨부"를 선택합니다.

스크립트 생성 대화 상자가 나타납니다. 이 대화 상자에서는 스크립트의 언어, 클래스 이름 및 기타 관련 옵션을 설정할 수 있습니다.

GDScript에서 파일 자체는 클래스를 나타내므로 클래스 이름 필드는 편집할 수 없습니다.

스크립트를 연결할 노드는 패널이므로 상속 필드는 자동으로 "패널"로 채워집니다. 즉, 우리는 패널노드의 기능을 확장한다는 의미입니다.

마지막으로 스크립트의 경로 이름을 입력하고 Create 버튼을 선택합니다.

그러면 스크립트가 생성되어 노드에 추가됩니다. 이것은 씬탭의 노드 옆에 있는 "스크립트 열기" 아이콘과 Inspector 아래의 스크립트 속성에서 확인할 수 있습니다.

스크립트를 편집하려면 위 이미지에서 강조 표시된 두 버튼 중 하나를 선택합니다. 그러면 기본 템플릿이 포함된 스크립트 편집기로 이동합니다.

_ready() 함수는 노드와 모든 자식이 활성 장면에 들어갈 때 호출됩니다. 단, _ready() 생성자가 아닙니다. 생성자는 _init()입니다.

스크립트의 역할

스크립트는 노드에 동작을 추가합니다. 노드가 작동하는 방식과 다른 노드(자식, 부모, 형제 등)와의 상호 작용을 제어하는 ​​데 사용됩니다. 스크립트는 해당 노드에서 제공하는 기능을 상속합니다.

Signal 처리

Signal은 특정 종류의 액션이 발생할 때 "Emit(방출)"되며 모든 스크립트 인스턴스의 모든 기능에 연결될 수 있습니다. Signal은 다른 노드에도 있지만 대부분 GUI 노드에서 사용되며 자신의 스크립트에서 사용자 정의 Signal을 정의할 수도 있습니다.

이 단계에서는 "pressed" Signal을 사용자 정의 함수에 연결합니다. 첫번째는 Signal 생성이고, 두번째는 연결 함수 정의입니다. 첫 번째 부분에서 Godot는 연결을 생성하는 두 가지 방법을 제공합니다: 편집기가 제공하는 시각적 인터페이스를 이용하거나 코드를 이용하는 것입니다.

우선 편집기의 인터페이스를 사용하는 방법을 알아보겠습니다.

장면 트리에서 버튼 노드를 선택한 다음 "노드" 탭을 선택합니다. 다음으로 "신호"가 선택되어 있는지 확인하십시오.

그런 다음 "BaseButton" 아래에서 "pressed()"를 선택하고 오른쪽 하단의 "연결..." 버튼을 클릭하면 연결 생성 대화 상자가 열립니다.

대화 상자의 상단에는 방출 노드의 이름이 파란색으로 강조 표시된 상태로 해당 씬의 노드 목록이 표시됩니다. 여기에서 "Panel" 노드를 선택합니다. Panel 노드의 스크립트에 해당 함수를 추가하는 것입니다.

대화 상자의 맨 아래에는 생성될 메서드의 이름이 표시됩니다. 기본적으로 메서드 이름은 _on_[EmitterNode]_[signal_name] 형식에 따라 생성됩니다.

 

다음으로 코드로 Signal을 연결하는 방법을 알아보겠습니다. 

Godot 프로그래머가 가장 많이 사용하는 Node.get_node() 함수를 소개합니다 . 이 함수는 경로를 사용하여 스크립트가 작성되는 노드를 기준으로 자식노드를 가져올 수 있습니다.

 

extends Panel 아래에 있는 모든 항목을 삭제 합니다. 나머지 스크립트는 수동으로 작성합니다.

스크립트가 첨부된 Panel노드 아래에서 Button과 Label은 형제노드이기 때문에 _ready() 함수 내부에 다음을 입력하여 Button을 가져올 수 있습니다.

func _ready():
    get_node("Button")

다음으로, 버튼을 눌렀을 때 호출될 함수를 작성합니다.

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"

마지막으로 Object.connect()함수를 이용하여 "Pressed" Signal과 _on_Button_pressed()함수를 연결합니다.

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")

[최종 스크립트]

extends Panel

func _ready():
    get_node("Button").connect("pressed", self, "_on_Button_pressed")

func _on_Button_pressed():
    get_node("Label").text = "HELLO!"

씬을 실행하고 버튼을 누르면 아래 화면과 같이 나타납니다.

드디어 첫 스크립트를 완성하였습니다. 

[참고]

Button이 Label의 자식인 경우 이를 얻기 위한 코드는 다음과 같습니다.

# Not for this case,
# but just in case.
get_node("Label/Button")

또한 예제에서는 이름을 변경하지 않고 노드이름을 그대로 사용했지만, 실제로는 Label, Button등의 노드는 유형이 아니라 우리가 정의한 bullet, player등의 Name으로 참조된다는 점도 유의하시기 바랍니다.

반응형
반응형

요약

인스턴싱은 편리함이 많습니다. 

  • 장면을 세분화하고 관리하기 쉽게 만드는 기능.
  • 여러 노드 인스턴스를 한 번에 관리하고 편집하는 기능.
  • 복잡한 게임 흐름 또는 UI를 나누어 구성하고 포함하는 기능. (Godot에서 UI 요소도 노드(장면)임).

 

소개

Scene & Node 에서 씬은 하나의 루트 노드에서 시작되는 트리 구조의 노드 모음임을 배웠습니다.


원하는 만큼 장면을 만들어 디스크에 저장할 수 있습니다. 이러한 방식으로 저장된 장면을 "Packed Scene"이라고 하며 .tscn 확장자의 파일로 저장됩니다.

씬이 저장되면 다른 씬에서 노드처럼 인스턴싱 할 수 있습니다.

위 그림에서 Scene B는 Scene A에 인스턴스로 추가되었습니다.

인스턴싱 예시

샘플 프로젝트: instancing.zip. .

적당한 곳에 압축을 풀고 프로젝트 매니저에서 '가져오기(Import)' 버튼을 사용하여 프로젝트를 추가합니다.

압축을 푼 폴더의 "project.godot" 파일을 열면 새 프로젝트가 프로젝트 목록에 나타납니다. '편집' 버튼을 눌러 프로젝트를 편집합니다.

이 프로젝트에는 "Ball.tscn" 및 "Main.tscn"의 두 씬이 포함되어 있습니다. Ball 씬은 RigidBody2D 를 사용하여 물리적 동작을 제공하고, Main 씬은 Ball이 충돌할 장애물 세트로 되어 있습니다( StaticBody2D 사용 ).

 

Main씬을 열고 Main루트 노드를 선택합니다.

Ball씬의 인스턴스를 Main씬의 자식으로 추가하려고 합니다. "링크" 모양의 버튼(호버 텍스트에 "씬 파일을 노드로 인스턴스화"라고 표시됨)을 클릭하고 Ball.tscn파일을 선택합니다.

공은 화면 영역의 왼쪽 상단 모서리에 배치됩니다( 화면 (0, 0)좌표에 있음). 편집창의 상단 중앙 근처로 공을 드래그합니다.

"재생"을 누르고 공이 화면 하단으로 떨어지는 것을 확인합니다.

Multi Instance

"인스턴스" 버튼을 다시 사용하거나 공 인스턴스를 클릭하고 Ctrl + D( macOS의 경우 Cmd + D) 키를 눌러 인스턴스를 복제 하여 원하는 만큼 넉넉히 인스턴스를 장면에 추가합니다.

장면을 다시 실행하면 모든 공이 떨어집니다.

인스턴스 편집

Ball씬을 열고 PhysicsMaterial클릭하여 확장하고 Bounce속성을 1로 설정합니다.

"재생"을 누르면 인스턴싱된 모든 Ball이 더 탄력 있는 모습이 확인될 것입니다. 인스턴싱된 Ball은 Packed Scene을 기반으로 하기 때문에 해당 장면에 대한 변경 사항은 모든 인스턴스에 영향을 미칩니다.

개별 인스턴스를 조정할 수도 있습니다. 바운스 값을 다시 0 으로 설정한 다음 Main씬에서 Ball 인스턴스중 하나를 선택합니다. PhysicsMaterial 같은 리소스는 기본적으로 인스턴스 간에 공유되므로 고유하게 만들어야 합니다. Inspector 독의 오른쪽 상단에 있는 도구 버튼을 클릭하고 "하위 리소스를 고유하게 만들기"를 선택하십시오.  Bounce 1로 설정하고 "재생"을 누릅니다.

조정된 속성 옆에 회색 "되돌리기" 버튼이 나타납니다. 이 버튼이 있으면 Packed Scene에서 해당 값을 재정의하도록 인스턴싱된 씬의 속성을 수정했음을 의미합니다. 해당 속성이 원본 장면에서 수정되더라도 사용자 정의 값은 유지됩니다. 되돌리기 버튼을 누르면 속성이 Packed Scene의 값으로 복원됩니다.

 

디자인 언어

그러나 씬 인스턴싱 방식의 가장 큰 장점은 뛰어난 디자인 언어로 작동한다는 것입니다. 이것은 Godot를 다른 모든 엔진과 구별하는 특징으로 Godot엔진은 처음부터 이러한 개념으로 설계되었습니다.

Godot로 게임을 만들 때 권장되는 접근 방식은 MVC 또는 Entity-Relationship 다이어그램과 같은 가장 일반적인 디자인 패턴 대신 더 자연스러운 방식으로 장면에 대해 생각하는 것입니다. 프로그래머뿐만 아니라 누구나 생각할 수 있는, 눈에보이는 게임의 요소를 상상하는 것부터 시작합니다.

예를 들어 간단한 슈팅 게임을 다음과 같이 상상해보겠습니다.

거의 모든 종류의 게임에 대해 이와 같은 다이어그램을 만들 수 있습니다. 게임에서 시각화할 수 있는 부분을 기준으로 다이어그램을 만들고, 화살표를 추가하여 구성 요소간의 소유(상위) 개념을 표시합니다.

이러한 다이어그램을 작성한 후에는, 다이어그램의 각 요소에 대해 장면(씬)을 만드는 방식으로 작업하는 것을 추천합니다. 소유권 관계에 대해 인스턴싱(코드로 또는 편집기에서 직접 추가 가능)을 사용합니다.

게임(또는 일반적인 소프트웨어)을 프로그래밍하는 데 사실은 굉장히 많은 시간이 아키텍처를 설계하고, 해당 아키텍처에 게임 구성 요소를 맞추는 데 소요됩니다. 장면(씬)을 기반으로 하는 디자인은 이러한 접근 방식을 대체하고 개발을 훨씬 빠르고 간단하게 만들어 게임 로직 자체에 집중할 수 있도록 합니다. Godot엔진에서는 게임 구성 요소가 장면(씬)과 동일하게 매핑되기 때문에, 장면 인스턴싱을 기반으로 하는 디자인을 사용하면 그 외의 추가적인 아키텍처 코드가 거의 필요하지 않습니다.

많은 자산과 중첩 요소가 있는 오픈 월드 게임 유형의 좀 더 복잡한 예를 하나 더 살펴보겠습니다.

Room 요소(씬) 에서 시작했다고 가정해 보겠습니다. 우리는 서로 다른 가구(씬)를 배치(인스턴싱)하여 다른 Room장면(씬)을 만들 수 있습니다. 여러 Room장면(씬)을 연결하여 내부를 구성하는 House장면(씬)을 만들 수 있습니다.

그런 다음 많은 House장면(씬)으로 인스턴싱된 Citadel장면(씬)을 만들 수 있습니다. 그런 다음, 세계 지도 Terrain에 대한 작업을 시작하여 그 위에 Citadel를 추가할 수 있습니다.

나중에 경비원(및 다른 NPC)을 나타내는 장면(씬)을 만들고 Citadel에도 추가할 수 있습니다. 결과적으로 전체 게임 세계에 간접적으로 추가됩니다.

Godot엔진을 사용하면 더 많은 장면(씬)을 만들고 인스턴싱하기만 하면 되므로 이와 같이 게임 요소를 쉽게 반복 제작할 수 있습니다. 또한 편집기 UI는 프로그래머와 비프로그래머 모두에게 사용자 친화적으로 설계되었습니다. 일반적인 팀 개발 과정에는 2D 또는 3D 아티스트, 레벨 디자이너, 게임 디자이너 및 애니메이터 등이 있는데 모두 단일 편집기 인터페이스로 작업할 수 있습니다.

 

반응형

+ Recent posts