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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

Storyboard API 추가 사항 안내

2012. 4. 2. 10:49 | Posted by 솔웅


반응형

Storyboard Scene Events Explained  

Posted by Jonathan Beebe

지난 11월에 Storyboard API를 소개했을 때 (한글, 영어) 이 스토리보드 API 소개와 함께 어떻게 사용할 것인가에 대한 간단한 설명을 한 이후로 이 API에 대한 추가적인 가이드나 튜토리얼을 소개해 드리지 못했습니다.

그래서 오늘 스토리보드 API 전체를 구성하고 움직이게 하는 각각의 개별 이벤트들에 대해 설명하려고 합니다. 그리고 이 각기 다른 이벤트들이 어떻게 조화를 이루도록 하는지에 대해 보여 드리겠습니다. 여러분 중 scene 이벤트들에 대한 이해가 부족해서 스토리보드 API를 사용하시는데 문제가 있으셨던 분들은 이 글이 많은 도움이 되실 겁니다.

일단 스토리보드 의 scene 객체들이 무엇인지 그리고 각 scene들의 view display group과 scene을 purge 한다는게 무슨 의미인지에 대해 설명 드리겠습니다. 스토리보드 scene 이벤트들을 사용하시면서 view group이나 purge, remove 같은 개념들을 이해하시는 것은 중요한 일입니다.






Scene Objects

Storyboard API의 가장 핵심 객체는 scene 객체 입니다. 이 객체는 단지 특정 프로퍼티들과 이벤트들로 구성된 테이블입니다.

모든 scene 객체들은 storyboard.newScene() 함수를 사용해서 생성됩니다. 여러분은 여러분들이 원하는 만큼의 scene을 만들 수 있습니다. 하지만 일반적으로 새로운 모듈의 top에서 storyboard.newScene() 함수를 한번 call 하게 됩니다. 그리고 모듈의 마지막 부분에 이것을 return 합니다. (그 사이에 scene event-handling 하는 부분이 있겠지요.) 이 방법을 따른다면 scene들은 각 루아 모듈(루아 파일) 마다 하나씩 나오게 됩니다. 그리고 이렇게 정석대로 개발하게 되면 코딩이 더 쉬워지고 가독성도 훨씬 올라가게 됩니다.


The “view” display group

모든 scene들은 display group으로 구성됩니다. 이 display group에 각 scene의 display 객체들이 포함되게 됩니다. 이 display group은 view 프로퍼티를 통해서 access 될 수 있습니다.

sceneObject.view  -- Corona display group

이 display group은 항상 현재 상태인 것은 아닙니다. scene들이 루아 모듈들과 서로 관계하면서 부터 가끔씩  모듈 자체가 로드 되게됩니다. 이 때 scene 객체는 active 상태가 됩니다.  하지만 view display group은 그렇지 않습니다. scene이 purged 상태일 때 주로 그렇습니다. 이것은 스토리보드 API를 이용할 때 자주 발생하게 됩니다.

Scene Purging

scene이 purged 된다는 것은 view display group이 removed 됐다는 걸 의미합니다. scene이 purged 됐을 때 scene object는 아직 메모리에 있는 상태입니다. 하지만 눈에 보이는 상황은 아니죠. 이것은 조만간에 다시 이 scene을 로드할 때 유용합니다. 메모리에 있기 때문에 빨리 불러 올 수가 있죠. 실제 texture 메모리를 차지하지는 않으면서 단지 메모리에 존재하는 상태이기 때문에 빨리 불러 올 수 있는 겁니다.

Scene Events

코로나 SDK로 이벤트를 작업는것과 같은 개념으로 스토리보드 API가 설계 됐습니다.

scene이 생성하고 purge , remove 하고 scene들 사이에서 서로 왔다갔다 하는 것들을 모두 API를 통해 콘트롤 할 수 있습니다. 각각의 상황에 맞는 이벤트들이 존재하고 또 연관을 맺게 됩니다. 이 이벤트들이 있음으로 해서 특정 액션이 일어나기 직전이나 일어난 이후에 어떠한 일을 할 수가 있게 됩니다.
아래에 scene 과 관련한 이벤트 들이 열거돼 있습니다.

- createScene
- enterBegan (requires build 2012.773 or later)
- enterScene
- exitScene
- exitEnded (requires build 2012.773 or later)
- destroyScene



createScene

이 이벤트는 scene의 view display group이 생성 될 때 dispatch 됩니다. 그러니까 일반적으로 storyboard.gotoScene()에 의해 call 되는 사이에 dispatch 되게 되죠. 그런데 storyboard.gotoScene() 이 call 됐는데 view display group이 이미 있다면 어떻게 될까요? (이 얘기는 이전에 이 scene이 불려져서 view 객체들이 화면에서는 없어졌지만 메모리에는 아직 남아있는 purge 상태라는 거죠.) 이럴때는 이 createScene 이벤트가 call 되지 않습니다. 곧바로 enterBegan-enterScene 이벤트로 가게 되죠.

createScene 이벤트 리스너 함수내에서는 그 scene에 등장해야 할 display 객체들을 생성하고 그 객체들을 그 scene의 view display group에 insert 하는 일을 하게 됩니다.
아래에 createScene 이벤트 리스너 관련 예제가 있습니다.

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

-- forward declarations
local object1, object2

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

    object1 = display.newImage( "image1.png" )
    object2 = display.newImage( "image2.png" )

    group:insert( object1 )
    group:insert( object2 )
end
scene:addEventListener( "createScene", scene )

return scene


Side note : 이 객체를 선언할 때는 creaScene 바깥에서 로컬로 선언을 합니다. 그리고 구체적인 이미지나 텍스트 할당을 createScene 함수 안에서 하게 되는거죠. 이렇게 해야지 그 객체들을 다른 이벤트 리스너 (enterScene 등) 에서 사용할 수 있습니다.

enterBegan

NOTE : 이 이벤트는 Corona build 2012.773 버전에서 추가 됐습니다. 그 이전 버전에서는 사용하실 수 없습니다.

storyboard.gotoScene()이 call 됐을 때 그 scene의 view display group이 생성 될 것입니다. (이미 있지 않으면) 바로 그 다음에 그리고 다른 transition이 일어나기 전에 enterBegin 이벤트가 실행 될 겁니다.

enterBegan 이벤트 리스너를 통해 enterBegin 하는 순간에 원하는 일을 할 수 있습니다. pre-loading 을 한다거나 데이터를 저장한다거나 하는 일들을요. 객체들이 실제 화면에 뜨기 바로 직전에 어떠한 일을 할 수가 있는 겁니다. 아래 예제가 있습니다.

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

-- forward declarations
local object1, object2

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

    object1 = display.newImage( "image1.png" )
    object2 = display.newImage( "image2.png" )

    group:insert( object1 )
    group:insert( object2 )
end
scene:addEventListener( "createScene", scene )

function scene:enterBegan( event )
    object1.isVisible = false
    object2.isVisible = false
end
scene:addEventListener( "enterBegan", scene )

return scene


이 예제에서 createScene 이벤트 리스너와 enterBegan 이벤트 리스너가 어떻게 역할 분담을 해서 일을 하게 할 수 있는지에 대해 보실 수 있을겁니다.

enterScene


이 이벤트는 enterBegan이랑 비슷합니다. 단지 이 이벤트는 scene transition이 일어난 이 후에 call 된다는 것만 다릅니다. 객체들을 특정 지점에 positioning 하거나 유저에게 메세지를 보여주거나 physics를 시작하거나 touch나 enterFrame 같은 이벤트 리스너들을 추가하거나 하는 일들을 이 enterScene 이벤트 리스너에서 하시면 됩니다.

storyboard.gotoScene()에서 enterScene 이벤트까지 어떠한 과정들이 있는지 아래 그림을 통해 보실 수 있습니다.

exitScene


storyboard.gotoScene() 으로 다른 scene을 call 했을 때 현재의 scene에서는 어떠한 일이 일어날까요? 첫번째로 exitScene 이벤트가 dispatch 될 겁니다. 이 이벤트는 현재의 scene이 transition out 되기 전에 일어나게 됩니다.

이 exitScene에서는 어떤일을 주로 하는게 좋을까요? 여기서는 touch, enterFrame 같은 이벤트 리스너들을 remove 하고 애니메이션을 stop 시키고 physics를 pause 시키는 일들을 해야 합니다.

다시 정리하자면 유저가 storyboard.gotoScene()을 call 하면 현재의 scene에서 exitScene 이벤트가 첫번째로 dispatch 될 겁니다. 저 위에 있는 다이어그램을 다시 보시면 이 exitScene 다음에는 어떤 이벤트가 그 다음에 dispatch 되는지 아실 수 있죠?

exitEnded

NOTE : 이 이벤트는 Corona build 2012.773 에서 새롭게 추가 됐습니다. 그러므로 이전 버전에서는 사용하실 수 없습니다.

exitScene은 현재의 scene이 transition out 되기 전에 dispatch 됩니다. 그리고 exitEnded 이벤트는 그 scene이 완전히 transition out 된 직후에 dispatch 됩니다. 그러니까 그 다음 scene이 나오기 바로 직전에 dispatch 되는 겁니다. 아래에 이 5개의 다른 scene 이벤트들에서 어떠한 일들을 시켜야 하는지에 관한 예제가 있습니다.

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

-- forward declarations
local object1, object2

-- enterFrame event listener function
local function onUpdate( event )
    -- do something here
end

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

    object1 = display.newImage( "image1.png" )
    object2 = display.newImage( "image2.png" )

    group:insert( object1 )
    group:insert( object2 )
end
scene:addEventListener( "createScene", scene )

function scene:enterBegan( event )
    object1.isVisible = false
    object2.isVisible = false
end
scene:addEventListener( "enterBegan", scene )

function scene:enterScene( event )
    object1.isVisible = true
    object2.isVisible = true
  
    Runtime:addEventListener( "enterFrame", onUpdate )
end
scene:addEventListener( "enterScene", scene )

function scene:exitScene( event )
    object1.isVisible = true
    object2.isVisible = true

    Runtime:removeEventListener( "enterFrame", onUpdate )
end
scene:addEventListener( "exitScene", scene )

function scene:exitEnded( event )
    print( "This scene has fully transitioned out and is no longer the active scene." )
end
scene:addEventListener( "exitEnded", scene )

return scene


destroyScene


이 이벤트는 그 scene의 view display group이 remove 될 때 dispatch 됩니다. 이 이벤트는 view display group이 모두 remove 되기 전에 display object들과 관련된 어떠한 일들 (이벤트 리스너를 없앤다던가 특정 scene 객체들을 manually 없앤다던가 하는) 일들을 하기에 좋습니다.

scnen이 purge 될 때마다 destroyScene은 dispatch 됩니다. scene purging은 다음과 같은 상황에서 발생하게 됩니다.

    - storyboard.purgeScene() 이나 storyboard.purgeAll()이 call 됐을 때
    - storyboard.removeScene() 이나 storyboard.removeAll() 이 call 됐을 때 (scene은 scene objects 들이 remove 되기 전에 먼저 purged 됩니다.)
    - OS 가 메모리 부족 경고 메세지를 보낼 때 가장 최근에 사용된 scene은 자동적으로 purged 됩니다. (모듈은 load 된 상태로 남아 있습니다. 다만 view display group은 texture memory 공간을 확보하기 위해서 remove 됩니다.)


Putting it all together

지금까지 모든 events 에 대해 어떻게 동작들을 하는지 설명했습니다. 그리고 이 각각의 이벤트들이 서로 어떻게 연관을 맺으면서 일을 하게 되는지를 보여주는 간단한 예제들을 보여 드렸습니다. 그런데 이 다른 스토리보드 이벤트들을 필요에 따라 어떻게 활용해야 할까요? 이건 기억해 두세요. 이 모든 이벤트들을 다 활용할 필요는 없습니다. 이 이벤트들은 당신이 필요로 할 때 사용할 수 있도록 만들어져 있지 당신이 모두 사용해야만 하도록 만들어 진 것은 아닙니다.

그리고 일단 createScene 이벤트 리스너에는 객체를 생성하는 코드를 넣으세요. 리스너를 달고 객체들의 위치를 잡고 physics를 시작하고 하는 일들은 대개 enterScene 리스너에서 하게 됩니다. 그리고 객체와 리스너를 remove 하고 physics를 없애고 하는 일들은 exitScene에서 하게 됩니다. (이 exitScene에서는 주로 이렇게 어떤 것들을 해제하거나 없애는 장소 입니다. 다른 화면으로 넘어가기 전에 현재 화면해서 메모리를 잡아 먹고 있는 것들을 다 해제해 주는 작업을 하는 것이죠.)

