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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

코로나 네트워킹 과 웹 서비스 1

2011. 10. 21. 23:27 | Posted by 솔웅


반응형
Networking and Web Services

코로나는 웹 서비스를 위해서 LuaSocket library 2.02버전을 사용합니다.
이 루아 소켓 라이브러리는 SMTP, HTTP, FTP같은 프로토콜을 지원합니다. 이 외에 인코딩 프로토콜인 MIME와 URL 사용 그리고 LTN12 (데이터 트랜스퍼와 필터링) 기능도 지원합니다.
이 Luasocket은 코로나 앱에 이미 인스톨 되 있어서 director.lua나 ui.lua 같이 따로 파일을 복사해 넣을 필요는 없습니다. 하지만 앱실행시의 퍼포먼스 문제로 자동으로 로드되지는 않습니다.
그러므로 이것을 사용하기 위해서는 우선 require 해야 합니다.
local socket=require("socket") 이나 local http = require("http") 같이 먼저 선언하고 난 다음에 사용할 수 있습니다.
LuaSocket 문서는 아래 링크에서 볼 수 있습니다.

http://www.tecgraf.puc-rio.br/~diego/professional/luasocket/reference.html

아래 샘플 코드가 있습니다.
-- Load the relevant LuaSocket modules
local http = require("socket.http")
local ltn12 = require("ltn12")
 
-- Create local file for saving data
local path = system.pathForFile( "hello.png", system.DocumentsDirectory )
myFile = io.open( path, "w+b" )
 
-- Request remote file and save data to local file
http.request{
    url = "http://developer.anscamobile.com/demo/hello.png",
    sink = ltn12.sink.file(myFile),
}
 
-- Display local file
testImage = display.newImage("hello.png",system.DocumentsDirectory,60,50);

print("hello.png image is in " .. path)


일단 http 통신을 하기 위해서 socket.http를 require하고 파일을 transfer하기 위해서 ltn12를 require했습니다.
그 다음 http.request{} 부분을 보면 이미지가 있는 url을 지정하고 이 파일을 미리 지정해 두었던 myFile에 hello.png라는 이름으로 저장합니다.
io.open 으로 미리 쓰기 가능 상태로 두었습니다.
이 path는 device의 기본 폴더입니다. 캡쳐 화면의 터미널 부분을 보면 애플 컴퓨터의 디렉토리가 나오는데요. 아이폰으로 하면 아이폰 폴더 경로가 나올 겁니다.

여기 까지 하면 원격에 있는 이미지를 내 핸드폰으로 가져오는 것까지 성공 한 겁니다.
일단 가져왔으니까 그 다음엔 그냥 보여주면 되겠죠?
display.newImage() 를 통해서 이미지를 폰에 뿌려줍니다.

안드로이드에서는 인터넷을 하려며 퍼미션 (Permissions)를 주어야 합니다.
이 퍼미션은 build.settings 파일에서 설정해 줍니다.

-- build.settings
settings =
{
    androidPermissions =
    {
         "android.permission.INTERNET",
    },
}

광고 ( Ads)
광고는 이전에도 잠깐 다룬적이 있습니다.
현재 코로나는 inMobi 광고만 공식적으로 지원하고 있습니다.
그리고 광고는 시뮬레이터에서 지원을 안하기 때문에 빌드하고 디바이스에 인스톨 한 다음에 테스트 하셔야 됩니다.

아래 샘플 코드를 보시면요.

local ads = require "ads"
 
-- Example for inmobi:
ads.init( "inmobi", "12345abc6789defghijk123" )
 
-- iPhone, iPod touch, iPad
ads.show( "banner320x48", { x=0, y=100, interval=5, testMode=false } )
ads.show( "banner300x250", { x=0, y=100, interval=5, testMode=false } )
 
-- The following are iPad only
ads.show( "banner728x90", { x=0, y=100, interval=5, testMode=false } )
ads.show( "banner468x60", { x=0, y=100, interval=5, testMode=false } )
ads.show( "banner120x600", { x=0, y=100, interval=5, testMode=false } )

처음에 ads를 require합니다.
그리고 ads.init을 불러와서 광고서비스 이름과, App ID 를 세팅하고
ads.show로 적당한 위치에 제공되어지는 배너 크기를 정해 넣습니다.
광고를 보이지 않게 하기 위해선 ads.hide()를 사용합니다.

이 때 테스트를 하시려면요.
App ID 에 4028cb962895efc50128fc99d4b7025b 를 넣으시구요.
testMode=true 로 하세요.

그리고 직접 등록해서 사용하시려면 www.inmobi.com 으로 가셔서 등록하신 후 사용하시구요.

아래는 제 안드로이드 앱인 Spin the bottle 1 Lite 를 코로나로 iPhone 버전으로 만든겁니다.

이 화면은 ads.show() 를 이용해서 banner320x48 를 맨 위에 위치 시킨 겁니다.

이 화면은 ads.show()를 이용해서 banner320x48 를 맨 아래에 위치 시킨 겁니다.

이 화면은 ads.show()를 이용해서 banner300x250 를 적당한 위치에 놓은 겁니다.

지금 아직 앱스토어에서 Waiting for Review 상태라서 앱 URL을 inMobi 에 등록 시키지 못했습니다.
아마 앱이 앱스토어에 오픈 되고 이 URL이 확인 된 다음에 광고를 보내 주나 봅니다.

저희 회사에서 테스트할 수 있는 디바이스가 iPod Touch뿐이라서요. 다른건 테스트를 못 해봤습니다. (테스트 디바이스 지원이 너무 열악해요. 우리 회사... ;;)

iPod Touch 에서는 화면이 뜬 다음에 아직 광고가 나오지 않은 상태에서 다른 화면으로 넘어가면 에러가 나오면서 앱이 강제종료 되더라구요.

이에 iPod Touch에서만 나오는 에러인지 아니면 이런 에러를 방지하는 코딩이 따로 있어야 되는건지는 잘 모르겠어요.

다른 디바이스에서도 테스트를 해 봐야 겠는데....

하옇든 아래 코로나에서 제공하는 Ads관련 샘플 코드를 분석 해 보겠습니다.
혹시 이에 대한 해결책이 있는지 한번 볼까요?

--==================================================================================================
--
-- Abstract: InMobi Ads Sample Project
--
-- This project demonstrates Corona Banner Ads support (from inmobi network).
--
-- IMPORTANT: You must get your own "app ID" from the advertising
-- agency you want to display banner ads for. Further, you must build for device
-- to properly test this sample, because "ads" library is not supported by the
-- Corona Simulator.
--
--   1. Get your app ID (example, from inmobi)
--   2. Modify the code below to use your own app ID
--   3. Build and deploy on device
--
-- The code below demonstrates the different banner ads you can use
-- with the InMobi ad network.
-- Version: 1.0 (July 7, 2011)
-- Version: 1.1 (July 22, 2011) - Added Hide button and changed Next button behavior.
--
-- Sample code is MIT licensed, see http://developer.anscamobile.com/code/license
-- Copyright (C) 2011 ANSCA Inc. All Rights Reserved.
--
--==================================================================================================


--==================================================================================================
-- INITIAL SETTINGS
----------------------------------------------------------------------------------------------------

-- hide the status bar:
display.setStatusBar( display.HiddenStatusBar )

-- Below is the ad network that will be used:

local adNetwork = "inmobi"

-- Replace nil below with your app ID:
-- String; e.g. surrounded by quotes (ex. "abcdefghijklmnop")

local appID = "4028cb962895efc50128fc99d4b7025b"

--==================================================================================================

-- Make Banner Ads features available under "ads" namespace
local ads = require "ads"

-- initialize ad network:
if appID then
    ads.init( adNetwork, appID )
end

-- initial variables
local sysModel = system.getInfo("model")
local sysEnv = system.getInfo("environment")

local bgW, bgH = 320, 480
local currentAdIndex = 1

local adsTable = {
    "banner320x48",
    "banner300x250",
}
if sysModel == "iPad" then
    -- change settings if on iPad. It has 3 additional adUnitTypes it can show.
    bgW, bgH = 768, 1024
    adsTable = {
        "banner320x48",
        "banner300x250",
        "banner728x90",
        "banner468x60",
        "banner120x600"
    }
end

-- localize a widget function
local newButton = require( "widget" ).newButton

-- change settings if on iPad
if sysModel == "iPad" then
    bgW, bgH = 768, 1024
end

-- create a background for the app
local backgroundImg = display.newImageRect( "space.png", bgW, bgH )
backgroundImg:setReferencePoint( display.TopLeftReferencePoint )
backgroundImg.x, backgroundImg.y = 0, 0


