티스토리 뷰

728x90

위의 그림은 고도의 데모 프로젝트 FPS MP Client에 있는 스크립트 예제로 GDScript라 불리는 고도의 스크립트 언어는 파이썬의 언어 체계와 유사한 특성을 가지고 있는데 대표적인 유사점으로 키워드들이 비슷하고, 들여 쓰기로 코드 블록을 구분한다는 점이다. 게임 개발과 통합에 있어 스크립트를 통해 상당한 유연성을 확보할 수 있다. 버전 3.0 이전에는 GDScript만 지원했으나 현재는 GDScript를 다이어그램 형태로 사용할 수 있는 Visual Script를 사용할 수 있게 되었다.

 

위의 그림은 템플릿에서 "Visual"로 검색하면 나오는 "Pong with VisualScript" 프로젝트의 스크립트 편집화면으로 내용을 보면 GDScript를 그림 형태로 만들 수 있다는 점을 제외하면 프로그래밍 경험이 있는 개발자들에게는 그리 매력적인 기능이 아닐 수 있다. 비주얼 스크립트의 제작 목적도 코딩 경험이 없는 게임 개발자나 디자이너를 대상으로 했다고 한다. 디자이너가 간단히 프로토타입을 제작해 보거나, 프로그래머의 가이드 하에 일부 기능을 디자이너들이 소화하도록 하여 협의의 능률을 높일 수 있게 하기 위한 목적이다. 아무튼 GDScript 처럼 비주얼 스크립트도 고도 편집기 내에서 편집할 수 있고 *.vs라는 파일 확장자로 저장한다. GDScript는 *.gd로 저장한다.

 

GDScript와 비주얼스크립트외에도 고도는 mono 닷넷 기반으로 C#언어를 지원하고 고도 엔진 내부 기능(GDNative)을 C++로 사용할 수 있지만 이들 스크립트는 편집기 외부의 도구를 사용해야 하는 제약이 있다. C# 같은 경우에는 닷넷 프레임워크 4.5 이상과 비주얼 스튜디오를 설치해야 한다. 고도 엔진 자체가 C/C++로 제작되었으므로 C++ 스크립트와 API로 엔진 내부 기능을 사용한다면 최상의 성능을 낼 수 있지 않을까 싶다.

 

이제는 따라하기를 통해서 스크립트 맛보기를 하는데, 앞서 수행했던 예제("노드와 씬(Scene), Hello World 프로젝트" 참조)에서 위의 그림과 같이 버튼을 추가하고 버튼을 누르면 레이블에 특정 내용을 출력하는 스크립트를 작성한다.

 

레이블과 버튼으로 구성된 사용자 인터페이스가 준비되었으면 루트 노드를 선택한 상태에서 상단의 스크립트 붙이기 아이콘을 클릭하거나, 우측 마우스로 콘텍스트 메뉴> 스크립트 붙이기로 스트립트를 새롭게 작성한다. "노드 스크립트 붙이기" 대화창이 나오면 언어는 GDScript, 템플릿은 기본 상태로 [만들기]를 클릭하면 "노드 이름.gd"로 스크립트 파일이 생성된다.

 

위의 그림은 자동으로 생성된 템플릿의 모습으로 노드 Control을 확장한다는 것을 알 수 있다. _ready()는 해당 노드와 모든 자식 노드들이 활성 씬으로 들어갈 때 호출되는 함수로 pass부분에 필요한 내용을 입력하면 된다. _process() 함수는 일단 주석처리 되어 있는데 앞의 #을 지우면 사용할 수 있다. 게임의 매 프레임마다 신호가 전달되는 함수다.

 

버튼을 누를 때의 처리를 위해서 위의 그림처럼 버튼을 선택한 상태에서 우측의 인스펙터 옆에 있는 "노드"창을 선택하고 pressed()를 더블클릭한다.

 

"시그널을 메서드에 연결" 대화창이 나오면 Control을 선택하고 [연결]하면 앞서 추가한 스크립트에 _on_Button_pressed() 함수가 자동으로 추가된다. 비주얼스튜디오처럼 시그널을 추가하면 시그널 생성자도 자동 추가되면 좋을 것 같은데 이 부분은 수작업으로 추가해 주어야 한다. 

 

extends Control

var accum = 0

# Called when the node enters the scene tree for the first time.
func _ready():
	get_node("Button").connect("pressed", self, "_on_Button_pressed")

# Called every frame. 'delta' is the elapsed time since the previous frame.
func _process(delta):
	accum += delta
	if accum > 2.0 :
		get_node("Label").text = str(accum)


func _on_Button_pressed():
	get_node("Label").text = "Pressed!!!"
	accum = 0

 

