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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
오늘은 우선 Function Listener와 Table Listener 에 대해서 알아보는 것으로 시작 하겠습니다.

local myListener = function( event )
        print( "Listener called with event of type " .. event.name )
end
Runtime:addEventListener( "touch", myListener )
Runtime:addEventListener( "enterFrame", myListener )

위 리스너는 function 리스너 입니다.
Runtime으로 시작했으니 Global 리스너네요. 하나는 touch에 걸고 다른 하나는 enterFrame에 리스너를 걸었습니다.
touch를 하면 "Listener called with event of type touch"가 나올것이고 가만히 있으면 touch대신 enterFrame이 계속 찍히게 될 겁니다.

두 이벤트 모두 myListener라는 함수를 호출하죠? 이건 함수 리스너입니다.
가끔 함수 리스너는 이 리스너가 발생할 때 특정 변수를 처리하는데 한계가 있습니다. 이럴 경우 object Listener (table Listener) 를 사용할 수 있습니다.

-- assume MyClass and MyClass:new() already exist
 
function MyClass:enterFrame( event )
        print( "enterFrame called at time: " .. event.time )
end
 
function MyClass:touch( event )
        print( "touch occurred at ("..event.x..","..event.y..")" )
end
 
local myObject = MyClass:new()
 
Runtime:addEventListener( "touch", myObject )
Runtime:addEventListener( "enterFrame", myObject )

object Listener 는 위와 같이 사용합니다.
(이 코드는 이미 MyClass가 생성 돼 있어야 실행 됩니다.)
처음 코드와 같이 Runtime으로 리스너를 달았지만 테이블 리스너를 사용해 MyClass:enterFrame, MyClass:touch 이렇게 특정 이벤트에 함수를 달기 때문에 enterFrame에만 있는 정보들 그리고 touch 에만 있는 정보들 등을 자유롭게 사용할 수 있습니다.

Runtime Events

Runtime Events에 대해 알아 보겠습니다.

enterFrame
이 enterFrame은 Runtime:addEventListener  API를 이용해서 작동시킬 수 있습니다. 각 frameTime마다 불려질 겁니다.

local myListener = function( event )
        print( "Listener called with event of type " .. event.name )
end
Runtime:addEventListener( "enterFrame", myListener )

아래와 같은 프로퍼티가 있습니다.
event.name : enterFrame 이라는 스트링이 반환 됩니다.
event.time : 앱 시작 부터 지금 까지의 시간이 밀리세컨드로 표시 됩니다.

System

앱 실행 중에 전화가 온다든가 하는 이유로 다른 일을 할 동안 앱이 계속 유지 되게 하는데 필요합니다.
아래와 같은 프로퍼티들이 있습니다.
event.name : system 스트링 반환
event.type - 아래와 같은 스트링이 반환됩니다.
 : applicationStart - 앱이 시작될 때 그리고 main.lua에ㅔ 있는 모든 코드가 실행 됐을 때 발생함
 : applicationExit - 유저가 앱을 끝낼때 실행 됨
 : applicationSuspend - 전화가 오거나 오랫동안 사용을 안 해서 화면이 까맣게 될 때 같이 앱이 계속 유지될 필요가 있을 때 발생합니다.
 : applicationResume - 앱이 다시 실행 될 때 발생합니다. 시뮬레이터에서는 시뮬레이터가 백그라운드에 있다가 다시 포그라운드로 올라 올 때 실행 됩니다.

Orientation

지난 시간에 예제를 통해서 봤는데요. 전화기의 방향이 바뀔 때 이벤트들이 발생합니다.
아래와 같은 프로퍼티들이 있습니다.
event.name : orientation
event.type : portrait, landscapeLeft, portraitUpsideDown, landscapeRight, faceUp, faceDown

accelerometer

이것은 전화기를 기울이는 대로 중력이 작용하도록 할 때 사용합니다.(제 경험상)
아래와 같은 프로퍼티들이 있습니다.
event.name : accelerometer
event.xGravity : x 축 의 중력 관련 가속도 acceleration
event.yGravity : y 축의 중력 관련
event.zGrabity : z 축의 중력 관련
event.xInstant : x 축의 순간 가속도 instantaneous
event.yInstant : y 축의 순간 가속도
event.zInstant : z 축의 순간 가속도
event.isShake : 전화기를 흔들어쓸 때

location (GPS)

GPS 하드웨어에 의해 발생되는 위치 이벤트 입니다.
event.name : location
event.latitude : 위도
event.longitude : 경도
event.altitude : 고도
event.accuracy : 정확도(몇 미터를 기준으로 파악할지 여부)
event.speed : 초당 미터 m/s 로 나타나는 순간 스피드
event.direction : 북쪽부터 시작하는 시계방향으로의 방향
event.time : location event의 UTC timestamp

에러가 났을 경우에는 아래 프로퍼티에 어떤 값이 할당 됩니다.
event.errorMessage : error description. 에러가 났을 때에만 나옵니다.
event.errorCode : 에러 메세지

heading (compass)

안드로이드에서는 event.magnetic 만 지원되고 event.geographic은 지원 되지 않습니다.
event.name : heading
event.geographic : geographic 북극을 기준으로 시계방향으로의 heading 방향
event.magnetic : magnetic 북극을 기준으로 시계방향으로의 heading 방향

memoryWarning

iOS에서만 지원되는 메모리 사용 관련 이벤트. 거의 5초 이내에 shut down 될 정도의 상황에서 발생.

local function handleLowMemory( event )
  print( "memory warning received!" )
end
 
Runtime:addEventListener( "memoryWarning", handleLowMemory )

Targeted Event

어떠한 단일 Target에 이벤트를 보냄

completion
오디오, 비디오 부분에서  예제로 다뤘었는데요. 오디오나 비디오가 완전히 끝났을 때 발생하는 이벤트 입니다.
event.name : completion

timer
event.name : timer
event.sorce : 타이머에 등록된 값
event.count : 타이머가 실행 된 횟수
event.time : 앱이 시작된 이후부터 지금까지의 시간

urlRequest

native.webPopup() 함수와 함께 등록되는 이벤트 입니다.
event.name : urlRequest
event.url : absolute URL
event.errorMessage : 에러 메세지 유저의 언어 세팅에 따라 표시 됨
event.errorCode : 에러 메세지 유저의 언어 세팅에 관계 없이 표시 됨

오늘은 빨리 작업할 일이 있어서 여기까지 밖에 정리하지 못하겠네요.
다음 시간에 Touch Events, Multi Touch Events 그리고 Timer에 대해 알아 보겠습니다.

감사합니다.

반응형


반응형
오늘 다룰 이슈는 코로나에서의 이벤트와 리스너 입니다.
이벤트, 리스너는 어떤 행위(이벤트) 가 일어났을 때 이를 감지(리스너) 하고 이에 대해 특정 행위를 하도록 하는 상호 작용 효과를 내는데 꼭 필요한 기능입니다.

글로벌 이벤트(Global Events)
어떤 이벤트 들은 광범위하게 적용 되는 겁니다. 예를 들어 enterFrame, system, orientatin등이 있습니다. 이미지,버튼,텍스트 같이 특정 객체(object)에 한정된 이벤트가 아니라 프로그램 전체적으로 영향이 있는 글로벌 이벤트 입니다.

local label = display.newText( "portrait", 0, 0, nil, 30 )
label:setTextColor( 255,255,255 )
label.x = display.stageWidth/2; label.y = display.stageHeight/2
 
local function onOrientationChange( event )
        label.text = event.type   -- change text to reflect current orientation
        -- rotate text so it remains upright
        local newAngle = label.rotation - event.delta
        transition.to( label, { time=150, rotation=newAngle } )
end
 
Runtime:addEventListener( "orientation", onOrientationChange )

위 소스 코드는 orientation 의 변화가 있으면 onOrientationChange 함수를 실행합니다.
핸드폰이 세워저 있으면 portrait 글자가 쓰여지고 옆으로 뉘어져 있으면 글자도 각도를 바꾸고 text도 바뀌는 함수 입니다.


orientationr과 관련된 cases는 상하좌우로 돌릴때랑 faceUp,faceDown 이렇게 6개가 있네요.


Local Events


Hit Events -  user가 스크린을 터치 하면 이 히트 이벤트가 생깁니다. 만약에 이 이벤트를 어떤 특정 객체(object)에 줬다면 이 hit 포인트의 좌표를 그 object가 가로 채서 사용하게 될 겁니다.


이벤트의 전달(전파)와 핸들링 (Propagation/Handling)

이 이벤트들은 특정한 순서에 따른 객체(objects)들을 통해 전파 됩니다. 디폴트로는 이벤트를 받기 위한 display hierarchy에 따른 첫번째 object가 그 이벤트를 갖게 될 겁니다. 쉽게말하면 일반적으로 가장 위에 display된 객체가 우선적으로 이벤트를 가로 채 가게 될 거라는 겁니다. 이 이벤트는 그 이벤트가 핸들(처리) 될 때까지 전파(전달) 될 겁니다. 그러니까 한 위치에 여러개의 객체(object)가 중복 돼 있고 그 위치를 hit 했다면 맨 위의 객체에게 전달 되고 그 객체가 핸들링을 하지 않으면 그 다음객체 또 핸들되지 않으면 그 다음 객체 ... 이런 순으로 이벤트가 각 객체들에 전달되고 맨 마지막에는 스크린 자체 객체(Runtime object)에 전달 될 겁니다.