if appID then
    -- Shows the banner indexed by variable "currentAdIndex"
    local showIndexedBanner = function()
        print("Showing Banner: " .. adsTable[currentAdIndex])
        local adX, adY = 0, 0
        ads.show( adsTable[currentAdIndex], { x=adX, y=adY, interval=5, testMode=true } )
    end

    -- onRelease event listener for 'nextButton'
    local onNextButtonReleased = function( event )
        currentAdIndex = currentAdIndex + 1
        if (currentAdIndex > #adsTable) then
            currentAdIndex = 1
        end
        showIndexedBanner()
    end

    -- onRelease event listener for 'hideButton'
    local onHideButtonReleased = function( event )
        ads.hide()
    end

    -- if on simulator, make sure onRelease event for buttons are set to nil
    if sysEnv == "simulator" then
        onNextButtonReleased = nil
        onHideButtonReleased = nil
    end

    -- create a next button (to show a different ad unit)
    local nextButton = newButton{
        default="button.png",
        over="button_over.png",
        label = "Show Next Banner",
        onRelease= onNextButtonReleased
    }
    nextButton:setReferencePoint( display.CenterReferencePoint )
    nextButton.x = display.contentWidth * 0.5
    nextButton.y = display.contentHeight - 120

    -- create a hide button
    local hideButton = newButton{
        default="button.png",
        over="button_over.png",
        label = "Hide Banner",
        onRelease= onHideButtonReleased
    }
    hideButton:setReferencePoint( display.CenterReferencePoint )
    hideButton.x = display.contentWidth * 0.5
    hideButton.y = display.contentHeight - 60

    -- if on simulator, let user know they must build for device
    if sysEnv == "simulator" then
       
        local font, size = "Helvetica-Bold", 22
       
        local warningText1 = display.newText( "Please build for device ", 0, 135, font, size )
        local warningText2 = display.newText( "to test this sample code.", 0, 165, font, size )
       
        warningText1:setTextColor( 255, 255, 255, 255 )
        warningText2:setTextColor( 255, 255, 255, 255 )
       
        warningText1:setReferencePoint( display.CenterReferencePoint )
        warningText2:setReferencePoint( display.CenterReferencePoint )
       
        local halfW = display.contentWidth * 0.5
        warningText1.x, warningText2.x = halfW, halfW
       
        -- make buttons appear disabled
        nextButton.view.alpha = 0
        hideButton.view.alpha = 0
    else
        -- display initial banner ad
        showIndexedBanner()
    end
else
    -- If no appId is set, show a message on the screen
    local font, size = "Helvetica-Bold", 22

    local warningText1 = display.newText( "No appID has been set.", 0, 105, font, size )
    warningText1:setTextColor( 255, 255, 255, 255 )
    warningText1:setReferencePoint( display.CenterReferencePoint )

    local halfW = display.contentWidth * 0.5
    warningText1.x = halfW

    if sysEnv == "simulator" then
        local warningText2 = display.newText( "Please build for device ", 0, 185, font, size )
        local warningText3 = display.newText( "to test this sample code.", 0, 215, font, size )
        warningText2:setTextColor( 255, 255, 255, 255 )
        warningText3:setTextColor( 255, 255, 255, 255 )

        warningText2:setReferencePoint( display.CenterReferencePoint )
        warningText3:setReferencePoint( display.CenterReferencePoint )
        warningText2.x, warningText3.x = halfW, halfW
    end
end

이 샘플 코드는 Show Next Banner를 누르면 해당 디바이스에서 보일 수 있는 배너 크기별 광고를 차례대로 보여줍니다.
그리고 Hide Banner를 누르면 이 배너를 감춥니다.

코드를 라인별로 분석해 볼까요?
(아래 압축파일에 원본소스와 이미지 등이 있습니다.)



우선 주석은 다 지나가고 34번째 줄부터 시작합니다. 이 줄은 아이폰의 statusBar를 없애는 부분입니다.
39번째 줄에 adNetwork라는 변수에 inmobi라는 값을 담았습니다.
그리고 appID에 inmobi에서 받은 값을 넣어야 하는데요. 위 소스는 nil로 돼 있어서 저는 테스트용 id를 넣었습니다. "4028cb962895efc50128fc99d4b7025b"

그 다음 49번째 줄에서 ads를 require 했고 다음 줄(52)에서 appID가 nil이 아니면 ads.init을 해 줍니다.

그리고 sysModel에 디바이스의 모델을 그리고 sysEnv에 디바이스의 environment를 담습니다.

61번째 줄을 보면 bgW,bgH를 아이폰 해상도에 맞게 320,480 으로 정합니다.
다음줄에 currentAdIndex 에 1을 대입합니다.

다음 adsTable이라는 테이블에 아이폰에서 가능한 배너크기 두가지를 넣습니다.
그리고 sysModel이 iPad 이면 bgW,bgH에 768,1024 를 대입하고 adsTable에는 5종류의 배너크기 모두를 대입합니다.

82번째 줄은 newButton을 wedget의 버튼으로 선언합니다.

85~88번째 줄은 iPad일 경우 bgW,bgH를 바꿔 주는 부분인데요. 70번째 줄에서 이미 한 작업인데 여기서 또 하네요. 이건 없애도 되겠습니다.

다음 92~94째줄은 backgroundImg를 세팅해 주는 부분입니다.

이제 다음부터 어떤 로직이 나오나 봅니다. (저도 지금 이거 처음 보면서 작성하는 거라서 뭐가 나올지 모릅니다.)

appID가 있으면  local showIndexedBanner 함수를 선언하는데요. adX,adY를 0,0으로 하고 ads.show()를 통해서 광고를 보여줍니다.

그 다음 onNextButtonReleased 함수가 있는데 파라미터로 event를 받는 거로 봐서 어떤 리스너에서 호출할 건가 봅니다.
여기서는 currentAdIndex에 1을 더해주고 adsTable의 요소보다 커지게 되면 다시 currentAdIndex를 1로 해주는 로직이 있구요.
이 작업이 끝나면 showIndexedBanner()를 호출합니다.

그러니까 어떤 버튼이 클릭되면 currentAdIndex를 바꿔서 showIndexedBanner()함수를 호출하니까 adsTable에 있는 배너 종류가 차례대로 보이겠네요.

그 다음 117번째 줄은 onHideButtonReleased 함수가 있는데 이것도 event 파라미터가 있는 걸로 봐서 리스너에서 호출 할 겁니다.
당연히 hide ads버튼에서 호출하겠죠?
이 함수가 호출되면 ads.hide()를 실행 시켜서 광고를 없앱니다.

그 다음 123번째 줄은 sysEnv가 시뮬레이터면 onNextButtonReleased와 onHideButtonReleased 함수를 모두 없앱니다.

130번째 줄 nextButton 에는 default button image와 over시 버튼 이미지를 정해주고 글자를 Show Next Banner라고 정해 줍니다. onRelease 에는 onNextButtonReleased를 대입해 줍니다.
그리고 그 위치를 정해 주고요.
142번째 줄에서는 hideButton을 선언해주고 위치를 정해 줍니다.

154번째 줄에서는 sysEnv가 시뮬레이터 이면 warningText1,warningText2를 뿌려줍니다.
그리고 두개의 버튼에 투명도를 주어서 안 보이도록 합니다.
시뮬레이터가 아니면 showIndexedBanner()를 실행 시켜서 광고를 보여줍니다.
버튼에 투명도 처리하는 부분도 적용 안 되니까 이 경우엔 버튼도 보이겠죠?

177번째 줄은 저 위에 있는 if문하고 연결 됩니다 뭐냐하면 97번째 줄의 if appID then 요.

만약 appID가 없다면 97번째 이후에 있던 코드는 다 실행하지 않고 181번째 줄에 있는 warningText1을 화면에 표시합니다.
그리고 시뮬레이터 이면 warningText2,warningText3를 출력합니다.

여기까지가 이 소스코드를 모두 분석한 내용입니다.

제가 iPod Touch에서 겪었던 에러 (화면 뜨고 광고가 뜨기 전에 화면이동-director.lua- 할 경우 생기는) 를 해결 할 수 있는 부분은 없네요.

아직 코로나에서 미처 대응하지 못했던 에러가 아닌가 합니다.
iPhone이나 그 이후 버전의 phone이나 iPad에서는 이러한 현상이 일어나는지 어떤지 잘 모르겠습니다.

이거 회사 테스트 디바이스 지원이 너무 열악해요...

하여간 오늘은 간단히 소켓통신에 대해 알아봤구요.
그리고 코로나에서의 광고 띄우기 (inMobi) 에 대해 자세히 알아봤습니다.

다음엔 네트워킹과 web service에 대해 마저 알아볼 건데요.
요즘 제가 관심 가지고 있는 In-App Purchases 에 대해서도 있네요.

시간이 허락되면 여기까지 다 공부 하겠습니다.

반응형

오디오, 비디오, 사진 컨트롤 2

2011. 10. 20. 22:51 | Posted by 솔웅


반응형
비디오 라이브러리

비디오 파일 플레이는 media.playVideo 함수로 구현 되고 이것은 비동기적입니다.
비디오가 끝나거나 유저가 끝낼을 때 어떤 핸들링을 하려면 onComplete를 사용합니다.
local onComplete = function(event)
   print( "video session ended" )
end
media.playVideo( "Movie.m4v", true, onComplete )
비디오 파일 플레이는 remote URL을 이용해 상영할 수도 있습니다.
신택스는 아래와 같습니다.
media.playVideo( path [, baseSource ], showControls, listener )

baseSource 부분은 옵션으로 디폴트는 system.ResourceDirectory입니다. URL을 넣을 수도 있습니다.

코로나에서 샘플로 제공하는 샘플 코드인 StreamingVideo 소스 코드를 보면 아래와 같습니다.

display.setStatusBar( display.HiddenStatusBar )   -- 아이폰의 StatusBar를 없앤다.
local posterFrame = display.newImage( "Default.png" )
function posterFrame:tap( event ) -- posterFrame이미지를 tap하면 실행되는 함수
    msg.text = "Video Done"        -- message will appear after the video finishes
    media.playVideo( "http://www.anscamobile.com/video/Corona-iPhone.m4v", media.RemoteSource, true )
end
-- Determine if running on Corona Simulator
-- 현재 device가 simulator인지 체크 함
local isSimulator = "simulator" == system.getInfo("environment")
-- Video is not supported on Simulator
if isSimulator then -- 시뮬레이터라면 이 메세지를 화면에 뿌림
    msg = display.newText( "No Video on Simulator!", 0, 60, "Verdana-Bold", 22 )
else -- 시뮬레이터가 아니면 아래 메세지를 뿌림
    msg = display.newText( "Tap to start video", 0, 60, "Verdana-Bold", 22 )
    posterFrame:addEventListener( "tap", posterFrame )        -- add Tap listener
end
msg.x = display.contentWidth/2        -- center title
msg:setTextColor( 0,0,255 )



비디오 파일 플레이하는것도 보시다시피 아주 간단합니다.

코로나에서 지원하는 비디오파일 포맷은 아래와 같습니다.
.mov, .mp4, .m4v, .3gp

카메라와 사진 라이브러리

media.show(imageSource,listener)
imageSource 부분엔 아래 세가지 중 하나가 들어갑니다.
media.PhotoLibrary
media.Camera
media.SavedPhotosAlbum

이 함수는 비동기적입니다. 그 의미는 이 함수 다음에 어떤 메소드(함수)가 있다면 이 함수가 끝난 이후에 실행 될거라는 겁니다.
아래 소스를 참고하세요.
local onComplete = function(event)
   local photo = event.target
   print( "photo w,h = " .. photo.width .. "," .. photo.height )
end
media.show( media.Camera, onComplete )

Event Sounds

media 라이브러리는 재생을 위해  아래기능을 지원합니다.
event sounds : 짧은 소리, 전체가 재생 됨
extended sounds : 긴 소리. 재생 기간 동안 정지 할 수도 있음. 한번에 하나만 open 할 수 있음

Event Sounds

1~3초 정도의 짧은 소리가 있다면 event sound API를 사용하는 것이 좋습니다.
local soundID = media.newEventSound( "beep.caf" )
 
local playBeep = function()
        media.playEventSound( soundID )
end
timer.performWithDelay( 1000, playBeep, 0 )

이 사운드 파일은 media.newEventSound를 통해 한번 로딩 된 후 여러번 반복해서 사용 될 수 있습니다.

media.newEventSound(soundFile) : soundFile로부터 sound를 로딩합니다. 그리고 sound id 이벤트를 리턴합니다. 이것은 media.playEventSound에 전달 될 인수입니다.
media.playEventSound(sound) : 사운드를 재생합니다.

안드로이드에서는 사운드를 로딩하고 준비하는데 약간의 딜레이가 있을 수 있습니다. 그러므로 playEventSound에 filename 파라미터를 사용하는 것은 추천하지 않습니다. newEventSound로 미리 로딩해서 사용하세요.

playEventSound 안에 아래와 같이 함수를 지정할 수 있습니다.
media.playEventSound("beef.caf",onComplete)
이렇게 되면 플레이가 끝난 이후 onComplete함수를 실행합니다.

Extended Sounds

좀 긴 사운드 (아이폰에서의 MP3 포맷이나 배경음악 재생같은)에 사용됩니다.
play,pause,stop같은 기능들이 있습니다.

media.playSound( "song.mp3" )
 
local stopAfter10Seconds = function()
        media.stopSound()
end
timer.performWithDelay( 10000, stopAfter10Seconds )

media.stopSound()와 media.playSound(soundFile) 은 위에 있구요.
이외에 media.pauseSound() 도 있습니다.

아주 큰 사운드 파일일 경우는 로딩하는데 시간이 오래 걸릴 수 있습니다. 이 경우 애니메이션이 실행 되고 있다면 잠깐 중단 될 수도 있습니다. 이를 방지 하기 위해서 애니메이션이 실행되기 이전에 로딩해 둘 수 있겠죠?

media.playSound( 'sound.mp3' )
media.stopSound()

이렇게 play시켰다가 곧바로 stop 시키면 미리 로딩을 시킬 수 있을 겁니다.
어쩐지 좀 꼼수 같죠? 코로나 Doc에서 정식으로 이 방법을 소개하고 있네요.

아래와 같이 사운드 종료시 다른 함수를 실행 시키도록 할 수 있습니다.
local onComplete
onComplete = function(event)
        print( "sound play ended" )
        media.playSound( "note2.mp3", onComplete )
end
 
media.playSound( "note2.mp3", onComplete )

이 코드는 note2.mp3가 끊이지 않고 계속 루핑되면서 재생되는 효과가 있겠네요.
루핑기능을 위한 꼼수로 보이네요.

Loop Parameter
media.playSound("note2.mp3", true)
이렇게 마지막에 true 파라미터를 주면 사운드를 계속 반복 재생시켜줍니다.
이 방법이 조 위에 있는 방법보다 부담을 덜 줄겁니다.

media.setSoundVolume(0.5)
print("volume = "  .. media.getSoundVolume())
위와 같이 볼륨 조절이 가능합니다.

event sound에는 기 볼륨조절 기능이 지원이 안 됩니다. 이건 iPhone API에서 제공하지 않는다고 하네요.

코로나 SDK에서 지원하는 Audio Format들은 아래와 같습니다.
event sound file
- 수초 이내의 짧은 시간이어야 한다.
- PCM,IMA4(IMA/ADPCM) 포맷
- .caf나 .aif 파일
media.playSound()를 이용하는 좀 더 긴 파일은 mp3파일을 지원 합니다.

녹음 기능
recording = media.newRecording([file])
recording:startRecording()
recording:stopRecording()

녹음에는 이런 기능들이 제공 됩니다.
이건 시뮬레이터나 iPod Touch 같이 하드웨어에 마이크(녹음) 기능이 없으면 테스트가 불가능하겠죠.
그러면 빌드를 해서 직접 폰에서 테스트를 해야 할 텐데 그러려면 코로나 유료버전을 사야 되구요.
테스트 하기엔 좀 제한이 있네요.

result = recording:isRecording()
현재 녹음 되고 있다면 true를 그렇지 않으면 false를 반환합니다.

recording:setSampleRate() ; rate = recording:getSampleRate()

디폴트는 44100dlqslek. 8000,11025,16000,22050,44100 Rate 등이 검증 된 수치들 입니다.
startTuner()를 하기 전에 반드시 이 setSampleRate()를 불러와야 합니다.
사운드 관련된 전문 앱을 만드려면 이런걸 신경 쓰셔야 겠지만 또 하드웨어 적으로 지원이 안되면 이것도 한계가 있을 겁니다.

Audio Tuner
recording:startTuner()
tuning을 할 수 있도록 합니다.

freq = recording:getTunerFrequency()
Hz 를 반환합니다. 이 기능은 tuner가 on 인 상태에서만 작동 됩니다.

recording:stopTuner() : tuner를 정지 합니다.

volume = recording:getTunerVolume()
최근에 확정된 볼륨 숫자를 반환합니다. 범위가 -1에서 1사이로 나온다고 하는데요.
이것을 알기 쉽게 보려면 10*math.log(volume)을 통해서 보는게 좋습니다.

자 그럼 Corona SDK 의 Media 기능에 대해 다 훑어 봤습니다.

이제 남은 주제들은 Networking and Web Service, System and OS, Interactivityand Detecting Events, Location and Maps, Native UI, Advanced Programming Techniques 등이 남았네요.

꽤 많이 한것 같은데 아직 다뤄야 할 이슈들이 많습니다.
부지런히 다루고 그 다음은 샘플 코드들을 분석해 볼까 합니다.
여러가지 TIP들도 많이 다뤄보구요.

다음 시간에 뵐께요.


 

반응형

오디오, 비디오, 사진 컨트롤 1

2011. 10. 19. 23:18 | Posted by 솔웅


반응형
이제 오디오, 비디오, 사진 컨트롤을 코로나에서는 어떻게 하는지 공부하겠습니다.
첫번째로 오디오에 대해서 알아보겠습니다.

안드로이드 앱을 만들어 보신 분들은 아시겠지만 안드로이드에서는 이미지나 오디오 파일, xml 파일을 인식할 때 확장자는 고려하지 않습니다.
그러니까 aaa.png나 aaa.gif 나 aaa.jpg 이렇게 확장자만 다르고 이름이 같은 파일들은 동일하게 인식을 합니다. (결국엔 에러를 일으키게 됩니다.)
오디오도 마찬가지 인데요. aac, aif, caf, mp3, ogg 등이 오디오에서 사용하는 확장자 인가봅니다. 이름을 정할 때 확장자를 제외한 파일 이름이 유니크 하도록 정해야 합니다.

코로나에서 오디오를 다룰 때 사용하는 채널의 맥시멈은 32 입니다.
그러니까 동시에 32 channel 까지 사용할 수 있습니다.
audio.totalChannels 를 통해서 채널 갯수를 알 수 있습니다.

사운드(sound) 를 load하는 방법은 두가지가 있습니다.
loadSound()와 loadStream() 입니다.
첫번째는 사운드 파일을 모두 메모리에 올려 놓는 것이고 두번째는 play 할 때 스트리밍 하면서 플레이 하는 것입니다.
첫번째는 시간상으로 절약이 되는 대신에 메모리를 많이 차지하겠고 두번째는 시간이 지연될 수는 있지만 메모리를 아낄 수 있겠죠?

위 파일을 받아보세요.
제가 스나이퍼의 총소리를 좋아하는데 그거 비슷한 소리 같아서 자주 이용해요.
그리고 main.lua에 아래 코드를 넣어보세요.
 explosionSound = audio.loadSound("explosion6.wav")
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)
  audio.play(explosionSound)

