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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형

Parallax Simplified

Posted on . Written by

I

이번주 화요일의 튜토리얼은 코로나 Ambassador 인 Brent Sorrentino 씨가 해 주셨습니다. parallax scrolling 과 여러분 앱에 customizable touch-and darg parallax view 를 구현하는 데모 프로젝트에 대해 다룹니다.


We Live in a Parallax World



Parallax 는 천문학, photography,광학 에서 다루는 개념인데요 좀 의미가 광범위 합니다.  모바일 게임 개발자의 경우 2-dimensional scrolling simulation 에 대해 작업할 경우 parallax는 카메라와 움직이는 객체 사이의 거리감 (sense of “distance”) 을 제공하게 되죠.  이 메소드는 16 비트 닌텐도 시절부터 사용되어져 왔습니다. 그리고 요즘 인기있는 Angry Birds 와 Squids 등에서도 사용되고 있죠.


코로나의 display groups를 활용하면 차례로 겹쳐진 layer 들로 parallax scrolling을 간단하게 구현할 수 있습니다. 이론적으로는 그런데 실제 많은 개발자들이 실제 이것을 코딩하려고 하면 많이 헛갈려 하시죠. 오늘의 튜토리얼과 데모 프로젝트는 이 부분에 촛점이 맞춰져 있습니다.




1. Getting Started


처음으로 우리가 할 작업은 우리의 parallax "layers" 들을 display groups 로 configure 하는 겁니다. main.lua 파일에 아래 코드를 복사해 넣으세요.


--First, declare a local reference variable for the stage.
local stage = display.currentStage

--Declare your parallax layers in back-to-front order and specify their distance ratio.
--This "distanceRatio" sets the layer's scrolling speed in relation to the foreground (1.0) layer.
local back00 = display.newGroup() ; back00.distanceRatio = 0.2
local back01 = display.newGroup() ; back01.distanceRatio = 0.4
local back02 = display.newGroup() ; back02.distanceRatio = 0.6
local back03 = display.newGroup() ; back03.distanceRatio = 0.8
local foreground = display.newGroup() ; foreground.distanceRatio = 1.0

--IMPORTANT NOTE 1: You MUST have one layer set to 1.0 ratio, even if it won't contain any objects.
--IMPORTANT NOTE 2: Attach a reference variable named "refGroup" to the stage table and set its
--value to the 1.0 layer from above. This group will be used as the core reference group in
--relation to screen touch-and-drag.
stage.refGroup = foreground


  

각 parallax layer 는 “distanceRatio” parameter를 정해 줍니다. 이것은 아주 중요한 부분입니다. 우리는 이것을 foreground 의 1:1 scrolling 과 연결해서 각 레이어별로 스크롤링 스피드를 주는부분에서 사용할 겁니다.  만약 특정 layer 가 parallax scrolling에서 제외되어야 한다면 간단하게 이 파라미터를 undeclare 하면 됩니다.  예를 들어 이 display group setup의 hierarchy 중에 한개 나 두개의 layer 를 scroll 하고 싶지 않다면 , 예를 들어 UI/button/widget layer 같은 경우가 될 수 있겠죠, 이럴 경우 이 distanceRatio 값을 넣지 않으면 됩니다. 이 “distanceRatio” parameter 가 있는 layer 만 parallax scrolling 이 될 테니까요.


Additional note: “distanceRatio” parameter 는 여러분이 원하신다면 1.0 보다 클 수도 있습니다.  예를 들어 1.4로 했다면 이 레이어는 foreground 보다 더 빨리 움직일 겁니다.  이건 아마 카메라와 foreground 사이에 연한 안개가 있도록 할 경우에 활용할 수 있겠네요.


2. Setting Scroll Limits


대부분의 시나리오에서 어떤 제한된 공간(세계) 가 있게 되죠. 유저가 어느쪽으로 어디까지 drag 할 수 있는지에 대한 제한 등이 그런 것들인데요. 이런것들은 다음에 나오는 4개의 변수들에 의해 해결할 수 있습니다. 다음에 이것들을 쉽게 사용하도록 하기 위해 stage 의 파라미터들로서 이 4개를 정의해 둡니다.

여러분의 프로젝트에 다음 한 줄을 추가해 주세요.