만약에 겹쳐진 두개의 객체에서 핸들링 하도록 되 있다면 우선순위에 있는 객체에 이벤트가 전달되고 핸들링 되면 이 이벤트의 전달(전파)는 끝이 날 것이기 때문에 우선순위에 밀린 객체는 그 이벤트를 핸들링 할 수 없게 됩니다.


Overriding Propagation with Focus

Focus를 세팅함으로서 특정 오프젝트에 앞으로의 hit 이벤트를 redirect할 수 있습니다. 예를 들어 버튼을 터치 하면 다른 버튼 이미지로 바뀌도록 했을 경우를 상상해 보면, down 하면 이미지가 바뀌고 up 하면 원래 이미지로 돌아와야 되는데 user가 down한 후 up을 하지 않고 버튼 밖으로 나가서 up을 하게 되면 원래 이미지로 돌아오지를 못 합니다. 이 경우 앞으로 일어날 up이벤트를 이 버튼에 포커스를 맞추겠다는 의미로 이 setFocus 를 이용하면 어디에서  up을 하든지 이 버튼의 토글 기능은 제대로 작동 할 겁니다.

function button:touch( event )
        local phase = event.phase
        if "began" == phase then
                -- Subsequent touch events will target button even if they are
                -- outside the stageBounds of button
                display.getCurrentStage():setFocus( self )
        else
        ...
        end
        return true
end


Listener and Event Delivery


리스너는 함수이거나 테이블 객체 일 수 있습니다. 두 경우 모두 event 인수가 리스너에 전달 됩니다. 


함수 리스너(function Listener)

 local function listener( event )
   print("Call #"..event.count )
end
timer.performWithDelay(1000, listener, 5 )


테이블 리스너(Table Listener)

local listener = {}
function listener:timer( event )
   print("Call #"..event.count )
end
 
timer.performWithDelay(1000, listener, 5 )


위 소스코드는 timer 이벤트에서 listener라는 함수를 호출하고 있습니다.

위에것은 함수 리스너이고 아래것은 테이블 리스너 입니다.


Registering for Events


이벤트는 addEventListener라는 메소드를 통해서 등록합니다.  그리고 removeEventListener라는 메소드로 제거합니다.


    •    object:addEventListener( )
    •    object:removeEventListener( )


* Function Listener

local button =  display.newImage("button.png")
 
local function listener(event)
  print(event.name.."occurred")
  return true
end
 
button:addEventListener( "touch", listener )


* Table Listener

local button = display.newImage("button.png")
 
function button:touch(event)
  print(event.name.."occurred")
  return true
end
 
button:addEventListener( "touch", button )


위 두 소스 코드는 버튼 이미지에 이벤트리스너를 단 것입니다. 위에것은 함수 리스너이고 아래 것은 테이블 리스너 입니다.


객체가 아니라 시스템에 add하는 이벤트리스너는 아래와 같이 add합니다.


* Function Listener

local function listener(event)
  print(event.name.."occurred")
end
 
Runtime:addEventListener("enterFrame", listener )


* Table Listener

local listener = {}
 
function listener:enterFrame(event)
  print(event.name.."occurred")
end
 
Runtime:addEventListener("enterFrame", listener )


이제 이벤트 다는 법을 배웠습니다.

그런데 Function Listener와 Table Listener가 나오는데 두개의 차이점과 장단점이 무엇인지 궁금하지 않나요?


두 리스너의 차이점과 장단점에 대해서는 다음 글에서 다루겠습니다.

다음 글에서는 이 외에 이벤트의 종류와 timer에 대해서도 배워보겠습니다.



반응형

System과 OS Library

2011. 10. 31. 19:43 | Posted by 솔웅


반응형
오늘은 시스템과 operating system Library에 대해 알아 보겠습니다.


system.getInfo( param )
시스템 정보를 얻어오는 함수입니다.


시스템 함수를 사용해서 시스템 정보를 터미널에 뿌려주는 소스입니다.

아래 소스를 보면서 정리하겠습니다.

local sName = system.getInfo( "name" ) 
print("name = " .. sName);
디바이스의 이름을 출력합니다. iPhone에서는 iTunes에 나오는 이름이 출력됩니다.
local sModel = system.getInfo( "model" )
print("model = " .. sModel);
디바이스의 타입이 출력됩니다. (iPhone,iPad,iPhone Simulator,Droid,Galaxy Tab 등등)
local sDID = system.getInfo( "deviceID" )
print("deviceID = " .. sDID);
디바이스의 유니크한 아이디를 출력합니다.
local sEnv = system.getInfo( "environment" )
print("environment = " .. sEnv);
앱이 돌아가는 디바이스 환경을 출력합니다. 시뮬레이터인지 iOS인지 등등
local sPFN = system.getInfo( "platformName" )
print("platformName = " .. sPFN);
Mac OS X, Win, iPhone OS, Android 등 플랫폼(OS)의 이름을 출력합니다.
local sPFV = system.getInfo( "platformVersion" )
print("platformVersion = " .. sPFV);
플랫폼의 버전을 출력합니다.
local sVer = system.getInfo( "version" )
print("version = " .. sVer);
코로나 버전이 출력됩니다.
local sBul = system.getInfo( "build" )
print("build = " .. sBul);
local sTMU = system.getInfo( "textureMemoryUsed" )
print("textureMemoryUsed = " .. sTMU);
현재의 texture memory usage를 출력합니다.
local sMTS = system.getInfo( "maxTextureSize" )
print("maxTextureSize = " .. sMTS);
texture의 맥시멈 사이즈를 출력합니다.
local sArcInfo = system.getInfo( "architectureInfo" )
print("architectureInfo = " .. sArcInfo);
CPU architecture 정보를 출력합니다.
안드로이드는 주로 ARM, ARM Neon등이 출력되고 iOS는 iPhone1,1 iPhone1,2 등이 출력됩니다.

system.getPreference( category, name )

local sTime = system.getTimer()
print("system time = " .. sTime);
앱이 시작되고 난 다음의 시간이 출력됩니다.
local sURL = system.openURL( "http://coronasdk.tistory.com" )
if(sURL) then print("OK") else print("No") end
URL을 오픈합니다. 결과 값은 true,false가 출력됩니다.
local sPath = system.pathForFile( "main.lua" )
print("sPath = " .. sPath);
해당 파일이 있는 경로를 출력합니다.
-- Set the measurement interval to 50 Hz.
-- This makes the system take 50 measurements per second.
system.setAccelerometerInterval( 50 )
system.setIdleTimer( true )  -- enable (turn on) the idle timer
system.setLocationAccuracy( 10 )  -- set GPS accuracy to 10 meters
GPS정확도를 10미터로 합니다.
system.setLocationThreshold( 100 )  -- fire the location event every 100 meters
100미터마다 로케이션 이벤트를 줍니다.
system.vibrate()
진동을 줍니다.

System-defined directories

system.DocumentsDirectory -  persist하게 유지될 필요가 있는 파일에 사용됩니다.

system.TemporaryDirectory - 일시적으로 필요한 파일에 사용됩니다.

system.ResourceDirectory - 리소스가 있는 디렉토리 입니다. 다운 받거나 하게 되면 이곳에 파일이 저장 됩니다. 이곳에 있는 파일은 개발자 임의로 생성,제거,수정 하면 안됩니다. 앱이 실행 될 때 이곳의 정보를 읽기 때문에 이 정보가 맞지 않으면 앱이 실행되지 않을 수도 있습니다.

OS Library

다음은 OS 에 대한 정보 입니다.


아래 소스를 보면서 정리하겠습니다.
local clock = os.clock ()
print("clock = " .. clock);
CPU 타임 1초동안 프로그램이 사용한 대략적인 시간입니다.

local date = os.date( "*t" )    -- returns table of date & time values
print( date.year, date.month )  -- print year and month
print( date.hour, date.min )    -- print hour and minutes
print( os.date( "%c" ) )        -- print out time/date string: e.g., "Thu Oct 23 14:55:02 2010"
날짜를 포맷에 맞게 출력합니다.

local t1 = os.time()
 
-- Print the elasped time
local function dspTime()
        print( "Time elasped = " .. os.difftime( os.time(), t1) )
end
 
timer.performWithDelay( 2000, dspTime )  -- wait 2 second before calling

두 시간 동안의 차이를 출력합니다.

os.exit()   -- exit app
앱을 종료합니다.

local destDir = system.DocumentsDirectory  -- where the file is stored
local results, reason = os.remove( system.pathForFile( "apple.txt", destDir  ) )
 
if results then
   print( "file removed" )
else
   print( "file does not exist", reason )
end
--> file does not exist    apple.txt: No such file or directory
파일을 지웁니다.