audio.play를 한개만 했을 때와 위와 같이 여러개 했을 때와 비교해 보세요.
위와 같이 loadSound로 불러들이면 메모리에 사운드를 로딩해 놓고 있는 상황이기 때문에 동시에 사운드를 들려줍니다.
audio.loadSound 를 audio.loadStream으로 바꾸면 어떻게 될까요?
이 경우엔 audio.play 하면서 파일을 메모리로 일부분씩 불러들이고 (streaming 하고) 소리를 내기때문에 1개의 소리만 들립니다.

loadStream을 하면서 동시에 소리를 내고 싶으면 아래와 같이 해야 됩니다.
explosionSound1 = audio.loadStream("explosion6.wav")
explosionSound2 = audio.loadStream("explosion6.wav")
explosionSound3 = audio.loadStream("explosion6.wav")
explosionSound4 = audio.loadStream("explosion6.wav")
explosionSound5 = audio.loadStream("explosion6.wav")
  audio.play(explosionSound1)
  audio.play(explosionSound2)
  audio.play(explosionSound3)
  audio.play(explosionSound4)
  audio.play(explosionSound5)

이렇게 하니까 동시에 소리가 나네요.

loadSound로 불러들이면 미리 메모리에 올려놓은 상태이기 때문에 즉시 소리를 play할 수 있습니다. 반면에 loadStream을 사용하면 첫번째 chunk를 메모리로 불러들이는데 시간이 좀 걸릴 수 있습니다.

audio.play를 다 하고 메모리를 비우려면 audio.dispose() 함수를 사용해야 합니다.
물론 해당 오디오 파일이 프로그램 끝날 때 까지 계속 사용해야 되면 이 함수를 사용할 일은 없겠지만요. 당연히 프로그램이 종료 될 때 모든 메모리는 릴리즈 됩니다.

audio.dispose()를 확실하게 사용하려면 아래와 같이 하시면 됩니다.
laserSound = audio.loadSound( "laserSound.wav" )
backgroundMusic = audio.loadStream( "backgroundMusic.m4a" )
audio.dispose( laserSound )
audio.dispose( backgroundMusic )
laserSound = nil  -- This makes sure we can't use the handle again
backgroundMusic = nil  -- This makes sure we can't use the handle again

저 사운드를 게임의 스테이지 1에서만 사용하고 그 위 단계에서는 사용하지 않는다면 저렇게 메모리에서 완전히 사라지게 하는것도 좋은 방법이겠죠? 메모리 관리와 퍼포먼스 차원에서요......

