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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

Handling Corona System Events

2012. 5. 22. 11:06 | Posted by 솔웅


반응형

Handling Corona System Events

어플리케이션을 개발 할 때 유저가 다양하게 여러분의 앱을 다루게 되는데 그에 대해 어떤 동작을 할 수 있도록 기능을 구현하는것은 아주 중요합니다. 또한 유저가 의도했던 안했던 다양한 system 에 대해 반응하는 것도 아주 중요합니다.


예를 들어 만약에 여러분의 앱을 사용하고 있는 중간에 유저에게 전화가 걸려온다던가 혹은 유저가 home 버튼을 누를 수도 있겠죠, 이 경우엔 여러분의 앱은 background에서 작동하고 있을겁니다. suspended나 paused 상태가 되서 말이죠.). 이렇게 suspended 된 상황에서 유저가 다시 앱으로 돌아왔을 때나 앱을 완전히 exit 했을 때도 발생할 수 있습니다.


코로나에서는 이러한 모든 것들을 system 이벤트에서 처리합니다. 오늘은 이 system 이벤트에 대해서 다룰겁니다.





Event Listener

여러분의 코로나 앱에서 system event 모니터링을 시작하기에 앞서 listener 함수를 set up 해야 합니다. 그리고 system 이벤트 리스너 를 add 하셔야 합니다. 만약 여러분이 코로나의 events 와 친하지 않으시다면 The Corona Event Model Explained document 를 먼저 보시고 다음 진도를 나가시라고 권해드립니다.

아래 코드는 event listener를 어떻게 set up 하는지를 보여드립니다. 그리고 글로벌 Runtime object를 add 합니다.  (이 object는 유일하게 system 이벤트를 listen 할 수 있는 object 입니다.)

local function onSystemEvent( event )

    -- Test for different event types here

end
Runtime:addEventListener( "system", onSystemEvent )

applicationStart


local function onSystemEvent( event )
    if event.type == "applicationStart" then

        do_something()

    end
end


위의 샘플코드는 여러분의 system event listener에서 applicationStart 이벤트를 어떻게 테스트 하는지 보여드립니다.

main.lua 가 가동되고 앱이 시작될 때 이 applicationStart 이벤트를 받습니다.

applicationExit

local function onSystemEvent( event )
    if event.type == "applicationExit" then

        do_something()

    end
end


applicationexit 시스템 이벤트는 앱이 끝나기 바로 직전에 dispatch됩니다. (suspending 이 아니라 완전히 끝날 때 입니다.) 이것은 유저가 끝낼 수도 있고 어떤 이유에서인가 시스템이 끝낼수도 있습니다. (메모리 부족 등)

이 이벤트는 앱이 끝나기 직전에 데이터를 저장하고 디비 연결을 끊는 일을 해야 할 때 많이 사용됩니다.

applicationSuspend

local function onSystemEvent( event )
    if event.type == "applicationSuspend" then

        do_something()

    end
end


앱이 실행중에 전화가 온다던가 아니면 유저가 switch out 시킨다던가 또는 유저가 power 버튼을 눌러서 디바이스를 sleep 상태로 돌릴때 여러분의 앱은 suspend 상태로 됩니다. (끝나지는 않은 상태입니다.)

suspended state로 가기 직전에 applicationSuspend 이벤트가 dispatch 됩니다. 이 이벤트를 이용해서 게임을 pause 시키는 일 등을 하실 수 있습니다. (그러면 다시 앱으로 돌아올 때 유저는 이 pause 화면을 볼 수 있을 있겠죠.) 그리고 필요하면 시간을 mark 할 수도 있고 timer나 transision 들을 pause나 stop 시킬 수도 있습니다.

applicationResume

local function onSystemEvent( event )
    if event.type == "applicationResume" then

        do_something()

    end
end


suspended 되기 전에 무엇인가를 할 수 있듯이 앱이 다시 돌아올 때도 무엇인가를 할 수 있습니다. 이렇게 앱이 다시 돌아오자마자 applicationResume 이벤트가 dispatch 됩니다.

Putting it all together

local function onSystemEvent( event )
    if event.type == "applicationStart" then

        -- this block executed when application launches (after main.lua)

    elseif event.type == "applicationExit" then

        -- this block executed just prior to the app quitting
        -- OS closes least recently used app, user explicitly quits, etc.

    elseif event.type == "applicationSuspend" then

        -- this block executed when app goes into "suspend" state
        -- e.g. user receives phone call, presses home button, etc.

    elseif event.type == "applicationResume" then

        -- this block executed when app resumes from "suspend" state
        -- e.g. user goes back into app (while it is still running in bg)

    end
end
Runtime:addEventListener( "system", onSystemEvent )


위 예제는 system event listener 의 기본 골간입니다. 이 예제를 탬플릿으로 사용하셔서 이용하시면 편하실 겁니다. 저 안에 어떤 동작을 넣을 지는 여러분 필요에 따라 구현하셔야겠죠.

이 기능들이 모든 앱에서 반드시 사용되어야 하는 것은 아니지만 많은 경우에 이 기능을 이용하면 아주 훌륭한 앱이 되도록 구현할 수 있을 겁니다.

여러분의 앱을 release 하시기 전에 내 앱이 suspended mode로 됐을 때 내 앱은 어떻게 해야 할까? 다시 돌아올 때는 무엇을 할까? 를 한번 생각 해 보세요. 여러분 앱이 훨씬 수준있는 앱이 될 수 있을 겁니다.



반응형


반응형

Scene Overlays and Parameter Passing

Posted by Jonathan Beebe

Storyboard API에서 가장 요구되어지는 두가지 사항들은 화면 전환하면서 custom data를 전달할 수 있는 기능과 한 scene 이 다른 scnen 위에 표시되는 pop up 기능입니다.

