블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 받은 트랙백

글 보관함

calendar

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        


지난 번 글을 쓴지 3주나 지났군요. 자기가 쓴 코드도 며칠만 지나면 뭐가 뭔지 너무 오래 되서 이전에 어디까지 공부했는지도 잘 모르겠습니다.


지난번 글 대충 읽어 봤더니 chainFunctions() 함수를 분석하면 될 것 같습니다.


이 함수에 전달되는 파라미터를 보겠습니다.


function chainFunctions(exec,options,runParent,noanim)


exec,options,runParent,noanim 이렇게 4개의 파라미터들이 전달 됩니다.


이 함수를 call 하는 anim 함수에서 이 함수가 call 되는 부분을 보죠.


return chainFunctions(function () return transition.to(obj,options) end, options,runParent)


첫번째 exec 에 해당 되는 부분은 function () return transition.to(obj,options) end 라는 함수 입니다. 여기서 obj,option 은 지난 번 글에 서 다뤘죠.




obj는 흰공의 위치이고 options는 빨간점의 위치값입니다.

그러니까 chainFunction 의 첫번째 인자는 흰공이 options 에 설정된 위치까지 움직이는 겁니다.

두번째 파라미터는 뭘까요?  options입니다. 이 options는 빨간공의 위치이죠? 흰공이 빨간공으로 움직이면 그 다음은 그 빨간공 자리에 흰공이 있게 되니까 그 위치가 흰공의 위치값이 될 겁니다. 아마 그걸 다루려고 이 options 값을 전달하는 것 같습니다.

코드를 분석 해 보면 나오겠죠.

세번째 값인 runParent 는 뭘까요? anim() 함수에서도 runParent를 던져주긴 하는데 이제 main 함수에서 call 됐을 때는 전달받지 못한 겁니다.


그럼 뭘까요?



anim() 함수가 main 함수에서만 call 되는 건 아닙니다. chainFunction() 함수 안에서도 이 anim() 함수가 호출되는데요.


return anim(arg[1],arg[2],run) 라는 부분이 있습니다.


여기에 세번째 파라미터가 있죠?

이건 chinFunction() 내부를 분석하면 뭔지 알 수 있을 겁니다.


그리고 chainFunction() 에는 네번째 파라미터가 noanim 이라고 있는데 anim() 함수에서 호출 될 때는 이 값이 없습니다.

자세히 보니까 chainFunction() 함수 내에서 자기 자신인 chainFunction()을 호출하는 부분이 있습니다.


return chainFunctions(arg[1],{},run,true)


이 4번째 파라미터는 true 이군요.

이것도 chainFunctions() 함수 내부 로직을 분석하면 어떻게 사용되는지 알 수 있을 것 같습니다.


들어가기 전에 이 소스를 만든 개발자가 적어 놓은 주석부터 한번 보겠습니다.


각 파라미터 (parameters, arguments) 들에 대한 설명을 달아 줬네요.


-- args
-- exec - is the current function to execute. It is assumed to be a function that runs a Corona SDK transition

exec는 현재 실행되는 함수이다. 이 값은 Corona SDK transition이 실행되는 함수가 전해질 것이다.


-- options - options for the animation (see Corona SDK). These are assumed to be the same closed over by the exec call - chainFunctions relies on being able to manipulate them for "onComplete" and "whenDone" to work

options 는 animation을 위한 것이다. (Corona SDK)를 보세요. 이것들은 chainFunctions에서 exec에 의해 실행 된 다음에 onComplete과 whenDone 이 됐을 때 생성되게 되는 것들이 될 것이다.


--         # Additional feature: if options.delete==true the subject of the animation will be deleted (obj:removeSelf() called) when the animation completes

# 추가 기능들 : 만약 options.delete 가 true 이면 애니메이션이 완료 되면 그 애니메이션 되는 대상은 delete 될 것이다. (obj:removeSelf() 가 call 됨으로서).


-- runParent (optional) - run the previous function in the chain. It takes a single function as an argument. chainFunction creates this function itself

runParent(옵션)은 chain에 있는 이전 함수를 실행한다. argument로 single function을 갖는다. chainFunction 이 이 함수를 생성한다.


-- noanim (optional) - true if exec does not represent a function executing a Corona SDK transition. If true, the only valid call to the chain is "start"

noanim(옵션) - true. 만약 exec 가 Corona SDK 의 transition을 실행하지 않을 때 이 값은 true 가 될 것이다. 그러면 여기서 start 함수만 call 될 것이다.




이렇게 친절하게 주석을 달아 주었네요.


이 주석만 봐도 대충 이 함수의 로직이 이해가 됩니다.


main 함수에서 설정했던 빨간공의 위치들이 돌아가면서 options 가 되서 흰공이 이 빨간공 위치로 계속 이동할 것이고 더이상 빨간공의 위치가 없으면 noamin이 true 가 되서 start 함수가 호출되서 끝나는 거군요.


오늘은 여기까지 하구요.


다음 글에서 제대로 chainFunctions() 부분을 분석해 보도록 하겠습니다.



저작자 표시 비영리 동일 조건 변경 허락
신고


지난번에 이어 animation chain 소스를 분석해 보겠습니다.

지난번 글을 올린지 1주일이 지났나보네요.


지난번 main.lua 에서는 화면에 나오는 빨간 공 4개와 흰공 한개를 배치하는 로직이었습니다.

animationchain.lua 에서는 이 흰공을 4개의 빨간공을 따라서 움직이게 하는 코드가 있을 겁니다.


main.lua 의 마지막 줄에 이 작업을 하기 위해 animationchain.lua 에 있는 함수를 이렇게 call 했습니다.


animationchain.anim(c,stops[1]).whenDone(c,stops[2]).whenDone(c,stops[3]).whenStart(c,stops[4]).onComplete(c,stops[5]).onStart(function() print("done") end).start()


c 가 바로 흰공이었고 stops 에는 빨간공들의 위치값들이 있습니다.


animationchain.lua 의 소스는 아래와 같습니다.


local M={}
animationchain=M

local transition=transition
local setmetatable=setmetatable
local type=type
local error=error

setfenv(1,M)

local function start(anim,options,onStart,onComplete)
    return function()
        if options.delete then
            options.onComplete=function(obj)
                obj:removeSelf()
                if onComplete then
                    onComplete()
                end
            end
        else
            options.onComplete=onComplete
        end
        anim()
        if onStart then
            onStart()
        end
    end
end

-- args
-- exec - is the current function to execute. It is assumed to be a function that runs a Corona SDK transition
-- options - options for the animation (see Corona SDK). These are assumed to be the same closed over by the exec call - chainFunctions relies on being able to manipulate them for "onComplete" and "whenDone" to work
--         # Additional feature: if options.delete==true the subject of the animation will be deleted (obj:removeSelf() called) when the animation completes
-- runParent (optional) - run the previous function in the chain. It takes a single function as an argument. chainFunction creates this function itself
-- noanim (optional) - true if exec does not represent a function executing a Corona SDK transition. If true, the only valid call to the chain is "start"
function chainFunctions(exec,options,runParent,noanim)
    local t={}

    local mt={
        __index=function(t,k)
            local run=function(execChildAnim)
                local doIt
                -- wrap child animation in relation to previous call
                local onStart,onComplete
                if k=="onStart" or k=="whenStart" then
                    onStart=execChildAnim
                elseif k=="onComplete" or k=="whenDone" then
                    onComplete=execChildAnim
                end
               
                doIt=start(exec,options,onStart,onComplete)
               
                if runParent then
                    runParent(doIt)
                else
                    doIt()
                end
            end

            return function(...)
                if k=="start" then
                    return run()
                end
                if noanim then
                    error("animationchain: passing in pure functions must terminate the chain. Only call start after passing a single function into the chain")
                end
                if #arg==1 and type(arg[1])=="function" then
                    -- just a function has been passed in.
                    return chainFunctions(arg[1],{},run,true)
                end
                   
                return anim(arg[1],arg[2],run)
            end
        end
    }

    setmetatable(t,mt)
    return t

end

function anim(obj,options,runParent)
    return chainFunctions(function () return transition.to(obj,options) end, options,runParent)
end

return M


여기서 어느 부분이 제일 먼저 실행 될까요?


이 파일에는 크게 세개의 함수가 있습니다. 맨 처음에 start() 함수가 있구요. 두번째에 chainFunctions() 그리고 세번째로 anim() 함수가 있습니다.


start 함수가 맨 처음에 있고 이름도 start 이니까 이게 제일 먼저 실행 될까요?

아닙니다. 사실은 start() 함수는 이 세개중에 제일 나중에 실행 됩니다.


함수는 running time 중에 누군가가 call 해 주지 않으면 실행되지 않습니다.

마치 옛날 왕의 여자들처럼 나라를 run 하시는 왕이 call 하지 않으면 평생 독수공방해야 되듯이요.


