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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

Storyboard: Scene Purging vs. Removal

2012. 8. 2. 11:01 | Posted by 솔웅


반응형

Storyboard: Scene Purging vs. Removal


Posted on . Written by


코로나 SDK 의 storyboard API에서 혼동되는 부분 중 하나가 scene을 purging 하고 removal 하는 겁니다. purging과 removing의 차이점은 무엇일까요? 그리고 scene을 purge 한다는게 무슨 의미죠? 이런것들이 우리가 자주 받는 질문들 중 일 부분입니다.


오늘은 이런 의문점을 깨끗하게 날려드리기 위해 purging과 removal이 되면 scene들 뒷편에선 어떤 일들이 일어나는지에 대한 high-level overview를 알려드리겠습니다. 그리고 몇가지 예제들도 보여드릴거구요.


Memory Consumption


Corona display object가 생성 됐을 때 이 객체는 texture 메모리 (graphics)와 Lua 메모리 (테이블과 변수 같은 데이터)를 차지하게 됩니다. scene 모듈 같은 모듈이 로딩 될 때 이것은 메모리에 저장돼 있는 단순한 테이블입니다. 그래서 좀 더 빠르게 메모리에서 로딩 할 수 가 있습니다. 그렇지만 한번에 메모리에 너무 많은 것들이 있으면 퍼포먼스에 지장을 줄 수 밖에 없겠죠. 이것 때문에 저희는 개발자들이 메모리에 무엇을 남길지 그리고 어떤 것들을 unload 시키거나 freed 시킬지의 여부에 대해서 전적으로 콘트롤 할 수 있도록 하자고 정했습니다. 


이러한 배경을 염두에 두신후 아래 스토리보드 scene들에 서 purging과 removing이 무엇인제에 대한 기본 개요를 보시기 바랍니다.


Purging은 scene에서 display object들만 remove 시키는 겁니다. 그렇게 되면 texture memory는 freeing 되겠죠. 그리고 이 scene이 external module에 의해 represent 되면 이 모듈은 계속 메모리에 남아있는 상태가 될 겁니다. 여러분이 만든 custom 변수들이 모두 메모리에 있게 된다는 겁니다.


scene을 Removing 한다는 것은 display 객체든 루아 객체든 모두 remove 한다는 의미 입니다. 그 결과로 texture 메모리 뿐만이 아니라 루아 메모리도 모두 freed 되게 됩니다.



So why purge at all?


만약에 유저가 나갔다가 다시 돌아올 것으로 예상되는 scene이 있다면 그런데 그 scene이 아주 많은 texture 메모리를 가지고 있다면 이 scene은 purge 시키는 것이 좋을 겁니다. 그러면 다른 scene이 로드될 떄 퍼포먼스에 지장을 주지 않겠죠. (그 모듈-scene-은 계속 메모리에 남아 있는 겁니다.)


예를 들어 action arcade 게임을 만든다고 했을 때, 그 게임은 아주 많은 physics와 아주 빠르게 움직이는 수 많은 객체들을 사용하게 될 겁니다. 두말 할 것도 없이 그 게임 scene은 아주 많은 texture 메모리를 필요로 하게 되죠.


이 game screen 구석에는 pause 버튼이있습니다. 그 버튼을 누르면 pausegame 이 로드 되죠. 유저기 이 scene을 자주 들락거려야 한다면 이 scene을 purge 하는게 훨씬 나을 겁니다. 유저는 이 scene에 다시 돌아올 가능성이 많을 겁니다. 그러니까 다른 scene의 퍼포먼스에  부담을 덜기 위해서 pause를 사용해서 texture 메모리를 절약하고 다시 돌아올 경우에 빨리 로드되게 하기 위해서 루아 객체는 메모리에 그대로 두는 pause가 유용하게 이용될 수 있을 겁니다


Functions for Purging


scene purging을 handle 하는 스토리보드의 function에는 두가지가 있습니다.



첫번째 함수는 single argument를 받습니다. sceneName을 받게 되죠. purge 할 특정 scene을 지칭하는 겁니다. purged 될 scene이 external module과 correspond 된다면 이것은 module name이 되겠죠. (.lua 확장자를 뺀 나머지 부분)

이렇게 purging이 되면 해당 scene의 display object들은 destroy 될 겁니다. 더이상 display object들이 화면에 display 될 필요가 없을 떄 사용하는 것이죠. 이 작업을 하기에 가장 알맞는 이벤트는 didExitScene 입니다.

scene이 완전히 screen에서 사라졌을 떄 그 scene을 purge 하는 예제가 아래에 있습니다.


-- scene1.lua
local storyboard = require "storyboard"
local scene = storyboard.newScene()

-- createScene event simply creates a background image
function scene:createScene( event )
    local bg = display.newImage( self.view, "background.png" )
end
scene:addEventListener( "createScene" )

function scene:didExitScene( event )
    storyboard.purgeScene( "scene1" )
end
scene:addEventListener( "didExitScene" )

return scene


어떤 scene에서 menu.lua로 전환한다고 칩시다. 이 경우 menu.lua로 되돌아 오는 다른 scene들은 여러경우가 있습니다. 이럴 경우 그 이전 scene이 어떤건지 확실히 모를 때 어떻게 이전 scene을 pause 하는지에 대한 예제가 아래에 있습니다.


-- menu.lua
local storyboard = require "storyboard"
local scene = storyboard.newScene()

-- createScene event simply creates a background image
function scene:createScene( event )
    local bg = display.newImage( self.view, "background.png" )
end
scene:addEventListener( "createScene" )

function scene:enterScene( event )
    local prior_scene = storyboard.getPrevious()
    storyboard.purgeScene( prior_scene )
end
scene:addEventListener( "enterScene" )

return scene


여러분들은 현재 작동중인 scene을 제외한 모든 scene들을 purge 시키기 위해 storyboard.purgeAll()를 사용하실 수 있습니다.  scene이 purge 될 때는 destroyScene 이벤트가 dispatch 될 겁니다. 그 이벤트를 이용해서 Runtime listener를 remove 한다던가 하는 last minute action들을 처리하실 수 있습니다.


Functions for Removing


scene을 removal 하는 것을 처리하기 위한 스토리 보드 함수에는 두가지가 있습니다. 아래 두 함수중에 하나가 call 되면 해당 scene은 우선 purge 하고 그 다음에 remove 될 겁니다.


첫번째 함수는 single argument를 받습니다. sceneName 인데요 remove 할 scene을 말하는 것이죠. 이 remove 될 scene이 external module 이면 그 모듈 이름이 되겠죠. (.lua 확장자를 제외한 부분)

remove 되기 전에 해당 scene이 purge 되면 destroyScene 이벤트가 해당 scene에 dispatch 될 겁니다.


Automatic Purging


iOS에서는 디폴트로 OS가 Low memory warning을 issue 하면 least-recently-used scene이 자동적으로 purge 됩니다. 이 purge가 자동적으로 이뤄지도록 하고 싶지 않으면 storyboard.disableAutoPurge 프로퍼티를 사용하시면 됩니다.


storyboard.disableAutoPurge = true


디폴트 값은 false 입니다. 그러니까 여러분이 true로 세팅하시면 더이상 필요하지 않거나 여러분 앱의 texture memory 소비를 많이 잡아 먹는 scene 이 있으면 여러분이 직접 pause 해 주셔야 합니다. 이 자동 pause 기능은 안드로이드 디바이스에서는 작동되지 않습니다.


만약 매뉴얼 purging 도 원하지 않고 automatic scene purging 도 원하지 않으면 storyboard.purgeOnSceneChange 프로퍼티를 true로 해 놓으세요. 그러면 scene이 바뀔 때마다 전환될 scene을 제외한 모든 scene들이 purge 될 겁니다.


좀 더 자세한 내용은 storyboard.* API documentation을 보세요.


반응형


반응형

Detecting Object and Screen Taps

Posted on

. Written by



이전에 touch 이벤트를 이용해서 Corona SDK에서 어떻게 detect touches를 하는지 보여드렸었습니다. 아실 지 모르겠지만 quick touch나 quick tap을 감지하는 훨씬 간단한 이벤트가 있습니다. 이 기능은 다양한 시나리오나 게임 또는 테스트 등에 아주 유용하게 사용될 수 있습니다.


The “tap” event


Tap은 touch와는 다르게 한번 손가락이 움직이면 이 이벤트는 cancelled 됩니다. 이것은 quick touch로도 설명될 수 있죠. 이런 경우는 multiple quick tap들을 detect 해야 될 경우에 필요합니다. (예를 들어 특정 객체나 스크린에 double-tap을 하는 경우)


Event Listener


오브젝트에 이벤트 리스너를 추가하기 전에 (혹은 global Runtime object에 추가하기 전에) 여러분은 tap이 일어나면 어떤 일이 발생하는지에 대해서 정하셔야 합니다. 다른 이벤트들과 마찬가지로 이 부분에서 이벤트 리스너 함수가 나오게 되거든요. 아래 예제가 있습니다.


