3. Colorslide tutorial

2020. 6. 18. 21:37DEFOLD/튜토리얼

Colorslide 게임 튜토리얼에 오신 것을 환영한다. 기존 멀티 레벨 모바일 게임에 간단한 GUI 흐름을 추가하는 과정을 안내한다. 당신이 에디터를 잘 알고 있다고 가정한다. 그렇지 않다면, 저희의 매뉴얼과 초급 자습서를 확인해 보십시오.

 

이 자습서의 출발점은 이 프로젝트다. 여기에는 필요한 모든 것이 포함되어 있다.

- 모든 벽돌의 색상이 자신의 타일과 일치 할 때까지 플레이어가 타일 판에 색깔의 벽돌을 밀어 넣는 단순하지만 완벽하게 플레이 가능한 게임.

- 4 가지 예시 레벨이 포함되어 있습니다. 그들은 다양한 난이도를 제공한다.

- 각 레벨은 자체 컬렉션에 내장되어 있습니다. 타일 에디터를 사용하여 여러 레벨을 빌드 할 수 있도록 asset이 포함되어 있습니다.

 

아래와 같은 기능을 구현한다.

- 플레이어가 4 가지 레벨 중 하나를 시작할 수있는 레벨 선택 화면을 추가했습니다.
- 플레이어가 다음 레벨로 계속할 수 있도록 레벨 완료 메시지를 추가했습니다.
- 시작 화면을 추가했습니다.
- 사용자가이 화면을 탐색 할 수 있도록 버튼을 추가했습니다.

게임 설정의 이해

튜토리얼을 시작하기 전에 게임을 실행 한 다음 "main.collection"을 열어 게임 설정 방법을 확인하십시오.

 

전체 게임은 "main.collection"의 "level"이라는 하위 컬렉션에 포함되어 있습니다. 

현재 "level"은 "/main/level_2/level_2.collection"파일을 참조합니다. "level" 컬렉션 파일을 열면 두 가지 게임 개체가 나타납니다.

 

ID가 "board"인 하나의 게임 객체. 이것은 "tiles"id를 갖는 타일맵을 포함하고 있다. 타일맵에는 두 개의 레이어가 있는데, 하나는 실제 플레이 필드(레이어 ID가 "board"인 경우)이고, 다른 하나는 벽돌에 대한 초기 설정(레이어 ID가 "setup"된 경우)을 포함한다. 게임이 시작되면 "setup" 레이어를 보고 자유롭게 애니메이션이 가능한 별도의 게임 객체로 벽돌 타일을 대체한다. 그런 다음 layer를 지운다.

ID가 "level"인 하나의 게임 객체. 여기에는 게임 논리 스크립트("level.script")와 게임 시작 시 벽돌을 생성하는 데 사용되는 factory가 들어 있다. 이 게임 오브젝트는 "/main/level.go"라는 별도의 파일에 저장되므로 이 Blueprint 파일의 게임 오브젝트는 각각의 개별 레벨 컬렉션에서 인스턴스화할 수 있다.

 

"main.collection"을 여십시오. "level" 게임 객체의 Path 속성을 "/main/level_3/level_3.collection"으로 변경 하고 게임을 다시 빌드 하고 실행해보자.

Proxy를 통해 Collection 불러오기

이 게임에 필요한 것은 레벨 로딩을 자동으로하고 플레이어 선택에 변화되는 level입니다. Defold에는 컬렉션을 동적으로 로드하는 두가지 메커니즘이 있습니다.

 

1. Collection factories : 적 유닛, 효과 또는 대화형 개체와 같이 실행중인 게임 개체의 계층 구조를 생성하는 데 적합합니다. 스폰된 오브젝트는 시작 메인 컬렉션 월드의 일부이며 명시적으로 오브젝트를 삭제하지 않는 한 게임이 종료 될 때까지 유지됩니다.

 