local destDir = system.DocumentsDirectory  -- where the file is stored
local results, reason = os.rename( system.pathForFile( "orange.txt", destDir  ),
        system.pathForFile( "apple.txt", destDir  ) )
 
if results then
   print( "file renamed" )
else
   print( "file not renamed", reason )
end
--> file not renamed    orange.txt: No such file or directory

파일 이름을 새로 바꿉니다.

local t = os.date( '*t' )  -- get table of current date and time
print( os.time( t ) )      -- print date & time as number of seconds
--> 1287516614
t.min = t.min + 1         -- add one to the minute field
print( os.time( t ) )      -- print number of seconds (increases by 60 seconds)
-->  1287516674
현재의 시간을 출력합니다.

os.execute( "ls" )

해당 os 명령어를 실행 합니다.

오늘 사용했던 소스는 아래에 있습니다.

system과 OS에 관련한 함수들을 살펴 봤습니다.
잘 이용하면 앱 만드는데 꽤 유용하게 쓰일 수 있습니다.

궁금한 점은 댓글을 이용해 주세요.
그럼 다음에 뵙겠습니다.






반응형


반응형
지난번 Corona DOC 로 공부하면서 Sprite Sheets 는 직접 예제를 다루면서 한번 해 보기로 했죠?

오늘 직접 예제를 보면서 하겠습니다.


위 그림을 가지고 작업하겠습니다.
한개의 큰 이미지에서 부분만 잘라서 하나의 이미지처럼 사용하는 기법이 Sprite Sheets Animation 입니다.

이 애니메이션에서 가장 먼저 할 일은 sprite를 require하는 것이고 그 다음이 newSpriteSheet를 생성하는 겁니다.
이 newSpriteSheet를 하기 전에 개발자가 먼저 체크해 둬야 할게 있습니다.

sprite.newSpriteSheet( spriteSheetFile, [baseDir,]  frameWidth, frameHeight )
신택스는 위와 같은데요. spriteSheetFile은 이미지 이름이 들어가면 되구요. baseDir은 그냥 옵션 입니다. 그 다음이 sprite로 사용될 프레임의 너비와 높이를 넣어야 됩니다.
저 위 person.png 파일에서 보면 12개의 사람 이미지가 있는데 이 각 이미지의 너비와 높이를 말합니다.

아마 이걸 구할 수 있는 프로그램이 있을겁니다. 아니면 이걸 디자인한 디자이너에게 물어봐도 되구요. 그냥 포토샵 같은데서 값을 구해도 될 것 같은데...

저는 그냥 코로나에서 해 봤습니다.


보시면 x,y좌표가 각각 32.36이 나왔죠? 소스를 볼까요?

display.setStatusBar (display.HiddenStatusBar)
local _W,_H = display.contentWidth,display.contentHeight;

local bgImg = display.newImage ("background.png")
local person = display.newImage ("person.png")

local xLoc = display.newText("",_W/2, (_H/2) - 15, native.systemFontBold, 18);
local yLoc = display.newText("",_W/2, (_H/2) + 15, native.systemFontBold, 18);
xLoc:setTextColor(0,0,0)
yLoc:setTextColor(0,0,0)

    local function touchScreen(event)
        local x,y = event.x,event.y;
        local phase = event.phase;

        if phase == "began" then
            xLoc.text = "X= " .. x;
            yLoc.text = "Y= " .. y;
        elseif phase == "moved" then
            xLoc.text = "X= " .. x;
            yLoc.text = "Y= " .. y;
        elseif phase == "ended" then
            xLoc.text = "X= " .. x;
            yLoc.text = "Y= " .. y;
        end
    end

bgImg:addEventListener( "touch", touchScreen );

우선 작업하기 편하게 statusBar를 없애고 스크린 너비와 높이를 변수에 담습니다.
그리고 background.png와 person.png를 display합니다.
x,y좌표를 설정하지 않았으니까 디폴트로 0,0이 x,y좌표 일 겁니다.

그리고 가운데 좌표를 뿌려줄 텍스트를 빈칸으로 만들어 놓고 색을 검은색으로 설정했습니다.

맨 마지막 줄 보면 백그라운드 이미지에 리스너를 달았습니다.
그리고 touchScreen 함수를 보면요.
각 이벤트 마다(began,moved,ended) x,y좌표를 화면에 표시하도록 만들었습니다.

저는 마우스를 저 12개 사람 이미지 중 첫번째 뒷모습 이미지의 적당한 곳에 이동 시켰습니다.

그랬더니 x=32,y=36이 나왔네요. 전 이 값을 sprite의 너비와 높이로 사용 할 겁니다.

그럼 이제 본격적으로 sprite sheet animation을 만들어 볼까요?
들어가기에 앞서 4개의 이미지를 더 받아 보세요.

4개의 화살표 이미지 입니다. (이것도 사실 하나가지고 돌려가면서 쓸 수 있습니다. 메모리를 절약하시려면요.)

display.setStatusBar (display.HiddenStatusBar)
--> Hides the status bar

----------------------------------------------------------------------
--                                BASICS                                --
----------------------------------------------------------------------
require "sprite"
-- Very important!

local background = display.newImage ("background.png")
-- Sets the background

자 첫 부분은 편의상 statusBar를 없애는 코딩을 했습니다.
그리고 두번째로는 sprite sheets animation을 사용하기 위해서 sprite를 require 했습니다. 주석으로 아주 중요하다고 돼 있죠?
그 다음엔 백그라운드 이미지를 그렸습니다.

----------------------------------------------------------------------
--                                SPRITE                                --
----------------------------------------------------------------------
local herosheet = sprite.newSpriteSheet("person.png", 32, 36)
-- Our sprite sheet
-- 32 is the width of each "box", this is the image width divided by the number of images across
-- 36 is the height of each "box", this is the image height divided by the number of images down
   
    local heroset = sprite.newSpriteSet (herosheet, 1, 12)
    sprite.add (heroset, "heroleft", 10, 3, 300, 0)
    sprite.add (heroset, "heroright", 4, 3, 300, 0)
    sprite.add (heroset, "heroup", 1, 3, 300, 0)
    sprite.add (heroset, "herodown", 7, 3, 300, 0)
-- The sprite set uses images 1 to 12 (all of them)
-- "heroup" starts at image 1 and includes 3 frames. (In this case 1, 2, 3.)
-- The "300" indicates .3 seconds per frame, the "0" indicates it will repeat until we stop it.
   
    local hero = sprite.newSprite (heroset)   
    hero.x = 160
    hero.y = 200
-- We insert out hero sprite
   
    hero:prepare("herodown")
-- We prepare the sprite   

이제 sprite 이미지를 설정하겠습니다.
newSpriteSheet 신택스는 위에서 보셨죠?
person.png를 spriteSheet로 이용할 거고 각 프레임의 크기는 너비 32,높이 36을 할 겁니다.
그 다음 newSpriteSet을 합니다. 신택스는 아래와 같습니다.
sprite.newSpriteSet (spriteSheet, startFrame, frameCount)
위에 코드를 보면 스프라이트 세트로 herosheet을 쓸거고 프레임은 1번에서부터 12번까지 있다는 내용입니다.

sprite.add
sprite.add( spriteSet, sequenceName, startFrame, frameCount,time,[loopParam] )
파라미터를 보면 스프라이트 세트와 이름 그리고 시작 프레임과 프레임 숫자와 시간 그리고 루프 도는 횟수등이 들어갑니다.
위 코드를 보면
sprite.add (heroset, "heroleft", 10, 3, 300, 0)
heroset에 heroleft라는 이름으로 10프레임부터 3개 프레임을 0.3초 간격으로 표시를 하고 루프는 무한대라는 의미입니다.

sprite.newSprite
스프라이트의 새 인스턴스를 생성합니다. 한마디로 메모리 공간을 확보해 둔다는 의미겠죠. 스프라이트는 Corona DOC 강좌에서 살펴 봤듯이 DisplayObject 입니다.
신택스는 sprite.newSprite( spriteSet ) 입니다.

위 코드를 보면 heroset을 newSprite으로 정하고 x,y 위치를 지정했습니다.

prepare

스프라이트 인스턴스에서 실행되고 있는 애니메이션 시퀀스를 stop시킵니다. 그리고 이 시퀀스의 첫번째 프레임으로 갑니다. 루프 counter도 reset됩니다.
spriteInstance.play()가 사용되면 다시 플레이 됩니다.
신택스는 spriteInstance:prepare( [sequence] ) 입니다.

위의 코드에서는 hero 인스턴스를 herodown에서 stop된 상태로 준비 합니다.
person.png 이미지에서 보면 세번째 줄 첫번째 이미지가 나타나겠죠?

----------------------------------------------------------------------
--                                ARROWS                                --
----------------------------------------------------------------------   
up = display.newImage ("up.png")
up.x = 250
up.y = 380
---
down = display.newImage ("down.png")
down.x = 250
down.y = 440
---
left = display.newImage ("left.png")
left.x = 210
left.y = 410
---
right = display.newImage ("right.png")
right.x = 290
right.y = 410
-- The arrow images to move our sprite