local function onImageTap( self, event )
    print( self.id .. " was tapped." )
end


그 다음으로는 객체에 tap 이벤트 리스너를 추가하시면 됩니다.


local object = display.newImage( "image.png" )
object.id = "myObject1"
object.touch = onImageTap  -- the function we created previously
object:addEventListener( "tap" )

아래는 똑같은 기능을 하는 건데요. 객체가 아니라 전체 스크린에서 tap 이벤트를 감지할 겁니다.


local function onScreenTap( event )
    print( "The screen was tapped." )
end
Runtime:addEventListener( "tap", onScreenTap )


touch 이벤트와 비교한다면  리스너 함수 부분이 훨씬 간단한 걸 느끼실 겁니다. 왜냐하면 이벤트 phase 부분을 체크할 필요가 없으니까요. 그리고 focus나 return value 등도 체크할 필요가 없습니다. 이렇기 때문에 여러분은 간단하게 테스트를 해야 될 때 touch event를 적용하기 이전에 이 기능을 활용하실 수 있습니다.






event.numTaps


이 tap 이벤트를 touch 이벤트 대신에 사용하시게 되면 event.numTaps 프로퍼티를 통해서 몇번의 탭이 일어났는지를 쉽게 알아낼 수 있습니다. touch event로 구현하려면 훨씬 더 복잡하게 해야 되는 부분이죠.

대개 체크하는 touch 수는 1이나 2 입니다. 하지만 tap이 충분히 빠르다면 3도 체크할 수 있겠죠. 짐작하시겠지만 이 기능은 double-tap을 감지하는데 사용됩니다. 예를 들어 double-tap 했을 경우에만 어떤 동작을 하는 객체가 있다면 이 event.numTaps를 사용하시면 좋을겁니다. 아래 예제 처럼요.


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

function object:tap( event )
    if (event.numTaps >= 2 ) then
        print( "The object was double-tapped." )
    end
end

object:addEventListener( "tap" )


또한 여러분은 tap event가 일어난 위치를 알기 위해 event.x, event.y 를 사용할 수 있습니다.

좀 더 자세한 내용은 tap event documentation 를 참조하세요.



반응형


반응형
Posted on . Written by


요즘 점점 관심을 모으고 있는 것 중에 "놓고 간 곳에서 pick up" 하는 기능이나 멈추고 다시 시작하는 과정에서 매끄럽게 동작이 이뤄지는 것이 있습니다. 점점 앱이 이전에 stop 했던 곳에서 다시 시작하는 기능이 많아 졌습니다.

유저가 마지막에 있었던 곳에서 다시 앱을 시작하도록 하는것은 state saving 로 불리기도 하고 이 주제가 오늘 제가 다룰 주제입니다. 이곳에 포스트된 튜토리얼들은 여러분이 앱을 개발하면서 필요로 하는 부분에 대해 어떻게 해야 하는지를 알려주는 튜토리얼이 되도록 구성하고 있습니다.





Resuming Suspended Apps


디폴트로 앱이 suspended 됐을 때 (예를 들어 유저가 폰의 홈버튼을 눌렀을 때 등) 여러분 앱은 실제로 중지된것이 아닙니다. 그 앱은 suspended state로 됩니다. 그리고 유저가 간 곳에서 떠날 때 (left off) 그 앱은 다시 실행됩니다. 이렇게 suspended 상태에서 앱이 끝나는 경우는 너무 많은 앱을 띄워서 메모리가 부족할 때 device의 operating system이 suspended 상태의 앱을 quit 시킬 때 입니다.

이런 default로 설정된 상황을 바꾸려면 iOS 앱의 경우는 build.settings에서 UIApplicationExitsOnSuspend 를 true로 놓으면 됩니다. 디폴트는 fault 인데요. 그 의미는 이 앱이 left off 상황일 때 자동적으로 재시작 되도록 한다는 의미입니다. (OS가 이 앱을 quit 하는 경우는 해당이 안 되겠죠.)


iphone = {
  plist = {
    UIApplicationExitsOnSuspend = true
  }
}


대부분 유저는 본인의 의지로 앱을 떠납니다. 하지만 가끔 그렇지 않을 때도 있습니다. (전화가 걸려 온다던지 하는 경우) 그래서 그런 경우 user's experience를 깨지 않도록 재시작(resume) 상황에서 뭔가를 작동하도록 만드는 것은 개발자의 판단에 달려있습니다. 이 상황에 딱 맞는 예는 게임하는 상황입니다.
예를 들어 두들 점프 (Doodle Jump)를 하고 있는 중간에 앱을 닫았고 나중에 다시 돌아올 때 이 게임을 pause screen으로 할 수 있습니다. 만약 전화통화가 끝났는데 바로 character가 점프 해 있는 상황이 된다면 그 판은 여러분의 high score를 깨기 힘든 상황이 될 겁니다. pause screen으로 재시작하는 것은 user-experience 관점에서 훨씬 좋은 흐름일 것입니다.

다행히도 코로나 (Corona)는 여러분의 앱이 suspend 됐거나 resume 될 때 system events를 사용할 수 있도록 합니다. 아래에 앱이 suspend 됐을 때 어떻게 게임을 pause 시키는지를 보여주는 예제가 있습니다.


local function onSystemEvent( event )
    if (event.type == "applicationSuspend") then
        pause_game()
    end
end
Runtime:addEventListener( "system", onSystemEvent )


pause_game() 함수가 무슨일을 하는지는 여러분이 구현하기 나름이지만 대략 어떤 일을 해야 될지는 알겠죠? 일단 global Runtime객체에 리스너를 추가했습니다. 그리고 이 이벤트가 call 됐을 때 실행 될 특정 함수를 지정했습니다.

그 함수안에서는 event.type 이 "applicationSuspend" 인 것을 체크해서 그럴 경우 실행할 함수를 명시했습니다.


Resuming from a “cold” start


지금 다룰 시나리오는 조금 어렵습니다. 여러분 앱이 cold로 시작 됐을 때 (예를 들어 suspended가 아니라 closed나 quit 상태에서 시작됐을 때) 디폴트 behavior는 처음 (main.lua) 부터 새로 시작하는 것일 겁니다.

첫번째로 하셔야 될 일은 현재 상태를 save 하셔야 되는 겁니다. 어떤 방법으로 저장할지는 여러분 마음이지만 table 형태로 데이터를 저장하고 이것을 JSON 스트링으로 converting 하고 file로 저장한 다음에 나중에 로드하시기를 권고 드립니다. (이런 경우 코로나에서 file 읽고 쓰기 부분을 참조하세요.)

다음으로 파일을 파일을 로드하고 앱에게 시작됐을 때 이 데이터를 근거로 어떤 일을 하라고 알려 주어야 합니다. 이럴 경우 suspended 상태이건 cold 상태이건 앱이 새로 시작할 때 이전 상황과 이어지는 상황에서 앱을 다시 시작할 수 있을 겁니다.

데이터를 저장하는 시기는 조금 전에 셋업한 시스템 이벤트 리스너에서 event.type이 applicationExit 일 경우를 체크해서 수행하시면 도비니다. 그리고 나서 event.type이 applicationOpen일 경우 저장해 뒀던 데이터를 로드하고 그것을 근거로 어떤 일들을 수행하면 됩니다.


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

        save_state()    

    elseif (event.type == "applicationOpen") then

        load_saved_state()

    elseif (event.type == "applicationSuspend") then

        pause_game()

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


예제를 보니 그렇게 복잡해 보이지 않죠?

save_state()나 load_saved_state() 함수와 관련해서는 더이상 자세한 내용을 언급하지는 않겠ㅅ브니다. 왜냐하면 이것은 앱마다 다 다를 테니까요. 적어도 특히 게임에서는 객체들이 화면상에 있는 정보인 프로퍼티들과 위치들 같은 데이터들을 저장해야 할 겁니다. (그래야지 load_saved_state() 함수에서 그 객체들을 이전에 있던 위치에 display 할 수 있으니까요.)

유저들은 마음대로 그 앱을 열었다 닫았다 할 겁니다. 그리고 그러한 상황에서 유저들에게 자연스러운 앱의 진행상황을 제공하려면 여러분은 그 앱이 suspended나 cold state에서 다시 재시작할 때 유저들이 아무런 끊김없는 혹은 뭔가 당황스러운 상황이 없는 화면 전개를 보여주는데 신경을 쓰셔야 될겁니다.

반응형

Best Practices for Organizing Projects

2012. 7. 11. 11:33 | Posted by 솔웅


반응형
Posted on

. Written by


샘플코드나 다른 예제 혹은 이 웹사이트의 튜토리얼을 포함해서 코로나를 배우는데 명료하게 알기 힘든것이 바로 프로젝트를 어떻게 organize 할 것이냐 입니다. 알려 드릴 사항은 모든것이 여러분의 project 폴더 아래로 가게 된다는 거고 나머지는 여러분 취향대로 하시면 되는겁니다. 그리고 sub folder 도 지원 됩니다. 여기까지가 지금까지 이 주제와 관련해서 저희가 한 말이죠.