2. Collection proxy :  게임 레벨과 같이 더 큰 게임 덩어리를 동적으로 로드하려는 경우이 방법을 사용하는 것이 좋습니다. 프록시를 사용하면 컬렉션을 기반으로 새로운 "world"를 만듭니다. 컬렉션 컨텐츠에서 스폰된 오브젝트는 생성 된 월드의 일부이며 컬렉션이 프록시에서 언로드되면 자동으로 소멸됩니다. 생성 된 새로운 세계에는 오버 헤드 비용이 부과되므로 프록시는 대량의 소량 컬렉션을 동시에 생성하는 데 적합하지 않습니다

 

여기서는 proxy를 사용하는것이 좋습니다.

 

"main.collection"을 열고 "level" 콜렉션 참조를 제거하십시오. 대신 새 게임 오브젝트를 추가하고 ID를 "loader"로 지정하십시오.

 

Collection proxy 컴포넌트를 게임 개체에 추가하고 이름을 "proxy_level_1"로 지정하고 Collection 속성을 "/main/level_1/level_1.collection"으로 설정하십시오.

 

"loader.script" 라는 새 스크립트 파일을 추가하고 "loader"게임 오브젝트에 스크립트 컴포넌트로 추가하십시오.

 

"loader.script"를 열고 내용을 아래와 같이 작성합니다.

 

function init(self)
	msg.post("#proxy_level_1", "load") -- (1)
end

function on_message(self, message_id, message, sender)
	if message_id == hash("proxy_loaded") then -- (2)
		msg.post(sender, "init")
		msg.post(sender, "enable")
	end
end

(1) collection을 로드하라는 메세지를 #proxy_level_1"아이디를 가진 collection proxy 컴포넌트로 보낸다.

(2) proxy component 로딩이 완료되면 "proxy_loaded" 메시지가 다시 전송이 됩니다. 그런 다음 "init"및 "enable"메시지를 collection으로 보내 collection 내용을 초기화하고 활성화 할 수 있습니다. 이 메시지를 proxy component 인 "sender"에게 다시 보낼 수 있습니다.

 

이제 게임을 실행 해보면 불행이도 오류가 발생하고 있습니다.

ERROR:GAMEOBJECT: The collection 'default' could not be created since there is already a socket with the same name.

 

이 오류는 proxy가 "default"라는 이름으로 새로운 세계(소켓)를 작성하려고 하기 때문에 발생합니다. 그러나 엔진 부팅시 "main.collection"에서 생성된 이름의 세계가 이미 존재합니다. 소켓 이름은 collection 루트의 속성에 설정되어 있으므로 쉽게 수정할 수 있습니다.

 

"/main/level_1/level_1.collection"파일을 열고 컬렉션의 루트를 표시 한 다음 Name 속성을 "level_1"로 설정하십시오. 그리고 level.go를 표시하고 Id 속성을 "level_1"로 설정하십시오. 파일을 저장하십시오.

 

게임을 실행 시켜보자.

 

이제 레벨이 표시되지만 보드를 클릭하여 타일을 이동하려고 하면 아무 일도 일어나지 않습니다. 왜 그런 겁니까? 문제는 입력을 다루는 스크립트가 이제 프록시 세계에 있다는 것입니다. 입력 시스템은 다음과 같이 작동합니다.

 

- 입력 포커스를 얻은 부트 스트랩 컬렉션의 모든 게임 객체에 입력을 보냅니다.
- 입력을 수신하는 이러한 객체 중 하나에 프록시가 포함 된 경우, 입력은 입력 포커스를 얻은 프록시 뒤에 있는 게임 세계의 모든 객체로 전달됩니다.

 

따라서 프록시 컬렉션에 입력을 받으려면 프록시 컴포넌트가 포함된 게임 오브젝트가 입력을 청취해야합니다.

 

"loader.script"를 열고 init() 함수에 아래 코드를 작성 하시오.

function init(self)
	msg.post("#proxy_level_1", "load")
	msg.post(".", "acquire_input_focus") -- (1)
end

이 게임 오브젝트는 입력이 필요한 컬렉션을 위한 프록시를 가지고 있기 때문에, 이 게임 오브젝트도 입력 포커스를 획득할 필요가 있다.

 