audioPlayFrequency를 설정할 수도 있습니다.

config.lua 파일에 아래와 같이 audioPlayFrequency를 설정합니다.
  application =
  {
      content =
      {
          width = 320,
          height = 480,
          scale = "letterbox",
          audioPlayFrequency = 22050
      },
  }
22050Hz이상이 필요하지 않으면 위와 같이 하면 됩니다. 이것보다 높은 Hz가 필요하다면 44100으로 세팅을 하시구요.
코로나에서 제공되는 값은 11025,22050,44100Hz라고 합니다. 그 이외의 것들은 아직 테스트를 해보지 못했다고 하네요.

stereo가 아니라 mono sound를 사용하게 되면 메모리 점유 공간을 반으로 줄일 수 있습니다. 코로나에서는 OpenAL을 사용한다고 하는데요. OpenAL은 mono sound에 대해서 spatialized/3D effects를 제공한다고 합니다. 하지만 스테레오에는 이 3D효과를 제공하지 않는다네요. 오디오 쪽은 제가 잘 몰라서 이 3D 효과가 뭔지는 모르겠습니다. (돌비 서라운드 시스템 지원을 말하나???) 하여간 코로나에서는 아직까지 이 3D 효과는 지원하지 않고 있답니다.

audio.reserveChannels()
오디오를 로딩하고 플레이를 하면 코로나는 자동적으로 특정 채널에 이 오디오를 할당하게 됩니다.
그런데 이 채널에 볼륨이라던지 다른 세팅이 돼 있다면 그리고 1번 오디오를 플레이하고 2,3,4 번이 다른 채널에서 플레이 되다가 5번채널을 플레이 했는데 이것이 1번 오디오를 플레이했던 채널이랑 동일한 채널에 할당 된다면.
미리 그 채널에 맞춰놨던 세팅값이 5번 사운드에도 적용이 될 겁니다.

이런 부분 까지 제어 해야 될 필요성이 있다면 이 함수를 씁니다.
audio.reserveChannels(2) 이런식으로요. 그러면 해당 채널은 자동 할당이 안 되겠죠.

그리고 audio.findFreeChannel()함수도 있는데요.
이것은 해당 채널부터 할당할 채널을 검색하게하는 함수입니다.

local availableChannel = audio.findFreeChannel()
audio.play( laserSound, { channel=availableChannel } )

이렇게 하면 이 가능한 채널보다 높은 숫자의 채널들을 검색해서 할당하게 될 겁니다.

이상 코로나에서의 사운드 컨트롤에 대해서 알아보았습니다.
다음 시간엔 비디오, 카메라, 사진 라이브러리에 대해서 알아보겠습니다.

그리고 이런 미디어를 다룰 때 유용하게 사용할 수 있는 함수들을 알아보겠습니다.
예를 들어 반복 실행, 딜레이, Completion Listener 로 완료 후 어떤 동작 할 수 있도록 하기, 원래 실행 시간보다 길게 실행 되도록 하기, 잠깐 멈추기, 볼륨 조절 등등이요.

그럼 다음에 뵙겠습니다.




반응형

코로나 SDK에서 파일 다루기

2011. 10. 18. 22:51 | Posted by 솔웅


반응형
다른 기능과 마찬가지로 코로나에서는 file control 도 간단한 코딩으로 구현할 수 있습니다.

우선 파일이 저장될 path를 지정해 주셔야 되는데요. path지정은 system.pathForFile 함수를 사용합니다. 다음은 절대경로에 있는(main.lua에 있는) 아이콘 파일을 가리킵니다.
local path = system.pathForFile( "Icon.png", system.ResourceDirectory )
일반적으로 파일을 저장하려면 아래 3가지 종류의 기본 디렉토리 중 하나를 사용하셔야 합니다.
system.DocumentsDirectory : 어플리케이션 세션 사이에서 persist하게 유지될 필요가 있을 때 사용합니다.
system.TemporaryDirectory : 임시 디렉토리 입니다.
system.ResourceDirectory : 어플리케이션의 모든 asset들이 있는 디렉토리. 이곳에 있는 파일을 생성하거나 수정하거나 추가할 수 없습니다.

아래와 같이 코딩 한 다음 실행해 볼께요.
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
print("path = " .. path)

system.DocumentsDirectory를 print로 찍어봤더니 맥에 있는 코로나 시뮬레이터 디렉토리안이 찍힙니다. 아마 전화기이면 전화기 내의 특정한 장소에 저장이 되겠죠?


system.ResourceDirectory 를 print 로 찍어보면 main.lua 가 있는 그 메인 디렉토리가 나옵니다. 위 소스와 같이 system. 을 찍지 않아도 이렇게 나오네요.


system.TemporaryDirectory 는 system.DocumentsDirectory 와 같은 경로가 찍히는데요. persist하게 유지되느냐 그냥 temporary하게 유지하느냐의 차이가 있습니다.

아래 코드를 보겠습니다.
local path = system.pathForFile( "data.txt", system.DocumentsDirectory )
 
-- io.open opens a file at path. returns nil if no file found
local file = io.open( path, "r" )
if file then
   -- read all contents of file into a string
   local contents = file:read( "*a" )
   print( "Contents of " .. path .. "\n" .. contents )
   io.close( file )
else
   -- create file b/c it doesn't exist yet
   file = io.open( path, "w" )
   local numbers = {1,2,3,4,5,6,7,8,9}
   file:write( "Feed me data!\n", numbers[1], numbers[2], "\n" )
   for _,v in ipairs( numbers ) do file:write( v, " " ) end
   file:write( "\nNo more data\n" )
   io.close( file )
end

1번을 보면 경로를 system.DocumentsDirectory로 하고 파일 이름은 data.txt로 정의했습니다.
4번째줄을 보면 data.txt파일을 io.open 함수를 이용해서 엽니다.
file이 있으면 file:read 로 모두(*a) 읽어옵니다.
이 읽어온 내용을 print로 찍습니다.
그리고 9번째 줄에서 io.close로 파일을 닫습니다.
만약 file이 없으면 10번째 줄 그 다음이 실행 됩니다.
file을 write권한으로 열구요.
file:write 함수를 이용해서 내용을 파일에 넣습니다.
그리고 파일을 닫습니다.

파일을 만들고 내용을 넣고 나중에 그것을 다시 읽고 하는게 무척 간단하죠?

주의할 점은 처음에 언급했듯이 main.lua 파일이 있는 폴더 그러니까 system.ResouceDirectory 로 접근할 때에는 파일을 수정하거나 지우거나 만들어서는 안됩니다.
처음에 앱을 실행할 때 애플리케이션의 정합성을 체크하게 되는데요. 이 때 등록돼 있는 파일과 똑 같은 파일이 그 디렉토리에 있어야 합니다. 만약에 다를 경우 앱이 실행 되지 않습니다.

보안상의 이유로 파일은 그 애플리케이션의 sandbox에 있는 것만 열 수 있습니다. io 라이브러리는 그 path를 필요로 합니다.
그 path는 system.pathForFile을 통해 제공됩니다. (위에 있는 예제에서와 같이요)

io 라이브러리엔 다음과 같은 합수 들이 있습니다.
io.close(file)
io.flush()
io.input(file)
io.lines(filename) : read 모드로 열어서 iterator 로 리턴합니다.
io.open(filename,mode)
  r : read mode (default), w : write mode , a : append mode, r+ : update mode, 이전 데이터들은 남아있는다. w+ : update mode 이전 데이터들은 지워진다. a+ : append update mode, 이전 데이터들은 남아있는다. 파일 마지막부분서부터 추가된다.
io.output
io.popen(prog,mode) : 별도의 프로세스에서 program prog를 시작한다.
io.read = io.input():read
io.tmpfile() : 프로그램이 끝나면 저절로 해당 파일을 없어진다.
io.type(obj)
io.write() = io.output():write

file:close(), file.flush(), file:lines(), file:read(), file:seek(),file:setvbuf(),file:write()

각 함수들에 대한 상세한 내용은 API를 보세요.

Crypto

Corona SDK 는 hash-based 메세지 검증코드를 제공합니다 (HMAC).
이 기능을 제공하는 crypto 라이브러리는 코로나 앱에 pre-install된 external library입니다. 이 라이브러리를 이용하려면 local crypto = require("crypto") 식으로 require한 후 사용이 가능합니다.

crypto.digest( algorithm, string [, raw] )

crypto.hmac( algorithm, string, key [, raw] )

위와같은 함수들이 있습니다.
이 함수들은 제가 설명하기엔 역부족이네요. 뭔지도 잘 모르겠구요. 언제사용하는건지도 모르겠고...

혹시 아시는 분 계시면 조언 부탁드려요.

일단 오늘로 코로나 SDK의 Data and Files 섹션은 모두 끝났습니다.

다음엔 Audio,Video and Photos 에 대해서 공부해 볼까 합니다.

그럼 담 시간에 뵈요.



반응형

코로나에서 SQLITE DB 이용하기

2011. 10. 17. 22:40 | Posted by 솔웅


반응형
오늘은 코로나에서 데이타베이스를 이용하는 방법을 보겠습니다.
안드로이드나 아이폰에서 SQLite라는 DB를 쓰니까 코로나에서도 당연히 SQLite 연동 기능을 제공 하겠죠?

기본적으로 코로나는 아이폰에 맞는 SQLite built-in을 실행합니다. 그리고이것을 안드로이드의 SQLite 버전으로 컴파일 하게 되는데요. 이것은 안드로이드 바이너리 파일(apk 파일)의 사이즈를 약 300k 정도 커지게 만듭니다.

SQLite 은 코로나 시뮬레이터에서도 제공 되므로 시뮬을 통한 테스트도 가능합니다.
(지금 Spin the Bottle 을 코로나 버전으로 옮기고 있는데요. text field, text box 같은 것들은 시뮬에서 제공이 안 되더라구요. 그래서 소스 고친 다음에 빌드하고 디바이스에 인스톨 해서 테스트 해야 하니 많이 번거롭습니다.)

아래 샘플을 보세요.

require "sqlite3"  -- SQLite 3 를 import 한다.
local db = sqlite3.open_memory() -- 메모리에 디비를 만든다.
 
