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

최근에 받은 트랙백

글 보관함


Posted on . Written by


오늘의 튜토리얼은 Corona Labs Developer Relations team의 Rob Miracle 님이 제공해주셨습니다. Rob은 18개월간 코로나 개발자로 활동하였고 10개가 넘는 앱을 릴리즈 했습니다.



모바일 앱을 개발할 때 가장 많이 떠오르는 생각 중 하나가 "어떻게 이 다양한 디바이스들을 모두 다 지원할 수 있을까?" 입니다. iOS 쪽만 하더라도 3가지 종류의 기기들이 있고 또 거기에다가 다양한 해상도가 있습니다. 여기다가 안드로이드까지 더하면 미치기 일보 직전까지 가죠.



어떤 화면은 넓고 어떤 화면은 좁고 그렇습니다. 그걸 제대로 디바이스에 맞게 하려면 일일이 맞춰줘야 하는데요. 코로나는 Dynamic Scaling라는것을 사용해서 그 작업을 쉽게 도와드립니다. 이 Dynamic Scaling로 각각의 스크린에 따라 세팅을 하시면 코로나가 자동으로 각각의 해상도 화면에 맞게 텍스트와 그래픽을 맞춰 드립니다. 여러분이 설정한 기준에 따라 upward 할 수도 있고 downward 할 수도 있습니다. 그리고 scale up을 하기 위해 필요할 떄는 substitute higher resolution images도 할 수 있습니다. 이것은 여러분의 프로젝트 폴더 안에 있는 config.lua라는 Lua 파일로 구현할 수 있습니다.



고민해야 하는 해상도가 아주 많습니다. 각 디바이스에 대해 같은 scale 을 사용하면 편하겠죠.  320×480의 아이폰 3GS 에 있던지 1536×2048의 Retina iPad 에 있던지 (0,0)이라는 위치는 top-left corner  입니다. 그리고 (320,480) 는vertical portrait mode 에서는 bottom-right corner이죠. 화면의 중심은 (160,240)입니다. 3GS 같이 낮은 해상도 기기 - native 화면 해상도가 320×480- 에서 각 point는 한 픽셀을 말합니다. 그리고 Retina iPad 같은 경우엔 각 point는  4픽셀 입니다. 이걸 일일이 계산할 생각은 하실 필요 없습니다. 코로나가 다 계산해 드릴겁니다.





Basic Screen Shapes


너비와 높이가 360×570인 이미지가 있다고 가정해 봅시다. 우리는 실제 화면보다 더 큰 배경이미지를 만들겁니다. 왜냐구요?  그 이미지를 여러 디바이스에서 다 사용할 거거든요. 안쪽 사각셩( inner rectangle)은 320×480 block 입니다. 아마 이미지의 맨 가장자리 부분은 화면 밖으로 나갈 겁니다. 이것을 iPhone 5 나 대부분의 안드로이드 폰 같은 tall device 에서는 이 이미지의 full height를  이용할 겁니다. 그 경우에도 왼쪽과 오른쪽 가장자리는 화면 밖으로 나가게 될 겁니다. 여러분이 좀 더 정사각형에 가까운 iPad 를 가지고 있다면 그 이미지는 위와 아래쪽 가장자리가 잘리고 왼쪽과 오른쪽은 모두 다(full width) 보일 겁니다. 이렇게 밖으로 나가는 부분은 “bleed area” 라고 합니다. 일반 인쇄를 할 때 종이 여백에 잉크가 번질 수도 있는 그런 여분의 공간을 말할 때 그 말을 사용합니다.



이제 어떻게 background 가 사용되는지 이해하고 직접 볼 수 있습니다. 바로 config.lua file 에 셋업하시면 됩니다. 이 파일은 standard Corona Lua file 이기 때문에 아래 사항을 구현하기 위해 몇가지 API 를 사용할 수 있습니다.


  • read the pixel width/height of the device. 디바이스 너비 높이 얻기
  • use math for various calculations. 다양한 계산을 위해 math 사용하기
  • read the device model number. 디바이스 모델 번호 얻기
  • perform string manipulations. string manipulation 사용하기
  • use conditional “if-then-else” logic. if 문 사용하기


iPad Configuration


우리가 할 첫번째 작업은 앱이 실행되는 기기가 아이패드일 경우에 대한 코딩입니다. 아래 Corona API 로 모델 number 를 얻을 수 있습니다.


system.getInfo("model")



실제 지금 아이패드에서 실행하면 string iPad를 결과 값으로 얻으실 겁니다. 그런데 아마 애플은 조만간 이것을 바꿀 수도 있습니다.  iPad Retina 뭐 이런걸로요. 그러니까 if 문을 사용해서 이런 경우에도 대비를 해 두는게 낫겠죠.


if ( string.sub( system.getInfo("model"), 1, 4 ) == "iPad" ) then
   application =
   {
      content =
      {
         width = 360,
         height = 480,
         scale = "letterBox",
         xAlign = "center",
         yAlign = "center",
         imageSuffix =
         {
            ["@2x"] = 1.5,
            ["@4x"] = 3.0,
         },
      },
   }


코로나는 루아의 string.sub() 함수를 사용합니다. 디바이스 모델을 읽은 뒤 앞의 4글자만 취하게 되죠. 그러니까 애플이 iPad 뒤에 무엇을 넣던지 이 로직에서는 상관이 없겠죠. 이 경우 얻은 글자가 iPad이면 content table을 작성할 건데요. width and height parameters 들은 각각 360 and 480 으로 합니다. 여기서 너비를 360으로 하는 이유는 우리가 사용하는 샘플 이미지의 너비가 360 픽셀이기 때문입니다. 우리는 이 이미지를 아이패드 스크린에 맞게 이미지를 늘릴겁니다. 코로나는 기본으로 portrait 을 사용합니다. 만약 여러분 앱이 landscape 모드를 사용한다면 그 이미지를 portrait orientation 을 가정해서 거기에 맞게 너비와 높이를 정해야 합니다. 그러면 코로나가 자동으로 알아서 기기를 세웠을 때와 뉘었을 때에 맞게 알아서 이미지를 돌려 줍니다.



너비가 360 이니까 이제 높이를 계산해야죠. 이 높이도 iPad 의 화면에 딱 맞게 해야 합니다. iPad 스크린은 768X1024 입니다. 그리고 Retina iPad 인 경우는 1536X2048 이죠. (정확히 두배입니다. 그러니까 그 비율은 정확히 같은 겁니다.) 우선 첫번쨰로 1024를 768로 나눕니다. 그 결과는 1.33 이 됩니다. 그리고 높이를 얻기 위해 360X1.33을 합니다. 그러면 480이 되겠죠.



1024 / 768 = 1.33  --screen ratio (height:width)
1.33 x 360 = 480


iPhone5 Configuration


elseif
( string.sub( system.getInfo("model"), 1, 2 ) ==
"iP" and display.pixelHeight > 960 ) then
   application =
   {
      content =
      {
         width = 320,
         height = 568,
         scale = "letterBox",
         xAlign = "center",
         yAlign = "center",
         imageSuffix =
         {
            ["@2x"] = 1.5,
            ["@4x"] = 3.0,
         },
      },
   }


아이폰의 경우는 모델 string 에서 첫 두글자만 취하겠습니다. iPad 하고 iPod Touch 둘 다 iP 로 시작하는데 왜 그렇게 하냐구요? 그래서 이것 말고 디바이스의 pixelHeight 를 구할 거거든요. (이 pixelHeight 프로퍼티는 Corona build #971 에서부터 지원했습니다. 그러니까 그 아래 버전을 사용하시는 분은 그 상위 버전을 download 하세요.)  만약 그 pixel heignt 가 960 보다 크다면 그 디바이스는 iPhone 5 가 되는 겁니다. (아니면 iPod Touch 최신버전이던가요.)  그 디바이스의 해상도는 640 X 1136 이니까 이것을 320 pixel로 그 비율을 맞춘다면 320×568 이 됩니다.

그래서 너비와 높이늘 320×568 로 해 주시면 됩니다.


위 계산대로 하면 배경 이미지의 높이가 iPhone 5 의 높이에 딱 맞게 표시 될 겁니다. iPhone 5의 실제 해성도는 640X1136 이구요 우리 이미지의 안쪽 사각형 (inner rectangle" 이 너비가 320 이니까 이걸 가지고 아래처럼 간단히 계산하면 그 비율에 맞는 높이를 구할 수 있습니다.



320 / 640 = 0.5  --inner rectangle width / device pixel width = factor
1136 × 0.5 = 568  --device pixel height × factor = config.lua 'height' setting



우리가 가지고 있는 샘플 배경 이미지의 높이는 570 입니다. 그러니까 iPhone 5 화면에서는 위와 아래쪽에 각각 1pixel 씩 화면 밖으로 나갈 겁니다.


iPhone 3,4 and Older iPod Touch



elseif
( string.sub( system.getInfo("model"), 1, 2 ) == "iP" ) then
   application =
   {
      content =
      {
         width = 320,
         height = 480,
         scale = "letterBox",
         xAlign = "center",
         yAlign = "center",
         imageSuffix =
         {
            ["@2x"] = 1.5,
            ["@4x"] = 3.0,
         },
      },
   }


위쪽에서 이미 iPhone 5 를 사용했으니까 다음 else if 문에서 모델이 iP 로 시작하는 스트링을 얻는다면 그것은 이전 버전의 iPhone 이나 iPod Touch 가 될 겁니다. 해상도는 320X480 과 640X960 이렇게 두 종류가 있죠. 이 경우에는 우리가 가지고 있는 이미지의 inner rectangle 인 320X480 을 사용할 수 있습니다.



Android, Kindle Fire, and Nook


마지막으로 우리는 16:9 인 안드로이드 디바이스와 5:3 인 NOOK 과 Kindle Fire 디바이스를 따로 작성해야 합니다. 16:9 device 들은 그 비율이 1.777:1 이 됩니다. Nook 과 Kindle Fire 는 1024X600 에 가깝구요 그 비율은 1.7:1 이 됩니다. 그러니까 이 경우 화면 크기 비율을 계산하기 위해 pixelHeight and pixelWidth를 사용할 수 있겠죠. 만약 그것이 1.72 보다 크면 iPhone 5 와 비슷한 16:9 로 계산하면 될 겁니다.  만약 그렇지 않으면 그 기기에 맞도록 너비와 높이를 세팅해야 겠죠.


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,
         },
      },
   }