게임을 실행해보면 이상없이 동작을 할 것이다.

 

게임에는 4가지 레벨이 포함되어 있기 때문에 나머지 3가지 레벨에 대한 프록시 컴포넌트를 추가해야 한다. 프록시가 로드될 때 소켓 이름이 충돌하지 않도록 각 레벨 컬렉션에 대해 Id 속성을 고유한 이름으로 변경하는 것을 잊지 마십시오.

 

script에서 "load" 메시지를 지금 추가한 프록시 컴포넌트들로 변경하여 각 레벨이 로드되는지 테스트 하십시오.

msg.post("#proxy_level_1", "load")
msg.post("#proxy_level_2", "load")
msg.post("#proxy_level_3", "load")
msg.post("#proxy_level_4", "load")

레벨 선택 화면

이제 언제든지 로딩하는데 필요한 설정을 구축했으므로 레벨 로딩에 대한 인터페이스를 구성해야 할 때 입니다.
새 GUI 파일을 만들어 "level_select.gui"라고 작성하자.

 

GUI의 font 섹션에 "heading"이라는 font을 추가하십시오. (Outline에서 font 항목을 마우스 오른쪽 버튼으로 누르고 Add > font... 선택).

GUI의 Textures 섹션에 "bricks" 아틀라스를 추가하십시오(Outline에서 Textures 항목을 마우스 오른쪽 버튼으로 누르고 Add ▸ Texture... 선택).

각 레벨당 하나씩, 4개의 버튼으로 인터페이스를 구성한다 :

 

1. Root 박스 노드 하나를 생성하십시오(Nodes에 마우스 오른쪽 버튼으로 누르고 add > box 추가 선택).

2. Id는 "level_1"로 설정하세요

3. Size Mode를 Manual로, 크기를 100, 100, 0으로 설정하십시오.

4. 노드가 보이지 않도록 Alpha를 0으로 설정하십시오.
5. "level_1"에 하위노드를 생성하십시오("level_1"을 마우스 오른쪽 버튼으로 누르고 add ▸ Box를 선택하십시오)
6. 하위 노드의 Id를 "1_bg"로 설정하십시오.
7. 노드의 텍스처를 bricks/button으로 설정하십시오.
8. 상위 항목이 투명하더라도 렌더링되도록 노드에서 Inherit Alpha을 선택 취소하십시오.
9. "level_1"에 하위 텍스트 노드를 생성하십시오 ("level_1"을 마우스 오른쪽 버튼으로 누르고 add> text 선택).
10. 하위 노드의 Id를 "1_text"로 설정하십시오.
11. 노드의 텍스트를 "1"로 설정하십시오.
12. 노드의 글꼴을 "heading" 으로 설정하십시오.
13. 상위 항목이 투명하더라도 렌더링되도록 노드에서 Alpha 상속을 선택 취소하십시오

 

그래픽 크기를 변경할 경우 루트 노드가 입력 테스트에 사용되므로 각 루트 노드가 전체 버튼 그래픽을 포함할 수 있을 만큼 충분히 큰지 확인하십시오.

 

4개의 레벨 버튼 모두 위의 단계를 반복하고 각 루트 노드를 아래와 같이 이동하십시오. 그리고 텍스트 노드 헤더 추가 합니다.

 

1. 텍스트 노드 하나를 생성하십시오(노드 오른쪽 클릭 후 add ▸ text 선택).
2. ID를 "select_header"로 설정하십시오.
3. 글꼴을 "heading"으로 설정하십시오.
4. 텍스트를 "SELECT LEVEL"로 설정하십시오.

 

"level_select.gui_script"이름을 가진 새로운 gui script 파일을 만들고 아래와 같이 해당 코드를 작성한다.

function init(self)
	msg.post(".", "acquire_input_focus")
	msg.post("#", "show_level_select") -- (1)
end

function final(self)
	-- Add finalization code here
	-- Remove this function if not needed
end

function update(self, dt)
	-- Add update code here
	-- Remove this function if not needed