db:exec[[  -- 테이블을 생성한다.
  CREATE TABLE test (id INTEGER PRIMARY KEY, content);
  INSERT INTO test VALUES (NULL, 'Hello World');
  INSERT INTO test VALUES (NULL, 'Hello Lua');
  INSERT INTO test VALUES (NULL, 'Hello Sqlite3')
]]
 
print( "version " .. sqlite3.version() ) -- SQLite의 버전을 터미널에 프린트한다.
 
for row in db:nrows("SELECT * FROM test") do  -- 모든 데이터를 화면에 출력한다.
  local t = display.newText(row.content, 20, 30 * row.id, null, 16)
  t:setTextColor(255,0,255)
end

소스 설명은 옆에 주석으로달았습니다.

두번째 줄에서 보시듯이 이 소스는  디비를 메모리에서 만들도록 했습니다.
그래서 재 실행하면 디비가 없어지고 다시 새로 만들기 때문에 항상 저 위에 3개의 문자만 나옵니다.

아래 샘플 코드는 메모리가 아니라 디바이스에 디비를 만듭니다.

--Include sqlite
require "sqlite3"
--Open data.db.  If the file doesn't exist it will be created
local path = system.pathForFile("data.db", system.DocumentsDirectory)
db = sqlite3.open( path )  
 
--Handle the applicationExit event to close the db
local function onSystemEvent( event )
        if( event.type == "applicationExit" ) then             
            db:close()
        end
end
 
 
--Setup the table if it doesn't exist
local tablesetup = [[CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, content, content2);]]
print(tablesetup)
db:exec( tablesetup )
 
--Add rows with a auto index in 'id'. You don't need to specify a set of values because we're populating all of them
local testvalue = {}
testvalue[1] = 'Hello'
testvalue[2] = 'World'
testvalue[3] = 'Lua'
local tablefill =[[INSERT INTO test VALUES (NULL, ']]..testvalue[1]..[[',']]..testvalue[2]..[['); ]]
local tablefill2 =[[INSERT INTO test VALUES (NULL, ']]..testvalue[2]..[[',']]..testvalue[1]..[['); ]]
local tablefill3 =[[INSERT INTO test VALUES (NULL, ']]..testvalue[1]..[[',']]..testvalue[3]..[['); ]]
db:exec( tablefill )
db:exec( tablefill2 )
db:exec( tablefill3 )
 
--print the sqlite version to the terminal
print( "version " .. sqlite3.version() )
 
--print all the table contents
for row in db:nrows("SELECT * FROM test") do
  local text = row.content.." "..row.content2
  local t = display.newText(text, 20, 30 * row.id, null, 16)
  t:setTextColor(255,0,255)
end
 
--setup the system listener to catch applicationExit
Runtime:addEventListener( "system", onSystemEvent )

이렇게 디바이스에 디비를 만들면 맨 마지막 줄처럼 앱이 끝날 때 디비를 close 시키기 위해 리스너를 달고 onSystemEvent( event ) 함수처럼 db를 close 시킵니다.


코로나에서는 이 SQLite 관련해서 자세하게 사용법을 알려주지 않더라구요.
그리고 SQLite의 여러 신택스들도 사용하면서 잘 실행이 안되는 것 같구요.

그래서 저는 데이터를 다룰 때 select * 을 해서 모든 데이터를 가져오고 이 데이터들을 코로나의 배열(테이블)에 넣어서 사용했습니다.

function fetchAll()
    local r = {}
    local i = 1;
   
    for row in db:nrows("SELECT * FROM table") do
        --print("id = " .. row.id .. " content = " .. row.content );
        local id = row.id;
        local contnt = row.content;

        r[i] = {}
        r[i].id = id
        r[i].content = contnt
        i = i+1;
    end

return r; -- 모든 데이터와 count(*) 값을 리턴한다.   
end

이렇게 데이터를 r이라는 테이블(배열) 에 담아서 리턴하는 함수 하나 만들어 놓고 사용합니다.
코로나는 간편하게 테이블(배열)을 control 할 수 있어서 이게 더 편하더라구요.

물론 SQLite를 통해서 제공하는 기능을 잘 알면 그걸 쓰면 더 편할 수도 있겠지만요.

신택스는 제가 보니까 아래 처럼 두가지가 있더라구요.
    db:exec[[
        INSERT INTO table VALUES (NULL, 'contents contents contents');
    ]]