else
   application =
   {
      content =
      {
         width = 320,
         height = 512,
         scale = "letterBox",
         xAlign = "center",
         yAlign = "center",
         imageSuffix =
         {
            ["@2x"] = 1.5,
            ["@4x"] = 3.0,
         },
      },
   }

end



Important Notes


여기에 완성된 파일이 있습니다. 이 파일만 사용하시면 현재까지 나온 대부분의 디바이스들이 cover 될 겁니다. 아래 사항들을 기억해 두세요.


  • On some devices parts of the background will be off screen, so don’t put anything “critical” in the bleed areas.
  • Objects positioned using fixed numbers like player.x = 64 and player.y = 64 will always be 64 points away from the top and left edges, but based on the device will be closer or further away from center.  Likewise, objects placed using the right and bottom edges will stay the prescribed distance away from those edges (player.x = display.contentWidth-64 will stay 64 points away from the right edge) but they will move closer or further from the center too. Objects positioned based on a center coordinate (player.x = display.contentCenterX+100) will stay in the same relative position regardless of the screen shape.


Dynamic Image Selection


각 configuration block 에는 아래와 같이 imageSuffix table 이 있습니다.


imageSuffix =
{
   ["@2x"] = 1.5,
   ["@4x"] = 3.0,
},


만약 코로나를 처음 접해 보신다면 이 방법이 바로 코로나 SDK 가 Dynamic Image Selection을 지원하는 부분이라는 점을 알고 계시면 됩니다. 예를 들어 여러분이 Retina iPad 와 iPhone 3GS 에 같은 이미지를 사용하지 않고 해상도를 충분히 지원해 주는 그런 이미지를 별도로 사용하고 싶으실 수 있을 겁니다. 그러면 이 image sets 를 일반 디바이스와 Retina/HD 디바이스에 맺게 각각 다르게 세팅하시면 그 디바이스 해상도에 맞게 작업하신 이미지를 자동으로 바꿔서 display 할 수 있습니다.



imageSuffix table 은 두개의 부분으로 구성돼 있습니다.



["@2x"] =        --append this suffix to all images designed for those device(s)
(decimal value)  --scale factor that Corona uses to pick the proper assets



첫번째 값은 여러분이 이름을 정해 주시면 됩니다. 두번째 값은 아래 공식에 의해 계산된 값입니다.


device width / config.lua 'width' = scale factor



만약 특정 디바이스의 scale factor가 이 숫자보다 크다면 코로나는 그 asset 을 사용할 겁니다.

아래와 같이 세팅하시면 될 겁니다.



["@2x"] = 2.0
["@4x"] = 4.0



저희들이 계산한 아래 예제들을 참조하세요.




600 / 320 = 1.875  --Kindle Fire & Nook 640 / 320 = 2.0   --iPhone 5 768 / 360 = 2.13   --iPad 800 / 320 = 2.5   --Kindle Fire HD / Nexus7 1200 / 320 = 3.75  --Kindle Fire HD 8.9 1536 / 360 = 4.26   --Retina iPad



만약 여러분이 4.0 으로 세팅했다면 이것은 Retina iPad 에만 적용 되고 Kindle Fire HD 8.9 이하에는 적용되지 않을 겁니다. 즉 Retina iPad 전용 이미지가 되는 것이죠.

그런데 대개 Kindle Fire HD 8.9 와 Retina iPad 에는 같은 이미지를 사용해도 문제가 없습니다.

그래서 다운 받은 configuration file 에는 아래와 같이 세팅 돼 있을 겁니다.



["@2x"] = 1.5,
["@4x"] = 3.0,



이렇게 하면 해당 “@4x” image 는 Retina iPad, Kindle Fire HD 8.9, and the Nexus 10 같은 고 해상도 디바이스용 이미지를 별도로 사용할 때 유용할 수 있습니다. 그 이외의 디바이스들은 “@2x” images 를 사용할 겁니다.


끝으로 중요한 것 한가지 더 언급해 드린다면 배경을 display 하기 위해 display.newImageRect() 를 사용하시라는 겁니다.



local
background = display.newImageRect( "background.png", 360, 570 )


끝의 두 숫자 360과 570 은 1X 에 근거한 이미지의 너비와 높이 입니다. 샘플 파일에서는 lowest resolution target device 가 original Kindle Fire 와 NOOK Color 로 돼 있습니다. 이 디바이스들과 “@2x” bracket 에 속하는 다른 디바이스들은 코로나에서 720X1140 을 사용할 거고 “@4x” version 에서는 1440X2280 을 사용할 겁니다. 이런 작업들은 코로나가 알아서 할 거니까 별로 크게 신경 안 쓰셔도 됩니다.


In Summary…


이 튜토리얼은 약간 detail 한 부분까지 설명 했습니다. 코로나를 처음 접해 보시는 분들에게는 좀 더 간단히 설명해 드릴 필요가 있을 텐데요. 위에 저희가 샘플 파일을 다운로두 받을 수 있도록 했으니 그 config.lua file 을 받으셔서 잘 분석해 보시고 또 실제로 적용해 보시면 이해하시는데 도움이 되실 겁니다.  아직 다운 받지 않으신 분들을 위해서 여기 다운 받으실 링크를 다시 알려 드릴께요.



반응형

'Corona SDK > Corona Doc' 카테고리의 다른 글

Multi-Element Physics Body 다루기  (0) 2013.01.10
내 앱에 애플의 iAds 광고 달기  (0) 2013.01.02
코로나의 Holiday Gifts : Android Push Notification 등 등  (0) 2012.12.29
iOS 에 구글 맵이 돌아왔다. (Corona의 구글 맵 지원 기능 소개)  (2) 2012.12.18
간단하게 Device 분별하는 예제  (0) 2012.12.12
다양한 디바이스 해상도에 맞게 이미지 표시하기 - config.lua file-  (0) 2012.12.05
physics engine (물리엔진)의 새로운 기능 event.contact 소개  (0) 2012.11.29
새 위젯 사용하기 Part 1  (0) 2012.11.08
Blend Modes 사용해서 Creative Effects 내기  (1) 2012.11.01
원근감을 주는 parallax scrolling 구현하기  (0) 2012.10.17
Sprite 로 애니메이션 표현하기와 그 methods 들  (0) 2012.10.11

Comment



Posted on . Written by


오늘의 튜토리얼은 코로나의 physics contact에 대해 소개해 드리려고 합니다. 이 기능은 physics engine 에 최근에 implement 됐습니다. 개략적으로 말하자면 physics contact 는 막 일어나려는 특정 collision (충돌) 을 말하는 겁니다.

개발자들에겐 아마도 physics pre-collision이 친숙할텐데요. 아마도 이 physics contact 하고 뭐가 다른지 의문이 드실 겁니다. physics contact 는 대부분의 경우 pre-collision event 중에 사용됩니다. 그럼에도 이 두가지는 중요한 차이점이 있습니다.



Pre-collision Event


두 object 사이의 pre-collision (or any collision) 이 일어날 경우 Corona는 Lua ID에 의해 각각의 object 에 접근할 수 있도록 합니다. 이 방법을 통해 어떤 object (객체) 에 어떤 action 을 구현할 수 있을 겁니다. 예를 들어 dl pre-collision에서 한 object 를 센서로 바꿔서 다른 object 가 이 object를 통과할 수 있도록 만들 수도 있구요 아니면 둘 중 하나는 터지도록 만들 수도 있을 겁니다.  또는 여기에 force를 더 줄 수도 있고 혹은 다른 많은 action 들을 부여할 수 있습니다. 이 기능은 Corona 에서 오래 전부터 제공해 왔죠. 그런데 여기에는 몇가지 제한이 있었습니다. 그리고 physics contact로 그 제한을 해결할 수 있게 됐습니다.



One-Sided Platforms


2D side-view games 에서 자주 요구되어지는 것 중 하나가 “one-sided platform” 입니다. (수퍼 마리오를 비롯해 다른 수 많은 2D platform games 에서 이 기능이 사용되고 있습니다.) character 가 platform 아래에서 점프를 할 수 있고 이 platform 을 통과 할 수도 있죠. 그리고 그 platform 위에 사뿐히 착지하게 됩니다. 코로나에서는 이전에 점프할 때 character를 sensor 로 바꿔서 해결했습니다. 그리고 그 platform 위에서 character 는 다시 일반적인 object로 전환 됐죠.

대부분의 경우 이 방법으로 해결이 가능했습니다. 그런데 한가지 중요한 문제점이 있었죠. 만약에 enemy “goomba” 가 platform 위에서 걸어가고 있는 경우에는 어떻게 될까요? 이 character 가 그 enemy “goomba” 하고 부딪힐 때도 여전히 sensor 인 상태가 되죠. 그러다가 platform 위로 올라가게 되는 순간 두 object 의 충돌이 인식되서 “bounce” 가 일어나지 않는 등의 문제가 생길 수 있습니다.






Introducing the Physics Contact


위 문제는 새로 도입된 physics contact로 해결 할 수 있습니다. 위에서 언급한대로 특정 reference 에 대해 막 일어나려고 하는 충돌의 경우 알 수가 있습니다. pre-collision event listener 가 사용되고 있는 사이에 physics contact 와 4개의 properties에 접근할 수 있습니다. 이전에는 ㅇ접근할 수 없었던 것이죠.

one-sided platform 예제를 한번 더 생각해 볼까요. character 가 점프하고 아래쪽에서 platform 에 충돌 했을 때 코로나는 character 의 머리와 platform의 밑부분이 충돌하려고 한다는 것을 알 수가 있습니다. physics contact를 사용해서 곧 일어날 temporary access를 알아채서 코로나에게 여러분이 하고 싶은 일을 하라고 얘기할 수 있습니다. 혹은 이것을 완전히 무시하라고 얘기할 수도 있죠.


아래에 pre-collision event listener를 사용해서 어떻게 physics contact에 어떻게 접근하는지에 대한 예제가 있습니다.


function character:preCollision( event )
   print( event.contact ) --"event.contact" is the physics contact "userdata"
   --the following properties of the collision can be accessed; the last three are settable!
   print( event.contact.isTouching ) --read-only: are the two objects touching?
   print( event.contact.isEnabled ) --'true' or 'false'; will the collision resolve?
   print( event.contact.bounce ) --get/set the bounce factor of the collision
   print( event.contact.friction ) --get/set the friction factor of the collision

end
character:addEventListener( "preCollision" )


