Corona SDK로 게임을 만들 때 어떻게 Level별 Lock 기능을 걸고 푸는지에 대해 샘플을 보면서 공부 해 보겠습니다.
오늘 소스는 개발자 peach pellen 이 만들고 공유한 소스코드입니다.
http://peachpellen.com/ 로 가면 많은 정보들도 있구요. 우리의 peach가 열심히 만들어서 공유하고 있는 다른 정보들도 접할 수 있습니다.
가서 댓글로 고마움을 표시하는 것도 좋은 일 인것 같습니다.
원본 파일은 아래에 올리겠습니다.
우선 main.lua를 보겠습니다. (지면을 줄이기 위해서 필요없는 주석은 지울께요.)
display.setStatusBar (display.HiddenStatusBar)
local director = require ("director")
local mainGroup = display.newGroup()
local function main()
mainGroup:insert(director.directorView)
director:changeScene("menu")
return true
end
main()
첫째줄은 늘 그래왔듯이 iPhone의 status bar를 없애는 라인이구요.
두번째 라인은 director.lua 클래스를 require한 라인입니다.
이 director클래스는 일반 개발자가 만든 화면 전환용 클래스죠?
얼마전 코로나에서 화면전환용 api인 storyboard api를 제공하기 시작했으니 그 api를 사용해도 될 겁니다.
이 소스는 그 api가 나오기 이전에 만들어진 것이니 그대로 director 클래스를 사용하겠습니다.
director 클래스에 대해서는 이전에 살펴 본 적이 있으니 따로 설명하지는 않겠습니다.
세번째 줄은 mainGroup이라는 그룹을 만든것이고 그 다음줄에 main() 함수를 만들었습니다.
이 함수 안에서 mainGroup에 directorView를 insert합니다.
그 다음에 changeScene으로 menu.lua 파일로 스크린을 옮깁니다.
화면전환 효과는 아무것도 사용하지 않았네요.
마지막 줄은 이 main함수를 call 해 주는 겁니다.
이젠 그럼 menu.lua를 볼까요?
module(..., package.seeall)
-- BELOW is Director code
function new()
local localGroup = display.newGroup()
-- ABOVE is Director code
require "saveit"
-------------------------------------------------------------------------
-------------------------------------------------------------------------
-------------------------------------------------------------------------
local function resumeStart()
local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory )
local file = io.open( path, "r" )
if file then
print("Loading our data...")
local contents = file:read( "*a" )
-- Loads our data
local prevState = explode(", ", contents)
_G.onelock = prevState[1]
_G.twolock = prevState[2]
io.close( file )
else
_G.onelock = 1
_G.twolock = 0
end
end
-- Separates the variables into a table
local function onSystemEvent( event )
if( event.type == "applicationExit" ) then
local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory )
local file = io.open( path, "w+b" )
-- Creates the file where we save our data
file:write( _G.onelock ..", ".. _G.twolock)
io.close( file )
end
end
-- Saves our data
--BELOW THIS LINE IS ANSCA'S SAMPLE CODE (I don't mess with perfection)
-- explode helper function zomgdata
function explode(div,str)
if (div=='') then return false end
local pos,arr = 0,{}
-- for each divider found
for st,sp in function() return string.find(str,div,pos,true) end do
table.insert(arr,string.sub(str,pos,st-1)) -- Attach chars left of current divider
pos = sp + 1 -- Jump past current divider
end
table.insert(arr,string.sub(str,pos)) -- Attach chars right of last divider
return arr
end
local function init()
-- start and resume from previous state, if any
resumeStart()
Runtime:addEventListener( "system", onSystemEvent )
end
--start the program
init()
--ABOVE THIS LINE IS ANSCA'S SAMPLE CODE (You don't need to change it, so don't!)
----------------------------------------------------------------------
-- SCENE SETUP --
----------------------------------------------------------------------
local background = display.newImage ("background.png")
localGroup:insert(background)
-- Sets the background
local leveloneicon = display.newImage ("level1icon.png")
leveloneicon.x = 50
leveloneicon.y = 40
localGroup:insert(leveloneicon)
-- Sets the icon for level one
local function gotoone (event)
director:changeScene("level1")
end
leveloneicon:addEventListener("tap", gotoone)
-- Go to level one when icon is tapped
local function gototwo (event)
director:changeScene("level2")
end
-- Function to go to level two
local function seticontwo (event)
if _G.twolock-0 == 0 then
local leveltwoicon = display.newImage ("level2locked.png")
leveltwoicon.x = 130
leveltwoicon.y = 40
localGroup:insert(leveltwoicon)
elseif _G.twolock-0 == 1 then
local leveltwoicon = display.newImage ("level2icon.png")
leveltwoicon.x = 130
leveltwoicon.y = 40
localGroup:insert(leveltwoicon)
leveltwoicon:addEventListener("tap", gototwo)
end
end
seticontwo()
----------------------------------------------------------------------
----------------------------------------------------------------------
-- BELOW is Director code
-- MUST return a display.newGroup()
return localGroup
end
-- ABOVE is Director code
모듈로 사용되기 위해 필요한 부분이 맨 처음 줄이구요.
그 다음은 Director클래스를 사용하기 위해 new()함수 안에 모든 코드를넣고 localGroup이라는 그룹을 만든 겁니다.
다음엔 saveit.lua파일을 require했습니다.
짧으니까 잠깐 saveit.lua 파일을 볼께요.
module(..., package.seeall)function save( event ) local path = system.pathForFile( "ourdata.txt", system.DocumentsDirectory ) local file = io.open( path, "w+b" ) -- Creates the file where we save our data if needed file:write( _G.onelock ..", ".. _G.twolock) io.close( file ) -- Saves our dataend
첫줄은 마찬가지로 모듈로 사용되기 위해 필요한 라인이구요. 그 다음에 save(event) 함수를 구현했습니다.
event가 인수로 있는 것으로 봐서 어떤 리스너에서 불러오지 않나 생각되네요.
함수 내용은 ourdata.txt를 쓰기 가능으로 열고 그 파일 안에 _Gonelock,_Gtwolock을 씁니다. 그리고 저장하면서 닫습니다.
이 기능이 menu.lua 어느곳에선가 불려져서 사용 될 겁니다.
다시 menu.lua로 넘어올께요.
첫번째로 resumeStart()함수를 보겠습니다.
이 함수에서는 ourdata.txt라는 파일을 엽니다.
만약 이 파일이 있으면 "Loading our data..." 라는 문장을 터미널에 print합니다.
그리고 파일 안의 내용을 읽어오구요. explode 함수를 사용해서 쉼표 , 를 기준으로 파일 안의 내용을 나눕니다.
_G.onelock 에 첫번째, _G.twolock 에 두번째 내용을 저장합니다.
그리고 파일을 닫습니다. (이 때 저장됩니다.)
만약 파일이 없다면 (이 앱이 처음으로 실행 되는 경우가 됩니다.)
_G.onelock 에 1을 _G.twolock에 0을 넣습니다.
두번째로 onSystemEvent(event)함수를 알아볼까요?
이 함수는 event를 인수로 받기 때문에 어떤 리스너에서 Call하는 걸 겁니다.
밑에 보니까 앱이 최초로 실행 될 때인 Runtime 리스너에서 call하네요.
Runtime:addEventListener( "system", onSystemEvent )
이 함수 내용을 보면 event.type 이 applicationExit일 경우 그러니까 앱이 종료 될 때 if 문 안에 있는 내용들이 실행 됩니다.
그 내용은 ourdata.txt를 쓰기 가능으로 열고 그 안에 _G.onelock,_G.twolock을 저장하고 파일을 닫습니다.
이 내용은 saveit.lua의 내용과 똑 같네요. 이렇게 중복된 코딩은 그렇제 좋은 방법은 아니죠. 저 같으면 이 내용도 saveit.lua 파일 안에 있는 함수를 call 해서 사용할 것 같습니다.
어쨌든 이 소스코드 분석을 하자면 그렇고.. 압축파일을 열어보시면 그 아래 peach가 직접 주석을 달아서 설명한 부분이 있으니까 한번 읽어 보세요.
그 다음엔 explode(div,str) 함수가 있습니다.
그 내용은 div 가 아무 내용이 없으면 false를 return 하구요.
아니면 pos와 arr을 0과 {} (테이블)로 선언합니다.
그리고 for문을 돌려 arr 테이블에 나누는 기준에 따라 string을 나눠서 저장합니다.
마지막엔 이 arr 테이블을 return합니다.
다음 함수는 init()함수인데요. resumeStart()함수와 런타임 리스너로 onSystemEvent를 call합니다.
그 밑에선 init()함수를 call 하고 있네요.
여기까지만 보면 최초로 init()이 불려지고 그 다음에 resumeStart() 그리고 onSystemEvent 함수가 불려지겠네요.
그 다음은 화면 셋업 부분입니다.
백그라운드 이미지를 표시하고 level1icon 이미지를 표시합니다. 그리고 두 이미지 모두 localGroup에 insert합니다.
그 다음은 gotoone(event)함수인데요. 이건 바로 아래 leveloneicon의 tap리스너에서 call 됩니다. 그 내용은 level1.lua화면으로 넘어 가는겁니다.
다음은 gototwo(event)함수로 내용은 level2.lua 화면으로 넘어가는 건데요. 이 함수는 seticontwo(event) 함수 내에서 call 됩니다.
즉 gotoone 함수는 무조건 실행되고 gototwo함수는 seticontwo 함수에서 일정 조건에 부합되면 실행이 되겠네요.
그러면 seticontwo(event)함수를 살펴보겠습니다.
만약에 _G.twolock-0이 0이면 leveltwoicon이 level2locked.png로 설정이 되구요.
이 변수를 그룹에 insert합니다.
여기서 참고로 왜 -0을 했냐 하면요. 혹시 그 이전에 _G.twolock 변수가 string으로 처리 됐을 경우 == 0 같은 숫자 비교하는 부분이 에러가 날 수 있습니다.
그래서-0 을 해 주면 자동으로 int형으로 바꿔주기 때문에 따로 -0을 하지 않았나 생각되네요.
저도 그런 경험이 있거든요.
그리고 만약에 _G.twolock-0이 1이면 leveltwoicon은 level2icon.png가 되고 이 이미지를 tap할 경우 gototwo 함수가 실행 됩니다.
다음줄은 이 seticontwo()함수를 호출 한 겁니다.
그 다음줄은 director 클래스를 사용하려면 반드시 있어야 하는 부분인데요. localGroup을 return하는 겁니다.
이제 주요 내용은 다 본 겁니다.
level1.lua를 한번 볼까요?
module(..., package.seeall)
function new()
local localGroup = display.newGroup()
----------------------------------------------------------------------
-- SCENE SETUP --
----------------------------------------------------------------------
local background = display.newImage ("background.png")
localGroup:insert(background)
-- Sets the background
local unlockbutton = display.newImage ("unlock2.png")
unlockbutton.x = 160
unlockbutton.y = 140
localGroup:insert(unlockbutton)
-- Image of unlock button
local lockbutton = display.newImage ("lock2.png")
lockbutton.x = 160
lockbutton.y = 240
localGroup:insert(lockbutton)
-- Image of lock button
----------------------------------------------------------------------
-- FUNCTIONALITY --
----------------------------------------------------------------------
local function onewin (event)
print "Level 2 is now unlocked! :)"
_G.twolock = 1
saveit.save()
director:changeScene("menu")
end
unlockbutton:addEventListener("tap", onewin)
local function onefail (event)
print "Level 2 is now locked! :("
_G.twolock = 0
saveit.save()
director:changeScene("menu")
end
lockbutton:addEventListener("tap", onefail)
----------------------------------------------------------------------
----------------------------------------------------------------------
-- Below is the standard stuff, commented in MENU.LUA
-- MUST return a display.newGroup()
return localGroup
end
마찬가지로 첫줄은 모듈로 쓰이기 위해서 필요한 라인이구요. 그 다음 두 줄은 dorector.lua클래스를 사용할 때 반드시 있어야 되는 부분이구요.
다음은 화면 셋업하는 부분입니다.
백그라운드, unlockbutton, lockbutton 세개 이미지를 셋업합니다.
그 다음은 함수들인데요.
onewin(event)함수는 unlockbutton을 tap했을 때 불려지는 함수입니다.
그 내용은 터미널에 "Level 2 is now unlocked! :)" 를 프린트 해 주고 saveit.lua파일에 있는 save()함수를 실행시킵니다. 그리고 menu.lua로 돌아가구요.
두번째 함수는 onefail(event)인데요. 이건 lockbutton 이미지를 tap하면 call되는 함수입니다.
내용은 "Level 2 is now locked! :(" 를 터미널에 프린트 해 주고 _G.twolock을 0으로 해 줍니다. 그 다음 save.save() 함수를 call하고 menu.lua 내용을 화면에 뿌려 줍니다.
다음은 마찬가지로 director.lua 클래스를 사용할 때 있어야 하는 규칙으로 localGroup을 return하구요.
마지막으로 level2.lua를 보시죠.
module(..., package.seeall)
-- Main function - MUST return a display.newGroup()
function new()
local localGroup = display.newGroup()
----------------------------------------------------------------------
-- SCENE SETUP --
----------------------------------------------------------------------
local background = display.newImage ("background2.png")
localGroup:insert(background)
return localGroup
end
여기는 특별한 부분 없이 그냥 background 이미지를 출력하는 부분만 있습니다.
이제 게임 중 Level별로 Lock하고 Lock을 해제하고 하는 부분들을 이해하시겠죠?
Lock이 풀리면 맨 마지막에 우리의 Peach 사진을 볼 수가 있네요.
전 얘가 남자인지 여자인지 확실히 모르겠어요. 20대 초반에 시드니에 살고 있다고 하던데..
http://peachpellen.com/about/
하여간 얘가 운영하는 홈페이지가 아래에 있습니다.
http://techority.com/
저하고는 관계가 없지만 얘가 share한 소스코드로 제가 공부를 많이 했고 또 여러분에게도 소개해 드리고 해서 이렇게 소개 드립니다.
이 세상을 경쟁으로 생각하기 보다는 이렇게 서로서로 돕자는 마음으로 하면 더 좋을 것 같아서.. 홈페이지 들어가주고 내용 봐주고 필요하면 이용해 주고 힘 주는 댓글 남겨주고 하는게 우리들이 도움 받은거 은혜 갚는거 같애서... ^^
하여간 여러분들도 즐팅 하세요....