제일 처음 실행되는 함수는 anim() 입니다. 왜냐하면 main.lua 의 맨 마지막 단계에서 이 함수를 call 했기 때문이죠.


이 anim() 함수는 무슨 행동을 할까요?


chainFunctions() 함수를 return 합니다. 즉 chainFunctions() 함수가 실행 되는 거죠.

여기서 파라미터로 function () return transition.to(obj,options) end 와 options, 그리고 runParent가 pass 됩니다.


이 파라미터들이 어디서 온건지 알아볼까요?




그냥 설명하면 길어질 것 같아서 위와 같이 정리해 봤습니다.

c 는 흰공이고 stops[1] 은 빨간공의 좌표 입니다.

빨간공은 따로 파라미터로 넘겨 주지 않습니다. 왜냐하면 흰공만 움직일 거니까요. chainFunctions() 에서 필요한건 빨간공의 좌표값뿐입니다.


우선 main.lua 에서 이 c(흰공) 과 stop[1] 첫번째 빨간공의 좌표를 넘겨 줍니다.

이걸 animationchain.lua 의 anim(obj,options,runParent) 함수가 받죠. 

여기서 obj 는 첫번째 파라미터로서 흰공 c를 말하고 options는 빨간공의 좌표 입니다. 그리고 세번째 좌표 runParent는 main.lua 에서 전달하지는 않았습니다.

이건 나중에 살펴 보겠습니다.


anim() 함수 안에서는 chainFunctions()를 콜하면서 3개의 파라미터를 전달하죠?

그런데 첫번째 파라미터는 함수를 전달해 줍니다.

transition.to(obj,options) 라는 함수 입니다.

transition.to()함수는 corona sdk 에서 제공되는 함수인데 애니메이션 표현할 때 아주 많이 사용합니다.

transition() 함수 API 를 보시려면 여기로 가셔서 참고하세요.


이 transition.to(obj.options)를 해석하자면 obj를 options 까지 움직인다는 의미 입니다. 즉 흰공을 stops[1] 까지 움직이는 거죠.


한번 소스 코드를 바꿔볼까요?


main.lua 에서

animationchain.anim(c,stops[1]).whenDone(c,stops[2]).whenDone(c,stops[3]).whenStart(c,stops[4]).onComplete(c,stops[5]).onStart(function() print("done") end).start()를 없애시고 animationchain.anim(c.stops[1])을 넣어 보세요.


그리고 animationchain.lau 의 anim() 함수 안에 

return chainFunctions(function () return transition.to(obj,options) end, options,runParent) 를 없애시고

transition.to(obj.options)를 넣어 보세요.


그러면 흰공이 가운데에서 왼쪽 빨간 점으로 움직일 겁니다.


main.lua 에서 animationchain.anim(c.stops[2]) 를 넣어 보세요. 

그러면 흰공이 곧바로 왼쪽 위에 있는 빨간공으로 움직이죠?


이렇게 해 보시면 transition.to() 함수의 기능을 확실히 아실 수 있을 겁니다.


일단 오늘은 animationchain.lua 파일에 있는 3가지 함수 중에 main.lua 에서 call 되서 가장 먼저 실행되는 anim(obj.options,runParent) 함수에 대해서 분석해 봤습니다.


이제 이 anim() 함수 안에서 chainFunctions()를 호출 하는 것을 확인 했습니다.

이때 3개의 파라미터가 전달 되는데 첫번째 파라미터는 흰공이 2번 빨간 공까지 transition 한다는 함수이고 두번째 파라미터는 options로서 이건 main.lua에서 부터 전달 된 빨간 공의 좌표 입니다.


runParent는 아직 정체를 알 수는 없습니다.


아마 이 소스의 핵심 부분인 chainFunctions()를 분석하면 알 수 있을 겁니다.


오늘은 여기까지만 하고요.


다음에 chainFunctions()를 분석해 보겠습니다.

저작자 표시 비영리 동일 조건 변경 허락
신고


오늘은 정말 오랜만에 Corona SDK 의 한 샘플 코드를 받아서 공부 좀 해 봤습니다.


animationchain 이라는 소스인데요.


소스는 https://github.com/personalnadir/animation 에 가시면 보실 수 있습니다.


파일을 다운 받아서 실행시켜 보시면 아래와 같은 화면을 보실 겁니다.






화면에 빨간 점이 4개 찍혀 있고 하얀 공이 이 빨간 공들을 찍으면서 움직입니다.

궁금하신 분들은 파일 다운 받아서 직접 실행해 보시면 됩니다.


오늘은 우선  main.lua  파일을 분석해 보죠.


require "animationchain"

local stops={
    {x=20},
    {y=20},
    {x=200,simul=true},
    {y=600},
    {x=display.contentCenterX,y=display.contentCenterY,delete=true}
}

local x,y=display.contentCenterX,display.contentCenterY
for k,v in ipairs(stops) do
    x,y=(v.x or x), (v.y or y)
    if not v.simul then
        display.newCircle(x,y,10):setFillColor(255,0,0)
    end
end

local c=display.newCircle(display.contentCenterX,display.contentCenterY,20)
animationchain.anim(c,stops[1]).whenDone(c,stops[2]).whenDone(c,stops[3]).whenStart(c,stops[4]).onComplete(c,stops[5]).onStart(function() print("done") end).start()


첫번째 줄은 animationchain.lua 라는 파일에 있는 함수를 implement 한다는 얘기 입니다.

오늘은 main.lua 만 다룰거니까 이 animationchain.lua 파일은 신경쓰지 않겠습니다.


다음은 stops 라는 배열에 key,value 형식으로 값들을 집어 넣었습니다.


다음은 x 와 y라는 변수에 화면 중심의 x, y 값을 대입했습니다.


그 다음은 for 문으로 위에 정해 줬던 stops 라는 key,value 쌍으로 돼 있는 배열을 돌면서 원을 만드네요.

화면에 찍힌 4개의 빨간색 점들이 이 for 루프를 돌면서 찍힌 겁니다.


display.newCircle(x,y,10):setFillColor(255,0,0) 는 지름이 10픽셀인 원을 x,y 좌표에 그리는데 그 원의 색은 빨간색 (RGB 에서 R 만 255이기 때문에 빨간색이 됩니다) 으로 그리라는 뜻입니다.

그다음에 또 원을 그리네요 c 라는 변수에 담기는 원을 그리는데요.

20픽셀의 지름을 갖는 원을 그리는데 그 위치는 화면의 중앙이 됩니다.

따로 원의 색을 지정해 주지 않았기 때문에 디폴트인 흰색(255,255,255)이 됩니다.


그 다음은 아까 require 에서 지정한 animationchain 이라는 파일 안에 있는 anim() 이라는 함수를 call 하는 겁니다.


일단 이 부분은 오늘 다루지 않을 부분이구요.


for 문에서 main.lua 의 주요 작업이 다 진행되니 이 for 문을 집중적으로 보겠습니다.

처음에 나오는게 ipairs 함수 입니다.


이 함수는 3개의 값을 return 합니다.

신택스는

for i,v in ipairs(t) do body; end

입니다.


이럴경우 (1,t[1]), (2,t[2]), ..., 이런 값들을 리턴하는데요. 이 루프는 ipairs안에 있는 t 라는 테이블의 마지막에 있는 값까지 도달하면 끝이 납니다.



그럼 main에 있는 for 문을 볼까요?


for k,v in ipairs(stops) do
    x,y=(v.x or x), (v.y or y)
    if not v.simul then
        display.newCircle(x,y,10):setFillColor(255,0,0)
    end
end


for 문만 보면 stops 에 있는 값들의 갯수 만큼 for  문이 돌겠군요.

그리고 그 값들은 1,stops[1], 2,stops[2] 이런식으로 진행이 됩니다.


다음줄은 x 에 v.x 나 x 를 대입하고 y에는 v.y나 y 를 대입합니다.

그러니까 v.x 가 있으면 그 값을 대입하고 그 값이 없으면 위에 지정한 x 값 즉 화면 중앙의 x 좌표값을 대입하게 됩니다.


그러니 처음 x 값은 stops 의 첫번째 값인 20 이 대입될 겁니다. 그 다음엔 y 값은 없으니까 for 문 바로 위에 지정해 둔 화면 중앙의 y 값이 대입될 거구요.


그리고 그 값을 x,y 값으로 해서 지름 10짜리의 빨간색 원을 그릴겁니다.


그 다음은 다시 for 문 처음으로 가는데 x, 값은 아까 지정된 20이 있을 테고 그 다음은 stops[2] 가 선택이 되니까 y 값으로 20이 지정될 겁니다.


그 다음에 두번째 빨간색 원이 그려질텐데 그 위치는 20,20이 될 겁니다.


다음 for 루프 돌 때는 stops[3] 가 되서 x는 200 이 되겠구 y는 따로 없으니까 바로 전에 지정된 20이 되겠네요.