end

function on_message(self, message_id, message, sender)
	if message_id == hash("show_level_select") then -- (2)
		msg.post("#", "enable")
		self.active = true
	elseif message_id == hash("hide_level_select") then -- (3)
		msg.post("#", "disable")
		self.active = false
	end

	
end

function on_input(self, action_id, action)
	-- 터치하고 눌럿으며 level select가 보일경우
	if action_id == hash("touch") and action.pressed and self.active then
		for n = 1,4 do -- (4)
			-- 1~4까지 반복하며 level_1 ~ 4의 노드를
			local node = gui.get_node("level_" .. n)
			if gui.pick_node(node, action.x, action.y) then -- (5)
				msg.post("/loader#loader", "load_level", {level = n}) -- (6)
				msg.post("#", "hide_level_select") -- (7)
			end
		end
	end
end

function on_reload(self)
	-- Add input-handling code here
	-- Remove this function if not needed
end

(1) GUI를 설정하십시오.

(2) GUI show 및 hide 작업은 다른 스크립트에서 수행할 수 있도록 메시지를 통해 트리거된다.

(3) message_id에 따라서 gui의 show/hide 수행을 분기 한다

(4) 버튼 노드의 이름은 "level_1"에서 "level_4"로 지정되어 루프가 가능하다.

(5) 터치 동작이 노드 "level_n"의 경계 내에서 발생하는지 확인하십시오. 이것은 버튼에서 클릭이 발생한다는 것을 의미한다.

(6) loader 스크립트로 메시지를 전송하여 level_n을 로드하십시오. 이 스크립트는 "proxy_loaded"에 대한 대응으로 나머지 프록시 로직을 처리하지 않으므로 여기서 "load" 메시지가 직접 프록시로 전송되지 않는다는 점에 유의하십시오.

(7) 이 GUI를 숨기십시오.

 

"level_select.gui"를 열고 루트 노드 script 속성을 위의 스크립트로 변경한다

 

 

이 단계를 마치려면 로더 스크립트가 "load_level" 메시지에 반응하기 위해 약간의 새로운 코드가 필요하며, init에 대한 프록시 로딩은 제거되어야 한다.

loader.script를 열고 init() 및 on_message() 함수를 변경하십시오.

function init(self)
	msg.post(".", "acquire_input_focus")
end

function final(self)
	-- Add finalization code here
	-- Remove this function if not needed
end

function update(self, dt)
	-- Add update code here
	-- Remove this function if not needed
end

function on_message(self, message_id, message, sender)
	if message_id == hash("load_level") then
		local proxy = "#proxy_level_" .. message.level -- (1)
		msg.post(proxy, "load")
	elseif message_id == hash("proxy_loaded") then
		msg.post(sender, "init")
		msg.post(sender, "enable")
	end
end

function on_input(self, action_id, action)
	-- Add input-handling code here
	-- Remove this function if not needed
end

function on_reload(self)
	-- Add reload-handling code here
	-- Remove this function if not needed
end

메시지 데이터를 기반으로 로드할 프록시를 구성하십시오.

 

"main.collection"을 열고 ID "guis"가 있는 새 게임 객체를 추가하십시오.

새로운 "guis" 게임 객체에 GUI 구성요소로 "level_select.gui"를 추가한다.

게임을 실행하고 레벨 셀렉터 화면을 테스트하십시오. 레벨 버튼을 클릭할 수 있어야 하며 해당 레벨이 로드되어 재생 가능해야 한다.

In game GUI

이제 시작도 하고 레벨 플레이도 할 수 있지만 돌아갈 길은 없다. 다음 단계는 레벨 선택 화면으로 다시 이동할 수 있는 게임 내 GUI를 추가하는 것이다. 또한 레벨이 완료되면 플레이어를 축하하고 다음 레벨로 바로 이동할 수 있어야 한다.

 

새 GUI 파일을 만들어 "level.gui"라고 부른다.

font 섹션에 "headings"을 추가하고 GUI의 Textures 섹션에 "bricks" 아틀라스를 추가하십시오.