SampleCode/Interface/Storyboard에 있는 샘플 코드를 실행해 보세요. 터미널에 transition이 시작되거나 끝날 때 이러한 이벤트들이 dispatch 되는 로그들이 찍힐 겁니다.

그리고 이 글을 읽고 난 후에 직접 한번 만들어 보세요. 금방 하실 수 있을 겁니다. 그리고 API의 더 다양한 메소드와 관련된 정보를 얻으시려면 Storyboard Reference를 참조하세요.


아주 오랜만에 글을 올립니다. 개인적으로 좀 일이 있어서요.

오랜만에 올렸는데...
오랜만에 추천도 꾸욱~~ 해 주세요. :)

반응형

Developing for iPad Retina Display

2012. 3. 23. 12:01 | Posted by 솔웅


반응형
애플이 얼마전 새로운 아이패드를 만듦으로서 iOS 용 앱도 이제 3~4가지 해상도를 고려해서 개발을 해야 되네요.
안드로이드까지 합쳐지면 해상도 종류가 굉장히 많아지죠?

일단 이미지를 Display 할 때 이미지가 찌그러지거나 흐리게 나오거나 하는 문제들이 있습니다.

각기 다른 해상도에서도 선명한 이미지를 보여줄 수 있는 방법에 대해 이번에 Corona에서 블로그에 포스팅을 해 주었네요.

Android Native Language로 할 때는 px 말고 div인가? 뭐 그런 이미지 사이즈 단위를 사용하는 방법도 있고 또 코드 내에서 디바이스 프로파일 정보를 얻어서 다른 이미지를 출력하는 방법 또는 layout xml을 디바이스별로 다르게 준비해서 사용하는 방법이 있고 그렇죠?
Android Native Language로 개발한지 오래 되서 잘 모르겠네요.

하여간 코로나에서는 비교적 간단하게 이 부분을 해결 할 수 있습니다.

이번에 코로나에서 아주 정리를 깔끔하게 잘 해서 올려 준 것 같습니다.
원본은 여기로 가시면 보실 수 있습니다.



Developing for iPad Retina Display

Corona SDK는 현재 공식적으로 New iPad (이 후에는 iPad 3라고 부르겠습니다.) 를 support하고 있습니다. 이 support는 일반 공개버전이나 유료사용자를 우한 Daily Builds 모두에서 지원하고 있습니다.

이 iPad 3는 원래 iPad 보다 3배나 많은 픽셀을 지원합니다. 그래서 개발자들은 이 해상도를 위해 최적화 된 그래픽을 지원하는 방법에 대해 혼란스러워 하고 있습니다.

좋은 소식은 현재의 iPhone이나 iPod touch 를 위한 retina display에서 여러분이 작업을 했다면 새 iPad3도 기본적으로 똑 같다는 겁니다. 하지만 좀 혼동할 수 있는 부분도 있을 것 같아 이 Tutorial에서 그 부분에 대해 설명 하겠습니다.

이 글은 몇개의 다른 시나리오로 나뉠겁니다. 특정 configuration을 이용하는 방법과 여러분의 app이 target 하는 특정 디바이스의 타입에 대해 해야 하는 일 등으로 나뉠겁니다. 여러분은 그냥 필요로하는 부분에 대해 읽고 따라하시면 됩니다.

iPad-only Apps

이 시나리오는 여러분의 앱이 iPad 형식의 디바이스를 위해 만들어지는 경우를 가정한 겁니다. (그러니까 다른 tablet들에도 해당이 됩니다.)

쉬운 방법은 아이폰을 위한 앱을 만들 경우 일단 아이폰에 맞게 작업을 하고 그 다음에 새로운 아이폰에 맞춰 작업하는 겁니다. 그리고 이 방법이 정상적입니다.

그래픽 이미지들을 평상시처럼 만드세요. 그라고 나서 똑같은 이미지를 width와 height가 두배로 키워서 만드세요. 두배로 큰 이미지는 이미지 이름에 공통된 접두어같은 것을 넣어 두세요. (나중에 config.lua 파일에서 사용할 겁니다.) 그렇게 되면 코로나 엔진이 상황에 맞게 필요한 이미지를 가져다가 display 할 겁니다.

그냥 이미지 관련 프로그램으로 원래 이미지를 두배 늘리는 일은 하지 마세요. 그러면 이미지가 번지거나 흐려지게 될 거예요. 원래 목적이 큰 해당도에서도 깨끗한 이미지를 보여주기 위한 거니까 두배로 큰 이미지는 크면서도 깨끗하게 보일 수 있도록 따로 작업하세요. 큰 이미지를 먼저 만들고 나서 작은 이미지로 크기를 줄이는 방법도 좋을 겁니다.

아래 두 개의 작은 해상도를 위한 이미지와 큰 해상도를 위한 이미지에 대한 파일 이름과 관련한 예제가 있습니다.
큰 이미지에는 @2x라는 접미어가 똑같이 붙어있습니다.

    star.png – 128 x 128
    star@2x.png – 256 x 256
    background.png – 768 x 1024
    background@2x.png – 1536 x 2048

그리고 이를 config.lua에서 어떻게 처리해야 하는지 아래에 예제가 있습니다.

application =
{
    content =
    {
        width = 768,
        height = 1024,
        scale = "letterbox",

        imageSuffix =
        {
            ["@2x"] = 2,    -- images with "@2x" appended will be used for iPad 3
        }
    }
}

어떤 화면 방향을 taget으로 하던지 width와 height는 항상 portrait 를 기준으로 config.lua에 명시하셔야 합니다. (그러니까 width는 항상 height보다 작겠지요?)

위 config.lua에서 imageSuffix 테이블에는 @2x 라는 아이템이 있습니다. 이는 코로나 엔진에게 해상도가 두배인 디바이스인 경우에는 이 @2x가 붙은 이미지를 사용하라고 얘기하는 겁니다. (만약에 있으면)

위에 iPad  해상도의 width와 height를 정해 줬죠? iPad 3의 해상도는 이 보통 아이패드의 딱 두배 입니다. 그러니까 위의 예제 config.lua를 사용하시면 이미지를 사용할 때 iPad 3 이면 그 이미지 이름 뒤에 @2x가 붙은 이미지를 사용할 겁니다. (만약에 @2x 가 붙은 이미지가 없으면 원래 이미지를 씁니다.)

여러분이 이렇게 config.lua만 제대로 세팅해 놓으면 코로나는 알아서 디바이스의 크기를 체크하고 이미지를 불러올 때마다 거기에 맞는 이미지를 display 합니다.

local bg = display.newImage( "background.png" )

위와 같이 이미지를 불러오고 위에 제시한 config.lua를 사용한다면 background.png는 iPad 와 iPad 2 에서 display 될 겁니다. 그리고 iPad 3 에서는 background@2x.png가 display 될 겁니다.


iPad + iPhone (Universal) Apps

이 시나리오는 iPhone 해상도 (320X480)에 맞게 작업을 하고 코로나의 dynamic content-scaling 기능을 이용해서 iPad 에 맞게 자동적으로 이미지가 늘어나서 화면에 꽉 차게 display하는 방법을 사용하는 방법입니다.

이 시나리오대로 하기 위한 config.lua 예제가 아래에 있습니다.

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "letterbox",

        imageSuffix =
        {
            ["@2x"] = 2,    -- for iPhone, iPod touch, iPad1, and iPad2
            ["@4x"] = 4,    -- for iPad 3
        }
    }
}


위 config.lua를 사용하면 여러분의 앱은 iPhone 해상도에 맞게 작업이 이뤄질 겁니다. 어떤 디바이스이든지 상관 없이 안의 로직은 똑 같이 적용되서 돌아갈 겁니다. 하지만 이미지는 해상도에 맞게 별도로 display 할 수 있습니다. @2x 나 @4x 라는 글자가 추가된 이미지들이 디바이스에 맞게 선택되서 출력 될 겁니다.

@2x와 @4x 뒤에 붙은 2와 4의 의미는 320X480을 기준으로 해상도가 2배 이면 @2x가 붙은 이미지를 보여주고 해상도가 4배이면 @4x가 붙은 이미지를 보여주라는 뜻 입니다.

Getting the Scale Factor

아래에 scale 과 관련한 약간의 트릭이 있습니다. 코로나 시뮬레이터에는 다양한 디바이스에 맞는 시뮬레이터 스킨이 있는데요 아래 코드를 main.lua에 넣고 시뮬레이터를 실행해 보세요.

local deviceWidth = ( display.contentWidth - (display.screenOriginX * 2) ) / display.contentScaleX
local scaleFactor = math.floor( deviceWidth / display.contentWidth )
print( scaleFactor )

위 코드가 하는 일은 device의 width을 얻어내서 이것을 content width로 나눕니다. 이러면 config.lua에 있는 해상도와 비교해서 지금 디바이스 해상도의 크기가 몇배인지 대략적으로 알 수 있습니다.

코로나 시뮬레이터(Corona Simulator)에서 각각의 device skin들을 바꿔가면서 테스트 해 보세요. 그러면 숫자가 바뀌는 걸 보실 수 있으실 겁니다. @2x 에 해당하는 값을 보시려면 iPhone4 시뮬레이터 스킨을 실행해 보시면 됩니다. 그러면 터미널에 2가 찍힐 겁니다. 그리고 iPad Retina 도 선택해 보세요. 그러면 숫자 4가 찍힐 겁니다. New ipad 이전의 iPad는 iPhone 해상도의 두 배 입니다.


Cross-Platform Apps

이 시나리오는 iPad 3를 비롯한 여러개의 디바이스에서 동시에 사용할 수 있도록 앱을 개발하려고 할 때 dynamic content scaling 을 사용할 수 있는 방법입니다.

좋은 소식은 바로 전에 보여드렸던 예제대로 하시면 된다는 겁니다. 단지 좀 더 많은 image suffixes 들이 config.lua의 imageSuffix 테이블에 정의 되어야 겠죠. 또한 이미지들도 여러 사이즈에 맞도록 미리 준비가 되어 있어야 하겠구요.

아래 예제가 있습니다.

application =
{
    content =
    {
        width = 320,
        height = 480,
        scale = "zoomStretch",

        imageSuffix =
        {
            ["@1-5"] = 1.5, -- for Droid, Nexus One, etc.
            ["@2x"] = 2,    -- for iPhone, iPod touch, iPad1, and iPad2
            ["@3x"] = 3,    -- for various mid-size Android tablets
            ["@4x"] = 4,    -- for iPad 3
        }
    }
}

이렇게 하시면 됩니다.

여기까지만 아시면 다양한 해상도를 가진 디바이스들에 (애플(Apple)의 뉴 아이패드(New iPad)를 포함해서) 맞는 이미지를 보여주기 위한 기본 세팅 방법은 모두 알고 계신 겁니다.

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

이런 방법으로 코딩은 아주 간단하게 해결할 수 있는데 해상도가 다르더라도 선명한 이미지를 보여주기 위해서는 아주 많은 이미지를 사용해야 하네요.
디자인 실력이 없는 프로그래머로서 이미지 하나 만드는것도 많이 신경쓰이고 시간이 많이 걸리는데..
이거 해결해줄 프로그램은 어디 없을까요???
반응형

새로 추가된 이미지 캡쳐 기능

2012. 3. 21. 11:05 | Posted by 솔웅


반응형
Taking Snapshots of Objects and Groups

Corona Daily Build 2012.768 에서는 display.capture() 함수를 call 함으로서 간단히 display object (혹은 group) 를 snapshot 하는 새로운 기능이 추가 됐습니다.

이전에는 display.save() 함수를 사용해서 화면의 display 객체들을 저장했었습니다. 이번 display.capture()에서는 save to disk 부분을 거치지 않아도 됩니다. 그리고 snapshot의 새로운 display object로서 사용할 수도 있습니다.

이것은 기본적으로 display.captureScreen()과 같습니다. (display object를 return 한다는 점에서) 하지만 display.captureScreen()에서는 특정 display object나 display group을 따로 캡쳐할 수는 없습니다. display.captureScreen()은 화면 전체의 snapshot을 가져올 뿐입니다.

이번에 새로 추가된 display.capture()함수는 특정 객체나 그룹에 대해서만 snapshot을 할 수가 있습니다. 이 기능을 이용하면 아주 많은 부분에서 활용이 가능 하실 겁니다.