많은 분들이 그런 기능을 기다려왔는데 이제 더 이상 기다리지 않으셔도 됐습니다. Daily build 2012.797에 바로 이 두가지 기능이 모두 추가 됐습니다. 이름은 parameter passing과 scene overlays 입니다. (build 2012.785 에는 이미 parameter passing이 있었습니다.

이 기능들과 관련된 튜토리얼은 storyboard.gotoScene()의 parameter passing 과 storyboard.showOverlay()의 scene overlays 에서 보실 수 있습니다. 이렇게 튜토리얼이 있지만 여러분들의 이해를 돕기 위해 이 글에서 간단히 설명 드리겠습니다.

Parameter Passing

parameter passing은 한 scene에서 다음 scene으로 넘어가면서 custom data (하나의 변수 일 수도 있고 테이블-배열- 형식일 수도 있습니다.)를 쉽게 전달할 수 있도록 해 줍니다.

이것을 사용하게 하려면 storyboard.gotoScene() 의 신택스를 바꿔야만 했습니다. (걱정마세요. 이전 신택스도 계속 사용할 수 있습니다. 그러니 지금 진행하시는 프로젝트에는 아마 문제가 없습니다.) 아래 이 parameter passing을 사용하기 위한 새로운 신택스 예제가 있습니다.

local options =
{
    effect = "fade",
    time = 400,
    params =
    {
        var1 = "custom data",
        sample_var = 123,
    }
}
storyboard.gotoScene( "game_scene", options )


가장 크게 다른 점은 storyboard.gotoScene() 함수의 두번째 인수입니다. 이 인수가 optional key들과 optional table로 이루어져 있습니다. 위 예제에서는 모든 가능한 옵션들을 다 보여 줍니다. (effect,time,params). 새로운 부분은 params option 입니다. 이것은 변수가 될 수도 있고 테이블이 될 수도 있습니다.

이렇게 custom data를 pass 하면 그 다음 화면에서는 createScene이나 willEnterScene 그리고 enterScene event 리스너 등에서 event.params 로 이 데이터에 접근할 수 있습니다.

아래에는 game_scene.lua의 enterFrame에서 custom data를 access 하는 예제를 보여드리고 있습니다.

function scene:enterScene( event )
    local params = event.params

    print( params.var1 )    -- "custom data"
    print( params.sample_var )  -- 123
end





Scene Overlays

scene overlay 는 현재의 scene 위에 overlay 해서 다른 scene을 보여 주는 것입니다. 화면전환 효과를 사용해서 이 다른 scene이 나오지만 그 이전 scene이 없어지지 않고 그 자리에 그대로 있게 됩니다. 이 두 화면(scene)에는 두가지 이벤트가 추가 됩니다. 하나는 scene overlay가 display 됐을 때 dispatch 되는 이벤트하고요 그렇지 않을 때 dispatch 되는 이벤트입니다.

storyboard.showOverlay()

View Documentation

이 함수는 overlay를 보여주는 역할을 합니다. overlay 하기 위해 만든 scene은 다른 scnen과 같은 구조를 가졌을 겁니다. 어느 scene이든지 overlay 하고 싶으면 하시면 됩니다. 사실 overlay 가 display 되면 overlayBegin 이벤트가 현재 active 한 non-overlay scene에 dispatch 되게 됩니다. overlay 가 remove 되면 overlayEnded 가 dispatch 되죠.

아래 예제를 보세요. (scene1.lua에 있는 코드라고 생각해 봅시다.)

local options =
{
    effect = "fade",
    time = 400,
    params = { sample_var=456 }
}
storyboard.showOverlay( "overlay_scene", options )


이 scene1.lua의 어느 부분엔가 여러분은 옵션으로 overlayBegan과 overlayEnded 이벤트를 추가할 수 있습니다.

function scene:overlayBegan( event )
    print( "The overlay scene is showing: " .. event.sceneName )
    print( "We get custom params too! " .. event.params.sample_var )
end
scene:addEventListener( "overlayBegan" )

--

function scene:overlayEnded( event )
    print( "The following overlay scene was removed: " .. event.sceneName )
end
scene:addEventListener( "overlayEnded" )


위 리스너들에서 유심히 보셔야 될 것들이 두가지 있습니다. 첫번째는 일반적인 화면 전환에서와 같이 파라미터(custom data)들을  overlays scene에 params option을 사용해서  pass 할 수 있습니다. 그 custom data는 위에서 보신 예대로 리스너 함수에서 event.params 로 접근할 수 있구요.

두번째로 보셔야 될 부분은 event.sceneName 변수 입니다. 이것은 overlay scene의 이름을 나타냅니다. 이 변수는 overlayBegan과 overlayEnded 이벤트에서만 접근 가능합니다.

좀 더 자세한 사용법과 신택스는 storyboard.showOverlay() 문서를 봐 주세요.

storyboard.hideOverlay()

View Documentation

이 함수는 이름만 봐서도 어떤 일을 하는지 알 수 있겠죠? 그런데 한가지 알아두셔야 할 중요한 사항이 있습니다. 이것은 현재 보여지는 overlay를 hide 해줍니다. 하지만 그 이상의 무엇인가가 또 있습니다.

디폴트로 overlay scene은 완전히 remove 될 겁니다. (purge되고 메모리에서 그 모듈이 unload 될 겁니다. 현재 load된 scene이 일반적인 scene이고 overlay로 사용되고 있지 않다면요) 옵션으로 이 함수로 overlay scene을 간단하게 purge 시킬 수도 있습니다. 이것은 조만간 이 화면을 다시 사용하려고 할 때 유용하게 이용될 겁니다.

storyboard.showOverlay() 함수가 있는데요. 이 함수에서 storyboard.gotoScene()에서 사용할 수 있는 화면전환 효과도 사용할 수 있습니다.

좀 더 자세한 사용법과 신택스는 storyboard.hideOverlay() 문서를 참조하세요.

Important Notes

overlay scene과 관련해서 몇가지 알아 두셔야 할 점들이 있습니다.

첫번쨰로 한번에 단 한개의 overlay 화면만이 보여질 수 있습니다. 여러분이 storyboard.gotoScene(), storyboard.removeAll(), storyboard.purgeAll() 을 call 하거나 다른 overlay scene을 보여주려고 시도한다면 현재의 overlay scene은 remove 될 겁니다. 이 overlay scene은 그 이전에 있던 오리지널 scene에 종속돼 있는 것입니다.

또 다른 중요한 사항은 overlay scene들을 어떻게 call 하느냐에 대해 주의하셔야 합니다. 예를 들어 시간을 좀 delay 시킨 후 overlay를 보여준다면 화면전환 효과는 이 time delay가 끝날 때까지 일어나지 않을거라는 겁니다. 그리고 오리지널 scene에서 display.gotoScene()을 하기전에 이 overlay를 remove 해 주셔야 합니다.



반응형


반응형

Working with Time: Delays and Counting 

Posted by Jonathan Beebe

time 을 사용하는 앱이나 게임은 무수히 많습니다. 몇 초 후에 어떤 일이 일어나게 하던지 유저가 얼마 안에 특정 일을 끝내게 해야 한다던지 같은 시간을 컨트롤 해야 할 일들은 많습니다.

Corona SDK에서는 이러한 time을 빠르고 쉽게 콘트롤 함으로서 게임이나 앱의 개발에 도움을 줄 수 있는 가이드를 제공하고 있습니다.

이 튜토리얼에서는 코로나의 아주 유용한 timer.performWithDelay() 함수에 대해 소개하려고 합니다. 그리고 이 함수를 가지고 여러분의 앱에서 간단하게 활용할 수 있는 방법도 알려 드리겠습니다.

Time Delays


timer.performWithDelay() 는 특정 시간 이후에 특정 동작을 행할 수 있도록 해 줍니다. 이 함수 이름만 봐도 이 함수가 뭘 하는지 쉽게 짐작하실 수 있을 겁니다.

아래 신택스가 있습니다.

timer.performWithDelay( delay, listener [, iterations] )


여러분이 이 함수를 call 했을 때 여러분은 얼마동안 delay 하는지에 대해 정해 주시면 됩니다. (단위는 밀리세컨드입니다.) 그리고 그 지정된 시간이 다 됐을 때 불리워질 함수를 넣으시면 됩니다. 옵션으로 이 작업을 몇번이나 반복해서 수행하는지도 iterarions 에 숫자를 넣어서 정해 주시면 됩니다. 디폴트는 1 입니다.

What are iterations?

timer iteration은 이 timer.performWithDelay 를 몇번 수행할지 정해 주시는 부분입니다. 이 숫자를 0으로 지정하시면 이 timer를 remove 하거나 앱을 종료할 때까지 무한으로 반복합니다.


다음에는 timer.performWithDelay() 함수와 함께 anonymous 함수를 어떻게 사용할 수 있는지에 대해 알려드리겠습니다.





Using a function

아래 예제에서는 1초 후에 hello_world() 함수를 call 하는 것을 단 1회 시작하도록 코딩 했습니다.

local function hello_world()
  print( "Hello world!" )
end

timer.performWithDelay( 1000, hello_world, 1 )


hello_world vs. hello_world()

계속 진행하기 이전에 많은 개발자 분들이 혼동하시는 부분에 대해 명확히 밝히고 넘어가겠습니다. 위 예제에서 timer.performWidhDelay() 함수의 두번째 인수로 hello_world() 를 사용하지 않고 hello_world 를 사용한 점입니다.

그 이유는 이것은 존재하는 함수를 단지 참조하라고 알려주는 것이라서 입니다. 그래서 우리는 그 함수의 변수명(variable name)을 사용하면 되는 겁니다. 만약에 여기에 hello_world()를 사용한다면 이 함수를 실제로 call 하게 되는 겁니다. 그러면 hello_world() 함수에서 return 되는 값에 따라 그 다음 동작이 일어날 수도 있고 안 일어 날 수도 있습니다. 기본적으로 함수의 디폴트 return 은 nol 이기 때문에 그렇게 하면 문제가 발생할 수가 있습니다. 그래서 함수명이 아니라 변수명을 사용하시는 겁니다.


Using anonymous functions

timer.performWithDelay() 함수의 두번째 인수가 함수가 들어가는데요. 여기에 anonymous 함수를 사용할 수도 있습니다. 그냥 이 부분에 함수를 구현하실 수 있다는 애기입니다. 아래 예제에서 보시는 대로 두번째 인수에 이 함수를 구현하시면 됩니다.

timer.performWithDelay( 1000, function()
  print( "Hello world!" )
end, 1 )


물론 이것을 1라인으로 만들 수도 있지만 이것이 함수인것을 좀 더 이해하기 쉽도록 하기 위해 저렇게 줄을 나눴습니다.

이 anonymous 함수를 사용하실 때 알아두셔야 할 점은요. 쉼표 (,) 사이에 전체 함수 (function() 에서 end 까지)가 들어가야 한다는 것입니다. 그래야지 timer.performWithDelay()는 이 인수가 valid 하다고 판단합니다.

좀 더 자세한 정보는 1년전에 Eric Wing이 작성한 using closures as function callbacks 튜토리얼을 보시면 아실 수 있을 겁니다.

Passing Arguments to the Listener

timer.performWithDelay()를 사용하면서 많이 나오는 질문이 호출하는 함수에 어떤 인자를 전달할 수 있느냐 입니다.

이것을 구현하는 방법은 아래와 같이 하시면 됩니다.

local function hello_world( arg1, arg2 )
  print( "Hello world!", arg1, arg2 )
end

timer.performWithDelay( 1000, function()
    hello_world( "first", "second" )
end, 1 )


어떻게 하는지 아시겠죠? timer.performWithDelay() 안에서 anonymous 함수를 사용해서 그 함수에서 외부 함수를 인수와 함께 호출했습니다.
좀 헛갈리시나요? 몇번 해 보시면 금방 익숙해 지실 겁니다.

Other functions

timer 함수는 timerId를 return 합니다. 그래서 다른 함수가 이 timerId를 가지고 어떤 동작을 하도록 컨트롤 할 수 있습니다. timer 함수에는 timer.pause, timer.cancel(), timer.resume() 등의 메소드가 있습니다. 이 메소드들은 이름만 봐도 어떤 일을 하는지 알 수 있겠죠? 자세한 내용은 timer API reference page 를 참조하세요.

Counting Time


앞서 얘기했듯이 앱에서 이 time을 컨트롤 해야 할 상황이 아주 많습니다. 예를 들어 게임을 시작하는 시간과 끝나는 시간을 한번 catch 해 봅시다.

이 게임을 시작하는 부분에 아래 코드를 넣습니다.

local markTime = os.time()

os.time()
이 함수는 현재의 시간을 return 합니다. (위와 같이 아무런 인자 없이 call 했을 경우) 이외에도 더 많은 기능이 있는데요. 그 기능은 여기를 참조하세요.



사용자가 game over 상태에 이르렀을 때 아래 코드로 게임 시간이 얼마나 길었는지를 얻어낼 수 있습니다.

local timePassed = os.time() - markTime


처음 시작했을 때의 시간이 markTime 이니까 끝날 때의 현재시간 (os.time()) 에서 markTime을 빼면 게임 시간이 나오겠죠? 여기서 timePassed 는 초단위로 값을 갖고 있습니다. 분단위로 표시하고 싶으면 이 값을 60으로 나누면 되겠죠. 밀리세컨드로 하려면 1000을 곱하면 되겠구요. 밀리세컨드로 곧바로 값을 받을 수도 있습니다. system.getTimer()를 이용하시면 됩니다. 이용하시는 방법은 위와 똑 같구요.

Time’s up!

이제 여러분은 timer.performWithDelay() 함수를 사용하시는데 프로가 되셨습니다. 그리고 여러분의 앱에서 어떻게 time을 관리해야 하는지에 대해서도 이해하셨을 겁니다.

time 과 관련된 다른 함수들들을 더 이해하고 싶으시면 os.clock(), os.date(), os.difftime(), system.getTimer() 함수들을 보시면 더욱 더 유용하게 여러분의 앱에서 time을 컨트롤 하실 수 있을 겁니다.

반응형


반응형

Director to Storyboard “Transition” Guide -2-


Switching Scenes

디렉터 클래스나 스토리보드 모두 화면 전환의 목적은 같습니다. 하지만 그 화면의 이면에서는 어떤 일들이 벌어지는지는 완전히 다릅니다. 그 다른 부분 중 어떤 것들은 여러분들이 컨트롤 해야 될 필요가 있고 어떤 부분들은 별로 신경을 쓰지 않아도 되는 부분이 있습니다.

Director Scene Transitions


아래 예제는 디렉터 클래스를 사용해서 scene1에서 scene2 로 전환하는 방법을 보여 드립니다.

-- somewhere in scene1.lua...
director:changeScene( "scene2", "moveFromRight" )


위 코드대로 하면 scene1은 움직여서 보여지지 않고 scene2 가 화면에 나타날 겁니다. moveFrimRIght로 해서 화면은 오른쪽에서 왼쪽으로 옮겨질 겁니다. (이런 화면 전환 효과를 사용하지 않으려면 changeScene의 두번째 파라미터를 없애면 됩니다.) 아래 내용들은 director:changeScene()이 call 됐을 때 화면 뒤에서는 어떤 일들이 일어나는지를 정리한 내용입니다.
   
    - 디렉터가 scene2.lua를 로드하고 제 위치에 위치 시킵니다.
    - 두 scene 모두 최종 목적지로 transition 됩니다.
    - 디렉터는 scene1의 display group이 clean()메소드를 가지고 있는지 체크하고 만약 있으면 메소드를 call 합니다.
    - scene1의 display group이 remove되고 scene1이 unload 됩니다.


아주 간단하고 직관적입니다. 만약 여러분이 디렉터 클래스 사용자라면 위에 정리한 내용이 아주 익숙할 겁니다. 이제 스토리보드에서는 같은 화면전환이면서 그 이면에 어떤 다른 일들이 일어나는지 살펴 보겠습니다.





Storyboard Transitions

아래 스토리보드 API 를 사용해서 scene1에서 scene2로 화면 전환하는 예제입니다.

-- somewhere in scene1.lua...
storyboard.gotoScene( "scene2", "slideLeft" )


아래는 위에서 처럼 gotoScene이 call 됐을 때 화면 뒤에서 어떤 일이 일어나는지를 정리한 내용입니다.

    - scene1 : "exitScene" 이벤트가 dispatch 됩니다.
    - scene2 : 만약 view display group 이 없으면 새로 생성해서 시작 위치에 위치 시킵니다. (시작 위치는 화면전환 효과에 따라 달라집니다.) 그리고 나서 createScene 이벤트가 createScene 이벤트가 dispatch 됩니다.
    - scene2 : 만약 view 가 이미 존재한다면 그것을 시작 위치에 위치 시킵니다.
    - scene2 : willEnterScene 이벤트가 dispatch 됩니다. scene1과 scene2가 제 위치로 이동합니다.
    - scene1 : didExitScene 이벤트가 dispatch 됩니다.
    - scene2 : enterScene 이벤트가 dispatch 됩니다.


아주 많은 것들이 진행 되는 것 같네요. 그것들이 모두 유용한 것들입니다.

위에서 ~event가 dispatch 된다는 것은 그 이벤트를 이용해서 listener 함수를 사용하고 그 안에서 여러분이 어떤 작업을 할 수 있다는 얘기입니다. (뭐든지 필요한 기능을 구현하시면 됩니다.) 너무 복잡하게 느끼실 필요가 없습니다. 스토리보드의 다양한 이벤트를 이용해서 무엇을 구현한다는 것은 필수가 아닙니다. 여러분이 필요한 부분만 사용하시면 됩니다. 만약 여러분이 willEnterScene 이벤트를 작성하지 않았다면 이 이벤트는 dispatch 된 다음게 그대로 cancel 될 겁니다.

Which one should you use?

이 글을 쓴 목적은 디렉터 클래스 대신 스토리보드 API를 사용하라거나 반대로 스토리보드 대신 디렉터 클래스를 사용하라고 설득하기 위해 쓴 것이 아닙니다. 이 글의 목적은 디렉터 클래스의 주요 기능들을 스토리보드 API를 사용할 때 어떻게 사용해야 하는지를 보여드리기 위해 쓴 글입니다.

스토리보드 API는 코로나 SDK에서 화면 관리하는 것을 지원하려고 만든 코로나 SDK의 공식 API 입니다. 그렇다고 해서 여러분이 필요한데도 디렉터 클래스를 사용하지 말고 스토리보드 API를 사용하라는 의미가 아닙니다. 어떤 분들은 간단하게 화면 전환을 컨트롤 할 수 있는 디렉터 클래스를 선호할 것입니다. 그리고 디렉터 클래스는 오픈 소스 스크립트 입니다. 그리고 다른 관점에서 보면 스토리보드를 이용해서 코로나 이벤트 모델을 사용함으로서 좀 더 유연하고 다양한 기능을 사용하기 원하는 분들은 스토리보드 API를 선호할 겁니다.

어느것을 선택하시든 여러분들의 프로젝트에 더 적합한 수단을 사용하시면 되고 이 글을 통해 그 사용 법을 익힐 수 있었기를 바랍니다. 만약 여러분이 디렉터 클래스를 사용하시다가 스토리보드를 사용하시는데 어려운 부분이 있었다면 이 글을 통해서 그 혼란 스러움이 다 가셨기를 바라는 마음입니다.

특별히 스토리보드에 대해 좀 더 알고 싶으시면 이 링크를 클릭하셔서 공식 Storyboard API 문서를 보세요.

반응형


반응형

Director to Storyboard “Transition” Guide -1-

Lights… camera… CUT!

조명... 카메라... 컷!

코로나의 Storyboard API 가 나온지 꽤 됐습니다. 그런에 이 사용법에 대해서는 아직까지 약간의 혼동이 있는 것 같습니다. 최근에 제가 소개해 드린 스토리 보드의 이벤트 에 대한 글이 그 혼동을 해소하는데 약간 도움이 될 수 있을 겁니다. 하지만 스토리보드에 대한 설명은 단 한번으로는 부족할 겁니다.

스토리보드 API 이전에 Corona SDK에서는 scene 관리를 위한 공식적인 솔루션은 없었습니다. 그래서 대부분의 사람들은 Ricardo Rauber의 Director 클래스를 사용 했었죠. scene 관리를 도와주는 정말 대단한 3rd-party 오픈 소스 라이브러리 였습니다. 또한 scene 전환은 여러개의 쿨한 화면전환 효과를 사용해서 구현 할 수도 있었죠. 스토리보드가 나왔을 때 그 디렉터 클래스와는 사용법이 많이 달랐습니다. 그래서 디렉터 클래스 사용자들이 스토리보드를 곧바로 사용하는것은 좀 어려웠었던 것 같습니다.

그래서 오늘 여러분이 Director class를 사용하고 있다면 어떻게 스토리보드를 사용할 수 있는지 간단하면서도 쉽게 설명 드리겠습니다. 그리고 만약 여러분이 director class 유저가 아니라면 곧바로 스토리보드를 배울 수 있는 좋은 기회일 겁니다. 그런 분들은 이 문서를 꼭 보실 것을 권장합니다. (그 곳에는 more information section 있습니다.)





Scene Templates

디렉터 클래스는 오래 전부터 있어 왔습니다. 그 클래스는 꽤 큰 feature set 입니다. 저는 이 튜토리얼에서 module structure와 scene 변환에 대한 기본적인것을 다룰께요.

우선 Director 1.4의 scene template 으로 시작하죠. (이 글을 쓰는 현재 가장 최신 버전입니다.)

template.lua (Director)

module(..., package.seeall)

--====================================================================--
-- SCENE: [NAME]
--====================================================================--

new = function () 
    ------------------
    -- Groups
    ------------------
  
    local localGroup = display.newGroup()
  
    ------------------
    -- Your code here
    ------------------
  
    --- CODE ---
  
    ------------------
    -- MUST return a display.newGroup()
    ------------------
  
    return localGroup
end


디렉터 클래스는 아주 간단하고 사용하기 쉽습니다. (그래서 개발자들이 아직까지 이 방법을 사용하고 있는 이유겠죠). 바로 위에 소개한 scene tamplate 에 대한 설명을 하겠습니다.

new() 함수안에 여러분의 scene과 관련된 코드가 들어갑니다. 그리고 중요한것은 모든 display 객체들은 같은 group에 insert 하셔야 합니다. 그리서 그 그룹이 마지막에 return 되야 합니다. (위 예제에서는 localGroup이 됩니다.)

화면전환 (scene transition)이 발생하면 디렉트 클래스는 현재 화면을 밖으로 보내고 그 다음 화면을 화면으로 불러옵니다. (이 때 화면 전환 효과를 사용하실 수 있습니다.)

화면 전환이 완료 되면 이전 화면은 remove 됩니다. 옵션으로 clean() 메소드를 화면의 group에 추가해도 됩니다. 이것은 화면을 remove 하기 바로 전에 call 됩니다. clean() 메소드는 timer들을 정지시키고 Runtime listener를 remove 하기에 좋은 메소드 입니다. 아래 예제는 이 clean() 에소드가 포함된 경우 입니다.

template.lua (Director)

module(..., package.seeall)

--====================================================================--
-- SCENE: [NAME]
--====================================================================--

local cleanUp = function()

    -- stop timers, transitions, listeners, etc.

end

new = function () 
    ------------------
    -- Groups
    ------------------
  
    local localGroup = display.newGroup()
  
    ------------------
    -- Your code here
    ------------------
  
    --- CODE ---
  
    ------------------
    -- MUST return a display.newGroup()
    ------------------

    -- add clean method
    localGroup.clean = cleanUp
  
    return localGroup
end


Storyboard Scene Template

위의 코드를 스토리보드 API를 사용해서 구현하려면 어떻게 해야 되는지에 대한 예제가 아래에 있습니다.

scenetemplate.lua (Storyboard)

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

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

function scene:createScene( event )
    local group = self.view

    --
    -- Create objects here (e.g. scene creation)
    --
end

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

function scene:enterScene( event )

    --
    -- Manipulate objects here (e.g. scene logic)
    --
end

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

function scene:exitScene( event )

    --
    -- Optional cleanup code here
    --
end

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

scene:addEventListener( "createScene", scene )
scene:addEventListener( "enterScene", scene )
scene:addEventListener( "exitScene", scene )

return scene


디렉터 클래스의 new() 함수에 해당하는 스토리보드의 이벤트는 두가지가 있습니다. createScene과 enterScene 입니다. 사실 좀 더 있지만 일단 이 두가지를 보기로 하겠습니다.

스토리보드에서는  self.view 를 사용해서 그 화면의 view display group에 접근 할 수 있습니다. 디렉터 클래스에서 localGroup에 해당 화면의 모둔 객체들을 insert 했듯이요. 스토리보드에서도 그 화면의 모든 객체들을 self.view에 insert 하셔야 합니다. 이것을 하기에 가장 좋은 장소는 바로 createScene event listener 입니다.

위 예제에서 마지막에 있는 이벤트 리스너는 exitScene 이벤트 입니다. 이 부분은 디렉터 클래스의 clean() 메소드와 비슷합니다. 이 함수에서 타이머나 transition들을 정지시키고 Runtime listener들을 remove 시키는 등의 일을 하시면 됩니다.

스토리보드에서는 왜 이렇게 세분화 해서 나눴는지 궁금하실 겁니다. 나중에 설명 드리겠습니니다. 일단 왜 스토리보드에서는 createScene과 enterScene이 나눠졌는지를 알려드릴께요.

It has to do with scene “purging”.

디렉터 클래스와는 다르게 스토리보드에서 화면이 사라질때 그 화면은 자동적으로 remove 되지 않습니다. 그냥 화면 밖으로 사라져 버릴 뿐입니다. 여러분이 purge를 사용해서 그 scene을 따로 remove 시켜야 합니다. remove 되지 않는 다는 것은 그 화면의 객체들이 여전이 메모리에 있다는 얘기고 그러면 그 다음에 다시 그 화면을 불러 올 때 더 빨리 불러 올 수 있다는 얘기입니다. 그래서 자동적으로 remove 시키지 않고 개발자가 따로 함수를 사용해서 remove 시키도록 돼 있습니다.

Caveat : 만약 OS가 메모리가 부족하다는 경고를 보내면 최근에 접근된 화면이 purge 될 겁니다. 이 경우가 유일하게 스토리보드에서 화면이 자동적으로 remove 되는 상황입니다. 다른 경우에는 purge 나 remove 함수를 사용해서 특정 화면이나 이전의 전체 화면을 remove 시킬 수 있습니다. purge나 remove 같은 용어들의 정확한 의미를 아시려면 Storyboard Scene Events를 살펴 보세요.

조금 전으로 다시 돌아가서, 다른 화면으로 전환했는데 이전 화면은 화면에서만 사라지고 실제로는 메모리에 있는 상황을 가정합시다. 그 경우에 그 이전 화면이 다시 call 됐을 때 그 화면의 createScene event는 불려지지 않을 겁니다. (왜냐하면 이전에 이미 불려졌기 때문이죠). 이것이 createScene과 enterScene 이벤트가 둘로 분리된 가장 큰 이유입니다.

위 예제에 있는 것 말고도 여러분들이 사용할 수 있는  몇개의 Storyboard scene event들이 더 있습니다. 이것들을 모든 경우에 다 사용하셔야 된다는 것은 아닙니다. 필요한 경우에 사용하시면 됩니다.

   -  createScene
    - willEnterScene
    - enterScene
    - exitScene
    - didExitScene
    - destroyScene

위 이벤트들에 대한 좀 더 깊은 설명을 보려면 Storyboard Scene Events Explained 글을 봐 주세요. 그 글에는 각 이벤트들이 실행되는 순서를 보여주는 다이어그램도 있습니다. 그러니까 스토리모드의 위 이벤트들에 대해서 이해하시기 어려우시거나 좀 더 확실하게 사용하시려면 위 글을 봐 주세요. 


---- o ---- o ---- o ---- o ---- o ----


다음 글에서는 디렉터 클래스와 스토리보드 API 의 화면전환에 대해 비교해 보겠습니다.


반응형


반응형

얼마전 KOW님 질문에 대한 corona sdk 의 답변이었죠?


코로나로 개발하면서 안드로이드의 메뉴 버튼 클릭했을 때 이것을 콘트롤 할 수 있느냐는 거요.

대답은 event.keyName API를 보라는 거였는데요.


그 document를 아래 번역합니다.

예제 소스도 하나 만들었습니다.


event.keyName

Description:

"key" 리스너를 사용해서 눌려진 navigation key 의 이름을 스트링으로 얻습니다. key의 state (up,down)에 대한 사항은 event.phase를 보세요.

Note : home 하고 text 키들 (A-Z,0-9,punctuation)을 capture 될 수 없습니다.

** 안드로이드 디바이스에만 해당 됩니다.***


Syntax: keyName = event.keyName


Example:

-- Key listener
local function onKeyEvent( event )
        local phase = event.phase
        local keyName = event.keyName
        eventTxt.text = "("..phase.." , " .. keyName ..")"
 
        -- we handled the event, so return true.
        -- for default behavior, return false.
        return true
end
 
-- Add the key callback
Runtime:addEventListener( "key", onKeyEvent );


Note : keyEvent 리스너에서 false를 사용하면 해당 키의 디폴트 액션이 실행 됩니다.


Parameters: 없음


Returns: string


눌려진 navigation key의 이름이 return 됩니다 : "back", "search", "menu", "volumeUp", "volumeDown".

The D-Pad/Trackball key strings 는 "up", "down", "left", "right", "center" 가 return 됩니다

Note: home 키는 캡쳐 될 수 없습니다.


안드로이드 디바이스에서만 가능하고 코로나 SDK Build 2011.559 이상에서만 작동 합니다.

   

이상입니다.





위 소스를 조금 변경해서 아래 샘플 코드를 만들어 봤습니다.


-- Key listener
    _W = display.contentWidth;
    _H = display.contentHeight;
local txtPhase, txtKeyName, haveFun

txtPhase = display.newText( "Phase ", _W/2, _H/5, native.systemFont, 32 )
txtKeyName = display.newText( "KeyName : ", _W/2, _H/3, native.systemFont, 32 )
haveFun = display.newText( "Move", _W/8, _H/2, native.systemFont, 32 )

local function onKeyEvent( event )
        local phase = event.phase
        local keyName = event.keyName

        txtPhase.text = phase;
        txtKeyName.text = keyName;

        haveFun.x = haveFun.x + 50;
        if(haveFun.x > _W) then haveFun.x = 0 end
         -- we handled the event, so return true.
        -- for default behavior, return false.
        return true
end
 
-- Add the key callback
Runtime:addEventListener( "key", onKeyEvent );


main.lua에 이 소스를 붙여 넣고 실행하시면 화면에 phase와 keyName이 뜰겁니다.

제 디바이스에서 menu , back, search, volumeUp, volumeDown 등이 뜨는 거 확인 했어요.

마지막에 haveFun은 그냥 키가 눌려질 때 글자가 움직이도록 해 봤어요. 심심해서....


KOW님 덕분에 저도 몰랐던 사실을 알게 됬네요.


감사합니다.


반응형


반응형

코로나에서 이번에 업데이트된 Daily build 2012.782, Daily build 2012.783 에서 몇가지 API를 변경 했습니다.

아래 그 변경된 내용을 소개해 드리겠습니다.



Storyboard, Network Changes, and More!


최근의 Daily Builds 를 눈여겨 보시지 않으신 분들께 안내 말씀 드리겠습니다. 몇가지 API 가 바꿘것들에 대해 소개합니다. 실제로 바뀐 내용들은 많이 있지만 이 글에서 다룰 수 있는 내용만 다루겠습니다. 유료 고객이시면 Daily Builds summary 를 보시면 변경된 사항을 자세히 보실 수 있을 겁니다.

Network API Changes

build 2012.782 에서는  network.request()network.download() 에 새로운 HTTP 메소드 지원을 하도록 기능을 확장했습니다.  (PUT,DELETE,HEAD - 기존의 POST와 GET은 여전히 사용가능 합니다. -)

이 기능 확장으로 코로나의 네트워킹 기능이 REST API에 좀 더 어울릴 수 있도록 함으로서 웹상의 여러 개발 API들을 더 많이 활용해서 코로나 앱을 개발 할 수 있도록 했습니다.


media.show() + Android = Better

buils 2012.783 에서 media.show() API가 코로나 internal camera 기능말고도 안드로이드 디바이스의 디폴트 카메라 앱을 사용할 수 있도록 확장됐습니다. 이를 위해서는 "WRITE_EXTERNAL_STORAGE" 퍼미션이 필요합니다. 물론 그 디바이스에는 Enternal storage 가 있어야 겠죠.

안드로이드의 디바이스 카메라를 사용해서 안드로이드 앱을 개발하려고 하시는 분들에게는 아주 좋은 뉴스가 되겠죠? 

Storyboard API Changes

Storyboard API에 몇가지 변경된 사항들이 있습니다. 큰 것은 아니지만 아주 유용한 추가 기능도 있구요.

“willEnterScene” and “didExitScene”

스토리보드 이벤트 이름이 바뀌었습니다. enterBegan 이 willEnterScene으로 바뀌었고 exitEnded 가 didExitScene으로 바뀌었습니다.  이전의 이름은 해당 기능에 대해 딱 맞는 이름이 아니었습니다. 그리고 다른 이벤트와도 이름이 너무 비슷했구요.  Storyboard Scene Events Explained  document 에도 이 바뀐 내용이 적용되었습니다.


New Storyboard Functions


스토리보드에 두개의 새로운 function이 추기 됐습니다. storyboard.loadScene(), storyboard.reloadScene(), 과 storyboard.getCurrentSceneName()      

두 함수인데요. 자세한 설명은 위 함수를 클릭하시면 보실 수 있습니다.


storyboard.loadScene()은 어떤 scene으로 화면전환을 하지 않은 채 그 scene을 load 해야 할 경우 사용할 수 있습니다. (전체 scene을 로드할 수도 있고 특정 모듈을 로드할 수도 있습니다. 설명서를 보시면 그 방법을 자세히 아실 수 있습니다.)  이 기능은 아주 heavy한 scene을 로드할 경우 미리 로드함으로서 화면전환을 자연스럽게 하고자 할 때 사용 하시면 유용하실 겁니다.

Storyboard 변경사항은 build 2012.782 버전 이상에서만 적용됩니다.


이 사항들은 최근의 Daily Build에서 바뀐 내용들의 일부분일 뿐입니다. 새로운 API를 사용해서 sprite들을 trim 하는 기능도 있구요. 그리고 다른 작은 추가되는 사항들과 변경되는 사항들이 있습니다.


이 기능들은 최근의 daily build 에서 사용 가능하시구요. 이 버전은 유료 사용자만이 사용하실 수 있읍니다.


반응형

Tutorial: Detecting Touches in Corona

2012. 4. 12. 22:03 | Posted by 솔웅


반응형

Tutorial: Detecting Touches in Corona

오늘날 가장 구별되는 모바일 디바이스의 특징은 Touch screen 이라는 겁니다. 그러니까 개발자로서 코로나로 만드는 앱에 대해 유저에게 좀 더 리얼하고 풍부한 효과를 주기 위해  어떻게 user의 touch를 감지하는지를 정확히 아는 것은 중요합니다.

이 touch 에 대해 여러분들이 생각하는 것보다 더 많은 생각해야 할 것이 있습니다. 이 튜도리얼에서는 단지 touch를 어떻게 감지하는지 이외에도 user touch의 다양한 경우에 대해서도 생각해 보고 이것을 앎으로서 구현하고자 하는 기능을 좀 더 정확히 구현할 수 있도록 도움을 드리고자 합니다.

이 튜토리얼을 끝내시게 되면 touch phases, focus, dragging 같은 것들에 대해 좀 더 친숙해 지실겁니다. 이 튜토리얼은 가장 single touch 에 대해서만 다루겠습니다. Multi-touch는 좀 더 깊이 들어가야 할 주제입니다. 이 주제에 대해서는 나중에 다루겠습니다. 그 멀티 터치에 대해서 알기 위해서도 이 기본 touch 이벤트에대해 먼저 이해하는 것이 중요합니다.

만약 이 글을 읽고 계시는 분이 Corona event들에 대해 친숙하지 않으시다면 Corona Event Model Explained 를 먼저 읽어 보시기를 권합니다. 그 글을 읽으시면 이벤트와 리스너에 대해 이해하실 수 있을 겁니다. 그 글을 통해서 touch event에 대한 기본적인 내용도 아실 수 있습니다. event model에 대해 읽어 보신 후 이 튜토리얼로 돌아와서 보신다면 완벽하게 이해하실 수 있을 겁니다.





Detecting User Touches

User touch는 두가지 방법으로 감지 됩니다. 각 객체단위로 감지될 수 있고 전체 스크린 단위로 감지될 수 있습니다. (Runtime이나 global touch 가 전체 스크린 단위에서 감지 되는 겁니다.) 이 두가지가 어떻게 다른지에 대해 이해하는 것은 아주 중요합니다.

Per-object touches

우선 다른 특정한 이벤트가 감지되기 전에 이벤트를 add 해야 합니다. add 방법은 두가지 방법이 있습니다. table 리스너를 이용하던가 function 리스너를 이용하는 방법입니다.
아래 두개에 대한 예제가 있습니다.

Table Listener A와 Table Listener B는 기본적으로 같습니다. 약간의 다른점만이 있을 뿐입니다. 예제를 보면서 그 다른 점을 공부해 보세요.


* Table Listener A


local obj = display.newImage( "image.png" )

-- touch listener
function obj:touch( event )
    -- 'self' parameter exists via the ':' in function definition
end

-- begin detecting touches
obj:addEventListener( "touch", obj )


* Table Listener B

local obj = display.newImage( "image.png" )

-- touch listener
local function onObjectTouch( self, event )
    -- 'self' parameter must be defined via function argument
end

-- begin detecting touches
obj.touch = onObjectTouch
obj:addEventListener( "touch", obj )


* Function Listener

local obj = display.newImage( "image.png" )

-- touch listener
local function onObjectTouch( event )
    -- no 'self' parameter exists in this scenario
end

-- begin detecting touches
obj:addEventListener( "touch", onObjectTouch )


리스너 함수 안에서 어떤 일이 벌어지고 있는지에 대해서는 잠시 후에 다루겠습니다. 여기서는 객체에 어떻게 리스너가 add 되는지에 대해 주목해 주세요. 위 예제는 코로나에서 touch 이벤트에 대해 리스너를 다는 모든 방법에 대해 보여줍니다. 세번째 예제를 예를 들어 설명해 볼께요.

"유저가 obj를 touch 하면 onObjectTouch() 함수를 call 한다."


NOTE : 한 번 touch를 하면 touch event는 여러번 dispatch 됩니다. 그 이유는 touch 이벤트 안에는 여러개의 phases가 있기 때문입니다. 이 부분은 중요합니다. 잠시 후에 여기에 대해서 자세히 다루겠습니다.

Runtime Touches

여러분은 또한 Runtime 에 global 하게 touch 리스너를 add 할 수 있습니다. 그렇게 하면 전체 screen에서 touch 이벤트를 감지하게 될 겁니다. Runtime touch는 function listener 만 사용할 수 있습니다. 아래 예제가 있습니다.

local function onScreenTouch( event )
    -- no 'self' parameter
end

-- begin listening for screen touches
Runtime:addEventListener( "touch", onScreenTouch )


How to STOP detecting touches

특정 object에 대한 (global Runtime object도 포함해서) touch 이벤트 listening을 중지하시려면 addEventListener() 하셨던 방법대로 간단히 removeEventListener()를 call 하시면 됩니다.

-- example 1:
obj:removeEventListener( "touch", obj )

-- example 2:
obj:removeEventListener( "touch", onObjectTouch )

-- example3 :
Runtime:removeEventListener( "touch", onScreenTouch )


VERY IMPORTANT: Runtime 이벤트 리스너는 절대 자동적으로 remove 되지 않습니다. 만약 global Runtime object에 어떤 리스너를 add 하셨다면 이 리스너가 필요 없게 될 때 반드시 여러분이 직접 remove 해 주셔야 합니다. 그렇지 않으면 버그나 충돌이 일어날 수 있습니다.

개별적인 객체에 add된 이벤트 리스너는 그 객체(object)가 remove 될 때 자동적으로 remove 됩니다. 하지만 그 객체에 있는 리스너가 필요 없을 때 여러분이 직접 remove 해 주실것을 권장합니다. 항상 같은 객체에 같은 이벤트를 여러번 add 하는 일이 없도록 주의하셔야 합니다.

Touch Event Phases

초보 코로나 개발자분들은 touch listener를 사용하면서 몇가지 문제점에 당면하기 쉽습니다. 왜냐하면 유저가 한번 touch 할 때마다 listener fucntion이 한번만 call 될 걸로 기대하고 있기 때문입니다. 하지만 유저가 한번 Touch 한다고 그 리스너 함수가 한번만 call 되는 것이 아닙니다. 터치 이벤트가 발생하면 아래와 같은 phases 가 있습니다.

    began – 유저가 touch 하는 순간에 감지 됨
    moved – 유저가 터치한 손가락을 움직일 때 감지 됨
    ended & cancelled – 유저가 터치한 손가락을 화면에서 뗄 때 감지됨. 혹은 어떤 이유에서건 이 touch 이벤트가 cancel 됐을 때 감지 됨


이 touch phase가 감지될 때마다 지정한 터치 이벤트 리스너 함수가 call 되는 것입니다. 유저가 터치할 때마다 최소한 두번 내지는 세번 이 함수가 call 되는 것입니다.

그렇기 때문에 여러분들은 터치 했을 때 구현될 동작들이 어떤 phase에서 할 것인지 결정 하셔야 합니다. 그리고 나서 각 phase가 일어날 때 그 동작을 하도록 코딩을 하셔야 합니다. 아래 그 예제가 있습니다.

local function onObjectTouch( self, event )
    if event.phase == "began" then

        -- Touch has began

    elseif event.phase == "moved" then

        -- User has moved their finger, while touching

    elseif event.phase == "ended" or event.phase == "cancelled" then

        -- Touch has ended; user has lifted their finger
    end

    return true    -- IMPORTANT
end


위 예제 처럼 각 phase 를 if 문에서 감지하고 해당 phase에 여러분들이 넣고 싶은 동작들을 넣으시면 됩니다. 한번 터치 할 때마다 4가지 phase가 발생하고 이 phase가 발생할 때마다 onObjectTouch 함수가 call 되는 겁니다. 위의 예제처럼 대부분 ended와 cancelled phase는 같이 다룹니다.

To return true, or not to—THAT is the question.

위 예제 마지막에 보면 제가 OMPORTANT라고 주석을 단 것을 보실 수 있을 겁니다. 함수 끝에는 이와 같이 true 나 false 가 return 되어야 합니다.

touch 리스너가 true를 return 하면 그 touch 가 successful 하다는 의미입니다. touch 된 그 객체는 그 시간에 touch 됐음을 알립니다. 그렇게 함으로서 그 다음 event 가 제대로 dispatch 되게 됩니다. 그 object 밑에 touch 될 다른 object가 있다면 그 touch는 그 아래 object 에는 해당되지 않을 겁니다. 왜냐하면 그 위의 object 가 이미 그 touch에 대해 사용했기 때문입니다.

만약 리스너가 false를 return 했다면 (아무것도 return 하지 않아도 false를 return 한 것과 같습니다.) 그 touch는 뭔가 잘 못 됐다고 판단될 것이고 오직 began phase 만이 감지되고 끝날 겁니다. 이 경우 만약에 그 객체 밑에 터치 리스너를 감지하는 다른 객체가 있다면 그 터치 이벤트는 그 다음 객체에 전달이 될 겁니다. (이것을 이용하면 한 번의 터치로 두개의 object 에서 어떤 동작이 일어나도록 할 수 있을 겁니다. 단지 fault를 return 한 함수에서는 began phase 밖에 사용 못 하겠죠.)

이 부분이 새로 코로나 개발을 시작하는 분 들에게 혼동을 주기도 합니다. 이러한 touch 이벤트를 사용할 경우에 꼭 함수 끝날 때 true를 return 해야 한다는 것만 잊지 마세요.

Touch Focus

터치 리스너를 셋업했다면 아마 아래와 같은 모습을 하고 있을 겁니다.

local function onObjectTouch( self, event )
    if event.phase == "began" then

        -- specify the global touch focus
        display.getCurrentStage():setFocus( self )
        self.isFocus = true

    elseif self.isFocus then
        if event.phase == "moved" then

            -- do something here; or not

        elseif event.phase == "ended" or event.phase == "cancelled" then

            -- reset global touch focus
            display.getCurrentStage():setFocus( nil )
            self.isFocus = nil
        end
    end

    return true
end


해당 객체에 대한 터치 이벤트가 감지 됐을 때 touch focus를 global로 선언하는 것도 중요합니다. 이것은 began phase 단계에서 해야 합니다. 그리고  ended/cancelled phase 에서 이 Touch focus를 reset 시키게 됩니다. 그래야지 다른 객체들에 대한 touch 를 감지할 수 있게 됩니다. 이러한 내용들은 위 예제에 모두 구현돼 있습니다.

이 touch focus를 사용하신다면 힘들게 type 할 필요 없이 그냥 저 위의 예제를 복사해서 붙여 넣기만 하시면 됩니다.

Other Event Properties

터치 이벤트에는 phase 이외에도 여러가지 프로퍼티들이 있습니다. 아래 내용을 참고하세요.

    event.id – 해당 touch 이벤트에 대한 유닉한 구분자. (multi-touch에서 주로 사용됨)
    event.name – 해당 이벤트의 이름
    event.phase – 유저가 touch 한 현재의 phase 상태. began, moved, ended, cancelled 등이 있습니다.
    event.target – touch 된 object; self랑 똑 같은 의미임
    event.time – 이벤트가 일어난 이후부터 지금까지의 시간
    event.x, event.y – 이벤트가 일어난 x,y 좌표
    event.xStart, event.yStart – 최초 이벤트가 일어난 x,y 좌표. (began의 좌표가 됨). 유저가 drag 하게 될 경우 현재의 위치와 최초의 위치를 구분해서 사용할 수 있음
. 사용 예제를 보시려면 여기를 클릭 하세요.

프로퍼티에 대해 좀 더 자세한 내용을 알고 싶으시면 event API reference page를 참조하세요.

코로나에서는 이 touch 이벤트 이외에 tap 이벤트도 지원합니다. 이것은 touch 이벤트이지만 phase가 없습니다. 이것은 유저가 스크린 내 한 객체에 아주 빠른 tap이 일어나는것을 감지하고자 할 때 유용합니다.

tap 이벤트에 대한 자세한 정보는 Basic Interactivity 에 있습니다. 터치 이벤트에 대해 좀 더 자세히 할고 싶으시면 Events and Listeners documentation의 Touch Event 섹션을 보실것을 권장합니다.

반응형


반응형

오늘도 어제에 이어서 코로나에서 SQLite 다루는 방법에 대한 튜토리얼을 정리하겠습니다.

===== o ===== o ==== o ====


Inserting new rows into a table

새로운 row를 만드는 것은 INSERT 문을 통해서 할 수 있습니다. 우선 아주 기초적인 사용법을 보여 드리겠습니다. 그리고 나서 좀 더 다이나믹한 예제를 보여 드릴께요.

local insertQuery = [[INSERT INTO test VALUES (NULL, 'John Doe','This is an unknown person.', 'http://www.example.com/'); ]]
db:exec( insertQuery )


그냥 딱 봐도 알겠죠? 그래도 설명을 드리자면, 이전 에제에서 test라는 테이블을 생성했습니다. 그리고 이 예제에서는 그 test라는 테이블에 row를 생성한 겁니다.

주의하셔야 할 것은 컬럼의 순서에 맞춰서 value를 넣어 주셔야 합니다. 그 컬럼 순서는 테이블을 만들 때 사용했던 순서 입니다.

    id → NULL
    name → John Doe
    description → This is an unknown person.
    website → http://www.example.com/


이전 예제에서 테이블을 만들 때 id는 auto-increment로 설정 했습니다. 자동적으로 row 번호를 매기도록 하기 위해서 입니다. 그래서 id 값은 NULL 로 넣었습니다. 그러면 NULL이 들어가는 것이 아니라 자동적으로 숫자가 들어갈 겁니다. SQLite가 알아서 하는 부분이니까 별로 신경 안 쓰셔도 됩니다.

이제 좀 더 다이나믹한 예제를 볼까요? 좀 더 창의력을 발휘해 보겠습니다. 아래 예제는 루아 테이블로부터 value들을 가져와서 SQL 테이블에 row들을 insert 합니다.

local people =
{
    {
        name = "John Doe",
        description = "This is an unknown person.",
        website = "http://www.example.com/"
    },
    {
        name = "Jane Doe",
        description = "The wife of an unknown person.",
        website = "http://www.example2.com/"
    },
    {
        name = "Oscar",
        description = "Green guy lives in trash can.",
        website = "http://www.example3.com/"
    }
}

for i=1,#people do
    local q = [[INSERT INTO test VALUES (NULL, ']] .. people[i].name .. [[',']] .. people[i].description .. [[', ']] .. people[i].website .. [['); ]]
    db:exec( q )
end


NOTE : 여러분들이 입력하고자 하는 값에 따옴표가 있으면 그 따옴표 옆에 역슬래시를 넣어 주어야 합니다. 그렇지 않으면 에러가 발생할 겁니다.


 Updating existing rows

항상 새 row를 만드는 일만 하는 것은 아니죠? 사실 실전에서는 기존에 있던 row의 데이터를 업데이트 하는 일이 더 자주 있습니다. 아래 예제는 테이블에 이미 3개의 row들이 있다고 가정하고 만든 예제입니다. (위의 예제에서 만들었던 3개의 row들이라고 가정합시다.)

local q = [[UPDATE test SET name='Big Bird' WHERE id=3;]]
db:exec( q )


위 예제의 쿼리가 하는 일은 id가 3인 row를 찾아서 그 중의 name 컬럼의 값을 Big Bird 로 바꾸는 일을 합니다. 이전 예제에서 만들었던 세번째 row의 name인 Oscar 가 Big Bird가 되겠죠. 꼭 id로 특정 row를 찾아야 되는 것은 아니지만 대개 이렇게 하는게 간편합니다. (그 값은 unique 해서 항상 딱 1개의 row만 해당 되거든요.)

Deleting a row from an SQL table

delete는 update와 비슷합니다. 다른점은 UPDATE 대신 DELETE를 사용한다는 것이죠. 아래 예제에서는 id가 1인 row를 delete 합니다. John Doe가 있는 row가 delete 되겠네요.

local q = [[DELETE FROM test WHERE id=1;]]
db:exec( q )


Retrieving data with “SELECT”

SQL 데이터베이스에서 데이터를 검색할 때 다양한 필요에 의해 행해지게 됩니다. 어떤 때는 단 1줄만 필요할 때도 있고 특정 값을 가진 (예를 들어 서울에 사는 사람들 등) row들을 모두 검색해야 할 때도 있습니다.

이 모든 경우를 다루지는 않겠습니다. 다만 아래 예제를 통해 기존에 있던 데이터베이스에서 SELECT 쿼리를 통해 얻은 값을 루아 배열에 담는 과정을 보여드리겠습니다.

require "sqlite3"

local path = system.pathForFile( "data.db", system.DocumentsDirectory )
local db = sqlite3.open( path )

local people = {}  -- starts off emtpy

for row in db:nrows("SELECT * FROM test") do
    print( "Row " .. row.id )

    -- create table at next available array index
    people[#people+1] =
    {
        name = row.name,
        description = row.description,
        website = row.website
    }
end


여기서 가장 중요한 부분은 8번째 줄인 SELECT 구문이 있는 곳입니다. nrows() 함수를 이용해서 for 루프를 돌리도록 합니다. 이렇게 해서 루아 배열에 값을 넣고 그 값을 가지고 앱 내에서 이런 저런 프로그래밍을 할 수 있습니다.

SELECT 와 관련되서 좀 더 자세히 알고 싶으시면 이 글을 읽어 보세요.

Closing the database

데이터베이스와 관련된 작업이 끝났다면 데이터베이스와의 연결을 끊어 줘야 합니다. 이것은 굉장히 중요한 부분 입니다. close() 메소드를 사용해서 연결을 끊는데요. 필요할 때는 언제든지 사용하실 수 있습니다. 하지만 되도록이면 applicationExit 시스템 이벤트가 발생할 때 사용할 것을 권장 합니다. 이렇게 되면 앱이 종료할 때 데이터베이스 연결이 끊어지게 됩니다.

local function onSystemEvent( event )
    if event.type == "applicationExit" then
        if db and db:isopen() then
            db:close()
        end
    end
end
Runtime:addEventListener( "system", onSystemEvent )


위 코드는 여러분의 앱내 어느 부분에 있어도 됩니다. 단지 데이터베이스를 오픈한 이후가 되도록 하는게 좋겠죠. 위의 경우는 db 객체가 생성된 이후가 좋겠죠. 그리고 데이터베이스를 닫아야 하는 시점과 관련된 위치가 좋겠구요.

Further Reading

이것이 코로나에서 데이터베이스를 이용하는 대략적인 Tutorial 입니다. 코로나에서 SQLite으로 어떤 일을 할 수 있는지 살짝 맛만 보인겁니다. 더 많은 정보들은 여러분 스스로 찾으셔야 합니다. 인터넷에는 수 많은 SQLite + Lua 관련된 자료들이 있습니다. 그러니까 앱을 만들 때 데이터베이스를 좀 더 효과적으로 이용하시려면 더 많은 공부가 필요할 겁니다.

우선 SQL의 다양한 쿼리 사용법에 익숙해 지는것이 좋습니다. 이것만 해도 수 많은 Tutorial들을 만들 수 있습니다. 이 글에서 다룬 내용은 단지 처음 시작할 수 있도록 기본적인 내용만 다룬겁니다.

SQLite에 대해 좀 더 깊은 정보를 원하신다면 zetcode.com 의 SQLite tutorial을 보실것을 권장합니다.

반응형


반응형

이번주 코로나 Blog Tutorial 은 데이터베이스 입니다.

모바일기기에서는 SQLite을 사용하는데요.

이번엔 내용이 좀 기네요.

이번주 튜토리얼을 2회나 3회로 나눠서 소개해 드리겠습니다.

======= o ======= o ======== o ======== o =======

Database Access in Corona with SQLite   

프로그래밍 세계에서 특히 웹 프로그래머한테 데이터베이스 기술은 아주 큰 부분을 차지합니다. 데이터베이스는 아주 많은 데이터를 저장할 수 있는 훌륭한 기술입니다. 그것만이 아니라 그 저장한 데이터를 나중에 찾아서 사용할 수도 있습니다. 이것이 가장 중요한 역할이죠.

예를 들어 note 앱을 만든다면 유저 개개인이 만든 note들을 데이터베이스에 저장해야 할 겁니다. 물론 각각의 note를 텍스트 파일로도 저장할 수 있을 겁니다. 하지만 그러면 아마 수많은 파일을 관리해야 될겁니다. (데이터베이스를 사용하면 그냥 한게의 .db 파일만 있으면 되겠죠.) 그리고 데이터를 저장하고 찾고 하는 방법도 데이터베이스 이용과 다른 방법을 사용해야 합니다.




데이터베이스 파일은 SQL 테이블에 데이터가 저장이 됩니다.

그런데 이렇게 데이터를 저장하고 다시 나중에 검색하고 하는 일을 이전에 우리가 다루지 않았었나요?

이전에 JSON tutorial에서 제가 JSON은 같은 형태의 정보를 쉽게 저장할 수 있는 좋은 도구라고 말했었습니다. 코로나에서 디코드된 JSON 스트링이 루아 테이블로 컨트롤 하는 겁니다.


그래서 데이터베이스와 연관되서 JSON을 사용할 때 아미면 데이터베이스의 내용을 JSON으로 보낼 때 두 부분 모두 아주 파워풀하게 사용될 수 있습니다. 이런 방법들은 그 나름대로의 특징이 있습니다. 앱을 만들 때 필요에 따라 이러한 데이터를 다루는 기술을 이용하시면 됩니다.

이러한 방법들 중 어떤 방법을 사용할까를 정하는 기본적인 룰은 아주 큰 용량의 데이터를 다루게 되면 (특히 그 데이터를 search하고 앱 내에 저장되어야 한다면) 데이터베이스 기술을 사용하는것이 좋을 겁니다. 작은 용량의 데이터라면 예를 들어 configuration data나 case 들 같은 경우는 단순하게 Lua table에 저장해서 파일로 저장하고 나중에 이 파일을 이용해야 합니다. 이 경우에는 JSON으로 간단하게 처리하면 됩니다.

사실 많은 경우 큰 용량의 데이터를 저장해야할 앱을 다룰 때 SQLite와 JSON을 함께 사용하게 됩니다. JSON 객체가 단지 string일 경우 JSON blobs 를 데이터베이스에 저장할 수 있습니다. 그리고 그것을 나중에 Lua 변수로 로드하고 JSON 라이브러리를 사용해서 그 string을 table로 convert 하게 됩니다. JSON은 좀 더 작은 data chunk를 관리 합니다. 그리고 데이터베이스는 이 JSON blobs들의 모임인 큰 data structure를  organize 하게 되는 겁니다.

JSON과 함께 사용하면 데이터베이스를 사용하는데 훨씬 간편하게 사용할 수 있는 부분이 있습니다. 이와 관련해서는 나중에 다루겠습니다.

이 튜토리얼에서는 database를 create하고 파일로 저장을 하고 정보를 저장하고 데이터를 검색하는 기능을 코로나 앱에서 어떻게 해야 하는지에 대해 다루겠습니다.

Creating a database

데이터베이스를 생성하는 방법에는 두가지가 있습니다. 첫번째는 메모리에 데이타베이스를 만드는 방법으로 이 경우에는 앱이 사용중일 때만 만들어지고 사용되고 이 앱이 꺼지면 없어지게 됩니다. 그리고 두번째 방법은 데이터베이스 파일에 생성하는 겁니다. 이렇게 되면 언제든지 로드될 수 있겠죠. 이 튜토리얼에서는 메모리에서 데이터베이스를 다루는 방버은 커버하지 않을 겁니다.

데이터베이스를 사용하는 예제가 아래에 있습니다. 데이터베이스를 생성하고 save된 데이터베이스를 오픈하기 위해서 아래 작업들을 해야 합니다.

require "sqlite3"

local path = system.pathForFile( "data.db", system.DocumentsDirectory )
local db = sqlite3.open( path )


NOTE : 데이터베이스를 만드는 장소는 system.DocumentsDiretory를 사용할것을 추천합니다. 프로젝트 리소스 디렉토리는 writable 하지 않고 temp/cache 디렉토리는 OS에 의해서 순식간에 정리될 수도 있습니다. 그러므로 system.DocumentsDirectory 가 가장 안전한 곳입니다.

다시 예제로 돌아가서요. 첫번째 줄은 sqlite3 namespace를 통해서 SQLite 함수를 만드는 겁니다. 마지막줄은 특정 위치에 있는 데이터베이스 파일을 로드하기 위해 (혹은 그 위치에 데이터베이스가 없으면 생성하기 위한) sqlite3.open()을 하는 겁니다.

Creating a Table

테이블 (루아 테이블이 아닌 SQL 테이블), 컬럼, row 등과 같이 SQLite 데이터베이스를 사용하게 되면 일상에서 사용하는 용어들을 접하게 됩니다. 테이블은 데이터의 카테고리를 연상하실 수 있을 겁니다. 저 위에 note 앱을 예로 들을 때 얘기 한건데요. 앱 세팅, 유저 세팅 그리고 유저가 생성한 노트를 보관하기 위해서 각각 다른 테이블들을 가질 수 있습니다.

각각의 SQL 테이블은 복수의 컬럼들을 가질 수 있습니다. 예를 들어 id, name, description, website 등등. 프로퍼티라고 생각할 수 있겠죠. 테이블에 insert 된 각각의 아이템들은 row라고 합니다. Row는 실제로 다루게 되는 데이터들이 됩니다. 이 row들을 만들기 위해서 먼저 컬럼들을 만들어야 하는 것이죠.

local tablesetup = [[CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY autoincrement, name, description, website);]]
db:exec( tablesetup )


위의 예제에 있는 tablesetup 변수에는 SQL Query 가 string으로 저장돼 있습니다. 이 쿼리는 데이터베이스가 무엇을 해야 하는지 정리해 놓은 겁니다. 그 다음에 우리는 이 쿼리를 실행하게 되죠. db.exec로 실행을 합니다. 그러면 이 쿼리가 데이터베이스에 전달 되서 데이터베이스가 그 쿼리대로 일을 합니다. 위 예제에서는 text 라는 테이블이 만들어 지고 그 테이블에는 4개의 컬럼들이 있게 됩니다.

    id (primary key that will auto-increment upon inserting new rows)
    name
    description
    website


NOTE 1 : 대개 테이블의 첫번째 컬럼은 id 입니다. 이것은 primary key로 세팅 됩니다. (모든 테이블은 한개의 primary key 가 있어야 됩니다.) auto-increment flag는 row가 추가 될 때마다 이 컬럼의 값이 자동적으로 그 다음 id number로 설정 된다는 겁니다. (1,2,3,4 이렇게 순서가 매겨 지겠죠.) 그러니까 개발자는 이 컬럼의 row 값들을 따로 입력할 필요가 없습니다.

NOTE 2 : 위 예제에서 query string이 따옴표 대신  double brackets ([[ 와 ]]) 로 싸여져 있는 것을 보실 수 있을 겁니다. 왜냐하면 SQL query 를 만들 때 그 안에 큰 따옴표나 작은 따옴표가 사용되어야 하기 때문에 [[와 ]] 를 사용해서 좀 더 안전하게 코딩할 수 있도록 한 겁니다.

이제 새롭게 만든 test라는 SQL 테이블에 각각의 row들을 insert 할 수 있습니다. 모든 row들은 id,name,description, website 프로퍼티들이 있습니다. 그리고 각 row들은 이에 대해 각각 다른 값들을 가지고 있을 수 있습니다. 조금 전에 얘기했듯이 컬럼은 테이블에서 row에 대한 프로퍼티들로 생각할 수 있습니다.

반응형