physics contact 는 collision-specific properties 를 제공합니다. (이전에 traditional listener events 에서는 접근할 수 없었던 것들이죠.)  간단한 condition 로직을 사용하셔서 (if문 같은) 해당 충돌을 완전 무시하거나 선택할 수 있습니다. 혹은 특정 충돌에 대해 bounce/friction factor(s)등에 변화를 줄 수 있죠. 해당 객체에 적용한 bounce/friction  을 override 하시면 됩니다.


one-sided platform 을 위해서는 physics contact 메소드를 사용하는 것이 훨씬 좋습니다. 처음에 플랫폼의 프로퍼티를 “pass through” (or any other term)로 설정하고 그 다음에 conditional logic (if 문 같은)을 사용해서 코로나에게 만약 그 플랫폼이 “passthru” type 이면 해당 충돌을 무시하라고 얘기하실 수 있습니다. 아래 예제에서 처럼요.


local platform = display.newImage( "platform.png" )
platform.collType = "passthru"
physics.addBody( platform, "static", { bounce=0.0, friction=0.3 } )

function character:preCollision( event )

   local collideObject = event.other
   if ( collideObject.collType == "passthru" ) then
      event.contact.isEnabled = false --disable this specific collision!
   end
end

 

physics contact 충돌하는 두 객체의 reference 는 아닙니다. 다만 충돌 그 자체의 reference 입니다.그 충돌을 다시 적용시키기 위해 override 하실 수 있습니다. physics contact properties 를 세팅하면 두 객체 모두에 이 프로퍼티가 계속 적용되는 것이 아닙니다. 코로나는 그 특정 충돌에 대해서만 해당 프로퍼티를 사용할 겁니다.  그리고 나서는 그 프로퍼티를 무시합니다. 예를 들어 physics contact 의 “bounce” factor 를 바꾼다면 두 객체의 core bounce 에 대해서는 set/reset을 하지 않습니다.


코로나에서 physics contact (and its properties)는 어떤 타입의 충돌 리스너에서든지 가리지 않고 사용할 수 있습니다.(pre-, post-, or standard). 대부분의 경우 pre-collision를 많이 사용하죠. 왜냐하면 프로퍼티에 접근하고 세팅하고 하는 작업은 그 충돌이 일어나기 전에 필요한 거니까요.




Other Possibilities


physics contact 를 사용해야 하는  one-sided platform 이외의 경우를 한번 볼까요. 핀볼 게임 아시죠? 이 게임을 위해서 아래와 같은 conditional logic(if문 등)을 사용하실 수 있을 겁니다.


  • Silver balls collide with red bumper for extreme bounce (set physics contact bounce to 1)
  • Silver balls collide with blue bumper for no bounce (set physics contact bounce to 0)
  • Gold balls collide with red bumper for no bounce (opposite of the first case)
  • Gold balls collide with blue bumper for medium bounce



See it in Action


“one-sided platform” method를 사용하신 경우롤 보시려면 demo project를 다운 받아서 참조하세요.






In Summary…


새로만든 physics contact는 네가지 새로운 프로퍼티들을 제공합니다.


  • event.contact.isTouching — read only: are the two objects touching?
  • event.contact.isEnabled — read/write: ‘true’ or ‘false’; should the collision resolve?
  • event.contact.bounce — read/write: get or set the bounce factor of the collision
  • event.contact.friction — read/write: get or set the friction factor of the collision


특정 상황에서 이 프로퍼티들은 기본적인 충돌 이벤트와 충돌 필터들을 사용해서 이전에는 구현할 수 없었던 정교한 콘트롤을 할 수 있도록 해 줍니다. 이 프로퍼티들을 다음 physics-based project에서 사용해 보세요.


질문이나 의견이 있으시면 저 위 본문 시작하는 제목에 걸린 링크를 클릭하세요.

원문페이지기 있는데요. 거기다가 댓글로 질문 하시면 됩니다.


영어로 하셔야 되는데요.


영어가 어려우시면 이 글 밑에 댓글로 달아 주세요.

제가 여건이 되는대로 대신 질문 전달하고 답변 받으면 이 블로그에 공유해 드릴께요.



반응형

Comment

새 위젯 사용하기 Part 1

2012. 11. 8. 04:44 | Posted by 솔웅



Posted on . Written by


만약 여러분이 Corona Indie 나 Pro 의 등록자라면 daily builds 를 다운 받아서 사용하실 수 있으실 겁니다. 그리고 Build 947 에 new widgets 들이 포함된 내용도 전달 받으셨을 겁니다. 이 포함된 widget들은 아래 사항들을 포함하고 있습니다.


  • switch — in the form of a radio button, checkbox, or “on/off” slider.
  • segmented control — a “segmented button” in which each segment responds individually.


이 새 위젯들은 일반적인 특성을 따르고 있습니다. 각 위젯들은 새로운 widget library foundation 을 바탕으로 추가 돼 좀 더 flexible 하고 essentially 합니다.


Patience Please!



이 새 위젯들은 widget library 의 개선해 나가는 것들 중의 일부입니다. 더 많은 widget들이 다음주에 더 새로운 모습으로 소개 될 겁니다. 그 각각에 대해 cover 할 수 있도록 tutorial 을 작성해 드릴겁니다. 지난 widget들과 비교하면 거의 새로운 기반하에서 새롭게 작성되어야 할 정도로 많은 변화가 있었습니다. 여러분들은 아직까지 이전 위젯들을 사용하고 계실 건데요. 이전 위젯들에 대해서는 중요한 버그들에 대한 수정만 해 나갈 것이고 그 기능에 대한 개선이나 변경은 없을 겁니다. 이번주 다음주에 발표할 새로운 위젯들이 기존의 위젯들을 대신해 나가도록 진행할 계획입니다.


이 글은 native iOS visual theme 에 대해 설명드립니다. Android theme 은 다음번 daily build 에서 추가될 겁니다. 물론 굳이 이 주어진 visual theme만 사용할 수 있는 건 아닙니다. 여러분들 나람대로 스타일링해서 위젯들을 customizing 해서 사용하실 수 있습니다.



Getting Started…


새 위젯들을 경험하기 위해 우선 새로운 project 부터 만듭니다.



1. “require” the widget library


Just like with many Corona libraries, you’ll need to “require” the widget library to use widgets:

다른 많은 Corona library들과 같이 widget을 사용하기 위해서는 widget library 를 require 합니다.


local widget = require( "widget" )

2. Copy theme file and assets



모든 widget들은 디폴트로 Lua file 이름에서 theme 정보를 가져옵니다. theme_ios.lua or (coming soon) theme_android.lua 가 그 파일 이름입니다. 이미지 파일들은 이 파일에서 정한 폴더에서 가져오게 됩니다. 위젯들은 custom theme file 을 세팅함으로서 스타일링할 수 있고 이미지 세트들도 생성할 수 있습니다. 이런 부분들은 약간 고급 기술 부분인데요. 나중에 이런 고급 기술 부분에 대해서 따로 다루도록 하겠습니다.


일단 default theme file과 asset folder를 여러분의 main project 디렉토리에 카피해 넣으세요. 이름은 theme_ios.lua and widget_ios가 될 겁니다.  이 파일들은 아래 경로로 가면 찾을 수 있습니다. 또는 이곳에서 다운로드 받으실 수도 있습니다.



CoronaSDK > SampleCode > Interface > WidgetDemo




3. Declare the theme using “setTheme()”


widget library 를 require 한 후 그 다음줄에 theme 을 정의해야 합니다.

아래 예제를 참조하세요. 처음부터 새로 만드실 필요는 없고 샘플로 드린 파일을

수정하셔서 사용하시면 편리하실 겁니다.


widget.setTheme( "theme_ios" )

NOTE:  Do not  append .lua to the reference passed to widget.setTheme().



Widget #1: Switch


첫번쨰로 선보일 새로운 widget 은 switch widget 입니다. 이 위젯은 세가지 종류가 있습니다.
checkbox, radio button, or on/off slider




Event Listener



이 위젯에 유저가 어떤 행위를 했을 때 이를 감지할 basic listener function를 생성하셔야 합니다. 이 섹션에서 사용할 샘플 리스너를 한들어 보죠.

아래 예제를 여러분 파일에 복사해 넣으세요.


local function onSwitchPress( event )
   local switch = event.target --event.target references the switch object
   local response = switch.id.." is on: "..tostring( switch.isOn )
   print( response )
   switch.text:setText( tostring( switch.isOn ) )
end


switch의 현 상태는 .isOn parameter에 의해 참조됩니다. 여러분은 이 프로퍼티를 사용해서 현재의 switch 상태를 파악할 수 있습니다. true 이면 selected 나 on 인 상태이구요. false 이면 off 나 deselected 상태입니다.



Checkbox



checkbox switch widget을 생성하려면 아래와 같이 하세요.


local checkbox = widget.newSwitch
{
   left = 60,
   top = 230,
   style = "checkbox",
   id = "My checkbox widget",
   initialSwitchState = false,
   onPress = onSwitchPress
}

-- Text to show the on/off switch state
checkbox.text = display.newEmbossedText( tostring( checkbox.isOn ), 0, 0, native.systemFontBold, 18 )
checkbox.text.x = checkbox.x
checkbox.text.y = checkbox.y - checkbox.text.contentHeight



보시다시피 실제 widget은 widget.newSwitch command를 사용해서 생성합니다. 그리고 디테일한 내용은 “options” table에 있습니다. 이 예제에서는 아래 프로퍼티들이 콤마로 구분돼 있습니다.


  • left and top (optional) — the left and top position of the switch. You can also position the switch normally by setting its x and y position afterward.
  • style — for this example, set it to checkbox.
  • id (optional) — this property allows you to set a named reference for the widget. This is useful if you want to use the same event listener function for several switches and identify a specific one by its .id property within the listener function.
  • initialSwitchState (optional) — true = on/selected; false = off/deselected.  Default is false.
  • onPress (optional) — listener function called when the switch is touched.



Radio Button


radio button
switch는 checkbox widget 이랑 거의 같은 방식으로 생성합니다. 다른 부분은 style parameter를 radio로 세팅하는 부분입니다. 위에 on-off 를 테스트하기 위해 만들었던 리스너 함수를 복사해서 여기서 사용하시면 됩니다.


local radioButton = widget.newSwitch
{
   left = 150,
   top = 230,
   style = "radio",
   id = "My radio button widget",
   initialSwitchState = false,
   onPress = onSwitchPress
}

-- Text to show the on/off switch state
radioButton.text = display.newEmbossedText( tostring( radioButton.isOn ), 0, 0, native.systemFontBold, 18 )
radioButton.text.x = radioButton.x
radioButton.text.y = radioButton.y - radioButton.text.contentHeight


 


On/Off Switch