그런데 여러분의 프로젝트가 규모가 더 커지면 이 지식만 가지고는 organize 하기가 좀 부족합니다. 점점 더 많은 리소스들이 여러분 프로젝트에 추가 될 겁니다. 오디오 파일이나 이미지, 비디오, 루이 모듈 같은 것들이요. 이걸 제대로 정리하지 못하면 개발하는데 아주 복잡하게 만들죠.


이 튜토리얼은 코로나 SDK 프로젝트 organization 테크닉의 전부를 말하지는 않습니다. 단지 좀 더 효율적으로 관리할 수 있는 방법을 제안할 뿐입니다.




Project Structure


모든걸 간단하게 가지고 가는 것은 여러분의 프로젝트가 잘 organize 되어야하고 그로 인해 유지 관리하는 것이 쉽도록 하는 겁니다. 제가 소개할 이  organization technique 은 대부분의 프로젝트에서 쉽게 셋업하고 쉽게 이해하고 쉽게 scale (여러분의 프로젝트가 점점 더 커지게 될 때를 대비해서) 하는데 충분히 도움이 될 겁니다.


프로젝트 폴더를 구성할 때 여러분이 따라야할 기본적인 전제는 비슷한 파일들을 다른것과 별도로 보관하라는 겁니다. 비슷하고 다르다는 기준은 type 이나 그 종류를 말할 수도 있습니다. 아래 샘플로 제공하는 프로젝트 hierarchy 구조가 있습니다.


  • Project_Folder/ (top-level)
    • images/ (folder)
    • audio/ (folder)
    • videos/ (folder)
    • data/ (folder)
    • scripts/ (folder)
    • main.lua
    • config.lua
    • build.settings
    • Default.png
    • Default@2x.png
    • Icon.png
    • Icon@2x.png


위의 폴더 구조 예제에서 top-level 디렉토리(폴더) 에 있는 것은 가장 중요한 것은 main.lua 입니다. 다른 config.lua, build.settings 같은 파일들도 top-level에 있어야 된는 겁니다.

이 top 에 있는 폴더들은 그 이름만 봐도 어떤 것들인지 쉽게 이해가 갈 겁니다. 아래 간단히 설명을 덧 붙이겠습니다.

  • images – image files with the extension of (most-likely) .png or .jpg.
  • audio – audio files (.caf, .mp3, .ogg, etc.)
  • videos – video files (.avi, .mp4, etc.)
  • data – read-only data files used by your app. They can really be any format, but common ones include .json and .xml.
  • scripts – other Lua files (apart from main.lua). More on this in a moment.


더 이상의 설명은 필요 없겠죠? script 폴더만 제외하고는요.

여러분이 util.lua를 사용해야 한다고 가정해 봅시다. 일반적이로 이 파일을 top-level에 위치시키는데요. (main.lua 하고 같은 위치에요). 만약 그렇게 할 경우 아래처럼 불러올 수 있습니다.


local util = require( "util" )


만약 이 util.lua를 scripts 폴더 아래에 넣었다면 위와 같이 하면 작동하지 않을 겁니다. 폴더이름에 점을 붙이고 모듈 이름을 넣으셔야 합니다. 아래처럼요.


local util = require( "scripts.util" )


이미지를 불러올때는 폴더 이름을 쓰고 / 를 넣고 이미지 이름을 넣으셔야 합니다. script에서는 .을 사용했는데 여기서는 /를 사용해야 합니다. 사실 스크립트만 다릅니다. 아래 그 예제가 있습니다.


local obj = display.newImage( "images/corona.png" )


Scaling Projects

여러분의 프로젝트에 더 많은 이미지나 스크립트 그리고 다른 파일들이 추가되면 이 sub directory 만으로도 충분하지 않게 되죠. 그렇게 되면 서브 디렉토리 안에 또 다른 서브디렉토리를 넣어야 겠죠. 아래 depth 가 더 늘어나도 그 안에 있는 파일이나 스크립트를 require 하는 방법은 위에서 설명한 것과 같습니다. 아래는 몇단계의 level 이 있는 게임을 상상하고 폴더 구조를 구성해 봤습니다.


  • Project_Folder/ (top-level)
    • images/ (folder)
      • level-001/ (folder)
      • level-002/ (folder)
      • level-003/ (folder)
    • audio/ (folder)
      • level-001/ (folder)
      • level-002/ (folder)
      • level-003/ (folder)
    • videos/ (folder)
      • level-001/ (folder)
      • level-002/ (folder)
      • level-003/ (folder)
    • data/ (folder)
      • level-001/ (folder)
      • level-002/ (folder)
      • level-003/ (folder)
    • scripts/ (folder)
      • level-001/ (folder)
      • level-002/ (folder)
      • level-003/ (folder)
    • main.lua
    • config.lua
    • build.settings
    • Default.png
    • Default@2x.png
    • Icon.png
    • Icon@2x.png


위의 구조와 다른 점은 이미지,오디오, 비디오, 스크립트 디렉토리들이 이제 그 안에 또 sub-directory들을 가지고 있다는 겁니다. 각각의 레벨 별로 구분되는 서브 폴더들을요. 10단계 20단계 되는 게임을 상상해 보시면 이런식으로 개발하는 것이 훨씬 더 간편하겠죠? 오랫동안 유지 관리하는데 훨씬 유리하게 잘 조직화 돼 있다고 할 수 있습니다.


50단계가 있는 게임인데 모든 리소스를 top-level  프로젝트 디렉토리에 보관하고 있다고 상상해 보세요. 유지 관리하는 관점에서 보면 악몽이 되겠죠?


이 튜토리얼에서 소개한 대로 프로젝트 구조를 관리하시던지 아니면 여러분만의 방법을 사용하시던지 Corona SDK 프로젝트를 시작할 때 프로젝트의 파일구조를 충분히 고민하시고 난 후 시작하는것이 나중에 가서 훨씬 더 도움이 되실 겁니다.



반응형


반응형
Posted on . Written by

필요없는 객체를 제거하고 변수를 처리하는것은 사소한 일 처럼 느껴지지만 코로나의 신규 개발자이든 경력이 있는 개발자이든 항상 질문하는 내용 중 하나입니다. 이 일을 처리한 결과 일어나는 일은 아주 간단합니다. 하지만 이 일을 제대로 처리하지 않아서 일어나는 메모리 손실이나 속도저하 그리고 crash 같은 것들은 아주 큰 영향을 미칩니다.


Removing display objects


코로나 display object(그리고 다른 타입들의 객체들)은 object:removeSelf()메소드를 사용해서 remove될 수 있습니다. 이 메소드를 call 하는 것은 쉽습니다. 이 메소드를 call 해서 얻어지는 결과는 현재 보이는 객체가 보이지 않게 되므로 확인하는것도 쉽습니다. 

이것을 잘못 사용할 수 있는 경우는 단지 그 객체가 스크린에서 없어지느냐 아니면 앞으로 더 이상 사용될 필요가 없는것이냐를 구분해야 되는데 있습니다. 객체에 어떤 참조를 store하기 위해 사용했던 변수는 skeleton 테이블을 가지고 있습니다. 그것은 객체를 remove 한 이후에도 계속 있게 됩니다. 여러분이 object:removeSelf()를 하시고 난 후 그 변수를 print 하면 어떻게 될까요?


-- create the object
local object = display.newImage( "image.png" )

-- remove the object

object:removeSelf()

print( object )



이 변수는 여전히 table value를 가지고 있는것을 보실 수 있습니다. 그러니까 아직까지 그 변수는 empty 가 아닌겁니다. 그 의미는 이 변수가 아직 메모리를 차지하고 있다는 것이고 여러분이 Lua 에게 이 변수는 더 이상 사용하지 않을 것이라는 것을 말해야 된다는 것을 의미합니다. 그 다음에야 clean up 할 수 있죠. 그 방법은 그 변수에 nil을 할당해주기만 하면 됩니다. 그러면 그 변수를 완전하게 remove 하게 되는 겁니다.


object:removeSelf() 문서에서 예제 부분을 보시면 아래와 같이 변수에 nil을 할당하는 부분을 보실 수 있을 겁니다.


object = nil

다른 객체를 참조하고 있는 살아있는 변수를 없애는데도 이와 똑같이 해 주시기만 하시면 됩니다. 살아있는 변수의 의미는 자동적으로 clean up 되지 않는 글로벌 변수이든지 로컬 변수이든지 어떤 객체에 대한 참조를 하고 있어서 완전하게 remove 되어야 할 필요가 있는 변수를 말하는 겁니다.




Local variable gotcha


I touched on it already, but local variables (that is, variables that are declared with the word local preceding the variable name), get cleaned up automatically at the end of their block of code.

로컬 변수는 그 변수를 감싸고 있는 블럭이 사용되지 않는 순간에 자동적으로 clean up 됩니다.