그런데 if 문에서 v.simul이 아닐 경우에만 원을 그리라고 했으니까 이 원은 그려지지 않을 겁니다.


말로 설명하면 잘 감이 안올 수도 있으니까 눈으로 확인해 보겠습니다.


local stops={
    {x=20},
    {y=20},
    {x=200,simul=true},
    {y=600},
    {x=display.contentCenterX,y=display.contentCenterY,delete=true}
}

local x,y=display.contentCenterX,display.contentCenterY
for k,v in ipairs(stops) do
    x,y=(v.x or x), (v.y or y)
    print (v.x )
    print (v.y)
    if not v.simul then
        display.newCircle(x,y,10):setFillColor(255,0,0)
        print (k ..  " : " .. x .. " , " .. y)
        cord = k .. " : " .. x .. "," .. y;
        display.newText(cord,x+10,y, native.systemFont, 20)
    end
end

local c=display.newCircle(display.contentCenterX,display.contentCenterY,20)


for 문 안에서 x, y 좌표를 console 하고 화면에 찍는 코드를 삽입했습니다.

그러면 결과는 아래와 같습니다.




보시면 첫번째 빨간 원은 20,427 이고 두번째는 20,20 입니다. 그리고 세번째는 200,20 이 될 텐데 if 문에 걸려서 이 세번째 원은 그려지지 않을 겁니다.

네번째는 200,600 이고 다섯번째는 stops 의 마지막 값으로 화면 정중앙에 있는 빨간 원이 그려집니다.



그냥 좌표값을 사용해서 그리면 될 텐데 왜 이렇게 복잡하게 할까요?


일단 이렇게 하면 코드의 양을 줄일 수 있고 빨간원이 그려지는 위치를 stops 배열 안에서 정해주면 되니까 나중에 수정하거나 할 떄 편해 집니다.

가독성이 좋고 maintanance 를 용이하게 해 주는 것이죠.


그리고 모바일은 기계마다 해상도가 다른데 이 stops 좌표를 각 해상도마다 따로 만들어 줘서 기계의 해상도에 따라서 다른 좌표를 사용해서 빨간 원을 그리게 할 때도 편리하게 이용할 수 있습니다.


맨 마지막 줄은 animationchain 파일에 있는 anim()함수를 call 하고 있습니다.

이 때 parameter들이 전달 되는데요.


첫번째 파라미터로 c 즉 하얀 원이 전달 됩니다. 이 c 가 움직일 거니까 이 객체를 파라미터로 전달해야겠죠.

그리고 두번째는 stops[1] 이 전달 됩니다. 이 하얀 공이 첫번째로 갈 목적지가 될 겁니다.


목적지에 도달하면 (whendone) 두번쨰로 c,stops[2] 를 인수로 해서 anim 을 call 하고 이 작업을 c,stops[5] 가 될 때까지 할 겁니다.


그 다음에 onStart()를 붙인 것은 transition이 일어나기 전에 이 onStart() 메소드를 실행하라는 의미입니다. 다음 파일안에 이 onStart 메소드가 있어야 되고 그 메소드 안에서 이벤트 대신 target이 pass 되도록 할 수 있습니다.


잘 이해가 안 되시는 분들은 API를 보시면 도움이 되실 겁니다.


여기서는 onComplete() 다음에 호출 되니까 공이 다 돌면 다음 transition 하기 전에 print 'done' 을 하라는 거네요.


그 다음은 animationchain 에 있는 start() 함수를 호출하는 거구요.


여기까지 main.lua 에 대해 공부해 봤습니다.


다음엔 이 오픈소스의 핵심인 animationchain 파일을 분석해 보겠습니다.


저작자 표시 비영리 동일 조건 변경 허락
신고


Emanuele Feronato의 블로그를 알게되서 가끔 들어가 보는데 아주 훌륭한 로직들을 많이 share하고 있네요.

눈내리는 효과, 물방울 올라가는 효과 두가지를 분석해 봤는데요.
여기 뱀 기어다니는 효과가 재밌어서 제 블로그에 정리해 둘까 합니다.
Emanuele는 String Avoider라고 이름을 붙였는데요. 제 눈에는 뱀 기어다니는걸로 보이더라구요.

아래 유튜브에도 올려놨는데 아주 간단한 코드로 이런 효과를 주고 있습니다.


아래 소스가 있습니다.

-- Declaring Variables
-- Stage Width and Height
local _W,_H = display.contentWidth,display.contentHeight
 
local tailLength = 5
local tailNodes = 200
local head = display.newCircle(_W/2,_H/2,10)
local nodes = {}
 
for i=1,tailNodes,1 do
    nodes[i] = {}
    nodes[i].x = head.x
    nodes[i].y = head.y
end
 
-- Creating Display Groups
local tailGroup = display.newGroup()
 
local function startDrag(event)
    -- Store the Target (the Head) in a variable
    local t = event.target
    if event.phase == "began" then
        -- When the touch started, set the Focus on the Head
        display.getCurrentStage():setFocus( t )
        t.isFocus = true
        -- Store initial Position of the Head
        t.x0 = event.x - t.x
            t.y0 = event.y - t.y
    elseif t.isFocus then
        if event.phase == "moved" then
            -- While moving the head
            -- remove every drawn line from the group
            for i=tailGroup.numChildren,1,-1 do
                tailGroup:remove(i)
            end
 
            -- Move the Head
            t.x = event.x - t.x0
            t.y = event.y - t.y0
 
            -- Create starting line and insert it into the group
            local line = display.newLine(head.x,head.y,nodes[1].x,nodes[1].y)
            tailGroup:insert(line)
            line.width = 20
 
            nodes[1].x = head.x
            nodes[1].y = head.y
 
            -- Loop through every tailNode and save new positions in table
            -- Note: LUA tables start with an index of 1 and not 0!
            for i=2,tailNodes,1 do
                local nodeAngle = math.atan2(nodes[i].y-nodes[i-1].y,nodes[i].x-nodes[i-1].x);
                nodes[i].x = nodes[i-1].x+tailLength*math.cos(nodeAngle)
                nodes[i].y = nodes[i-1].y+tailLength*math.sin(nodeAngle)
 
                -- You could use
                -- line:append(nodes[i].x,nodes[i].y)
                -- but this wouldn't allow you to alter each segment (fading the color, reducing linewidth etc.)
 
                -- Creating new line segments
                local nLine = display.newLine(nodes[i-1].x,nodes[i-1].y,nodes[i].x,nodes[i].y)
                -- c will store the colorvalue - starting from 255 and ending with 0 (white to black)
                local c = 255-i/tailNodes*255
                nLine:setColor(c)
                -- width will start at 20 and fade out to 0
                nLine.width = line.width-i/tailNodes*line.width
 
                -- Important: insert the new Line segments into the table!
                tailGroup:insert(nLine)
            end
        elseif event.phase == "ended" or event.phase == "cancelled" then
            -- Remove the focus
            display.getCurrentStage():setFocus( t, nil )
            t.isFocus = false
        end
    end
end
 
-- Create touch Eventlistener
head:addEventListener("touch",startDrag)

한번 분석해 볼까요?

첫줄은 모바일 기기의 가로 세로 길이를 변수에 담았구요.
tailLength와 tailNodes 변수에 숫자를 대입했습니다.
그리고 head라는 변수에 원을 그려 넣었구요. nodes 라는 테이블 변수를 만들었습니다.

다음 for 문에서는 tailNodes 값 만큼 nodes에 head를 넣습니다. 총 200개가 되겠죠?
nodes 테이블에는 작은 원 200개가 들어있습니다. 이걸 가지고 효과를 만들겁니다.

다음에는 Display Group을 만들었습니다. tailGroup이라는 이름으로요.

그 다음으로는 맨 밑에 있는 리스너를 보죠.
head에 touch 리스너를 달았네요. 맨 처음 그려진 head에 Listener가 달립니다.
이 head를 touch 하면 startDrag함수가 실행되구요. 그러면 began, moved, ended phases 에 따라서 Control 할 수가 있습니다.

이제 startDrag 함수를 분석해 보죠.
처음 변수 t를 만들고 여기에 event.target 즉 head 를 넣습니다.
이제 control을 해 보죠.
began 단계에서는 t에 setFocus를 합니다.
setFocus는 해당 리스너에 Focus를 둔다는 의미인데요. 제 블로그에서 setFocus로 검색하시면 관련 글이 뜰 겁니다. 참고 하세요.
그리고 t.x0 에는 현재 이벤트가 일어나는 x포인트에서 t(head)의 x포인트를 뺀 값을 넣습니다. t.y0에도 현재 이벤트가 일어나는 y포인트에서 t의 y포인트를 뺀 값을 넣구요.
began에서는 여기까지 하구요. t.isFocus가 계속 유지되고 있으면 moved 일 때 로직을 실행합니다.
moved에서 처음 하는 일은 tailGroup의 모든 구성요소들을 지우는 겁니다. 이전 이미지가 그대로 남아 있으면 움직이는 효과가 아니라 선이 그려지는 효과가 날겁니다.