그리고 이 새로운 기능에서는 캡쳐된 스냅샷의 배경을 투명 처리 할 수도 있습니다. (display.captureScreen()과 display.save() 모두 백그라운드를 투명처리하지 못했습니다.)

NOTE : 안드로이드에서는 display.save() 기능이 업데이트 되서 백그라운드를 투명처리할 수 있게 됐습니다. 이것은 daily build 2012.768 부터 가능합니다. 아직까지 iOS에서는 지원이 안 됩니다.



Wait, display.save() can do the same thing!

display.capture() 가 아주 좋은 기능이라고 생각 되시죠? 그런데 display.save() 만 사용해서 저장된 이미지를 새로운 display 객체로 display.newImage()를 사용해서 로드할 수 있을까요?

배경 화면을 투명처리하는 것만 빼고는 가능합니다. display.capture() 가 하는 일과 똑 같이 display.save()를 사용해서도 할 수 있습니다. 그런데 여기에는 좀 큰 단점이 있습니다. 이미지를 디스크에 저장하고 이것을 다시 로드하는 과정에서 퍼포먼스에 큰 영향을 미치게 될 겁니다. 그러므로 display.save를 사용해서 하는 것보다 display.capture()를 사용하는 편이 훨씬 이로울 겁니다.

file I/O 과정을 생략하고 새로운 display 객체로 바로 이미지를 렌더링하는 것, display.capture()는 이런 점에서 display.save()를 사용하는 것 보다 훨씬 빠를 겁니다. 물론 display.save()도 아주 유용한 부분이 있죠. 이미지를 특정 위치에 save 해야 할 때는 display.save()를 사용 하셔야 합니다.

display.capture() Usage

신택스는 아래와 같습니다.
display.capture( displayObject [, saveToPhotoLibraryFlag ] )

아주 간단하죠? 아래 파라미터에 대한 설명이 있습니다.

displayObject — snapshot을 하고 싶은 display 객체나 그룹 입니다.

saveToPhotoLibraryFlag — 이 부분은 필수사항은 아닌데요. true로 설정하면 (디폴트는 false입니다.) 캡쳐된 객체는 display.captureScreen()의 같은 파라미터가 하듯이 디바이스의 photo library로 저장이 될 겁니다.

아래에 display.capture()를 사용하는 예제가 있습니다.

  local group = display.newGroup()

    local image1 = display.newImage( group, "image1.png" )
    local image2 = display.newImage( group, "image2.png" )

    -- take snapshot of the entire group
    local snapshot = display.capture( group )

    -- the object 'snapshot' is now another display object
    snapshot:translate( 100, 100 )
   
주의하셔야 할 부분은 display object가 return 됐을 때 이것은 캡쳐한 object 처럼 캡쳐되기 전과 같은 위치에 잊지 않을 거라는 겁니다. 디폴트로 return 된 display object는 0,0 에 위치합니다. 그러니까 좌상단에 위치하게 되죠. (디폴트로 이미지의 기준점은 이미지의 중앙이니까 실제로는 좌 상단을 더 벗어 나겠네요.)

반응형


반응형
Image Groups

Image Groups는 이번에 새로 소개되는 Corona의 기능입니다. 조금 전에 다뤘던 image sheets 와 이름이 비슷하긴 하지만 완전 다른 기능입니다. Image Groups는 일반 display groups의 sub class 입니다.

Here are the primary differences between Image Groups and normal Display Groups:
아래에 Image Groups가 일반 Display Groups 와 다른 점을 적어놓았습니다.

    Image sheet 객체로부터 생성된다.
    children은 반드시 같은 image sheet에서 나온 것이어야 한다.
    image group내의 모든 child 객체들에대해 제한을 갖는다.
    Image Groups는 nested 될 수 없다. (다른 image groups에 insert 될 수 없다.)

한개의 image sheet에서 나와야 한다? child 객체들에 제한이 있다? 여러분들은 이렇게 제한이 있으면 왜 일반적인 groups를 사용하지 않고 이 Image Groups를 사용해야 하는지 의아해 할 수 있습니다. 정말 그렇습니다. 하지만 여기 이 image groups를 사용함으로서 얻는 장점들도 있습니다.

객체들이 image group에 insert 될 때 일반 display object 들은 할 수 없는 몇가지 이미지 최적화 기능들을 사용할 수 있습니다. 여러분 앱이 최대의 퍼포먼스를 필요로 할 때 그리고 내부적인 그래픽 최적화를 필요로 할 때 바로 이 Image Groups 라는 것을 사용하시면 됩니다. 다음의 제한 사항이 여러분의 앱에 별 상관을 미치지 않을 때 사용하시면 되겠죠?

    image group에 insert 된 객체들은 개별적으로 Mask를 적용할 수 없다.
    nested image groups는 없다. 그러므로 같은 image sheet 안에 있는 다른 곳에 group화 되지 않은 객체만이 같은 image group에 insert 될 수 있다.
    per-object blend mode 가 없다. (image group에 insert 된 객체들에 대해)


How to Use Image Groups

Image Groups는 Image Sheets와 직접적으로 연결이 돼 있습니다. (그러니까 image sheet 을 제대로 보지 못했다면 Image Groups를 보기 전에 이전 글인 image sheet를 보셔야 합니다.)

Image Group Syntax:
display.newImageGroup( imageSheet )

imageSheet 파라미터는 graphics.newImageSheet()를 사용해서 생성한 image sheet 객체를 참고합니다. 한번 Image Group을 만들면 해당 imageSheet에 있는 객체들을 child 객체들로 image Group에 insert 할 수 있습니다.

아래 예제를 참고하세요.

-- assumes an image sheet has already been created
local imageGroup = display.newImageGroup( imageSheet )

local object1 = display.newImage( imageSheet, 1 )
local object2 = display.newImage( imageSheet, 2 )
local object3 = display.newImage( otherImageSheet, 2 )
local object4 = display.newImage( "image.png" )

-- insert objects into image group
imageGroup:insert( object1 )
imageGroup:insert( object2 )

-- lines below result in errors because 'object3' and 'object4'
-- do not belong to same image sheet as imageGroup
imageGroup:insert( object3 )
imageGroup:insert( object4 )

위 예제에서 보듯이 imageGroup은 약간 까다롭습니다. object3,object4 같이 다른 imageSheet나 imageSheet를 사용하지 않은 일반 이미지 객체는 위에서처럼 imageGroup에 insert 될 수 없습니다. 하지만 이렇게 사용하기 조금 불편하고 제약이 있음에도 불구하고 이 image group을 잘 사용하면 그렇지 않았을 경우에 나왔던 단점들을 훌륭하게 극복할 수 있습니다.

코로나는 일부 device에서 퍼포먼스의 문제가 있었습니다. 하지만 이 image groups를 사용함으로서 이러한 퍼포먼스 문제가 깨끗이 해결 됐습니다.
(저도 fireman -iPhone,iPad-, firefighter -Android- 앱을 개발 한 후에 퍼포먼스 문제로 아주 고민을 많이 했었거든요. 이 문제가 해결 됐다니 아주 반갑네요.)

Removing Image Groups

다른 display 객체들 처럼 image groups도 간단히 remove 할 수 있습니다. object:removeSelf()와 display.remove(object) 로 image groups도 remove 합니다. 이 작업을 한 다음에 nil을 대입해 주시는 걸 잊지 마세요.

image group이 child 객체들을 가지고 있다면 그 child들을 개별적으로 remove 한 다음에 image group을 remove 하시기를 권장해 드립니다.

Sprites

새로운 API인 image sheets, image groups 가 나오면서 기존에 있던 애니메이션을 위한 Corona Sprite API도 좀 더 쉽게 사용할 수 있도록 바뀌었습니다. 아래에 바뀐 sprite API에 대한 summary 가 있습니다.

    이전의 sprite sheets나 sprite sets 같은 혼란스러운 개념들 대신 그냥 image sheets를 사용해서 sprite animation 기능을 사용하시면 됩니다.
    변경된 API는 좀 더 직관적이고 이해하기 쉽습니다.
    image sheets를 사용함으로서 dynamic 하게 이미지 해상도에 맞게 작업을 해 줍니다. 다른 말로 이제 sprite를 사용함으로서 retina graphics를 아주 쉽게 사용할 수 있습니다.
    좀 더 파워풀하고 유연한 기능을 위해 새로운 프로퍼티들과 메소드들이 제공됩니다.
   
   
또한 sprite 객체들이 같은 image sheet에 있다면 image groups에 insert 할 수 있습니다. 이것을 사용하면 이전에 image groups에서 얘기했던 제한들이 sprite 객체에도 적용되게 됩니다.

Syntax
display.newSprite( [parent, ] imageSheet, sequenceData )
[Editors Note: Multisprites are not available yet. Sorry for the confusion]
[멀티 스프라이트는 아직 지원되지 않습니다. 혼돈을 드려서 죄송합니다.]

display.newSprite는 single image sheet에서 애니메이션 효과를 주는 객체를 생성하기 위해 사용하는 함수 입니다.
display.newMultiSprite()는 여러 image sheet으로부터 애니메이션 효과를 주기 위한 frame들을 불러와서 애니메이션 효과를 줄 때 사용합니다. 하지만 아직까지는 이 multi sprites는 지원되지 않습니다.

sequenceData는 특적 sequence에 대한 데이터를 가지고 있는 테이블 입니다. 혹은 single object에 대해 1개의 애니메이션 sequence 이상의 sequence를 가지고 있다면 sequenceData는 각각의 sequence에 대한 데이터를 가지고 있는 테이블 배열이 될 겁니다. 예를 들어 charactor 객체를 가지고 있다고 상상해 보면, 걷기, 점프하기, 수영하기 등의 각기 다른 애니메이션 효과를 가지고 있을 수 있습니다.
sequence는 해당 imagesheet의 순서대로 1,2,3,4 가 될 수 있고 1,3,4,6,9 처럼 순서대로 안 돼 있을 수 있습니다. 이 두 경우에 대한 예제를 모두 보시겠습니다.

Single Sequence; Consecutive Frames

아래의 경우는 1,2,3,4 처럼 한개의 image sheet에 있는 frame 순서대로 sequence가 이뤄지는 경우 입니다.

-- Example assumes 'imageSheet' already created from graphics.newImageSheet()

-- consecutive frames
local sequenceData = {
    name="walking",
    start=3,
    count=6,
    time=100, -- Optional. In ms. If not supplied, then sprite is frame-based.
    loopCount = 0 -- Optional. Default is 0 (loop indefinitely)
    loopDirection = "bounce" -- Optional. Values include: "forward","bounce"
}

local character = display.newSprite( imageSheet, sequenceData )

Single Sequence; Non-Consecutive Frames

아래 예제는 frame의 순서와 상관없이 sequence가 이뤄지는 경우인데요. 기본적으로 위의 예제와 같습니다. 다만 frames라는 배열을 넣어야 하는데 이것은 image sheet 안의 frame들의 index들을 넣은 배열입니다.

-- Example assumes 'imageSheet' already created using graphics.newImageSheet()

-- non-consecutive frames
local sequenceData = {
    name="walking",
    frames= { 3, 4, 5, 6, 7, 8 }, -- frame indexes of animation, in image sheet
    time = 50, -- Optional. In ms. If not supplied, then sprite is frame-based.
    loopCount = 0 -- Optional. Default is 0.
}

local character = display.newSprite( imageSheet, sequenceData )

Multiple Sequences

아래 예제는 하나의 sprite 객체에 어떻게 여러개의 애니메이션 sequence들을 가질 수 있는지를 보여 줍니다.
보시면 각 애니메이션 별로 1,2,3,4 처럼 순서대로 index를 사용할 수 있고 비 순서적으로 frame 인덱스를 넣어서 애니메이션을 만드실 수도 있습니다.

-- Example assumes 'imageSheet' already created using graphics.newImageSheet()

local sequenceData = {
    { name="walking", start=1, count=3 },
    { name="running", frames={ 3, 4, 5, 6, 7, 8 }, time=50, loopCount=4 },
    { name="jumping", start=9, count=13, time=300 }
}

local character = display.newSprite( imageSheet, sequenceData )