즉 여러분이 어떤 객체를 어떤 독립된 블럭 안에서 생성했다면 이 함수가 실행되고 난 후에 그 객체는 자동적으로 remove 된다는 겁니다. (그 변수 선언은 앞에 local 이 붙어야 합니다.) . 그렇게 remove 되면 여러분은 그 변수에 다시 access할 수 없게 되죠.


아래 그 예제가 있습니다.


local function create_and_abandon()
    local outcast = display.newImage( "image.png" )
    outcast.x, outcast.y = 160, 240
end

create_and_abandon()


위 예제에서 create_and_abandon() 함수는 간단히 하나의 객체를 생성하고 (로컬변수를 사용해서) 그 객체의 위치를 설정해 주는 일을 합니다. 이 함수의 마지막 end 부분에서 이 outcast 변수는 더 이상 access 될 수 없게 됩니다. 그 객체를 내버려 두고 떠나서 더이상 다뤄지거나 remove 될 수 없게 되는 겁니다.

이러한 상황을 방지하기 위해서는 이 변수 선언을 함수 블럭 바깥에서 선언하셔야 합니다. 아래에 그 예제가 있습니다.


local outcast  -- forward declaration

local function create_and_abandon()

    outcast = display.newImage( "image.png" )
    outcast.x, outcast.y = 160, 240
end

create_and_abandon()


-- we can now remove the object

outcast:removeSelf()
outcast = nil



이 예제에서는 이 함수가 끝난 이후에도 우리는 outcast 변수에 access 할 수 있게 됩니다.  객체를 생성할 때 local 이 빠져있죠? 단지 블럭 바깥에서 변수를 생성할 때 local을 붙였습니다. 이 의미는 이 변수가 그 블럭내의 local 이 아니라는 거죠. 이 outcast 변수는 local 변수 입니다. 단지 저 함수 블럭 내의 local 이 아닌 겁니다. 그래서 함수가 다 실행되고 나서도 이 변수를 우리가 접근해서 컨트롤 할 수 있는 겁니다.


여기까지가 코로나에서 변수나 객체를 remove 하는데에서 알아야할 내용 전부 입니다. 이 튜토리얼에 있는 내용은 아주 중요한 부분 입니다. 코로나를 새로 접하시는 분들이 항상 혼동을 일으키는 부분입니다. 이 부분을 잘 이해하셔서 여러분 앱에서 hard-to-trace 문제를 일으키지 않도록 주의하셔야 합니다.

좀 더 많은 정보를 완하시면 Display Objects and Stage 가이드의 Removing Objects Properly  섹션을 보세요.


반응형

How to Use Custom Events in Corona

2012. 6. 28. 02:46 | Posted by 솔웅


반응형

How to Use Custom Events in Corona

Posted on . Written by



1년 쯤 전에 (2011년 6월), Corona Event Model 에 대해서 다뤘었습니다. 거기서 코로나에서의 이벤트가 정확히 무엇인지 설명을 했었죠. 이벤트가 발생했을 때 어떻게 여러분이 hook 해서 코로나의 여러 기능들을 적용할수 있는지에 대해서도 다뤘었습니다.

하여간 지난번에는 코로나에서 제공하는 built-in 이벤트들에 대해서 그 API를 주로 다뤘었습니다. 그 때 제가 다루지 않았던 부분은 개발자가 스스로 custom 이벤트를 정의할 수 있다는 것입니다. 그리고 그 이벤트가 발생했을 때 listen 할 수 있다는 것하구요.(개발자 스스로 완벽하게 컨트롤 할 수가 있죠)


이 튜토리얼은 여러분만의 custom 이벤트를 어떻게 정의하고 dispatch 하는지에 대해 알려드릴겁니다.  여러분은 여러분의 게임이나 앱에 이 기능들을 적용할 수 있겠죠. 만약 여러분이 코로나 이벤트 모델에 대해서 그렇게 친숙하지 않다면 우선  The Corona Event Model Explained부분을 읽고 난 후 아래 부분을 읽기를 권장합니다.






The Scenario


이 튜토리얼의 시나리오를 한번 짜 보면 한개의 object와 하나의 player 가 있습니다. 여기에 그 건강상태와 관련된 많은 이벤트를 적용해 볼 겁니다. 우리는 이것을 health 이벤트라고 부르겠습니다. (이름은 여러분이 원하는대로 붙이시면 됩니다. 단지 다른 Corona event와 이름이 중복되거나 이미 있는 다른 custom event와 이름이 겹치지만 않으면 되요.)


Dispatching an Event


게임이 시작되면 어떻게 되는지 한번 얘기해 봐요. player의 건강은 75%일 겁니다. 그는 med pack을 집을거고 그러면 그의 건강의 100%가 될겁니다. 이 부분에서 우리는 health 이벤트를 dispatch 할 겁니다. event.type을 full로 할거구요. 여기서 여러분이 첫 번째로 할 일은 event table을 만드는 겁니다. 이 테이블은 listener 함수에 보낼 모든 데이터를 가지고 있게 될 겁니다. 일단 event table 셋업이 완료 되면 여러분은 object:dispatchEvent() 메소드를 call 할 수 있습니다.


local event = {
    name = "health",
    type = "full",
    healthPercent = 100
}
player:dispatchEvent( event )


event table에 있는 name 키는 object:dispatchEvent() 메소드에 전달될 건데요. 이게 유일하게 반드시 있어야 될 항목입니다. 나머지는 완벽하게 여러분 마음에 달려있습니다. 옵션이죠.

아래에 보면 health level이 low나 empty일 때 어떻게 health를 dispatch 하는지 보여 드립니다.


local event = {
    name = "health",
    type = "low",
    healthAmount = 10
}
player:dispatchEvent( event )
local event = {
    name = "health",
    type = "empty",
    healthAmount = 0
}
player:dispatchEvent( event )


Listening to Events


custom event에 object listen을 갖게 하는 방법은 코로나의 predefine된 이벤트에 listen을 다는 것과 똑 같이 하시면 됩니다. 간단하게 event name을 명시하고 (dispatch 했을 때 명시된 이벤트 이름이요) 그런 type의 event가 receive 됐을 때 그 함수가 call 되게 됩니다.

아래 예제는 player object가 dispatch 된 health 이벤트들에 어떻게 object listen 하게 되는지를 보여드리고 있습니다.


local function health_listener( event )
    if event.type == "full" then

        print( event.healthAmount )  -- 100
        turn_health_bar_green()    

    elseif event.type == "low" then

        print( event.healthAmount )  -- 10
        warn_player()
        turn_health_bar_red()

    elseif event.type == "empty" then

        print( event.healthAmount )  -- 0
        initiate_game_over()
    end
end
player:addEventListener( "health", health_listener )


table listener를 사용함으로서 self object로 그 object를 reference 할 수 있게 됩니다.


local function health_listener( self, event )
    if event.type == "full" then

        print( event.healthAmount )  -- 100
        turn_health_bar_green()    

    elseif event.type == "low" then

        print( event.healthAmount )  -- 10
        warn_player()
        turn_health_bar_red()

    elseif event.type == "empty" then

        print( event.healthAmount )  -- 0
        initiate_game_over()
    end
end
player.health = health_listener
player:addEventListener( "health", player )


어떻게 리스너 함수에서  healthAmount 변수가 접근 될 수 있는지 주목해 주세요. 이것이 Corona의 이벤트 모델이 진정 가치있는 부분입니다. 언제든지 이벤트의 특정 타입으로 object를 respond 하기를 원한다면 여러분은 object:addEventListener() method를 사용해서 그렇게 하실 수 있습니다. 역으로 object:removeEventListener() method를 사용해서 그 object의 listening을 stop 시킬수도 있습니다.


Things to Keep in Mind

믿으실지 모르겠지만 custom event의 dispatch와 listening 을 모두 끝냈습니다. 이제 여러분이 custom event를 사용하실 일 만 남았는데요. 몇가지 염두에 두셔야 할 것을들 정리했습니다.
  • 코로나에 이미 내장된 코로나 이벤트와 비교해서 살펴보면 custom event에서는 이벤트가 dispatch 될 때 반드시 여러분이 컨트롤 해야 합니다.
  • 코로나에 이미 내장된 코로나 이벤트와 마찬가지로 이벤트를 dispatch 되게 하기 위한 object listening을 하지 않으면 아무 일도 일어나지 않습니다.
  • object:dispatchEvent() method에 pass 된 이벤트 테이블은 name 키가 필요합니다. 그 나머지는 옵션입니다.


물론 여러분은 이벤트를 dispatching 하는 대신 direct로 함수를 call 하실 수 있습니다. 이벤트를 사용하는 가장 큰 이유는 여러분이 원하면 언제든지 그 이벤트에 대한 listening을 stop 시킬수 있다는 겁니다. (만들고 관리하고 함수를 call 할 때마다   Boolean 변수를 사용해서 항상 체크하고 하는것보다 훨씬 간편합니다.).

이벤트를 사용하는것은 또한 간단한 코딩을 지원합니다. 그러므로 유지보수를 더 수월하게 하죠. 어떤 종류의 custom 방식으로 코로나의 이벤트 모델을 사용하실건가요?