이 지우는 기능을 빼면 이런 효과가 납니다. 한번 해 보셔도 재밌을 거예요. 이 로직도 잘 이용하면 좋은 효과를 낼 수 있겠는데요.

그리고  t.x와 t.y를 이벤트가 일어난 좌표에서 began에서 구했던 t.x0와 t.y0를 뺀 값을 넣습니다. 그러면 head가 움직이게 됩니다.

그리고 head의 x,y좌표에서 그 다음 node에 선을 긋습니다. (뱀의 몸이 이어지도록 보이겠죠?)
그리고 이 라인을 그룹에 insert하구요. 그 width를 20으로 설정합니다.
그리고 이 node 1을 head 위치로 옮깁니다.

다음에는 for문을 돌려서 모든 tailNode를 새로운 위치로 옮겨서 뱀 몸체가 기어가는 효과가 나도록 합니다.
우선 현재의 node와 이전 node 사이의 각도를 구해서 nodeAngle에 넣습니다.
두 점의 좌표를 가지고 각도를 구하는 공식은 math.atan2(y,x) 함수를 이용하시면 됩니다.
그리고 이 각도를 이용해서 각 node의 x,y좌표값을 얻습니다.
x는 math.cos(x) 함수를 이용하시고 y는 math.sin(x)를 이용하시면 됩니다.

이제 뱀 머리를 움직이면 이전 이미지들은 지워지고 이전 node 이미지들이 계속 따라오면서 움직이는 효과가 날 겁니다.

그런데 아직까지는 짧은 선이 계속 따라 올 거구요. 긴 뱀 꼬리가 따라오도록 하는 건 아래 로직에서 처리 해 줍니다.

아까는 head와 첫번째 node사이에 line을 그려 줬는데요. 이제는 node와 node들 사이에 라인을 그려 줍니다.
그리고 나서 색이 점점 검은색에 가까와 지도록 계산을 해서 이 새 라인의 색을 set 해 줍니다.
그러면 꼬리로 갈 수록 점점 색이 여리게 나올 겁니다.

여기까지 하면 이런 효과가 납니다.
아직 width를 설정해 주지 않아서 그렇습니다.
이 모양은 정자가 움직이는 모양 같네요.

이 node들 사이의 line의 두께는 뒤로 갈 수록 점점 줄어들도록 하는 공식을 넣습니다.
그리고 이 라인을 tailGroup에 insert합니다.

여기까지 하면 모든 움직이는 로직은 완료 됩니다.
마지막으로 유저가 손을 떼어서 이벤트가 완료 되면 setFocus를 해제합니다.

이러면 저 위에 유튜브 영상에서 보는 것처럼 뱀이 기어다니는 효과를 주실 수 있습니다.

이 소스에서는 math 함수 3개에 대해서 익숙해 지도록 연습하라고 권해 드리고 싶네요.
그러면 정말 아주 다양한 효과들을 낼 수 있습니다.
즐코딩 하시구요. ~~~~ ~~~~ 추천 추천 부탁 드려요...
저작자 표시 비영리 동일 조건 변경 허락
신고


오늘은 Corona로 그럴듯하게 눈발 흩날리는 느낌을 한번 줘 보겠습니다.

ramdom하고 translate, newImageRect 를 이용해서 이 효과를 줘 봤습니다.

우선 먼저 소스를 보고 분석 해 보죠.

display.setStatusBar( display.HiddenStatusBar )
local mRand = math.random; math.randomseed(os.time())
local xGravity,yGravity,xWind, yWind = 0,18,45,2
local bg = display.newImage("usa_bg2.png")
local snow = display.newGroup()
local function animateSnow( event )
    for i=1, snow.numChildren do
        local flake = snow[i]
        flake:translate((flake.xVelocity+xWind)*0.1,(flake.yVelocity+yWind)*0.1)
        if flake.y > display.contentHeight then
            flake.x, flake.y = mRand(display.contentWidth), mRand(60)
        end
    end
    if mRand(64) == 1 then xWind = 0-xWind; end
end

local function initSnow(snowCount)
    for i=1,snowCount do
        --local flake = display.newImageRect(snow,"flake.png",1,1)
        local flake = display.newImageRect(snow,"drophose.png",6,6)
        if mRand(1,2) == 1 then flake.alpha = mRand(25,100) * .01; end
        flake.x, flake.y = mRand(display.contentWidth), mRand(display.contentHeight)
        flake.yVelocity, flake.xVelocity = mRand(60), mRand(50)
    end
    Runtime:addEventListener("enterFrame", animateSnow)
end
initSnow(1000)

처음엔 아이폰의 status바 없애는 부분이고요.
두번째 줄은 mRand라는 변수에 랜덤값을 넣구요 randomseed로는 os time을 사용합니다.
중력은 y방향으로반 18을 주고 바람의 첫번째 값은 x로 45 y로 2 입니다.
다음는 배경화면을 깐겁니다.
5번째 줄에 그룹을 만들었는데요 이 그룹은 눈을 담을 겁니다.
눈 이미지는 하나만 사용할 거구요. 이 이미지를 1000개 만들어서 이 그룹에 넣게 됩니다.

다름에 animateSnow 함수가 나옵니다.

이 부분이 눈발을 흩날리는 부분입니다.
처름에 for문을 돌리는데요. snow그룹안에 있는 인자들의 갯수만큼 for문을 돌립니다.
 맨 아랫줄 보시면 1000이라는 숫자가 보이죠? snow그룹안에는 인자가 천개가 될 테니까 1000번 돌겁니다.
그 다음 flake라는 변수에 snow의 i번째 인자를 담구요 이 flake를 translate 시킵니다.
이 falke가 y방향으로 밖으로 나가게 되면 다시 화면 안으로 들어오게만들구요.

여기서 falke의 방향하고 속도는 initSnow에서 랜덤하게 정해진 값입니다.

initSnow함수는 snowCount만큼 for문을 돌립니다. 역시 1000개가 되겠죠?
flake에 imageRect를 할당합니다.
여기서 Rect에 크기를 조절하면 싸리눈도 되고 함박눈도 됩니다.
그리고 이 flake에 alpha값을 랜덤하게 지정해 줍니다.
위치도 랜덤하게 지정하구요.
다음으로 속도도 랜덤하게 적용합니다.

이렇게 1000개의 flake를 만들구요. 그 다음에 animateSnow를 Runtime의 enterframe으로 실행 시킵니다.

맨 마지막 줄은 이 initSnow함수를 실행시키는데 인자 값으로 1000을 전달합니다.
이 숫자가 적으면 눈이 적게 내리겠고 많으면 눈이 많이 내래겠죠?



완성된 화면입니다.
눈이 바람에 흩날리면서 내립니다.
눈(flake)의 갯수나 imageRect 사이즈를 조정하시면 눈을 많이 내리게도 할 수 있고 조금 내리게도 할 수 있고 싸리눈이 될 수도 있고 함박눈이 될 수도 있습니다.

이렇게 그럴듯한 눈내리는 효과를 단 25줄로 처리해 버립니다.
코로나SDK 아주 훌륭합니다...
이 효과 잘 활용하시구요. 꾹~~ 꾹~~ 추천 부탁드립니다.~~~~~
저작자 표시 비영리 동일 조건 변경 허락
신고


어제 세계 최고의 기획자로 부터 야바위 게임 스토리 보드를 전달 받았습니다.
기획자가 너무 훌륭해서 스토리 보드만 가지고도 충분히 코딩을 할 수가 있겠네요. ^^
기획자로부터 자세히 설명을 듣고 이렇게 스토리보드 까지 받았습니다.

이제 프로그래머는 나름대로 objects 들과 함수들에 대한 설계를 했을 테고 또 Flow Chart 를 그려서 어떻게 앱이 진행될지 그려 보았을 겁니다.

좀 더 완벽하게 준비하는 스타일의 개발자라면 Use Case 도 한번 정리 해 봤겠죠...
물론 이 내용들은 하나의 문서에 정리 돼 있겠구요.

이제 코딩을 시작하겠습니다.

display.setStatusBar(display.HiddenStatusBar)
-- Graphics-- [Background]
local bg = display.newImage('bg.png')

일단 아이폰에서 status bar 를 안보이도록 하구요.
백그라운드 이미지는 앱이 시작하면서 끝날때까지 유지 되니까 함수(function) 내가 아니라 이렇게 앱 시작하면서 display를 해 놓습니다.

그 다음엔 각 objects를 담을 변수들을 만듭니다.

-- [Title View]
local title; local playBtn; local creditsBtn; local titleView

여긴 게임 첫 화면에 나올 objects를 정리했습니다.
게임 제목과 플레이 버튼,크레딧 버튼이구요. 이 첫 화면 객체들을 담을 그룹으로 titleView를 사용할 겁니다.