이렇게 직접 쿼리를 실행하는 것 하구 아래처럼 변수에 쿼리를 담아서 실행하는 것도 있습니다.

    local insertRule =[[INSERT INTO table VALUES (NULL, ']]..contents..[[');']]
    db:exec( insertRule )

    local updateRule =[[update table SET content = ']] ..content .. [['WHERE id = ']].. id .. [[';']]
    db:exec( updateRule )

자 이렇게 DB control에 필요한 connect, select,insert,update,delete,drop, db close 기능에 대해서 배웠구요.
이 데이터들을 코로나 배열(테이블)에 넣어서 사용하는 법도 배웠습니다.

다음 시간에는 file control에 대해서 살펴 볼께요.

반응형


반응형
어제에 이어서 계속 하겠습니다.

어제까지 한 소스는

--import the table view library
local tableView = require("tableView")
display.setStatusBar( display.HiddenStatusBar )

--local background = display.newImage("hutbg.png")

function listButtonRelease( event )
    self = event.target
    local id = self.id
    print(self.id)
end

local data = {
"감자떡",
"생선구이",
"된장찌개",
"막걸리",
"소주",
"동동주"
}

local myList = tableView.newList{
    data = data,
    default="listItemBg.png",
    onRelease=listButtonRelease,
}

여기까지 였습니다.

오늘은 여기에 이미지를 넣어 보겠습니다.
아래 이미지들을 받으세요.

제가 먹고 싶은 것 들인가 봐요.


이 그림들을 다 받으셨으면 이제 코딩을 시작하겠습니다.



우선 data 부터 바꾸겠습니다.
local data = {감자떡,생선구이,된장찌개,막걸리,소주,동동주}

이것을
local data = {}

--setup each row as a new table, then add title, subtitle, and image
data[1] = {}
data[1].title = "감자떡"
data[1].subtitle = "100% 감자로 만든 강원도 특산품"
data[1].image = "potato.png"

data[2] = {}
data[2].title = "생선구이"
data[2].subtitle = "맛있는 고갈비"
data[2].image = "grillfish.png"

data[3] = {}
data[3].title = "된장찌개"
data[3].subtitle = "구수한 된장찌개"
data[3].image = "soy.png"

data[4] = {}
data[4].title = "막걸리"
data[4].subtitle = "몸에 좋은 막걸리"
data[4].image = "mak.png"

data[5] = {}
data[5].title = "소주"
data[5].subtitle = "짜르르 몸에 퍼지는 소주"
data[5].image = "soju.png"

data[6] = {}
data[6].title = "동동주"
data[6].subtitle = "고향의 맛 동동주"
data[6].image = "dong.png"

이렇게 바꿔주세요.

이렇게 하고 실행 하면 아래와 같이 에러가 납니다.

잘 보시면 아시겠지만 첫번째 줄 디폴트 이미지를 뿌려주고나서 데이터를 뿌릴때 에러가 납니다.
데이터가 여러개라서 그런데요.
이렇게 한 row에 여러 텍스트라든지 혹은 텍스트 + 이미지를 표시하시려면 callback을 사용합니다.
그리고 이 callback에 있는 object들은 하나의 group으로 관리하게 됩니다.
그럼 myList를 아래와 같이 고쳐 보세요.

local myList = tableView.newList{
    data = data,
    default="listItemBg.png",
    onRelease=listButtonRelease,
    callback = function( row )
        local g = display.newGroup()
        return g  
    end
}

이제 에러는 안 나죠?
대신 callback 안에 어떤걸 표시할지 알려주지 않아서 그냥 row 배경이미지만 6개 나옵니다.

callback을 아래와 같이 채워주세요.
    callback = function( row )
        local g = display.newGroup()
            local img = display.newImage(row.image)
            g:insert(img)
            img.x = math.floor(img.width*0.5 + 6)
            img.y = math.floor(img.height*0.5)

            local title =  display.newText( row.title, 0, 0, native.systemFontBold, 14 )
            title:setTextColor(255, 255, 255)
            g:insert(title)
            title.x = title.width*0.5 + img.width + 6
            title.y = 30

            local subtitle =  display.newText( row.subtitle, 0, 0, native.systemFont, 12 )
            subtitle:setTextColor(255,255,255)
            g:insert(subtitle)
            subtitle.x = subtitle.width*0.5 + img.width + 6
            subtitle.y = title.y + title.height + 6
        return g  
    end

우선 이미지를 표시하고 group에 insert시키고 x,y 좌표를 설정해 줍니다.
title과 subtitle도 마찬가지로 합니다.

자 이제 제대로 ListView(TableView) 가 나오죠?

여기다가 배경 화면을 은은하게 깔겠습니다.

local background = display.newImage("hutbg.png")
background.alpha = 0.4

이렇게 간단하게 이미지 하나 불러오고 투명도를 약간 줬습니다.



이번 TableView 모든 소스와 이미지 파일 입니다.

받으셔서 참고 하세요.

그럼 더 신나는 코딩을 위해 다음 시간에도 새로운 걸 배울께요....
반응형


반응형
원래 오늘은 SQLite를 할 까 했는데요.
이건 나중에 하고 우선 List View (Table View)를 할께요.
제가 했던 안드로이드에서는 이걸 List View라고 했는데 아이폰쪽에서는 Table View라고 하나봐요.

우선 List View에서는 아래와 같은 파라미터들이 사용 됩니다.
data. 값들을 가지고 있는 테이블. 각 row별로 표시될 값들
default.  row의 배경 화면이 될 이미지. touch 영역 설정 가능
backgroundColor. R,G,B 값으로 배경 색을 정할 수 있음
callback. 각 row에 데이터를 어떻게 display할까를 정의하는 함수. 테이블의 각 값들은 callback함수의 할당 된 'item' 인자와 관련되서 사용됨.
over. 터치 시 보일 이미지
onRelease(optional). row가 tap된이후에 일어날 action을 정의한 함수의 이름
top. list의 위쪽 영역. 리스트가 시작하고 다시 돌아오는 포지션
bottom. 리스트의 아래쪽 영역. 스크롤이 초과해서 진행 되면 이쪽으로 돌아온다.
cat. 각 아이템의 카테고리 값을 저장하기 위해 사용되는 테이블 키 이름.
order. cat의 option 항목. 임의의 정렬을 지정할 수 있다.

아래와 같은 메소드들이 있습니다.

* myList:addScrollBar() and myList:removeScrollBar()
스크린에 스크롤바를 넣거나 없앤다.

* myList:addOnTop(object, xVal, yVal)
리스트에 처음에 object를 추가한다. (search bar나 text 같은)

* myList:addOnBottom(object, xVal, yVal)
리스트의 마지막에 object를 추가한다.

* myList:scrollTo(yVal, timeVal)
리스트를 다이나믹하게 움직이도록 한다.

* myList:cleanUp()
리스트를 없애고 메모리를 풀어준다. 그리고 모든 이벤트 리스너들을 정지시킨다.

그럼 이제 실제로 table view (List View)를 사용해 보겠습니다.
우선 tableView.lua 파일이 있어야 됩니다.
아래 파일을 다운 받으세요.

그리고 이 파일이 있는 폴더에 main.lua 파일을 만드세요.
우선 아래와 같이 tableView.lua 파일을 사용하겠다고 선언합니다.

--import the table view library
local tableView = require("tableView")

일단 여기서 편의상 아이폰의 statusbar를 없앨께요.
display.setStatusBar( display.HiddenStatusBar )

아주 간단한 테이블을 만들어 보겠습니다.
local data = {
"감자떡",
"생선구이",
"된장찌개",
"막걸리",
"소주",
"동동주"
}

이 데이타들을 아래와 같이 해 보세요.
그리고 tableView.lua에 있는 List View 메소드를 선언합니다.
local myList = tableView.newList{data=data}

실행해 보면 이렇게 나옵니다.
글자를 누르고 드래그 하듯이 움직이면 터미널처럼 touch: began moved, ended 가 나옵니다.
이걸 보면 touch 리스너가 달려있고 began,moved,ended 이벤트시 어떤 동작을 지정할 수 있다는 걸 알 수 있겠죠?
그리고 드래그를 하지 않고 그냥 tap 만 하면 아래와 같이 나옵니다.

터미널에 보면 onRelease 필드가 nil 이라고 하면서 에러메세지가 나오죠?

그럼 이 newList안에 아래와 같이 추가 하겠습니다.
onRelease=listButtonRelease,
그리고 그 위쪽에 listButtonRelease 함수를 아래와 같이 만들어 주세요.
function listButtonRelease( event )
    self = event.target
    local id = self.id
    print(self.id)
end

이렇게 하면 각 row를 클릭하면 에러가 나지 않고 그 id가 터미널에 출력 됩니다.
여기까지 하면 가장 간단한 Table View(List View)가 완성 된 겁니다.

좀 더 그럴듯 하게 리스트 뷰를 만들어 볼까요?
아래 이미지를 다운 받으세요.

그리고 myList 에 default="listItemBg.png", 를 추가해 보세요.
local myList = tableView.newList{
    data = data,
    default="listItemBg.png",
    onRelease=listButtonRelease,
}

그럼 이와 같이 그럴듯한 ListView (TableView) 가 됩니다.

자 오늘은 코로나 SDK에서 ListView(TableView) 표시하기 가장 기초적인 부분을 다뤄봤습니다.

다음시간에는 이 글 첫번째에서 소개했던 여러 파라미터들하고 메소드들을 사용해서 좀 더 다이나믹한 효과를 내는 방법을 알아보겠습니다.


반응형


반응형
오늘 공부할 Tables and Arrays 는 양이 매우 짧습니다.
그리고 제가 이 글을 회사에 1시간 일찍 출근해서 쓰는데요.
테이블 관련한 예제는 금방 만들어내기도 힘들고 해서 따로 예제를 올려드리지 못 할것 같구요.
그래서 오늘은 그냥 원문을 번역해 볼까 합니다.

아래 주소에서 복사해 왔습니다.
http://developer.anscamobile.com/content/tables-arrays

Tables and Arrays

Tables in Lua implement associative arrays. That is, they can be indexed not just with numbers, but also with strings or any other value of the language, except nil.

루아의 테이블은 배열 역할을 한다. 숫자값으로만이 아니라 스트링이나 다른 문자로도 인덱스 할 수 있다는 의미다. 단 nill은 제외한다.

This library provides generic functions for table manipulation. It provides all its functions inside the table table.

이 라이브러리는 테이블을 다루는 기능을 제공한다. 이 기능들은 table 이라는 테이블을 통해서 제공한다.

Most functions in the table library assume that the table represents a numerically-indexed array or list. For these functions, when we talk about the "length" of a table we mean the result of the length operator.

테이블 라이브러리의 대부분의 기능들은 숫자로 인덱스된 배열이나 리스트의 기능을 수행한다. 이 기능과 관련 우리가 테이블의 length라고 말하는 것은 length 오퍼레이터에 의해 받아온 결과를 말하는 것이다.

table.concat (table [, sep [, i [, j]]])

Given an array where all elements are strings or numbers, returns table[i]..sep..table[i+1] ... sep..table[j]. The default value for sep is the empty string, the default for i is 1, and the default for j is the length of the table. If i is greater than j, returns the empty string.

테이블 내에 있는 문자나 숫자들을 table[i]..sep..table[i+1] ... sep..table[j] 식으로 리턴한다.  sep의 디폴트 값은 empty string이고 i는 1, j 는 테이블의 length이다. i가 j 보다 크면 empty string을 반환한다.

table.insert (table, [pos,] value)

Inserts element value at position pos in table, shifting up other elements to open space, if necessary. The default value for pos is n+1, where n is the length of the table, so that a call table.insert(t,x) inserts x at the end of table t; however, if you are inserting at the end of the table, it is faster to use the length operator: t[#t + 1] = x.

테이블의 pos 위치에 해당 value를 삽입한다. 필요하면 다른 값들은 위치를 이동하게 된다. pos의 디폴트는 n+1이다. (n은 테이블의 length). 예를 들어 table.insert(t,x)라고하면 x를 테이블 t의 맨 마지막 요소로 삽입하게 된다.

table.maxn (table)

Returns the largest positive numerical index of the given table, or zero if the table has no positive numerical indices. (To do its job this function does a linear traversal of the whole table.)

해당 테이블에 있는 값들의 갯수를 리턴하게 된다. 값이 없으면 0를 리턴한다.

table.remove (table [, pos])

Removes from table the element at position pos, shifting down other elements to close the space, if necessary. Returns the value of the removed element. The default value for pos is n, where n is the length of the table, so that a call table.remove(t) removes the last element of table t.

pos위치에 있는 값 remove한다. 필요하면 다른 값들의 위치를 이동한다. 그리고 remove된 값을 리턴한다. pos의 디폴터 값은 n이다. table.remove(t) 하면 테이블 t의 마지막 값이 remove된다.

table.sort (table [, comp])

Sorts table elements in a given order, in-place, from table[1] to table[n], where n is the length of the table. If comp is given, then it must be a function that receives two table elements, and returns false when the first is less than the second (so that not comp(a[i+1],a[i]) will be false after the sort). If comp is not given, then the standard Lua operator < is used instead.

주어진 조건으로 테이블 내의 값들을 sorting한다. comp 값이 주어지면 두개의 테이블 값들을 받게 된다. 만약 첫번째가 두번째보다 적으면 false를 리턴한다. comp가 주어지지 않으면 Lua의 standard operator 인 < 가 사용된다.

The sort algorithm is not stable; that is, elements considered equal by the given order may have their relative positions changed by the sort.

소팅은 주어진 조건에 맞게 이루어지므로 항상 조건이 고정돼 있는 것은 아니다.

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

코로나 SDK에서의 테이블(배열)에 대해 훑어 봤습니다.

다음 주제는 Math인데요. 이것도 Math관련 함수를 죽 정리해 놓은것 뿐이니까 이것도 간단히 정리하겠습니다.

원문은 아래 주소에 있습니다.

http://developer.anscamobile.com/content/math

This library is an interface to the standard C math library. It provides all its functions inside the table math.

이 라이브러리는 standard C 의 math 라이브러리를 사용한다. 이 기능들은 math라는 테이블을 통해 제공된다.

math.abs (x)

Returns the absolute value of x.

x의 절대값을 리턴한다.

math.acos (x)

Returns the arc cosine of x (in radians).

x의 원호 코사인을 리턴한다.

math.asin (x)

Returns the arc sine of x (in radians).

x의 원호 사인을 리턴한다.

math.atan (x)

Returns the arc tangent of x (in radians).

x의 원호 탄젠트를 리턴한다.

math.atan2 (y, x)

Returns the arc tangent of y/x (in radians), but uses the signs of both parameters to find the quadrant of the result. (It also handles correctly the case of x being zero.)

y/x의 원호 탄젠트를 리턴한다.

math.ceil (x)

Returns the smallest integer larger than or equal to x.

x보다 크거나 같은 최소값의 인티저를 리턴한다.

즉 소수값을 자연수로 고쳐준다.

math.cos (x)

Returns the cosine of x (assumed to be in radians).

x의 코사인을 리턴한다.

math.cosh (x)

Returns the hyperbolic cosine of x.

x의 쌍곡선 코사인을 리턴한다.

math.deg (x)

Returns the angle x (given in radians) in degrees.

x의 각도를 리턴한다.

math.exp (x)

Returns the value ex.

ex의 값을 리턴한다.

math.floor (x)

Returns the largest integer smaller than or equal to x.

x보다 작거나 같은 수 중 가장 큰 자연수를 리턴한다.

math.fmod (x, y)

Returns the remainder of the division of x by y that rounds the quotient towards zero.

x를 y로 나눈 값의 나머지를 리턴한다.

math.frexp (x)

Returns m and e such that x = m2e, e is an integer and the absolute value of m is in the range [0.5, 1) (or zero when x is zero).

x=m2e 같은 m,e 를 리턴한다. e는 m의 자연수이고 절대값이다. m은 [0.5,1] 사이에 있다.

math.huge

The value HUGE_VAL, a value larger than or equal to any other numerical value.

다른 수들보다 큰 수.

math.ldexp (m, e)

Returns m2e (e should be an integer).

m2e를 리턴한다. (e는 자연수라야 한다.)

math.log (x)

Returns the natural logarithm of x.

x의 로그를 리턴한다.

math.log10 (x)

Returns the base-10 logarithm of x.

x의 -10로그를 리턴한다.

math.max (x, ...)

Returns the maximum value among its arguments.

괄호 안의 값 들 중 가장 큰 수를 리턴한다.

math.min (x, ...)

Returns the minimum value among its arguments.

괄호 안의 값들 중 가장 작은 수를 리턴한다.

math.modf (x)

Returns two numbers, the integral part of x and the fractional part of x.

integral x와 팩토리얼 x 두개 값을 리턴한다.

math.pi

The value of pi.

파이 값

math.pow (x, y)

Returns xy. (You can also use the expression x^y to compute this value.)

xy를 리턴한다.

math.rad (x)

Returns the angle x (given in degrees) in radians.

각도 x의 라디안 값을 리턴한다.

math.random ([m [, n]])

This function is an interface to the simple pseudo-random generator function rand provided by ANSI C. (No guarantees can be given for its statistical properties.)

랜덤 값을 구한다.

When called without arguments, returns a uniform pseudo-random real number in the range [0,1). When called with an integer number m, math.random returns a uniform pseudo-random integer in the range [1, m]. When called with two integer numbers m and n, math.random returns a uniform pseudo-random integer in the range [m, n].

m,n이 주어지지 않으면 디폴트로 (0,1)의 값으로 계산한다. m이 주어지면 [1,m]으로 계산한다. m,n모두 주어지면 범위는 [m,n]이 된다.

math.randomseed (x)

Sets x as the "seed" for the pseudo-random generator: equal seeds produce equal sequences of numbers.

math.sin (x)

Returns the sine of x (assumed to be in radians).

x의 사인 값을 리턴한다.

math.sinh (x)

Returns the hyperbolic sine of x.

x의 쌍곡선 사인 값을 리턴한다.

math.sqrt (x)

Returns the square root of x. (You can also use the expression x^0.5 to compute this value.)

x의 square root 값을 리턴한다.

math.tan (x)

Returns the tangent of x (assumed to be in radians).

x의 탄젠트 값을 리턴한다.

math.tanh (x)

Returns the hyperbolic tangent of x.

x의 쌍곡선 탄젠트 값을 리턴한다.

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

조금 골치 아픈 공식들이죠?
저는 spin the bottle  게임 만들 때 테이블 관련해서는 table.maxn(table)을 사용했었구요. 병돌리기 위해 두 점 사이의 각도를 구하기 위해 radian구하는 공식과 각도 구하는 공식 등을 사용했는데요.
그건 위에 있는 함수를 사용하지는 않고 그냥 공식이 있는 함수를 만들어서 사용했었습니다.
위에 있는 수학 함수 중에는 math.ceil 이라던가 math.random 정도만 자주 쓰는 거 같애요.
아무래도 제 수학 실력과 프로그래밍 실력이 딸려서 그러겠죠?
게임 만들 때 이런 수학 함수 잘 사용하면 좋다고 그러던데...

오늘은 Corona SDK 에서의 배열인 table과 standard C 언어에서 가져온 코로나 SDK 의 수학함수에 대해서 알아봤습니다.

다음시간엔 Corona에서의 SQLite 데이타 베이스 사용법에 대해 다루겠습니다.

감사합니다.

반응형

Data and Files 첫번째 시간 - String -

2011. 10. 11. 22:47 | Posted by 솔웅


반응형

코로나에서 String을 다루는 방법이 첫번째 살펴볼 주제있니다.
Data and Files 에는 String, Table (Array), Math, Database (SQLite), Files, Crypto 등의 이슈가 있습니다.

첫번째 시간으로 String 인데요.
오늘 배울 부분들을 제가 연습하느라고 샘플 코드를 작성해서 공부했었는데요.
이것을 올려드릴께요.

오늘 글을 보면서 이 샘플코드에 직접 이것 저것 바꿔보면서 출력되는 것을 보면 좀 더 이해가 쉬울것 같습니다.


이 소스를 돌려보면 터미널에 결과값들이 찍힙니다.

String Manipulation

String Library 는 string 을 다루는 함수들을 제공해 줍니다.
루아에서 스트링을 인덱스 할 때 첫번째 문자는 포지션 1 입니다. (C 나 자바와 같이 0이 아닙니다.)
이 숫자는 마이너스도 허용합니다. 이 경우 스티링 맨 마지막에서부터 뒷쪽으로 검색하게 됩니다.
그러니까 마지막 문자가 -1이 됩니다.
String library는 테이블 스트링으로 이러한 함수기능을 제공합니다. 또한 스트링 테이블의 _index 필드에 스트링에 대한 메타테이블도 세팅 되어 있습니다. 이것을 개발자들은 객체 지향 개념으로 이용할 수 있습니다. 예를 들어 string.byte(s,i)는 s:byte(i)로 바꿔도 결과 값은 같습니다..

string.byte(s[,i[,j]])
문자 s[i],s[i+1] 문자의 내부 numerical code가 리턴 됩니다. 디폴트는 1 입니다. j의 디폴트 값은 i값입니다. 이 numerical code는 서로 다른 플랫폼간의 이동 및 공유가 되지 않습니다.

string.char(...)
0 이상의 integer를 Receive 받습니다. argument 숫자 길이 만큼 스트링이 리턴 됩니다. 근데 이건 제가 이것 저것 넣어 봤는데.. 결과값이 안 나오더라구요. 혹시 여러분 중에 무엇인지 아시는 분 댓글 부탁드립니다.

string.find(s,pattern[,init[,plain]])
스트링 s내에서 패턴에 맞는 첫번째를 찾습니다. 매치 되는 것을 찾으면 s의 인텍스를 리턴합니다. 없으면 nil을 리턴합니다. 세번째 옵션으로 있는 init 검색을 시작할 위치를 나타냅니다. 디폴트는 1이고 마이너스 값도 올 수 있습니다. 네번째 plain이 false일 경우 pattern matching 이 turn off 됩니다. plain이 있으면 init은 반드시 있어야 됩니다.
패턴이 검색 되면 이 검색된 패턴이 리턴됩니다.

string.format(formatstring,...)
매치 되면 미리 formatted 된 버전이 리턴됩니다. format string은 일반 C 함수의 printf 와 유사한 기능이 있습니다. 다른 점은 옵션들 입니다. l,L,n,p,h 는 support 되지 않습니다. 그리고 q 라는 옵션이 제공 됩니다. q는 루아 번역기가 읽기 쉬운 형태로 스트링을 포맷합니다.

예를 들어
  string.format('%q', 'a string with "quotes" and \n new line')

      "a string with \"quotes\" and \
      new line"
로 포맷 됩니다.

c, d, E, e, f, g, G, i, o, u, X, and x 옵션은 argument로 숫자가 들어가게 됩니다. 그리고 q와 s 는 문자가 들어가게 됩니다.

샘플코드에 이런 패턴들을 다 넣어 봤습니다. 결과값들이 다 다르게 나오는데 어떤건지 잘 와 닿지가 않네요. 실제 상황에서는 어떻게 써 먹는 건지도 모르겠구요.
제 프로그래밍 경력이 많지 않고 또 깊지 않아서 잘 모르는 것 같습니다. 혹시 이걸 어느 경우에 써 먹을 수 있는지 아시는 분들 조언 부탁 드립니다.

string.gmatch(s.pattern)
iterator 함수가 리턴 됩니다. 검색된 곳의 그 다음 capture 가 리턴 됩니다. 패턴이 없으면 전체가 매치된 것으로 나옵니다.
예를 들어
     s = "hello world from Lua"
     for w in string.gmatch(s, "%a+") do
       print(w)
     end
이것은 스트링 s의 모든 것을 iterate 합니다. 그리고 한줄당 하나씩 프린트 됩니다.
아래 예제는 key=value 의 모든 쌍들을 collect 합니다.
     t = {}
     s = "from=world, to=Lua"
     for k, v in string.gmatch(s, "(%w+)=(%w+)") do
       t[k] = v
     end

샘플코드에 있으니까 직접 결과 값을 확인 해 보세요.

string.gsub(s,pattern,repl[,n])
이건 해석하거나 설명하기 좀 복잡하네요.
아래 샘플을 보시죠.
     x = string.gsub("hello world", "(%w+)", "%1 %1")
     --> x="hello hello world world"
     
     x = string.gsub("hello world", "%w+", "%0 %0", 1)
     --> x="hello hello world"
     
     x = string.gsub("hello world from Lua", "(%w+)%s*(%w+)", "%2 %1")
     --> x="world hello Lua from"
     
     x = string.gsub("home = $HOME, user = $USER", "%$(%w+)", os.getenv)
     --> x="home = /home/roberto, user = roberto"
     
     x = string.gsub("4+5 = $return 4+5$", "%$(.-)%$", function (s)
           return loadstring(s)()
         end)
     --> x="4+5 = 9"
     
     local t = {name="lua", version="5.1"}
     x = string.gsub("$name-$version.tar.gz", "%$(%w+)", t)
     --> x="lua-5.1.tar.gz"

좀 헛갈리네요. 저도 소스를 잘 분석해 봐야겠어요.
뭐 그렇게 쓸일이 있을까? 싶지만요.

아래 함수들은 많이 쓰일 것 같네요.

string.len(s)
s의 length를 반환합니다. 아무 값도 없으면 length는 0 입니다. embeded 0도 카운트 됩니다. 그래서 "a\000bc\000" 의 length 는 5 입니다.

string.lower(s)
스트링을 모두 소문자로 바꿉니다.

string.match(s,pattern[,init])
패턴에 맞는 첫번째 조건을 찾습니다. 찾으면 찾은 값을 리턴해 줍니다. 없으면 nil을 리턴해 줍니다. 패턴이 아무것도 없으면 모든 문자를 리턴합니다. 세번째 옵션값인 init 은 검색을 시작할 위치를 표시합니다.

string.rep(s,n)
n개의 수만큼 s를 표시합니다.

string.reverse(s)
s의 거꾸로 된 값을 리턴합니다.

string.sub(s,i[,j])
i 번째에서 j번째까지의 문자를 표시합니다. i,j는 마이너스가 될 수 있습니다. j가 없으면 -1이 디폴트 입니다. string.sub(s,1,j)는 j에 -1값이 들어가고 string.sub(s,-i)는 length i의 suffix를 리턴합니다.

string.upper(s)
모든 문자를 대문자로 바꿔 줍니다.

### Patterns

Character Class

캐릭터 클래스는 문자들의 세트를 표현합니다.

. (dot) 모든 문자를 표시한다.
%a : 모든 문자를 표시한다.
%c : control character들을 표시한다.
%d : 숫자들을 표시한다.
%l : 소문자를 표시한다.
%p : 기능 문자들을 표시한다.
%s : 스페이스를 표시한다.
%u : 대문자를 표시한다.
%w : 알파뉴메릭을 표시한다.
%x : 헥사디시멀 숫자들을 표시한다.
%z : 0으포 표시되는 문자를 출력한다.
%x (x가 알파벳 문자가 아닌 경우) 캐릭터 x를 표시한다.
[set] : 세트 안의 모든 문자들의 집합 클래스를 표시한다.
%w 나 _%w는 모든 알파뉴메릭 문자와 언더스코어를 표시하고 [0-7] 은 8진수를 [0-7%l%-]은 8진수와 소문자와 -를 표시한다.
[^set] : 위에 해당하는 것의 반대로 표시한다.
 %a , %c 등으로 표시 되는데 이것을 대문자로 표시하면 그것의 반대로 표시한다.
예를 들어 %S 의 경우는 non-space 문자들을 출력한다.

pattern item
패턴 아이템은 아래와 같은 조건을 갖는다.
- 클래스의 싱글 캐릭터에 매치 되는 싱글 캐릭터 클래스
- *로 시작하는 싱글 캐릭터 클래스 0번 이상 반복되는 캐릭터. 이 반복되는 아이템들은 가능한 긴 시퀀스에 매치 된다.
- +로 시작하는 캐릭터. 1번 이상 반복되는 캐릭터. 이 반복되는 아이템들은 가능한 가장 긴 시퀀스에 매치된다.
- -로 시작하는 캐릭터. 0번 이상 반복되는 캐릭터. 이 반복되는 아이템들은 가능한 가장 짧은 시퀀스에 매치된다.
- ? 로 시작하는 캐릭터.
- %n n은 1~9 사이이다. n번째 무자를 말한다.
- %bxy x와 y 는 두개의 구별되는 문자들이다. x에서 시작해서 y에서 끝난다.

이렇게 코로나의 String 다루는 법을 정리했습니다.

다음엔 코로나에서 사용하는 배열인 tables 에 대해 알아보겠습니다.
반응형

코로나 애니메이션 Sprite Sheets.

2011. 10. 8. 20:53 | Posted by 솔웅


반응형
이 Sprite Sheets 는 조금 독특한 것 같습니다.
이전에 플래시 같은데서 썼던 개념인지 어떤지 모르겠지만.
여러 동작을 표현한 하나의 이미지 파일을 불러와서 특정 구역을 지정하면.
코로나는 그 특정구역별로 하나씩 이미지로 인식합니다.
그리고 그것을 Frame 단위로 display 하면서 애니메이션 효과를 냅니다.

글로만 쓰니까 잘 모르겠죠?

이렇게 하나의 이미지파일에 여러 동작을 그려 넣습니다.
개발자는 Sprite Sheets를 이용해서 저 하나하나별로 구역을 나눕니다.
그러면 코로나는 그 개별 구역을 개별 이미지로 인식해서 애니메이션을 연출합니다.

이렇게 하면 하나의 이미지 파일을 불러오니까 메모리 절약 효과도 있겠네요.
Loading 시간도 줄일 수 있겠구요.
그 외에 어떤 장점이 있는지 (MovieClips와 비교해서) 한번 알아보죠.

Corona 는 두가지 형태의 Sprite Sheets 를 제공합니다.
사이즈와 포지션이 일정한 것과 사이즈와 포지션이 일정하지 않은 것이 있습니다.

위에 있는 이미지는 uniformly-sized 프레임 입니다. (이 이미지는 코로나 샘플 파일의 sprites 폴더에 보면 있습니다.)

두번째는 외부 데이터 파일에서 그 크기와 위치를 지정하는 건데요. 이건 ZwoptexTexturePacker 같은 툴을 이용해서 만듭니다.

이 그림은 위에서 말한 샘플 코드 sprites 폴더 아래 있습니다. 그리고 외부 데이터 파일은 uma.lua 이름으로 있습니다.

코로나는 애니메이트 된 캐릭터에 대한 순서나 거꾸로 움직이기 등의 메소드를 제공하는 constructor들을 제공합니다. 이것은 frame-base 가 아니라 time-base로 주로 이용 됩니다.
샘플 코드 중 JungleScene 샘플 을 보시면 퓨마의 움직임하고 사람의 움직임이 약간 다릅니다. 정확히 속도가요.  이것은 두 객체가 다른 frame rate 으로 움직이기 때문입니다.


위에서 말한대로 이 JungleScene은 uniformly-sized frames를 사용했고 HorseAnimation 은 non-uniformly sized and positioned 방법을 사용했습니다.

혹시 다음에 시간 되면 이 샘플 소스들도 자세히 살펴 보도록 하겠습니다.
우선은 이 Sprite Sheets에 대한 진도를 계속 나가겠습니다.
주의 할 점은 이미지 사이즈가 디바이스의 맥시멈 사이즈를 넘으면 그림이 표시 되지 않습니다.
이 정보는 system.getinfo("maxTextureSize")를 통해서 얻을 수 있습니다.

이 sprite 효과를 사용하려면 아래와 같이 합니다.
require "sprite"
그리고 sprite.newSpriteSheet , sprite.newSpriteSheetFromData 이 두 함수는 새로운 sprite sheets를 생성합니다.

spriteSheet = sprite.newSpriteSheet("image.png", fromeWidth, fromeHeight)
이 코드는 uniformly-sized frames sprite sheet를 생성합니다.
spriteSheet = sprite.newSpriteSheetFromData("image.png", spriteData)
이 코드는 외부 파일에서 지정한 frame size와 포지션을 적용할 때 이용합니다.

외부 데이터 파일은 아래와 같은 형식으로 돼 있습니다.
-- test.lua
module (...)
 
function getSpriteSheetData()
 
        local sheet = {
                frames = {
                        {
                        name = "01.png",
                        spriteColorRect = { x = 38, y = 38, width = 50, height = 50 },
                        textureRect = { x = 2, y = 70, width = 50, height = 50 },
                        spriteSourceSize = { width = 128, height = 128 },
                        spriteTrimmed = true,
                        textureRotated = false
                        },
 
                        {
                        name = "02.png",
                        spriteColorRect = { x = 38, y = 36, width = 50, height = 52 }, 
                        textureRect = { x = 2, y = 242, width = 50, height = 52 }, 
                        spriteSourceSize = { width = 128, height = 128 }, 
                        spriteTrimmed = true,
                        textureRotated = false
                        },
                }
        }
        return sheet
end

위 소스는 이미지 파일을 두개의 이미지로 나누는데요. 위치와 사이즈 등을 설정하도록 돼 있습니다.

이 파일 (test.lua) 을 이용하려면 아래와 같이 합니다.
local sprite = require("sprite")
-- In this case, test.lua is exported from Zwoptex
local test = require("test.lua")
-- Method defined by test.lua that returns table data defining the sprites
local spriteData = test.getSpriteSheetData()
-- Load the sprite sheet in test.png using the sprite definitions from spriteData
local spriteSheet = sprite.newSpriteSheetFromData( "test.png", spriteData )

이 경우 밑에 두 줄은 아래와 같이 합칠 수도 있습니다.
local spriteSheet = sprite.newSpriteSheetFromData( "test.png", test.getSpriteSheetData() )

하나의 기본 이미지에 여러 캐릭터들이 들어 있을 때 관련 된 캐릭터들만 따로 세팅을 할 수 있습니다.
spriteSet = sprite.newSpriteSet(spriteSheet, startFrame, frameCount)

sprite에 추가 할 수도 있습니다.
sprite.add(spriteSet, "startFlying", startFrame,frameCount, time,[loopCount])

sprite sheet를 없애고 메모리에서 release시키려면 아래와 같이 합니다.
sprite.Sheet:dispose()
이 경우 removeSelf()도 불려 집니다.

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

si = sprite.newSprite( spriteSet )
sprite의 새로운 인스턴스를 만듭니다. 스프라이트는 Display Object입니다. 스프라이트는 한번에 하나의 애니메이션 시퀀스를 플레이 합니다.

si:prepare([sequence])
현재 플레이 되고 있는 애니메이션 시퀀스를 정지합니다. 현재 시퀀스를 바꿀수도 있고 첫번째 시퀀스의 프레임으로 갈 수도 있습니다.

si:play()
애니메이션 시퀀스를 플레이 합니다.현재의 프레임에서 시작합니다.
 
si:pause()
애니메이션을 정지시킵니다. 맨 마지막 display됐던 프레임이 남아 있게 됩니다.
 
si:addEventListener("sprite", listener)
스프라이트 인스턴스 애니메이션이 이벤트를 일으켰을 때 리스너에 통보 합니다.

이벤트는 리스너에게 다음과 같은 필드들을 전달합니다.

event.sprite
이벤트를 발생한 스프라이트; event.sprite.sequence 같이 현재 스프라이트의 프로퍼티들에 접근 할 수도 있습니다.

event.phase
phase는 다음과 같은 요소들이 있습니다.

"end" - 플레이를 멈춘다.
"loop" - 순차적으로 아니면 거꾸로 스프라이트를 루핑합니다.
"next" - 스프라이트의 다음 프레임이 플레이 됩니다.

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

여기까지 코로나의 Sprite Sheets로 애니메이션을 구현하는 방법에 대해 알아봤습니다.
근데 뭔가 어렵고 불편한 것 같은 느낌이 드네요.
뭔가 숙달이 안 되서 그런건가?

한번 다양한 방법으로 실습을 해 봐야겠습니다.
샘플을 보던지... 내가 직접 만들던지... 어쨌든 그래야 정확하게 내것이 되겠는데요.

일단 다음 주제는 데이터와 파일 단원에 들어가겠습니다.
SQLite (DB) 와 파일 (file) control도 하게 될 텐데요.

이 주제로 강좌를 진행하다가 sprite sheet 직접 실습해보고 sprite sheet 사용  방법을 알기 쉽게 한번 더 다룰께요.

그럼.......
반응형