on/off switch는 작은 “flick” switch 입니다. 일반 전등 스위치같은 역할을 하죠. 디폴트 switch widget 입니다. 셋업하는 부분은 switch widget 과 비슷한데요. 몇가지 key 가 다르죠. 기본 구조는 아래와 같습니다.


local onOffSwitch = widget.newSwitch
{
   left = 250,
   top = 230,
   id = "My on/off switch widget",
   initialSwitchState = true,
   onRelease = onSwitchPress
}


가장 많이 다른 부분은 event listener function 인데요. onPress parameter가 아닌 onRelease parameter 로 분류 됩니다.  이 경우 touch 가 released 되면 “release” event가 발생합니다.


Widget #2: Segmented Control


segmented control 은  multi-segment 버튼을 쉽게 만들 수 있도록 도와줍니다. 각 segment 별로 on/off 상태가 될 수 있고 그에 따라 관련된 값이 return 됩니다.



Event Listener


switch widget 처럼 segment control 에 대ㅐ 유저의 action 을 report 하기 위해 basic listener function을 생성해야 됩니다.


local function onControlPress( event )
   local target = event.target
   print( "Segment Label is:", target.segmentLabel )
   print( "Segment Number is:", target.segmentNumber )
end

 

이 segmented control 은 리스너에게 두가지 essential 값을 전달합니다. 유저가 touch 한 segment 의  segment labelsegment number 가 그 값들입니다.


Basic Segmented Control


segmented control 은 아주 간단합니다. 아래 예제를 보세요


local segmentedControl = widget.newSegmentedControl
{
   left = 65,
   top = 110,
   segments = { "s1", "s2", "s3", "s4" },
   segmentWidth = 50,
   defaultSegment = 4,
   onPress = onControlPress
}

  
  • left and top (optional) — the left and top position of the control. You can also position the control normally by setting its x and y position afterward.
  • segments — sub-table in which you define the segments; it determines the total count and the label that appears on each. Simply define them as comma-separated strings.
  • segmentWidth (optional) — the pixel width of each segment. Default is 50 pixels.
  • defaultSegment (optional) — this property allows you to set the segment that is “on” when the control is rendered. Specify a number between 1 and the total number of segments. If you don’t specify this property, it defaults to the first segment.
  • onPress (optional) — listener function called when the control is touched.


More to Come!


다른 훌륭한 widgets들이 이어서 선보입니다. 다음주 daily builds 가 릴리즈 될 때 선보일 예정인데요. 다음주 화요일의 Tutorial 을 통해서 그 위젯들에 대한 설명을 드리겠습니다. 그 동안 최신 daily build를 다운 받으시고 위 3개 widget들에 대해 익혀 두세요. daily builds 는 유료 등록하신 분들만 다운받으실 수 있습니다. 코로나의 최신 기술을 그 때 그 때 사용하고 싶으신 분은 지금 유료등록을 해 주세요.










반응형

Comment


Posted on

. Written by


할로윈 시즌 입니다. 오늘은 코로나의 blend modes  에 대해서 다루겠습니다. 그리고 이것이 어떻게 앱 내에서 창조적인 비쥬얼 효과를 내는데 사용될 수 있는지에 대해서도 알아보구요. 대부분의 Corona API들이 그렇지만 이런 효과도 간단한 코딩에 의해서 구현할 수 있습니다. Blend mode는 다양한 시각 효과에서 아주 유용하게 사용할 수 있습니다. 그리고 포토샵 같은 데서나 낼 수 있었던 그런 효과들을 내고 싶은 디자이너들도 활용하실 수 있습니다. blend mode가 무엇인지 모르시는 분들은 이 글을 끝까지 읽으세요. 이 글에서는 두개의 Halloween-theme image들을 사용할 겁니다. spooky swamp 와 wisps요. 이 글을 그대로 따라하면서 배우고 싶으신 분들은 여기에서 이미지를 다운 받아서 사용하세요. (제가 만든거니까요 다른데에 배포하지는 말아주세요.)




Layers and Pixels


다른 그래픽 어플리케이션과 개발 언어와 마찬가지로 코로나에도 layers 가 있어서 back에서 front로 z-index 순으로 비쥬얼 객체들을 arrange 합니다. 레이어들은 코로나에서는 display groups를 말하고 용법은 같습니다.





보시는 바대로 위 레이어에 있는 객체(wisps) 의 모든 픽셀들은 그 아래 있는 레이어의 객체 앞에 overlap 돼 있습니다. 그리고 그 픽셀들이 렌더링되서 뒤의것과 잘 조화 돼 있죠.  이것은 Painter's Algorithm 이라는 개념입니다. 실제 캔버스에 그림을 그린다고 보면 붓질을 하면 물감은 이전 물감의 위에 덧칠되게 됩니다. 이것이 Painter's Algorithm 입니다.


About Visual “Blending”


위의 예와 같은 기초적인 layering 도 유용하고 필요한 기술이긴 하지만 creative visual effect를 구현하기에는 한계가 있습니다. 그래서 대부분의 그래픽 애플리케이션은 (코로나도 마찬가지구요.) 레이어들에 blending 이라는 사용할 수 있도록 허용하고 있습니다.


core level에서 보면 blending 은 수학적인 알고리즘을 이용해서 여러 레이어들에 걸쳐 픽셀들이 overlapping 돼 서로 combine (blend) 하도록 하는 메소드 입니다. 단지 위에 오는 레이어의 픽셀들이 밑의 픽셀들에 overlapping 되서 밑의 것을 안 보이도록 하는  것이 아니라 blend를 사용하면 multiple pixels 들이 한꺼번에 보이게 됩니다.



현실세계를 상상해 보시죠. Painter’s Algorithm analogy를 사용하면 붓의 터치가 가해지면 밑의 색에 덧칠 됩니다. 또 그 색에 붓질을 하게 되면 마찬가지로 덧칠을 하게 되죠. blend 도 마찬가지로 digital painting canvas 에서 똑 같은 일을 하는 겁니다. 다만 실제 세계의 캔버스에서와는 다르게 표시되게 되죠.


디지털 환경에서 생각해 보면 blending은 3원색-red, green, and blue (RGB)- 모두에 적용 됩니다. 이 각각의 색을 channel 이라고 하는데요. 더 자세히는 다루지 못하지만 각 channel 마다 0 에서 255 까지의 숫자 범위가 있다는 것은 알아 두시기 바랍니다. 0 은 none 을 의미하고 255는 maximum을 의미합니다. 얼마나 이런 channel 들이 mix 되느냐에 따라서 최종 색이 결정 되는 겁니다. RGB 값 0,0,0 은 검은색이고 255,255,255 는 흰색입니다. 이 RGB에 대해 더 자세히 알고 싶으시면 여기를 참조하세요.


이 튜토리얼을 보시려면 각 color channel 마다 0-255 의 숫자 범위가 있다는 것만 아시면 됩니다. 이 글에서는 blend 가 어떻게 각 channel의 숫자들을 다루고 그 픽셀의 최종색을 만드는지를 설명 드릴 겁니다.



object.blendMode


코로나에서 객체에 blend mode를 apply 하려면 blendMode parameter에 add, multiply, screen, or normal 중 하나의 값을 대입시키면 됩니다.


예를 들어



wisps.blendMode = "add"



“normal” blend mode (default) 는 그냥 blend mode를 사용하지 않는다는 의미입니다. 그 외에 3개의 blend mode에 대해서는 아래에서 설명 하겠습니다.

한가지 기억하셔야 할 것은 반드시 blend mode는 per-object basis로 apply 해야 한다는 것입니다. object:setFillColor() 와 비슷합니다. blend mode를 전체 display group에 한번에 적용할 수는 없습니다. 하나의 display group 에 다 같은 blend mode를 적용하시려면 display group 의 child 만큼 loop를 돌려서 각각의 아이템마다 blend mode를 별도로 적용시켜야 합니다.


Blend Mode “add”


add blend mode 는 불이나 레이저, exaggerated 같은 glowing visual effects를 주는데 효과적입니다. 대부분의 그래픽 애플리케이션에서는 linear dodge로 알려져 있기도 합니다. 하는 일은 단순하게 모든 레이어에 걸쳐서 각 color channel 의 값을 adds 하는 겁니다.





이제 테크니컬하게 들어가 볼까요! 객체에 부여된 RGB 값들은 아래와 같습니다.


background:      ( 34, 34, 44 )
red circle:      ( 255, 51, 51 )
green circle:    ( 0, 153, 51 )
blue rectangle:  ( 102, 153, 204 )



With each object set to blendMode=“add”, Corona’s OpenGL engine simply adds the channel values for each pixel.

각 객체마다 blendMode=“add”로 세팅 돼 있습니다. 코로나의 OpenGL 엔진은 단순히 각 픽셀의 channel 값을 더할 겁니다.


산수를 한번 해 보죠. 빨간 원과 배경이 합쳐지는 부분에 대해 계산을 합니다.



R: 34 + 255 = 289
G: 34 + 51  = 85
B: 44 + 51  = 95



R (red) 값은 255를 넘었네요. (maximum 값이 255 잖아요). 이럴 경우는 그냥 255로 됩니다. 그러니까 최종 RGB 값은 (255,85,95) 가 되죠. 원래 빨간색의 RGB 값은 (255,51,51) 였죠. 두 이미지에서 빨간 원 부분을 보세요. blend 가 적용된 부분의 좀 더 밝아 졌죠.


다음은 좀 더 복잡한 부분을 볼까요. 빨간색과 녹색 원이 겹쳐지는 부분입니다. 그리고 파란 네모가 겹쳐진 부분은 제외하구요.



R: 34 + 255 + 0   = 289
G: 34 + 51  + 153 = 238
B: 44 + 51  + 51  = 146



이 경우 background 까지 합해서 각 channel 의 값들의 합을 구합니다. 255 가 넘는 겂이 있으면 그냥 255로 정해 지는 거구요. 그러면 최종값은 (255,238,146)이 됩니다.


add blend를 사용하면 우리의 Halloween scene 은 어떻게 될 까요? wisps 에 blendMode=“add”를 적용하면 좀 더 creative 한 효과를 볼 수 있습니다. 하얀 wisps 부분은 눈부시게 빛나는 것 같고 어두운 wisps 부분도 (원래 보라색이었던 부분) 약간 빛나면서 번져 보입니다.