-- [Credits]
local creditsView -- 첫 화면에서 나올 credit 이미지가 들어갈 변수입니다.

-- [Bank Credits]
local bank; local bankText

게임 화면에서 좌측 상단에 들어갈 숫자(bank)와 이미지(bankText) 변수 입니다.

-- [Shells]
local s1; local s2; local s3; local shells;

3개 shell에 대한 변수 이구요. 이 3개 shell 들을 담을 로컬그룹 변수 shells입니다.

-- [Ball]
local ball -- ball 이미지를 담을 변수입니다.
-- [Button Bar]
local buttonBar -- 아래 진한 색 네모를 표시할 이미지를 담을 변수입니다.
-- [Bet Button]
local betBtn     -- 게임 시작 버튼 이미지를 담을 변수 입니다.
-- [Message Text]
local msg        -- 왼쪽 아래 안내 문구를 담을 변수 입니다.
-- [GameView]
local gameView  -- Game 화면에 있는 모든 변수
-- [Alert]
local alert-- alert 이미지 담을 변수
local moveSpeed = 600  -- shell 움직이는 스피드
local totalMoves = 5      -- shell 움직이는 횟수

자 여기까지 야바위 게임에서 사용할 모든 objects에 대한 변수를 선언했습니다.
세계최고의 기획자가 스토리보드에서 언급한 모든 객체를 생성했구요.
또 프로그래머가 화면 전환할 때 사용하기 위해 각 화면별 objects들의 localGroup으로 사용할 변수들도 선언했습니다.
그리고 스피드,움직이는 횟수같은 개념적인 객체에 대한 변수까지 다 선언했습니다.
변수의 갯수가 총 20개네요.
20개의 objects들이 이 게임을 만들어 나갈겁니다.
출연자들이죠. 주연도 있고 조연도 있고...
이제 스토리보드 대로 (시나리오대로) 움직임을 만들 차례입니다.

그러기 위해 함수(메소드)를 선언합니다.

-- Functions
local Main = {}
local startButtonListeners = {}
local showCredits = {}
local hideCredits = {}
local showGameView = {}
local placeBet = {}
local randomShellMove = {}
local checkMovesLeft = {}
local revealBall = {}
local alert = {}

이 앱에서는 총 10개의 메소드가 사용됩니다.

-- Main Function
function Main()
    title = display.newImage('title.png', display.contentCenterX - 123, 40)
    playBtn = display.newImage('playBtn.png', display.contentCenterX - 25.5, display.contentCenterY - 10)
    creditsBtn = display.newImage('creditsBtn.png', display.contentCenterX - 40.5, display.contentCenterY + 45)
    titleView = display.newGroup(title, playBtn, creditsBtn)
  
    startButtonListeners('add')
end

첫번째 함수는 Main()함수입니다.
이 소스코드 맨 마지막을 보시면 이 메인 함수를 호출합니다.
Main() 부분이죠. 그러니까 이 앱은 이 Main()부터 시작합니다.
그러니까 첫화면에 display될 제목과 플레이/credit 버튼을 화면에 그립니다.
그리고 이 title화면의 객체들을 titleView라는 localGroup으로 그룹화 합니다.
(여기서 bg는 빠졌습니다. 왜냐하면 bg는 모든 화면에 다 등장할 거거든요.)

그리고 startButtonListeners()라는 함수에 add라는 인수를 전달하면서 호출합니다.

function startButtonListeners(action)
    if(action == 'add') then
        playBtn:addEventListener('tap', showGameView)
        creditsBtn:addEventListener('tap', showCredits)
    else
        playBtn:removeEventListener('tap', showGameView)
        creditsBtn:removeEventListener('tap', showCredits)
    end
end

메인에서 호출된 함수인데요.
메인에서 add를 인수로 던졌죠?
안에 내용을 보니까 add가 인수일 경우에는 플레이버튼과 크레딧 버튼에 리스너를 답니다.
플레이버튼을 누르면 showGameView() 함수를 실행하고 크레딧버튼을 누르면 showCredits() 함수를 실행하게 됩니다.
만약 인수가 add가 아니라면 이 두 리스너를 remove합니다.

우선 크레딧 버튼을 눌렀을 때 실행될 showCredits() 함수를 보겠습니다.

function showCredits:tap(e)
    playBtn.isVisible = false
    creditsBtn.isVisible = false
    creditsView = display.newImage('credits.png')
    transition.from(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:addEventListener('tap', hideCredits) creditsView.x = creditsView.x - 0.5 end})
end

메인함수에서 크레딧 버튼을 누르면 실행되는 함수입니다.
처음에 플레이 버튼과 크레딧 버튼을 안 보이도록 만듭니다.
그리고 creditsView 이미지를 만들구요. 이 이미지를 transition합니다.
0.3초동안 왼쪽에서부터 서서히 나올 겁니다.
이미지가 다 나오면 이 이미지에 리스너를 답니다.
이 creditsView 이미지를 클릭하면 hideCredits() 함수가 실행 됩니다.

function hideCredits:tap(e)
    playBtn.isVisible = true
    creditsBtn.isVisible = true
    transition.to(creditsView, {time = 300, x = -creditsView.width, onComplete = function() creditsView:removeEventListener('tap', hideCredits) display.remove(creditsView) creditsView = nil end})
end

크레딧 이미지를 클릭하면 실행되는 함수 입니다.
아까 안 보이도록 했던 플레이버튼과 크레딧 버튼을 모두 다시 보이도록 합니다.
creditsView를 transition.to를 이용해서 왼쪽으로 사라지도록 합니다.
다 사라지면 아까 만들었던 creditsView에 대한 리스너를 remove합니다.

여기까지가 첫 화면에서 크레딧 버튼을 누르고 나서 다시 원래 화면으로 돌아오기 까지 진행시키는 함수들 입니다.

다음엔 첫 화면에서 플레이 버튼을 누르면 실행되는 showGameView() 함수입니다.
본격적으로 게임이 시작 되는 부분입니다.

function showGameView:tap(e)
    transition.to(titleView, {time = 300, x = -titleView.height, onComplete = function() startButtonListeners('rmv') display.remove(titleView) titleView = nil end})
    -- [Bank Credits]
    bank = display.newText('5', 18, 5, native.systemFontBold, 14)
    bank:setTextColor(234, 170, 12)
    bankText = display.newImage('bankText.png', 7.5, 25)

    -- [Ball]
    ball = display.newImage('ball.png', 228, 142)
  
    -- [Shells]
    s1 = display.newImage('shell.png', 50, 114)
    s2 = display.newImage('shell.png', 195, 84)
    s2.name = 's2'
    s3 = display.newImage('shell.png', 340, 114)
    shells = display.newGroup(s1, s2, s3)
  
    -- [Button Bar]
    buttonBar = display.newImage('buttonBar.png', 0, 270)
    msg = display.newText('Click Bet to start', 1, 307, native.systemFont, 9)
    betBtn = display.newImage('betBtn.png', 223, 275)
  
    betBtn:addEventListener('tap', placeBet)
  
    gameView = display.newGroup(bank, bankText, ball, shells, buttonBar, msg, betBtn)
end

실제 게임화면으로 넘어오면 처음에 실행되는게 이전 화면의 객체들을 없애는 작업입니다.
아까 첫 화면 객체들은 titleView라는 localGroup으로 그룹화를 했었습니다.
transition.to를 이용해서 이 titleView를 왼쪽으로 이동시키고 다 이동되면 startButtonListener에 rmv라는 인수를 전달하면서 실행시킵니다.
startButtonListener는 아까 add를 인수로 전달하면서 불렀던 함수인데요.
인수가 add가 아니면 첫 화면의 플레이버튼하고 크레딧 버튼의 리스너를 remove하도록 돼 있었습니다.
그러니까 첫화면의 객체들을 사라지게 하고 이 객체들에 할당된 리스너들도 다 remove 한 겁니다.
그 다음으로는 text를 출력할 bank를 만들고 위치시켜주고 색도 지정합니다.
그리고 bankText 이미지도 지정된 위치에 display합니다.
다음은 ball 이미지를 지정된 위치에 display하구요. shell 3개를 나란히 display합니다.
여기서 ball이 들어가 있게 될 shell은 별도로 name을 s2라고 할당합니다.
그리고 이 shell들을 shells라는 변수에 그룹화 시켜 놓습니다.
다음에 하단에 buttonBar를 표시하고 msg Text를 표시합니다.
그리고 betBtn을 표시하고 이 이미지에 리스너를 답니다.
이 이미지를 누르면 paceBet() 함수가 실행됩니다.
맨 마지막에는 이 게임 화면에 있는 모든 objects를 gameView변수로 그룹화 합니다.
나중에 화면전환 할 때 사용하기 위해서 입니다.