stage.xMin = -400 ; stage.xMax = 400 ; stage.yMin = -150 ; stage.yMax = 1000
 

이 4개의 파라미터가 간단하게 그 제한된 공간을 나타낼 겁니다. xMin는  대개 음수가 되겠죠. 왼쪽까지 얼마나 멀리 그 공간이 정해질지를 정합니다. xMax는 오른쪽까지의 공간이구요. yMinyMax는 위쪽과 아래쪽으로 스크롤할 수 있는 공간을 나타낼겁니다.

이 숫자들은 모두 픽셀단위를 사용합니다. % 가 아닙니다. 또한 foreground 1:1 레이어는 움직이지 않을 거구요. 이 foreground layer 이외의 layer 들은 이 제한사항들이 적용될 겁니다.


3. Add Layers to a Containing Table


이제 우리는 이 parallax layer 들을 paraGroups라는 이름의 테이블에 포함시킬 겁니다. 그리고 그 테이블은 stage 의 sub-table 이 될 거구요.

아래 코드를 여러분 프로젝트에 복사해 넣으세요.


stage.paraGroups = {}
for g=1,stage.numChildren do
  if ( stage[g].distanceRatio ) then stage.paraGroups[#stage.paraGroups+1] = stage[g] end
end

  

이 코드를 보면 첫번째로 테이블을 정의합니다. 그리고 display groups 에 대해 stage 의 각 child 숫자 만큼 루프를 돌립니다. 그리고 if 문을 사용해서 “distanceRatio” parameter가 있는 경우에만 테이블에 집어 넣습니다.


4. Populate the World


다음 코드에서는 gradient background를 세팅할 겁니다. 그리고 our world 에 몇개의 샘플 객체들을 추가할 거구요. 이 코드들에 대해서는 일일이 설명하지는 않을께요. 여러분들은 여러분들 나름대로의 parallax world 를 만들실 거니까요. 아래에서 하나의 객체를 각 parallax layer 에 add 하는 것을 보실텐데요. 여러분은 많은 객체들과 이미지들을 추가하셔야 한다는 것만 기억해 두세요.


local cX = display.contentWidth/2 ; local cY = display.contentHeight/2
local bkgrad = graphics.newGradient( {42,17,10}, {0,0,0}, "up" )
local bk = display.newRect( 0,0,cX*2,cY*2 ) ; bk:setFillColor( bkgrad ) ; bk:toBack()

local object00 = display.newRect( back00,cX-16,cY-16,32,32 ) ;
    object00:setFillColor(150,0,50,120)
local object01 = display.newRect( back01,cX-32,cY-32,64,64 ) ;
    object01
:setFillColor(150,20,50,140)
local object02 = display.newRect( back02,cX-48,cY-48,96,96 ) ;
    object02
:setFillColor(150,40,50,160)
local object03 = display.newRect( back03,cX-64,cY-64,128,128 ) ;
    object03
:setFillColor(150,60,50,180)
local foreObject = display.newRect( foreground,cX-80,cY-80,160,160 ) ;
    foreObject
:setFillColor(150,80,50,200)


5. Implementing Touch and Drag

여러분이 움직일 수 없다면 이 scrollable world 는 소용이 없겠죠. 아래 함수는 screen touch 의 "began" 과 "moved" phases 를 사용해서 paraGroups table 안에 있는 모든 그룹들을 스크린 내에서 여러분이 drag 할 수 있도록 만들겁니다.

이 함수와 touch listener 를 여러분 프로젝트에 복사해 넣으세요.


local function touchScreen( event )
  local stageID = event.target
  local refGroup = stageID.refGroup ; local paraGroups = stageID.paraGroups
  local eventX = event.x ; local eventY = event.y

  if ( #paraGroups < 1 ) then return end

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

    for i=1,#paraGroups do
      paraGroups[i].offsetX = eventX - refGroup.x
      paraGroups[i].offsetY = eventY - refGroup.y
    end

  elseif ( event.phase == "moved" ) then

    local xDif = eventX - refGroup.offsetX
    local yDif = eventY - refGroup.offsetY

--If you are NOT limiting the boundaries of your world, comment out these 2 lines.
    if ( xDif < stageID.xMin ) then xDif = stageID.xMin
    elseif
( xDif > stageID.xMax ) then xDif = stageID.xMax end
    if ( yDif < stageID.yMin ) then yDif = stageID.yMin
    elseif ( yDif > stageID.yMax ) then yDif = stageID.yMax end

    for i=1,#paraGroups do
      local group = paraGroups[i]
      group.x = xDif * group.distanceRatio
      group.y = yDif * group.distanceRatio
      group = nil
    end
    xDif, yDif = nil,nil

  end

  stageID, refGroup, paraGroups, eventX, eventY = nil,nil,nil,nil,nil
  return true

end

stage:addEventListener( "touch", touchScreen )



이 코드에 대해서도 일일이 설명을 드리지는 않겠습니다. 단지 이 behavior를 summarize 할께요.


began phase에서 우리는 paraGroups table에 대해 루프를 돌립니다. 그리고 the foreground reference group 에 대해 각각 특정 X/Y 값을 세팅합니다. 이것은 core 1:1 group 의 포지션에 근거해서 각 layer들의 위치를 정해주기 위해 필요한 부분 입니다.


moved phase 는 실제 action 이 일어나는 곳입니다. 우선 첫번째로 어디에 유저가 touch 했는가에 따라 관련 group 들을 움직이기 위해 xDifyDif ”delta” values를 정해야 합니다. 그리고 바로 직전의 phase check 으로부터 얼마나 멀리 움직였는지도 정해야 하구요.


그 다음으로는 위의 Step #2 에서 정해 놓은 world boundary 들의 바깥으로 X, Y 포지션이 나갔는지 여부를 체크합니다. 4개 중에 하나라도 밖으로 나가게 되면 xDifyDif value 를 pre-adjust  합니다. 그렇게 함으로서 그 world 밖으로는 절대 나가는 일이 없게 되는거죠. 코드 안에 적어놨듯이 scrolling 의 제한을 주고 싶지 않으면 이 부분을 주석을 달 수도 있습니다. 


마지막으로는 paraGroups table에 루프를 돌리고 각각의 parallax layer의 위치를 조정합니다. 이 때 group 에서 정해 줬던 distanceRatio value에 따라 위치를 조정하게 됩니다. distance ratio 가 1.0 인 foreground group은 정확히 유저의 touch 를 따라서 움직이게 됩니다. 그리고 그 이외에 layer 들은 여러분이 정해 준 distance ratio 에 따라서 약간씩 다르게 움직입니다. 한번 ratio들을 바꾸고 나서 다시 시도해 보세요. 움직임이 다를 겁니다. 여러번 테스트 해 보시고 가장 그럴듯한 ratio 들을 정하셔서 사용하세요.



아주 간단하죠? 이제 몇개의 layer 든지 상관없이 scrolling touch-and-drag parallax world 를 구현할 수 있는 flexible 한 메소드를 구현했습니다. 이제 실전에서는 각각의 ratio 들만 조정해서 사용하시면 됩니다. 아니면 database 나 text file을 이용해서 dynamically 움직임들을 조정 할 수도 있죠.


6. Cleaning Up


마지막으로 layer references들을 clean up 하는 것을 잊지 마세요. 특히 여러분이 Storyboard 나 Director 와 같은 scene manager 를 사용하고 있다면 더 잊지 마셔야 됩니다. 각 scene manager 에서 사용하는 standard cleanup practices 를 사용하는 것 외에 parallax display groups 에 대한 references 를 가지고 있는 paraGroups table 을 clean up 하는 것도 중요합니다. scene 들이 바뀔 때마다 이 작업들이 이루어 지도록 해야 합니다. touch listener도 remove 하는 것 잊지 마시구요. (전체 메소드가 전체 scene 에서 작동하는 global level 이면 필요가 없겠지만요.)



for i=#stage.paraGroups,1,-1 do
  stage.paraGroups[i] = nil
end
stage:removeEventListener( "touch", touchScreen )


Looking Forward!


다음 튜토리얼에서는 이 메소드를 build 하고 dynamic 하게 parallax layer 를 zoom in/out 하는 간편한 zoom feature에 대해 다루겠습니다. 그 때까지 이 데모 project 를 구현하고 수정하고 테스트 해 보시고 질문이나 제안이 있으면 댓글 달아 주세요.



반응형