그 다음은 화살표들을 display합니다.
아까 언급한 대로 한개의 이미지 가지고 rotate 시키면서 4개의 화살표를 만들 수 있습니다. 이건 여러분이 직접 해 보세요.
이 부분은 많이 다뤄본 부분이기 때문에 별다른 코멘트 없이그냥 넘어 갑니다.

----------------------------------------------------------------------
--                                MOVEMENT                            --
----------------------------------------------------------------------
local motionx = 0
local motiony = 0
local speed = 4
-- These are required below; change the speed if you wish to experiment but not motionx or motiony.

local function stop (event)
    if event.phase =="ended" then
        motionx = 0
        motiony = 0
        hero:pause()
    end
end
Runtime:addEventListener("touch", stop )
-- Here we state that we don't want the sprite animated or moving if we aren't pressing an arrow

local function movehero (event)
hero.x = hero.x + motionx
hero.y = hero.y + motiony
end
timer1 = timer.performWithDelay(1,movehero,0)
-- The function to move the hero; it's on a timer but you could also use a Runtime listener


function touchup (event)
motionx = 0
motiony = -speed
hero:prepare("heroup")
hero:play("heroup")
end
up:addEventListener("touch", touchup)
-- When the up arrow is touched, play hero up and move the hero upwards

function touchdown (event)
motionx = 0
motiony = speed
hero:prepare("herodown")
hero:play("herodown")
end
down:addEventListener("touch", touchdown)
-- When the down arrow is touched, play hero down and move the hero downwards

function touchleft (event)
motionx = -speed
motiony = 0
hero:prepare("heroleft")
hero:play("heroleft")
end
left:addEventListener("touch", touchleft)
-- When the left arrow is touched, play hero left and move the hero left

function touchright (event)
motionx = speed
motiony = 0
hero:prepare("heroright")
hero:play("heroright")
end
right:addEventListener("touch", touchright)
-- When the right arrow is touched, play hero right and move the hero right

이제 sprite도 모두 세팅 됐고 화살표도 세팅 됐으니 화살표를 누르면 animation이 일어나도록 할 차례입니다.

첫번째로 motionx,motiony,speed 변수를 생성합니다. 값은 0,0,4 로 설정해 둡니다.

Runtime:addEventListener("touch", stop )
그 다음 첫번째 이벤트 리스너로 Runtime 이벤트 리스너가 있습니다.
아무 화살표도 누르고 있지 않으면 애니메이션을 그 상태로 stop시키는 부분 입니다.
stop함수를 보면 motionx,motiony모두 0으로 세팅하고 hero 스프라이트는 pause()시킵니다.
마우스 up일 경우 이 함수가 실행 되게 됩니다.

timer1 = timer.performWithDelay(1,movehero,0)
그 다음은 timer 입니다. 위 코드는 0.001초마다 movehero를 실행한다는 의미 입니다.
맨 마지막 0은 루프로서 무한대를 의미합니다.
movehero함수를 보면 hero.x와 hero,y를 motionx와 motiony만큼씩 이동하도록 설정했습니다.
위 Runtime 리스너가 작동되면 motionx와 motiony 모두 0이니까 움직이질 않겠고 sprite도 pause돼 있으니까 정지 돼 있을 겁니다.

up:addEventListener("touch", touchup)
이제 위쪽 화살표에 리스너를 달았습니다.
그 내용을 보면 x좌표는 0이고 motiony를 -speed만큼씩 변화시킵니다.
speed는 4로 설정 돼 있고 Corona는 위로 올라 갈수록 y좌표가 줄어드니까 이렇게 되면 이미지는 위로 올라갈 겁니다.
그리고 play는 heroup을 설정했으니 이에 해당하는 3개의 프레임이 연달아 display될겁니다. 이 화살표를 누르고 있는 동안 timer가 계속 실행 될 겁니다.
(사실은 안 누르고 있을 때도 계속 실행됩니다.)
그럼 0.001초마다 motiony좌표를 4씩 마이너스 해 주고 timer에서 호출하는 movehero함수에서 hero 스프라이트 위치를 변경시켜 줍니다.
이럼으로서 스프라이트 이미지 3개가 번갈아 가면서 출력되고 그 위치도 4픽셀씩 바뀌니까 자연스러운 애니메이션 효과를 얻을 수가 있습니다.

나머지 버튼들도 마찬가지로 작동합니다.


이러면 화살표를 누르는 대로 이미지가 걸어가는 애니메이션 효과를 낼 수 있습니다.

아래 전체 소스코드와 이미지들이 있습니다.


오늘은 sprite sheets animation에 대해 자세히 알아 봤습니다
그럼 모두 즐거운 주말 되세요.

반응형

Time Bar 만들기

2011. 10. 27. 22:06 | Posted by 솔웅


반응형
오늘은 timer.performWithDelay()를 이용해서 시간이 지날수록 점점 줄어드는 Time Bar를 만들어 보겠습니다.

Corona DOC에서 배운 기본 테크닉으로 응용해 본 건데요.


앱을 시작하면 저 흰 Bar가 점점 줄어드는 겁니다.

그럼 소스를 보겠습니다.

--Show a basic white bar
local timeBar = display.newRect( 20, 165, 280, 20 )
timeBar:setReferencePoint(display.BottomLeftReferencePoint)
timeBar.x = 20
timeBar.y = 165

--Make the bar shrink over time
local function loseTime (event)
timeBar.width = timeBar.width - 2
timeBar:setReferencePoint(display.BottomLeftReferencePoint)
timeBar.x = 20
end
gameTimer = timer.performWithDelay(100,loseTime, 0)

1. display.newRect로 사각형을 그립니다.
    color를 지정해 주지 않았는데 그러면 기본으로 White가 그려집니다.
    display.newRect( [parentGroup,] left, top, width, height )
2. ReferencePoint를 BottomLeft로 줍니다.
    이러면 Rect의 왼쪽 아래가 기준이 되서 x좌표 20 과 y좌표 165 가 그려집니다.
3. timeBar 의 x좌표와 y좌표를 그려줍니다.
    이미 newRect에서 x,y가 설정 돼 있기 때문에 이 부분은 생략되도 됩니다.
    혹시 x,y좌표를 바꿀 필요가 있을 때 이용하시면 됩니다.
4. 그 다음은 loseTime(event) 함수 부분입니다.
   event를 파라미터로 받는 걸로 봐서 어떤 리스너에서 호출되는 겁니다.
   그 리스너는 맨 마지막에 있는 timer.performWithDelay 입니다.
   신택스는 아래와 같습니다.
   timer.performWithDelay( delay, listener [, iterations] )
   위 예제에서는 delay가 100으로 돼 있기 때문에 0.1초마다 한번씩 loseTime을 콜 합니다.
   맨 마지막에 0으로 돼 있는건 이걸 계속 호출한다는 내용입니다.
   3으로 돼 있으면 3번 호출합니다.
5. loseTime(event) 함수 내용은요.
    실행 될 때마다 timeBar의 width를 -2 합니다.
    ReferencePoint는 여전히 BottomLeft로 하고 x좌표는 계속 20으로 합니다.

이런식으로 간단히 Time Bar 효과를 냈습니다.

살펴본 김에 Listener에 대해 자세히 한번 살펴 보고갈까요?


이 예제는 하얀색 Bar는 점점 줄어들고 녹색 공은 좌우로 랜덤하게 움직이는 겁니다.
그리고 Function Listener와 Table Listener에서는 Terminal에 일정 시간별로 print를 하구요.

아래 전체 소스 코드를 보겠습니다.

--Show a basic white bar
local timeBar = display.newRect( 20, 165, 280, 20 )
timeBar:setReferencePoint(display.BottomLeftReferencePoint)
timeBar.x = 20
timeBar.y = 165

--Make the bar shrink over time
local function loseTime (event)
timeBar.width = timeBar.width - 2
timeBar:setReferencePoint(display.BottomLeftReferencePoint)
timeBar.x = 20
end
gameTimer = timer.performWithDelay(100,loseTime, 0)

-- Function Listener
 local function listener( event )
    print( "Function listener called" )
 end
 
 timer.performWithDelay(500, listener,0 )
 
 -- Table Listener
 local listener1 = {}
function listener1:timer( event )
   print( "Table listener called" )
end
 
timer.performWithDelay(700, listener1,0 )


function newBall()
    local randomPosition = 100 + math.random(200)
    ballPosition = display.newImage("b3.png")
    ballPosition.y = 240
 
    -- wrap spawnBall and randomPosition inside a closure
    local myclosure = function()
        local randomPosition = 100 + math.random(200)
        return spawnBall( randomPosition )
    end
   
    timer.performWithDelay(180, myclosure, 0)
   
    --spawnBall()
    function spawnBall(pos)
        print( "randomPosition = " .. pos )
        ballPosition.x = pos
    end
end

newBall()

Function Listener 윗 부분은 처음에 살펴 봤던 부분이라서 넘어갑니다.

Function Listener는 TimeBar에서 우리가 사용했던 형식 입니다.
일정 시간이 지나면 해당 함수(Function)를 호출합니다.