function placeBet:tap(e)  
    -- Place Bet
    bank.text = bank.text - 1;
    -- Remove Button Listener
    betBtn:removeEventListener('tap', placeBet)
    -- Change Msg
    msg.text = ''
    -- Reset Total Moves
    totalMoves = 5
    -- Hide Ball
    transition.to(s2, {time = moveSpeed, y = s2.y + 30, onComplete = randomShellMove})
end

betBtn을 누르면 실행 될 placeBet()함수입니다.
이 함수에서는 bank.text를 -1해서 다시 display합니다.
그리고 betBtn이미지의 리스너를 remove합니다.
다음으로는 msg.text를 아무것도 표시하지 않도록 고치구요.
shell이 몇번 움직일지 그 숫자를 다시 지정합니다.
(지금은 초기 설정값이랑 같은데 만약에 점점 더 움직이는 횟수를 늘리고 싶으면 이 부분에서 코딩해 주면 됩니다.)
다음은 s2를 아래로 내립니다. 그러면 공을 덮어서 안 보이게 됩니다.
완료 되면 randomShellMove() 함수가 호출 됩니다.

function randomShellMove()
    local randm = math.floor(math.random() * 2) + 1
  
    local shell1 = shells[randm]
    local shell2
  
    if(shell1 ~= 3) then
        shell2 = shells[randm + 1]
    elseif(shell1 ~= 1) then
        shell2 = shells[randm - 1]
    end
  
    ball.isVisible = false
  
    totalMoves = totalMoves -1
  
    transition.to(shell1, {time = moveSpeed, x = shell2.x, y = shell2.y})
    transition.to(shell2, {time = moveSpeed, x = shell1.x, y = shell1.y, onComplete = checkMovesLeft})
end

여기는 3개의 shell을 움직이는 로직입니다.
아마 이 앱에서의 핵심 부분일 겁니다.
이 로직은 여러분이 연구해 보세요.
이것보다 더 좋은 로직을 생각해 보셔도 되구요.
이 로직에 대한 해석은 생략하겠습니다.
하여간 로직대로 shell3개를 5회 움직입니다. 이때 ball은 안보이도록 하구요.
shell2가 다 움직이면 checkMovesLeft()를 호출합니다.

function checkMovesLeft()
    if(totalMoves > 0) then
        randomShellMove()
    else
        s1:addEventListener('tap', revealBall)
        s2:addEventListener('tap', revealBall)
        s3:addEventListener('tap', revealBall)
      
        -- Change Msg
        msg.text = 'Click where the ball is'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    end
end

이 함수에서는 totalMoves를 먼저 체크합니다. 0이 아니면 randomShellMove()함수를 다시 호출합니다. 이렇게 해서 총 5번 randomShellMove()함수가 실행 될 겁니다.
totalMoves가 0 이면 각 shell에 모두 리스너를 답니다.
tap 하면 revealBall함수가 실행되도록이요.
그리고 msg.text는 Click where the ball is 로 바꿔 주시고 그 위치를 바로 잡아 줍니다.

function revealBall:tap(e)
    -- Remove Shell Mouse Listeners
    s1:removeEventListener('tap', revealBall)
    s2:removeEventListener('tap', revealBall)
    s3:removeEventListener('tap', revealBall)
  
    -- Move Ball to correct position
    ball.x = s2.x + 75
    ball.y = s2.y + 150
    ball.isVisible = true
  
    -- Give credits if correct guess
  
    if(e.target.name == 's2') then
        bank.text = bank.text + 2
        -- Change Msg
        msg.text = 'Correct! Click Bet to play again'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    else
        msg.text = 'Wrong! Click Bet to play again'
        msg:setReferencePoint(display.TopLeftReferencePoint)
        msg.x = -20
    end

    -- Reveal Ball
    transition.to(s2, {time = moveSpeed, y = s2.y - 30})
  
    -- Add Bet button listener
    betBtn:addEventListener('tap', placeBet)
  
    -- Check for bank credits
    if(bank.text == '0') then
        betBtn:removeEventListener('tap', placeBet)
            alert()
    end
end

이 함수에서는 호출되면 일단 3개 shell에 할당된 리스너를 모두 remove합니다.
그리고 ball의 위치를 두번째 shell 즉 s2 안으로 위치시키고 이 볼이 보일 수 있게 합니다.
터치한 shell이 s2이면 즉 ball이 있는 shell이면 msg.text를 Correct! Click Bet to play again 으로 표시하고 그 위치를 바로 잡습니다. 그리고 s2가  아니면 Wrong! Click Bet to play again 을 표시하고 그 위치를 잡습니다.
그리고 s2를 transition.to를 이용해서 위로 올립니다. 그러면 ball이 나오겠죠?
이제 게임을 다시 시작할 수 있도록 betBtn에 리스너를 다시 달아줍니다.
만약 bank.text가 0 이면 그 리스너를 다시 없애고 alert() 함수를 실행합니다.

function alert()
    alert = display.newImage('alert.png')
    alert:setReferencePoint(display.CenterReferencePoint)
    alert.x = display.contentCenterX
    alert.y = display.contentCenterY
    transition.from(alert, {time = 300, xScale = 0.3, yScale = 0.3})
  
    msg.text = ''
  
    alert:addEventListener('tap', restart)
end

이 함수에서는 alert이미지를 transition.from을 이용해서 display하구요.
msg.text를 아무것도 표시 안 합니다.
그리고 이 alert이미지에 리스너를 달아서 tap하면 restart()함수가 실행되도록 합니다.

function restart()
    display.remove(gameView)
    gameView = nil
    display.remove(alert)
    alert = nil
  
    Main()
end
이 함수에서는 게임화면에 있는 모든 객체들(gameView)을 remove합니다.
그리고 alert화면도 remove합니다.
그리고 Main()을 실행시켜 게임을 다시 시작하도록 합니다.

Main()
이 부분은 앱이 처음 시작할 때 Main()을 호출하는 부분입니다.

이번엔 야바위게임을 같이 살펴봤습니다.
게임 설계가 아주 모범적으로 잘 돼 있는것 같습니다.

저도 이 모범적인 설계를 배우고 싶어서 공부 자료로 사용했습니다.
코딩이 아주 깔끔하네요.

아래 원래 소스 파일이 있습니다.

저작자 표시 비영리 동일 조건 변경 허락
신고


오늘은 Corona SDK 로 만든 돈 넣고 돈 먹기 게임을 공부해 보겠습니다.
예전에 장터에 가면 사발이 세개 있고 그 중 한개에 구슬이나 주사위를 넣은 다음 막 돌린 다음 그 주사위가 어디에 있는지 알아 맞추는 놀이가 있었죠?
야바위라고 어른들이 그러셨는데....

Carlos Yanez 라는 개발자가 이 야바위를 모바일 애플리케이션으로 개발 했습니다. Corona SDK로요.
그리고 그 소스를 공유했습니다.

제가 보기엔 아주 프로그램이 알차게 잘 돼 있는 것 같습니다.
이 소스코드를 분석하면 아주 공부가 잘 될 것 같습니다.
(그리고 이 야바위 게임이 전 세계적으로 있었나 보네요. 몰랐는데......)

아래 각 게임 화면들을 캡쳐 했습니다.

이걸 게임 개발 하기 전에 개발자가 받은 Story Board 라고 생각을 해 보죠.
아주 훌륭한 기획자라면 이렇게 디자인까지 다 된 스토리보드를 개발자에게 줄 거예요.
그런데 너무 그런거까지 기대하지 마세요. 디자인 까지는 안 되더라도 모바일 이미지 내 objects 배치해서 주는 기획자도 드물거예요.
그냥 볼펜으로 끄적인 스토리 보드만 줘도 감지덕지죠.
안 에 들어가는 Objects 들하고 있게 될 Event 들 하고 그 이벤트로 인해 진행되는 object 들의 behavior 만 잘 정리 되면 되죠 뭐.

하여간 아래 내용은 세계에서 최고로 개발자에게 편하게 기획하는 기획자가 만든 앱의 스토리보드라고 생각해 봐요 우리.

1. 첫 화면


Objects (객체들)

:  bg 배경이미지, title 게임 제목,  playBtn 플레이 버튼, creditsBtn 크레딧 버튼

Events/Behavior (이벤트)

: playBtn 누르면 현재화면 중 배경만 남고 모두 없어짐 -> 게임 화면으로 옮김

  현재 화면 객체들 왼쪽으로 slide 되면서 없어질 것

: creditsBtn 누르면 Credits를 보여 줌

  플레이와 크레디트 버튼 없어지고 크레디트 이미지가 왼쪽에서 slide 되면서 나옴


2. Credit 화면 (Credit 버튼 누르면 나옴)



Objects

: Credits 이미지

  나머지 bg와 title은 이전화면에서 없어지지 않고 그대로 있음

: Events/Behavior

  Credits 이미지를 클릭하면 이미지가 왼쪽으로 사라짐

  1번 화면이 다시 나옴


