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

최근에 받은 트랙백

글 보관함

Pinch Zoom Rotate 구현하기 - 4/11 -

2013. 1. 31. 12:59 | Posted by 솔웅


Posted on . Written by



Faking Multitouch Input


파란 사각형 객체는 multiple touch input들의 recipient(받는사람) 이 될 겁니다. 이렇게 하기 위해서 우선 그 touch listener 함수를 수정해야 합니다. 우선 moved와 eneded 그리고 cancelled phases에 대해 print() 구문을 몇개 넣을 겁니다. 아래 코드는 작은 파란 사각형의 :touch() listener function을 수정한 결과 입니다.


sample4.lua

function rect:touch(e)
 
local target = e.target -- get the object which received the touch event
-- handle began phase of the touch event life cycle...
if (e.phase == "began") then
 
print( e.phase, e.x, e.y ) -- create a tracking dot
local dot = newTrackDot(e) -- we handled the began phase
return true
 
elseif (e.parent == rect) then
 
if (e.phase == "moved") then
print( e.phase, e.x, e.y )
else -- ‘ended’ and ‘cancelled’ phases
print( e.phase, e.x, e.y )
end
return true
 
end
return false -- if target is not responsible for this touch event return false
end






가장 많이 바뀐 부분은 moved,ended, cancelled phases 에 추가 된 건데요. 여기서 tracking dot들이 파란 사각형의 :touch() function을 call 하도록 하는 겁니다. event parameter 로 pass 하고 하얀 원의 touch 함수에의해 receive 되게 되죠.

elseif 구문도 중요한데요. tracking dot들이 사각형에 event parameter를 pass 한다면 e.target은 사각형이 아니라 그 dot에 대한 reference 가 될 겁니다. 이 reference 를 .parent 프로퍼티안의 사각형으로 저장할 겁니다. 이렇게 함으로서 rect:touch() function은 해당 터치 이벤트가 맞는지 안 맞는지 체크할 수 있게 됩니다. 물론 아직 원의 touch 함수가 사각형의 :touch()를 call 하도록 바꾸지는 않았습니다. 이 작업을 하기 전에 각 원이 사각형 객체에 대한 reference를 가지고 있어서 rect:touch() function를 call 하고 evnet parameter를 pass 할 수 있는지 여부를 확인해야 합니다.

아래 newTrackDot() function의 시작부분 입니다. 이 함수는 이벤트 파라미터의 원래 .target 프로퍼티에 대한 local copy 를 만들어야 합니다.


-- creates an object to be moved
function newTrackDot(e)
 
local circle = display.newCircle( e.x, e.y, 50 ) -- create a user interface object
circle.alpha = 0.5 -- make it less imposing
local rect = e.target -- keep reference to the rectangle
function circle:touch(e) -- standard multi-touch event listener
-- ...


원래의 began event phase를 receive 한 객체를 계속 reference 하는 것은 tracking dot을 multitouch event에서 해당 reference로 영향을 줄 수 있도록 합니다. 이제 began phase event parameter를 "rect"로 보내기 위한 tracking dot이 필요 없게 됐습니다. 왜냐하면 이미 이벤트를 receive 했기 때문이죠. 이제 우리가 해야할 작업은 tracking dot의 :touch() function 안의 rect:touch(e)를 call 하는 겁니다. 그렇게 하면 다른 phase들이 그 rect 객체로 보내질 수 있게 되는 것이죠.

main.lua

sample4.lua

-- standard multi-touch event listener
function circle:touch(e)
 
local target = circle -- get the object which received the touch event
circle.alpha = 0.5 -- make it less imposing
e.parent = rect -- store the parent object in the event
-- handle each phase of the touch event life cycle...
if (e.phase == "began") then
display.getCurrentStage():setFocus(target, e.id) -- set touch focus on this object
target.hasFocus = true -- remember that this object has the focus
return true -- indicate the event was handled
elseif (target.hasFocus) then -- this object is handling touches
if (e.phase == "moved") then -- move the display object with the touch
target.x, target.y = e.x, e.y
else -- "ended" and "cancelled" phases
display.getCurrentStage():setFocus(target, nil) -- remove touch focus
target.hasFocus = false -- this object no longer has the focus
end
 
rect:touch(e) -- send the event parameter to the rect object
return true -- indicate that we handled the touch and not to propagate it
end
 
return false -- if target is not responsible for this touch event return false
end


아주 간단하죠. 이제 우리는 사각형을 만들었습니다. 그 사각형은 touch 가 될 때마다 tracking dot을 생성하게 됩니다. 각 dot들은 또한 그들의 touch 정보를 그 사각형에 보내게 됩니다. 이 때 그 dot들의 original touch handler 함수를 사용하게 되는 거죠. 이 사각형은 그것이 맞는  target인지 알고 있습니다.

이 trick 을 사용함으로서 얼티터치 정보를 사용할 수 있게 됐습니다.





반응형

Comment

Pinch Zoom Rotate 구현하기 - 3/11 -

2013. 1. 29. 04:58 | Posted by 솔웅


Posted on . Written by



The Multitouch Problem


위 코드의 장점은 multiple touche들을 쉽게 구분할 수 있다는 겁니다. 그래서 각 object들은 자신들에 할당된 touch를 잃지 않고 계속 영향을 받을 수 있는거죠. 아주 큰 장점이지만 작은 문제점도 있습니다.

"
아주 큰 장점은 setFocus 로 유저가 어떤 object를 touch 헀으면 그 object 내에서 해당 touch는 계속 유효하게 되는 겁니다. 그 object 밖으로 나가버리면 더이상 유효하지 않게 되는거죠.

작은 문제점은 다른 touch 이벤트를 receive 하게 되면 이 setFocus 는 이 display object를 stop 시킨다는 겁니다.


만약 아직 setFocus를 call 하지 않았다면 hasFocus 를 사용하는 것이 해당 object 가 그 object에서 시작하지 않은 이벤트들을 무시하도록 하는 편리한 방법입니다. 이 방법은 자주 필요한데요. 왜냐하면 유저들은 그 객체가 아니라 배경화면이나 그 객체 밖에서 시작하는 swiping 이벤트를 자주 발생시키거든요. 그 swiping 이벤트는 이 객체를 지나가게 되죠. 그래서 이 객체에서 시작되지 않은 이벤트를 무시하도록 하려고 하는 겁니다. 그리고 그 다음 드는 의문은 코로나가 어떻게 여러 객체들이 multiple touch들을 받아서 작동하도록 만드는가 입니다. 그 방법은 바로 began phase안에 tracking object를 생성하는 것입니다.


The Concept

이전에 다뤘던 코드에 약간의 변화를 주겠습니다. 우리는 began phase안에서 여러 객체들을 생성하는 하나의 object를 생성할 겁니다. 이 객체들은 각각의 touch들을 track 할 겁니다. 그리고 그 touch 가 끝나면 해당 객체를 없애도록 코딩할 겁니다. 이를 구현하기 위해 touch event의 began phse를 listen 하는 함수를 하나 만들겁니다. 그리고 moved 를 listen 하는 함수도 하나 만들거구요. 이 두개의 함수들은 target listening object와 tracking dot object들에 add 될 겁니다.


Spawning Tracking Dots




첫번째로 처음에 began phase를 handle 하게 될 객체를 생성해야 합니다. began phase에서 이 객체는 tracking dot을 생성하기 위해 function을 call 할 겁니다.



sample3.lua


system.activate("multitouch") -- turn on multitouch

 
-- create object to listen for new touches
local rect = display.newRect( 200, 200, 200, 100 )
rect:setFillColor( 0, 0, 255 )
 
-- standard multi-touch event listener
function rect:touch(e)
 
local target = e.target -- get the object which received the touch event
-- handle began phase of the touch event life cycle...
if (e.phase == "began") then
 
local dot = newTrackDot(e) -- create a tracking dot
return true -- we handled the began phase
end
 
return false -- if target is not responsible for this touch event return false
end
 
rect:addEventListener("touch") -- listen for touches starting on the touch object

간단해서 딱 보면 코드를 이해하실 수 있을 겁니다. 어떤 touch 이벤트가 일어나던지 그 began phase를 listen 할 display 객체를 생성합니다. 이 객체가 touch 이벤트의 began phase를 receive하면 새로운 display object를 생성할 함수를 call 하게 됩니다. 이 새 object는 그 객체를 생성시킨 touch를 track 하게 될 겁니다. 그 작업은 setFocus 를 call 함으로서 가능해 지죠. hasFocus 값을 세팅하지 않은 점을 잘 보세요. 왜냐하면 이 multitouch 객체들은 began phase에서만 처리되고 있거든요.



다음으로 tracking dot을 생성해야 합니다. 이 코드는 이전의 multitouch function과 거의 유사합니다.


-- creates an object to be moved
local function newTrackDot(e)
 
local circle = display.newCircle( e.x, e.y, 50 ) -- create a user interface object
circle.alpha = 0.5 -- make it less imposing
-- standard multi-touch event listener
function circle:touch(e)
 
local target = circle -- get the object which received the touch event
-- handle each phase of the touch event life cycle...
if (e.phase == "began") then
 
display.getCurrentStage():setFocus(target, e.id) -- set touch focus on this object
target.hasFocus = true -- remember that this object has the focus
return true -- indicate the event was handled
 
elseif (target.hasFocus) then -- this object is handling touches
if (e.phase == "moved") then -- move the display object with the touch
target.x, target.y = e.x, e.y
else -- "ended" and "cancelled" phases
display.getCurrentStage():setFocus(target, nil) -- remove touch focus
target.hasFocus = false -- this object no longer has the focus
end
return true -- indicate that we handled the touch and not to propagate it
end
return false -- if target is not responsible for this touch event return false
end
circle:addEventListener("touch") -- listen for touches starting on the touch layer
circle:touch(e) -- pass the began phase to the tracking dot
return circle -- return the object for use
end




이 함수에서는 단 두가지 변화만 주었습니다.

  • circle:touch(e)를 call 했습니다. 왜냐하면 이 circle 이 바로 그 생성된 객체이기 때문이죠. 그리고 touch 이벤트의 began phase를 실제로recieve 하지 않은 객체이구요. 이렇게 call 함으로서 이 circle 객체가 그 touch 이벤트의 control 을 받도록 합니다.
  • :touch() function의 시작부분에 그 circletarget 으로 사용하기 위해 수정했습니다. 왜냐하면 그 e.target property는 실제 touch began이 시작한 그 "rect" 객체이기 때문입니다.


이 코드가 실행되면 작은 파란 사각형을 볼 수 있습니다. 이 사각형은 여러개의 하얀 원들을 생성해 낼 수 있습니다. 각각의 원은 가각의 touch 에 의해 움직입니다. 이 매커니즘은 모든 touch 정보를 파란 사각형에 직접 사용하면서 실제로는 multitouch input 이 일어나는 것처럼 구현할 수 있는 방법입니다.





반응형

Comment

Pinch Zoom Rotate 구현하기 - 2/11 -

2013. 1. 28. 13:12 | Posted by 솔웅


Multiple Touches


이 함수를 multiple display object들에 의해 사용되도록 하는 방법은 그렇게 어렵지 않습니다. setFocus 로 catch 해서 각 display object별로 한개의 touch 에 대해 listen 하도록 할 수 있습니다. 즉 하나의 touch 가 하나의 object 에 할당 되면 다른 touch 들은 그 object 에서는 무시되는 거죠. 이렇게 각 object 별로 각 touch 들을 할당해 놓으면 멀티터치 기능이 구현 가능합니다.



multitouch 를 구현하기 위해 이전 글에서 만들었던 코드를 수정할 겁니다.


sample2.lua



system.activate("multitouch") -- turn on multitouch

 

-- creates an object to be moved

local function newDragObj( x, y )

local circle = display.newCircle( x, y, 50 ) -- create a user interface object

circle.alpha = 0.5 -- make it less imposing

-- standard multitouch event listener

function circle:touch(e)