그 다음 Table Listener 는 Table(배열) 값에 대해 리스너를 달 때 사용합니다.
소스코드와같이 함수를 테이블 이름 : timer(event) 형식으로 답니다.
timer.performWithDelay부분은 같습니다.

newBall()
함수 안에서 함수를 호출하는데 거기서 Parameter를 던져주는 방법에 대한 예제입니다.
처음에는 볼의 이미지를 그리고 그 y좌표를 설정했습니다.

그리고 performWithDelay에서 0.18초마다 myclosure를 호출합니다.
myclosure에서는 random한 값을 만들고 이를 spawnBall에 파라미터를 전달해 주면서 호출합니다.
spawnBall() 함수는 이 랜덤 값 파라미터를 받아서 볼의 x좌표를 세팅해 줍니다.

이렇게 하면 공은 좌우로 아주 랜덤하게 움직입니다.

오늘 샘플 코드는 아래에 있습니다. 그럼 즐공 하세요.






반응형


반응형
오늘은 간단한 팁을 하나 살펴 보겠습니다.

소스와 이미지는 위 압축파일 안에 있습니다.


오늘의 팁은 배경화면이 계속 위에서 아래로 내려가는 효과 입니다.
이 소스에서는 두개의 이미지를 사용했습니다.
갤러그같은 게임에 응용할 수있을 것 같습니다.

소스를 분석 해 볼까요?

-------------------------------------------------------------------------
-- SCROLLING GAME BACKGROUND --
--------------------------------------------------------------------------

local localGroup = display.newGroup()
--First half of scrolling background
local bg1 = display.newImage("background1.png")
bg1:setReferencePoint(display.CenterLeftReferencePoint)
bg1.x = 0
bg1.y = 0
localGroup:insert(bg1)

-- Second half of scrolling background
local bg2 = display.newImage("hutbg.png")
bg2:setReferencePoint(display.CenterLeftReferencePoint)
bg2.x = 0
bg2.y = 480
localGroup:insert(bg2)

local tPrevious = system.getTimer()
local function move(event)
local tDelta = event.time - tPrevious
tPrevious = event.time

-- Change this to adjust the speed of the background
local yOffset = ( 0.15 * tDelta )

bg1.y = bg1.y + yOffset
bg2.y = bg2.y + yOffset

if bg1.y > 720 then
bg1:translate( 0, -480*2)
end
if bg2.y > 720 then
bg2:translate(0, -480*2)
end
end

-- Gets the background moving
Runtime:addEventListener( "enterFrame", move )

우선 localGroup을 만들구요.
배경 이미지 두개를 설정합니다.
하나는 x=0,y=0으로 위치를 지정하구요. 다른 하나는 x=0, y=480으로 지정했습니다.
(y는 이미지 높이를 생각해서 지정하셔야 할 겁니다.)

그리고 이 두 이미지를 localGroup에 insert하구요.

다음엔 system.getTimer()를 해서 tPrevious라는 변수에 넣습니다.
그 다음 move(event) 함수를 만듭니다.
이 함수는 맨 아래에 있는 리스너에서 호출 될 건데요. 맨 아래에 있는 리스너는 Runtime리스너이고 enterFrame으로 파라미터가 돼 있네요.
앱이 시작하면서 이 함수가 호출 될 겁니다.

이 함수 내용을 보면요.
tDelta 변수에 event.time - tPrevious 를 담구요. tPrevious에는 새롭게 이벤트 시간을 담습니다.
그리고 yOffset변수에는 대강의 이미지 스피드를 계산해 넣습니다.
이 부분은 여러분이 원하는 숫자를 맘대로 넣어보세요.
1을 넣으면 아주 천천히 흘러갈 테고 큰 숫자를 넣을 수록 아주 빨리 흘러갈 겁니다.

그리고 첫번째 백그라운드 이미지의 y 좌표를 이 속도 만큼 + 해 줍니다.
bg1.y 가 720 보다 크면 첫번째 백그라운드 이미지를 translate 합니다.
여기에 나오는 y좌표의 숫자 480, 720 등은 배경이미지의 높이에 따라 적당히 바꿔 주시면 됩니다.

첫번째 백그라운드 이미지 처럼 두번째 백그라운드 이미지도 속도만큼 y좌표를 옮겨주고 720보다 크면 translate 시킵니다.

이렇게 하면 두개의 배경 이미지가 계속 번갈아가며 아래로 흐릅니다.

오늘은 배경 이미지 스크롤 다운에 대한 간단한 팁을 알아봤습니다.

반응형


반응형
In App Purchasing 부분은 저도 잘 모릅니다.
우선 들어가기에 앞서 제가 잘 하는 방식인 샘플 코드 분석하기 부터 할까요?

샘플코드 주석

차근 차근 모범생 모드로 샘플코드에 있는 주석부터 보겠습니다.

--
-- Abstract: InApp sample project
--
-- This project demonstrates Corona In App Purchase support.
-- IMPORTANT:  Parts of this code can be exercised in Corona Simulator for
-- testing, but Corona Simulator cannot connect to the iTunes store.
-- To test with the iTunes store, you must
--   1. set up your own In App Purchase products in iTunes Connect
--   2. modify the code below to suit your products
--   3. set up a test user account and provisioning for your iOS test device
--   3. build and deploy on device
-- The code attempts to connect to the Apple iTunes store,
-- retrieve valid product information, and handle transactions.
--
-- Requires Corona build #261 or later.
--
-- Version: 1.0 (January 7, 2010)
--
-- Sample code is MIT licensed, see http://developer.anscamobile.com/code/license
-- Copyright (C) 2010 ANSCA Inc. All Rights Reserved.

일단 코로나 시뮬레이터로는 iTunes store에 접속 할 수 없다고 하니까 시뮬레이터로 하는 테스트에는 한계가 있겠네요.
테스트 하기 위해서는
1. iTunes Connect 에 본인 소유의 In App Purchase Products 를 셋업해야 한답니다.
2. 제공되는 샘플을 여러분의 product에 맞게 수정해야하구요.
3. 이 샘플코드를 빌드하고 핸드폰에 인스톨 해야 한답니다.
이 샘플코드는 iTunes store에 접속하기 위해 시도해서 product정보를 받고 일을 진행할 거랍니다.

주석을 보니까 코로나의 In App Purchase는 아이폰용 앱에만 적용 되나 봅니다.
(확실한 건 아닙니다. 저도 지금 처음 보면서 공부 하는 중이니까요. Stable version이 아닌 build 버전에서는 안드로이드에도 적용 될 수 있습니다.)

자 이제 샘플 코드를 직접 보겠습니다. (일단시작 부분부터 합니다.)

local ui = require("ui")
local store = require("store")   -- Available in Corona build #261 or later

local isSimulator = "simulator" == system.getInfo("environment")

display.setStatusBar( display.HiddenStatusBar )

-- Unbuffer console output for debugging
io.output():setvbuf('no')  -- Remove me for production code

local titlecard
function showTitle()
    if isSimulator then
        local myAlert = native.showAlert(  "iTunes Store not available in Corona Simulator",
                "Offline testing: see console for output.",
                { "OK" } )
        end
    titlecard = display.newImage( "titlecard.png", true )
    titlecard.x = display.contentCenterX
    titlecard.y = display.contentCenterY
end

local bg
local validProducts, invalidProducts = {}, {}
local descriptionArea

ui와 store를 require했습니다. store는 코로나 빌드 버전 261 이상에서만 가능하답니다.
테스트하기 전에 여러분 코로나 SDK 버전부터 확인하세요.


현재 제 맥에 깔려 있는 버전은 591 이니까 제 SDK에서도 안 되겠네요.

테스트를 위해서 버전 업 해야겠습니다.
제 SDK는 며칠전에 받은 stable한 최신 버전이었었는데...
아직 In App Purchase 기능 (store)은 stable한 버전에 속해 있지는 않습니다.2011년 10월 현재까지는요.

그 다음은 isSimulator 에 현재 디바이스가 시뮬레이터이면 true를 아니면 false를 받도록 했습니다.
그리고나서 아이폰의 status bar를 없앴구요.
io.output():setvbuf('no')는 디버깅을 위해서 콘솔의 아웃풋을 unbuffer한다는 내용인데요. 실제 빌드할 때는 필요 없는 부분입니다.
그리고 titlecard라는 local 변수를 하나 선언만 해 놨구요.
그리고 showTitle() 함수가 나옵니다.
이 함수가 하는 일을 보면요.
시뮬레이터이면 iTunes Store는 시뮬에서 안된다는 메세지를 Alert 화면으로 뿌려 줍니다.
그리고 아까 함수 밖에서 선언했던 titlecard 변수에 이미지를 디스플레이 하도록 넣고 x,y좌표를 설정해 줍니다.
이게 showTitle() 함수가 하는 일입니다.

그 다음엔 로컬로 bg를 선언하고 validProducts,invalidProducts 테이블(배열) 변수를 선언합니다. 그리고 descriptionArea라는 변수도 선언해 둡니다.