반응형


반응형

Guided Tour of Corona’s API Reference

Posted on . Written by



이번주의 튜토리얼은 약간 다른겁니다. 코로나의 다른 기능들에 대해 다루기 보다는 어느 특정 기능보다도 더 중요하다고 할 수 있는 전체 코로나 개발에 대한 측면에서 바라볼 겁니다. 여러분이 오늘 배우실 것은 여러분이 코로나 프로젝트를 진행하면 반드시 사용하셔야 하는 분야 입니다. 바로 Corona SDK API Reference 이죠


Corona API Reference는 코로나의 특정 기능을 어떻게 사용하느냐에 대해서 배울 수 있는 문서 입니다. 특정 이벤트나 객체 타입에 대한 정보도 얻을 수 있고 항상 사용하셔야 할 자원입니다. 그러므로 이에 대한 튜토리얼을 소개하는 것은 당연하게 필요한 것이겠죠.


만약 여러분이 아직까지 API Reference를 충분히 활용하지 못하고 계신다면 처음에는 너무 많은 정보와 배울것들이 있다고 느낄겁니다. 오늘 제가 전반적인 내용을 훑어서 알려드리겠습니다. 그리고 이 Corona API reference가 더 좋아질 수 있도록 여러분이 도움을 주실 수 있는 방법도 알려드리겠습니다.


Interface Overview


코로나로 앱을 개발하기 위해 API를 많이 참조하시는 분은 지난주 이 API reference 웹 페이지가 새로 개편된 것을 보셨을 겁니다. 예전 페이지를 많이 참조하셨던 분들은 이번에 인터페이스가 완전히 바뀌어서 약간 불편해 하시는 분도 계실 수 있습니다. 하지만 조금 더 써보시면 훨씬 좋아졌다는 것을 느끼실 겁니다. 새 창이나 새 탭에 Corona API Reference를 띄우시고 아래 내용을 쭉 따라가 보세요.


Navigation and Sidebar


위쪽 sidebar에 있는 navigation bar는 보시면 아실 겁니다. 코로나의 전체 documentation을 크게 3부분으로 나눠서 각 section으로 갈 수 있도록 만들었습니다.

왼쪽은 각 세부 섹션들에 직접 갈 수 있는 링크들이 있습니다.  Jump to... 드롭 다운 메뉴도 이 좌측 링크들과 비슷한 역할을 합니다. 하지만 좌측 메뉴는 그 세부 섹션 페이지로 가는데 반해 이 드롭다운 메뉴는 현재 페이지 내에서 해당 항목으로 가는 겁니다. (페이지가 바뀌지는 않습니다.)





이 API Reference page는 코로나의 모든 함수, 이벤트, 프로퍼티, object methods 그리고 data type 들에 대한 리스트가 있는 index 입니다. 여러분이 무엇을 찾는지 확실하게 아신다면 ctrl+F 를 누르셔서 검색하는 게 가장 빠른 방법입니다. 또 다른 유용한 기능은 아래쪽에 있는 바로 breadcrumbs 라고 합니다. 이 하단의 바는 항상 있는데요. 여러분이 지금 어디에 있는지 알려주는 역할을 합니다.






API Reference Columns


API Reference index (home) page에 보면 3가지 주제로 나뉘어져 있는 걸 보실 겁니다. -Libraries, Events, Types -




Libraries  컬럼은 Corona에 내장된 함수의 리스트 입니다. (display.newImageRect()나  storyboard.gotoScene() 같은). Events 리스트는 다양한 이벤트의 종류를 열거했습니다. 그리고 각 이벤트들에 대한 연관된 프로퍼티들도 있구요.Types 는 코로나의 여러 데이터 타입들을 포함하고 있습니다. 그리고 Library 함수들에 의해 생성된 객체들하구요 그와 관련된 프로퍼티와 메소드들(특정 객체 타입에 attached 된 함수들)이 있습니다.


Reference Pages


각각의 API reference 페이지들은 특정 함수와 프로퍼티 등에 대해 여러분들이 알아야할 모든 정보가 있습니다. 만약 여러분이 이 library 함수에 대한 아무 페이지나 보시게 되면 -예를 들어 display.captureScreen() 같은 - 아래 이미지와 같은 내용을 보실 겁니다. 보시면 아시겠지만 이전의 API reference 보다 더 많은 정보들이 있습니다.


딱 보면 어떤 종류의 객체(혹은 데이터 타입)를 이 함수가 return 하는지 Revision (Build)이 뭔지, parent library 는 뭔지 그리고 관련된 키워드는 무엇이 있는지 그리고 연관된 링크(다른 API나 튜토리얼, 외부 웹페이지 등) 등을 보실 수 있습니다. 다른 타입의 API 페이지는 윗 부분에 다른 내용들이 있을 겁니다. 그건 그 타입에 걸맞는 내용들입니다.




좀 더 아래쪽에는 overview가 있습니다. 여기에는 일반적으로 조심해야 될 부분이나 헛갈리는 부분에 대한 설명이나 syntax structure, 관련된 함수 파라미터들 그리고 예제들이 있습니다.


Searching the Documentation


만약 여러분이 특정 API 페이지를 찾으셔야 한다면 home/index 페이지로 가세요. 그리고 브라우저에서 page search 를 하시던가 좌측 메뉴 링크를 이용하시던가 Jump To 드롭다운 메뉴를 이용하세요. 그런데 그 API의 정확한 이름을 모르신다면 전체 Corona Docs에서 키워드를 사용해 검색을 하실 수 있습니다. (구글에서 검색하시는 것처럼요). 검색 필드는 오른쪽 위에 있습니다. (아래 이미지를 참조하세요.)





검색 결과는 구글이 코로나 내의 블러그나 API reference page 들에서 검색한 내용입니다. images를 타입하시고 엔터키를 쳐 보세요. 그 정확도에 놀라실 겁니다.


Help Improve the API Reference


새로운 API reference는 이전 API reference 보다 많이 개선 되었습니다. 하지만 완벽한것은 아니라는 것을 저희들은 압니다. 우리가 중요한 것을 간과하거나 잊어버리고 만들었을 수도 있습니다.  (그것이 이전 API reference에 있었을 수도 있고 없었을 수도 있구요.)  어딘가 오탈자가 있을 수도있구요. 예제가 있어야 되는데 빠져 있을 수도 있구요.


코로나 API Reference 웹사이트는 계속 보완을 해 나갈 겁니다. 그리고 저희 스스로도 계속 보완할 내용을 찾는 작업을 할테고 여러분의 feedback 도 계속 받겠습니다. 특정 API 페이지에서 어떤 잘못 된 것을 발견하시거나 빠진것을 발견하시면 저희에게 알려 주세요. 그 방법은 아주 간단합니다. 각 API Reference Page의 아래쪽에 3개의 링크를 보실 수 있을 겁니다. 관련된 링크를 누르고 그 내용을 넣어 주시면 됩니다.




그러니까 API 에 관련해서 잘못 된 부분을 알려주시려면 이 블로그 글의 댓글을 달기 보다는 해당 API 페이지에 가셔서 밑에 있는 링크를 누르고 알려 주시면 감사하겠습니다. 그 링크를 누르시면 몇개의 checkbox들이 있을 겁니다. 그리고 comment 필드도 있구요. 관련 사항에 체크하시고 가급적이면 자세하게 코멘트를 달아 주세요.


여러분들 중에 이미 많은 분들이 이 feedback form을 사용해 보셨을 겁니다. 이 웹사이트를 지난주에 오픈했는데도 이미 많은 글들이 올라왔거든요. 저희들의 실수를 지적해 주신 모든 분들께 감사드립니다. 그리고 의견을 올려 주신분들도 감사드리구요. 여러분들 중에 몇분은 아주 잔인할 정도로 정직하시더라구요. 그게 바로 저희들이 원하는 겁니다. 계속 와 주세요. 지금 코로나의 함수들, 이벤트들 그리고 객체 프로퍼티와 메소드들에 대해 정통한 정보를 찾아서 사용 하세요.


Still confused?


만약 API Reference 페이지가 아직 헛갈리시면 그리고 어디서부터 시작해야 할 지 모르시면 우선 보실 필요가 있는 곳이 Guides 섹션 입니다. 여기에는 초보자 분들을 위한 Corona SDK Quick-Start 가 있습니다.



반응형


반응형

Applying Basic Animation with Transitions

Posted on . Written by


하지만 그 외에도 display objects에 대해 기본적인 애니메이션 효과를 제공하도록 하는 기능들도 유용하게 사용 하실 수 있습니다. 객체를 계속 회전시킨다던지 투명도를 주었다 뺐다 하던지 한 곳에서 다른 곳으로 이동을 시킨다던가 아니면 위 세가지를 모두 합쳐서 어떤 효과를 낸다던가 하는 일을 코로나의 transition library 를 통해서 구현하실 수 있습니다.