start/count, frame 파라미터이외에 다른 sequenceData 파라미터들이 있습니다.
아래 내용을 참조하세요.
   
    name - sequence에 대한 unique한 이름 애니메이션이 실행 될 때 이 name을 사용해서 sequence를 load 합니다.
    start & count - consecutive-frame sequences (순차적인 frame 시퀀스)를 위한 파라미터 입니다. start는 시작하는 frame이고 count는 start 부터 끝나는 frame 까지의 index 갯수를 말합니다.
    frames - non-consecutive-frame sequence (비 순차적인 frame 시퀀스)를 위한 파라미터 입니다. 애니메이션을 위해 play 되는 frame 들의 순차적이지 않은 index 데이터를 말합니다.
    time - 각 frame들 사이의 시간 (밀리세컨드)을 말합니다. 이 숫자를 지정하지 않으면 여러분 앱의 기본 framerate을 사용하게 됩니다.
    loopCount - 애니메이션이 몇번 일어나는지 그 숫자를 써 넣으시면 됩니다. 1을 넣으면 애니메이션이 1번 일어나고 멈추게 됩니다. 디폴트는 0이구요 애니메이션이 계속 반복되는 겁니다.
    loopDirection - forward 와 bounce가 있습니다. 둘 중에 아무것도 지정하지 않으면 디폴트인 forward가 지정됩니다. bounce는 forward로 진행했다가 다시 start로 가지 않고 backward로 진행하는 겁니다.
    imageSheet - 이 파라미터는 multi-sprite를 사용할 경우에 필요한 겁니다. (display.newMultiSprite). 해당 시퀀스가 속한 image sheet를 표시합니다. 하지만 아직까지는 multi sprite를 지원하지 않기 때문에 이 파라미터를 사용할 일은 없습니다.

Using “Old” Spritesheet Data

많은 third-party 툴들이 old spritesheet 데이터 포맷을 사용해서 작업을 합니다. 그래서 image sheet 객체를 생성할 때 이 데이터 포맷을을 사용할 수 있도록 했습니다. 아래 그 방법을 보시죠.

local oldStyleSpriteSheetData = require("uma").getSpriteSheetData()

local options =
{
    spriteSheetFrames = oldStyleSpriteSheetData.frames
}

local imageSheet = graphics.newImageSheet( "uma.png", options )

Sprite Properties

일반적인 display object 프로퍼티 이외에 sprite에서 사용하는 프로퍼티들이 있습니다.

    spriteObject.frame – Read-only – 로드된 시퀀드싀 frame index를 보여 줌
    spriteObject.numFrames – Read-only – 로드된 시퀀스의 frame 갯수를 보여 줌
    spriteObject.isPlaying – Read-only – 이름 그대로 현재 플레이되고 있는지 여부를 알려 줌.

Sprite Methods

아래는 display.newSprite()로 생성된 모든 object들과 연관된 sprite만의 메소드들입니다.

    spriteObject:setSequence( name ) – name을 가지고 애니메이션 시퀀스를 로드함. name 파라미터가 없을 경우 현재 로드된 시퀀스의 첫번째 프레임이 보여지게 됩니다.
    spriteObject:play() – 현재 로드된 시퀀스를 플레이 합니다.
    spriteObject:pause() – 시퀀스내의 현재 보여지는 프레임에서 pause 합니다.

Sprite Listener

old API를 보시면 sprite 객체들은 리스너 함수를 사용해 sprite event를 listen 할 수 있습니다. 이벤트 프로퍼티가 변경됐는데요, 지금은 began과 ended event phase 가 있습니다.

Here’s an example:

local function spriteListener( event )
    if event.phase == "began" then
        -- sequence has began playing; do something

    elseif event.phase == "ended" then
        -- sequence has reached the last frame; do something
    end
end
 
-- Add sprite listener
spriteObject:addEventListener( "sprite", spriteListener )

우리는 이 스프라이트 리스너 함수를 좀 더 유용하게 사용할 수 있도록 작업을 하고 있습니다. 조만간에 공개 될 겁니다.

Removing Sprite Objects

sprite 객체들은 다른 일반 display 객체들을 remove 하듯이 remove 하시면 됩니다. object:removeSelf()를 사용하시고 display.remove(object) 도 사용하세요. 이 작업을 한 다음에 nil값을 대입시키는 것도 잊지 마시구요.

Frame Trimming Not Yet Supported

Image Sheets 나 새로운 Sprite API에서 아직 지원하지 못하는 부분이 있는데 그것은 tril/crop frames 기능입니다. 이 기능이 가능할 수 있도록 지금 현재 작업중에 있습니다. 완료되면 공개 하겠습니다.



Wrapping it all up…

여러 이미지를 하나의 이미지 파일에 넣어서 각각의 이미지를 image sheet나 sprite API를 사용한 애니메이션으로 사용하는 방법을 다뤘습니다.
그런데 저는 개발자로서 그래픽 작업하는게 많이 부담 됩니다.
여러 이미지를 하나의 파일로 만들어야 되고 각각의 이미지들 규격도 알아내야 되고...
이러한 작업을 해 주는 third-party 제품들이 있습니다.
코로나에서는 SpriteLoqTexture Packer 를 추천하고 있습니다.
필요하신 분 잘 활용하세요..
저도 필요하긴 합니다.
요즘 시간적인 여유가 좀 생겨서 개인 프로젝트를 시작하려고 하는데요.
그래픽 부분이 부담 되서....
하여간 새로운 기술들을 잘 활용해서 개성있는 앱을 하나 만들어 보려고 합니다.
반응형


반응형
지난번에 올린 Tutorial 인 In App Purchase on Android 이전에 나왔던 새로운 기능에 대한 튜토리얼이 있었습니다.
그 주에 바빠서 정리를 못했는데 너무 delay 되다 보면 다루지 못하고 넘어갈 것 같아 서둘러 정리합니다.

앱을 만들다 보면 애니메이션을 사용할 때도 있고 꼭 애니메이션을 사용하지 않아도 많은 이미지 파일을 사용하게 됩니다.
disk에서 이미지를 불러와서 rendering 하고 화면에 display 하는 과정은 device의 resource를 많이 사용하는 아주 expensive 한 작업입니다.
너무 많은 이미지 사용은 앱의 용량도 크게하고 퍼포먼스에도 안 좋은 영향을 줍니다.

이번에 Corona에서 image sheet라는 새로운 API를 제공하면서 이러한 이미지 처리를 효율적으로 할 수 있는 방법을 제공하고 있습니다.

저도 Fire Man (Fire fighter)  앱이 너무 많은 애니메이션 기능 사용으로 아이폰 3GS나 구식 안드로이드 폰에서는 퍼포먼스가 아주 느려서 고민이 많았거든요.

다음 앱을 개발할 때는 이 image sheet를 사용해서 퍼포먼스에 문제가 없도록 해야 할 것 같습니다.



Corona build 2012.759 버전에 그래픽 관련 새로운 기능이 추가 됐습니다.
오늘은 이 기능에 대해 알아보겠습니다.

아주 많은 부분이 다뤄질 예정이니 주의를 집중해서 보세요. 조금 어렵기도 합니다. 아마 중급정도 수준의 튜토리얼이라고 할까요? 오늘 다룰 부분 중에는 이전의 Corona Doc나 튜토리얼에 나왔던 개념이 아닌 전혀 새로운 개념들도 있습니다.
오늘의 세가지 주요 topic은 아래와 같습니다.
- Image Sheets (새로운 기능)
- Image Groups (새로운 기능)
- Sprite (완전히 바뀜)

아래 다뤄지는 기능은 Corona sdk build 2012.759 버전 이상에서만 작동 됩니다. 그리고 build 2012.761 버전 이상을 사용하실 것을 권장합니다. 오늘 소개할 기능과 관련돼 약간 수정된 내용이 761버전 이후 부터 적용됐거든요.

Notes on Performance

Graphics의 많은 부분이 바뀌었고 그것과 더불어 performance 향상을 제공합니다. 특히 iPhone 3GS 같은 조금 오래된 기계에서 퍼포먼스가 안좋았던 부분이 많이 개선 됐습니다. 하지만 개발자로서 항상 유념해야 될 것은 잘 구성된 코드가 퍼포먼스 향상의 가장 기본이라는 것입니다.

Image Groups 같은 경우에는 어느 부분에서는 좋은 점을 제공하지만 또한 어느 부분에서는 사용하는데 제한이 있을 수 있습니다.
이 새로운 기능들을 사용할 때 이 잇점과 제한된 점을 잘 생각해서 best 한 선택을 하셔야 합니다.

그리고 또 하나의 좋은 소식은 최근의 코로나 버전에서 다른 방법으로 퍼포먼스 향상 효과를 주었는데요. 그것은 바로 화면 밖에 있는 객체에 대해서는 렌더링이 일어나지 않도록 만들었다는 것입니다. 이 방법은 따로 코딩을 바꾸실 필요는 없습니다. 그냥 최근 버전의 코로나를 다운 받으셔서 새로 빌드 하시면 됩니다.

Image Sheets

이 API는 이번에 새로 나온 기능 입니다. 전문 용어에 익숙하신 분은 texture atlas  기능이라고 하면 이해하시기 쉬우실 겁니다.

이해를 돕기위해 좀 은유적으로 설명을 드리자면 여러분 앱의 모든 이미지들이 한장의 종이에 있고 그 종이의 부분 부분을 가져와서 한개의 이미지처럼 사용할 수 있도록 합니다. 그러면 메모리를 줄일 수 있고 이미지 렌더링 시간도 줄일 수 있어서 큰 퍼포먼스의 향상을 가지고 올 수 있습니다.

disk에서 이미지를 메모리로 불러와서 그것을 렌더링 해서 앱에 표시하는 작업은 아주 비싼(에너지 소비가 많은) 작업입니다. 그래서 각각의 이미지별로 이 작업을 하도록 하기 보다는 여러 이미지를 하나의 이미지화 해서 이런 비싼 작업을 한번만 할 수 있도록 하는게 기본 개념입니다.

Texture Size Limit

한 개의 이미지 파일에 몇개의 실제 이미지를 넣을 수 있는지에 대한 제한은 없다. 하지만 이 부분은 유념해야 한다. 각 디바이스 마다 최대 texture size의 제한이 있다. 한개의 이미지 파일의 width,height에 대한 픽셀 수에 대한 제한값이다. 그러므로 한 이미지 파일안에 몇개의 실제 이미지가 들어가는지는 제한이 없지만 이미지 파일  크기가 너무 크면 사용할 수 없다. (그러면 이론적으로는 디바이스가 허용하는 최대크기의 이미지 파일의 픽셀 수가 최대 하용할 수 있는 이미지 갯수겠네요. 1픽셀짜리 이미지를 사용한다면.. 실제 그렇지는 않겠지만요.. 그러면 그건 이미지가 아니라 그냥 color가 있는 점이겠죠?  그냥 심심해서 상상해 봤어요... ^^)

아래 코드는 해당 디바이스에서 허용하는 texture 의 최대사이즈를 구하는 방법입니다.

print( system.getInfo( "maxTextureSize" ) )

대개 코로나가 지원하는 디바이스들은 허용 사이즈가 아주 크다는 겁니다. 여러분 앱에 수백개의 이미지 객체들이 들어간다고 하더라도 몇 개의 이미지 sheet만 사용해도 충분히 해결이 될 겁니다.

Wait, I’m already doing this!

여러분들 중에는 이전의 Sprite API를 사용해서 이러한 기능들을 이미 사용해 보신 분들도 많이 계실 겁니다. 이전의 Sprite API에서는 애니메이션을 사용하기 위한 기능을 주로 지원했습니다. 그래서 애니메이션이 아닌 정적인 이미지를 만들려면 불필요한 부분들이 많이 있습니다. 메모리를 절약하기 위해 이 Sprite API를 사용한다는 건 좀 부담스러운 일이죠.

그래서 이 image sheet를 지원하게 됐습니다. 이 image sheet API를 사용하면 훨씬 더 효율적으로 그리고 쉽게 사용할 수 있습니다. 이 API를 사용하면 정적인 이미지와 애니메이션 모두에서 사용하실 수 있습니다.

보너스가 한가지 더 있는데요. image sheet는 해상도별로 이미지를 다이나믹하게 지원해주는 기능이 있습니다. (예: retina graphics) 이전의 sprite API에서는 이 같은 기능을 구현하기 위해서는 조금 복잡하게 해야 합니다. 이 의미는 이 기능을 사용하면 이번에 해상도가 아주 높아져서 나온 새 애플(Apple)의 아이패드(New iPad) 에도 자동적으로 이미지가 맞게 지원된다는 겁니다.

How to Use Image Sheets