여기에는 없지만 만약 black pixels 들이 있다면 그 픽셀들은 완전 투명하게 될 겁니다. 왜 그러냐구요? 검은색의 RGB 값은 (0,0,0) 이거든요. 그러니까 픽셀에 아무 값도 더해지지 않게 됩니다. 그러면 당연히 밑의 색이 그대로 보이겠죠. 그러니까 검은색은 투명한 효과가 나게 되는 겁니다.

요약하자면 add blend mode는 각 color channel 의 값을 더해 주는 겁니다. 더해진 값이 255가 넘으면 그냥 255로 정해지는 거구요. 그러니까 모든 색이 더 밝아 지겠죠. additive blending 은 그래서 레이저, 불, flash 들 같은 밝고 colorful 한 효과를 내는데 좋습니다.


IMPORTANT NOTE:  저 위에서 계산하는 거 보셨죠? 여러분이 프로그래밍할 때 저 계산을 할 필요는 없습니다. 코로나에서 해 주니까요. 여러분은 그냥 어떤 blend 효과를 사용할 지만 정하시면 됩니다.



Blend Mode “multiply”


이 이름만 보고는 딱 와 닿지 않지만 multiply blend mode는 사실 좀 더 어둡게 만드는 효과가 필요할 때 사용하시면 됩니다. 예를 들어 그림자 효과 같은 것을 넣을 때 좋죠.


color channel value들을 더하는 대신에 multiply blend mode는 (짐작하시겠지만) 그 값들을 multiply (곱하기) 합니다. 이렇게 하면 RGB channel 값이 65025 (255*255) 같이 아주 높아지겠죠. add blend mode 였다면 모두 255(하얀색)이 될 겁니다. 그런데 왜 multiply blend 는 좀 더 어둡게 될 까요? 왜냐하면 곱한 값을 다시 255로 나누거든요.

아래 그 공식을 보겠습니다.




resulting color = ( top color * bottom color ) / 255




다시 각 부분의 RGB 값을 볼까요.


background:      ( 34, 34, 44 )
red circle:      ( 255, 51, 51 )
green circle:    ( 0, 153, 51 )
blue rectangle:  ( 102, 153, 204 )



빨간 원의 왼쪽 부분을 계산해 봅시다. (잘 보세요. 좀 더 어두워 질 거예요.)



R: ( 34 * 255 ) / 255 = 34
G: ( 34 * 51 )  / 255 = 6.8
B: ( 44 * 51 )  / 255 = 8.8




먼저 곱한 다음에 그 값을 다시 255로 나누기 때문에 결과값이 낮은 겁니다. 그러면 더 어두운 색이 되겠죠. 검은색은 어떻게 되는지도 보셔야 할 겁니다. 검은색은 그 값이 0 이죠. 0에 무슨 값을 곱해도 0 이 되죠. 그래서 black 인 부분은 항상 black 이 됩니다.

아래 이미지는 Halloween 이미지들에 blendMode=“multiply” 값을 세팅한 결과 입니다.





보시다시피 wisps 가 있는 부분들은 더 어두워 졌죠? 이 효과는 이런 Halloween 에 맞는 효과는 아니네요. 그냥 multiply blend 가 어떻게 효과를 내는지 이해하는데만 사용하세요. 흰색(255) 부분은 어떻게 됐을 까요. 255에 무엇을 곱하더라도 다시 255로 나누면 그냥 그 색이 되겠죠? 그러니까 이 효과에서는 흰색은 투명한 효과가 납니다. 그냥 밑에 있는 색이 그대로 보이는 거죠.


Blend Mode “screen”


screen blend mode 도 add blend mode 와 마찬가지로 좀 더 밝게 만들어 주는데요. 다만 아주 어두운 부분은 오히려 lightening 하지 않습니다. 이 blend mode의 algorithm은 좀 복잡한데요. channel 값들은 마이너스를 하고 곱하기를 하고 그리고 다시 마이너스를 하고 뭐 그렇습니다.

아래 그 공식이 있습니다.



resulting color = 255 - ( ( ( 255 - top color ) * ( 255 - bottom color ) ) / 255 )



multiply blend 의 반대 효과를 주어서 밝게 만듭니다. 그러니까 add blend 와 비슷한 효과를 주기는 하지만 아주 똑 같지는 않구요.





이 screen blend 의 공식은 복잡합니다. 다행히 여러분은 이런 계산을 하지 않으셔도 됩니다. 단지 여러분은 Halloween 장면에 어떤게 더 효과적인지만 정하시면 됩니다.






이것도 효과가 괜찮네요. 이 효과의 특징은 additive blend 와 비슷하지만 그것보다 살짝 연하다는 겁니다. wisps 의 어두운 부분들은 아주 연하게 표현이 됐습니다. 이렇게 어두운 부분은 additive blend 가 좀 더 낫지 않나 싶네요.


In Summary


코로나에서의 Blend modes는 특정 상황에서는 아주 powerful 한 tool 이 될 수 있습니다. 이 기능을 적용하는 것도 아주 간단합니다. (단지 한 줄만 코딩해 넣으면 됩니다.)  코로나의 그래픽 엔진이 모든 계산을 다 해 줄 겁니다. 어떤 blend mode를 사용할 까는 여러분 앱의 scene 에 어떤 효과를 넣어야 할지 여러분이 결정하시면 됩니다.  질문이 있으신 분들은 여기 에 댓글로 질문해 주세요. Happy Halloween!



반응형

Comment

  1. 전준완 2013.12.02 07:11

    정말 도움 되었습니다 고맙습니다.


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 를 구현하고 수정하고 테스트 해 보시고 질문이나 제안이 있으면 댓글 달아 주세요.



반응형

Comment


Posted on . Written by


화요일의 Tutorial이 다시 돌아왔습니다. 오늘의 튜토리얼은

Brent Sorrentino 가 작성한 건데요. Northern Colorado 에서 Corona Ambassador 로 활동하고 있습니다. Brent는 2년여 코로나 커뮤니팅서 활발히 활동하고 있습니다. 그는 프리랜서 여행사진작가이고 코로나 개발자이며 그래픽 디자이너 입니다. 그만의 앱을 개발하기 위해 코로나를 사용하고 있습니다. 또한 정기적으로 포럼에서 여러사람에게 도움을 주고 있고 여러 이슈들을 해결해 주고 있습니다 그의 웹사이트를 보시려면 여기를 클릭하세요.



오늘의 튜토리얼은 어떻게 animated sprite를 implement 할 것인가와 그와 관련된 API 들에 관한 겁니다. 아마 이전 튜토리얼에서 다룬 내용도 있을 겁니다. 올해 초 현재의 스프라이트 시스템이 소개 된 이후 몇가지 주요 개선사항이 있었습니다. 그리고 많은 개발자들이 아직도 코로나에서 어떻게 sprite를 implement 하는지에 대해 혼동을 하고 있습니다. 이 sprite를 사용하는 방법에는 두가지가 있습니다.


  1. The old (and now depreciated) sprite.* library.
  2. The current sprite APIs that work cohesively with image sheets and the display.* library.


이렇게 코로나의 스프라이트 라이브러리가 변화를 보였는데요. 그 중에서 현재 버전을 사용할 것을 강력히 추천합니다. 현재 방법을 사용하지 않았거나 코로나에서 스프라이트를 한번도 사용해 보지 않았다면 이 튜토리얼이 많이 도움이 될 겁니다.


코로나의 basic animation에 이미 익숙하신 분이라면 이 튜토리얼이 sprite event listeners에 대한 모든 정보를 제공하고 어떻게 implement 할 것인지를 가이드 해 드릴 겁니다.


Tutorial Contents

  1. Configuring a Basic Image Sheet or “Sprite Sheet”
  2. Defining Animation Sequences
  3. Declaring a Sprite Object
  4. Sprite Control Methods — managing playback and sequences
  5. Sprite Properties — accessing and setting various sprite properties
  6. Sprite Event Listeners — detecting sequence events such as “ended”, “loop”, and “next”
  7. Frame Trimming Support


Configuring a Basic Image Sheet or “Sprite Sheet”



정확히 image sheet 이 뭘까요? 한번 상상해 보세요. 여러분들의 animated object 들을 위해 각각 의 프레임들을 그린 한장의 종이가 있다고요. 코로나에서 다른 전문적인 용어로는 texture atlas, image map, or sprite sheet 라고 합니다. 이걸 그냥 간단히 image sheet라고 부를께요. 이것은 static 이든 animated object 이든지 상관없이 활용할 수 있습니다.

graphics.newImageSheet() API에 대한 자세한 사용법과 샘플들은 여기에 있습니다. 이 튜토리얼에서는  그중에서 어떻게 animated sprite를 위해 image sheets를 사용할지에 대해 다루겠습니다.



아래 그림은 running cat 을 표현하는 샘플 image sheet 입니다. 이 튜토리얼을 공부하면서 이 이미지를 사용하시려면 여기에 hi-resolution version 이미지가 있습니다. 이 sheet 에는 8개의 프레임이 순서대로 있습니다. 디폴트로 애니메이션은 top-left frame 부터 시작해서 오른쪽으로 진행하죠. 오른쪽 끝까지 가면 그 다음 줄로 갑니다. 그리고 전체 프레임을 다 돌면 중지합니다.






코로나에서 이 image sheet 을 어떻게 다루는지 보겠습니다. 우선 uniform-sized frame 인 basic image sheet 를 위해 indexed table을 setup 합니다.

local sheetData = {
  width = 512, --the width of each frame
  height = 256, --the height of each frame
  numFrames = 8, --the total number of frames on the sheet
  sheetContentWidth = 1024, --the total width of the image sheet (see note below)

  sheetContentHeight = 1024 --the total height of the image sheet (see note below)

}


IMPORTANT:  sheetContentWidthsheetContentHeightoverall 1:1 dimensions of the image sheet (전체 가로 세로)를 나타냅니다.1:1 content scale 은 여러분이 앱의 config.lua file에 set up 한 내용을 기준으로 합니다.  이렇게 하면 다른 디바이스별로 다른 이미지를 사용할 수 있도록 합니다. 예를 들어 @1 sheet는 original iPad 에 맞는 이미지를 사용하고 high-resolution @2 sheet 는 Retina iPad 에 맞는 이미지를 사용할 수 있습니다. 그러니까 1:1 sheet이 1024×1024 pixels 이라면 2:1 sheet 는 2048×2048 인 이미지가 되는거죠. image sheet setup에는 항상 1:1 dimensions를 사용하세요. config.lua file 세팅이 정확하다면 그 다음부터는 코로나가 알아서 처리할 겁니다.