local target = e.target -- get the object which received the touch event

-- handle each phase of the touch event life cycle...

if (e.phase == "began") then

display.getCurrentStage():setFocus(target, e.id) -- set touch focus on this object

target.hasFocus = true -- remember that this object has the focus

return true -- indicate the event was handled

elseif (target.hasFocus) then -- this object is handling touches

if (e.phase == "moved") then

target.x, target.y = e.x, e.y -- move the display object with the touch

else -- "ended" and "cancelled" phases

display.getCurrentStage():setFocus(target, nil) -- remove touch focus

target.hasFocus = false -- this object no longer has the focus

end

return true -- indicate that we handled the touch and not to propagate it

end

return false -- if target is not responsible for this touch event return false

end

circle:addEventListener("touch") -- listen for touches starting on the touch layer

return circle -- return the object for use

end

 

local group = display.newGroup() -- create layer for the draggable objects

 

-- create 5 draggable objects

for i=1, 5 do

local circle = newDragObj( 100, i*100 )

group:insert( circle ) -- add it to the control layer

end




이 코드가 이전 코드와 다른점들을 살펴 보죠.

- multitouch 를 activate 했습니다.
- display object 생성을 wrap 했습니다. 그래서 display object 가 반복적으로 call 될 수 있게 됐습니다.
- setFocus 가 특정 touch ID 를 받아서 유저가 화면에 하는 여러 touch 들을 구분할 수 있도록 했습니다.
- When ending the touch, setFocus accepts nil to release the object’s touch input.
- touch가 끝나면 setFocus 는 nil 을 받아서 해당 object가 그 touch input 받는 일을 release 시켜 줍니다.


위 의 코드를 실행하면 5개의 큰 원을 생성하게 됩니다. 그 각 원들은 따로따로 움직일수가 있죠. setFocus 가 display object에 특정 Touch ID를 연결시켜주기 때문에 그 object는 다른 touch 들은 무시하게 됩니다. 이 로직으로 각 원들은 각각 다른 touch들이 할당 되서 multitouch로 따로따로 움직일 수 있게 되는 거죠.




반응형

Comment

Pinch Zoom Rotate 구현하기 - 1/11 -

2013. 1. 25. 13:47 | Posted by 솔웅


이번주의 Corona Tutorial은 무척 깁니다.

한번에 다 포스팅 하기는 어렵구요... 그렇게 하다가 작성해 놓은 글 날렸어요... ;;


예제가 총 11개 나오던데 이 예제별로 포스팅을 하려고 합니다.

이번에는 직접 실습도 해 가면서 저 나름대로 덧 붙일게 있으면 덧 붙여서 정리하려구요.



Posted on . Written by



오늘의 guest tutorial 은 Matt Webster, a.k.a. “HoraceBury.”  가 무료로 정보를 공유합니다. Matt 은 central London 에서 Development Manager 로 일하고 있습니다. 그는 닷넷과 자바 웹 개발분야에 15년의 경력을 가지고 있습니다. 그리고 게임이나 physics-based app 을 개발하는데는 Corona 를 사용하고 있습니다. 그는 Corona Labs Ambassador이기도 한데요 London meet-ups 를 두번이나 개최했습니다. 그리고 2013년도에는 더 많은meet-up을 가질 계획입니다. Matt 의 첫번째 Corona game 은  Tiltopolis 이었습니다. 고전인 Columns 와 Tetris를 합친 게임이죠.



Preface

우선 project files을 다운 받으세요.그러면 아래 예제들을 쉽게 테스트해 보실 수 있습니다. 각각의 samplex 모듈들은 기능을 갖고 있는 mini-project 입니다. 그리고 main.lua 에서 한번에 하나씩 실행시켜 보실 수 있습니다. 필요한 구문만 코멘트를 지워서 사용하세요. 마지막 모듈은 sample11.lua 는 전체 pinch-zoom-rotate module 입니다. 이 모듈은 여러분이 앱을 개발하실 때 실제 활용하시면 아주 유용할 겁니다.



Introduction



대부분의 어플리케이션에서는 하나의 터치 포인트일 경우 이를 제대로 표현할 수 있습니다. 아주 다양한 앱들이 저마다 아주 많은 기능들을 가지고 있지만 한번에 한개의 input 만 사용할 수 있죠. 한번에 한개의 버튼을 누를 수 있고 한번에 한개의 swipe 이벤트를 다룰 수 있고 등등이요.



앵그리버드를 예로 들어보죠. 이 앱에서는 각각의 tap, drag 그리고 swipe 들이 단지 한 손가락으로 이루어 지도록 돼 있습니다. 메뉴를 살펴보는 것, settings 를 여는 것, 새를 날려버리는 것 모두 한개의 손가락으로 처리하죠. 한 손가락으로만 하게 되면 일단 간단하죠. 그리고 직관적이고 또 집중할 수 있도록 도와 줍니다. 그런데 pinch zoom 같이 두 손가락 이상으로 해야 되는 경우도 있습니다.




이 경우 규칙을 어떻게 적용해야 할까요? 한손가락을 사용할 때는 터치된 object 에 action 을 구현하고 두 손가락이 사용됐을 때는 parent display group 의 top-level 에 어떤 action을 구현해야 겠죠.


이 튜토리얼의 목적은 이 multitouch 와 관련해서 최대한 간단하게 하려면 어떻게 처리해야 되는지에 대해 여러분에게 알려드리려는 겁니다. 그리고 pinch zoom 에 대해서도 좀 더 자세하게 다룰 겁니다.




Touch Basics




이 튜토리얼을 보시는 분들은 대부분 Corona touch model 을 사용해 본 경험이 있으 실 겁니다. 그러니 그와 관련해서는 핵심적인 내용 몇가지만 언급하겠습니다.



    addEventListener() 는 특정 display 객체에 대해 사용자의 터치가 일어나는 지를 listen 하기 위해 사용됩니다.
    터치 이벤트에는 두 종류가 있습니다 : touch and tap.
    터치 이벤트에는 다음과 같은 phases 가 있습니다 : began, moved and ended..
    display object 에 터치와 tap 이벤트가 동시에 일어날 경우 tap event 보다 touch event phases 가 먼저 fire 됩니다.
    Returning true from an event function stops Corona from passing that event to any display objects beneath the object.
    multitouch를 위해 system.activate(“multitouch”) 를 사용함니다.
    터치 이벤트가 일단 시작되면 다른 touch phases들은display.getCurrentStage():setFocus().를 call 함으로서 같은 listener 에 연결 되게 됩니다.
    setFocus 는 한 object와 한 evnet 별로 call 될 수 있습니다.
    display object에 dispatchEvent()를 call 하면 별도의 원하는 event들을 만들어 낼 수 있습니다.
    dispatchEvent로 fire 된 이벤트들은 다른 display hierarchy 에 전파되지 않습니다.




The Tap Problem



위에 언급한대로 터치 이벤트에는 여러 phases들이 있습니다. 각각 디바이스에 대한 유저의 interaction 상황을 나타냅니다. 화면에 손가락을 대고, 움직이고 그리고 떼고 하는 상황들을요.


일반적인 tap 이벤트는 주어진 시간안에 — iOS employs about 350 milliseconds — 위에 언급한 이벤트 phases들을 fire 합니다. 그리고 시작과 끝 지점은 10 픽셀이하의 거리이구요.
이 시간과 거리별로 계속 해당 phases가 발생 되는 겁니다.


즉 터치이벤트와 tap 이벤트를 동시에 listening 한다면 터치 리스너 함수 내에서 이미 곧 tap 리스너 함수가 call 될 거라는 것을 알 수 있을 겁니다. 그리고 이미 tap 이벤트를 감지했다면 더는 tap 리스너를 attach 할 필요가 없습니다. 이 튜토리얼에서는 tap 이벤트는 코드를 복잡하게 만들 뿐이라서 코딩해 넣지 않을 겁니다.



Single Touch


To demonstrate the typical touch event, let’s create a display object with a standard touch listener and use it to move the display object around.
일반적인 터치 이벤트를 구현하기 위해 standard touch listener 와 display object를 생성하겠습니다. 그리고 이 display object를 움직일 수 있도록 만들겠습니다.


sample1.lua



local circle = display.newCircle( 0, 0, 50 )  -- create a user interface object
circle.alpha = 0.5  -- make it less imposing

-- standard single-touch event listener
function circle:touch(e)

   local target = e.target  -- get the object which received the touch event
  
   -- handle each phase of the touch event life cycle...
   if (e.phase == "began") then
      
      display.getCurrentStage():setFocus(target)  -- set touch focus on this object
      target.hasFocus = true  -- remember that this object has the focus
      return true  -- indicate the event was handled
     
   elseif (target.hasFocus) then  -- this object is handling touches

      if (e.phase == "moved") then  -- move the display object with the touch
         target.x, target.y = e.x, e.y
      else  -- "ended" and "cancelled" phases
         display.getCurrentStage():setFocus(nil)  -- remove touch focus
         target.hasFocus = false  -- this object no longer has the focus
      end
      return true  -- indicate that we handled the touch and not to propagate it
   end
  
   return false  -- if target is not responsible for this touch event, return false
end

circle:addEventListener("touch")  -- listen for touches starting on the touch layer





위 함수에서는 multitouch 가 아닌 일반적인 touch event를 처리합니다.
이 예제로는 아래와 같은 일을 할 겁니다.



    객체에 터치가 시작됨
    터치 후 움직임으로서 객체도 움직이게 됨
    touch 가 off 가 되면 객체는 영향을 받지 않음
    해당 터치가 한 객체를 움직이는 동안은 다른 객체는 터치의 영향을 받지 않는다.
    display object는 자신의 :touch(e) function을 갖는다. (global function이 아니라)



해당 객체에서 터치가 일어났을 때에만 영향을 받고 터치가 객체 바깥에서 일어나면 터치의 영향을 받지 않습니다. 이 기능은 hasFocus 를 세팅해서 인데요. 해당 객체는 began이 일어난 이후에 touch phase들을 accept 하게 됩니다. 또한 한번 터치가 일어나면 그 터치는 끝날때까지 해당 객체에 영향을 미치게 됩니다. 바로 setFocus 가 코로나에게 이후의 터치 진행 상황을 해당 객체에 적용하라고 일러 주는 것이거든요.


반응형

Comment

코로나에서 time, date 다루기

2013. 1. 17. 13:49 | Posted by 솔웅


Posted on . Written by

“Time keeps on slippin’, slippin’, slippin’, into the future.” —Steve Miller Band / Fly Like An Eagle


어떤 프로그래밍 언어로 time 관련해서 작업을 하는 것은 프로그래머에게 혼란을 주게 됩니다. 이번주의 튜토리얼은 time 과 date 에 대해 다룹니다. 그리고 date 계산, time zone 문제 그리고 date formatting 관련해서 예제를 보여드리겠습니다.


코드를 보기전에 먼저 이해해 둬야할 몇가지 중요한 개념들이 있습니다. 컴퓨터에게는 time 의 기본 유닛은 1초 입니다. 이 값으로 우리는 date 도 계산할 수 있고 fraction 들도 다루고 심지어 time 의 더 작은 순간들도 계산해 냅니다. 사람에게 1초는 짧은 시간이지만 컴퓨터에게는 아주 긴 시간입니다. 사람들도 긴 시간의 경우 세분화해서 사용하듯이 컴퓨터도 마찬가지 입니다. 어떤 함수는 1/1000 초(milliseconds ) 단위로 작업을 하고 이것을 uSec 나 uSeconds으로 쓰기도 합니다. 더 좋은 컴퓨터는 이것보다 더 짧은 시간을 다루기도 합니다. 코로나에서는 3가지 종류의 time measurement 를 사용합니다.




Microseconds and Milliseconds


Microseconds 는 아래와 같이 call 하시면 됩니다 :


system.getTimer()


이 call은 여러분의 app 이 시작한 이후의 시간을 return 해 줍니다. 그 값은 milliseconds (1/1000ths) 단위 입니다. 대부분의 device들에서는 아래와 같은 값을 return 할 겁니다.