이제 이 image sheets를 어떻게 사용하는지 알아보겠습니다. 우선 신택스부터 보겠습니다. 그리고 나서 display.newImage()와 display.newImageRect()와 함께 어떻게 image sheets가 사용되는지 알아보는 예제를 다뤄보겠습니다. 이 image sheets를 image group과 sprite animation과 함께 사용 하실 수도 있습니다.

Image Sheet Syntax:
graphics.newImageSheet( filename, [baseDir, ] options )

아주 간단하죠? 파라미터 filename은 실제 image file의 이름이구요. baseDir은 디렉토리 입니다. (system.ResourceDirectory 가 될 수도 있고 아니면 다른 곳이 될 수도 있죠.) options 파리미터는 필수로 이 image sheet의 프레임에 대한 데이터를 가지고 있는 테이블을 가리킵니다.

혼란을 방지하기 위해 image는 image sheet file로 graphics.newImageSheet()로 불러온 파일을 말하고 frame은 그 image sheet안의 각각의 이미지들을 구분해서 말하고 있습니다.

options

option 테이블에는 세가지 케이스가 있을 수 있습니다.

    Simple: image sheet의 모든 frame들이 각각 같은 width와 height를 가질 경우
    Complex: image sheet의 frame들이 각각 다른 width나 height를 가질 경우
    Old-Style:  sprite API 데이터 포맷과 같은 방법입니다. (이전에는 sprite.newSpriteSheetFromData() 함수를 사용했었습니다.)

old style은 이제는 권장하지 않습니다. simple이나 complex 케이스 중 하나만 선택하시면 됩니다. old style에 대해서는 따로 다루지 않겠습니다. 이 케이스가 있는 이유는 이것을 이용하던 다른 third-party 툴들이 있기 때문에 아직 없애지 않은 겁니다.

Simple Example

예제를 하나 소개해 드리겠습니다. 우선 image sheet를 로딩할 때 simple option을 사용하는 방법을 보죠. 다시한번 언급하면 image sheet 내의 모든 frame이 각각 같은 width와 height를 가지고 있는 경우 이 simple option을 사용합니다.

local options =
{
    -- The params below are required
   
    width = 70,
    height = 41,
    numFrames = 2,

    -- The params below are optional; used for dynamic resolution support

    sheetContentWidth = 70, -- width of original 1x size of entire sheet
    sheetContentHeight = 82 -- height of original 1x size of entire sheet
}

local imageSheet = graphics.newImageSheet( "fishies.png", options )

예제에서 보시듯이 simple case option은 진짜로 simple 합니다. options에 들어가는 파라미터는 width,height,numFrames 이렇게 3개밖에 없습니다. 남은 2개의 optional 파리미터들은 dynamic resolution images를 사용할 경우 필요한 겁니다. (예를 들어 retina graphics 를 사용하기 위한 image sheet의 @2x 버전 같은)

Complex Example

image sheets에 여러 다른 객체들을 넣어서 사용할 경우 complex-case option 테이블을 사용해야 합니다. 이 경우 각각의 frame에 대해 정해줘야겠죠.

local options =
{
    -- array of tables representing each frame (required)
    frames =
    {
        -- FRAME 1:
        {
            -- all params below are required for each frame
            x = 2,
            y = 70,
            width = 50,
            height = 50
        },
       
        -- FRAME 2:
        {
            x = 2,
            y = 242,
            width = 50,
            height = 52
        },
    },

    -- optional params; used for dynamic resolution support
    sheetContentWidth = 1024,
    sheetContentHeight = 1024
}

local imageSheet = graphics.newImageSheet( "imageframes.png", options )

위 예제를 보시면 image sheet는 두개의 frame을 가지고 있습니다. simple case와는 조금 다르죠? 왜냐하면 각각의 frame들을 array로 그 값을 지정해줘야 하기 때문입니다. image sheet의 complex-case option 테이블을 이용할 경우 이 예제를 참고로 작성하시면 됩니다.

Using with Existing Image Functions

다음으로는 image sheets로부터 얻어낸 각각의 객체들을 기존의 함수들인 display.newImage()와 display.newImageRect()을 가지고 어떻게 사용해야 하는지를 알아보겠습니다.

일단 image sheet를 이용해서 이미지 객체를 이미 만들었다고 가정한 후 아래 예제를 살펴 보세요.
-- assumes 'options' is already constructed (simple or complex cases)
local imageSheet = graphics.newImageSheet( "myimagesheet.png", options )

-- display.newImage()
--
-- SYNTAX:
-- display.newImage( [parent ,] sheet, frameIndex )

local fish = display.newImage( imageSheet, 2 )
fish.x, fish.y = 100, 100

-- display.newImageRect()
--
-- SYNTAX:
-- display.newImageRect( [parent ,] sheet, frameIndex, width, height )

local frog = display.newImageRect( imageSheet, 4, 40, 82 )
frog:setReferencePoint( display.TopLeftReferencePoint )
frog.x, frog.y = 0, 10

보시다시피 일반적으로 display.newImage()나 display.newImageRect()함수를 사용하는것과 크게 다르지 않습니다. 다른 점은 image sheet를 사용할 경우 특정 filename 대신 해당 frame index number를 사용한다는 것입니다.

여러분들이 image sheet를 로딩할 때 simple을 사용했던지 complex를 사용했던지 상관없이 위 예제처럼 사용하시면 됩니다. (old-style을 사용해도 마찬가지 입니다.)

Power of 2 Dimensions

build 2012.264를 보시면 power of 2 에 대한 제한이 있었습니다. 지금은 이 image sheet를 사용하면서 이 제한이 없어졌습니다. 그 제한들은 width와 hight가 8, 16, 32, 64, 128, 256, 512, 1024, 2048 .... 사이즈에 부합되야 한다는 것이었습니다. 하지만 image sheets를 사용하면서 이런 제약은 없어졌습니다. 하지만 texture memory 관리 등을 생각하면 이 power of 2 에 대한 규칙을 지키는 것이 좋습니다.

Removing Image Sheets

image sheet를 remove하려면 image sheet를 사용하는 객체를 그냥 remove 하시면 됩니다. (image objects, sprites, image groups 등) 그리고 나서 image sheet를 nil로 선언하세요.

아래 예제가 있습니다.

-- obj1 and obj2 are using the image sheet
obj1:removeSelf()
obj1 = nil

obj2:removeSelf()
obj2 = nil

-- remove reference to the image sheet
imageSheet = nil


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

내용이 길어서 2회로 나눠 싣습니다.
다음 글에서는 Image Groups() , Sprites 등에 대해 다룰 겁니다.
반응형


반응형
코로나 SDK에서 안드로이드용 In-App Purchase 관련  API를 내 놨습니다.
이제 아이폰,안드로이드폰 모두에서 코로나로 개발한 In-App Purchase기능을 사용할 수 있게 됐습니다.

아래 이번주에 Corona SDK에서 배포한 안드로이드 In-app Billing 튜토리얼을 정리했습니다.

여러분도 참고하세요.

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

Getting Started with Android In-app Billing

앱을 통해서 돈을 벌 수 있는 방법은 유료 앱으로 publish를 하던가 광고를 달던가 아니면 가상 머니를 사용하는 방법이 있습니다.
그 외에 in-App Billing이 있는데요. 이번에 Corona SDK에서 안드로이드용 In-App Billing 기능을 발표했습니다.

이 in-App Purchase 기능은 모바일 앱을 이용한 많이 애용되는 비지니스 모델인데요 이것을 다른 말로 freemium 이라고 합니다. freemium은 일단 무료로 제공하고 그 외의 고급 기능이나 아이템을 유료로 구매할 수 있도록 하는 것을 말합니다.
모바일 앱의 경우는 다음 레벨로 가거나 특별한 아이템을 구한다던가 virtual currency를 사용하던가 하는 기능을 넣을 수가 있을 겁니다.

유저가 구매를 approve하면 새로운 컨텐츠가 unlock 되거나 외부 소스로부터 다운로드 되던가 할 겁니다.
이 방법을 사용하면 아주 다양한 방법의 수익 모델을 만들어 낼 수 있습니다. 그리고 이번에 Corona SDK에서 안드로이드 용으로 이 기능을 지원했기 때문에 그 다양한 수익 모델을 Corona SDK로 개발하면서도 사용할 수 있습니다.
아이폰 앱에는 이미 이 기능이 지원됐기 때문에 코로나 개발자들은 두개의 시장에서 이 기능을 이용할 수 있게 되겠죠? 이것이 Corona SDK 같은 cross-platform development tool의 매력이라고 할 수 있을 겁니다.

하지만 이 좋은 기능도 개발자나 기획자가 사용할 줄 모르면 아무 소용이 없을 겁니다. 이 기능을 이용하려면 최신버전의 Corona SDK가 있어야 합니다. (현재는 유료 사용자만이 다운 받을 수 있습니다.)



Getting Started

시작하기 전에 여러분이 준비해 두셔야 할 부분이 있습니다.
1. Corona SDK build 2012.769 이후 버전
2. Indie-AndroidCorona Pro 유료 사용자 일 것
3. Android Developer 계정을 가지고 있을 것

1. Android Deveoper Console
안드로이드 내에서 billing이 가능하게 하려면 BILLING 퍼미션이 build.settings에 세팅 돼 있어야 합니다. 그리고 이것을 Android Developer Console에 없로드해서 당신의 앱이 In-App Billing Products를 시작할 수 있도록 만들어 두어야 합니다.
main.lua에 아무런 코딩이 안 돼 있어도 이 작업을 해야 합니다. 그리고 지금 개발이 진행 중이라도 상관없습니다. 어쨌든 우선 이 step을 밟아야 합니다. 여러분의 앱에 In-app 상품을 넣을 수 있으려면 우선 이 과정을 거쳐야 하는게 구글이 만들어 놓은 룰입니다.
당신의 앱이 이미 판매중이거나 마켓에 오픈돼 있는 상태라도 이 기능을 넣으려면 우선 build.settings에 퍼미션을 넣고 업로드 한 다음에 In-App purchase 기능을 개발해야 합니다.
한번 이렇게 해 두면 Android developer console의 해당 앱 밑에 in-app products라는 링크를 클릭해서 이곳에서 올린 in-app 상품을 보실 수가 있습니다.

build.settings에 BILLING 퍼미션을 넣는 방법은 아래와 같습니다.

settings =
{
    orientation =
    {
        default = "landscapeRight",
    },

    android =
    {
        usesPermissions =
        {
            "com.android.vending.BILLING",
        },
    }
}

이 예제에서 주의깊게 보셔야 할 점은 android - usesPermissions의 com.android.vending.BILLING 부분 입니다.
이 부분은 orientation을 지정한 settings 다음에 위치해 있습니다.
 
이렇게 하신 후에 android 앱으로 build하세요. 그리고 그 apk화일을 android developer console에 업로드 하시면 됩니다.

아직까지는 Publish를 하지는 마세요.

그렇게 한 다음부터 In-app 제품을 넣기 위한 Administering In-app Billing Guide를 진행 하실 수 있습니다.

안드로이드 개발자로서 마켓에 앱을 올려보신 분들은 아시겠지만 앱을 다 개발 한 다음에 publish를 해야하는데요. 요즘은 이것을 또 Activate 하는 단계도 생겼더라구요.
혹시 나중에 앱을 다 만들고 나서 마켓에 올리고 테스트 할 때 주의하세요.

Google Documentation

구글 Guide에 대해서는 위에 링크를 걸어 놓기는 했지만 여기에 대해서는 한번 언급하고 넘어가는게 좋을 것 같습니다.

구글에서는 In-app Products를 어떻게 셋업해야 하는지에 대해 꽤 좋은 가이드를 제공하고 있습니다. 그리고 그것을 쉽게 테스트 할 수 있도록 했구요.
만약 여러분이 Android Developer Console쪽에서 어떻게 세팅해야 하는지를 자세히 알고 싶으시면 아래 두 가이드를 참고하세요.

2. The Corona "store" API

이 튜토리얼에서는 구글에서 제공하는 test product identifier들을 사용하겠습니다.
이 테스트 상품에서는 아래 세가지 Action을 가져올 수 있습니다.

- android.test.purchased : 구매가 성공했을 때 얻어 옴
- android.test.canceled : 구매 트랜잭션이 cancel 됐을 때 얻어 옴
- android.test.item_unavailable : 해당 제품 구매가 가능하지 않을 때 얻어 옴