상단에는 back 버튼 1개, 상단에는 레벨 번호 표시기 1개를 구축한다.

"well done" 메시지와 "next" 버튼을 사용하여 수준 높은 완료 메시지를 작성하십시오. 이러한 항목을 패널(색상자 노드)에 고정하고 "done"라고 부르며 레벨이 완료될 때 뷰로 슬라이딩할 수 있도록 뷰 외부에 배치하십시오.

 

새로운 GUI 스크립트 파일을 작성하고 "level.gui_script"라고 만듭니다

function init(self)
	-- Add initialization code here
	-- Remove this function if not needed
end

function final(self)
	-- Add finalization code here
	-- Remove this function if not needed
end

function update(self, dt)
	-- Add update code here
	-- Remove this function if not needed
end

function on_message(self, message_id, message, sender)
	if message_id == hash("level_completed") then -- (1)
		local done = gui.get_node("done")
		gui.animate(done, "position.x", 320, gui.EASING_OUTSINE, 1, 1.5)
end

function on_input(self, action_id, action) -- (2)
	if action_id == hash("touch") and action.pressed then
		local back = gui.get_node("back")
		if gui.pick_node(back, action.x, action.y) then -- back 버튼에 이벤트 발생시
			msg.post("default:/guis#level_select", "show_level_select") -- (3)
			msg.post("default:/loader#loader", "unload_level")
		end

		local next = gui.get_node("next")
		if gui.pick_node(next, action.x, action.y) then
			msg.post("default:/loader#loader", "next_level") -- (4)
		end
	end
	
end

function on_reload(self)
	-- Add input-handling code here
	-- Remove this function if not needed
end

(1) "level_complete" 메시지가 수신되면 "next" 단추가 있는 "done" 패널을 표시 합니다 (애니메이션 효과)

(2) 이 GUI는 "level.script"를 통해 이미 입력 포커스를 획득한 "level" 게임 객체에 배치되므로 포커스 스크립트를 넣을 필요가 없다.
(3) 플레이어가 "back" 를 누르면 레벨 선택기가 표시되고 로더가 level을 언로드하도록 메세지를 보낸다. 부트스트랩 컬렉션의 소켓 이름이 주소에서 사용된다는 점에 유의하십시오.
(4) 플레이어가 "next"를 누를 경우 로더에게 다음 레벨을 로드하도록 지시하십시오.

 

"loader.script"를 열고 아래와 같이 작성 하십시요

function init(self)
	msg.post(".", "acquire_input_focus")
	self.current_level = 1	-- (1)
end

function final(self)
	-- Add finalization code here
	-- Remove this function if not needed
end

function update(self, dt)
	-- Add update code here
	-- Remove this function if not needed
end

function on_message(self, message_id, message, sender)
	if message_id == hash("load_level") then
     	self.current_level = message.level
		local proxy = "#proxy_level_" .. message.level
		msg.post(proxy, "load")
	elseif message_id == hash("next_level") then -- (2)
		msg.post("#", "unload_level")
		msg.post("#", "load_level", {level = self.current_level + 1})
	elseif message_id == hash("unload_level") then -- (3)
		local proxy = "#proxy_level_" .. self.current_level
		msg.post(proxy, "disable")
		msg.post(proxy, "final")
		msg.post(proxy, "unload")
	elseif message_id == hash("proxy_loaded") then
		msg.post(sender, "init")
		msg.post(sender, "enable")
	end
end

function on_input(self, action_id, action)
	-- Add input-handling code here
	-- Remove this function if not needed
end

function on_reload(self)
	-- Add reload-handling code here
	-- Remove this function if not needed
end

(1) 언로드시 사용하기 위해 현재 로드된 레벨을 추적하고 다음 레벨로 진행 할 수 있도록 self에 속성을 추가한다.

(2) 다음 레벨을 로드 한다. 실제로 다음 레벨이 존재하는지 확인은 하고 있지 않다

(3) 현재 로드된 레벨을 언로드 한다.

 