3. 게임 화면



objects

: bg 배경화면 그대로

: bank 왼쪽 상단 숫자, 처음엔 5자가 표시 됨

: bankText BANK 이미지

: ball

: s1/s2/s3 shell 3개

: buttonBar 밑에 진한 사각형

: msg 좌측 하단에 메세지, 처음에 Click Bet to start 가 표시 됨

: betBtn 게임 시작 버튼


Events/Behavior

betBtn 버튼에 tab 리스너를 담.

betBtn을 누르면

- Bank 숫자에서 -1을 함

- betBtn 리스너를 Remove 함

- msg 텍스트를 없앰

- 위로 올라간 shell 이 ball 을 덮음

- shell 을 총 5번 움직임. 움직임 속도는 600으로 함. 랜덤하게 움직임


4. Shell 이 다 움직이고 User 의 선택을 기다리는 화면, 선택한 후 화면





objects

: 게임 화면이므로 3번 게임화면과 똑 같음

Event/Behavior

: msg는 Click where the ball is 를 표시함

: 각 shell 에 리스너를 담

: 어떤 Shell 을 클릭해도 공이 있는 Shell이 올라감

: 공이 있는 Shell 을 클릭하면 bank+2를 해 줌, msg는 Correct! Click Bet to play again 을 표시해 줌

: 공이 없는 Shell 을 클릭하면 msg는 Wrong! Click Bet to play again을 해 줌

: betBtn 에 다시 리스너를 담,

: bank 가 0점이면 alert() 화면을 띄움


5. Game Over 화면 (Alert)



Objects
: alert 이미지
Event/Behavior
: msg는 표시 안 함
: alert 이미지에 리스너를 담
  클릭하면 게임을 재 시작 함


자 여기까지가 바로 세계에서 제일 훌륭한 기획자가 만든 스토리 보드 입니다.
기획에서 이정도만 해주면 개발자는 아주 편하겠죠?

이제 이 것을 토대로 Flow Chart 도 만들어 보고 Use Case 도 만들어보고 나름대로 함수(클래스) 설계도 해 보세요.

아마 그러면 실력향상에 아주 도움이 될 겁니다.

다음 시간엔 이 스토리 보드를 가지고 소스 코딩을 해 보겠습니다.

저작자 표시 비영리 동일 조건 변경 허락
신고


제가 예전에 써핑하다가 받은 소스입니다.
누가 만든 로직인지도 모르겠네요.

하옇든간 자기가 힘들게 만든 소스를 여러 사람들과 조건없이 공유하는 개발자분들께 감사드립니다.

오늘 살펴볼 앱은요.
앱을 시작하면 위에서 녹색 공이 떨어집니다.
그리고 화면을 Drag 하면 선이 그려집니다.
녹색 공이 그 선에 떨어지면 튀어 나가게 돼 있습니다.

일단 소스 부터 볼까요?

local physics = require ("physics")
physics.start(true)
physics.setGravity(0, 1)
 
-- Ball rolls on Line
 
local ball = display.newCircle( 0, 0, 25)
ball:setFillColor(0, 255, 0)
ball.x = display.contentWidth/2
ball.y = 0
 
physics.addBody(ball, {bounce=1.1, radius = 25, friction=1})

-- Draw a line

local i = 1
local tempLine
local ractgangle_hit = {}
local prevX , prevY
local function runTouch(event)
  if(event.phase=="began") then
    if(tempLine==nil) then
      tempLine=display.newLine(event.x, event.y, event.x, event.y)
 
-- Random Colors for line
 
      r = math.random (0, 255)
      g = math.random ( 0, 255)
      b = math.random (0, 255 )
      tempLine:setColor(r,g, b)
 
      prevX = event.x
      prevY = event.y
    end
  elseif(event.phase=="moved") then
    tempLine:append(event.x,event.y-2)
    tempLine.width=tempLine.width+0.8
    ractgangle_hit[i] = display.newLine(prevX, prevY, event.x, event.y)
    ractgangle_hit[i]:setColor(r,g, b)
    ractgangle_hit[i].width = 5
 
-- Creates physic joints for line (Turn on draw mode to see the effects)
 
    local Width = ractgangle_hit[i].width * 0.6
    local Height = ractgangle_hit[i].height * 0.2
 
-- Physic body for the line shape
 
    local lineShape = {-Width,-Height,Width,-Height,Width,Height,-Width,Height}
 
    physics.addBody(ractgangle_hit[i], "static", { bounce = -1, density=0.3, friction=0.7, shape = lineShape})
    prevX = event.x
    prevY = event.y
    i = i + 1
  elseif(event.phase=="ended") then
    tempLine.parent.remove(tempLine)
    tempLine=nil
  end
end

Runtime:addEventListener("touch", runTouch)

우선 Physics를 이용하기 위해 require하고 start 시킨 후 중력은 아래로 1을 주었습니다.
기본이 9.8인데 1로 주었으니 풍선처럼 아주 가볍게 그리고 천천히 떨어지겠네요.
그리고 25픽셀이 반지름인 녹색공을 만들고 위치는 상단 가운데로 주었습니다.
이 ball에 addBody를 했구요. 그 body 크기는 반지름이 25픽셀로 녹색 원과 크기가 같고 bounce와 friction 도 조금 주었습니다.

여기까지만 하면 녹색볼이 위에서 아래로 천천히 떨어질 겁니다.
정말 Corona SDK의 물리엔진은 아주 강력합니다.

그 다음은 여러 변수들을 선언했습니다.
맨 밑에 Runtime touch리스너가 있고 이 리스너는 runTouch함수를 호출합니다.
그러니까 앱이 시작할 때부터 이 runTouch 함수는 실행 될 겁니다.

이제 그 함수를 볼까요?
일단 touch가 시작되고 tempLine이 nil 일 경우 tempLine에 현재 위치로 라인을 그립니다.
시작점과 끝점 모두 현재 위치가 됩니다.
그리고 r,g,b 변수에 랜덤한 rgb값을 넣고 아까 그린 선에 그 랜덤한 색을 넣습니다.
그리고 현재의 위치를 prevX,prevY에 넣습니다.
그 다음에 touch 가 moved일 경우 즉 드래그가 진행중일 경우에는요.
선을 더 합니다. (append) 그리고 선을 그리는데요. 시작점은 아까 began 일때의 위치가 되고 끝위치는 현재가 됩니다.

다음엔 이 그려진 Line에 physics.addBody를 할 수 있도록 Width,Height공간만큼 구간을 잡구요 이것을 addBody에 shape로 집어 넣습니다.
그리고 다시 prevX,prevY에 현재 위치를 넣습니다.

드래그하던 손을 떼면 ended 부분이 실행 되는데요.
tempLine을 remove하고 nil을 만듭니다.

자 여기 까지가 소스코드를 모두 분석한 내용입니다.

이제 앱을 실행하면 녹색 공이 떨어지고 여러분이 그은 선에 그 공이 맞으면 튀게 될 겁니다.

이 앱은 그 자체로 완성된 게임은 아니지만 그 기본 로직을 활용하면 많은 곳에서 유용하게 사용할 수 있을 것 같습니다.

저작자 표시 비영리 동일 조건 변경 허락
신고


오랜만에 코로나쪽을 공부해 보겠습니다.
얼마전에 기능이 추가 된 화면전환 효과 Story Board 샘플 소스를 분석해 볼께요.

Story Board API는 예전에 살펴 본 바 있습니다.

이 샘플 예제는 Corona SDK/interface 안에 storyboard 라는 폴더에 있습니다.

우선 main.lua 소스 파일 입니다.

display.setStatusBar( display.HiddenStatusBar )
-- require controller module
local storyboard = require "storyboard"
local widget = require "widget"
-- load first scene
storyboard.gotoScene( "scene1", "fade", 400 )
-- Display objects added below will not respond to storyboard transitions
-- table to setup tabBar buttons
local tabButtons = {
    { label="First", up="icon1.png", down="icon1-down.png", width = 32, height = 32, selected=true },
    { label="Second", up="icon2.png", down="icon2-down.png", width = 32, height = 32 },
}
-- create the actual tabBar widget
local tabBar = widget.newTabBar{
    top = display.contentHeight - 50,    -- 50 is default height for tabBar widget
    buttons = tabButtons
}
main.lua에서는 딱 두 부분으로 나뉘어 져 있습니다.
첫번째는 Story Board 를 이용한 부분이고 두번째는 Tab Button을 이용한 부분입니다.
이 두 기능을 이용하기 위해 storyboard와 widget 을 require 했습니다.
그리고 storyboard 부분은 gotoScene 을 이용해서 화면 전환 한 것 밖에는 없습니다.
scene1.lua 로 가는데 0.4초동안 fade 효과를 이용해서 화면 전환을 합니다.
화면 전환 효과는 fade를 포함해서 모두 20개의 효과가 있습니다.
내용은 여기를 클릭하시면 보실 수 있습니다.
두번째는 탭바를 생성하는 건데요. 일단 tabButtons 테이블 안에 탭버튼 두개의 정보들을 담아 놓습니다. 그리고 widget.newTabBar로 Tabbar를 만드는데요. 중요한건 이 탭 바는 화면전환해도 계속 남아 있는 다는 겁니다.