이 테스트 상품의 진짜 nice 한 점은 이렇게 각각 다른 상황에서 여러분들이 이를 어떻게 쉽게 콘트롤 할 수 있을 지 경험할 수 있게 해 준다는 점 입니다. 예를 들어 여러분은 android.test.canceled 상품을 이용해서 유저가 트랜잭션 중 cancel했을 경우에 대한 코딩을 할 수 있게 됩니다.

store.init("google", transactionCallback)

여러분이 코딩을 하면서 이 in-app purchase 기능을 구현하기 위해서는 제일 처음 store.init() API를 사용하게 될 겁니다. 파라미터로는 구글 마켓(얼마전 Google Play로 바뀌었습니다.)을 사용할 거라는 것을 알리는 "google"을 첫번째로 넣고 두번째로는 불러올 함수 명을 넣습니다.

아래 transaction callback listener 함수와 함께 store.init()을 사용하는 방법에 대한 샘플 예제가 있습니다.

local function transactionCallback( event )
    local transaction = event.transaction

    if transaction.state == "purchased" then
            -- Transaction was successful; unlock/download content now
   
    elseif transaction.state == "restored" then
           -- You'll never reach this transaction state on Android.

    elseif transaction.state == "refunded" then
        -- Android-only; user refunded their purchase
        local productId = transaction.productIdentifier
        -- Restrict/remove content associated with above productId now
  
    elseif transaction.state == "cancelled" then
        -- Transaction was cancelled; tell you app to react accordingly here

    elseif transaction.state == "failed" then
            -- Transaction failed; tell you app to react accordingly here
     end

    -- The following must be called after transaction is complete.
    -- If your In-app product needs to download, do not call the following
    -- function until AFTER the download is complete:

    store.finishTransaction( transaction )
end

store.init( "google", transactionCallback )

맨 아래 store.init()이 transactionCallback 함수를 call 하는 보분이 있죠? 이 부분이 Android In-app Billing 이벤트가 발생 했을 때 실행 될 부분입니다.

Handling Refunds

Android In-app billing은 iOS의 In-app billing과 거의 같은데 한가지 refunded라는 트랜잭션 상태가 더 있습니다. iOS와는 다르게 안드로이드 플랫폼에서는 유저가 refund할 수 있도록 해 줍니다. 그래서 유저에게 주었던 contents를 다시 가져오려면 바로 이 refunded 트랜잭션 상태일 때 해야 할 것입니다.

위 샘플에서 보듯이 refunded 트랜잭션 상태에서 받는 중요한 데이터는 event.transaction.productIdentifier입니다. 이 정보를 가지고 여러분은 필요한 작업을 하실 수 있습니다. 아마도 그 필요한 작업은 환불 했을 경우 해당 content를 다시 block 시키는 거겠죠? 아니면 돈을 내고 추가로 다운 받은 파일을 지우는 기능이던가요.

No "restored" State on Android

store.restore() 함수는 로그인 된 유저 계정과 연관된 product id의 리스트를 다시 검색할 수 있도록 해 줍니다. iOS에서는 이 product 리스팅이 transactionCallback listener 의 restored 트랜잭션 상태에서 받을 수 있습니다. 그리고 안드로이드에서는 이 restored 트랜잭션 상태가 없습니다. 대신에 그 상품이 purchased를 다시 실행하면서 그 기능을 할 수 있습니다.
아주 작은 차이이지만 transactionCallback listener 함수를 디자인할 때 잘 고려해서 디자인을 해야 합니다.

Other Transacton Event Data

transaction events와 관계된 다른 모든 데이터들은 iOS의 In-app purchase와 동일합니다. 그러니까 다음 guide를 꼭 봐 주세요.
In-app  Purchase Guide
Transaction Listener Callback Events
그리고 예전에 in-Purchase app 샘플을 분석했던 글을 보시면 도움이 되실겁니다.
코로나 네트워킹 과 웹 서비스 3 (In App Purchase 1)

store.finishTransaction (transaction)

위 샘플 코드에 있는 transactionCallback() 함수에 있는 모두 아주 중요한 것들입니다.
store.finishTransaction은 모든 트랜잭션의 마지막부분에 반드시 call 되야 합니다.
어떤 트랜잭션이 끝났다는 것을 확실히 정의하는 부분입니다.
만약 어떤 파일을 다운로드 받아야 할 때는  어떤 상황이 발생할 까요? 아마도 이 다운로드가 모드 끝나고 나서 store.finishTransaction()이 call 되어야 할 겁니다. (그러려면 어딘가에 network.download() 리스너가 있어야 겠죠.)

만약 이미 코딩은 최초 다운받은 앱에 다 돼 있고 단순히 어떤 content를 unlock 하는 기능만 수행될거면 callback 리스너 마지막 부분에 store.finishTransaction()을 넣으면 되겠죠.

두 경우 모두 첫번째 인수로 event.transaction 테이블을 넘겨줘야 한다는 것을 잊지 마세요.
 store.finishTransaction( event.transaction )

3. Purchasing Products

이제 purchases, refunds, failed/cancelled 같은 트랜잭션들을 어떻게 다뤄야 할 지 알아 보겠습니다. iOS in-app purchase에서와 같이 store.purchase()를 사용해서 트랜잭션을 초기화 합니다. 대개 앱 화면에서 Buy 버튼을 누르면 이벤트 리스너가 감지해서 call할 때 이 동작이 이뤄 집니다.
-- single product purchase
    store.purchase( { "android.test.purchased" } )

-- multi-item purchase
   store.purchase( { "android.test.purchased", "android.test.canceled" } )

여기서도 Google에서 제공하는 테스트 product들을 사용해서 테스트 해 볼 수 있습니다.
좀 더 자세한 정보를 보시려면 store.purchase() documentation 을 참고하세요.

Cross-Platform Development

코로나의 장점은 한번의 코딩으로  아이폰, 안드로이드 용 앱을 만들 수 있는 multi-platform 기능입니다. 이 기능을 좀 더 쉽게 콘트롤 할 수 있도록 코로나에서는 store API에 새로운 프로퍼티를 추가했습니다.
애플의 In-app Purchase는 iOS 디바이스들에서만 가능하고 안드로이드의 In-app Billing은 Google Play에서만 가능합니다. (같은 안드로이드 앱이라도 Nook나 Kindle Fire 에서는 In-app Billing을 사용할 수 없습니다.)
새로 제공되는 기능은 store.availableStores 테이블로 어떤 디바이스 인지를 알수 있는 파라미터 입니다.

아래 예제가 있습니다.

if store.availableStores.apple then
    store.init("apple", transactionCallback)
   
elseif store.availableStores.google then
    store.init("google", transactionCallback)
end

이렇게 코딩을 하면 아이폰용과 안드로이드 용 파일을 따로 관리할 필요가 없겠죠?

iOS의 In-app Purchases와 Android의 In-app Billing은 한가지 크게 다른 개념이 있습니다. 안드로이드에서는 각각의 In-App product들에 대해 정보를 retrieve할 수 없는 반면에 iOS에서는 이 기능이 가능합니다. 이것은 구글의 한계라고 볼 수 있겠죠. 이 의미는 상품의 이름, description같은 정보들을 직접 앱 내에서 혹은 외부 서버에서 개발자들이 직접 관리해야 한다는 얘기 입니다. (만약에 필요하다면요.)

이 다른 점 때문에 store.loadProducts() 함수가 있는데 이것은 iOS in-app purchases에서만 사용될 수 있습니다. 여러분이 iOS,안드로이드 두 플랫폼 모두에서 돌아가는 앱을 개발하고자 한다면 이 함수가 가능한지 여부를 체크하기 위해서 store.canLoadProducts 프로퍼티를 사용하실 수 있습니다. (store.init()에서 pass된 플랫폼이 무엇인가에 따라 true와 false를 return 하게 됩니다.)

 Additional Resources

Android In-app Billing 을 테스트 하시려면 구글에서 제공하는 테스트 상품으로 쉽게 테스트하실 수 있구요. 또 여러분이 만든 상품으로도 쉽게 테스트 하실 수 있습니다. 이번에 새로 제공되는 Corona SDK 내의 InAppPurchase 샘플 코드를 참조하세요.
/SampleCode/Networking/InAppPurchase 에 있습니다.

그리고 예전에 iOS in-app purchase와 관련해서 올려 놓은 In-app Purchase Guide도 보시구요. iOS용 가이드 이지만 대부분이 안드로이드에서도 적용 됩니다. 그리고 각각의 store API 함수에 대한 정보를 보시려면 In-app Purchase API Reference를 보시기를 권장합니다.

이 기능은 Corona Daily Build 2012.760 이후 버전에서만 가능합니다.
현재까지 이 버전은 유료 사용자만 받으실 수 있습니다.

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

요즘 예전처럼 폭풍집필이 잘 안되네요.
슬럼프인가봐요.. 휴~~~~~~~
여러분의 추천이 큰 힘이 될 수 있을 것 같아요.
추천 부탁드려요.

추천.. 추천.. ~~~ ~~~ 감사.. 감사..
반응형

Corona Display Groups 101

2012. 3. 1. 22:03 | Posted by 솔웅


반응형
이번주 코로나에서 제공하는 튜토리얼은 Display Group에 대한 겁니다.

기본 API는 여기에 있습니다.
group.numChildren, group:insert(), group:remove() 세가지 요소들이 있습니다.
group.nemChildren은 해당 그룹에 있는 객체들의 숫자를 알 수 있는 메소드 입니다.
주로 그룹 내 모든 객체들에 어떤 변경이나 효과를 줄 때 for 문에서 이용하게 됩니다.
group:insert()는 새로운 객체를 해당 그룹에 추가할 때 사용하구요.
기본은 맨 마지막에 삽입 되는데 원하면 삽입되는 위치도 지정해 줄 수 있습니다.
그리고 group:remove()는 그 그룹에서 특정 객체를 제거할 때 사용합니다.

그룹을 처음 만들 때는 display.newGroup()을 사용하구요.
여기를 클릭하시면 이와 관련되서 정리한 문서를 보실 수 있습니다.

오늘 다룰 튜토리얼의 원본은 여기 있습니다.
이 원본을 정리해 두겠습니다.

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

Corona Display Groups 101



이번주 다룰 주제는 모든 개발자들이 가장 자주 접하는 코로나 프로그래밍에서 아주 기본이 되는 group에 관련 해서 다루겠습니다. 이 group을 자주 사용했던 개발자 분들에게도 아무 도움이 되는 내용일 겁니다.

What are Groups?

코로나에서 화면에 나오는 모든 객체들은 display object 입니다. 이 display object에는 세가지 유형이 있습니다.
- Vectors (Shapes & Text)
- Images (익히 알려져 있는 "normal" display objects)
- Groups

코로나 display group(이후 부터는 group으로 하겠습니다.)은 좌표(x,y), 투명도, 회전값 등의 여러 property들과 메소드들을 가지고 있습니다. 이는 이 object를 control 하는데 아주 편리한 기능을 제공해 줍니다.

Object Grouping

group의 목적은 다른 object 들을 하나로 묶는 것입니다. 그래서 여러개의 object들을 하나의 object 처럼 다룰 수 있도록 해 줍니다. 이 기능은 프로그래밍 할 때 손쉽게 여러 object들을 제어할 수 있게 도와줘서 편리하기도 하지만 반면에 불편한 점도 있습니다.
여러분들이 어떤 display object를 group에 넣고 이 그룹의 property들을 변경하더라도 그 그룹 안에 있는 개별 object들의 property들은 변경되지 않습니다. 다만 group의 property에 따라서 display 될 뿐입니다.
이 group이 또 다른 group에 포합 될 수도 있고 그 group이 또 다른 group에 포함될 수도 있습니다. 이 경우에도 group의 프로퍼티와 그룹 내 object의 프로퍼티의 관계는 위와 같습니다.
이런식으로 group의 포함관계가 많이 될 수록 이 그룹과 그 안의 요소들을 콘트롤 하는게 만만치 않습니다.

하나 예를 들어보죠.

A와 B 라는 그룹이 있습니다. 그리고 보통 display object인 C 가 있습니다. 모두 위치는 0,0 입니다. C가 B에 insert 됐습니다. B는 다시 A에 insert 됐습니다. 그러면 이제 A의 x를 100으로 바꾸고 B의 x를 50으로 바꾸겠습니다. C의 좌표는 여전히 0,0입니다. 하지만 이 C가 실제로 화면에 표시 될 때는 150,0 의 위치에 표시 됩니다. 왜 그럴까요? 왜냐하면 그 parent인 B의 x 값이 50이고 또 B의 parent인 A의 x 값이 100이기 때문입니다.