on_input()의 마지막에 게임이 완료되면 "level.script"를 열고 level gui 메시지를 추가하십시오.

if all_correct(self.bricks) then
	msg.post("#gui", "level_completed")
	self.completed = true
end

마지막으로 "level.go"를 열고 "level.gui"를 게임 객체에 GUI 구성요소로 추가한다. 구성 요소의 ID 속성을 "gui"로 설정하십시오.

게임을 실행해보자

시작 화면

마지막으로 시작화면을 만들어보자.

 

새로운 gui를 만들어보자. id는 "start.gui"로 하자

 

"start.gui_script"를 만들고 아래와 같이 코드를 작성하자.

function init(self)
	msg.post("#", "show_start")
	self.active = false
end

function final(self)
	
end

function update(self, dt)
	
end

function on_message(self, message_id, message, sender)
	if message_id == hash("show_start") then
		msg.post("#", "enable")
		self.active = true
	elseif message_id == hash("hide_start") then
		msg.post("#", "disable")
		self.active = false
	end
end

function on_input(self, action_id, action)
	if action_id == hash("touch") and action.pressed and self.active then
		local start = gui.get_node("start")
		if gui.pick_node(start, action.x, action.y) then
			msg.post("#", "hide_start")
			msg.post("#level_select", "show_level_select")
		end
end

function on_reload(self)
	
end

"start.gui"를 열고 위 스크립트를 script속성에 설정 하세요.

 

main.collection을 열고 start.gui를 gui 구성요소로 guis 게임 객체에 추가한다.

 

이제 level_select.gui를 열고 back 버튼을 추가 하자. level.gui에서 만든 것을 복사 해도 된다.

 

level_select.gui_script를 열고 아래와 같이 on_input() 함수안에 작성하자.

function on_input(self, action_id, action)
	-- 터치하고 눌럿으며 level select가 보일경우
	if action_id == hash("touch") and action.pressed and self.active then
		for n = 1,4 do -- (4)
			-- 1~4까지 반복하며 level_1 ~ 4의 노드를
			local node = gui.get_node("level_" .. n)
			if gui.pick_node(node, action.x, action.y) then -- (5)
				msg.post("/loader#loader", "load_level", {level = n}) -- (6)
				msg.post("#", "hide_level_select") -- (7)
			end
		end

		local back = gui.get_node("back")
		if gui.pick_node(back, action.x, action.y) then
			msg.post("#level_select", "hide_level_select")
			msg.post("#start", "show_start")
		end
	end
end

또한 시작시 level select gui가 표시 안되도록 init()에서 아래와 같이 코드를 작성합니다.

function init(self)
	msg.post(".", "acquire_input_focus")
	msg.post("#", "hide_level_select")
	self.active = false
end

자 실행해보시고 이상없이 동작하는지 확인합니다.

 

현재 게임 하는 곳에서 우상단 Text가 올바르지 않다. loader.script를 열고 아래 코드를 추가하자

function on_message(self, message_id, message, sender)
	if message_id == hash("load_level") then
		self.current_level = message.level
		local proxy = "#proxy_level_" .. message.level
		msg.post(proxy, "load")
	elseif message_id == hash("next_level") then
		msg.post("#", "unload_level")
		msg.post("#", "load_level", {level = self.current_level + 1})
	elseif message_id == hash("unload_level") then
		local proxy = "#proxy_level_" .. self.current_level
		msg.post(proxy, "disable")
		msg.post(proxy, "final")
		msg.post(proxy, "unload")
	elseif message_id == hash("proxy_loaded") then
        -- 추가한 코드
		msg.post("level_"..self.current_level..":/level#gui", "set_level_text", { level = self.current_level })
		msg.post(sender, "init")
		msg.post(sender, "enable")
	end
end

 

'DEFOLD > 튜토리얼' 카테고리의 다른 글

4. War battles tutorial  (0) 2020.06.22
2. Movement tutorial  (0) 2020.06.18
1. Walking astronaut tutorial  (0) 2020.06.18