이 글은 주로 이 transition.to()함수에 대해 다룰 겁니다. 코로나의 transition library를 사용할 때 가장 많이 사용해야 할 함수죠. 대략적으로 정리하자면 transition.to() 는 두개의 파라미터를 사용합니다. 애니메이션 효과를 줄 객체와 그리고 회전, 투명도 x,y 값의 combination으로 구성되는 ending property 들의 조합인 table로 구성돼 있습니다. 그리고 이 transition에 걸릴 시간과 옵션으로 이 transition이 끝났을 때 호출될 함수도 넣을 수 있습니다. 이 transition이 시작되면 이 객체의 현재 value들은 여러분이 정해준 값으로 "tweened" 됩니다.


여러분이 코로나를 이용해 게임등의 앱을 개발하면서 충분한 시간을 투자할 수 없는 상황이라면 아마도 이 함수를 사용하실 겁니다. 이 함수는 아주 많이 유용하거든요. 여러분도 아시게 되겠지만 이 함수는 사용하기에 아주 많이 쉽기도 하거든요.




Fading an Object


아주 기초적인것 부터 시작하겠습니다. 우리는 객체를 생성할 거고 이 객체가 점차 투명해져서 사라지도록 만들 겁니다. 여러분들도 이미 아시겠지만 display object의 alpha 프로퍼티가 그 객체의 opacity를 담당하고 있습니다. 그래서 그 프로퍼티를 transition.to() 함수에 pass 해서 1.0에서 0 이 되도록 만들 겁니다. (1.0은 투명도가 전혀 없는 상태고 0은 완전 투명해서 안 보이는 상태입니다.)

아래 예제를 보세요. 우리는 이렇게 객체가 사라지는 시간을 3초로 정할겁니다. (3000 milliseconds)


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

-- center the object

obj.x = display.contentWidth*0.5
obj.y = display.contentHeight*0.5

-- fade object to completely transparent

transition.to( obj, { time=3000, alpha=0 } )


위 코드는 객체를 생성하고 "tweens" 해서 그 alpha 프로퍼티를 1.0에서 0으로 3초동안에 변화 시키도록 하는 겁니다. 아주 간단합니다. 이제 저 객체가 완전히 투명해져서 안 보이게 되면 그 객체를 완전히 remove 해 버리죠. 이 transition이 끝났을 때 실행 될 함수를 call 할 수 있습니다. 그걸 하려면 onComplete 파라미터를 사용하시면 됩니다.


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

-- center the object

obj.x = display.contentWidth*0.5
obj.y = display.contentHeight*0.5

local function removeObject( object )

    object:removeSelf()
    obj = nil   -- nil out original reference (upvalue)
end

-- fade object to completely transparent

transition.to( obj, { time=3000, alpha=0, onComplete=removeObject } )


NOTE: 저 객체의 transition이 첫번째 argument에서 onComplete 리스너 함수까지 어떻게 진행되는지 그리고 구현하는것이 얼마나 쉬운지 보세요. 이것을 활용해서 여러 transition들에 대해 이 한가지의 listener를 만들어서 사용하실 수 있습니다. 또는 저 객체가 out of scope 일 때도 그 리스너를 사용하셔도 됩니다.


Moving Objects


transition.to() 함수의 아주 유용한 부분중의 하나는 한번에 tweening 하는데 한가지만의 프로퍼티만 사용하라는 법이 없다는 겁니다. 우리 첫번째 예제를 조금 바꿔서 그 객체를 좌상단에서 우 하단으로 움직이면서 투명해지도록 해 보죠.


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

-- set starting position (top-left of screen)
obj.x, obj.y = 0, 0

local function removeObject( object )
    object:removeSelf()
    obj = nil   -- nil out original reference (upvalue)
end

-- some variables to be used in the transition
local end_x = display.contentWidth
local end_y = display.contentHeight

-- fade object to completely transparent and move the object as well
transition.to( obj, { time=3000, alpha=0, x=end_x, y=end_y, onComplete=removeObject } )


Easing Library


코로나의 Easing Library 는 transition.to() 함수와 함께 쓰여서 transition 프로퍼티를 통해 transition의 behavior 를 하는데 다루는 데 사용되어 질 수 있습니다. 디폴트로 이 transition 프로퍼티는 easing.linear로 세팅돼 있습니다. 이것은 transition이 처음부터 끝까지 같은 공간에서 꾸준히 tween 되는 상태를 말합니다.

각 transition 프로퍼티 behaves를 설명하는 것은 약간 어렵습니다. 백문이 불여일견이라고 샘플 앱에서 Transition1과 Transition2 예제를 보실것을 권장합니다. 이 샘플은 아래 경로에 있습니다.

  • /SampleCode/Graphics/Transition1
  • /SampleCode/Graphics/Transition2


이 샘플 예제를 보시고 다른 transition type들을 적용하면서 실행해 보세요. 이렇게 하는게 가장 쉽고 확실하게 배우는 길입니다. 그리고 여러분이 배운 Corona의 transition library에 대한 모든 것을 확실히 다져 주세요.


Canceling Transitions


In the event you need to stop a transition mid-way through, you can do so by using the transition.cancel() function. However, this function requires that you store a reference to an id of the specific transition you need to cancel.

여러분이 transition이 일어나는 중간에 이를 stop 시킬 필요가 있다면 여러분은 그렇게 하실 수 있습니다. transition.cancel() 함수를 사용하시면 됩니다. 이걸 이용하시려면 해당 transition의 id를 사용하셔야 합니다.

그러니까 이 transition을 cancel 하기 이전에 그 transition의 id를 variable에 store 할 필요가 있습니다. 아래 첫번째 예제를 약간 바꾼 샘플이 있습니다. 해당 transition 아이디를 transition_id 라는 변수에 대입을 했습니다.


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


-- center the object
obj.x = display.contentWidth*0.5
obj.y = display.contentHeight*0.5

-- fade object to completely transparent
local transition_id = transition.to( obj, { time=3000, alpha=0 } )

Now, we can cancel the transition at any time by passing that stored id as the first parameter to transition.cancel(). In the following example, we’ll cancel the transition after 1.5 seconds.

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

-- center the object
obj.x = display.contentWidth*0.5
obj.y = display.contentHeight*0.5

-- fade object to completely transparent
local transition_id = transition.to( obj, { time=3000, alpha=0 } )

timer.performWithDelay( 1500, function()
    if transition_id then
        transition.cancel( transition_id )
        transition_id = nil
    end
end, 1 )


위에서도 언급했는데요. 우선 transition을 cancel 하기 이전에 그 transition이 존재해 있어야 합니다. 여러분이 실전에서 코딩을 하실 때는 반드시 해당 transition이 선언돼 있는지 확인하시기 바랍니다.

그리고 여러분이 transition.cancel()을 call 할 때는 그 transition id를 저장하기 위해 사용한 변수를 nil-out 시키시기 바랍니다. 그렇게 하지 않으면 skeleton table이 남겨져 있게 됩니다. (마찬가지로 object:removeSelf() 한 이후에도 반드시 그 변수를 nil-out 시켜 주세요.)


WARNING: Do not remove currently transitioning objects!


만약 현재 tweened 되고 있는 객체가 remove 된다면 여러분의 앱은 crash를 일으킬 겁니다. 이것은 scene change 할 때 종종 일어나는 현상인데요. scene change를 하기 전에 transition.cancel()을 현재 active 상태에 있을 법한 변수에 적용하는 것이 에러가 일어날 가능성을 미리 방지하는 방법일 겁니다. 이 부분은 exitScene 이벤트 리스너에서 수행하는것이 가장 이상적입니다. 좀 더 자세한 사항은 Storyboard Scene Events Explained를 확인하세요.

Corona의 transition library에 대한 좀 더 많은 정보는 Transitions API reference 페이지를 참조하세요. 좀 더 많은 정보들이 있습니다.

반응형

Local Notifications Guide (iOS)

2012. 6. 6. 23:35 | Posted by 솔웅


반응형

Local Notifications Guide (iOS)


이번주 화요일의 튜토리얼은 코로나의 Local Notification 기능에 대해 다루겠습니다. 현재 iOS 플랫폼에서만 가능합니다.

여러분 앱의 사용자가 local notification을 받으면 alert 하게 될 거고 소리가 날 거고 메세지가 뜰 겁니다. 그리고 그 notification과 관련된 어떤 행동을 할 수 있는 옵션들이 제공 될 겁니다.

notification을 받을 때 여러분의 앱이 foreground에 있지 않으면 (예를 들어 유저가 인터넷 서핑을 한다 던지, 페이스북을 한다 던지 등등) 유저에게 메세지를 보여줄 수 있습니다. 그리고 여러분의 앱으로 돌아갈 수 있도록 하는 옵션을 줄 수 있습니다. 그러면 여러분 앱을 좀 더 많이 사용할 수 있도록 하겠죠.

Notification은 아주 customizable 합니다. 소리를 내게 할 수도 있고 메세지를 보일수도 있고 앱 아이콘에 badge count를 넣을 수도 있습니다. 또한 user가 특정 notification에 답했을 때 정확히 여러부닝 필요로하는 것을 수행하기 위해서 custom data를 여러분 앱에 pass 할 수도 있습니다.