1839.3949


이 time은 microsecond resolution 으로 측정되는데요 여러분에게 보일 때는 milliseconds 로 변환해서 보여드리는 겁니다. 그리고 소숫점 아래는 microseconds 를 나타내죠. 이 마이크로세컨드 단위가 필요한 경우가 있을지 궁금해 하실수도 있습니다. 아마 실제로는 필요가 없겠죠. 아마 GPS 데이터를 다루거나 Einstein’s Pedometer (앱 이름인데요. 여러분이 빠르게 움직이면 얼마나 젊어지는지를 보여주는 앱입니다.) 작업을 하게 되면 이 microsecond resolution 이 필요할 수도 있습니다. 하지만 우리의 앱에서는 1초당 30~60 프레임을 사용할 거니까 1/30 에서 1/60 초 단위로 작업을 하게 될 겁니다. 그러니까 1/1000 초 보다 더 정교한 시간계산은 이 앱에서는 그다지 중요하지는 않습니다.


milliseconds와 관련해서 사용하는 함수들은 아래와 같습니다.


이 함수들은 millisecond 단위를 accept 합니다. 그러니까 이 함수에 값을 5000을 주면 그 시간은 5초가 되는 겁니다. 1/2 초는 500이 되겠죠. 2와 1/2 초는 2500이 됩니다.


timer.performWithDelay(500, someFunction)  -- a half second delay


초를 원하는 값으로 바꾸시려면 1000을 곱하면 되겠죠.


Dealing With Longer Periods of Time


위에서 언급했듯이 컴퓨터에서 time 의 기본적인 유닛은 1초입니다. iOS와 안드로이드 (Mac OS X 도 마찬가지로) 모두 Unix 에서 파생된 운영체제에 근거해 있습니다. 그리고 그런 운영체제에서 표준 "time" 함수는 1970년 1월 1일 자정 이후부터 경과된 시간을 초단위로 return 해 줍니다.  마이크로소프트는 좀 다른데요, 하지만 이미 많은 앱들이 C언어를 사용해서 만들어졌고 이 언어는 Unix 에 기반해 있거든요. 그리고 그 언어의 라이브러리도 이 time reference pointEpoch를 사용하구요.


마이너스일 경우에는 1970년 이전이 되겠죠. positive times 일 경우에는 1970년 이후가 되겠구요. 코로나에서는 os.time() API call을 사용해서 1970년 1월 1일 이후 지금까지의 number of seconds 를 얻을 수 있습니다.


print( os.time() )  -- outputs something like: 1358015442


사실 저 숫자만 나온다면 알아볼수가 없으니까 별로 의미가 없겠죠. 이것을 date math로 계산해서 실제 생활에서 쓰이는 날짜와 시간으로 바꿔야 알아볼 수가 있을 겁니다. 그런데 게임을 한번 생각해 보세요. turn-based game 이 있는데 유저가 너무 오랫동안 게임을 하고 있어서 어떤 "nudge" 가 필요한 경우 아래와 같이 마지막 move의 시간을 저장할 필요가 있을 겁니다.


player[1].lastMove = os.time()

그리고 나서 지난 한시간 동안 어떤 move 가 있는지 체크하고 싶으면 아래와 같이 하시면 됩니다.


local now = os.time()
if ( now > player[1].lastMove + 3600 ) then
  -- nudge the player
end


3600은 어디서 왔을까요? 60초 곱하기 60분을 하면 그 값입니다. 즉 1시간이죠. 그러니까 아래와 같이 하셔도 됩니다.


local now = os.time()
if ( now > player[1].lastMove + (60 * 60) ) then
  -- nudge the player
end

Lua 의 pre-processor 는 (60*60) 를 3600으로 convert 할겁니다. 그러면 여러분이 따로 미리 계산할 필요는 없겠죠. 아래와 같이 미리 날짜, 시간, 분을 상수로 정의해서 사용하셔도 됩니다.


DAY = 24 * 60 * 60
HOUR = 60 * 60
MINUTE = 60

local now = os.time()
if ( now > player[1].lastMove + HOUR ) then
  -- nudge the player
end


이렇게 초단위로 다룬 값으로 date math 를 사용하는 것은 아주 쉽습니다.


  • Is your time older than a week? Use ( 7 * 24 * 60 * 60 ).
  • Set an alarm in 30 days? Use ( 30 * 24 * 60 * 60 ).


또한 한달에 몇일이 있는지, 윤년인지 아닌지 등등도 계산할 것을 걱정하지 않으셔도 욉니다.

Working With Dates


Dates(날짜)와 관련해서는 조금 신경을 쓰셔야 됩니다. 왜냐하면 날짜를 표시할 때는 strings 를 사용하니까요.


  • April 1, 2010 4:53pm
  • April 1, 2010 4:53 P.M.
  • 4/1/10 16:53 MT
  • Sun Jan 13 15:17:32 EST 2013
  • 13-JAN-13 15:17


개발자로서 날짜를 component values로 parse 할 필요가 있습니다. date/time string의 각 파트별로 get 하셔야 합니다. (month, day, year, hour, minute, seconds and time zone). 만약에 month를 string 으로 가지고 있다면 이 string 을 number 로 그리고 timestamp로 convert 할 lookup table 을 사용하실 수 있습니다.


local monthNumbers = {}
monthNumbers["January"] = 1
monthNumbers["February"] = 2
monthNumbers["March"] = 3
--etc...
monthAsInteger = monthNumbers[monthString]


가장 일반적인 date format 중의 하나는 ISO-8601 time format입니다. 그 string 값은 대충 아래와 같습니다.


2012-01-12T12:04:35.03-0400


이 date 를 parse 하시려면 이 여러 bit들을  패턴을 사용한 각각의 값으로 break down 하기 위해 string:match string function 을 사용할 겁니다.


local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin =
  dateString:match( pattern )

Lua 를 처음 접하시는 분들 에게는 위 코드가 많이 복잡하게 보일 겁니다. 신택스가 그렇게 간단하지는 않죠? 이해하기 쉽도록 break down 해 보죠.


2012-01-12   T   12:04:35.03   -0400


T 이전 부분은 date 입니다. 알아보기 쉽고 또 나누기 쉽죠.  연,월,일이 숫자로 있고 각각 hyphen 으로 나눠져 있습니다. T는 시간 부분이 시작한다는 의미입니다. 그 다음에는 시,분,초를 가리키는 숫자들이 나오고 각각 colon들로 나눠져 있죠. 이 중 초단위는 floating point number 라는 것을 기억해 두세요. 그 다음부분에는 옵션으로 time zone이 나옵니다. 이 timezone 과 관련해서는 조금 더 얘기를 나눌 겁니다.


string:match() function은 어떤 패턴을 보여주는데요. string 의 span 에 대해 “wildcard” type searching 을 사용하는 그런 패턴이죠. 위의 경우에는 %d+ pattern을 상요해서 숫자를 찾고 있습니다. 이렇게 함으로서 Lua 에게 0에서 9까지의 숫자열을 찾도록 만들죠. + 표시는 한개 이상의 숫자열을 찾는 다는 것을 알려 주는 겁니다. 예를 들어 2012 같은 숫자열을요.


이 값들을 알아볼 수 있는 변수에 저장하기 위해 괄호 () 안에 어떤 pattern set들을 넣게 됩니다. 그러면 Lua에게 우리가 retrieve 하기 원하는 date/time 의 각각의 단위들을 알려줄 수가 있습니다. date 부분은 hyphen 들이 있는데요. 이것은 각 컴포넌트를 분리하는 기호입니다.


(%d+)%-(%d+)%-(%d+) -- gets the year, month and day
%a -- handles the single letter T
(%d+)%:(%d+)%:([%d%.]+) -- gets the hour, minutes and seconds



마지막 부분을 보세요. 숫자하고 점(.) 이 있죠? 초가 floating point value 이기 때문에 이렇게 찾는 겁니다. string matching에서 점(.) 은 “magic character” 입니다. 이 점 앞에 % 를 넣으셔야 Lua 는 실제 점(.) 을 찾을 겁니다.


마지막으로 + 나 - 구분자 다음에 timezone 정보가 옵니다. 혹은 Z 나 UTC 를 사용하기도 하죠. string:match function 은 각각의 match 되는 패턴들에 대한 값들을 return 할 겁니다. 그리고 그 값들을 변수에 담게 되죠. ISO-8601 standard는 timezone time 을 +/-HHMM+/-HH:MM를 사용합니다. 만약 이 timezone 정보가 없으면 여러분이 속해 있는 local timezone 을 사용할 겁니다. 아래의 패턴은 좀 복잡해 보이긴 하는데요. timezone 정보를 처리하는 코드 입니다.


([Z%p])(%d%d)%:?(%d%d)


Z 나 punctuation character 인 + 나 - 다음에 %p가 옵니다. 그 다음에는 두자리 숫자가 와야 됩니다. 그 다음에 colon 은 옵션인데요. 두개의 숫자가 더 올 수도 있습니다. 이 패턴은 tzoffset, offsethour, offsetmin등을 return 합니다. 이것을 date math 에서 사용할 숫자로 convert 하기 위해 os.time() function (documentation) 을 사용합니다.