첫번째 파트는 자세히 살펴 봤구요. 뭐 별로 이해 안 가는 부분은 없었습니다.
이제 두번째 파트를 볼까요?
-------------------------------------------------------------------------------
--  Product IDs should match the In App Purchase products set up in iTunes Connect.
--  We cannot get them from the iTunes store so here they are hard coded;
--  your app could obtain them dynamically from your server.
-------------------------------------------------------------------------------
local listOfProducts =
{
    -- These Product IDs must already be set up in your store
    -- We'll use this list to retrieve prices etc. for each item
    -- Note, this simple test only has room for about 4 items, please adjust accordingly
   
    -- The iTunes store will not validate bad Product IDs
    "com.anscamobile.NewExampleInAppPurchase.ConsumableTier1",
    "com.anscamobile.NewExampleInAppPurchase.NonConsumableTier1",
    "com.anscamobile.NewExampleInAppPurchase.SubscriptionTier1",
}

주석부분을 보면요.
Products IDs 가 iTunes 에 셋업 돼 있는 프로덕트들과 일치해야 한다고 하네요.
iTunes에서 이 정보를 가져올 수 없어서 하드코딩을 해야 합니다.
자신의 서버를 가지고 있으면 다이나믹하게 이 정보들을 가져올 수 있도록 코딩 가능합니다.
그리고 실제 코딩부분을 보면 listOfProducts라는 배열을 로컬로 선언하고 그 안에 product IDs 정보를 넣습니다.
이 부분은 각자 iTunes에 먼저 셋업 하고 난 다음에 자신에게 맞는 정보를 넣어야 할 것 같습니다.