오늘 다룰 튜토리얼에서는 코로나의 이 local notification 에 대해 알아야 할 모든것에 대해 다루겠습니다.





Local vs. Push Notifications


Local Notification하고 Push Notifications 하고 헛갈리지 마세요. 유저 입장에서 보면 alert이나 message, sound, badge icon 등등 모두 똑 같겠지만 개발자 입장에서는 분명히 다르니까요.

Push와 Local Notification의 다른점은 Push notification은 원격 서버에서 와서 유저에게 push 되는 겁니다.(내부적으로-locally- 따로 정해진 스케줄에 따라 일어나는 것이 아닙니다) 이건 셋업하는데 조금 더 복잡합니다 (보안 문제 같은것들을 생각해야 하거든요.) 그리고 서버를 셋업해야 되기 때문에 비용도 높은 편입니다. (유저가 많을 경우에는 더 그렇겠죠.)

Local notification을 보면 모든것이 유저의 device에서 locally 처리 됩니다. 그리고 미리 지정된 시간이나 간격 등이 세팅돼 있습니다. (scheduled) 그러니까 원격 서버가 필요 없는거죠. (추가적인 비용 발생이 없다는 겁니다.) 또 다른 잇점은 코로나에서 이 Locall Notification을 셋업 하는 것은 아주 쉽다는 겁니다.

사실 여러분들이 Push notification으로 하고자 하는 많은 것들을 local notification으로 할수 있는경우가 많습니다. 또 Local 로만 할 수 있는 것들도 있구요. 예를 들어 여러분들의 앱에서 원격에서 데이터를 가져올 필요가 없다면 Push 보다는 Local을 사용하는 것이 훨씬 더 나을 겁니다.


Scheduling Notifications


Notification에 대한 일정을 잡기 위해 (schedule) system.scheduleNotification() 함수를 사용해야 합니다.

신택스는 아래와 같습니다. 두 방법 모두 유효합니다.


system.scheduleNotification( secondsFromNow [, options] )
system.scheduleNotification( coordinatedUniversalTime [, options] )


이 함수는 나중에 notification을 cancle 하는 데 사용할 수 있는 reference ID를 return 합니다. 나중에 이 cancel 부분을 구현하려면 system.cancelNotification() 함수를 사용하시면 됩니다.

이 notification을 일정 시간 후로 일정을 잡으려면 secondsFromNow를 사용하거나 universal time을 사용해서 특정 date/time을 지정해 주시면 됩니다.

다룰 것들이 많이 있지만 오늘은 secondsFromNow를 사용해 보겠습니다. 만약 여러분이 coordinated universal time option을 사용하고 싶으시면 coordinatedUniversalTime argument에 대해 좀 더 자세한 설명이 있는  system.scheduleNotification() API 문서를 보세요.

두번째 argument는 secondsFromNow 를 선택했느냐 coordinatedUniversalTime를 선택했느냐와 관계 없이 사용하는 겁니다. 이것은 여러분의 notification에 대해 특정 정보를 보관하고 있는 options 테이블 입니다.

  • alert – 유저에게 보여지는 메세지 
  • badge – 앱 아이콘에 보여지는 badge number
  • sound – notificatin을 받으면 play 될 sound file
  • custom – Custom data/params to be used by your app, relevant to the notification (can be a variable, table, JSON string, etc).


개와 관련된 앱을 만든다고 가정하고 들어주세요. 여러분 앱은 10분(600초) 후에 강아지에게 먹이를 주어야 하는걸 알려주기 위해 notification 일정을 잡을겁니다. 그 예제는 아래와 같습니다.


local badge_count = native.getProperty( "applicationIconBadgeNumber" )

local notifyOptions =
{
   alert = "Time to feed your pet!",
   badge = badge_count + 1,
   sound = "woof.caf",
   custom = { scene = "pet_shop" }
}
local feed_notifier = system.scheduleNotification( 600, notifyOptions )



맨 처음에 native.getProperty( “applicationIconBadgeNumber” )를 사용해서 현재 application badge number를 받았습니다. 그리고 options table에서 그 숫자를 1 증가시켜서 세팅했습니다.

나머지는 그냥 보시면 아실겁니다. notification을 600초(10분)로 일정을 잡았고 두번째 인자로 notifyOptions table 을 전달했습니다.

notifyOptions table 안에 custom data로 launch_scene key 를 pass 했습니다. 이것은 이 notification을 받을 때 pet_shop으로 가도록 하기 위해 notification listener안에 있는 이 데이터를 보관하게 하는 겁니다. 나중에 이 데이터를 사용해서 pet_shop scene 으로 가겠죠. 이 custom 옵션에 무엇을 넣느냐는 전적으로 여러분 마음입니다.


Notification Listener


이 notification을 scheduling 하는 것은 단지 첫번째 step일 뿐입니다. 이제 이 notification을 받으면 어떤 것을 하도록 만들어야 합니다. 이 작업은 notification event listener 를 사용해서 작업합니다.

예제를 보여드리기 전에 여러분의 notification event listener function에 pass 될 event data를 알려드리겠습니다.

  • event.name – evnet.name은 항상 "notification” 입니다.
  • event.custom – 여러분이 custom option 을 통해서 pass 한 custom data 입니다.
  • event.type – 이 값은 항상 “local”이 될 겁니다.
아마 event.custom에 대해 궁금하시고 흥미로워 하실 겁니다. 

아래 notification listener 와 관련된 예제가 있습니다.


local function notificationListener( event )
    -- notification received, go to specified scene
    if event.custom.scene then
        storyboard.gotoScene( event.custom.scene )
    end
end
Runtime:addEventListener( "notification", notificationListener )


이 전에 notificatin schedule을 잡을 때 custom data로 scene key를 설정했죠? 위에서 보듯이 이 값은 event.custom에다 해당 key 값을 넣어서 그 value를 받아오도록 돼 있습니다. 프로그래밍을 하면서 다양하게 사용될 수 있겠네요.


NOTE: 위에서 보듯이 global Runtime object를 사용해서 notification event를 add 하실 수 있습니다. 


Canceling Notifications


system.cancelNotification() function를 사용하면 notification을 cancel 하실 수 있습니다. 이 함수를 call 할 때 두가지 옵션이 있습니다. system.scheduleNotification()에서 return 된 reference ID를 첫번째 argument에 넣어서 특정 notification을 cancel 할 수 있구요,  아니면 아무것도 안 넣고 현재 있는 모든 notification들을 cancel 할 수도 있습니다.


위에 예제로 만든 notification을 cancel 하려면 아래와 같이 하시면 됩니다.


system.cancelNotification( feed_notifier )

아래와 같이 하면 현재 schedule 된 모든 notification 이 cancel 됩니다.


system.cancelNotification()


launchArgs (Important)


유저가 여러분의 앱을 사용하고 있지 않을 때 local notification 이 왔다면 그 notification의 성격에 따라서 유저에게 알리거나 특정 scene으로 안내하고 싶으실 겁니다. 위에서 보았듯이 이런 작업은 notification event listener를 통해서 handle 해야 합니다.

문제는 여러분의 앱이 suspend 되거나 close 됐을 때 그 notification listener는 active 상태가 아니라는 겁니다.

경우에 따라 위의 예제의 경우 앱을 실행 할 때 main scene 이 아니라 pet_store로 가도록 해야 할 수도 있습니다. 이럴 때 launchArgs를 사용합니다. 


main.lua


local launchArgs = ...

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

local function notificationListener( event )
    -- notification received, go to specified scene
    if event.custom.scene then
        storyboard.gotoScene( event.custom.scene )
    end
end
Runtime:addEventListener( "notification", notificationListener )

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


if launchArgs and launchArgs.notification then

    -- call the event listener manually:
    notificationListener( launchArgs.notification )
end


위에서 보듯이 notification listener는 이전 예제에서 보여 드렸던 그 리스너랑 똑 같습니다. 다른 점은 main.lua 의 첫번째 라인에서 launchArgs  변수를 선언해서 실행 될 부분을 assign 한다는 겁니다.(the “…” is literal; not placeholder text).

notificationListener() function 내 어딘가에 launchArgs 에 attached 된 notification table 과 함께 앱이 시작됐는지 체크해야 합니다.

그러면서 이 listener function을 manually invoke 하게 됩니다. 이 listener 함수는 첫번째 argument로 launchArgs.notification 를 전달 했다는 것을 기억해 두세요.


여기까지 입니다. 코로나에서 local notifications 의 일정을 잡고 어떤 동작을 하도록 하는 것에 대해 모두 배우셨습니다. 이것을 활용하셔서 다양한 효과를 내실 수 있을 겁니다.

반응형

비트맵 마스크 사용하기

2012. 6. 2. 03:35 | Posted by 솔웅


반응형

How to Use Bitmap Masks

Posted by Jonathan Beebe