local timestamp = os.time(
  {year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )



이제 1970년 1월 1일 이후 몇초가 경과했는지에 대한 값을 갖게 됐습니다. 그리고 이것을 date math 로 이용할 수 있게 됐구요. 한가지 문제가 있는데요. 아직까지 timezone 부분에 대해 일을 마치지는 않았습니다. 다행히 그 작업은 어렵지 않은데요. 지금 이미 기본 시간을 seconds 로 갖고 있으니까 계산만 하면 되곘죠. Z는 time 이 Coordinated Universal Time (UTC)라는 것을 가리키는 겁니다. 만약 이것이 UTC 나 local time 이라면 따로 timezone 과 관련해서 작업할 것은 없습니다.


local offset = 0
if ( tzoffset ) then
  if ( tzoffset == "+" or tzoffset == "-" ) then  -- we have a timezone!
    offset = offsethour * 60 + offsetmin
    if ( tzoffset == "-" ) then
      offset = offset * -1
    end
    timestamp = timestamp + offset
  end
end


os.time()은 여러분이 있는 곳의 timezone 을 사용해서 값을 return 할 겁니다.


아래 코드는 여러분이 라이브러리로 사용할 수 있는 샘플 예제 입니다.


function makeTimeStamp(dateString)
  local pattern = "(%d+)%-(%d+)%-(%d+)%a(%d+)%:(%d+)%:([%d%.]+)([Z%p])(%d%d)%:?(%d%d)"
  local year, month, day, hour, minute, seconds, tzoffset, offsethour, offsetmin =
    dateString:match(pattern)
  local timestamp = os.time(
    {year=year, month=month, day=day, hour=hour, min=minute, sec=seconds} )
  local offset = 0
  if ( tzoffset ) then
    if ( tzoffset == "+" or tzoffset == "-" ) then  -- we have a timezone!
      offset = offsethour * 60 + offsetmin
      if ( tzoffset == "-" ) then
        offset = offset * -1
      end
      timestamp = timestamp + offset
    end
  end
  return timestamp
end


Final note:  여기서는 아직 Daylight Savings Time(써머타임)을 적용하지는 않은 상황입니다. Daylight Savings Time 은 이 튜토리얼에서 다루지 않습니다.


Converting a Timestamp to Something Readable


어떤 경우에는 이 time 을 유저가 알아볼 수 있는 date string으로 표현할 필요가 있을 겁니다. 이게 바로 os.date() function의 목적이죠. (documentation).


os.date() function 은 두개의 파라미터를 받습니다. 둘 다 옵션인데요. 이 옵션 없이 call 하면 현재 있는 지역의 timezone 에 맞는 date/time 을 return 할 겁니다. 아마 아래와 같은 형식일 겁니다.


Sat Jan 12 14:07:30 2013


아주 다양한 형식으로 표현할수 있는데요. 다양한 formatting parameter 들을 이용하시면 됩니다. 이 format parameter들은 Unix/C++ library function strftime에 근거합니다.


print( os.date("%A") )  -- prints out a string representing the weekday.
print( os.date("%A %l:%M%p") )  -- prints out "Saturday 2:30PM"

디폴트로 os.date()가 return 하는 값은 여러분의 지역이나 time zone 에 근거할 겁니다.  RSS feeds, Corona Cloud date 등에서 일반적으로 사용하는 위에서 사용한 날짜 형식을 생성하기 원하신다면 UTC 에 근거한 output 을 받아야 합니다. 이것은 format parameter 앞에 ! 를 붙이시면 됩니다.


print( os.date("%FT%X%z") )  -- outputs: 2013-01-12T15:30:09-0500

or if you wanted it in UTC:

print( os.date("!%FT%XZ") )  -- outputs: 2013-01-12T19:30:09Z



strftime에 있는 다양한 formatting parameter 들은 다양한 format 으로 month, day, year, weekday, am/pm를 얻을 수 있도록 합니다. 



Counting Down Time


흔히 하는 질문으로 "어떻게 countdown timer 를 만들수 있죠?" 가 있습니다.

가장 좋은 방법은 직접 한번 구현해 보는 걸겁니다. 만약에 60안에 한 레벨을 끝내기를 원한다면 60초 후에 뭔가 이벤트를 발생시키기 위해 timer.performWithDelay()를 사용하시면 됩니다. 보기 좋은 UI output 과 함께 좀 더 그럴듯한 방법을 원하신다면 enterFrame event 에 Runtime listener를 사용하실 수도 있습니다. 그렇게 하면 시간을 체크할 수 있습니다. 혹은 1초 timer 를 계속 반복할 수도 있겠죠. 그럼 필요할 때 쉽게 pause 시키고 또 resume 시킬 수 있을 겁니다.


-------- RUNTIME METHOD --------
local startTime = os.time()
local levelTime = 60
local displayTime = display.newText(levelTime, 0, 0, "Helvetica", 20)

local function checkTime(event)
  local now = os.time()
  displayTime.text = levelTime - (now - startTime)
  if ( now > startTime + levelTime ) then
    -- code to end the level
  end
end
Runtime:addEventListener("enterFrame", checkTime)
-------- REPEATING TIMER METHOD --------
local levelTime = 60
local displayTime = display.newText( levelTime, 0, 0, "Helvetica", 20 )

local function checkTime(event)
  levelTime = levelTime - 1
  displayTime.text = levelTime
  if ( levelTime <= 0 ) then
    -- code to end the level
  end
end
timer.performWithDelay( 1000, checkTime, levelTime )



In Summary


Corona에서 time 과 date를 다루는 것은 약간 복잡한 신택스를 다뤄야 합니다. 이 튜토리얼이 그것을 이해하는데 도움이 되셨으면 합니다. 위의 예제들을 활용해서 앱 개발시 이용하시면 훨씬 수월하실 겁니다.


반응형

Comment

Multi-Element Physics Body 다루기

2013. 1. 10. 12:51 | Posted by 솔웅


Posted on . Written by



이번주의 튜토리얼은 코로나 물리 엔진에 대한 겁니다. 특히 multi-element physics bodies 부분에 대해서 다룹니다.

첫번째로 multi-element body가 무엇인지 부터 알고 넘어가야 할 것 같습니다. multi-element body 는 두개 이상의 shapes 들이 모여서 구성된 physics body 입니다. raddoll 처럼 weld joint 같은 joint 로 연결된 physical 객체들의 집합으로 하나가 된 physical 객체를 말하는 것이 아닙니다. multi-element body 는 여러 shape 들로 구성되지만 전체가 하나로 통일되고 고정된 객체로 각각의 element들이 move 하거나 flex 하지 않습니다.



Why Multi-Element Bodies?


물리엔진을 많이 사용해 보신 분들은 다 아시는 얘기 일텐데요. 간략하게 더 설명하겠습니다. Box2D 에서는 모든 physical body 들은 반드시 오목한 angle들이 없는 최대 8개 면을 가진 다각형 모양으로 그려집니다.





볼록한 다각형은 괜찮습니다 하지만 볼록한 angle들로만 돼 있고 trace 될 수 없는 body 나 8개면 이하이면서 정확하게 trace 될 수 없는 body 라면 어떨까요? 이 룰을 따르지 않는다면 충돌시 일어나는 reaction 들은 어떻게 나올지 모르게 됩니다.


해결책은 multi-element body 입니다. 여러개의 볼록한 모양이 하나의 body 로 되서 trace 되는 모양이 기본적인 모양입니다. 여러분들은 간단한 shape들을 사용해서 multi-element bodies 들을 만들어야 합니다. 최소한의 shape 들을 사용해서요. 이 작업이 좀 어려울 것 같으면 3rd party tool인 PhysicsEditor가 이 작업을 쉽게 할 수 있도록 도와 드립니다.


Part I – Per-Element Collision Control


이미 이전에 multi-element bodies 작업을 해 보신적이 있으시면요, 아마 이 기능이 아주 유용하다는 것을 아실겁니다. 하지만 이 기능에도 약간의 제한사항들이 있는데요. 이 기능의 capabilities를 먼저 살펴보죠.


  1. 각 element 들은 유니크한 collision filters들을 가질 수 있습니다. 이 기능을 multi-element body의 일 부분이 다른 객체와 충돌하거나 할 때 어떤 reaction이 일어나도록 할 수 있습니다. 다른 부분은 충돌해도 아무 일이 일어나지 않구요. 
  2. 각각의 element들은 센서로 세팅할 수도 있습니다. 다른 객체들이 그 부분을 지나갈 때 어떤 일이 일어나도록 할 수 있죠. 충돌효과를 줄 수도 있지만 그냥 지나가도록 할 수도 있고 이 때 어떤 일이 일어나도록 할 수 있습니다.
  3. 충돌시 각 element는 사전에 physics addBody() function에서 지정된 고유의 숫자를 return 할 수 있습니다. 예를 들어 첫번째 element 는 1 두번째는 2 등으로 지정할 수 있죠.


이러한 unique capability들도 있지만 아래와 같은 제한사항들도 있습니다.


  1. 일단 collision가 filter element나 body에 정의되면 Runtime 동안은 바뀔수가 없습니다.
  2. 만약 element 가 센서로 정의돼었다면 Runtime 동안 각 element 별로 바꿀 수 없습니다. 다만 전체 body 가 sensor 나 non-sensor 로 바뀔 수 있을 뿐입니다.


Overcoming These Limitations


근데 너무 실망하지는 마세요. 오늘 이 튜토리얼에서 그 두가지 제한사항들을 어떻게 극복할지도 보여드릴테니까요. 우리는 아 작업을 physics contact를 사용해서 구현할 겁니다. 최근 있었던 튜토리얼에서 이 기능을 소개해 드린적이 있는데요. 여기로 가시면 그 글을 보실 수 있습니다.


physics contact를 사용해서 사전에 결정할 수 있습니다. pre-collision listener 를 사용해서 실제 충돌이 일어날 때 어떤 일이 일어날지를 결정할 수 있단 겁니다. 여러분 앱의 로직ㅇㅔ 근거해서 전체 collision을 void  하도록 할 수 있거든요. 이 튜토리얼에서는 이 기능을 multi-element bodies 에서 활용해 보겠습니다.



아래 “space nebula,” 를 사용할 건데요. 소설을 써 보면 star-fighter 는 우선 먼저 저 모서리에 있는 shield 를 부숴야 합니다. 그 다음에 가운에에 있는 대마왕을 없애야 해요. 이 시나리오를 구혀하기 위해서는 전통적인 방법으로는 아래와 같은 제한사항들이 있습니다. 



  1. 만약에 전통적인 방법인 joint를 사용해서 구현한다면 한쪽 모서리가 파괴 됐을 때 나머지 부분들의 joint 연결이 불안정해 지게 되는 문제점이 있습니다.
  2. object.isSensor를 사용하면 all or nothing 이 됩니다. 그러니까 파괴된 한 부분만 센서로 바꿀 수 없습니다.


이런 제약사항때문에 전통적인 joint 방법이 아닌 physics contact를 사용할 겁니다. per-element collision detection을 사용해서 이 destructible shield 이슈를 해결해 보겠습니다.




Assembling the Nebula


Let’s examine how to create a multi-element body in Corona. We’ll create a 9-element body to trace the nebula. In Corona, we simply do this:

일단 코로나에서 어떻게 multi-element body를 생성하는지 공부해 봅시다. 우리는 nebula 를 trace 하기 위해 9개의 element body 를 만들겁니다. 코로나에서는 이를 위해 아래와 같이 하면 됩니다.


  • nebula image 를 스크린에 display 시킨다.
  • nebula에 대한 shape 을 정의한다. top 을 시작점으로 해서 필요한 작업을 한다. outlying pod에 대해서는 8각형을 사용해야 합니다. 왜냐하면 multi-element body 에서는 radial shape 을 offset 할 수 없거든요. 정확하게 원이 아니니까요. 팔각형으로 충돌에 대한 자연스러운 반응을 이끌어 내기에도 문제가 없을 겁니다.
  • physical body 를 add 하고 element들의 순서에 따라 API 에 각 shape 들을 pass 합니다. 순서가 중요합니다. collision detection 에서 숫자를 return 할 거거든요.


local nebula = display.newImage( "nebula.png" )
nebula.x, nebula.y = display.contentWidth/2, display.contentHeight/2
local podT = {1,-89, 14,-83, 20,-70, 14,-57, 1,-51, -12,-57, -18,-70, -12,-83}
local beamTR = {19,-63, 63,-19, 59,-14, 14,-59}
local podR = {69,-20, 82,-14, 88,-1, 82,12, 69,18, 56,12, 50,-1, 56,-14}
local beamBR = {19,61, 14,56, 58,13, 62,17}
local podB = {1,49, 14,55, 20,68, 14,81, 1,87, -12,81, -18,68, -12,55}
local beamBL = {-18,63, -64,17, -59,13, -14,58}
local podL = {-70,-20, -57,-14, -51,-1, -57,12, -70,18, -83,12, -89,-1, -83,-14}
local beamTL = {-18,-65, -14,-61, -59,-15, -64,-20}

physics
.addBody( nebula, "dynamic",
{shape=podT},
{shape=beamTR},
{shape=podR},
{shape=beamBR},
{shape=podB},
{shape=beamBL},
{shape=podL},
{shape=beamTL},
{radius=24} --radial body used for the nucleus
)
local shieldStates = { true, true, true, true, true, true, true, true }


이제 맨 마지막에 이 8개의 shield 객체들을 관리하기 위해 간단한 shieldStates table을 셋업하셔야 합니다. 이 테이블은 나중에 어떤 특정 element 를 on/off 시킬때 사용할 겁니다. 그리고 shield element 가 inact인지 destroyed 돼 있는지를 track 할 때도 사용할 거구요. 이를 위해 8개의 boolean 값을 가진 간단한 non-indexed table 을 사용합니다.



The Basic Pre-Collision Listener



그 다음으로는 기본적인 pre-collision listener를 정의할 겁니다. 이전 튜토리얼에서 설명했듯이 물리적인 접촉을 utilize 하기 위해 pre-collision listener를 사용해야 합니다. 왜냐하면 코로나에게 충돌이 일어나기 직전에 그 충돌상황을 관리하도록 해야 하기 때문이죠.


local function nebulaCollide( self,event )
  print( event.selfElement )
end
nebula.preCollision = nebulaCollide ; nebula:addEventListener( "preCollision", nebula )


이 함수는 이주 기본적인 구조로 돼 있습니다. 이제 nebula 와 어떤 것이라도 충돌하게 되면 해당 element가 정의 돼 있는 순서에 따라 event.selfElement로 그 element의 corresponding integer가 return 될 겁니다. 위에 있는 pod를 첫번째 element로 정의했죠. 그러니까 그 element는 1을 return 할 겁니다. 오른쪽 beam 은 2를 반환하고 오른쪽 pod 는 3을 반환하고 뭐 이런식으로 진행될 겁니다.


Enhancing the Pre-Collision Listener


이제 nebula 의 어떤 element 가 충돌했는지 알 수 있게 됐습니다. 이제 shieldStates table 을 이용해서 충돌을 처리할 지 말아야 할지를 처리할 수 있도록 만들어야 합니다. 만약 그 shield element 가 게임을 하다가 destroyed 됐다면 코로나에게 해당 충돌은 void 처리하라고 지시할 수 있습니다. (event.contact)


local function nebulaCollide( self,event )

--query the position (and state) from "shieldStates" table
local isElementIntact = shieldStates[event.selfElement]

if ( isElementIntact == false ) then
event.contact.isEnabled = false --use physics contact to void collision!
end
end
 

shieldStates table를 관리하는 것은 아주 간단합니다. shield pod 을 destroy 처리하려면 간단하게 아래 처럼 하면 됩니다.



shieldStates[5] = false


Building on this concept, you can now manage your nebula shields and enact other creative methods, including:

이제 여러분은 nebula shield를 관리할 수 있게 됐습니다. 그리고 아래 내용들을 포함해서 여러 효과들을 구현하실 수 있습니다.

  1. shield pod 이 destroy 됐다면 옆에 있는 beam 도 destroy 시킴.
  2. 특정 시간이 지나면 shield pod 을 rebuild 하고 옆의 beam 들도 다시 rebuild 함
  3. shieldStates table setup 을 확장해서 각 pod 의 에너지 양을 관리함.


per-element detection 과 함께 physics contact를 사용하면 전통적인 방법이 갖는 한계를 해결할 수 있다는 걸 알게 되셨을 겁니다.




Part II – Multi-Element Bodies and Sensors



이제 multi-element bodies와 관련되서 많이들 잘 못 이해하고 계시는 sensor 부분에 대해 얘기해 보겠습니다.


Corona 의 physics 에 대해서 경험이 있으신 분들은 어떤 모양이나 타입(dynamic, kinematic, or static)에서든 physical body 가 sensor 로서 동작할 수 있다는 것을 알 수 있을 겁니다. sensor 는 부딪혀서 튀긴다던가 하는 physical sense 는 없습니다.


multi-element bodies와 관련해서 많이 오해하기 쉬운게 뭐냐하면 모든 element 들은 seosor로서 colision event를 return 합니다. body 가 전체 통으로 돼 있는 객체라면 그 전체가 충돌을 체크하는 포인트가 되죠. 여기서 헛갈릴 수 있는 것이 sensor 에서는 충돌시 여러개의 began phase event를 받을 수 있게 된다는 거죠. 혹은 센서 지역 밖에 있는데도 작은 부분이 걸쳐 있어서  ended phase를 받을 수도 있습니다.


사실 센서는 원래 그러기 위해서 만들어 졌습니다. 예를 들어서 트랙을 달리는 경주용 자동차의 앞 바퀴에만 센서를 달 필요가 있는 경우가 있을 겁니다. 조종석은 트랙이 있더라고 앞바퀴만 통과하면 어떤 결과를 일으켜야 되는 경우가 있죠. 또 다른 경우는 multi-element body 전체가 센서 지역의 안에 혹은 밖에 있는 것을 체크해야 될 때도 있습니다. “jumping fish” 를 보죠. 물고기는 완전히 물 안에 존재해야만 하죠? 이것도 센서로 정의할 수 있을 까요?



Counting the Collisions






이 jumping fish 시나리오는 fish의 body 에 있는 각각의 element들에 대해 어떤 충돌이 일어나는지를 counting 함으로서 해결할 수 있습니다. 이를 위해 값들이 들어갈 테이블을 만들고 그 이름을 elementStates라고 할 겁니다. began phase 에서 해당 count를 1씩 증가시키고 ended phase 에서는 1씩 감소 시킬 겁니다. 각 element들은 센서와 함께 collision event 를 return 할 겁니다. 그래서 만약 한 element에 4개의 센서들이 overlap 된다면 count는 4가 되겠죠. 만약 그 센서들 중 3개가 밖으로 나간다면 count 는 1로 줄겠죠. element count 가 0이 될 때는 전체가 센서 밖에 나가 있다는 것을 말합니다. 그러면 물고기가 완전히 물밖으로 나간 상태고 물고기가 점프했다고 볼 수 있겠죠.



또한 우리는 elementsIn를 만들어서 물고기의 전체 element들 중에 얼마나 많이  센서 범위 안이나 밖에 있는지를 count 할 겁니다. 이 값은 절대 5를 넘지 않을 겁니다. 왜냐하면 물고기는 5개의 element들로 구성될 거기 때문이죠.

마지막으로 inWater라는 boolean flag 를 정의할 겁니다. 이로 인해서 완전히 물 속에 있는지 물 밖에 있는지를 알 수 있게 되죠. 코딩을 편하게 하기 위해서 이 세개의 item들은 fish 의 properties 로 정의할 겁니다.

아래에 기본적인 예제 코드가 있습니다.



local
fish = display.newImage( "jumpfish.png" )

fish.x, fish.y = display.contentWidth/2, display.contentHeight/2-200
local tail = {-117,12, -123,-46, -68,-13}
local bodyBack = {-89,-26, -61,-39, -20,-46, 20,-49, 42,27, -12,28, -66,16, -94,0}
local bodyFront = {20,-49, 71,-43, 107,-32, 121,-20, 126,-10, 108,5, 78,19, 43,27}
local finBack = {-39,23, -11,29, -10,41, -32,50}
local finFront = {-9,51, -11,28, 41,27, 15,42}

physics.addBody( fish, "dynamic",
{shape=tail},
{shape=bodyBack},
{shape=bodyFront},
{shape=finBack},
{shape=finFront}
)
fish.elementStates = { 0,0,0,0,0 } --table of per-element collision counts
fish.elementsIn = 0
fish.inWater = false



보시다시피 물고기의 element들은 Part 1에서 다뤘던 nebula 와 비슷하게 정의했죠? 거기에다가 추가적으로 table elementStates와 properties elementsIn and inWater 를 생성했습니다.


Managing the Count


이 물고기는 standard collision listener 가 필요합니다. pre-collision listener 가 아니라요. 이번에는 physics contact 기능을 사용하지 않을 겁니다.


local function fishCollide( self,event )

if ( event.phase == "began" ) then
if ( self.elementStates[event.selfElement] == 0 ) then
self.elementsIn = self.elementsIn+1
end
self.elementStates[event.selfElement] = self.elementStates[event.selfElement]+1
elseif ( event.phase == "ended" ) then
self.elementStates[event.selfElement] = self.elementStates[event.selfElement]-1
if ( self.elementStates[event.selfElement] == 0 ) then
self.elementsIn = self.elementsIn-1
end
end
if ( self.elementsIn == 0 and self.inWater == true ) then
self.inWater = false
print("FISH ENTIRELY OUT OF THE WATER!")
elseif ( self.elementsIn == 5 and self.inWater == false ) then
self.inWater = true
print("FISH ENTIRELY IN THE WATER!")
end
end
fish.collision = fishCollide ; fish:addEventListener( "collision", fish )

이제 단계별로 볼까요

began phase:

  1.  첫번째로 총돌한 element의 count가 0인지 체크합니다. 만약 0이면 이 element는 처음으로 센서지역으로 들어가는 element라는 것을 알수 있죠. 그리고 이 물고기의 전체 elementsIn count를 1 증가시킵니다.
  2. 그 다음으로는 특정 element의 count 를 1 증가 시킵니다.


ended phase:

  1. 충돌한 element의 count 에서 1을 뺍니다.
  2. 그 다음에 element의 count가 0인지 체크를 하죠. 만약 0이면 이 물고기는 전체 센서지역에서 완전히 밖에 있는 상태라는 것을 알 수 있습니다. 그리고 물고기의 total elementsIn count 에서 1을 줄입니다.


The conditional check:

  1. 물고기의 total elementsIn count 가 0인지 여부를 체크합니다. 이전에 물속에 있었는지 여부를 체크합니다. 두 조건 모두 pass 하면 물고기는 물밖에 완전히 나온 상태라는 것을 알 수 있죠. 그러면 inWater flag를 false로 세팅합니다.
  2. elseif 조건문은 물고기의 elementsIn count가 5인지를 체크합니다. 이전에 물에 완전히 들어가 있지 않은지 체크하죠. 두가지 조건이 pass 라면 이 물고기는 이 물고기는 완전히 물 속에 있다는 것을 알 수 있죠. 그러면 inWater flag를 true로 세팅합니다.


물고기가 완전히 물 속에 있는지 아니면 완전히 물 밖에 있는지에 대해 이렇게 sensor collision을 가지고 handle 할 수 있습니다 이 방법은 센서들을 overlapping 하고 neighboring 과도 같이 사용할 수 있습니다. 예를 들어 core body 로 물 센서 지역을 만들었고 그 위에 파도가 있다면 이 코드는 물고기의 모든 elements들을 이 센서들에 모두 적용 시킬 수도 있습니다.


In Summary


여기까지가 오늘의 튜토리얼입니다. 배우신대로 mutli-element physics bodies 기능으로 joint-assembled bodies 가 할 수 없는 효과를 줄 수 있습니다. 하지만 사용하시려면 몇개의 장애물은 넘어야 겠죠. 이 튜토리얼이 여러분이 앱을 만드시면서 닥치는 그런 장애를 극복하는데 도움이 되기를 바랍니다.


반응형

Comment

내 앱에 애플의 iAds 광고 달기

2013. 1. 2. 16:44 | Posted by 솔웅



Posted on . Written by

“Show me the money!” — Cuba Gooding Jr. as Rod Tidwell in “Jerry McGuire”



Corona Labs 연말연시 선물중 하나가 애플 iAds 를 제공하게 된 것입니다. Project Gluon로 알려진 Corona SDK 플러그 인 시스템으로 만들어진 플러그인 중에 iAds 가 첫번째로 선보이는 플러그인입니다. iAds 는 daily build 992 이 후의 버전에서 사용 가능합니다.


Setup Simplified!


만약 애플의 어떤 기능을 사용하려고 하신다면 그 과정이 쉽지만은 않을 것이라는 걸 아실 겁니다. 코로나에서 제공하는 iAds 를 세팅하는 것은 놀라울 정도로 간단합니다. download 하기 위해 certificate 도 필요없고 인스톨하기 위한 provisioning profile들도 필요 없습니다. 그리고 iTunes Connect 에서 셋업하기 위한 그 까다로운 일도 안 하셔도 됩니다.


첫번째로, 여러분이 하실일은 iTunes Connect 계정으로 가시는 겁니다. “Contracts, Tax and Banking” section에서 iAds 에서 벌어들인 돈을 여러분의 은행계정으로 보낼수 있도록 하기 위해 여러 다양한 contract 에 동의하셔야 합니다.



아마 여러분은 유료 앱에 대한 은행 정보를 이미 셋업 하셨을 겁니다. 만약 지금 만드는 것이 여러분의 첫번째 앱이라면 혹은 지금까지 무료앱만 개발하셨다면 여기서 서류작업을 좀 하셔야 됩니다. 이미 이 부분을 셋업하셨다면 마케팅, finance 같은 다양한 entries 에 대한 contact 정보가 필요할 겁니다. 


그 다음으로는 “Manage Your Applications” section 으로 가세요. 여기서 아셔야 될 것은 이미 만든 앱에는 광고를 삽입할 수 없다는 겁니다. 이미 만든 앱의 새로운 버전을 업로드 하셔야 합니다. 왜냐하면 이미 만들어서 앱 스토어에 있는 앱 안에는 광고 관련 코딩이 안 돼 있을 테니까요.


새 버전 정보를 셋업했으면 “Manage iAds“ 라는 버튼을 보실 수 있을 겁니다. 여러분 앱이 17세 이하에게도 공개하시는 앱이라면 Save를 클릭해 주세요.




애플쪽의 셋업은 이게 다입니다. 이제 여러분 앱쪽으로 옮겨 보죠.

main.lua에서 ads 모듈을 turn on 하셔야 합니다.


local ads = require("ads")
 
local function adListener(event)
local msg = event.response
if event.isError then
-- Failed to receive an ad, we print the error message returned from the library.
print(msg)
end
end
 
ads.init( "iads", "com.yourcompany.yourappid", adListener )


com.yourcompany.yourappid setting 은 여러분 앱에 대한 bundle ID 입니다. iOS provisioning Portal 에서 셋업한 내용이 되겠죠. 광고를 보이고 보이지 않고 하기 위해 ads.init()를 사용하셔야 합니다. listener 함수에 테스트할 수 있도록 코딩을 해 넣으세요. 


iAds Functions


iAds 는 Corona SDK 의 ad system을 사용합니다. 아래 두 함수를 이용하실 수 있습니다.

  • ads.show()
  • ads.hide()


ads.hide() function은 보시면 아시겠죠? 광고를 화면에서 안 보이도록 할 때 사용하는 겁니다.

ads.show() function은 광고를 보이도록 할 때 사용하는 것이죠. 이 함수는 몇개의 파라미터를 사용해야 되는데요. positioning 등의 기능을 하도록 하기 위해 ad type 와a table of parameters들이 필요합니다.


ad type parameter 에는 아래 두가지 중 하나를 사용하실 수 있습니다.

  • banner
  • interstitial


Banner ads는 앱의 top 이나 bottom 에 모일 일반적인 사각형 앱을 말합니다. iAds 는 모든 디바이스에서 사용하실 수 있습니다. 다른 ad type으로 interstitial가 있는데요. scene들이나 level 들 사이에 표시될 fullscreen 광고가 그것입니다. IMPORTANT NOTE:interstitial ads는 iPad 에서만 가능합니다. iPhone 이나 iPod 에서는 사용하실 수 없습니다.

디폴트로 정해지는 positioning 도 괜찮다면 간단하게 아래 코드를 넣으시기만 하면 광고가 보일겁니다.


ads.show( "banner" )


iPad 앱을 개발하시고 fullscreen interstitial ad를 넣으시려면 아래와 같이 하세요.

ads.show( "interstitial" )


InMobi 와 Inneractive 와는 다르게 iAds 에서는 refresh time 을 control 하실 수 없습니다. 그 부분은 애플에서 관리합니다. interstitial ads 는 fullscreen 이기 떄문에 더군다나 이 기능이 필요 없구요. 여러분이 콘트롤 할 수 있는 것은 광고의 top-left 위치에 대한 x,y 값입니다. Ads 는 pixel 이 아니라 point 로 관리 됩니다. 만약 여러분 앱이 320X480 이라면 Retina device 에서는 두배크기로 보일 겁니다. 그리고 위치도 320X480 에 relative 된 위치가 사용될 겁니다.

배너 광고의 위치를 정하시려면 options table 에 x,y 파라미터에 값을 넣어 주시면 됩니다.

ads.show( "banner", { x=0, y=0 } )


이렇게 하면 디바이스의 top left 에 배너가 걸릴 겁니다. 이 값은 디폴트 값입니다. 애플은 iPhone/iPod 에 두가지 종류의 배너를 제공하고 iPad 에도 두가지 종류의 광고를 제공합니다.

이 광고들의 사이즈는 아래와 같습니다.

  • iPhone Portrait = 320×50
  • iPhone Landscape = 480×32
  • iPad Portrait = 768×66
  • iPad Landscape = 1024×66


값들이 달라서 광고를 위치지울 떄 좀 헛갈리실 수도 있습니다. 어떤 경우라도 width 는 full 이기 때문에 x 값은 0이외에 다른 값을 넣을 필요가 없을 겁니다. 만약 화면 아래쪽에 광고를 넣으시려면 코딩할 떄 조금 생각하셔야 할 겁니다. “Device Detection on Steroids” 에서 소개해 드린 device detection module 을 사용하신다면 아래처럼 하시면 될 겁니다.


local adY = display.contentHeight - 66
 
if not device.is_iPad then
if display.contentHeight > than display.contentWidth then
-- portrait
adY = display.contentHeight - 50
else
adY = display.contentHeight - 32
end
end
 
ads.show( "banner", { x=0, y=adY } )



오늘의 튜토리얼은 여기까지 입니다.

질문이 있으시면 댓글에 남겨 주세요.

새해 복 많이 받으세요.


반응형

Comment


Posted on . Written by


코로나 SDK 개발자들에게는 크리스마스가 일찍 왔습니다. 지난주 저희 팀들은 daily builds 에 여러 기능을 추가하느라고 하주 바쁜 시가늘 보냈습니다. 여러분들에게 소개해 드릴 새로운 기능들이 아주 많이 있습니다. 아래는 그 중 일부분입니다.


  • Build 987 — Inneractive ads SDK updated to 1.1.5
  • Build 990 — iOS 5/6 native Twitter access via native.showPopup()
  • Build 990 — new native.canShowPopup() API call to test for email, sms and twitter capability
  • Build 991 — Facebook SDK updated to 3.1.1
  • Build 992 — iAds support added
  • Build 993 — (drum roll please)… Android Push Notifications!


몇몇 개발자분들은 이런 새로운 기능들을 deploy 하고 계실 겁니다. 그리고 코로나 SDK의 new Plugin system인  Project Gluon 가 이런 작업들을 빨리 할 수 있도록 도와준 것에 대해 감사를 전합니다.


오늘은 Android Push Notifications에 대해 얘기를 해 보죠. 만약 애플에 push notification 을 deploy 해 보셨다면 아마 그 일은 certificates, keys, sandboxing, provisioning profiles, 3rd-party vendor setup 등등의 복잡한 것들을 처리하느라고 거의 로켓 과학하고 MBA 학위를 따는 것 만큼이나 힘들었을 겁니다. iOS 개발자 계정에 이러한 셋업을 다하고 나면 이제 여러분은 모바일 개발자로서 가장 어려운 부분을 해결한 겁니다.다행히도 여러분이 일단 애플쪽에 이런 setup 을 해 놓으셨다면 코로나의 push notification을 다루는 것은 아주 간단합니다.


이제 안드로이드 쪽을 살펴 보면 셋업 하는데 그렇게 많이 나쁘지는 않습니다. push notification 과 관련해 가장 큰 장애물은 Android OS 가 push notification을 관리하지 않는다는 겁니다. push 하는 것은 애플리케이션이 책임지는 겁니다. Corona Labs 는 이 안드로이드도 애플처럼 in-app implementation 을 만들기 위해 아주 많은 노력을 기울였습니다.


Four Basic Steps


안드로이드에서 push notification 을 얻는 방법은 다음 4 개의 기본 과정을 거칩니다.


  1. Setup for Google
  2. Setup for 3rd-party services (optional)
  3. Respond to registration events from your app
  4. Respond to push notifications as they arrive

1. Setup for Google



STEP 1:  브라우저에서  https://code.google.com/apis/console 로 가세요. 그리고 Services를 클릭하세요. Google Cloud Messaging for Android entry를 찾고 그것을 on 상태로 설정하세요. 아마 "Terms of Services" 에 동의 하셔야 할 겁니다.  거기서 제시하는 대로 다 동의 하세요.



STEP 2:   API Access를 클릭하세요. 그리고 Create new Server key를 클릭하세요. 아마 긴 화면이 나올 겁니다. IP address 쪽은 작성하실 필요가 없습니다. push notification을 send 할 때 사용하셔야 하니까 API Key 를 copy 하세요. (Corona Cloud service에서 했던 대로죠.)

NOTE:  Corona SDK sample app에는 push notification을 send 하는 기능이 있습니다. 여러분도 샘플로 테스트를 하시려면 이 API Key 를 사용하셔도 됩니다.



STEP 3:  API 콘솔의 Overview tab으로 가세요. 그리고 Project Number를 copy 하세요. Project ID는 무시하세요. 여러분의 Project Number는 또한 URL 이기도 합니다. 이 번호는 나중에 push notification 을 받을 때 필요하실 겁니다.



STEP 4: 이 부분은 그렇게 필요한 부분은 아닌데요. 일단 Google Play 개발자 콘솔에 방문하고 여러분이 업로드한 앱을 찾고 메타 데이터를 수정하고 Enable Google Cloud Messaging Stats entry를 찾으실 수 있습니다. 여기서 Project IDSender ID로 추가하세요. 이렇게 하면 여러분 앱의 GCM usage에 대한 stats 를 볼 수 있도록 해 줍니다.


2. Setup for 3rd-Party services (optional)


이 부분에서는 그다지 언급할 부분은 많이 없을 것 같습니다. Corona Cloud Services (Game Minion)에서부터 Urban Airship까지 많은 서비스 중 하나를 이용하실 수도 있고 또는  standard REST API call 을 사용해서 메세지를 보내는 여러분들만의 서비스를 사용하고 싶을 수도 있을 테니까요. 어느 경우에든 API Server Key는 있어야 됩니다.  GCM Server Key같은 걸 요구할 겁니다. 여러분이 제공해야 될 것은 이게 전부 입니다.


3. Respond to registration events from your app


iOS에 대한 push notification에 대한 코로나 Labs의  블로그 글을 올린것이 한 일년 전 쯤의 일입니다. 애플의 push notification을 셋업하는 일이 어려운 일의 대부분이었습니다. 대부분의 step 들이 다 거기에 대한 방법이었죠. step 4,5 번이 코로나와 관련된 부분이었습니다. 이 부분은 안드로이드에 대해서도 똑 같이 적용 됩니다.

push notification을 받기 위해서는 3개의 파일을 수정하셔야 합니다. 첫번째는 config.lua입니다.

아래 부분을 추가해 넣으시면 됩니다.


notification =
{
google =
{
projectNumber = "yourprojectnumberhere",
},
},

프로젝트 번호는 전부 다 숫자라도 따옴표 안에 있어야 합니다. 최근에 블로그에 올린 Ultimate config.lua 를 사용하실거라면  이 블록을 두번 복사해 넣으시면 됩니다. 하나는 tall 안드로이드 디바이스 부분 하나 하고 다른 디바이스 부분하고요.      


elseif ( display.pixelHeight / display.pixelWidth > 1.72 ) then
 
application =
{
content =
{
width = 320,
height = 570,
scale = "letterBox",
xAlign = "center",
yAlign = "center",
imageSuffix =
{
["@2x"] = 1.5,
["@4x"] = 3.0,
},
},
notification =
{
google = { projectNumber = "yourprojectnumberhere", },
},
}
else
application =
{
content =
{
width = 320,
height = 512,
scale = "letterBox",
xAlign = "center",
yAlign = "center",
imageSuffix =
{
["@2x"] = 1.5,
["@4x"] = 3.0,
},
},
notification =
{
google = { projectNumber = "yourprojectnumberhere", },
},
}
end

iOS device 부분에는 이 블럭을 넣으실 필요는 없습니다. 하지만 iOS 에 대한 push notification code를 이 블럭에 넣으셔야 된다는 점 기억해 두세요.

이제 두번째 수정하셔야 될 파일은 build.settings file입니다.

settings = {} table 안에 넣으셔야 될 겁니다. 그 안에 iPhone과 orientation 혹은 다른 블럭들이 있을 텐데요. 아래와 같이 안드로이드 부분을 세팅하시면 됩니다.


android =
{
permissions =
{
{ name = ".permission.C2D_MESSAGE", protectionLevel = "signature" },
},
usesPermissions =
{
"android.permission.INTERNET",
"android.permission.GET_ACCOUNTS",
"android.permission.RECEIVE_BOOT_COMPLETED",
"com.google.android.c2dm.permission.RECEIVE",
".permission.C2D_MESSAGE",
},
},



이제 마지막으로 main.lua 파일 입니다. 추가해야 될 코드가 있는데요. 이미 iOS push notification을 위해 이 코드를 넣으셨다면 이미 좋은 참고 자료를 확보한 셈입니다. 이 부분이 처음이라면 여러분이 추가하셔야 될 부분은 세부분으로 나눌 수 있습니다.   


첫번째로는 notification events 에 대해 respond 하기 위한 handler function을 셋업하셔야 합니다. 핸들링해야 할 이벤트가 두가지가 있는데요. (Registration and the Push Event itself) 둘 다 같은 handler function으로 관리하시면 됩니다. 이 함수를 사용하시기 위해 Runtime Listener 도 셋업 하셔야 됩니다.

suspended 나 stopped 상태에서 push notification이 여러분의 앱을 launches 시킬 어떤 특정 화면으로 가도록 프로그래밍을 하셔야 되는 걸 잊지 마세요. 


“remoteRegistration” event를 get 하면 애플이나 구글에 완전히 register 된 겁니다. 그리고 event.token variable에 여러분의 token 이 들어있는 상태가 됩니다. 이 값을 다른 서비스들과 함께 여러분의 디바이스에 register 시킬때 사용하실 수 있습니다. 이 remoteRegistration event 는 다른 서비스와 함께 register 시킬때나 나중에 token을 저장할 때 아주 유용합니다. (만약 여러분이 push sending service와 함께 로그인 과정이 끝나고 난 후 register 해야 할 떄 말이죠.)


local function onNotification(event)
 
if event.type == "remoteRegistration" then
-- This device has just been registered for push notifications.
-- Store the Registration ID that was assigned to this application by Google or Apple.
myRegistrationId = event.token
 
-- Display a message indicating that registration was successful.
local message = "This app has successfully registered for push notifications."
native.showAlert("Information", message, { "OK" })
-- Print the registration event to the log.
print("### --- Registration Event ---")
--
-- Here is where you put code to register with your push notification service
--
else
-- A push notification has just been received. Print it to the log.
print("### --- Notification Event ---")
-- here is where you respond to a push notification that comes in while the
-- app is running.
end
 
end


Now, to make this function do something useful, you need to hook it up to Corona’s event system:


Runtime:addEventListener( "notification", onNotification )


마지막으로 여러분의 앱이 running 상태가 아니라면 (i.e. it’s stopped/suspended, a push notification comes in for the app, and you tap on it) OS가 여러분의 앱을 launch 시킬 겁니다. 그 다음에 아무런 행동을 하지 않으면 유저가 앱 아이콘을 두드렸을 때 실행 되듯이 앱이 실행 될 겁니다. 하지만 그럴 경우 push notification의 content 내용에 따라 다른 특정 화면으로 앱을 시작하고 싶을 때는 어떻게 해야 할 까요? 예를 들어서 뉴스 관련 앱을 만들었고 새로운 속보가 push 됐다면 바로 그 story 로 화면이 시작되길 원하실 겁니다.


이럴 경우 launchArgs를 사용합니다. push notification은 custom data를 포함하고 있습니다.

앱이 시작될 때 이 launchArgs를 체크하세요. 그리고 필요하면 앱 실행부분을 코딩하셔서 원하는 기능을 구현하시면 됩니다. 이 코드는 대개 main.lua의 윗 부분에 있게 됩니다.


local launchArgs = ...
if launchArgs then
-- do something with the data in the launchArgs table
end


In Summary


이 튜토리얼의 내용은 여기까지 입니다. 이 push notification은 오직 iOS 디바이스와 Google Play 에 인스톨 된 앱과  안드로이드 디바이스 에서만 사용할 수 있다는 것을 유의하세요. Kindle Fire나 Barnes & Noble Nook 디바이스들에서는 이 기능을 제공하지 않습니다.

앞으로도 계속 튜토리얼들이 추가 될 겁니다. 관심있게 지켜봐 주시기 바랍니다.

Corona Labs 는 여러분들이 모두 연말 연시를 잘 보내시고 이 선물이 도움이 많이 되셨기를 바랍니다.

반응형

Comment



Posted on . Written by



구글 맵이 돌아왔습니다. 우리의 Corona iOS 앱에서 오늘 사용할 수 있습니다.

12-12-12 에 진짜 세계를 뒤 흔든 건 아이폰에 새 Google Maps app이 릴리즈 된 겁니다. 별로 신통치 않은 애플 맵을 대신 할 수 있게 됐죠. 구글은 turn-by-turn direction, vector based map tiles, 개선된 인터페이스 등을 새로 선보였습니다.



The URL Scheme


먼저 URL scheme의 origin 에 대해 알아보죠. WWW(World wide Web)이 처음 선보인  1990년대로 돌아가보죠. 개발자들은 뭔가 인터넷에서 어떤 것을 참조할 필요가 있었습니다. 그래서 그 참조할 정보의 위치를 간단하게 URL( Uniform Resource Locator)을 이용해서 찾아갔죠.


인터넷 상에서 access 할 수 있는 방법은 여러가지가 있습니다. 그리고 그것들은 단지 web page나 이미지들만이 아닙니다. terminal session을 열어서 Gopher 서비스에 연결할 수도 있고 FTP 서버로부터 파일을 다운로드 받을 수도있고 이메일 메세지를 보내고 하는 것들을 인터넷으로 할 수 있습니다.


colon 전의 URL 부분을 URL scheme이라고 합니다. 그 종류는 아래와 같습니다.

  • http: — a file from a web server
  • https: — a file from an encrypted web server
  • mailto: — send an email message
  • ftp: — access a file via FTP
  • telnet: — open terminal sessions to a server


iOS 에서는 이메일 안에 있는 전화번호를 touch 해서 dialer 를 열고 전화를 걸 수가 있습니다. 이런 기능들은 아래와 같은 URL schemes 를 사용해서 구현 합니다.


  • tel: — make a phone call
  • sms: — send a text message
  • itms-apps: — opens the app store
  • music: — go to the current playing song in the music app


애플은 애플리케이션이 자기 자신만의 scheme 을 define 하도록 합니다. 그래서 다른 앱에서 그 앱을 열수 있도록 허용합니다. Corona Labs 는 1년전에 이 기능을 제공했습니다. 이와 관련 된 글을 보시려면 여기를 클릭하세요.



Google Maps URL Scheme


Maps app 에 빠르게 접근하도록 하기 위해 Google 은 여러분 앱이 접근 가능하도록 URL scheme 을 사용합니다.


comgooglemaps:


물론 URL scheme 은 URL 의 일 부분입니다. 여기에 추가로 host 이름과 호스트상의 리소스들을 가집니다. 앱이 실제로 host name 이 없고 개별 파일로 접근할 필요가 없으면 full URL 은 아래와 같습니다.

comgooglemaps://


코로나에서는 이 API 를 아래와 같이 사용하시면 됩니다.


system.openURL("comgooglemaps://")



여러분의 앱은 suspend 상태가 되고 구글 맵이 시작될 겁니다. 간단하죠? 구글 맵에서 근처 피자가게를 찾고 싶으세요? 혹은 구글 맵에서 할 수 있는 다른 것을 여러분 앱에서 directly 하고 싶으세요?


URL scheme 에 다른 추가적인 정보를 덧붙이면 여러분 앱 안에서 그런 특정 기능을 사용하도록 하실 수 있습니다.





1. Map Coordinates and Traffic View


아래의 코드가 기본 형식입니다.

system.openURL("comgooglemaps://?center=40.765819,-73.975866&zoom=14&
views=traffic")




이렇게 하면 맵을 열고 traffic 상태를 보여 줍니다. 그리고 zoom level 은 14 이고 위도 경도는 40.7N and 73.9W 인 지점이 화면 가운데에 위치할 겁니다. 이 에제는 뉴욕의 센트럴 파크의 위치를 보여 줄 겁니다.

잘 보시면 첫번째 파라미터 전에 물음표가 있는것을 알아 채셨을 겁니다. 그리고 각 파라미터 사이는 &로 구분하구요. 그러니까 이 파라미터들을 분해하면 아래와 같이 되겠죠.


?center=40.765819,-73.975866   --center map at these coordinates
&zoom=14                       --zoom to level 14
&views=traffic                 --show traffic view


2. Map “Street View”

여기에 mapmode=streetview parameter를 추가하면 street view를 보실 수 있습니다.


system.openURL("comgooglemaps://?center=40.765819,-73.975866&
zoom=14&views=traffic&mapmode=streetview")


3. Map Query


맛있는 피자를 원하세요? 그러면은 q=pizza를 다른 파라미터 전에 추가히세요.


system.openURL("comgooglemaps://?q=pizza&center=40.765819,-73.975866")


4. Driving Directions


A 지점에서 B 지점까지의 경로는 어떻게 알 수 있을 까요?


system.openURL("comgooglemaps://?saddr=Google+Inc,+8th+Avenue,+New+York,+NY&
daddr=John+F.+Kennedy+International+Airport,+Van+Wyck+Expressway,
+Jamaica,+New+York&directionsmode=transit")



딱 보면 무지 복잡해 보이죠? 근데 자세히 보면 그냥 위와 같이 파라미터를 사용했을 뿐이예요.


?saddr=...         --"s" for "starting address"
&daddr=...         --"d" for "destination address"
&directionsmode=



+ 부호는 전부 다 뭘까요? 여기서 space 를 사용하면 문제가 발생할 수 있습니다. 그 space 를 + 부호로 대신 사용하는 겁니다. 아니면 hex code %20를 사용하셔도 됩니다.

근데 진짜 + 부호를 넣어야 되면 어떻게 할까요? 그럴 때는 hex code %2B 을 사용하셔야 됩니다.


Convenience Function


이렇게 파라미터를 추가하는게 짜증나시면 URL 을 encode 하는 함수를 사용하실 수도 있습니다.



function urlencode(str)
if (str) then
str = string.gsub (str, "\n", "\r\n")
str = string.gsub (str, "([^%w ])",
function (c) return string.format ("%%%02X", string.byte(c)) end)
str = string.gsub (str, " ", "+")
end
return str
end

system.openURL("comgooglemaps://?saddr=" .. 
urlencode("Google Inc., 8th Avenue, New York, NY") ..
"?daddr=" .. urlencode("John F. Kennedy International Airport,
Van Wyck Expressway, Jamaica, NY") .. "?directionsmode=transit")



보시듯이 openURL 안에 들어가는 것들을 함수로 간단하게 encoding 할 수 있습니다.



Maps App Installed or Not Installed?






URL scheme 에서 발생할 수 있는 문제는 디바이스에 구글앱이 깔려있지 않을 경우가 되겠죠? 깔려 있지 않으면 그 앱을 열 수가 없을 테니까요. 현재의 Corona Public Release(#971) 에서는 이럴 경우 화면과 같은 에러 메세지가 뜹니다. 아니면 그냥 silent fail 되던가요. 이건 바람직한 해결 방법이 아니죠.



Corona Daily Build #986부터 system.openURL() API를 call 했을 때 true/false를 리턴하기 때문에 이에 대해 대처할 수 있습니다.




 
local didOpenGoogleMaps =
system.openURL("comgooglemaps://?daddr=San+Francisco,+CA&saddr=cupertino")
if ( didOpenGoogleMaps == false ) then --defer to Apple Maps

system.openURL("http://maps.apple.com/?daddr=San+Francisco,+CA&saddr=cupertino")
end

  

디바이스에 구글 맵이 깔려 있으면  didOpenGoogleMaps 변수(변수명은 여러분이 정하실 수 있습니다)가  true 가 될 것이고 깔려있지 않으면 false 가 될 겁니다. 이렇게 되면 target 앱이 깔려 있지 않을 경우 개발자는 특정 메세지나 특정 action 을 구현할 수 있게 되죠. 이 기능은 Daily Build #986 부터 사용 가능합니다. 유료 사용자일 경우 여기에서 다운 받으세요.


Ready to Begin?


여러분이 보셨듯이 코로나에서 구글 맵을 implementation 하는 것은 URL scheme 을 사용해서 아주 간단하게 처리하실 수 있습니다.  구글 맵이 제공하는 모든 기능을 살펴 보시려면 구글 맵 URL Scheme 웹사이트로 가셔서 보실 수 있습니다. 만약 애플 맵을 사용하시거나 구글 맵의 백업기능 지원으로 애플 맵 기능을 지원하시려면 이 문서를 참조하세요.



반응형

Comment

  1. Kang 2012.12.21 02:48

    슬옹님 궁금한것이 있는데
    native.setActivityIndicator()
    위 API 를 사용할때 메시지도 같이 넣을 수 있는지요 ?
    인디게이터가 돌아가면서 옆에 "페이지 로딩중" 이런식으로 말이죵

    그리고 WEB 창에서 터치 드래그시에 오른쪽에 스크롤바가 뜨는걸 지우고 싶습니다 ㅠㅠ

    • 솔웅 2012.12.21 16:04 신고

      님이 하신 질문하고 다른 분들이 하신 질문하고 다 취합해서 영문으로 번역해 놨습니다.
      크리스마스 지난 다음에 Corona SDK 쪽에 질문을 보낼께요.
      답변 오면 제 블로그에 게시하겠습니다.

      요즘 저는 코로나를 다루지 않아서 직접 해보고 답변을 드릴 수가 없네요.

      코로나측으로부터 답변이 오는대로 게시하겠습니다.

간단하게 Device 분별하는 예제

2012. 12. 12. 13:40 | Posted by 솔웅


Posted on . Written by



지난주의 튜토리얼을 보셨나요? 보셨다면 Corona 가 다양한 디바이스의 스크린에 어떻게 각각 맞게 object들을 display 하는지 이해하셨을 겁니다. 저희는 이런 다양한 종류의 디바이스에 맞게 작업할 수 있도록 도와주기 위한 technology 를 사용하구 있구요. 또 각 디바이스에 맞는 폰트와 Kindle Fire soft button bar 같이 디바이스 별 특징 적인 부분들도 지원합니다.


Corona는 사용하는 device에 대한 정보를 얻을 수 있도록 하는 API 를 제공합니다. 이 API 를 call 함으로서 꽤많은 정보들을 얻고 그에 따라 어떤 작업을 할 수 있게 되죠. 개발자에게 가장 중요한 가이드 중 하나가 DRY (Don’t Repeat Yourself)죠. 코딩을 중복해서 하지 말라는 얘기 입니다. 아래 예제를 볼까요?


if ( system.getInfo("model") == "iPad" or

system.getInfo("model") == "iPhone" or

system.getInfo("model") == "iPod" ) then


store.init( "apple", transactionCallback )

store.restore()

else

-- IAP isn't yet supported on Amazon or Barnes and Noble so don't start it up.

if ( system.getInfo("model") ~= "Kindle Fire" or system.getInfo("model") ~= "Nook" ) then

store.init( "google", transactionCallback )

store.restore()

end
end

---------- OR ----------

local kindlePadding = 0

if ( system.getInfo("model") == "Kindle Fire" or system.getInfo("model") == "KFJWI" ) then

kindlePadding = 20

end

healthBar.y = healthBar.y - kindlePadding




만약 이 system.getInfo(“model”)을 온 동네에서 다 쓰고 다닌다면 엄청 양이 많은 코딩을 하게 될 겁니다. Amazon 하고 Barnes & Noble 이 새로운 device를 릴리즈 했는데. 그러면 model name들이 증가하겠죠. 현재까지 5개의 다른 Kindle Fire name 들이 있구요 Nook 은 4개가 있습니다.


이런 문제를 해결하기 위해 여러분은 if 문을 써서 간단한 external module 을 만드시면 됩니다.
그러면 위의 코드는 아래처럼 줄어들 수 있겠죠.


if ( device.is_iPad ) then
...
end
 
---------- OR ----------
 
if ( device.isKindleFire ) then
...
end



이렇게 함으로서 여러분은 키보드를 덜 누르고도 코딩을 하실 수 있습니다. 안드로이드 기반의 3가지 다른 플랫폼에 대해 일을 시작하기 전에, 일반 안드로이드 device인지 아니면 Nook이나 Kindle 같은 modified Android platform 인지 먼저 구별하는게 좋을 겁니다.




Assembling the Module


모듈을 하나 만들구요 이 모듈 이름을 device.lua 로 하겠습니다. 구버전에서 사용하던 “module(…, package.seeall)” method를 사용하지 않구요  new module method 를 사용할 겁니다. 아래 처럼 시작하시면 됩니다.




-- Create a table that will contain all of our tests we are setting up.
local M = {}
 
-- Set up some defaults...
M.isApple = false
M.isAndroid = false
M.isGoogle = false
M.isKindleFire = false
M.isNook = false
M.is_iPad = false
M.isTall = false
M.isSimulator = false




이 변수들을 가지고 작업을 하실건데요. 일단 디폴트로 모든 변수들에 false를 대입했습니다. 이제 model name 을 얻고 그것이 device 인지 Corona Simulator 인지를 먼저 구분한다음에 임시로 저장해 두죠.



local model = system.getInfo("model")
 
-- Are we on the Simulator?
if ( "simulator" == system.getInfo("environment") ) then
M.isSimulator = true
end


iPhone 5 가 나왔을 때 주요 이슈는 “tall” device 가 나왔다는 것이었습니다.  일단 이전의 아이폰은  16:9 HDTV shaped screen 이 아니었죠.  그러면 일단 이와 관련해서 16:9 Android devices 에 대한 정보도 같이 retrieve 해야 합니다.  여기서 먼저 전반적인 이해를 하고 넘어가야 할 부분은요. 7인치 tablet 들은 iPhone 4S와 iPhone 5 사이에 있는 디바이스 들입니다. 여기서 일단 우리는 iPhone 3 이나 4 보다 큰 것들을 먼저 추려 낼 겁니다.

320×480 devices 들은 1.5:1 (480/320=1.5) 의 비율을 가지고 있습니다. 그러니까 그것보다 크면 일단 "tall" device" 리스트에 넣겠습니다.



if ( (display.pixelHeight/display.pixelWidth) > 1.5 ) then
M.isTall = true
end
 
-- Now identify the Apple family of devices:
if ( string.sub( model, 1, 2 ) == "iP" ) then
-- We are an iOS device of some sort
M.isApple = true
 
if ( string.sub( model, 1, 4 ) == "iPad" ) then
M.is_iPad = true
end
 
else --...(the rest of the else is below)




그 다음으로는 애플 디바이스를 보겠는데요. 모든 Apple model 들은 iP로 시작합니다. ( iPod, iPad, or iPhone). 그러니까 모델 이름의 첫 두 글자를 보면 됩니다. 만약 iP로 시작하면 이것이 iOS 에서 구동 되는 것이라는 것을 알 수 있죠. 그러면 isApple flag 를 true로 할당합니다.


그리고 그 다음에는 그것이 iPad 인지 아닌지를 알아내야 합니다. 왜냐하면 iPad 와 다른 iOS device 들은 화면 모양에 대한 layout 이 다르게 작업해야 될 수 있거든요.

안드로이드는 저희의 삶을 도전적으로 만들었습니다. 왜냐하면 안드로이드 진영엔 3개의 marketplace 가 있거든요. (Google Play, Amazon, Barnes & Noble). 각각 저마다의 룰이 있어서 그것을 따라야 합니다. Barnes & Noble 같은 경우엔 “no ads” Amazon 같은 경우엔 “no links to Google Play”같은 것들이죠. Google Play 에 맞게만 작업할 수 없는 상황입니다. 그러니까 일단 Google Play 말고 다른 것들을 구별하죠.


위에 언급했듯이 Kindle 과 Nook device 들에는 다양한 모델들이 있습니다. 아래 코드를 계속 보죠.


else
-- Not Apple, so it must be one of the Android devices
M.isAndroid = true
 
-- Let's assume we are on Google Play for the moment
M.isGoogle = true
 
-- All of the Kindles start with "K", although Corona builds before #976 returned
-- "WFJWI" instead of "KFJWI" (this is now fixed, and our clause handles it regardless)
if ( model == "Kindle Fire" or model == "WFJWI" or string.sub( model, 1, 2 ) == "KF" ) then
M.isKindleFire = true
M.isGoogle = false --revert Google Play to false
end
 
-- Are we on a Nook?
if ( string.sub( model, 1 ,4 ) == "Nook") or string.sub( model, 1, 4 ) == "BNRV" ) then
M.isNook = true
M.isGoogle = false --revert Google Play to false
end
 
end


코드를 한번 볼까요? 첫번째 if part 에서는 애플 (Apple) device 일 경우 로직을 만들었습니다. 그러니까 else문 에서는 안드로이드 기기와 관련된 로직을 만들어야 겠죠. 그래서 일단 isGoogle flag를 true로 만들어 놓고 시작했습니다.


그 다음으로는 두 파트에 걸쳐서 나머지 두 marketplace 에 대해 걸러 내겠습니다. 첫번쨰로 Kindle Fire일 경우를 보죠. 이 경우 모델 이름은  KFWFJWI로 시작합니다. 아마존이 4개의 새 Kindle Fire 모델을 릴리즈 했을 때 그 모델 이름들은 KFJWI라고 지었습니다. 처음 Kindle Fire의 모델명은 Kindle Fire였구요. 제 2세대에서는 KFOT로 지었구요. 7인치 HD 모델은 KFTT입니다. 그리고 9인치 모델은 KFJWIKFJWA이구요. (WiFi를 지원하느냐 3G -WAn- 을 지원하느냐에 따라 다릅니다.) 다행스럽게도 모든 모델들에 공통적인 것은 KF or Kindle Fire 입니다.

K로 하면 어떨까 생각 드시나요? 그래도 됩니다. 하지만 안드로이드 디바이스의 수는 질리도록 많습니다. K로만 하면 예외 상황이 발생할 확률이 아주 높습니다.

이제 디바이스가 Kindle Fire 로 확인 되면 isGooglefalse 로 하고  isKindleFire 는  true로 세팅합니다.


이제 Nook 를 체크할 차례죠. 마지막 입니다. 대부분 Barnes & Noble 은 모델 이름이 BRNV 로 시작 됩니다. Nook Color 는 Nook Color를 사용합니다.


여기까지 했으면 다음에 할 일은 앱에 이 정보를 return 하는 것입니다.


-- Return the table "M", providing access to it from where the module is "require"d
return M


Putting it to Work


이것을 앱에서 사용하시려면 프로젝트 시작부분에서 간단하게 그 모듈을 require 하시면 됩니다.


local device = require("device")


그러면 이제 여러분이 필요할 때마다 그것을 불러 와서 사용하시면 됩니다.


if ( device.isApple ) then
store.init( "apple", transactionCallback )
store.restore()
 
elseif ( device.isGoogle ) then
store.init( "google", transactionCallback )
store.restore()
 
end


이렇게 하면 디바이스 체크하는 부분의 코딩이 훨씬 잘 정리가 될 겁니다. 그러면 여러 OS와 여러 Device 에서 구별되서 돌아가도록 앱을 개발할 때 아주 용이하게 사용하실 수 있겠죠. 더군다나 이 device detection 을 external module 로 만들어 놔서 새로운 디바이스가 나왔을 떄 거기에 맞게 수정 작업하는 것도 훨씬 쉬워질 겁니다.

완성된 소스는 아래에 있습니다.



device.lua

이 모듈을 이전 시간에 다뤘던 Ultimate config.lua 와 함께 사용하세요.

현재 market 에 나와있는 모든 디바이스에 맞게 여러분의 앱을 개발하고 빌드할 수 있을겁니다.

그리고 앞으로 새로운 디바이스가 나와도 쉽게 작업을 하실 수가 있을 겁니다.

반응형

Comment