약간 혼란스럽기는 하지만 실제 프로그래밍할 때는 그렇게 크게 문제가 되지는 않죠? 그냥 group별로 display 되면 되니까요.  하지만 touch 리스너에 따라 그룹 내의 특정 object를 움직이거나 회전시키거나 크기를 변화시킬 때는 약간 혼란 스러울 수가 있습니다. 만약 여러분의 parent group들의 값이 변경 됐을 때 그 children들에 어떻게 영향을 미치는지 자세히 알고 있다면 이런 경우 문제없이 프로그래밍을 하실 수 있을 겁니다.

Group Methods

Group에는 insert와 remove라는 두개의 메소드가 있습니다.

group:insert( [index,], child, [, resetTransform] )

이 메소드는 group에 display object 를 insert 할 때 사용됩니다. 이 함수에는 한개의 필수 parameter가 있고 두개의 옵션 파라미터들이 있습니다.

아래 예제는 이 insert  함수를 어떻게 사용하는지에 대해 보여줍니다. 주석을 잘 보시면 그룹의 프로퍼티들이 바뀔 때 그 안의 child는 어떤 영향을 받는지 잘 설명돼 있습니다.

-- 이미지 객체를 생성한다. 객체의 이름은 object1이고 위치는 50,50이다.
local object1 = display.newImage( "image.png" )
object1.x, object1.y = 50, 50
-- display group을 생성한다. 그룹의 이름은 group1이다.
local group1 = display.newGroup()
-- 그룹의 위치를 100,100으로 한다.
group1.x, group1.y = 100, 100
-- 이미지 객체를 그룹에 insert 한다.
group1:insert( object1 )
-- 이미지 객체의 위치는 50,50이지만 화면에 나타날 때는 150,150 위치에
나타난다.
-- group1에 insert 돼 있기 때문에 group의 위치인 100,100이 더해
지기 때문이다.

디폴트로 insert된 객체는 그 그룹의 top 에 위치 됩니다
디폴트로 insert된 객체를 그 그룹의 top에 위치 됩니다. 하지만 옵션 파라미터인 첫번째 argument인 index 번호를 바꿈으로서 해달 객체의 순서를 지정할 수 있습니다.
이 때 index번호 1은 가장 하위를 가리킵니다. 새로운 객체를 1로 지정하면 이전의 객체들은 하나씩 위로 올라갑니다. 다른 위치에 insert해도 그 상위의 객체들은 하나씩 위로 올라가게 됩니다.

예를 들어서 그룹에 4개의 객체들이 있는데 여러분이 새 객체를 index 위치 2로 지정하면 새 객체를 그룹내의 두번째 객체가 되고 기존의 2,3,4번째 객체들은 3,4,5번째 객체들로 바뀌게 됩니다.

만약에 새 객체를 그 그룹 안에 있는 객체수 보다 더 큰 수의 index 위치로 지정하게 되면 코로나는 그냥 default 로 처리해서 top에 위치시킵니다.

그 외에 optional parameter로 resetTransform이 있습니다. 만약에 여러분이 객체들의 위치, 회전, 투명도, 크기 등을 그룹에 insert 할 때 디폴트값으로 재 세팅하기를 원하신다면 이것은 true로 세팅 돼 있어야 합니다. 즉 그 객체가 가지고 있는 기본 값(위치, 회전, 투명도 등)은 적용되지 않고 오직 group의 값만 적용 되게 됩니다. resetTransform의 디폴트 값은 false입니다.

IMPORTANT NOTE

하나의 객체는 한 그룹 내에만 소속될 수 있습니다. 그렇기 때문에 객체를 group A에 insert 한 후 다시 group B에 insert 하면 이 객체는 A의 그룹에서는 자동으로 해제 되게 됩니다.
참고로 코로나의 display stage도 하나의 display group입니다. 그렇기 때문에 display object나 그룹이 생성되면 자동적으로 이 stage 그룹에 포함 되게 됩니다.
그렇기 때문에 한 객체가 다른 그룹에 insert 되면 그 객체는 더이상 stage에 소속 돼 있지 않겠죠. 하지만 그 그룹의 stage에 소속돼 있으니까 화면에 display하는데는 문제 없을 거예요.

group:remove( indexOrChild )

이 메소드는 그룹 내에 있는 특정 객체를 remove 하기 위해 사용 됩니다. removeSelf()나 display.remove() 와 개념이 유사합니다. 괄호 안에는 객체 이름이나 그 객체의 index 번호를 넣으면 됩니다.
어떤 display object를 remove 할 때 그 객체를 nil 로 만드는 것을 잊지 마세요. 효율적으로 메모리를 사용하는데 중요한 사항입니다.

Group Properties

group도 display object이기 때문에 이 그룹도 일반 display object들이 가지는 일반적인 메소드나 프로퍼티들을 가지고 있습니다. 그리고 numChildren이라는 프로퍼티를 덤으로 가지고 있구요.
이 numChildren은 그룹 내에 몇개의 객체들이 있는지 쉽게 알수 있게 해 줍니다. 이 프로퍼티는 for문등 루프를 돌릴 때 주로 사용 됩니다.
아래 예제는 그룹내의 모든 object들에 대해 어떤 값을 변경하기 위해 루핑 하는 방법을 보여줍니다.
-- This example assumes you have already inserted
-- several objects into the 'group2' display group. -- forward iteration
for i=1,group2.numChildren do
  local child = group2[i]   child.score = child.score + 1
end
-- backwards iteration; useful for removing objects manually
for i=group2.numChildren,1,-1 do
  local child = group2[i]   totalScore = totalScore + child.score
  child:removeSelf()
end
Property Differences

Group과 일반 이미지 객체와 몇가지 다른 점이 있습니다.
가장 중요한 다른 점은 default reference point가 다르다는 겁니다. 그룹은 display.TopLeftReferencePoint가 디폴트이고 다른 display object는 display.CenterReferencePoint가 디폴트입니다.
그 다음은 contentWidth와 contentHeight인데 당연히 group은 자체적으로 어떤 모양이 없기 때문에 고정된 width와 height가 없습니다. 그 child 중에 가장 큰 object의 값을 가질 겁니다.

Wrap-Up and Further Reading

지금까지 정리한 내용만 충분히 이해하면 코로나 Display Group에 대해서는 전문가가 되신 겁니다.
이번 튜토리얼은 basic 내용이긴 하지만 기본에 충실한 것이 좋은 앱을 만드는 바탕이 됩니다.

~~~~~~~~~~
반응형

WebView, Video and Network Updates

2012. 2. 13. 06:43 | Posted by 솔웅


반응형
아래 내용은 Corona SDK Web site 에 원본이 있습니다.
아래 내용은 이 원본을 번역한 내용입니다.

===========
=========== =========== =========== =========== ===========

WebView, Video and Network Updates



이번에 native Web/Video objects 가 릴리즈 되면서 API에 몇가지 중요한 기능이 추가 됐습니다. 또한 Network library에 몇가지 아주 유용한 기능이 추가 됐구요.

***** Web View Updates

아래와 같이 native web view object 들의 상태를 back이나 forward로 control 할 수 있습니다. back()/forward() 메

소드를 써서 간단하게 조정 가능합니다.

local webview = native.newWebView( 0, 0, 320, 240 )
webview:request( "http://www.anscamobile.com" )
webview:request( "http://www.google.com" )
webview:back()   -- goes back to www.anscamobile.com
webview:forward() -- goes forward to www.google.com

canGoBack/canGoForward

위 두 메소드와 관련해서 당신의 web view object가 back이나 forward가 가능한지에 대해 체크할 수 있습니다.
예를 들어 web view object를 처음 만들었다면 이 web view object는 단지 하나만 있기 때문에 back()을 할 수도 없고

forward()를 할 수도 없습니다. 이럴 경우 canGoBack/canGoForward 모두 false 가 됩니다.

print( webview.canGoBack )  -- false

좀 더 자세한 내용은 native.newWebView() documentation을 참조하세요.



*****Video Properties and Phases


새로운 video objects와 관련해서 isMuted 프로퍼티가 새로 선 보였습니다. 이것은 get/set 프로퍼티 입니다. 여러분의 앱에 video

를 임베딩 하는데 있어 좀 더 유연하게 콘트롤 할 수 있도록 도와 줄 겁니다. 또한 새 phase 프로퍼티도 생겼는데요 video 리스너가 그것입니

다. 이 리스너에는 ready와 ended phases 가 있습니다.

아래 예제를 참고하세요.

local function videoListener( event )
    if event.phase == "ready" then
        print( "Video is ready." )

    elseif event.phase == "ended" then
        print( "Video playback has ended." )
    end
end

local videoObj = native.newVideo( 0, 0, 320, 480 )
videoObj:addEventListener( "video", videoListener )

-- load a video and jump to 0:30
videoObj:load( "myvideo.m4v", system.DocumentsDirectory )
videoObj:play()

좀 더 자세한 내용은 native.newVideo() documentation을 참조하세요.


***** Network Library

이 native object 관련 내용을 추가하면서 networkRequest 가 한가지 status와 url 프로퍼티를 갖게 됐습니다.
이것은 network request들을 콘트롤 하는데 유용하게 이용될 수 있습니다.
현재까지는 Mac/iOS/Android 에만 지원 됩니다. (Windows 에서는 조만간 지원될 예정입니다.). 이 두 새로운 프로퍼티를 사용하는 방법

에 대한 예제가 아래 있습니다.