다음 세번째 파트를 보겠습니다.
이 부분이 이 샘플 코드 중에서는 제일 긴 부분이네요.
-------------------------------------------------------------------------------
-- Process and display product information obtained from store.
-- Constructs a button for each item
-------------------------------------------------------------------------------
function unpackValidProducts()

    -- Utility to build a buy button
    function newBuyButton (index)
        --    Handler for buy button
        local buttonDefault = "buttonBuy.png"
        local buttonOver = "buttonBuyDown.png"
        local buyThis = function ( product )
            print ("Ka-ching! Purchasing " ..product)
            -- Purchase the item
            if store.canMakePurchases then
                store.purchase( {product} )
            else
                native.showAlert("Store purchases are not available, please try again later",
                            { "OK" } )
            end
        end
        function buyThis_closure ( index )           
            -- Closure wrapper for buyThis() to remember which button
            return function ( event )
                    buyThis (validProducts[index].productIdentifier)         
                return true
            end       
        end
        local hideDescription = function ( )
            descriptionArea.text = "Select a product..."
        end
        local describeThis = function ( description )
            -- Display product description for testing
            print ("About this product:  " ..description)
            -- TODO wrap if necessary
            descriptionArea.text = description
            timer.performWithDelay( 2000, hideDescription) 
        end
        function describeThis_closure ( index )           
            -- Closure wrapper for describeThis() to remember which button
            return function ( event )
                    describeThis (validProducts[index].description)         
                return true
            end       
        end
        local formatted = string.format("%.2f", validProducts[index].price)
        local label = validProducts[index].title .. "  " ..formatted
        local myButton = ui.newButton{
                 default = buttonDefault, over = buttonOver,
                 onPress = describeThis_closure (index), onRelease = buyThis_closure (index),
                 text = "", textColor = {2, 0, 127, 255}, font="Marker Felt", size = 14, emboss = false
        }
        myButton:setReferencePoint(display.CenterLeftReferencePoint)
        myButton:setText(label)
        return myButton
    end

    -- Utility to build a restore button
    function newRestoreButton ()
        local buttonDefault = "buttonRestore.png"
        local buttonOver = "buttonRestoreDown.png"
        local restore = function ( product )
            -- Ask the iTunes Store to initiate restore transaction sequence
            print ("Restoring " )
            store.restore()
        end
        local hideDescription = function ( )
            descriptionArea.text = "Select a product..."
        end
        local describeThis = function ()
            -- Display info in description area
            print ("Test restore feature")
            descriptionArea.text = "Test restore feature"
            timer.performWithDelay( 2000, hideDescription) 
        end
            local label = "Test restore"
            local myButton = ui.newButton{
                     default = buttonDefault, over = buttonOver,
                     onPress = describeThis, onRelease = restore,
                     text = "", textColor = {255, 255, 1, 255}, font="Marker Felt", size = 14, emboss = false
            }
            myButton:setReferencePoint(display.CenterLeftReferencePoint)
            myButton:setText(label)
            return myButton
    end

    print ("Loading product list")
    if not validProducts then
        native.showAlert( "In App features not available", "initStore() failed", { "OK" } )       
    else
        print ("Product list loaded")
        print( "Country: " .. system.getPreference( "locale", "country" ) ) 
        -- Show store UI
        titlecard:removeSelf()
        bg = display.newImage( "storebg.png", true )
        bg.x = display.contentCenterX
        bg.y = display.contentCenterY       
        descriptionArea = native.newTextBox (10, 0.7*display.contentHeight, display.contentCenterX - 20, display.contentCenterY - 10)
        descriptionArea.text = "Select a product..."
        descriptionArea:setTextColor (2, 0, 127)
        descriptionArea.size = 18
        descriptionArea.hasBackground = false
        local buttonSpacing = 5
        print( "Found " .. #validProducts .. " valid items ")
        -- display the valid products in buttons
        for i=1, #validProducts do           
            -- Debug:  print out product info
            print ("Item " .. i .. ": " .. validProducts[i].productIdentifier
                            .. " (" .. validProducts[i].price .. ")")
            print (validProducts[i].title .. ",  ".. validProducts[i].description)

            -- create and position product button
            local myButton = newBuyButton(i)
            myButton.x = display.contentWidth - myButton.width - buttonSpacing
            myButton.y = i * buttonSpacing + (2 * i - 1) * myButton.height / 2
        end
        -- create and position Restore button
        local myButton = newRestoreButton()
        myButton.x = display.contentWidth - myButton.width - buttonSpacing
        myButton.y = display.contentHeight - myButton.height / 2 - buttonSpacing
       
        for i=1, #invalidProducts do
            -- Debug:  display the product info
            native.showAlert( "Item " .. invalidProducts[i] .. " is invalid.",
                            { "OK" } )
            print("Item " .. invalidProducts[i] .. " is invalid.")
        end

    end
end

우선 주석부터 살펴보면요. iTunes store로부터 얻은 상품 정보를 처리하고 보여주는 부분 입니다. 각 아이템 별로 버튼을 만듭니다.

처음에 unpackValidProdects() 함수가 나옵니다. 이 함수가 하는 일을 살펴 보겠습니다.
이 함수 안에는 두개의 함수가 있습니다.
첫번째는 newBuyButton(index) 함수입니다.
이 함수에는 bottonBuy 버튼 이미지와 이것이 눌렸을 때 나올 버튼 이미지 두개가 선언 돼 있습니다.
다음에 newBuyButton(index)의 로컬 함수인 buyThis함수가 있습니다.
butThis 함수는 product를 파라미터로 받습니다.
store.canMakePurchase가 true이면 store.purchase({product}) 로 아이템을 구매합니다. false이면 alert화면으로 구매가 불가능하다는 메세지를 뿌려줍니다.
여기까지가 newBuyButton(index)함수입니다.

그 다음 butThis_closure(index)를 보겠습니다.
이 함수는 buyThis()함수에서 validProducts 테이블(배열) 안의 상품 정보가 올바른 것인지를 체크합니다.

여기까지가 함수 unpackValidProducts()의 내용인데요. 상품을 확인하고 이것을 구매할 수 있도록 하는 기능이 있습니다.

다음으로는 hideDescription이라는 로컬함수가 있구요. 이 함수에는 descriptionArea의 text를 할당하는 역할을 합니다.

이어서 나오는 describeThis 로컬 함수는 파라미터로 description을 받습니다.
여기서도 descriptionArea 의 텍스트를 지정해 주는데 받은 파라미터인 description을 할당합니다. 그리고 timer.performWithDelay 함수를 써서 2000밀리 세컨드 단위로 hideDescription 함수를 실행합니다.

다음은 describeThis_closure(index)함수가 나옵니다.
이 함수는 describeThis 함수에 validProducts[index].description을 바라미터로 전달하면서 콜합니다.

이 함수들은 유저가 누른 아이템이 어떤것인지 알아내서 그 아이템을 표시해 주는 역할을 하네요.

다음엔 validProducts[index].price를 포맷에 맞게 string.format함수를 이용해서 바꾸구요.
lable에 validProducts[index].title 과 위에서 포맷한 formatted를 넣습니다.
다음 myButton에서는 처음에 require한 ui를 사용해서 새로운 버튼을 만들어 넣습니다.

지금까지는 newBuyButton에 대한 내용이구요.
그 다음엔 newRestoreButton에 대한 내용이 나옵니다.
내용은 위의 내용이랑 비슷할 겁니다.

마찬가지로 버튼 이미지 두개를 선언합니다. (나중에 눌렸을 때와 그렇지 않았을 때 사용할 버튼 이미지 입니다.)
restore 로컬함수에서 store.restore()로 iTunes Store에 restore 트랜잭션을 진행하도록 합니다.
hideDescription 로컬 함수는 위와 같구요.
descibeThis 로컬 함수도 위에 나온 것하고 같습니다.
그리고 Test restore 문자를 label에 넣고 myButton을 만듭니다.

여기까지가 newBuy버튼과 newRestore버튼을 만들고 그것을 눌렀을 때 아이템을 구매하거나 restore하는 기능을 합니다.

다음을 살펴 봅니다.

validProducts가 아니면 Alert 메세지를 띄워서 경고문구를 보여주구요.
validProdects이면 else 이하 부분을 실행합니다.

그 내용은 titlecard를 remove하고
백그라운드 이미지를 display합니다.
그리고 상품을 고르라는 텍스트 박스를 보여줍니다.
버튼 스페이스는 5로 지정하구요.
validProducts숫자에 맞게 버튼을 표시합니다. 이때는 newBuyButton함수를 이용합니다.

그 다음에 newRestoreButton()함수를 불러와서 버튼 세팅을 하구요.
invalidProducts 숫자 만큼 alert을 띄웁니다. 이 부분은 아마 디버깅을 위한 부분 같습니다.

여기까지가 세번째 파트로 가장 긴 부분이었습니다. 물건을 사고, Restore하는 내용을 담고 있었습니다.

다음 네번째 파트를 보겠습니다.

-------------------------------------------------------------------------------
-- Handler to receive product information
-- This callback is set up by store.loadProducts()
-------------------------------------------------------------------------------
function loadProductsCallback( event )
    -- Debug info for testing
    print("In loadProductsCallback()")
    print("event, event.name", event, event.name)
    print(event.products)
    print("#event.products", #event.products)
    io.flush()  -- remove for production

    -- save for later use
    validProducts = event.products
    invalidProducts = event.invalidProducts   
    unpackValidProducts ()
   
end

우선 주석을 보면 상품 정보를 받는 부분이라고 돼 있습니다. 이 콜백은 store.loadProducts() 함수에 의해 셋업 됩니다.

loadProductsCallback(event) 함수가 있습니다. 어떠한 리스너에서 이벤트를 받아서 이 함수를 콜 할 겁니다.
콜을 받으면 (event를 파라미터로 받으면) 이 함수는 디버그를 위해서 터미널에 이벤트 관련 된 정보를 뿌려주구요. io.flush()를 이용해서 일단 깨끗하게 정리합니다. (이전에 혹시 필요없는 정보가 남아 있을 경우 다 정리 하기 위해서 입니다.)
그리고 원래 구현하고자 하는 부분이 있는데요.
validProducts에 event.prodects를 담고 invalidProducts에 event.invalidProducts를 담습니다. 그리고 unpackValidProducts ()를 실행합니다.
unpackValidProducts () 는 첫 부분에서 만든 함수죠? 상품을 확인하고 이를 구매하도록 하는 함수였었습니다.

다음은 store.init()을 구현한 부분을 보겠습니다.
-------------------------------------------------------------------------------
-- Handler for all store transactions
-- This callback is set up by store.init()
-------------------------------------------------------------------------------
function transactionCallback( event )
    local infoString 
    print("transactionCallback: Received event ", event.name)
--[[
    -- Also available for your app to use:
    print("transaction", event.transaction)
    print("state", event.transaction.state)
    print("errorType", event.transaction.errorType)
    print("errorString", event.transaction.errorString)
--]]
--    print("testing", store.transactionStatePurchased, store.transactionErrorPaymentCancelled, store.transactionStateFailed )

    if event.transaction.state == "purchased" then
        infoString = "Transaction successful!"
        print (infoString)
        descriptionArea.text = infoString
    elseif  event.transaction.state == "restored" then
        -- Reminder: your app must store this information somewhere
        -- Here we just display some of it
        infoString = "Restoring transaction:" ..
                            "\n   Original ID: " ..event.transaction.originalTransactionIdentifier ..
                            "\n   Original date: "..event.transaction.originalDate
        print (infoString)
        descriptionArea.text = infoString
        print("productIdentifier", event.transaction.productIdentifier)
        print("receipt", event.transaction.receipt)
        print("transactionIdentifier", event.transaction.transactionIdentifier)
        print("date", event.transaction.date)
        print("originalReceipt", event.transaction.originalReceipt)

    elseif event.transaction.state == "cancelled" then
        infoString = "Transaction cancelled by user."
        print (infoString)
        descriptionArea.text = infoString

    elseif event.transaction.state == "failed" then       
        infoString = "Transaction failed, type: ",
            event.transaction.errorType, event.transaction.errorString
        print (infoString)
        descriptionArea.text = infoString
    else
        infoString = "Unknown event"
        print (infoString)
        descriptionArea.text = infoString
    end

    -- Tell the store we are done with the transaction.
    -- If you are providing downloadable content, do not call this until
    -- the download has completed.
    store.finishTransaction( event.transaction )
end

주석을 보면 모든 store 트랜잭션의 핸들러 입니다. 이 콜백은 store.init()에 의해 셋업 됩니다.
이 부분은 transactionCallback(event) 함수 하나가 있습니다.
어떠한 리스너에서 콜 되는 함수이고 event를 파라미터로 받습니다.

처음에 infoString변수를 선언하고 터미널에 event.name을 뿌려줍니다.

그 다음 이 이벤트 상태가 purchased일 경우 infoString에 Transaction successful!이라는 메세지를 할당하구 이를 descriptionArea의 텍스트에 넣습니다.
만약 이벤트가 restored일 경우에는 infoString에 event.transaction.originalTransactionIdentifier 와 event.transaction.originalDate 정보를 담습니다.
그리고 descriptionArea의 텍스트에 이를 할당합니다.

만약 이벤트가 cancelled일 경우 infoString에 Transaction cancelled by user 라는 텍스트를 할당하고 descriptionArea의 텍스트에 이를 할당합니다.

만약 이벤트 트랜잭션 상태가 failed일 경우는 에러 정보를 infoString에 담고 이 내용을 descriptionArea의 텍스트에 넣습니다.

그리고 이 이벤트 트랜잭션 상태가 아무것에도 해당되지 않을 경우에는 else부분을 실행합니다.
여기에는 infoString에 Unknown event라는 텍스트를 넣어서 이를 destriptionArea 의 텍스트에 넣습니다.

종합해 보면 이 파트는 event를 받아서 이 이벤트의 각 상태별로 다른 내용을 infoString에 넣고 이를 descriptionArea의 텍스트에 넣는 기능을 하네요.
그 다음에 마지막으로 store에 transaction을 finish하도록 합니다.

그 다음 파트를 보면 마찬가지로 어떤 리스너에서 콜 하는 함수인가 봅니다.
이는 파라미터로 event를 받는 걸 보고 짐작할 수 있거든요.
setupMyStore(event) 함수인데요.
-------------------------------------------------------------------------------
-- Setter upper
-------------------------------------------------------------------------------
function setupMyStore (event)
    store.loadProducts( listOfProducts, loadProductsCallback )
    print ("After store.loadProducts, waiting for callback")
   
    if isSimulator then
        -- No Store, so no callbacks, so exercise our GUI "manually" 
        validProducts[1] = {}
        validProducts[1].title = "Lemonade refill"
        validProducts[1].description = "A wonderful dummy product for testing"
        validProducts[1].price = 0.99
        validProducts[1].productIdentifier = "com.lemonadestand.consumable.001"
        validProducts[2] = {}
        validProducts[2].title = "Drinking glass"
        validProducts[2].description = "Makes lemonade easier to drink."
        validProducts[2].price = 1.99
        validProducts[2].productIdentifier = "com.lemonadestand.nonconsumable.001"
        validProducts[3] = {}
        validProducts[3].title = "Daily dose"
        validProducts[3].description = "Made fresh daily!"
        validProducts[3].price = 19.99
        validProducts[3].productIdentifier = "com.lemonadestand.subscription.001"
        unpackValidProducts()  
    end
end
처음에 store.loadProducts()를 실행합니다. iTunes store에 있는 상품들을 load하나 봅니다.
그게 끝이예요.
만약 기기가 시뮬레이터이면 그냥 이 샘플파일에 미리 지정한 세가지 상품이 뜨게끔 합니다.
이 샘플코드를 바탕으로 테스트를 하려면 처음 주석에 있는 것처럼 여러분의 상품 셋업이 iTunes Store에 돼 있어야 합니다. 저도 아직 하나도 없는데.. 저도 마찬가지구요.

-------------------------------------------------------------------------------
-- Main
-------------------------------------------------------------------------------

-- Show title card
showTitle ()

-- Connect to store at startup
store.init (transactionCallback )
print ("After init")

-- Hide title card, run store
timer.performWithDelay (1000, setupMyStore)

collectgarbage()

마지막 파트는 main부분으로 showTitle()을 시작하고 store.init(transactionCallback)을 실행합니다.
그리고 1초 간격으로 setUpMyStore를 진행합니다.
마지막엔 collectgarbage()를 해서 garbage data를 없애구요.

여기까지 코로나에서 제공한 in App Purchase 샘플 코드를 분석해 봤습니다.

일단 제대로 테스트 하기 위해서는 iTunes Store에 저의 products를 세팅하고
코로나 SDK도 더 최신 버전으로 깐 다음에 테스트를 진행 할 수 있겠네요.

혹시 iTunes Store에 in App Purchase 를 위해서 products 세팅하는 법 아시는 분 정보 부탁드릴께요...

지금까지 배운 코로나 코딩 중에 제일 복잡한 것 같은데요.

그럼 저도 이 선행 되어야 할 사항들 다 마무리 하고 테스트 한 다음에 다시 글을 올리겠습니다.

그럼....
반응형

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

2011. 10. 24. 21:17 | Posted by 솔웅


반응형
Asynchronous HTTP (비동기 HTTP)

이 기능은 HTTP 메소드 (GET,POST 등)를 사용하거나 개발자가 header와 body를 사용해서 비동기식 HTTP와 HTTPS/SSL 콜을 만들도록 합니다.
코로나는 이 때 서버로부터 어떤 응답을 받는 동안 멈출 필요가 없도록 하는 기능을 제공합니다.
여러분은 네트워크를 통해서 파일을 다운로드 할 수 있습니다. 이 파일을 메모리에 로딩 할 필요가 없습니다. 이것은 해당 파일이나 이미지가 아주 큰 경우에 아주 유용합니다.
코로나는 이와 관련 두개의 샘플 프로젝트를 제공합니다.

AsynchHTTP

local myText = display.newText("(Waiting for response)", 0, 0, native.systemFont, 16)
myText.x = display.contentCenterX
myText.y = 120

local function networkListener( event )
    if ( event.isError ) then
        myText.text = "Network error!"
    else
        myText.text = "See Corona Terminal for response"
        print ( "RESPONSE: " .. event.response )
    end
end

-- Access Google over SSL:
network.request( "https://encrypted.google.com", "GET", networkListener )

편의를 위해 주석부분은 제거했습니다.
간단히 살펴보면 첫 세줄은 텍스트를 뿌려주는 겁니다.
networkListener 함수를 보면 event를 받아서 에러일 경우 에러메세지를 뿌려주고 에러가 아닐 경우는 그 결과값을 터미널에 뿌려줍니다.
결과값은 맨 마지막 줄 network.request를 통해서 받아온 겁니다.


뭐 request 하는 것도 없고 그냥 network.request() 에서 get 방식으로 가져오는 것 밖에는 없네요.

두 번째 예제도 보면요.

AsynchImageDownload

local function networkListener( event )
    if ( event.isError ) then
        print ( "Network error - download failed" )
    else
        myImage = display.newImage( "helloCopy.png", system.TemporaryDirectory, 60, 40 )
        myImage.alpha = 0
        transition.to( myImage, { alpha = 1.0 } )
    end
   
    print ( "RESPONSE: " .. event.response )
end

network.download(
    "http://developer.anscamobile.com/demo/hello.png",
    "GET",
    networkListener,
    "helloCopy.png",
    system.TemporaryDirectory )

-- NOTE: files saved to system.TemporaryDirectory are not guaranteed to persist across launches.
-- Use system.DocumentsDirectory if you want files to persist.

----------------------------------------------------------------------------------------------------
-- Method 2: use display.loadRemoteImage() to get the file and create a display object in one step

local function networkListener2( event )
    if ( event.isError ) then
        print ( "Network error - download failed" )
    else
        event.target.alpha = 0
        transition.to( event.target, { alpha = 1.0 } )
    end
   
    print ( "RESPONSE: " .. event.response )
end

myImage2 = display.loadRemoteImage(
    "http://developer.anscamobile.com/demo/hello.png",
    "GET",
    networkListener2,
    "helloCopy2.png",
    system.TemporaryDirectory,
    60, 280 )


이 샘플은 HTTP로 이미지를 download할 수 있도록 한 건데요.
network.download()부터 보시면 경로가 있고 GET방식으로 불러오고 파일 이름이 있고 이 파일을 system.TemporaryDirectory 에 저장하네요.
중간에 보면 networkListener함수를 불러오는데요.
이 함수는 event를 파라미터로 받습니다.

함수 안을 보면 에러일 경우 에러 메세지 뿌려주고 에러가 아닐경우는 system.TemporaryDirectory에 있는 이미지를 뿌려줍니다.

두 번째 myImage2 변수선언 부분을 보면요.
display.loadRemoteImage()함수를 썼는데 이것도 그 안의 내용은 network.download()와 똑 같습니다.
불러오는 함수가 networkListener2를 불러오고 맨 마지막에 60,280 이란 숫자가 있는게 다르네요.
networkListener2 함수를 살펴보면 마찬가지로 에러일 경우 에러메세지 뿌려주고 에러가 아닐경우는 event.target.alpha =0으로 투명하게 하고 이것을 transition.to함수로 투명도를 없앴습니다.

대충 보니까 network.download()는 다운로드만 받는거고 display.loadRemoteImage() 는 다운로드 받은 다음에 이것을 화면에 뿌려주기까지 하는 거네요.

먼저 공부도 하기 전에 샘플코드를 분석했는데요. 제대로 공부를 함 해 보겠습니다.

Network Requests

network.request( url, method, listener [, params] )
여기서 디폴트 method 는 GET 입니다.
결과 event는 아래와 같은 프로퍼티들을 가집니다.
    •    event.response -- 서버로부트 받은 결과 값 (String)
    •    event.isError -- 네트워크 에러와 관련한 True, False 값
이 두 파라미터는 첫 번째 예제에서 사용했었죠?

예제에는 없지만 여기에 헤더나 바디를 추가할 수 있습니다.
params.headers - 테이블로 선언 함
params.body - 스트링을 담은 HTTP 바디

headers = {}
 
headers["Content-Type"] = "application/json"
headers["Accept-Language"] = "en-US"
 
headers.body = "This is an example request body."

위 샘플을 보면 헤더에는 json 사용한다는 것과 언어는 미국식 영어를 사용한다는게 있고요. body에는 예제 바디라는 문장이 있습니다.
HTML 많이 사용해 보신 분들은 이게 뭔지 다 아시겠죠?

Network Downloads

위의 Network Requests와 거의 유사하구요 + 다운로드 기능까지 있는 겁니다.
메모리에 캐싱하지 않고 제공된 디렉토리에 제공된 이름의 파일을 HTTP로 다운로드 하는 겁니다. 큰 파일일 경우 유용하게 사용 하실 수 있습니다.

network.download( url, method, listener [, params], destFilename [, baseDir] )

디폴트 method는 GET입니다. baseDir는 system.DocumentsDirectory나 system.TemporaryDirectory가 될 수 있습니다.

결과 event로는 Network Requests와 같이 event.response와 event.isError가 있습니다.

예제는 위의 두 번째 예제를 봐 주세요.

Displaying Remote Images

이건 예제에서도 봤듯이 다운로드 하면서 곧바로 화면에 이미지를 출력해 주는 기능입니다.

display.loadRemoteImage( url, method, listener [, params], destFilename [, baseDir] [, x, y] )

다 똑 같구요. 마지막에 이미지의 x,y 값이 있는게 다릅니다.

이 함수에는 세가지 프로퍼티가 있습니다. event.response와 event.isError는 위에것들이랑 똑 같구요. 이 외에 event.target이 있습니다.
이 프로퍼티는 이미지가 다운로드 된 후의 새로 만들어진 display object입니다.

예제는 위 두 번째 예제파일 두 번째 부분을 봐 주세요.

오늘 살펴 본 것은 비동기식 HTTP 통신입니다.

이외에 코로나 DOC에 있는 내용은 OpenFeint, Credit, In-App Purchase 입니다.
Open Feint가 무엇인지 아시려면 아래 링크를 보시면 도움이 될 거예요 .
http://novathin.kr/146
그리고 일반 안드로이드 앱 프로그래밍에서 활용하려면 아래 링크를 참조하시구요.
http://blog.daum.net/gkrttod/1091
코로나에서 이 Open Feint를 사용하는 방법은 제가 공부하고 테스트 하고 난 다음에 올리겠습니다.

Credit은 코로나SDK를 만든 회사인 Ansca Mobile에서 제공하는 서비스 인 것 같습니다.
원격으로 User의 Point를 적립하고 이것을 나중에 in-App Purchase 등 현금 구매 할 때 사용할 수 있도록 하는 서비스 같은데요.
저도 이 부분은 공부를 좀 해 봐야겠어요.

개인적으로 In-App Purchase 에 관심이 있어서요.
나머지 네트워킹 주제들 중에 이 In-App Purchase를 먼저 공부하고 글을 올릴 예정입니다.

그럼...



반응형

코로나 네트워킹 과 웹 서비스 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들도 많이 다뤄보구요.

다음 시간에 뵐께요.


 

반응형