Story Board API 에서는 scene 의 life cycle 메소드가 4개 있습니다.
createScene,enterScene,exitScene,destroyScene
그리고 이 메소드 안에서 생성한 object 들만 화면전환시 적용이 됩니다.
director.lua 클래스는 localGroup을 생성해서 그 그룹에 insert 한 객체들만 적용이 됐었습니다.
이번에 코로나에서 제공한 화면전환 API는 이보다 더 세밀한 화면 전환 단계를 이용할 수 있게 만들었습니다.
이 4개 메소드가 적용된 예는 Scene1 설명할 때 나올겁니다.

일단 여기서 만드는 tabbar 는 화면전환시 적용되지 않고 처음부터 끝까지 계속 남아있을 겁니다.
(그리고 tab bar 관련된 글을 한번도 안 다뤄봤는데요. 이 기능은 나중에 자세히 다뤄볼께요.)


-- storyboard.gotoScene( "scene1", "fade", 400 )
이렇게 scene1.lua로 넘어가는 부분을 주석 걸어놓고 실행하면 위 화면이 나옵니다.

이게 온전히 main.lua만을 실행했을 때 나오는 화면입니다.
그 다음에 scene1.lua를 보겠습니다.

local storyboard = require( "storyboard" )
local scene = storyboard.newScene()

---------------------------------------------------------------------------------
-- BEGINNING OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

local image, text1, text2, text3, memTimer

-- Touch event listener for background image
local function onSceneTouch( self, event )
    if event.phase == "began" then
        storyboard.gotoScene( "scene2", "slideLeft", 800  )
        return true
    end
end

-- Called when the scene's view does not exist:
function scene:createScene( event )
    local screenGroup = self.view
   
    image = display.newImage( "bg.jpg" )
    screenGroup:insert( image )
   
    image.touch = onSceneTouch
   
    text1 = display.newText( "Scene 1", 0, 0, native.systemFontBold, 24 )
    text1:setTextColor( 255 )
    text1:setReferencePoint( display.CenterReferencePoint )
    text1.x, text1.y = display.contentWidth * 0.5, 50
    screenGroup:insert( text1 )
   
    text2 = display.newText( "MemUsage: ", 0, 0, native.systemFont, 16 )
    text2:setTextColor( 255 )
    text2:setReferencePoint( display.CenterReferencePoint )
    text2.x, text2.y = display.contentWidth * 0.5, display.contentHeight * 0.5
    screenGroup:insert( text2 )
   
    text3 = display.newText( "Touch to continue.", 0, 0, native.systemFontBold, 18 )
    text3:setTextColor( 255 ); text3.isVisible = false
    text3:setReferencePoint( display.CenterReferencePoint )
    text3.x, text3.y = display.contentWidth * 0.5, display.contentHeight - 100
    screenGroup:insert( text3 )
   
    print( "\n1: createScene event")
end

-- Called immediately after scene has moved onscreen:
function scene:enterScene( event )
   
    print( "1: enterScene event" )
   
    -- remove previous scene's view
    storyboard.purgeScene( "scene4" )
   
    -- Update Lua memory text display
    local showMem = function()
        image:addEventListener( "touch", image )
        text3.isVisible = true
        text2.text = text2.text .. collectgarbage("count")/1000 .. "MB"
        text2.x = display.contentWidth * 0.5
    end
    memTimer = timer.performWithDelay( 1000, showMem, 1 )
end

-- Called when scene is about to move offscreen:
function scene:exitScene( event )
   
    print( "1: exitScene event" )
   
    -- remove touch listener for image
    image:removeEventListener( "touch", image )
   
    -- cancel timer
    timer.cancel( memTimer ); memTimer = nil;
   
    -- reset label text
    text2.text = "MemUsage: "
end


-- Called prior to the removal of scene's "view" (display group)
function scene:destroyScene( event )
   
    print( "((destroying scene 1's view))" )
end

---------------------------------------------------------------------------------
-- END OF YOUR IMPLEMENTATION
---------------------------------------------------------------------------------

-- "createScene" event is dispatched if scene's view does not exist
scene:addEventListener( "createScene", scene )

-- "enterScene" event is dispatched whenever scene transition has finished
scene:addEventListener( "enterScene", scene )

-- "exitScene" event is dispatched before next scene's transition begins
scene:addEventListener( "exitScene", scene )

-- "destroyScene" event is dispatched before view is unloaded, which can be
-- automatically unloaded in low memory situations, or explicitly via a call to
-- storyboard.purgeScene() or storyboard.removeScene().
scene:addEventListener( "destroyScene", scene )

---------------------------------------------------------------------------------

return scene

이 scene1.lua 는 onSceneTouch 함수와 StoryBoard 에 소속된 4개 함수로 구성된 간단한 구조입니다

이 함수 호출은 Storyboard 4인방은 scene:addEventListener 를 사용해서 호출이 되고 onSceneTouch 는 배경이미지에 리스너를 달아서 화면을 터치하면 scene2로 이동하게끔 하는 겁니다.

4인방 메소드에서는 self.view를 이용해서 객체들을 그룹화 하면서 화면에 보여줄 수 있도록 합니다.

처음 createScene 에서는 배경이미지와 텍스트 등 기본 display 요소들을 정의하고 표시합니다.

enterScene 에서는  0.1초 후에 실행되는 타이머를 달아서 showMem 함수를 실행하도록 했습니다.
이 함수는 배경이미지를 터치하면 scene2.lua로 가도록 하는 onSceneTouch 함수를 Call 하는 리스너를 달았고 현재 메모리를 모여주는 로직이 있습니다.
그 이전에 storyboard.purgeScene( "scene4" ) 이 있는데요. purgeScene 은 해당 scene(여기서는 scene4) 에 있는 객체들을 모두 unload 합니다. purge말고 remove를 사용하면 unload 될 뿐만 아니라 메모리에서도 사라집니다.
이 기능은 메모리를 절약하거나 할 때 사용하는 건데 scene1에서 scene2로 넘어 갈때 터미널에 뭐가 찍히는지 보면 잘 알 수 있습니다.

보시다시피 scene1이 시작되면 scene4 의 destroy 가 호출 되서 그 안의 내용이 실행 됩니다.
scene1에서 scene2로 넘어갈 때 destroyScene 함수가 호출되는게 아니라 해당 scene이 unload 될 때 destroyScene 이 호출 됩니다.
그리고 purgeScene 하게 되면 해당 scene 의 destroyScene 이 호출 됩니다.
removeScene 하게 되면 purgeScene 한 후 메모리에서 없애므로 자연히 destroyScene 이 호출 됩니다.

exitScene 은 다음 화면으로 넘어갈 때 실행 되는데 여기서는 모든 리스너를 remove 하고 timer 도 cancel 합니다.

여기까지 하면 실행되는 화면이 아래와 같습니다.


나머지 scene2,scene3,scene4 도 이 scene1과 구조가 동일 합니다.

그런데 여기서 드는 한가지 의문점은 Tab Bar 는 화면 전환에 상관 없이 계속 있는데요.

특정 화면에서 이 TabBar 를 안 보이게 하거나 없애거나 아니면 어떻게 콘트롤을 할 수 있을까요?

그렇게 하려면 main.lua 에서 tabBar 를 local 이 아니라 glabal 변수로 선언하면 됩니다. 그냥 local 이란 글자만 지우세요.

그리고 scene3.lua 맨 밑에 (return scene 바로 윗줄에) tabBar.isVisible = false; 를 넣어보시고. scene4.lua에는 tabBar.isVisible = true 를 넣어보세요.

그러면 이렇게 scene3에는 탭바가 없어지고 scene4에는 나타나게 됩니다.
이런식으로 tabbar를 제어하시면 됩니다.


저작자 표시 비영리 동일 조건 변경 허락
신고


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 data
end
첫줄은 마찬가지로 모듈로 사용되기 위해 필요한 라인이구요. 그 다음에 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한 소스코드로 제가 공부를 많이 했고 또 여러분에게도 소개해 드리고 해서 이렇게 소개 드립니다.

이 세상을 경쟁으로 생각하기 보다는 이렇게 서로서로 돕자는 마음으로 하면 더 좋을 것 같아서.. 홈페이지 들어가주고 내용 봐주고 필요하면 이용해 주고 힘 주는 댓글 남겨주고 하는게 우리들이 도움 받은거 은혜 갚는거 같애서... ^^

하여간 여러분들도 즐팅 하세요....
저작자 표시 비영리 동일 조건 변경 허락
신고
이전 1 2 다음