이제 실제 image sheet 를 정의하세요. 괄호 안에 image file 과 위에 정의한 data table을 넣어주시면 됩니다. 아래처럼 파일이름만 넣으면 그 이미지 파일을 여러분 프로젝트 디렉토리의 root 에서 찾을 겁니다. 


local mySheet = graphics.newImageSheet( "runningcat.png", sheetData )


Defining Animation Sequences


여러분은 두가지 방법으로 animated frame 순서를 정하실 수 있습니다.

  • consecutively using a starting frame index and frame count
  • non-consecutively using a specific order of frames


이 두 가지 방법으로 코로나에서 sprite system 을 아주 flexible 하게 사용하실 수 있습니다. 하나의 image sheet 으로 여러 animation sequences를 사용해서 활용할 수가 있죠. 두 경우 모두 sub-table에서 comma-separated array 로 sequences를 define 하시면 됩니다.


Consecutive frames


local sequenceData = {
  { name = "normalRun", --name of animation sequence

    start = 1, --starting frame index

    count = 8, --total number of frames to animate consecutively before stopping or looping

    time = 800, --optional, in milliseconds; if not supplied, the sprite is frame-based

    loopCount = 0, --optional. 0 (default) repeats forever; a positive integer specifies the number of loops

    loopDirection = "forward" --optional, either "forward" (default) or "bounce" which will play forward then backwards through the sequence of frames

  } --if defining more sequences, place a comma here and proceed to the next sequence sub-table


}



Non-consecutive frames


local
sequenceData = {
  { name = "fastRun",
    frames = { 1,2,4,5,6,7 }, --specific order of frame indexes from the image sheet

    time = 250,
    loopCount = 0
  } --if defining more sequences, place a comma here and proceed to the next sequence sub-table

}

Mixed sequences (both consecutive and non-consecutive sequences)


local
sequenceData = {
{ name="normalRun", start=1, count=8, time=800 },
  { name="fastRun", frames={ 1,2,4,5,6,7 }, time=250, loopCount=0 }

}

 

Declaring the Sprite Object


현재의 sprite method를 사용하려면 display.newSprite() API 로 sprite를 선언하셔야 합니다. 신택스는 간단합니다.


display.newSprite( [parent,] imageSheet, sequenceData )
  • parent = The parent display group in which to insert the sprite (optional).
  • imageSheet = The image sheet which the sprite should utilize.
  • sequenceData = The array of animation sequences which you set up. The sprite will default to the first sequence in the array unless you specify otherwise (see Sprite Control Methods below).


이 튜토리얼에 나온 예제로 한다면 sprite 선언은 아래와 같을 겁니다.

local animation = display.newSprite( mySheet, sequenceData )


이제 이 sprite 는 display object가 됐습니다. 그냥 일반 static image, vector objects 같은 것들과 같게 됐죠. 이제 이 sprite 는 움직일 수도 있고 manipulated 될 수도 있고 physics body 를 입힐 수도 있고... 등등을 할 수 있습니다. 이 sprite object 를 remove 시키려면object:removeSelf()display.remove( object ) 를 사용하시면 됩니다. remove 한 다음에 nil로 세팅하는 것을 잊지 마세요.


Sprite Control Methods


이 스프라이트 시스템은 4가지 주요 control methods를 제공합니다. 여러분들은 이것으로 스프라이트의 playback과 sequence 를 control 하실 수 있습니다.


  • animation:play()
    Start the animation playing. Animations do not begin playing when you create them — you must start each animation manually using this command.
  • animation:pause()
    Pauses the animation. There is no “stop” control method; instead, pause the animation using this method.
  • animation:setFrame( frame )


    Immediately set or skip to the indicated frame index. If you want to “stop and reset” an animation sometime after you have started playing it, use the :pause() and :setFrame( frame ) commands consecutively, setting the frame back to the beginning of the sequence.
  • animation:setSequence( sequence )


    Set the sprite to a specific sequence that you declared in your sequence array. For example, if you want to change your cat animation from “normalRun” to “fastRun”, you would use animation:setSequence( “fastRun” ) and use animation:play() to begin playing it, since the animation will not play automatically after you change the sequence.


Putting It Together


전체 애니메이션을 함께 넣어보죠. 달리는 고양이를 normalRun 과 fastRun 두가지로 셋업할 겁니다. 아래 예제가 있습니다.

local sheetData = { width=512, height=256, numFrames=8,

sheetContentWidth=1024, sheetContentHeight=1024 }

local mySheet = graphics.newImageSheet( "runningcat.png", sheetData )

local sequenceData = {
  { name = "normalRun", start=1, count=8, time=800 },
  { name = "fastRun", frames={ 1,2,4,5,6,7 }, time=250 }
}



local animation = display.newSprite( mySheet, sequenceData )
animation.x = display.contentWidth/2 --center the sprite horizontally
animation.y = display.contentHeight/2 --center the sprite vertically

animation:play()





코로나 시뮬레이터로 테스트 해 보세요. 저 고양이가 화면 중앙에 나올겁니다. 그리고 normalRun sequence로 animating 되겠죠. (sequence를 따로 선언하지 않으면 위에 얘기했던 디폴트 순수대로 진행합니다.)
테스트를 위해 animation:play()앞에 animation:setSequence( “fastRun” )를 넣어 보세요.


Sprite Properties


Corona provides several properties which can yield information about existing sprites. You can even modify the timeScale (relative speed) of a particular sprite. These properties are as follows:

코로나에는 현재의 스프라이트에 적용할 수 있는 몇가지 프로퍼티들을 제공합니다. 특정 스프라이트의 timeScale (relative speed)를 modify 할 수도 있습니다. 아래 프로퍼티들을


  • object.frame
    A read-only property that represents the currently shown frame index of the loaded sequence. This does not set the frame — use the :setFrame() command to explicitly set an animation frame.
  • object.isPlaying
    Returns true if the animation is currently playing; false if it is not.
  • object.numFrames
    A read-only property that represents the number of frames in currently loaded sequence.
  • object.sequence
    A read-only property that returns the name of the currently playing sequence.
  • object.timeScale
    Gets or sets the scale to be applied to the animation time. This is used to control a sprite’s animation speed dynamically. For example, a time scale of 1.0 (default) runs the animation at normal speed. A time scale of 2.0 runs the animation twice as fast. A time scale of 0.5 runs the animation at half speed. The maximum allowed value is 20.0 and the minimum allowed value is 0.05. The value supports up to 2 decimal places.


Sprite Event Listeners


이제 basic sprite 선언과 두개의 sequences (“normalRun” , “fastRun”) 가 생겼습니다. 

이제 sprite event listener를 살펴보죠. 그리고 그것을 어떻게 implement 하는지에 대해 알아보겠습니다. sprite event listener 의 정의는 'sprite의 activity를 "listens" 하고 그 정보를 listener function에 전달하는 것' 입니다.


예를 들어 여러분의 달리는 고양이를 "normalRun" sequence로 4번(4 cycles)를 돌게 한 다음에 "fastRun" sequence로 바꿀 수 있습니다. 이것은 standard timer 로 표현하기에는 불가능한 효과죠. 그래서  그 대신에 sprite event listener를 implement 하는 겁니다.



예제를 보기 전에 sprite에서 사용 가능한 5가지 를 event phases보겠습니다.

이 phases 들은 최근의 코로나 Public Release 인 (2012.894) 버전 이후부터 사용하실 수 있습니다.


  • began = The sprite has started playing.
  • ended = The sprite has finished playing.
  • bounce = The sprite has bounced from forward to backward while playing.
  • loop = The sprite has looped to the beginning of the sequence.
  • next = The sprite has played a subsequent frame that’s not one of the above phases.


이 phases 를 어떻게 listen 할지에 대해 다루겠습니다. 근데 우선 달리는 고양이에 loopCount = 4를 추가해서 normalRun 의 sequence를 바꾸고 시작하도록 하죠. 이렇게 하면 4번의 loop가 끝나면 ended phase를 받을 수 있도록 해 줍니다.


local sequenceData = {
  { name = "normalRun", start=1, count=8, time=800, loopCount=4 }, --add loopCount=4

  { name = "fastRun", frames={ 1,2,4,5,6,7 }, time=250 }
}

 

Now, let’s write the listener function and add the actual event listener to the running cat. You can place this at the end of your sample code, after the sequences are declared and the sprite object placed on the screen.

이제 listener function을 만들어 봅시다. 그리고 달리는 고양이에 실제 event listener를 달아보죠. 아래 내용을 위 샘플 코드의 마지막 부분에 sequence 가 선언된 다음에 추가하시면 됩니다.


local function mySpriteListener( event )
if ( event.phase == "ended" ) then
    local thisSprite = event.target --"event.target" references the sprite
    thisSprite:setSequence( "fastRun" ) --switch to "fastRun" sequence
    thisSprite:play() --play the new sequence; it won't play automatically!
  end

end

animation:addEventListener( "sprite", mySpriteListener ) --add a sprite listener to your sprite

 
sprite listener 가 이제 모든 phases를 listener 함수에 전달하게 됩니다. if-then 구문을 사용해서 이 phases를 사용하는 것은 개발자가 코딩해야 할 부분입니다. 특정 시점에 우리는 ended phase를 listen 하게 됩니다. sequence의 loopCount parameter에 의해 4번의 루프가 끝나면 이 ended phase가 발생하도록 했죠. 이 ended phase 를 받으면 이 cat animation을 fastRun sequence로 바꾸고 play 하게 됩니다.

하나의 sprite listener를 모든 sprite 에 대해 사용하실 수 있습니다. listener 함수에서 event.target을 사용해서 원하는 스프라이트를 catch 해서 사용하실 수 있는겁니다.


Frame Trimming Supported



최근의 Public Build (2012.894) 에서는 frame trimming 도 지원합니다. 3rd-party sprite utilities 인 SpriteLoqTexturePacker 같은 곳에서 이 기능을 제공하고 있습니다. 그리고 코로나에서도 이 어플리케이션과 호환성 있게 이 기능을 사용할 수 있도록 하고 있습니다.

frame trimming 예제는 current sprite methods 를 사용하고 있는데 이것은 여러분 시스템의 코로나 어플리케이션에 있는 “HorseAnimation” sample project에서 확인 하실 수 있습니다.


CoronaSDK → SampleCode → Sprites → HorseAnimation



이와 관련해서는 다음 튜토리얼에서 다뤄지게 될 것 같습니다. 그동안 여러분은 샘플 프로젝트를 보셔도 되고 여기에서 imageSheet documentation를 보시면 많은 도움이 되실 겁니다.


In Summary