코드 내용을 살펴보면 씬이 활성화 될때 수행되는 _ready()에서는 버튼을 눌렀을 때 수행할 _on_Button_pressed() 함수를 설정한다. 매 프레임마다 수행되는 _process()에서는 수행 주기 값을 변수에 누적시키다가 2초가 지나면 누적 상황을 레이블에 출력한다. 그리고 버튼이 눌리면 레이블에 버튼이 눌러졌다는 표시를 하면서 누적 변수 값을 초기화시키는 내용이다. 이런 내용만 보면 마치 C#으로 프로그램을 하나 개발하는 수준이다. 이런 기능을 게임 캐릭터나 게임 요소에 반영할 수 있는 것이니 상당한 유용한 도구가 되겠구나 하는 생각이 든다.

 

위의 그림들은 시작 단계, 누적후 2초가 지나서 누적 상황이 표시되는 단계, 버튼을 누른 경우의 실행화면이다.

 

코드를 작성하다보면 위의 그림처럼 자동 완성 기능이나 함수 인수에 대한 가이드가 나오는데 비주얼 스튜디오나 이클립스와 같은 통합 개발 환경에서 지원하는 것과 같은 개발 지원이므로 충분히 활용하는 것이 좋을 듯하다.

 

extends Node

# Here we can specify the public IP address for playing through the internet.
export var ip : String = "localhost"
# Port must be the same as the server's.
const PORT = 27015

var enet : NetworkedMultiplayerENet
var my_id : int = -1
var me_created : bool = false

onready var message = $ui/message
onready var player_scn = preload("res://scenes/player/player.tscn")
onready var puppet_scn = preload("res://scenes/player/puppet.tscn")

func _ready():
	get_tree().set_auto_accept_quit(false)
	# Connecting network and button events
	var _player_connected = get_tree().connect("network_peer_connected", self, "_on_player_connected")
	var _player_disconnected = get_tree().connect("network_peer_disconnected", self, "_on_player_disconnected")
	var _connected_to_server = get_tree().connect("connected_to_server", self, "_on_connected_to_server")
	var _connection_failed = get_tree().connect("connection_failed", self, "_on_connection_failed")
	var _server_disconnected = get_tree().connect("server_disconnected", self, "_on_server_disconnected")
	var _button_pressed = $ui/button.connect("pressed", self, "_on_connect_pressed")
	# Creating a new network instance
	enet = NetworkedMultiplayerENet.new()
	# Setting the anti-aliasing. TODO: settings
	get_viewport().msaa = Viewport.MSAA_8X

func _on_connection_failed():
	display_message("Connection failed!")
	get_tree().set_network_peer(null)

func _on_connected_to_server():
	my_id = get_tree().get_network_unique_id()
	display_message("Connection established. Your id is " + str(my_id))
	# Upon successful connection create a player instance and set it's camera to active
	var player = player_scn.instance()
	player.set_name(str(my_id))
	$players.add_child(player)
	player.get_node("head/camera").current = true
	me_created = true

func _on_server_disconnected():
	display_message("Server disconnected.")

func _on_player_connected(id):
	if me_created:
		var player = puppet_scn.instance()
		player.set_name(str(id))
		$players.add_child(player)

func _on_player_disconnected(id):
	for n in $players.get_children():
		if int(n.name) == id:
			$players.remove_child(n)
			n.queue_free()

func _on_connect_pressed():
	$ui/button.visible = false
	display_message("Connecting...")
	if !ip.is_valid_ip_address():
		display_message("IP is invalid!")
	var _client_created = enet.create_client(ip, PORT)
	get_tree().set_network_peer(enet)

func display_message(text : String):
	$ui/message.text = text

func _notification(what):
	if what == MainLoop.NOTIFICATION_WM_QUIT_REQUEST:
		get_tree().quit()

# Server calls this function upon connection
puppet func create_bots(bot_names):
	for n in bot_names:
		var bot = puppet_scn.instance()
		$bots.add_child(bot)
		bot.name = n

 

위의 코드는 앞서 언급한 데모 프로젝트 FPS MP Client에 있는 스크립트 예제로 다양한 스크립팅 샘플을 만나 볼 수 있다. onready var puppet_scn = preload("res://scenes/player/puppet.tscn") 과정을 통해서 파일에 있는 씬을 불러오고, var player = puppet_scn.instance()와 $players.add_child(player)로 개체화하는 것을 참조할 수 있다. "$ui/message"처럼 $다음에 노드의 이름을 붙여서 굳이 get_node()로 노드 오브젝트를 가져올 필요 없이 간편하게 노드를 참조할 수 있고 하위 노드를 지정할 경우에는 /로 구분할 수 있음도 확인할 수 있다. 개체화 등을 통해서 추가한 노드를 $players.remove_child(n)와 n.queue_free()를 통해서 안전하게 삭제하는 과정도 확인할 수 있다.

 

728x90
댓글
글 보관함
최근에 올라온 글
최근에 달린 댓글
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31