이번주 튜도리얼은 많은 프로젝트에서 꾸준히 애용되고 있는 기능이지만 한편으로는 특별한 use-case로 인해 여러분이 잘 모를 수 도 있는 기능을 다뤄볼까 합니다. 이 기능은 bitmap masking 인데요, graphics.newMask() 함수를 사용해서 구현합니다.

아마 bitmap masking이 무엇인지 잘 모를수도 있습니다. 또 어떻게 유용하게 사용될지에 대해서 알지 못할수도 있구요. 그러신 분들은 이번 강좌를 잘 보세요. 분명히 아주 유용한 툴일테니까요. Bitmap mask는 약간 tricky 한 방법으로 문제를 해결하는데 사용될 수 있습니다.

    - 이미지의 투명한 부분에는 touch 이벤트가 안 먹게 하기 (아주 많이 쓰이는 케이스 입니다)
    - 특정한 모양으로 이미지를 Clip 하기 (예를들어 스크롤 뷰 위젯에서 전체 스크린 내의 일부 sub-section 부분을 clip 하기 위해 bitmap mask 를 이용할 수 있습니다.)
    - solid background가 있는 이미지에 transparency 주기


위에 보시듯이 masking의 주요 목적은 이미지의 일 부분을 숨기는 겁니다. (혹은 전체 display 그룹의 일 부분을 숨길수도 있구요.) 즉 이미지를 masking 하는 겁니다. 그러려면 masking 할 이미지도 있어야 합니다. mask라는 단어를 사용하는 이유가 그냥 이미지나 그룹에 덮어씌우는 기능을 하기 때문입니다.





What a mask looks like

아래에 과일 이미지가 있습니다. (fruit.png) 그리고 bitmap mask image 가 있습니다. (fruit-mask.png) 이 두개의 이미지를 사용할 겁니다. 회색으로 서양의 체스판 모양으로 돼 있는 부분은 이미지의 투명한 부분입니다.





오른쪽의 bitmap mask의 검은 부분이 투명한 부분이 될 겁니다. 그리고 흰 부분은 이 mask가 정확하게 apply 됐을 때 보여지게 될 원래 source 이미지가 들어갈 부분입니다. 이 mask로 사용될 이미지에는 어떤 특별한 제약이 있는것이 아닙니다. 아무 이미지나 사용하실 수 있습니다.

What’s the point?

위 두개의 이미지를 사용할 건데 뭘 하려는 것일까요? mask 이미지에 있는 검은 색은 이미 source image에서 투명하게 돼 있는 부분인데요. 이 튜토리얼에서 다룰 것은 bitmap masking을 사용해서 특정 부분을 투명하게 만드는것 말고 투명한 부분에서는 touch 이벤트가 안 먹도록 만드는 방법을 알려 드릴 겁니다.

투명한 부분이 있는 이미지라도 touch listener를 달면 전체 이미지에서 이 touch 이벤트가 먹힐겁니다. 오직 저 사과만 터치했을 때 어떤 이벤트가 발생하도록 하려면 어떻게 해야 할까요? 그렇게 하려면 이 bitmap mask를 사용해야 합니다. 또 예를 들어 어떤 이미지가 있는데 그 이미지의 일 부분에만 터치가 먹히게 하고 싶을 때도 이 방법을 사용할 수 있습니다. 이미지내에 투명한 부분이 없을때도 말이죠.

Bitmap mask requirements

mask로 사용할 이미지를 만들때는 아래와 같아야 합니다.

   1. width와 height가 4의 배수여야 합니다.
   2. 적어도 3px의 검은테두리가 있어야 합니다.


위의 가이드라인을 따르지 않는다면 예상한 대로 masking이 일어나지 않을 것이고 이것을 debug 하는 것도 힘들겁니다. 추가적으로 고려해야 할 사항들을 아시려면 Bitmap Mask documentation 을 읽어 보세요.

Applying the mask

mask를 적용하려면 graphics.newMask()를 사용해서 새로운 mask를 만들어야 합니다. 그리고 object:setMask() 메소드를 사용해서 객체나 그룹에 apply 합니다.

local mask = graphics.newMask( "fruit-mask.png" )

local fruit = display.newImage( "fruit.png" )
fruit.x, fruit.y = display.contentCenterX, display.contentCenterY

-- apply mask to object
fruit:setMask( mask )


위의 코드를 보시면 우선 mask를 생성하고 display object를 만들고 그 마스크를 display object에 apply 했습니다. 이렇게만 하면 약간의 문제가 생길겁니다. 디폴트로 객체의 적용 좌표는 0,0 이 됩니다. (좌상단이 되겠죠) 그리고 mask는 중앙을 기준으로 하는 reference point가 적용 되면 아래와 같은 모양으로 object:setMask()가 적용 될 겁니다.





mask 가 적용될 객체의 정 중앙에 제대로 덮이길 원하시면 객체의 maskX와 maskY 프로퍼티를 수정해 줘야 합니다.

fruit.maskX = fruit.contentWidth*0.5
fruit.maskY = fruit.contentHeight*0.5


마스크는 대상 객체의 0,0 지점에 위치한다는 것을 꼭 기억해 두세요. 위의 코드를 추가하고 나면 아래와 같이 될 겁니다.



결과적으로 모바일 화면에는 아래와 같이 나오게 됩니다.



자 겉으로 보기에는 그냥 display.newImage() 해서 원래 배경이 투명한 이미지를 display 한것과 다를것이 없습니다. 만약 원래 배경색이 투명하지 않았다면 겉모습이 투명하게 바뀌었을 테니까 뭔가 변화가 있다고 느껴지겠죠. 어쨌든 위에 사용한 원래 이미지 파일에는 배경이 투명하게 이미 돼 있었습니다. 그러니까 위 예제가 유용하게 사용되려면 투명한 부분을 un-touchable 하게 만들어야 쓸모가 있게 되겠죠.

다행스럽게도 이미지가 이렇게 한번 mask 되고 나면 다음 사용법은 아주 쉽습니다. 그냥 간단하게 object.isHitTestMasked 프로프티를 true로 세팅하면 투명해진 부분에 touch 이벤트가 안 먹히게 할 수 있습니다.

fruit.isHitTestMasked = true

이제 여러분이 이 fruit 객체에 touch 리스너를 달게 되면 전체 256X256 이미지가 다 touchable 되는것이 아니라 투명하지 않은 부분만 touchable 될 겁니다. 이 예제의 경우엔 아주 정확하게 과일 부분에 터치할 경우에만 touch 이벤트가 적용 되겠죠.

Dynamic Content Scaling


bitmap mask는 dinamic content scaling을 지원하지 않습니다. 그렇다고 해서 방법이 전혀 없는 것도 아닙니다. 구현하시려면 high resolution asset를 로드해서 object.maskScaleX와 object.maskScaleY 프로퍼티를 원하는 크기에 맞게 세팅해 주세면 됩니다.

예를 들어, retina display에 맞춰서 개발을 하고 있고 아주 super-sharp 하게 되도록 mask를 사용하고 싶으시면 fruit-mask.png와 fruit-mask@2x.png 두개를 준비하세요. (원본보다 두배 크기의 이미지를 준비하시면 됩니다.) 그리고 mask를 생성할 때 (graphics.newMask()) @2x asset을 사용하시면 됩니다. 그리고 나서 fruit.maskScaleX 와 fruit.maskScaleY 를 display.contentScaleX와 display.contentScaleY로 세팅하세요.

아래 예제가 있습니다.

local mask = graphics.newMask( "fruit-mask@2x.png" )

local fruit = display.newImage( "fruit.png" )
fruit.x, fruit.y = display.contentCenterX, display.contentCenterY

-- apply mask to object
fruit:setMask( mask )
fruit.maskScaleX = display.contentScaleX
fruit.maskScaleY = display.contentScaleY
fruit.maskX = fruit.contentWidth*0.5
fruit.maskY = fruit.contentHeight*0.5


이런 방식을 사용하면 좀 더 다양한 해상도에 맞도록 이미지들을 준비하면 좀 더 super-sharp 하게 이 mask를 사용할 수 있겠죠. 물론 각각 다른 high resolution 이미지를 사용해야 겠죠. 같은 방법으로 안드로이드 디바이스들도 이렇게 적용하실 수 있습니다.

그리고 mask는 전체 display group 객체에도 apply 될 수 있다는 것을 기억해 두세요.

Removing a mask

객체에서 mask를 제거하고 싶으시면 간단하게 object:setMask(nil)을 해 주시면 됩니다. 또한 변수를 nil로 선언해도 이 mask를 제거하실 수 있습니다.

fruit:setMask( nil )
mask = nil


이 bitmap mask에 대한 또 다른 예제도 보실 수 있습니다. 다운로드 받으신 Corona SDK의 Flashlight 샘플을 한번 보세요. 경로는 /SampleCode/Graphics/Flashlight 폴더 입니다. 이 bitmap mask로 어떤 다른 좋은 효과를 줄 수 있을까요?

반응형
이전 1 2 3 4 5 6 7 8 ··· 12 다음