이 튜토리얼은 current sprite methods 의 대부분을 다뤘습니다. 여기에 basic image sheets, animation sequences 정의하기, sprite playback 다루기, 다양한 sprite property들 다루기 그리고 sprite event listener 사용하기 등을 추가로 다루고 있습니다. 모쪼록 이 튜토리얼이 개발자 여러분에게 스프라이트를 이용해서 앱을 만드는데 도움을 드릴 수 있기를 바라겠습니다. 특히 이전 버전의 sprite library 를 사용하시던 분들에게 새로 바뀐 sprite library 를 사용하시는데 도움이 되기를 바라구요.


반응형

Comment


Posted on . Written by


external modules 를 require 할 때 정확히 어떤 일이 일어나는지 헛갈릴 때가 있습니다. 더군다나 스토리보드 scene들이나 여러분의 custom module들과 같이 동작을 하는데 에상하지 못했던 결과가 나오면 더 혼동되죠.

오늘은 여러분과 같이 Lua 에서 module들은 정확히 어떻게 작동을 하는지에 대해 몇가지 실습을 해 보고 설명도 덧 붙이겠습니다. 이 글을 읽으시면 모듈 내의 코드가 실행되거나 built-in require() function 을 call 했을 때 어떤 코드가 run 하지 않는지 등에 대해 이해 하실 수 있을 겁니다.





Including External Modules


가장 간단한 형식은 external module이 테이블 같은 어떤 것을 return 하는 Lua 파일인 경우입니다. 외부 모듈이 하나의 function 인 셈이죠.

아래 정말 간단한 모듈이 있습니다. example1.lua 인데요. 터미널에 한 문장을 print 하고 빈 테이블을 return 하는 것입니다.


example1.lua


local t = {}

print( "example1.lua has been loaded." )

return t


이제 main.lua를 볼까요? 여기서 우선 example1.lua를 require 합니다.


main.lua


local ex1 = require "example1"

ex1.testvar = "Hello world"



예상한대로 ex1은 빈 테이블입니다. example1.lua에서 빈 테이블을 return 했으니까요. 그리고 “example1.lua has been loaded.” 라는 문장이 터미널에 쓰여질 겁니다. 그 다음에 위에 보면 ex1 테이블에 어떤 프로퍼티를 할당했습니다.

이 부분을 주목해 보세요. 여기 약간의 트릭이 있습니다. 다른 모듈에서 이 example1.lua를 require 해 봅시다. scene1.lua라는 모듈에서요. 이미 main.lua에서 example1.lua를 require 했었죠. 이제 어떤 일이 일어나는지 볼까요?


scene1.lua (previous main.lua still applies)


-- ...

local examp1 = require "example1"
print( examp1.testvar )

-- ...