local function networkListener( event )
    local status = event.status
    local url = event.url

    print( "The url " .. url .. " returned a status code of: " .. status
end

network.request( "http://www.google.com", "GET", networkListener )

좀 더 자세한 사항은 Network API documentation 을 참조하세요.

이 기능들을 이용하시려면 코로나 최신 버전을 사용하셔야 합니다.
최신버전은 subscriber들에게만 오픈 돼 있습니다.

추천 버튼 꾹꾹 눌러 주세요. ~~~~~~~~~~~~~~
반응형

Tutorial: Text Input with Native UI

2012. 2. 10. 00:24 | Posted by 솔웅


반응형
아래 내용은 아래 블로그에서 그 원본을 보실 수 있습니다.
http://blog.anscamobile.com/2012/02/tutorial-text-input-with-native-ui/


Text Input with Native UI

아래 내용은 코로나 SDK로 Text Field와 Text Box를 사용할 때 유용한 방법에 대해 설명한 내용입니다.

***** Text Fields vs. Text Boxes

텍스트 필드와 텍스트 박스는 아래와 같은 각각의 특징이 있습니다.

Text Fields
- 1줄 입력 기능을 제공한다. (스크롤 기능이 없다.)
- password fields로 이용할 수 있다.
- 오직 숫자만 입력하도록 설정할 수 있다. (전화기의 숫자 키보드가 나옴.)

Text Boxes
- 여러 줄 입력 기능을 제공한다. (스크롤 기능이 있다.)
- text만 보이도록 하기 위해 background를 숨길 수 있다.
- read only로 세팅할 수 있다.

아래는 두 가지 모두에 있는 기능들입니다.
- 폰트, 글자 사이즈, 글자 색을 바꿀 수 있다.
- 배경색을 바꿀 수 있다.
- userInput listening 기능이 있다.

더 자세한 신택스를 보려면 아래를 참조하세요.

Native Text Fields Documentation
Native Text Boxes Documentation 
   



***** Events and Listeners   

userInput 이벤트를 어떻게 감지하는지에 대해 알아보겠습니다.
텍스트 필드나 텍스트 박스를 생성할 때 이 userInput 이벤트 리스너를 달 수가 있습니다.
그러면 그 이벤트가 일어나는 것을 감지해서 어떤 특정한 기능을 넣을 수가 있습니다.

이 userInput 이벤트에는 몇가지의 phases가 있습니다.

began: 이 단계는 유저가 스크린에 키보드를 나오게 하는 순간입니다. 이 경우 키보드가 중요한 객체를 가리지 않게 하기 위해 위치를 변경하는 등의 기능을 넣을 수 있습니다.
edited: 이 단계는 유저가 텍스트 필드나 텍스트 박스에 타이핑을 하는 동안 일어납니다.
ended: 텍스트 필드나 텍스트 박스가 focus를 잃는 단계입니다. 예를 들어 다른 텍스트 필드/박스 를 tap할 때 등입니다.
submitted: 유저가 return/enter 키를 눌렀을 때 입니다. 즉 텍스트 필드/박스 의 내용을 submit 할때라고 얘기할 수 있습니다.

아래 userInput 리스너 를 이용하는 예제 코드가 있습니다.

local function onUserInput( event )
    if event.phase == "began" then
        print( "Keyboard has now shown up." )

    elseif event.phase == "edited" then
        print( "User has entered text." )

    elseif event.phase == "ended" then
        print( "We have lost focus." )

    elseif event.phase == "submitted" then
        print( "User is done editing, let's do something with text." )
    end
end

***** Not Normal Display Objects!
확실히 알아둬야 할 것은 native text field나 native text box는 Corona display 객체가 아닙니다.
말 그대로 native입니다. Corona SDK의 object들이 아니라 그 핸드폰의 객체들인거죠. 정확히는 그 핸드폰의 OS의 객체라고 하면 더 정확할 겁니다.
이 native객체들은 다른 Corona SDK의 객체들 처럼 위치나 투명도 같은것을 바꿀수는 있어도 Corona SDK내에서 다른 객체들과 함께 Group화 할 수는 없습니다.
그러면 화면전환등을 할 때 다른 객체들과 같이 움직이지 않을 겁니다. 보기가 조금 어글리 할 겁니다.
이러한 것을 보기 좋게 하는 방법이 있습니다. 다른 객체들처럼 움직이는 것 같이 보이도록 하는 거죠.

아래 몇가지 팁이 있습니다.
- native 객체들을 표현할 때 placeholders처럼 보이도록 text object를 만듭니다. 그리고 그 바탕에 사각형이나 rounded 사각형을 놓구요.
  여기서 text object나 사각형들은 모두 Corona SDK의 객체들입니다.
  사용자가 placeholder를 터치하면 그때 native 객체가 나오도록 합니다. 즉 유저가 editing할 때는 이 native 객체가 나와 있도록 하는 것이죠.
  유저가 입력을 끝내면 다시 native 객체를 없애고 다시 placeholder를 표시합니다. 이 때 물론 유저가 입력한 값이 해당 placeholder에 보이겠죠.
- placeholder objects들에 대해 transitions 효과를 줍니다.
- hasBackground 프로퍼티를 이용해서 텍스트 박스의 백그라운드를 안보이도록 합니다. 그리고 여러분의 백그라운드를 넣습니다.
  이 백그라운드는 일반적인 Corona SDK 객체를 이용합니다. 그래서 그 텍스트 박스를 코로나 내부 객체와 섞이도록 합니다.
 
이러한 방법들은 여러분들이 할 수 있는 여러 방법중에 극히 일부분입니다.
이러한 방법을 기초로 여러분들 나름대로 여러 효과들을 내실 수 있을 겁니다.

마지막으로 한가지 중요한 점은 이 native objects들은 object:removeSelf() 와 nil값을 대입하는 과정을 거치면서 분명히 메모리 관리를 해 주셔야 한다는 점 입니다.

***** Simulator Notes
현재 native text box와  native text field는 Mac Simulator, Xcode Simulator 그리고 디바이스에서 작동을 합니다.
맥 시뮬레이터로 테스트를 할 경우 Xcode 시뮬레이터나 디바이스에서 반드시 테스트를 해 보셔야 합니다.
왜냐하면 맥 시뮬레이터(Mac)에서 보이는 것과 실제 기계(iOS)에서 보이는 것이 다를 수가 있습니다.

맥 시뮬레이터에서는 리스너나 이벤트 처리 같은 것을 테스트 할 수 있어 개발 시간을 save해 주는 잇점이 있지면 실제 display는 다르게 작동되므로 반드시 기계에서 테스트해 보셔야 합니다.
가장 좋은 테스트 도구는 실제 device에 빌드해서 하는 것 입니다.

***** Basic Note App
아래 간단하게 Note를 할 수 있는 Note App을 한번 만들어 보겠습니다.
텍스트 필드나 텍스트 박스를 생성하는 예제 이구요 외부 소스로부터 가져와서 다이나믹하게 내용을 뿌려주는 효과도 보실 수 있을겁니다.
(여기서 외부 소스로는 text 파일이 사용됩니다.)

모든 코드는 하나의 코드파일에  들어갈 겁니다. 즉 main.lua한 파일에 다 들어갈 겁니다.

1. Creating the Interface
첫번째 단계는 아주 단순합니다. 앱의 배경을 만들고 위에 타이틀 바를 만들고 두개의 버튼 위젯을 만듭니다.

display.setStatusBar( display.DefaultStatusBar )

local widget = require "widget"
local sbHeight = display.statusBarHeight
local tbHeight = 44
local top = sbHeight + tbHeight

-- forward declarations
local titleField, noteText, loadSavedNote, saveNote

-- create background for the app
local bg = display.newImageRect( "stripes.jpg", display.contentWidth, display.contentHeight )
bg:setReferencePoint( display.TopLeftReferencePoint )
bg.x, bg.y = 0, 0

-- create a gradient for the top-half of the toolbar
local toolbarGradient = graphics.newGradient( {168, 181, 198, 255 }, {139, 157, 180, 255}, "down" )

-- create toolbar to go at the top of the screen
local titleBar = widget.newTabBar{
top = sbHeight,
gradient = toolbarGradient,
bottomFill = { 117, 139, 168, 255 },
height = 44
}

-- create embossed text to go on toolbar
local titleText = display.newEmbossedText( "NOTE", 0, 0, native.systemFontBold, 20 )
titleText:setReferencePoint( display.CenterReferencePoint )
titleText:setTextColor( 255 )
titleText.x = 160
titleText.y = titleBar.y

-- create a shadow underneath the titlebar (for a nice touch)
local shadow = display.newImage( "shadow.png" )
shadow:setReferencePoint( display.TopLeftReferencePoint )
shadow.x, shadow.y = 0, top
shadow.xScale = 320 / shadow.contentWidth
shadow.yScale = 0.25

-- create load button (top left)
local loadBtn = widget.newButton{
label = "Load",
labelColor = { default={255}, over={255} },
font = native.systemFontBold,
xOffset=2, yOffset=-1,
default = "load-default.png",
over = "load-over.png",
width=60, height=30,
left=10, top=28
}

-- onRelease listener callback for loadBtn
local function onLoadRelease( event )
loadSavedNote()
end
loadBtn.onRelease = onLoadRelease -- set as loadBtn's onRelease listener

-- create save button (top right)
local saveBtn = widget.newButton{
label = "Save",
labelColor = { default={255}, over={255} },
font = native.systemFontBold,
xOffset=2, yOffset=-1,
default = "save-default.png",
over = "save-over.png",
width=60, height=30,
left=display.contentWidth-70, top=28
}

-- onRelease listener callback for saveBtn
local function onSaveRelease( event )
saveNote()
end
saveBtn.onRelease = onSaveRelease -- set as saveBtn's onRelease listener

-- display warning that will show at the bottom of screen
local warning = display.newImageRect( "warning.png", 300, 180 )
warning:setReferencePoint( display.BottomCenterReferencePoint )
warning.x = display.contentWidth * 0.5
warning.y = display.contentHeight - 28



2. Text Box and Text Field
두번째 단계는 텍스트 필드와 텍스트 박스 객체를 생성할 겁니다.
이 두 native text 위젯을 사용할 때 폰트를 지정해주기 위해서 native.newFont()를 어떻게 사용하는지 잘 봐 두세요.
그리고 텍스트 박스는 디폴트가 read only라는 것을 명심해 두시구요.
그래서 텍스트 박스에 유저가 문자를 입력할 수 있도록 하려면 isEditable 프로퍼티를 사용하셔야 합니다.

-------------------------------------------------------------------------------------
-- Create textFields

local textFont = native.newFont( native.systemFont )
local currentTop = sbHeight+tbHeight+shadow.contentHeight+10
local padding = 10

-- create textField
titleField = native.newTextField( padding, sbHeight+tbHeight+shadow.contentHeight+10, display.contentWidth-(padding*2), 28 )
titleField.font = textFont
titleField.size = 14

currentTop = currentTop + 28 + padding

-- create textBox
noteText = native.newTextBox( padding, currentTop, display.contentWidth-(padding*2), 264-currentTop-padding )
noteText.isEditable = true
noteText.font = textFont
noteText.size = 14



여기까지 하면 겉모습은 완성 된 겁니다.

다음은 외부 txt파일로 저장하고 불러오는 부분을 다룰 겁니다.

3. Saving and Loading
이 단계에서는 saving과 loading 기능을 넣을 겁니다. 이 기능들은 유저가 Save 버튼이나 Load 버튼을 누르면 생성하도록 하겠습니다.
그리고 최초에 앱이 시작될 때 loadSavedNote()함수가 불려져서 이전에 저장됐던 내용들이 display되도록 하겠습니다.

-------------------------------------------------------------------------------------
-- Saving and Loading functions

function loadSavedNote()
local title_path = system.pathForFile( "title.txt", system.DocumentsDirectory )
local note_path = system.pathForFile( "note.txt", system.DocumentsDirectory )
local fh_title = io.open( title_path, "r" )
local fh_note = io.open( note_path, "r" )

-- load the title
if fh_title then
titleField.text = fh_title:read()
io.close( fh_title )
end

-- load the note
if fh_note then
noteText.text = fh_note:read( "*a" ) -- '*a' is important to preserve line breaks
io.close( fh_note )
end
end

function saveNote()
local title_path = system.pathForFile( "title.txt", system.DocumentsDirectory )
local note_path = system.pathForFile( "note.txt", system.DocumentsDirectory )
local fh_title = io.open( title_path, "w+" )
local fh_note = io.open( note_path, "w+" )

-- load the title
if fh_title then
fh_title:write( titleField.text )
io.close( fh_title )
end

-- load the note
if fh_note then
fh_note:write( noteText.text )
io.close( fh_note )
end
end

loadSavedNote() -- on app start, load previously saved note



이 화면이 완성된 화면입니다.

아래 파일을 다운 받으시면 Full Souce Code와 이미지 파일 등이 있습니다. 참고하세요.

파일 받으시면서 추천버튼도 꾹 부탁드려요.
질문 있으시면 언제든지 댓글에 남겨 주세요.

반응형

system.openURL() 로 전화 걸기

2012. 2. 3. 07:17 | Posted by 솔웅


반응형
안녕하세요?

제가 지금까지 잘 못 알고 있었던 것 같네요.

얼마전에(작년 12월 24일) 코로나에서는 native.showPopup() API를 새로 선 보였습니다.
이 API로 메일과 SMS를 보낼 수 있죠?
아래 제목의 글을 보시면 자세히 알 수 있습니다.
CoronaSDK 2011.715 버전 Email, SMS 기능 추가

이 API에서는 전화걸기 기능이 아직까지 지원되지 않고 있거든요.
그래서 코로나에서는 전화걸기 기능이 안되나? 했거든요.
그런데 예전부터 있었던 system.openURL()을 이용하면 코로나 앱에서 전화걸기 기능이 지원 되고 있습니다.

이 system.openURL()에 대해서 정리 해 놓고 가야겠습니다.

system.openURL()은 브라우저에서 웹페이지를 오픈한다던가 이메일을 생성한다거나 전화를 걸때 사용할 수 있습니다.

신택스는 아래와 같습니다.
system.openURL(url)

예) system.openURL("http://coronasdk.tistory.com")

파라미터로는 url을 사용하는데요.
아래 3개의 url이 올 수있습니다.

 * Email address: "mailto:nobody@mycompany.com"
    : 이메일 주소에는 물론 제목과 내용도 넣을 수 있습니다.
      예) "mailto:nobody@mycompany.com?subject=Hi%20there&body=I%20just%20wanted%20to%20say%2C%20Hi!"
-- %20은 한칸띄기입니다. url에서 사용되는 기호들은 여기를 참조하세요.
  * Phone number: "tel:415-867-5309"
  * Web link: "http://coronasdk.tistory.com"

Return값으로는 아무것도 받지를 않습니다.
그래서 제대로 동작이 수행 됐는지 안 됐는지를 알 수는 없겠네요.
(native.shouPopup()에서는 boolean으로 result값을 받습니다.)

CoronaSDK의 David라는 친구하고 이메일 주고 받다가 알게 됐습니다.
CoronaSDK DOC를 전부 정리하고 넘어간 줄 알았는데 이렇게 중요한걸 빠뜨렸었군요.

혹시 잘못된 정보로 혼란스러우셨던 분들께는 죄송합니다.

그럼...

반응형