scene1.lua에서 example1.lua를 require 하면 “example1.lua has been loaded” 이 터미널에 뿌려지지 않습니다. 그리고  ex1.testvar 값을 print 하면 터미널에 “Hello World” 가 뿌려집니다. (이 의미는 뭐냐하면 testvar 프로퍼티가 존재한다는 것이죠.


여기서 뭘 알 수 있나요?


첫번쨰로 “example1.lua has been loaded” 라는 문장에 터미널에 뿌려지지 않은 이유는 그 모듈이 이미 main.lua에서 로드 됐기 때문이죠. 모듈이 한번 로드 되면 그 안의 코드가 다 실행되게 되죠. 그리고 모듈의 return 값은 package.loaded라고 하는 글로벌 테이블에 저장됩니다.


여러분이 require를 call 하면 첫번째로 이전에 이 모듈이 이미 로드 된 것인지 아닌지 확인하기 위해 package.loaded table 을 살펴 봅니다. 만약에 있으면 외부 모듈을 새로 require 하는 대신 package.loaded 에 저장된 return value를 return 합니다. 이 return 된 value는 copy가 아니라 reference 입니다. 그러니까 여러분의 모듈이 테이블을 return 한다면 미래에 같은 모듈에 대해 require를 call 하면 같은 테이블을 get 하게 될 겁니다.


만약에 package.loaded 안에 그 모듈이 없으면 그 모듈이 로드 될 겁니다. (그 모듈안의 코드들이 실행 되겠죠.) 그리고 그 모듈의 return value가 package.loaded table안에 저장될 겁니다. (미래에 사용하기 위해 저장하는 거겠죠.) 그래서 두번째에서는 “example1.lua has been loaded.” 이 터미널에 뿌려지지 않은 겁니다. require() 가 call 됐을 떄 global package.loaded table 안에 이미 그 모듈이 존재하면 그 모듈을 re-load 하지 않으니까요.


모듈 안에 있는 코드를 두번 실행하도록 하는 방법에는 두가지가 있습니다.

  1. 코드를 모듈 내에서 함수 안에 넣고 그 함수를 부르는 경우
  2. package.loaded table 에서 그 모듈을 remove 한 다음에 다시 require 하는 경우


첫번째 시나리오에 대한 예제입니다.


example2.lua


local t = {}

print( "example2.lua has been loaded." )

t.hello = function()
    print( 'Hello world.' )
end

return t


main.lua


local ex2 = require "example2"
-- Terminal: example 2 has been loaded.

ex2.hello()
-- Terminal: Hello world.


scene1.lua


local examp2 = require "example2"

examp2.hello()
-- Terminal: Hello world.



예제에서 보듯이 example2.lua를 두개의 다른 모듈(main.lua and scene1.lua)에서 require 했습니다. 첫번째 print statement는 오직 한번 보여집니다. 첫번쨰로 그 모듈이 require 됐을 때죠.

두번째 print statement는 hello() 함수 안에 있습니다. 이것은 hello() 함수가 call 될 때마다 실행되겠죠. 그 코드가 한번 이상 실행되기를 원한다면 함수를 table에 attach 해서 모듈 마지막 부분에서 return 되게 하세요. (또는 단지 require 만 했을 떄 print 되지 않도록 할 때도 그렇게 하면 되겠죠.)


Removing from package.loaded


여러분이 모듈을 require 하려면 (예를 들어 example2.lua) require "example2" 라고 해야 되죠.

그러면 example2.lua의 return value 가 package.loaded table 에 저장 될 겁니다.

package.loaded["example2"]

만약 example2.lua에 있는 코드가 다시 execute 될 필요가 있다면 package.loaded table 에서 remove 한 다음에 다시 call 하시면 됩니다.


아래 그 예제가 있습니다.


main.lua


require "example2"
-- Terminal: example2.lua has been loaded.

package.loaded["example2"] = nil

require "example2"
-- Terminal: example2.lua has been loaded.

require "example2"
-- Terminal:


This is Universal

여러분이 만든 모듈이든 아니면 다운로드 된 모듈이든 혹은 built-in 모듈이든 이 모듈들을 다룰 때는 위에 설명드린 대로 외부 모듈들이 작동합니다.

스토리 보드의 scene들을 다룰 때 그 scene이 로드될 떄 스토리보드 리스너 함수안에 있는 코드들이 어떤 것은 실행되고 어떤 것은 실행되지 않는지에 대한것도 궁금하실 겁니다.

거기에 대한 것은 다음 기회에 다루도록 하겠습니다.

반응형

Comment

스토리 보드 기본 사용법

2012. 8. 22. 12:48 | Posted by 솔웅


Posted on . Written by



스토리보드 API는 아주 강력하고 flexible 하지만 new user들에게는 아주 헛갈리게 한다는 걸 인정해야겠네요. 그리고 Director Class 같은 3rd party scene management library를 사용하다가 스토리보드를 사용하려는 개발자 들도 헛갈릴 겁니다.

오늘은 이 간단한 Storyboard API를 정말 간단하게 사용하고 싶은 분들에게 기본적인 스토리보드 사용법에 대해 안내를 할 까 합니다. 아마 Director Class 만큼이나 간단할 겁니다. 스토리보드에는 많은 유용한 기능이 있지만 이 기능들이 항상 사용되야만 하는 것은 아닙니다. 그러니까 처음부터 이 모든걸 다 이해할 필요는 없습니다.

이 튜토리얼은 스토리보드 API를 빠르고 쉽게 사용할 수 있는 기본적인 기능에 대해서 다루려고 합니다.




Setup


스토리보드 API는 대부분의 다른 3rd party scene management libraries 하고는 약간 다른것들이 있습니다. 특히 화면 전환 부분이 그렇죠. 디폴트로 스토리보드의  scene 들은  화면전환 (scene transiton) 할 때 이전 scene들이 removed or purged 되지 않습니다. 대부분의 third-party scene management libraries 에서는 화면이 전환 될 때마다 이전 화면(scene)이 remove 되죠. Storyboard API에서도 이렇게 할 수가 있는데요. 자동으로 이전 scene들을 remove 하려면 함수가 call 될 때마다 inactive scene들을 purge 되도록 main.lua에 세팅할 수 있습니다.



main.lua


local storyboard = require "storyboard"
storyboard.purgeOnSceneChange = true


완전 간단하죠?


Scene Modules



Director class 에서는 scene 모듈은 new()라는 한개의 함수로 만들어 졌었습니다. 이 함수는 scene이 로딩 될 때마다 call 되게 되죠. 옵션으로 scene이 unload 될 때 call 되는 clean() 함수도 있었습니다. 아래는 Director Class에서 보여지는 scene 코드 입니다.


Director Class Scene


module(..., package.seeall)

-- setup function:
function new() 
    local localGroup = display.newGroup()

    local img = display.newImage( "hello.png" )
    localGroup:insert( img )
   
    return localGroup
end

-- cleanup function:
function clean()
    print( "Called when scene is unloaded." )
end



Storyboard API 를 사용할 때도 비슷한 structure와 로직이 적용될 수 있습니다. (위에 Setup 섹션에서 보실 수 있습니다.). Director Class 에서 위와 같이 사용해서 얻는 기능과 비슷하게 Storyboard API에서도 아래와 같이 코딩을 할 수 있습니다.



Storyboard Scene


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

-- setup function:
function scene:createScene( event )
    local img = display.newImage( "image.png" )
    self.view:insert( img )
end
scene:addEventListener( "createScene" )

-- cleanup function:
function scene:destroyScene( event )
    print( "Called when scene is unloaded." )
end
scene:addEventListener( "destroyScene" )

return scene



많이 다르지는 않죠? 코딩은 약간 다르지만 그 로직은 완전히 같습니다. createScene 이벤트 리스너에 모든 scene creation과 로직을 넣으시면 됩니다. 그리고 모든 cleanup 코드는 “destroyScene” event listener 에 넣으시면 되죠.


createScene 리스너는 Director의 new 함수와 비슷한 기능을 합니다. 그리고 destroyScene 리스너는 clean 함수와 하는 기능은 거의 같죠. main.lua 파일에 storyboard.purgeOnSceneChange를 true 로 하는 것을 잊지 마세요. 그렇게 하지 않으면 화면은 전환 할 때마다 destroyScene 이벤트가 자동적으로 dispatch 되지 않을 겁니다.


이제 여러분의 Storyboard API의 scene들을 어떻게 하면 Director 클래스와 비슷하게 셋업할 수 있는지 알게 됐습니다. 이제  storyboard.gotoScene() 를 사용해서 화면 전환을 하시기만 하면 됩니다.


Conclusion

Storyboard API 에 가시면 훨씬 더 많은 기능을 보실 수 있을 겁니다. 그 기능들은 필요하실 때만 가져다가 쓰시면 됩니다. 대부분의 경우 특히 Director Class를 사용하시다가 Storyboard로 전환하시는 분들에게는 이 튜토리얼이면 충분히 쉽게 스토리보드를 이해하시고 사용하실 수 있을겁니다.

반응형

Comment

  1. izowooi 2012.08.23 16:14

    감사합니다. 스토리보드 API 에 대한 기본 설명 잘 들었습니다. 간단히 쓰려면 어떻게 해야 하는지 알것 같네요. ^_^


Posted on . Written by



여러분 앱이 너무 많은 메모리를 차지한다면 OS는 low memory warning을 보낼 겁니다. 이 메세지의 의미는 뭔가 하라는 거겠죠. (메모리를 좀 더 풀어 주라던가 하는....) 계속해서 많은 메모리를 차지하고 있으면 OS는 아마 여러분 앱을 강제로 종료 시킬 겁니다. 여러분 앱이 강제로 종료 되면 유저 입장에서 보면 뭔가가 잘 못되서 앱이 끝난 겁니다. 그 앱에 대해 부정적인 이미지를 줄 수 있겠죠.


오늘 다룰 주제에서는 이 low memory warning이 왔을 때 어떻게 대응할 것인가 입니다. 그리고 crash를 방지하기 위해 해야 할 몇가지를 추천해 드릴거구요.


NOTE: Low memory warnings들은 현재 안드로이드 플랫폼에서는 제공하지 않습니다. 그러니까 오늘의 튜토리얼은 대부분 iOS에 포커스를 둔 겁니다. 하지만 예방 방법은 모든 플랫폼에 마찬가지로 적용할 수 있을 겁니다.






Responding to the Warnings


memory warnings에 응답하기 위해서는 memoryWarning 이벤트 리스너를 Runtime object에 추가 해야 합니다. 이 튜토리얼에서는 여러분이 main.lua에서 작업하는 것을 상정하고 얘기를 진행하겠습니다.


Runtime:addEventListener( "memoryWarning", onMemoryWarning )


만약 memoryWarning 이벤트 리스닝을 중지하고 싶으면 아래처럼 이벤트 리스너를 제거하면 됩니다.


Runtime:removeEventListener( "memoryWarning", onMemoryWarning )


만약 main.lua 가 아닌 다른 모듈에서 이 momoryWarning을 적용한다면 그 이벤트 리스너가 앱이 처음 시작할 때 로딩되지 않을 겁니다. 


The Listener


이제 onMemoryWarning 함수에 실제로 무엇을 넣을지는 여러분이 정하실 사항입니다. 왜냐하면 각 앱마다 로직이 다 다르고 그 자세한 사항은 여러분 만이 알 것이기 때문이죠. 

그 작업을 할 때 아래 사항들을 염두에 두시고 하세요.


  • 필요하지 않은 객체들은 가능한 모두 Unload 한다.
  • 필요하지 않은 이벤트 리스너들 특히 enterFrame 리스너 같은 것들은 stop 시킨다.
  • 현재 active 되 있지 않은 scene들은 Parge 하고 remove 시킨다. (try: storyboard.removeAll())
  • 제일 핵심사항은 필요하지 않은 것들은 다 제거한다 입니다.


아래 예제가 있습니다.


local function onMemoryWarning( event )
    audio.dispose( someAudioHandle )
    storyboard.removeAll()
end
Runtime:addEventListener( "memoryWarning", onMemoryWarning )


Preventing Memory Warnings


여러분이 할 수 있는 최선의 방법은 우선 memory warning이 일어나지 않도록 예방하는 겁니다. 정확히 무엇을 해야 된다라고 딱 찝어 내서 얘기할 수는 없습니다. 왜냐하면 여러분이 앱을 개발하는 사람이기 때문입니다. 그래도 몇가지 아이디어는 드릴 수 있습니다.


  • heavy scene으로 전환하기에 앞서 다른 모든 scene들을 purge 시키세요. (혹은 여러분의 scene을 생성할 때 그 이전의 모든 scene들을 purge 시키세요)
    See: storyboard.purgeAll()
  • 다양한 플랫폼과 디바이스에서 작업을 하신다면 프로그램 내에서 그 디바이스가 lower-performance device인지를 체크하세요. 그렇다면 그런 디바이스에는 다른 그래픽 set을 로드하시거나 좀 더 작은 객체들을 로드하도록 하세요.
  • 뭐가 필요하고 뭐가 필요 없는지 판단해서 불필요한 것들은 버리세요.
  • 코드를 한줄 한줄 다시 살펴 보세요. 혹시 어디엔가 메모리 누수현상이 없는지......


여러분들 앱에 맞게 여러분이 할 수 있는 다른 많은 것들이 있을 겁니다. 명심할 것은 memory warning 이 일어나기 전에 가능한 많은 리소스들을 free up 시키자는 겁니다. memoryWarning 이벤트에서는 더 말할 나위가 없겠죠. 그렇게 함으로서 OS 가 여러분 App 을 Kill 하는 것을 막는게 목표입니다.

반응형

Comment

  1. izowooi 2012.08.23 16:46

    잘 읽었습니다.
    메모리 문제는 신경을 쓰지 않고 하면 나중에 가서 꼭 문제를 일으키더군요.
    말씀하신 네 가지 조언들 참고하겠습다. 감사합니다. (__)

    • 솔웅 2012.09.04 13:16 신고

      예 메모리 문제는 간과했다가 나중에 문제가 생기면 다시 제대로 바로잡기가 힘든 것 같아요.
      프로그래밍 할 때부터 이 부분 잘 신경쓰고 작업해야 할 것 같습니다.


Posted on . Written by


storyboard API를 사용해서 switch between scenes 한다는 말은 딱 들어면 뭔지 알겠죠? 단지 scene이 바뀔 동안에 어떻게 데이터를 관리해야 하는지가 애매할 뿐이죠.

이게 왜 애매하냐 하면 대부분의 스토리보드 scene들이 각 모듈별로 (e.g. scene1.lua, scene2.lua) 관리 되고 이렇게 다른 파일들간에 왔다 갔다 할 때 어떻게 파일들간에 데이터를 공유할 것인지를 다루는게 쉽지는 않기 때문이죠. 특히 scene들이 어느때든지 create 되고 destory 될 수 있기 때문에 더 그렇습니다.

오늘의 튜토리얼에서 저는 scene들 사이에서 데이터를 공유할 수 있는 몇가지 방법을 보여드릴 겁니다. 그리고 여러 scene들의 로딩과 unloading 을 거치면서도 여러분 앱 안에서 singular overall "state"를 관리하는 방법들에 대해서도 보실 수 있을 겁니다.




Parameter Passing


한 scene에서 다른 scene으로 전환하기 위해서 storyboard.gotoScene()을 부를 때 여러분은 파라미터를 보낼 수 있고 이 파라미터는 다음 화면의 createScene, willEnterScene, and enterScene들에서 받을 수 있습니다.

아래 예제에서 우리는 score 변수를 gameplay scene에서 gameover scene으로 pass 할 겁니다.


gameplay.lua


-- ...

storyboard.gotoScene( "gameover", {
    effect = "fade",
    time = 800,
    params = {
        score = 100
    }
})

-- ...


gameover.lua


-- ...

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

    print( "You scored: " .. score )
end
scene:addEventListener( "enterScene", scene )

-- ...


Persistent Variables


한 scene에서 다음 scene으로 어떤 데이터를 패스해야 할 때 바로 위에서 보여 준 파라미터 passing 방법이 제일 좋은 방법일 겁니다. 그런데 가끔 전체 앱에서 일관되게 공유되어야 할 데이터가 있을 수 있습니다. 혹은 모든 scene 에서 접근 가능한 데이터가 필요할 때가 있습니다. 이럴 경우에는 파라미터를 pass 하는 방법이 최선의 방법이 아닐 수 있죠.

이런 경우 global 변수를 사용하거나 (그렇게 추천할 문한 방법은 아닙니다.) 혹은 모든 scene에서 access 가능한 스토리보드 객체에 attach 되는 state 테이블을 생성하실 수 있습니다. (모든 scene들은 반드시 storyboard 모듈을 require 합니다.)

아래에 global 변수를 사용하지않고 모든 scene들에서 접근 가능한 score 프로퍼티를 어떻게 사용하는지에 대한 예제가 있습니다.


main.lua


local storyboard = require "storyboard"

storyboard.state = {}
storyboard.state.score = 0

-- ...


gameplay.lua


local storyboard = require "storyboard"

-- ...

storyboard.state.score = storyboard.state.score + 100;
storyboard.gotoScene( "gameover", {
    effect = "fade",
    time = 800
})

-- ...


gameover.lua


local storyboard = require "storyboard"

-- ...

function scene:enterScene( event )
    print( "Your score is: " .. storyboard.state.score );
end
scene:addEventListener( "enterScene", scene )

-- ...


모든 scene들은 require( “storyboard” )를 반드시 해야 됩니다. 그러면 그 scene들은 common object를 share 하게 되는 거죠. main.lua에서 state 테이블을 선언했습니다. 그러면 그 테이블에 원하는 만큼의 변수를 추가할 수 있게 됩니다. 그 변수들은 require( “storyboard” )를 한 모든 모듈에서 접근 가능하게 되는 것이죠. global namespace를 사용해서 소스를 지저분하게 하지 않고서두요.

scene들 사이에 데이터를 공유하는 다른 방법들도 있을 겁니다. 위에 제시한 방법은 저희들이 추천드리는 방법입니다. 여러분들이 사용하시는 multiple modules and scenes 사이에서 데이터를 share 하는 방법은 뭐가 있습니까?

반응형

Comment

이전 1 2 3 4 5 6 7 ··· 12 다음