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

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
어제 애플(Apple)이 아이폰을 발표했는데 iPhone 5 가 아니라 iPhone 4S 였군요. 새로워진 것도 별로 없고... 안드로이드 진영에선 이미 다 구현 한 것들이구.
실망이 이만 저만이 아니겠는데요. 잡스 없는 애플이 이제 내리막 길로 가는 것은 아닌지......
우리나라의 삼성과 LG 에는 아주 좋은 기회가 될 듯 합니다.
1년 넘는 기간 동안 그정도 밖에 준비 못했다니..... 애플 완전 실망...

오늘은 코로나 SDK (Corona SDK) 에서 다각형 그리기 부터 하겠습니다.

코로나에서는 벡터 객체를 사용해서 다각형을 그립니다. 벡터 객체는 DisplayObject의 특별한 타입 입니다.
우선 사각형, 모서리가 둥근 사각형, 원 그리는 신택스를 보겠습니다.

display.newRect( [parentGroup,] left, top, width, height )
parentGroup은 옵션이구요. 이건 나중에 볼께요. 우선 좌측과 상단 값을 넣어서 좌상단 꼭지점의 위치를 정하고 너비와 높이 값을 주어서 사각형을 선언합니다. 여기서 색을 지정하는 건 없네요. 메소드를 사용해서 색을 지정할까요? 잠시후에 보죠.

display.newRoundedRect( [parentGroup,] left, top, width, height, cornerRadius )
모서리가 둥근 사각형은 위의 사각형과 같구요. 다만 맨 마지막에 모서리에 그릴 곡선의 반지름 값을 넣으면 됩니다.

display.newCircle( [parentGroup,] xCenter, yCenter, radius )
원은 원 중심의 x,y 값을 넣고 반지름 값을 넣습니다.
   
strokeWidth (테두리선)는 아래와 같이 파라미터를 사용해서 정해 줍니다.
object.strokeWidth = 픽셀
색 지정은 아래와 같이 메소드를 사용합니다.
object:setFillColor(r,g,b[,a]) , object:setStrokeColor(r,g,b[,a])
a는 알파값(투명도) 로 옵션 입니다. 들어갈 값은 0~255 사이에 있는 수입니다.

라인 그리기는 display.newLine(x1,y1,x2,y2) 을 사용합니다.
그리고 라인과 라인을 연결하면 다각형을 만들 수 있는데요. 이것은 object:append()를 사용합니다.

아래 샘플 코드를 작성하고 실행 해 볼까요.
 local star = display.newLine( 0,-110, 27,-35 )
  star:append( 105,-35, 43,16, 65,90, 0,45, -65,90, -43,15, -105,-35, -27,-35, 0,-110 )
  star:setColor( 255, 102, 102, 255 )
  star.width = 3


별을 그리는 코드인데요.
별 모양이 왼쪽 위에 반만 나와 있죠?
이 라인 위치와 색 그리고 width 등은 여러분들이 바꾸면서 익혀 보세요.
남의 소스를 자기 맘대로 막 고치면서 이해도 더 잘 되고 실력도 더 많이 늘더라구요.

아래는 코로나에서 제공하는 샘플 코드인데요. (Graphics/PolyLines)
한번 실행해 보세요.
--
-- Abstract: PolyLines sample app, demonstrating how to draw shapes using line segments
-- Version: 1.0
-- Sample code is MIT licensed, see http://developer.anscamobile.com/code/license
-- Copyright (C) 2010 ANSCA Inc. All Rights Reserved.
-- Example of shape drawing function
local function newStar()
    -- need initial segment to start
    local star = display.newLine( 0,-110, 27,-35 )
    -- further segments can be added later
    star:append( 105,-35, 43,16, 65,90, 0,45, -65,90, -43,15, -105,-35, -27,-35, 0,-110 )
    -- default color and width (can also be modified later)
    star:setColor( 255, 255, 255, 255 )
    star.width = 3
    return star
end

-- Create stars with random color and position
local stars = {}

for i = 1, 20 do
    local myStar = newStar()
   
    myStar:setColor( math.random(255), math.random(255), math.random(255), math.random(200) + 55 )
    myStar.width = math.random(10)
   
    myStar.x = math.random( display.contentWidth )
    myStar.y = math.random( display.contentHeight )
    myStar.rotation = math.random(360)
   
    myStar.xScale = math.random(150)/100 + 0.5
    myStar.yScale = myStar.xScale
   
    myStar:setReferencePoint( display.CenterReferencePoint )

    local dr = math.random( 1, 4 )
    myStar.dr = dr
    if ( math.random() < 0.5 ) then
        myStar.dr = -dr
    end

    table.insert( stars, myStar )
end

function stars:enterFrame( event )

    for i,v in ipairs( self ) do
        v.rotation = v.rotation + v.dr
    end
end

Runtime:addEventListener( "enterFrame", stars )


저는 사용하던 소스에 위 소스를 덧 붙였더니 위 화면같이 나옵니다.
지금 까지 배운 걸로도 이 소스를 충분히 분석할 수 있을 것 같은데요.
나중에 샘플 코드 분석할 때 시간 되면 이 소스도 함 자세히 분석해 보죠.
오늘은 Text, Group, Stage 등 앞으로 나갈 진도가 더 있어서 계속 진도 나가겠습니다.

TEXT
우리가 작성한 소스에 이미 Text 사용하는게 있는데요.
local textObj = display.newText("A short string", 0,0, nil, 26);
display.newText 를 사용하고 그 안에 텍스트와 x,y 좌표 그리고 폰트(여기선 nil), 글자 사이즈 이렇게 들어갑니다.
현재 사용하고 있는 폰트를 알아내시려면 native.getFontNames() 함수를 사용합니다.
디폴트 폰트는 native.systemFont , native.systemFontBold 입니다.
object.size - 텍스트 사이즈를 정합니다. (=숫자 를 덧붙이시면 됩니다.)
object.setTextColor(r,g,b[,a]) -  텍스트 칼라를 지정합니다.
파라미터는 . 를 사용하고 메소드는 : 를 사용하는건 이제 아시겠죠?
값을 대입 시키는 것도 대부분 파라미터는 = 숫자 , 문자 등등 을 사용하고 메소드는 () 안에 인수값을 넣구요.

원하는 폰트를 사용하시려면 우선 폰트 파일 (.ttf) 이 있어야 되고 이것을 building.settings 파일에 아래와 같이 세팅해 줘야 합니다.
settings =
{
        iphone =
        {
                plist =
                {
                        UIAppFonts =
                        {
                                "Harrowprint.ttf"
                        }
                },
        },
}
이것은 아이폰 일 경우만 해당되고 안드로이드용은 이 build.settings 파일에 세팅을 하지 않아도 됩니다.

Groups

이  Group 객체도 DisplayObject의 특별한 타입 중 하나입니다.
display.newGroup() 으로 그룹을 생성합니다. 그리고 이 그룹에 객체들을 insert 하고 각 객체들에 대한 접근은 테이블(배열) 에서 접근하는 것처럼 index 1번 부터 시작합니다.
그룹 생성 및 인서트는 아래와 같이 합니다.
 local group = display.newGroup()
  group:insert( rect1 ) -- assume rect1 is an existing display object
  group:insert( rect2 ) -- assume rect2 is an existing display object

만약 그룹 2개를 생성하고 한 객체를 두군데 모두 삽입했다면 코로나가 알아서 마지막 insert문만 적용하도록 만듭니다.
local txt=display.newText('Hello',0,0)
local g1=display.newGroup()
local g2=display.newGroup()
             
-- Insert text object into g1
g1:insert(txt)              
-- Insert same text object into g2
g2:insert(txt)
 
print("g1[1]: " .. tostring(g1[1])) -- prints nil
print("g2[1]: " .. tostring(g2[1])) -- prints textObject    

그룹을 없애려면 remove를 사용합니다. group:remove(indexOrChild)

이 그룹은 여러 객체를 하나의 객체처럼 사용할 때 편리합니다.

Stage

스테이지도 GroupObject의 특별한 타입 중 하나입니다.
스테이지는 모든 display 객체들의 root group입니다.
현재의 스테이지는 아래와 같이 접근 할 수 있습니다.
display.getCurrentState()

다른 Display함수들을 보면 아래와 같은 것들이 있습니다.
display.captureScreen(saveToAlbum) 화면을 캡쳐해서 이미지로 만듭니다. saveToAlbum 이 true이면 그 이미지를 디바이스(핸드폰) 의 앨범에 저장합니다.
시뮬레이터일 경우에는 데스크탑 컴퓨터에 저장합니다.

display.save(displayObject,filename[,baseDirectory])
해당 객체를 JPEG 이미지로 filename에서 지정한 이름으로 바꿔 줍니다.

display.setStatusBar(mode) mode가 display.HidenStatusBar이면 아이폰의 스테이터스 바가 화면에 나오지 않습니다. 관련된 argument들엔 다음과 같은 것들이 있습니다.

    * display.HiddenStatusBar
    * display.DefaultStatusBar
    * display.TranslucentStatusBar
    * display.DarkStatusBar

Size 관련된 프로퍼티들엔 아래와 같은 것들이 있습니다.
display.contentWidth - 스크린 너비가 픽셀단위로 나타납니다. config.lua에서 픽셀 말고 다른 것으로 설정해 놨으면 그 값이 나타납니다.
display.contentHeight - 스크린 높이가 나타납니다.
display.viewableContentWidth - 다이나믹 스케일 모드일 때 유용하게 사용할 수 있습니다. 해당 객체의 너비를 나타냅니다.
display.viewableContentHeight - 해당 객체의 높이를 나타냅니다.
display.statusBarHeight - 상태 바의 높이를 픽셀로 나타냅니다. iOS에서만 적용됩니다.

Display Object 대 Table

DisplayObject와 Table은 유사한 점이 많습니다. 다른 점은 Display Object의 메타테이블을 세팅할 수 없다는 겁니다.
내부적으로 코로나의 DisplayObject는 native C++객체를 사용합니다.

오늘은 여기까지 알아봤구요.

다음 시간에 Display Object 와 Stage에 대해 좀 더 자세히 예제와 함께 알아보겠습니다.

감사합니다.
반응형


반응형
오늘은 텍스트와 이미지, 라인, 다각형 같은 모양들 표시하는 방법에 대해 공부하겠습니다.

display.setStatusBar( display.HiddenStatusBar )
local textObj = display.newText("A short string", 0,0, nil, 14);
textObj:setReferencePoint(display.CenterLeftReferencePoint);
textObj.x = 20;

textObj.text = "This string has several more characters than before..."

textObj.text = "This is third sentence."
textObj:setReferencePoint(display.CenterLeftReferencePoint);
textObj.x = 20

우선 첫번째 줄은 아이폰에서 status bar 없애는 거구요.
다음줄 보맨 textObj 변수에 "A short string"이란 텍스트를 담고 위치는 0,0 폰트는 nil로 설정해서 그냥 디폴트가 나오도록 하고 글자 사이즈는 14포인트로 정했습니다.
다음은 textObj에 setReferencePoint를 적용하고 x 좌표를 20으로 했습니다.

그리고 textObj.text 를 이용해서 다른 문구를 적용했습니다.
그리고 다음줄은 또 다른 문구를 적용했구요.

화면에는 위와 같이 맨 마지막 적용된 값이 표시됩니다.

화면에 표시되는 모든 것은 DisplayObjects가 만듭니다. 즉 화면에 표시되는 모든 것은 이 DisplayObjects의 인스턴스 입니다.
사각형, 원, 이미지, 텍스트 같은 모든 것들은 다 DisplayObject를 사용해 표시할 수 있습니다.
이걸 기초로 애니메이션을 만든다던가 터치를 하면 다른 객체를 표시한다던가 하는 일들을 하게 됩니다.
이 DisplayObject는 특별한 함수인 생성자(constructor)를 호출함으로서 생성할 수 있습니다. 직접적으로 생성하지는 않습니다.
예를 들어 display.newRect()는 Vector Object를 생성하게 됩니다.

이러한 display object들은 아래와 같은 프로퍼티들과 메소드들을 사용할 수 있습니다.
프로퍼티들은 . 로 접근 할 수 있습니다.
예를 들어 아까 작성했던 텍스트에 투명도를 주려면
textObj.alpha=0.5 를 주면 됩니다.
자주 사용되는 프로퍼티 몇개를 살펴 보면요.
object.inVisible = true or false
object.height , object.width, object.length 객체 크기와 길이 등을 구할 수 있습니다.
object.isHitTestable 객체가 보이지 않을 때에도 hit 이벤트를 받을 수 있도록 합니다. (true 일 경우)
object.rotation = 숫자 -> 해당 객체를 로테이션 시킵니다.

위 화면은 아까 작성했던 코드에 아래 몇줄을 더 한 겁니다.
textObj.alpha=0.8
oHeight = textObj.height
print("Height = " .. oHeight)
textObj.rotation = 45

object.x, object.y 객체의 x,y 좌표
좀 더 자세한 사항은 아래 페이지를 참고 하세요.
http://developer.anscamobile.com/content/display-objects

다음은 메소드를 살펴 봅니다.
메소드는 : 를 이용해서 접근 할 수 있습니다.

object:rotate() 현재의 로테이션에 ()안에 있는 로테이션 각도를 추가해서 적용합니다.
예를 들어 아까 소스에 textObj:rotate(-25) 를 하면 45-25=20 이니까 20도의 기울기가 나오게 됩니다.
파라미터를 쓰게 되면 (textObj.rotation = -25) 그냥 -25도로 기울기가 표시 됩니다.
object:scale(sx,sy) 객체의 길이와 높이를 세팅합니다.
object:setReferencePoint(referencePoint) 기준점을 정합니다.
reference Point 종류는 아래와 같습니다.
    * display.CenterReferencePoint
    * display.TopLeftReferencePoint
    * display.TopCenterReferencePoint
    * display.TopRightReferencePoint
    * display.CenterRightReferencePoint
    * display.BottomRightReferencePoint
    * display.BottomCenterReferencePoint
    * display.BottomLeftReferencePoint
    * display.CenterLeftReferencePoint

예를 들어 처음의 예제에서 referncePoint를 CenterLeftReferncePoint를 사용했습니다. 그리고 text길이가 다르게 세팅을 했고 x포인트를 20으로 늘렸습니다.
이 레퍼런스 포인트를 어떻게 설정하느냐에 따라 해당 객체의 길이가 달라질 때 화면에 표시되는 위치가 다르게 보일 수가 있습니다.

object:translate(deltaX,deltaY) 현재 위치에서 deltaX,deltaY만큼 이동시킵니다.
object:removeSelf() 객체를 없앱니다. 이는 메모리 할당을 풀어주게 됩니다.


*** 이미지 Images
 Bitmap 이미지 객체도  DisplayObject의 한 타입입니다.

display.newImage(filename[,baseDirectory][,left,top])

저는 이미지테스트 할 때 아래 이미지를 사용하겠습니다.

이름은 endinggreen.png입니다.
이번에 Corona로 만든 간단한 게임에 사용했던 이미지 입니다.
제가 이미지 디자인한건 아니고 동료 디자이너가 디자인 했죠. 저는 개발만 했습니다.
잠깐 홍보 하자면 안드로이드는 아래 링크로 볼 수 있습니다.
https://market.android.com/details?id=nowbyte.corona.multipingpong&feature=search_result
저희 회사에서 제가 만든 앱들을 보려면 아래 링크로 가시면 보실 수 있구요.
https://market.android.com/search?q=effilonNY&so=1&c=apps
대부분이 게임이라서 한국에서는 볼 수 있을 지 모르겠습니다.
아이폰 앱 스토어는 아래 링크로 보실 수 있구요.
http://itunes.apple.com/us/app/multi-player-ping-pong/id466527438?ls=1&mt=8
이것도 게임 카테고리에 들어 있어서 한국에서는 보실 수 있을 지 모르겠습니다.
예전 강좌에 있던 대로 config.lua 파일에서 아이폰 (iPhone) 해상도 (resolution) 인 320 X 480 으로 하고 scale = "letterbox" 로 했기 때문에 안드로이드 폰의 모든 기기 (갤럭시S, 갤럭시S2, 드로이드, 넥서스 원 (NexusOne), 갤럭시 탭) 에서 사용할 수 있습니다. 새로나온 갤럭시 탭이나 이번에 나온 아마존의 킨들 파이어 (Kindle fire)에서는 테스트 해 보지 않았지만 여기서도 돌아갈 겁니다.
그리고 애플 쪽 디바이스은 아이폰, 아이폰 4 , iPod Touch, iPad 에서 다 돌아갑니다. 그리고 오늘 발표 될 iPhone 5 에서도 제대로 나올겁니다.

코로나 SDK (Corona SDK) 의 장점은 Box2D를 사용한 강력한 물리엔진 (Physics Engine)과 한번 개발해서 애플의 아이폰, 아이패드 디바이스와 안드로이드의 모든 모바일 폰 과 태블릿 (갤럭시 탭, 모토로라의 줌 (XOOM), 아이리버의 아이리버 탭 그리고 아마존의 Kindle fire 까지) 에서 사용할 수 있고 해상도도 간편하게 적용할 수 있다는 것에 있습니다.

일단 제가 개발한 앱과 코로나 SDK에 대한 홍보는 여기서 마치구요.
코로나에서 이미지 디스플레이 하는 것을 보도록 하겠습니다.

아까까지 사용했던 소스에 아래 두 줄을 덧 붙이겠습니다.

myImage = display.newImage( "endinggreen.png", 10, 150 )
myImage2 = display.newImage( "endinggreen.png" )


보시는 대로 이미지는 display.newImage 생성자(constructor)를 사용해서 그립니다.
이미지 이름만 파라미터로 넣어도 되고 x,y 좌표도 같이 넣어도 됩니다.
코로나에서는 PNG와 JPEG파일을 지원한다고 나옵니다.
제 기억엔 GIF도 나왔던 것 같은데요.. 잘 모르겠네요. GIF는 나오고 GIF 애니메이션은 지원이 안 되서 그냥 정지된 이미지 하나만 나온 것 같은데요... 궁금하시면 한번 해 보세요.
이미지 크기가 맥시멈을 넘어서면 autoscale됩니다. iPhone 3G 의 경우 1024  X 1024 , iPhone 3GS, iPad 의 경우는 2048X2048 이 맥시멈 사이즈 입니다.

이전 코드에 아래 세줄을 넣어 보세요.
myDynamicImage = display.newImageRect( "endinggreen.png", 60, 200 )
myDynamicImage.x =260
myDynamicImage.y =300

newImageRect 는 이렇게 이미지의 width,height를 다이나믹하게 설정할 때 사용합니다.
이 다이나믹 이미지 해상도(Dynamic Image Resolution) 과 configuring 에 대해 좀 더 자세히 보시려면 아래 링크를 참조하세요.
http://developer.anscamobile.com/content/configuring-projects#Dynamic_Image_Resolution

오늘은 이렇게 텍스트와 이미지 표시하는 법과 displayObject 에서 사용되는 파라미터와 메소드들에 대해 알아봤습니다.

다음 시간엔 사각형, 원 그리고 다각형 그리는 법과 색깔 넣는 법, 글자 폰트 지정하는 법과 새로운 폰트 사용하는 법, Group 사용법 (display.newGroup), stage 객체에 대해 알아보겠습니다.

이 모든 것들이 Display Object 의 객체(인스턴스) 들입니다.

그리고 간단히 Display Object 와 테이블에 대한 기술적인 분석에 대해서도 다룰 거구요.

뭐 별거는 아니지만 오늘 사용했던 코드 전체는 아래와 같습니다.
display.setStatusBar( display.HiddenStatusBar )
-- A text object is created and is aligned left at point x=20
local textObj = display.newText("A short string", 0,0, nil, 26);
textObj:setReferencePoint(display.CenterLeftReferencePoint);
textObj.x = 20;

 
-- Later, the textObj.text property is assigned a new string value of different length,
-- causing the object's width to change, but not its reference point.
-- Consequently, the text is no longer aligned left at point x =20
textObj.text = "This string has several more characters than before..."
 
-- Work-around:
-- Reset the text object's reference point and x position
-- after you update its text property:
textObj.text = "This is third sentence."
textObj:setReferencePoint(display.CenterLeftReferencePoint);
textObj.x = 20
textObj.alpha=0.8
oHeight = textObj.height
print("Height = " .. oHeight)
textObj.rotation = 45
textObj:rotate(-25)

myImage = display.newImage( "endinggreen.png", 10, 150 )
myImage2 = display.newImage( "endinggreen.png" )
myDynamicImage = display.newImageRect( "endinggreen.png", 100, 100 )
myDynamicImage.x =260
myDynamicImage.y =300


그럼 다음 시간에 뵙겠습니다.
반응형


반응형
오늘은 화면에 점수 표시하는 샘플코드를 공부할 겁니다.

이 파일이 전체 소스 파일입니다.
웹 싸이트는 http://techority.com/2011/01/26/how-to-add-a-score-to-your-iphone-app/ 입니다.
이 싸이트로 가면 유튜브 동영상도 있습니다.

이 소스를 보면서 제 식으로 공부 해 보겠습니다.


이 소스를 실행하면 위 화면처럼 나옵니다.
화면을 Tapping 하면 1 포인트씩 숫자가 올라갑니다.

우선 main.lua부터 보겠습니다.

--[[
Related files are score.lua, 1 through 9.png, scorebg.png and space.png
--]]
display.setStatusBar( display.HiddenStatusBar )
local background = display.newImage ("background.png")

score = require ("score")

local border = 5

local scoreInfo = score.getInfo()

score.init({
x = 40,
y = 5}
)
score.setScore(0)

local function addtoit (event)
if event.phase == "ended" then
score.setScore (score.getScore()+1)
end
end
background:addEventListener("touch", addtoit)

처음에 아이폰의 StatusBar를 없앱니다. (원래 소스에는 없는데 그냥 제가 추가 했습니다. 이게 보기에 더 편하더라구요.)
두번째는 백그라운드 이미지를 display 합니다.

세번째는 score.lua 소스를 require (implement) 합니다.
그 다음은 border를 5로 설정합니다.(이 border는 뭐 하는건지 모르겠습니다. 그냥 쓸데없이 있는거 같네요.)

그 다음엔 아까 require했던 score.lua에 있는 getInfo() 함수를 scoreInfo 변수에 담습니다. 이 getInfo() 함수는 이따 score.lua 파일을 살펴 볼 때 어떤 기능인지 공부하겠습니다.

그 다음은 score.lua에 있는 init() 함수에 x,y 파라미터를 넣어서 실행시킵니다.
그리고 setScore()함수에 파라미터 값을 0으로 해서 보냅니다.

로컬 함수 addtoit(event) 함수를 만듭니다.
그 내용은 이벤트가 끝나면 score.lua의 setScore함수에 이전 점수 +1 을 한 파라미터 값을 넣어서 부릅니다.

맨 마지막으로 background 에 touch 리스너를 달구요. 이 이벤트가 일어났을 경우 addtoit() 함수를 실행시킵니다.

지금까지 main.lua의 코드를 분석해 봤습니다.
이제 score.lua 파일을 분석해 보겠습니다.

-- A sample score keeping display
-- This updates a display for a numeric score
-- Example usage:
--    Place the score at 50,50
--         score.init( { x = 50, y = 50 } )
--    Update the score to current value + 10:
--        score.setScore( score.getScore() + 10 )

module(..., package.seeall)
 
-- Init images. This creates a map of characters to the names of their corresponding images.
 local numbers = {
    [string.byte("0")] = "0.png",
    [string.byte("1")] = "1.png",
    [string.byte("2")] = "2.png",
    [string.byte("3")] = "3.png",
    [string.byte("4")] = "4.png",
    [string.byte("5")] = "5.png",
    [string.byte("6")] = "6.png",
    [string.byte("7")] = "7.png",
    [string.byte("8")] = "8.png",
    [string.byte("9")] = "9.png",
    [string.byte(" ")] = "space.png",
}

-- score components
local theScoreGroup = display.newGroup()
local theBackground = display.newImage( "scorebg.png" )
local theBackgroundBorder = 10

theScoreGroup:insert( theBackground )

local numbersGroup = display.newGroup()
theScoreGroup:insert( numbersGroup )

-- the current score
local theScore = 0

-- the location of the score image

-- initialize the score
--         params.x <= X location of the score
--         params.y <= Y location of the score
function init( params )
    theScoreGroup.x = params.x
    theScoreGroup.y = params.y
    setScore( 0 )
end

-- retrieve score panel info
--        result.x <= current panel x
--        result.y <= current panel y
--        result.xmax <= current panel x max
--        result.ymax <= current panel y max
--        result.contentWidth <= panel width
--        result.contentHeight <= panel height
--        result.score <= current score
function getInfo()
    return {
        x = theScoreGroup.x,
        y = theScoreGroup.y,
        xmax = theScoreGroup.x + theScoreGroup.contentWidth,
        ymax = theScoreGroup.y + theScoreGroup.contentHeight,
        contentWidth = theScoreGroup.contentWidth,
        contentHeight = theScoreGroup.contentHeight,
        score = theScore
    }
end

-- update display of the current score.
-- this is called by setScore, so normally this should not be called
function update()
    -- remove old numerals
    theScoreGroup:remove(2)

    local numbersGroup = display.newGroup()
    theScoreGroup:insert( numbersGroup )

    -- go through the score, right to left
    local scoreStr = tostring( theScore )

    local scoreLen = string.len( scoreStr )
    local i = scoreLen   

    -- starting location is on the right. notice the digits will be centered on the background
    local x = theScoreGroup.contentWidth - theBackgroundBorder
    local y = theScoreGroup.contentHeight / 2
   
    while i > 0 do
        -- fetch the digit
        local c = string.byte( scoreStr, i )
        local digitPath = numbers[c]
        local characterImage = display.newImage( digitPath )

        -- put it in the score group
        numbersGroup:insert( characterImage )
       
        -- place the digit
        characterImage.x = x - characterImage.width / 2
        characterImage.y = y
        x = x - characterImage.width
        --
        i = i - 1
    end
end

-- get current score
function getScore()
    return theScore
end

-- set score to value
--    score <= score value
function setScore( score )
    theScore = score
   
    update()
end

score.lua 파일을 보겠습니다.
score.lua 파일 처음에 module(..., package.seeall) 를 넣습니다. 이러면 이 파일은 모듈로 사용될 수 있습니다.
이것을 했기 때문에 main.lua에서 require("score") 를 할 수 있고 이것을 변수 score에 담아서 score.(score.lua에 있는 함수) 방법으로 score.lua 내의 함수를 main.lua에서 사용할 수 있습니다.

그 다음 numbers 라는 테이블 변수에 0~9 의 이미지 파일을 담습니다.

그 다음은 display.newGroup()으로 객체들을 담을 그룹을 만듭니다.
그리고 score를 표시할 백그라운드 이미지를 넣고 theBackgroundBorder 변수에 10을 담습니다.

아까 만들었던  theScoreGroup에 백그라운드 이미지(theBackground)를 insert 합니다.
보시면 아까 theScoreGroup 말고 또다른 Group인 numberGroup을 만듭니다.
그리고 이 numberGrooup 자체를 처음의 theScoreGroup에 insert 합니다.

다음 줄에서 theScore 변수를 0으로 선언합니다. 처음 0점부터 시작하게 됩니다.
이제 함수가 나오네요. 아까 main.lua에서 불렀던 init(params) 함수가 나옵니다.

여기서는 theScoreGroup.x와 y를 전달받은 파라미터 값으로 넣고 setScore()함수에 0을 던져 줍니다.
아까 main에서 x=40, y=5 으로 보냈기 때문에 그 위치에 theScoreGroup의 객체들이 display 될 겁니다.

다음은 getInfo() 함수입니다. main함수에서는 이 함수에서 세팅된 값을 scoreInfo 변수에 담았었습니다.
getInfo() 함수에서는 7개의 값을 return합니다.
Lua (Corona) 에서는 복수개의 값을 return 할 수 있습니다.
theScoreGrooup,x , y 값과 ymax,ymax, contentWidth,contentHeight, score 등의 값을 return합니다.

다음은 update() 함수입니다.
처음엔 이전 score를 없앱니다.
그 다음은 numbersGroup이라는 그룹을 만들고 이를 통째로 theScoreGroup에 넣습니다.
다음줄은 theScore를 String 값으로 변환해서 scoreStr 변수에 담습니다.
Score가 한자리 수 인지 두자리 수인지 세자리 수인지 에 대한 정보를 담는 변수도 있네요.
scoreLen변수에 scoreStr 의 length를 담습니다.
local x, y는 점수가 표시되는 위치 입니다. 한자리 수일때 두자리 수일때 표시되는 위치가 조금씩 다르겠죠?

그 다음 while 문에서 어떤 점수 이미지를 어디에 뿌려줄지 계산하는 로직이 있습니다.
이미지를 출력하고 그 이미지를 numbersGroup에 담습니다.
그리고 나서 이미지의 x,y 좌표를 설정하구요.
이 while 문은 scoreLen 만큼 돌아갈 겁니다.

다음 함수는 getScore() 함수인데 여기서는 theScore를 리턴해 줍니다.

그리고 setScore() 함수에서는 theScore 함수에 score 값을 넣어 준 후 update() 함수를 실행시킵니다.

여기 까지 하면 scoring 샘플이 모두 완성된거구요.
이걸 실행 한 후 background를 누르면 점수가 1씩 증가하게 될 겁니다.

이 소스를 가지고 있으면 나중에 게임 개발할 때 아주 유용하게 사용할 수 있을 겁니다.

그럼.........

반응형

함수 (function) 공부

2011. 10. 3. 02:05 | Posted by 솔웅


반응형
오늘은 글로벌, 로컬 함수 사용법에 대해 자세히 살펴 보겠습니다.

함수의 기본

local function main()
   print("Hello from CoronaSDK")
end
main()

아주 평범하고 간단한 함수 작성 및 함수 불러오기 예제입니다.
main()함수는 로컬로 선언 됐고 터미널에 Hello from CoronaSDK 를 출력합니다.
이 함수는 맨 아랫 줄 에서 call 했습니다. (main() )

local main
  local function init()
    main()
  end
  function main()
   print("Hello from CoronaSDK")
  end
  init()

이번엔 main 을 로컬로 미리 선언해 놓고 함수 선언 시 local 을 따로 표시하지 않았습니다.
프로그램을 실행하면 로컬 init() 함수를 부를 것이고 이 init() 함수는 main() 함수를 부를겁니다. 문제없이 작동됩니다.
그런데 여기서

local main
  local function init()
    main()
  end
  local function main()
   print("Hello from CoronaSDK")
  end
  init()
이렇게 main() 함수 작성할 때도 local로 또 선언을 하면 에러가 나게 됩니다.
바로 init() 함수 안에서 main()함수를 불러올 때 에러가 납니다.
왜냐하면 main()을 불러올 때는 main() 함수가 없었기 때문에 main 이 nil 이기 때문에 불러오지 못하겠다고 에러 메세지를 뿌리는 겁니다.

local main

  local function main()
   print("Hello from CoronaSDK")
  end
  local function init()
    main()
  end
  init()
위 소스는 어떨까요. init()안에서 main()을 불러올 때 이미 그 위에서 로컬 main() 함수가 구현 돼 있죠? 이 경우는 제대로 실행이 될 겁니다.

Encapsulated Function (함수 캡슐화)

local function main()
    print ("Hello from CoronaSDK")

    function insideMain()
      print("This is a function inside of main, not available to be called from any other")
    end

   insideMain()
  end
 
  main()

자 이렇게 함수 안에 함수가 있는 경우 제대로 실행 됩니다.

local function spawnObject()
   local resultingObject = display.newRect(10,10,100,100)
   local function onTouch(event)
     if "ended" == event.phase then
      print("touched rectangle")
     end
   end
   resultingObject:addEventListener("touch",onTouch)
   return resultingObject
  end

  local xTemp = spawnObject()

위 코드를 실행하면 어떻게 될까요?
우선 맨 밑에 로클 xTemp 변수에 spawnObject() 가 담기변서 spawnObject() 가 실행 됩니다.
spawnObject()에서는 처음에 resultingObject라는 사각형을 그리게 됩니다.
그리고 event를 받는 onTouch라는 함수를 만들구요.
여기서는 touch 이벤트가 끝날 때 터미널에 문자열을 출력합니다.
그 다음엔 아까 그린 사각형에 리스너를 답니다. touch 가 일어났을 경우 onTouch 함수롤 불러오도록 설정합니다.
그 다음에 이 사각형을 리턴합니다.
이제 화면에 있는 사각형을 클릭하면 터미널에 문자열이 찍히게 됩니다.

Member functions (멤버 함수들)

멤버함수들은 객체가 되는 방법이 일반 함수들과 조금 다릅니다. 바로 전 코드에다가 색을 바꾸는 함수도 한번 추가해 봅시다.
일단 사각형 색을 바꾸는 함수 부분을 아래와 같이 추가합니다.
local function spawnObject()
   local resultingObject = display.newRect(10,10,100,100)

   function changeColor(theColor)
     resultingObject:setFillColor(theColor[1],theColor[2],theColor[3])
   end

   local function onTouch(event)
     if "ended" == event.phase then
      print("touched rectangle")
     end
   end

   resultingObject:addEventListener("touch",onTouch)

   return resultingObject
  end

  local xTemp = spawnObject()

자.. 이제 저 함수에 theColor라는 파라미터를 넣어서 불러오는 것을 해야 하는데요.
이 함수는 local로 선언돼 있지 않지만 spawnObject() 함수 밖에서는 불러올 수 없습니다.
spawnObject() 함수 안에 이 함수가 있으니까요.
그럼 밖에서 불러올 수 있도록 살짝 바꿔 보겠습니다.

local function spawnObject()
   local resultingObject = display.newRect(10,10,100,100)

   function resultingObject.changeColor(theColor)
     resultingObject:setFillColor(theColor[1],theColor[2],theColor[3])
   end

   local function onTouch(event)
     if "ended" == event.phase then
      print("touched rectangle")
     end
   end

   resultingObject:addEventListener("touch",onTouch)

   return resultingObject
  end

  local xTemp = spawnObject()
  xTemp.changeColor({255,0,255})

changeColor(theColor) 함수를 spawnObject()의 멤버인 resultingObject 의 멤버 함수로 선언했습니다.
이렇게 하면 spawnObject() 함수 밖에서 불러올 수 있습니다.
spawnObject() 를 xTemp 변수에 담았으니까. xTemp.changeColor({255,0,255}) 하게 되면 spawnObject()함수 안에 있는 changeColor를 불러오게 됩니다. 이 함수는 멤버인 사각형의 함수이니까요.

단순히 이 기능만 실행되게 하려면 여러 방법이 있습니다.
아래와 같이 해도 되구요.
local function spawnObject()
   local resultingObject = display.newRect(10,10,100,100)

   function changeColor(theColor)
     resultingObject:setFillColor(theColor[1],theColor[2],theColor[3])
   end

   local function onTouch(event)
     if "ended" == event.phase then
      print("touched rectangle")
     end
   end

   resultingObject:addEventListener("touch",onTouch)
    changeColor({255,0,255})
   return resultingObject
  end

  local xTemp = spawnObject()
  --xTemp.changeColor({255,0,255})

그런데 함수내 함수를 함수 바깥에서 부르기 위해서 멤버 변수를 사용한다는 예제를 배우는게 목적이니까 그걸 염두에 두고 보시면 될 거예요.

이 강좌의 원래 원본은 아래에 있습니다.
http://blog.anscamobile.com/2011/09/tutorial-scopes-for-functions/
코로나 SDK 만든 회사인 Ansca Mobile 에 있는 블로그에 있는 글입니다.

Cross calling

 다른 함수 안에 있는 함수 불러오는 방법 두번째 인데요. 이건 약간 복잡합니다.

local function spawnObject()
   local resultingObject = display.newRect(10,10,100,100)

   function resultingObject.changeColor(theColor)
     resultingObject:setFillColor(theColor[1],theColor[2],theColor[3])
   end

   local function onTouch(event)
     if "ended" == event.phase then
      print("touched rectangle")
     end
   end

  function wrap(event)
   if self.x < 25 then self.x = 25 end
   if self.x > 743 then self.x = 743 end
  end

   resultingObject:addEventListener("touch",onTouch)

   return resultingObject
  end

  local xTemp = spawnObject()
  Runtime:addEventListener("enterFrame", xTemp.wrap)

이 코드는 spawnObject() 안에 wrap(event) 함수를 만들고 spawnObject()
바깥에서 이 함수안의 wrap함수에 리스너를 답니다.

그런데 조금 이상하네요. 에러도 나구요. 아래 코드를 보겠습니다.

local function spawnObject() local resultingObject = display.newRect(10,10,100,100) function resultingObject.changeColor(theColor) resultingObject:setFillColor(theColor[1],theColor[2],theColor[3]) end local function onTouch(event) if "ended" == event.phase then print("touched rectangle") end end local function wrap(event)
print("in function wrap")
 if resultingObject.x < 25 then resultingObject.x = 25 end if resultingObject.x > 743 then resultingObject.x = 743 end end resultingObject:addEventListener("touch",onTouch) Runtime:addEventListener("enterFrame", wrap) return resultingObject end local xTemp = spawnObject()

이건 제대로 작동합니다.
spawnObject() 이 실행 됐을 때 이와 관련된 모든 함수들이 캡슐화 됐습니다.
이 코드의 목적은 이렇게 캡슐화 시키는 건가 봅니다.

Advanced Function

local hide = print
local print = math.random
local function main()
hide("What do you have to hide?")
hide( print(5) )
end

main()

루아는 위와 같이 함수를 변수에 담아서 편리하게 사용할 수 있습니다.

오늘은 함수와 관련된 좋은 아티클이 있길래 한번 공부해 봤습니다.
제가 100% 다 이해하지는 못한 것 같습니다.

저보다 더 프로그래밍 기초가 튼튼하신 분들은 좀 더 확실해 이해 하셨겠죠?
제가 잘못 이해했거나 부족한부분.... 덧 붙이고 싶으신 부분 있으면 언제든지 코멘트 해 주세요.

감사합니다.
반응형


반응형
accelerometer 는 가속도계라는 의미입니다.

이걸 적용하면 디바이스 기울기 이벤트에 따라 핸들링 할 수 있습니다.

어제 샘플로 드렸던 사람 인형 예제에 이 기능을 적용하도록 하겠습니다.

맨 밑줄에 아래 리스너를 다세요.

Runtime:addEventListener ("accelerometer", onAccelerate)

앱이 실행중에 이 리스너를 단다는 얘기예요. accelerometer 가 일어 날 때 onAccelerate 함수를 실행 하라는 의미구요.

그럼 저 리스너 위에 onAccelerate 함수를 만들어 넣을까요?

local function onAccelerate( event )
physics.setGravity( 10 * event.xGravity, -10 * event.yGravity )
end

이 함수는 중력을 세팅해주는 함수입니다.
기울기를 구해서 그것에 맞게 중력을 주는 겁니다.

아주 간단하죠?

이건 시뮬레이터에서는 못하구요. 직접 디바이스에 인스톨 하신 후에 테스트 할 수 있어요.

참고로 리스너 다는 방법은 아래와 같습니다.

object:addEventListener( eventName, listener )

eventName 에 들어갈 수 있는 것들은 touch, enterFrame, tap, accelerometer , collisiion, sprite 등 여러가지가 있습니다.

API 의 events 부분을 참고하세요.



반응형

Physics Joints 예제 코드

2011. 9. 29. 22:54 | Posted by 솔웅


반응형
Physics Joints 예제를 살펴 보겠습니다.

-살펴보기 전에 여러분들이 댓글로 힘을 주시면 고맙겠습니다. 댓글 꼭 남겨 주세요. -



위 코드를 살펴 보겠습니다.
자동차 몸체와 바퀴가 조인트로 연결 돼 있고 앱을 시작하면 위에서 밑으로 떨어집니다.
다 떨어지면 언덕에서 아래로 구르게 됩니다.

display.setStatusBar( display.HiddenStatusBar )

require "physics"
physics.start()
physics.setDrawMode("hybrid")

--Determine Device Size
local disw = display.contentWidth
local dish = display.contentHeight

local car = display.newGroup()

여기 까지는 따로 설명이 필요 없구요. (newGroup()에 대해서는 나중에 살펴 볼 겁니다.)

--Create The Car
local car_body = display.newRect(0, 0, 102, 20)
car_body:setFillColor(100,75,75)
local car_rear_wheel = display.newCircle( 10, 30, 15 )
local car_front_wheel = display.newCircle( 80,30,15 )
car_rear_wheel:setFillColor(25,128,25)
car_front_wheel:setFillColor(25,25,200)

여긴 자동차를 만드는 부분입니다.몸통을 newRect로 만들고 색을 정해 주고 앞바퀴와 뒷바퀴를 newCircle()을 이용해서 만듭니다.

여기까지 하면 이렇게 나옵니다.

자동차 몸통이 있고 그 아래에 파랗고 녹색인 바퀴 두개가 있습니다.



















































그 다음은 바닥과 왼쪽 오른쪽 벽 그리고 경사도를 아래와 같이 만듭니다.
--Add An Environment
local floor = display.newRect(0, dish-10, disw,dish )
local lwall = display.newRect(0, 0, 0,dish )
local rwall = display.newRect(disw,0,disw,dish)
local ramp = display.newLine(0,dish-40,disw,dish-10)
local rshape = {0,0,disw,30}
ramp.width = 3


여기 까지 하면 위와 같이 나옵니다.

rshape는 나중에 ramp에 addBody할 때 사용할 겁니다.


한번 볼까요?


--Apply The Physics
physics.addBody(floor,"static", {friction=0.5})
physics.addBody(lwall,"static", {friction=0.5})
physics.addBody(rwall,"static", {friction=0.5})
physics.addBody(ramp,"static",{friction=1,shape=rshape})
physics.addBody(car_body,{density=0,friction=0,bounce=0})
physics.addBody(car_rear_wheel,{density=2,friction=3,bounce=0,radius=15})
physics.addBody(car_front_wheel,{density=2,friction=3,bounce=0,radius=15})


지금까지 만든 객체들에 addBody를 해 줍니다.

자동차 몸체와 바퀴만 빼 놓고 나머지는 static으로 해 줍니다. 움직이지 않게 하겠다는 뜻이죠?

몸체와 바퀴는 표시를 안 했으니 디폴트 값인 dynamic이 적용 됩니다.

그러면 디폴트 중력인 9.8의 중력을 받아서 밑으로 떨어지게 될 겁니다.



이 상태로 실행하면 dynamic이 적용된 몸체와 바퀴가 떨어져서 경사면에서 아래로 미끄러지거나 구르게 됩니다.


그런데 아직 서로간의 관계를 joint를 써서 사용하지 않았기 때문에 따로따로 떨어져서 움직이게 됩니다.


이제 Joint를 사용하겠습니다.


--Add The Wheel Joints and Let The Problems Begin
local rear_wheel_joint = physics.newJoint("wheel",car_body,car_rear_wheel,10,10,0,10)
local front_wheel_joint = physics.newJoint("wheel",car_body,car_front_wheel,80,10,0,10)


앞바퀴와 뒷바퀴를 각각 자동차 몸체에 wheel joint를 사용해서 연결했습니다.



몸체랑 바퀴랑 연결되긴 했는데 뭔가가 조금 이상하죠?

일단 바퀴끼리 막 서로 가까워지기도 하고 그러니까 이 거리를 항상 유지해 보죠.


--Keep The Wheels Separated                                         
local wheel_distance_joint = physics.newJoint("distance",car_rear_wheel, car_front_wheel, car_rear_wheel.x, car_rear_wheel.y, car_front_wheel.x, car_front_wheel.y)

distance joint를 써서 두 바퀴의 거리를 항상 유지하도록 했습니다.


돌려 보시면 아까보다는 조금 나아 졌는데 아직 완벽하지는 않습니다.


나머지는 여러분들이 weld, pivot 조인트 등을 사용하셔서 한번 적용해 보세요.


현재까지의 전체 소스는 아래와 같습니다.

display.setStatusBar( display.HiddenStatusBar )

require "physics"
physics.start()
physics.setDrawMode("hybrid")

--Determine Device Size
local disw = display.contentWidth
local dish = display.contentHeight

local car = display.newGroup()

--Create The Car
local car_body = display.newRect(0, 0, 102, 20)
car_body:setFillColor(100,75,75)
local car_rear_wheel = display.newCircle( 10, 30, 15 )
local car_front_wheel = display.newCircle( 80,30,15 )
car_rear_wheel:setFillColor(25,128,25)
car_front_wheel:setFillColor(25,25,200)

--Add An Environment
local floor = display.newRect(0, dish-10, disw,dish )
local lwall = display.newRect(0, 0, 0,dish )
local rwall = display.newRect(disw,0,disw,dish)
local ramp = display.newLine(0,dish-40,disw,dish-10)
local rshape = {0,0,disw,30}
ramp.width = 3

--Apply The Physics
physics.addBody(floor,"static", {friction=0.5})
physics.addBody(lwall,"static", {friction=0.5})
physics.addBody(rwall,"static", {friction=0.5})
physics.addBody(ramp,"static",{friction=1,shape=rshape})
physics.addBody(car_body,{density=0,friction=0,bounce=0})
physics.addBody(car_rear_wheel,{density=2,friction=3,bounce=0,radius=15})
physics.addBody(car_front_wheel,{density=2,friction=3,bounce=0,radius=15})

--Add The Wheel Joints and Let The Problems Begin
local rear_wheel_joint = physics.newJoint("wheel",car_body,car_rear_wheel,10,10,0,10)
local front_wheel_joint = physics.newJoint("wheel",car_body,car_front_wheel,80,10,0,10)

--Keep The Wheels Separated                                         
local wheel_distance_joint = physics.newJoint("distance",car_rear_wheel, car_front_wheel, car_rear_wheel.x, car_rear_wheel.y, car_front_wheel.x, car_front_wheel.y)

--Populate The Scene
car:insert(lwall)
car:insert(rwall)
car:insert(floor)
car:insert(ramp)
car:insert(car_body)
car:insert(car_rear_wheel)
car:insert(car_front_wheel)

return car


끝에 insert는 처음에 설정했던 car라는 그룹에 각 객체들을 넣는다는 겁니다.

나중에 화면전환 같은 거 할 때 유용하게 사용 됩니다.

이 부분은 그 때 자세히 살펴 보도록 하겠습니다.


소스 하나만 더 소개시켜 드릴까요?



저 인형이 하늘에서 떨어져서 쓰러지는 건데요.

목, 팔, 다리,몸통 관절들이 제법 사실적으로 움직입니다.


이 샘플은 pivot 조인트만 이용해서 만들었습니다.

그리고 RotationLimits를 이용해서 팔관절, 다리 관절등이 반대로 꺾이지 않게 했구요


그리고 머리, 팔, 다리를 끌고 드래그 하면 그에 따라 움직이는데요.

이건 물론 touch 조인트를 이용해서 만들었구요.


샘플 코드를 압축해서 올려 놓습니다.

소스 분석은 직접 한번 해 보세요.



그리고 참고로 Box2D의 개발자 매뉴얼도 올려 놓습니다.


조인트에 대한 부분도 나와있으니까 한번 살펴 보세요.

이로서 Corona SDK의 Physics Engine에 대해 모두 훑어 봤습니다.


다음 시간부터는 어떤 주제로 할까요?

여하튼 다음에 뵈요....

반응형

Physics Joint

2011. 9. 28. 22:53 | Posted by 솔웅


반응형
오늘은 Physics의 Joint 라는 개념에 대해 알아보겠습니다.
조인트는 여러 객체를 연결시키는 겁니다. 예를 들어 인형의 머리 몸통 팔 다리 이미지가 있을 때 이 이미지들을 joint로 연결해서 자연스럽게 움직이게 할 수 있습니다.
코로나 샘플을 보면 Bridge나 Chains에서 이 기술을 썼습니다.
























































보시면 다리를 구성하는 나무조각과 체인을 구성하는 각각의 체인들이 이 조인트로 연결 된 겁니다.
Joint를 사용하시려면 우선 객체를 만들어서 addBody를 하신 후에 그 객체에 Joint를 선언하시면 됩니다.

Joint 의 종류에는 총 8가지가 있습니다.
이 8가지에 대해 하나하나씩 알아가 보도록 하겠습니다.

오늘은 간략하게 각 조인트들을 적용해서 실행해 볼께요.
아래 코드를 가지고 여러 조인트들을 적용해 보겠습니다.
이 코드는 지난번에 충돌(Collision) 인가에서 썼던 코드일 겁니다.

local physics = require "physics"
physics.start()
system.activate( "multitouch" )
physics.setDrawMode( "hybrid" )

local ground = display.newImage( "ground.png" )
ground.x = display.contentWidth / 2
ground.y = 445
ground.myName = "ground"

physics.addBody( ground, "static", { friction=0.5, bounce=0.3 } )

local crate1 = display.newImage( "crate.png", 20, 70 )
physics.addBody( crate1, { density=3.0, friction=0.5, bounce=0.3 } )
crate1.myName = "first crate"
crate1.setGravity = 2
 
local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density=3.0, friction=0.5, bounce=0.3 } )
crate2.myName = "second crate"

-- A general function for dragging physics bodies
local function dragBody( event )
        local body = event.target
        local phase = event.phase
        local stage = display.getCurrentStage()
 
        if "began" == phase then
                stage:setFocus( body, event.id )
                body.isFocus = true
      -- Create a temporary touch joint and store it in the object for later reference
                body.tempJoint = physics.newJoint( "touch", body, event.x, event.y )
 
        elseif body.isFocus then
                if "moved" == phase then  
                        -- Update the joint to track the touch
                        body.tempJoint:setTarget( event.x, event.y )
                elseif "ended" == phase or "cancelled" == phase then
                        stage:setFocus( body, nil )
                        body.isFocus = false
                        -- Remove the joint when the touch ends                
                        body.tempJoint:removeSelf()
                end
        end
 
        -- Stop further propagation of touch event
        return true
end
 
-- Make object draggable
crate1:addEventListener( "touch", dragBody )
crate2:addEventListener( "touch", dragBody )

이미지는 아래 이미지들이 필요합니다.










잠깐 코드를 보면 처음에 physics를 import 해서 start 시켰구요.
멀티터치를 적용했네요. (이건 나중에 따로 살펴 볼 겁니다.)
그리고 draw mode는 hybrid로 해서 body가 적용된 사항을 볼 수가 있게 했구요.

그 다음에 ground.png를 그리고 여기에 static 으로 addBody를 했습니다.
다음은 crate.png로 두개의 객체를 만든 후 addBody를 했습니다.
dynamic이 디폴트니까 이게 적용 됐겠네요.

맨 밑에 보면 crate1, crate2에 touch에 반응하는 리스너를 달았고 touch 시 dragBody 함수를 불러오도록 했습니다.

dragBody에는 touch 조인트가 사용됐네요.
touch 해서 move 할 때는 touch 조인트가 적용되서 박스가 그럴듯 하게 움직일 겁니다. 그리고 touch 가 끝나면 조인트가 remove되서 박스는 떨어지겠네요.


이대로 실행해 보시구요.

시뮬레이터인 경우엔 마우스로 디바이스인 경우엔 손가락으로 박스를 옮겨 보세요.
그리고 막 돌려보시고 박스로 박스를 밀쳐보시고 한번 해 보세요.

그럼 Touch Joint 가 어떤식으로 적용 되는지 아실거예요.

Touch 조인트는 다른 조인트와 다르게 객체 하나에만 적용 됩니다.

그럼 이 소스를 기본으로 해서 다른 여러 joint들을 적용해 볼까요?

1. Pivot Joint
Pivot 은 중심, 축 이라는 뜻입니다. Box2D에서는 revolute Joint 라고 합니다.
코드 맨 밑에 아래 코드를 추가해 보세요.
myJoint = physics.newJoint( "pivot", crate1, crate2, 50,60 )

신택스는 아래와 같습니다.

physics.newJoint( jointType, object1, object2, anchorX, anchorY )

jointType은 "pivot"이고 object2, object2는 조인트 대상입니다.

anchorX-x 앵커 포인트, anchorY - y 앵커 포인트


실행해 보시고 박스를 막 움직여 보세요. 그러면 일정 거리만큼 객체들이 유지 될 거예요. 앵커 포인트가 객체 위치와 비교해서 어디로 설정 되느냐에 따라 두 객체의 연결 관계가 변합니다.


2. Distance Joint
pivot 조인트 부분을 주석처리하시고 아래 코드를 넣어보세요.
myJoint = physics.newJoint( "distance", crate1, crate2, crate1.x,crate1.y, crate2x,crate2.y )
Distance Joint는 두개의 객체 거리가 Fix돼 있게 만든 겁니다.

3. Piston Joint
피스톤 조인트는 아래 코드를 적용해 보세요.
myJoint = physics.newJoint( "piston", crate1, crate2, crate1.x,crate1.y, 50,80 )
이 조인트는 Box2D에서는 prismatic joint라고 불립니다.
이 조인트는 피스톤이나 자동차의 shock Observer같이 움직이는 조인트라고 합니다.
첫번째 객체의 앵커포인트를 기준으로 움직일 때 축을 기준으로 다른 객체가 움직입니다.
제 소스에서는 한 박스가 옆쪽으로 ground를 넘어서면 한 없이 떨어지고 다시 올리기가 힘든데요.
여러분은 상하좌우 wall을 만들고 이것에 addBody를 하셔서 한번 테스트 해 보시면 좋을 것 같네요.

4. Friction Joint
friction이 마찰, 저항이란 뜻인데요. pivot 조인트의 특별한 형태입니다.
움직일 때 약간 저항을 받는것 같네요. 코로나에서는 Sticky라고 표현했습니다.
myJoint = physics.newJoint( "friction", crate1, crate2, 10,30 )
위 코드로 테스트 해 봤는데... 전 정확히 어떤 특징이 있는지 모르겠네요.

5. Weld Joint
이 조인트는 확실히 알겠습니다.
Weld가 용접이란 뜻이니까 두 객체가 용접된 듯이 딱 붙어서 떨어지지 않는다는 얘기겠죠?
myJoint = physics.newJoint( "weld", crate1, crate2, 200,300 )
이렇게 하면 두 박스가 떨어져 있지만 용접한 것처럼 완전 고정돼 있습니다.
여러 객체를 붙일 때는 Weld 보다 여러 객체를 합한 body를 사용하는게 낫다고 코로나에서 그러네요.

6. Wheel Joint
Box2D에서는 line joint라고 합니다. piston과 pivot 조인트가 합쳐진 것이라고 하는데요. piston joint와 거의 같은데 다른 점은 축의 끝에서 로테이션이 가능하다고 합니다.
myJoint = physics.newJoint( "wheel", crate1, crate2, crate1.x,crate1.y, 50,80 )

7. Pulley Joint
myJoint = physics.newJoint( "pulley", crate1, crate2, 50,80, 200,120, crate1.x,crate1.y, crate2.x,crate2.y, 3.0 )
이 조인트를 사용했더니 두 박스가 대롱대롱 매달려있네요.
오른쪽 박스를 잡아 당기면 왼쪽 박스가 막 움직이구요.

8. Touch Joint
이 조인트는 이미 기본 소스에 적용 돼 있습니다.
Box2D에서는 mouse joint라고 한답니다. 객체가 마우스를 따라 다녀서 그런가요?

조인트를 없애려면 아래와 같이 합니다.
myJoint:removeSelf()

이렇게 Joint 까지 하고 나니 Box2D를 사용한 코로나의 Physics Engine에 대해 한번 다
훑어 봤습니다.

다음 토픽으로 넘어가기 전에 다음 시간엔 joint에 관한 샘플코드 몇가지를 살펴보고
넘어갈 까 합니다.

그럼 다음 시간에 뵐께요.



반응형

Physics Bodies 코딩 하기

2011. 9. 28. 02:16 | Posted by 솔웅


반응형
오늘은 지난 강좌때 했던 Physics Bodies 를 토대로 직접 코딩을 해 나가겠습니다.

우선 배경 그림부터 그려 넣을까요?


위 세가지를 이용할 겁니다.
일단 physics 엔진을 사용할거니까 아래 코드를 넣어 주세요.

local physics = require("physics")
physics.start()
physics.setDrawMode( "hybrid" )

setDrawMode는 physics.addBody 한 후에 확인하기 위해 hybrid롤 설정했습니다.
그 다음은 이번 코딩 내용과 크게 상관은 없지만 제가 항상 넣는 코드니까 일단 넣겠습니다.
display.setStatusBar( display.HiddenStatusBar )
_W = display.contentWidth;
_H = display.contentHeight;
첫번째는 아이폰의 Status Bar를 없애는 것이구요. 그 다음 두 줄은 스크린의 Width,Height를 변수에 담는 겁니다.
실제 앱을 만들다보면 이미지나 텍스트 배치 등에 이게 많이 쓰입니다.

그 다음은 배경 이미지들을 배치 하겠습니다.

local bkg = display.newImage( "bkg_cor.png" )
local grass = display.newImage("grass.png")
grass.x = 160; grass.y = 430
local grass2 = display.newImage("grass2.png")
grass2.x = 160; grass2.y = 440

여기까지 하면 아래와 같이 화면이 보입니다.


이제 grass에 addBody를 해 보죠.
physics.addBody( grass, "static", { friction=0.5, bounce=0.3 } )


아래 연두색으로 사각형이 표시돼 있죠?
이건 grass.png파일에 addBody를 해서 그래요. 저 위쪽에 setDrawMode를 hybrid로 설정했잖아요.

지금까지 사용한 이미지는 총 3가지 이고 이중 한가지(grass.png)에만 physics.addBody를 했습니다.
그리고 그 객체는 static으로 지정돼 있습니다.
이 객체는 움직이지 않을 겁니다. 그리고 다른 addBody가 적용된 움직이는 객체가 와서 부딪히면 physics엔진의 적용을 받을 겁니다.

이제 네모난 상자를 위에서 떨어뜨려 볼까요?

이 이미지를 사용합니다.
아래 두줄을 추가하세요.
local crate = display.newImage( "crate.png", 100, 50 )
physics.addBody( crate, { density = 1.0, friction = 0.3, bounce = 0.2 } )

그리고 실행해 보세요. 박스가 땅으로 떨어지죠? 그리고 땅 밑으로 더 이상 꺼지지 않구요.
이제 crate의 bounce를  0.8정도로 올려 보세요.


박스가 아주 탄력있게 튀어오르죠?
gravity를 바꾸고 grass의 bounce도 바꾸고 또 다른 density나 frictions 같은 값들을 바꾸면서 테스트 해 보면 이 값들이 하는 역할을 자세히 알 수 있을 거예요.

그리고 위에 보시는 대로 addBody 할 때 grass는 static으로 했습니다. 그리고 crate(박스) 는 지정하지 않았습니다. 디폴트는 dynamic이기 때문에 다이나믹으로 돼 있습니다.
hybrid를 적용 했을 때 static은 녹색으로 dynamic은 오렌지색으로 보입니다.

이젠  addBody를 사각형말고 원형으로 적용시켜 볼까요?

이 이미지를 다운 받고 rock.png로 네이밍 해 주세요.
local crate = display.newImage( "rock.png", 150, 20 )
physics.addBody( crate, { density = 1.0, friction = 0.3, bounce = 0.3, radius=30} )

바위는 bounce를 0.3으로 해 봤습니다.


전 박스와 바위 x,y좌표를 조금 바꿨는데.. 땅에 떨어지면서 서로 부딪혀서 옆으로 튕겨 나가네요. 박스는 통통 튀고 바위는 구릅니다.
하여간 보시다시피 바위는 radius=30을 적용해서 원형으로 addBody가 됐습니다.

이제 다각형을 해야 하나요?
이건 좌표값들을 일일이 계산하기가 힘들어서...
어제 강좌에 실었던 Physics Editor로 했는데 잘 안 되더라구요.
오늘 메일을 그 회사로 보내놓긴 했는데 언제 답이 올지는 모르겠습니다.

그냥 딱 맞지는 않지만 PhysicsEditor로 받아온 좌표값을 적용해서 한번 해 보겠습니다.

왼쪽 탁구채를 할겁니다. 이름은 endingblue.png 입니다.
이름이 조금 이상하지만.. 제가 얼마전에 만들었던 Multi Player Ping Pong 에서 사용했던 이미지 입니다.
현재 안드로이드 마켓에만 publish돼 있고 게임 카테고리에 있어서 한국에서는 다운 받기 어려울 겁니다. (무료인데.....)
아이폰은 현재 Waiting For Review 상태입니다.


일단 아래와 같이 코딩하세요.

local pingpong = display.newImage("endingblue.png",100,10)
pingpongShape = {   -26.5, 17  ,  -27.5, -55  ,  -25.5, -55  ,  25.5, 16  ,  -18.5, 20  ,  -23.5, 20,
                28.5, -51  ,  34.5, -40  ,  37.5, -27  ,  37.5, -2  ,  27.5, 16  ,  -25.5, -55  ,  24.5, -55
                 }
                 
physics.addBody(pingpong,"dynamic",{density=3.0, friction=0.8, bounce=0.6, shape=pingpongShape})


보시면 다각형이 아주 이상하게 됐죠?
PhysicsEditor로 했는데 제가 사용법을 잘 몰라서 이상하게 됐나봐요.
(혹시 누가 포토샵이나 플래시 아니면 아무 프로그램 이던지.. 이런 좌표 쉽게 알아내는 방법 아시면 알려 주세요.)

어쨌든 다각형을 구현하려면 테이블에 필요한 x,y좌표들을 배열 한 다음에 addBody 의 shape 파라미터에 그 테이블 이름을 대입하시면 됩니다.

이외에 Sensor 라는 개념도 있습니다.
이 센서는 다른 body와 작용을 하지는 않습니다. 다만 collision 이 일어났을 때 그걸 감지할 수 있습니다.

아래 코드를 추가해서 실행해 보세요.
local rect = display.newRect( 50, 50, 100, 100 )
rect:setFillColor( 255, 255, 255, 100 )
rect.isVisible = false  -- optional
physics.addBody( rect, { isSensor = true } )

실행하시면 이 사각형은 보이지 않을 겁니다. (다만 hybrid가 적용되서 오렌지 사각형 영역이 보일겁니다.)
그리고 이 사각형은 다른 모든 body들을 그냥 통과할 것이고 땅으로(밑으로) 꺼져 버리게 될 겁니다.
이 센서 기능도 실제 앱에서 유용하게 사용할 수 있겠죠?
예를 들어 어느 영역(골대)로 가면 튕기지는 않고 통과하지만 점수는 올라가게 한다든지 하는....

addBody를 해제 하려면 아래와 같이 합니다.
myBody:removeSelf()
-- or --
myBody.parent:remove( myBody )
 
body 적용이 필요 없어지게 되면 이렇게 addBody를 해제하면 되겠죠?

끝으로 Body의 프로퍼티들과 메소드 들을 살펴 보겠습니다.

body.isAwake

myBody.isAwake = true
local state = myBody.isAwake
아까 만든 코드를 실행해 보면 박스나 바위 같은게 땅에 튕기다가 완전히 서게 되면 오렌지색이 어둡게 변하게 됩니다.
이렇게 아무런 상호작용이 일어나지 않을 경우 body 는 go to sleep 하게 됩니다.
현재의 isAwake 상태를 볼 수도 있고 true,false 값으로 설정할 수 있습니다.

body.isBodyActive

myBody.isBodyActive = true
local state = myBody.isBodyActive
현재 body의 액티브 상태를 나타냅니다. inactive 상태인 바디는 destroy되지 않습니다. 다만 다른 바디와 상호작용을 할 수는 있습니다.

body.isBullet

이건 바디가 총알로 취급되느냐 안 되느냐라는 거랍니다. 당연히 디폴트는 false구요.
정확한 건 저도 써 보지 않아서 모르겠습니다.

body.isSensor

센서는 아까 설명했듯이 다른 바디들과 상호작용은 안 하지만 충돌 등은 체크할 수 있는 기능입니다. 이 파라미터는 Sensor가 적용 됐는지 안 됐는지 알아볼 수 있습니다.

body.isSleepingAllowed

body가 항상 awake돼 있으면 메모리 차지를 많이 하겠죠? 이건 필요할 때 sleeping모드로 바꿀 수 있으면 바꿔야 할 때 사용 가능하겠네요.

body.isFixedRotation

로테이션이 lock됐는지 안 됐는지 알아 볼 수 있습니다.

body.angularVelocity

myBody.angularVelocity = 50
local v = myBody.angularVelocity
현재 로테이션 속도를 알 수 있습니다. 1초당 몇도 돌아가는지 알려 줍니다.
그리고 로테이션 속도도 설정할 수 있습니다.

body.linearDamping

이건 잘 모르겠는데.... 바디 객체가 얼마나 젖어 있는지 설정하는 건가?

body.botyType

myBody.bodyType = "kinematic"
local currentType = myBody.bodyType
static,dynamic,kinematic 인지 알아 내거나 설정할 수 있는 파라미터 입니다.


TIP : 코로나에서는 파라미터를 사용할 때는 . 를 사용하고 메소드를 사용할 때는 : 를 사용합니다.

Body Methods

body:setLinearVelocity
body:getLinearVelocity

바디의 속도를 set,get 하기 위한 메소드 입니다.
myBody:setLinearVelocity( 2, 4 );  vx, vy = myBody:getLinearVelocity();

body:applyForce
이건 한번도 사용 안해 봐서 잘 모르겠습니다.
myBody:applyForce( 500, 2000, myBody.x, myBody.y )

body:applyTorque

로테이션 force 값을 줄 때 사용합니다. 코로나에서 말하는 force는 뭘까요?
myBody:applyTorque( 100 )

body:applyLinearImpulse
이것도 applyForce랑 비슷하다네요. 아주 거친 느낌을 주는 무엇인거 같은데..

body:applyAngularImpulse

코로나에서 Force 와 Impulse에 대해 비교 설명하고 있네요.
이 두가지가 서로 헛갈리는 개념인가봐요.

impulse는 body에 즉각적으로 kick하고 force는 시간을 두고 영향을 미친다고 하네요.
한번 설정했다고 계속 적용되는 개념들이 아닌가봐요.

이거 익숙해지려면 많이 적용해 봐야겠습니다.

body:resetMassData

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

이런 파라미터들과 메소드들이 있답니다.
이 것들과 친해지는건 각자의 몫이구요.

다음시간에는 Physics Joints에 대해 다뤄 보도록 하겠습니다.

감사합니다.

P.S.) 댓글 달아 주시면 강좌 올리는 저도 좀 더 신날 것 같습니다.
         댓글 달아주세요....


오늘 모든 코드파일과 이미지 파일등은 위 압축파일에 담겨 있습니다.
참고하세요.
반응형

Physics Bodies

2011. 9. 27. 05:15 | Posted by 솔웅


반응형
object 에 물리 엔진을 적용하려면 addBody를 해 줘야 합니다.
그러면 그 적용된 객체는 코로나에서 제공하는 물리엔진의 적용을 받게 됩니다.
코로나에서는 box2D라는 물리엔진을 사용하고 있구요.

addBody에는 다음과 같이 3개의 프로퍼티들을 가지고 있습니다.

density : 사전에 보면 밀도라고 나옵니다. 코로나에서는 물의 density를 1.0으로 하고 있습니다. 그러므로 나무처럼 물에 뜨는 일반 물질들은 그 이하의값들을 넣어야 겠죠? 쇠 같이 물에 가라앉는 것들은 1.0보다 커야 하겠구요. 이 값은 gravity (중력)과 pixels to meter scale setting 과도 관계가 있습니다. 이러한 값들을 다양하게 바꿔가면서 테스트 해야 친숙해 질 것 같습니다. 디폴트는 1.0 입니다.

friction : 마찰입니다. 디폴트는 0.3 입니다. 0.0은 아무런 마찰력이 없는 것이고 1.0은 강한 마찰력이 있는 것입니다.

bounce : bounce 는 box2D 에서는 내부적으로 restitution 로 알려져 있다고 합니다. 객체끼리 충돌(collision) 하고 난 후 속도의 변화를 얼마나 줄 지에 대한 값입니다. 점점 줄어들면 공이 튀다가 땅에 멈춰 서듯이 설것이고 하나도 줄어들지 않으면 (1.0) 벽돌깨기의 공처럼 계속 튈 겁니다. 디폴트는 0.2 입니다. 그리고 1.0 보다 큰 값도 넣을 수 있습니다.

addBody는 아래와 같이 사용합니다.
local crate = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate, { density = 1.0, friction = 0.3, bounce = 0.2 } )

이렇게 되면 crate.png 이미지 파일에 physics가 적용됩니다.
물론 이것은 전체 이미지 크기인 사각형으로 적용됩니다. 이미지 파일 안의 특정한 이미지(병, 바퀴 같은) 에 적용되는게 아닙니다.

addBody의 프로퍼티들을 한번 지정한 후 이를 많은 객체들에 적용할 수도 있습니다.

local crate1 = display.newImage( "crate.png", 100, 200 )
local crate2 = display.newImage( "crate.png", 180, 280 )
 
local crateMaterial = { density = 1.0, friction = 0.3, bounce = 0.2 }
 
physics.addBody( crate1, crateMaterial )
physics.addBody( crate2, crateMaterial )

사각형이 아닌 원형으로 addBody를 하고 싶으면 반지름 파라미터를 하나 더 넣으면 됩니다.

local ball = display.newImage( "ball.png", 100, 200 )
physics.addBody( ball, { density = 1.0, friction = 0.3, bounce = 0.2, radius = 25 } )

이러면 반지름 25인 원형으로 addBody가 적용 됩니다.
적용된 부분을 확인 하려면 physics.setDrawMode( "hybrid" ) 를 사용하시면 됩니다.

그러면 다각형은 어떻게 할까요?


local triangle = display.newImage("triangle.png")
triangle.x = 200
triangle.y = 150
triangleShape = { 0,-35, 37,30, -37,30 }
 
physics.addBody( triangle, { density=1.6, friction=0.5, bounce=0.2, shape=triangleShape } )
 
------
 
local pentagon = display.newImage("pentagon.png")
pentagon.x = 200
pentagon.y = 50
pentagonShape = { 0,-37, 37,-10, 23,34, -23,34, -37,-10 }
 
physics.addBody( pentagon, { density=3.0, friction=0.8, bounce=0.3, shape=pentagonShape } )

위 경우는 3각형이고 아래 경우는 5각형입니다.
순서대로 각 꼭지점의 x,y 좌표를 구하신 후 이것을 shape 파라미터에 넣어주시면 됩니다.

bodyType 은 3종류가 있습니다.
physics.addBody( triangle, "static", { density=1.6, friction=0.5, bounce=0.2, shape=triangleShape } )

위 경우는 static 으로 설정 돼 있습니다. 이 외에 dynamic 과 kinematic 이 있습니다.

다음 예제를 보시죠.

local car = display.newImage("big_red_car.png")
roofShape = { -20,-10, 20,-10, 20,10, -20,10 }
hoodShape = { 0,-35, 37,30, -37,30 }
trunkShape = { 0,-37, 37,-10, 23,34, -23,34, -37,-10 }
 
physics.addBody( car, "dynamic",
  { density=3.0, friction=0.5, bounce=0.2, shape=roofShape },
  { density=6.0, friction=0.6, bounce=0.4, shape=hoodShape },
  { density=4.0, friction=0.5, bounce=0.4, shape=trunkShape }
)

만약에 자동차에 addBody를 할 때 지붕, 후드, 몸체 부분을 별도의 파트로 나눠서 정교하게 나눠 addBody를 할 수 있습니다.

이런 경우 쉽게 복잡한 이미지의 좌표값들을 찾아주는 툴이 있는데요.
http://www.physicseditor.de
위 싸이트로 가시면 Physics Editor를 다운 받으실 수 있습니다.
사용법을 보시려면 아래 유튜브를 참조하세요.

http://youtu.be/xKeYg1ViQJE


이번 시간에는 addBody에 대한 공부와 physics Editor 소개를 해 드렸는데요.

다음 시간엔 직접 코딩 해 가면서 보여드리려고 합니다.
적당한 예제를 찾으려고 하는데... 복잡한 것만 있어서...
코로나 샘플 중에는 당구게임이 좋던데.. 좀 복잡하네요.

한번 찾아보고 당구게임이나 너무 복잡하면 직접 코딩하면서 하던가 할께요.

다음 시간에 뵈요.
반응형

Physics의 Collision Event

2011. 9. 27. 00:20 | Posted by 솔웅


반응형
오늘은 아래 이미지를 사용할 겁니다.
미리 다운 받으신 후 따라 하시면 편하실 거예요.

    


코로나의 충돌 이벤트 이름은 collisioin 이고 상태 (phases) 는 began 과 ended 가 있습니다.
이외에 collision과 관계된 이벤트는 preCollision 과 postCollision 이 있습니다.

이런 이벤트들을 사용하려면 먼저 리스너를 implement 해야 합니다.
코로나에서는 preCollision에 대해 글로벌하게 사용하지 말고 object  의 로컬 안에서 사용할 것을 권하고 있습니다.
퍼포먼스에 영향을 줄 수 있기 때문입니다.

또한 먼저 알아 두어야 할 것은 코로나는 physics 엔진으로 box2D를 사용하고 있습니다.
주의할 점은 collision 중에 physics objects를 modify,create, destroy 하지 말것을 권장합니다. 만약 collision 의 결과로 physics objects 를 수정,생성, destroy 하려면 timer.performWithDelay() 함수를 써서 시간 차를 두던가 collision handler 에 어떤 flag를 달라고 합니다.

첫번째 예제 소스를 보겠습니다.

local physics = require "physics"
physics.start()

local crate1 = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate1, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate1.myName = "first crate"
 
local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate2.myName = "second crate"
 
local function onCollision( event )
        if ( event.phase == "began" ) then
                print( "began: " .. event.object1.myName .. " & " .. event.object2.myName )
        elseif ( event.phase == "ended" ) then
                print( "ended: " .. event.object1.myName .. " & " .. event.object2.myName )
 
        end
end
 
Runtime:addEventListener( "collision", onCollision )

우선 로컬 physics 변수로 physics 엔진을 require 해서 담습니다. 그리고 physics.start()로 physics 엔진을 시작합니다.
그리고 crate1이란 변수에 crate.png 이미지를 넣고 좌표를 100,200 으로 합니다.
-> 이 때 디바이스 화면에 이 그림이 그려 집니다.
그리고 crate1에 physics.addBody를 합니다.
나중에 collision을 체크할 때 이 addBody한 변수들의 collision을 체크하게 됩니다.
density,friction,bounce 는 나중에 collision 이 될 때 여러 값들을 바꿔 넣으면서 테스트 해 보시면 좋을겁니다.
그리고 다음 줄은 crate1 의 myName 을  first crate라고 정합니다.
myName은 미리 지정된 키워드가 아니라 개발자가 명명하는 겁니다.
그러니 아무거나 하시면 되요.

다음에 onCollision 함수를 만들어서 began 일 때 ended 일 때 Terminal 에 문자가 찍히도록 만듭니다.
그리고 리스너를 답니다.
Runtime 은 앱이 시작한 이후 끝 날 때까지 유효한 것이구요. 그 이벤트는 collision 입니다.
이 외에 이벤트는 tap, touch 등등 이 있습니다.
앱이 실행되고 있을 때 collision 이벤트가 발생하면 (addBody 한 변수들끼리 부딪히면) onCollision 함수를 실행 하라는 뜻입니다.

이제 이것을 실행하면 crate1 과 crae2 가 위에서 떨어져서 바닥 저 밑으로 지나가서 화면에서 사라지게 됩니다.

이렇게 되는 이유를 살펴보면요.
1. 떨어지는 이유 : Gravity를 설정하지 않으면 지구 중력값인 9.8이 적용 됩니다. 위에 코드에는 없지만 Gravity 값이 9.8로 지정되 있습니다. 그러니까 중력이 작용해서 밑으로 떨어지게 됩니다.
2. 밑으로 사라지는 이유 : 현재 addBody는 crate1과 crate2만 적용 됐습니다. 이 두개가 부딪히는 일이 없으므로 아무런 충돌이 일어나지 않습니다. 그러니 onCollision 함수가 호출 되지 않습니다.

자 이제 코드를 아래와 같이 살짝 바꿀까요?

local physics = require "physics"
physics.start()

local crate1 = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate1, { density=3.0, friction=0.5, bounce=0.3 } )
crate1.myName = "first crate"
 
local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density=3.0, friction=0.5, bounce=0.3 } )
crate2.myName = "second crate"
 
local function onLocalCollision( self, event )
        if ( event.phase == "began" ) then
                print( self.myName .. ": collision began with " .. event.other.myName )
        elseif ( event.phase == "ended" ) then
                print( self.myName .. ": collision ended with " .. event.other.myName )
        end
end
 
crate1.collision = onLocalCollision
crate1:addEventListener( "collision", crate1 )
       
crate2.collision = onLocalCollision
crate2:addEventListener( "collision", crate2 )

위 코드는 리스너를 로컬로 선언한 겁니다.
앱이 실행되는 동안 리스너를 작동시키는 것이 아니라 (Runtime:addEventListener( "collision", onCollision ))
addBody한 object 들에 리스너를 다는 겁니다.
지금 까지 화면상으로 실행되는 것은 똑 같지만 메모리 관리적인 측면에서 보면 리스너를 로컬로 하는게 훨씬 낫겠죠?

자 그럼 addBody한 object들 끼리 충돌해서 onLocalCollision함수가 실행되고 Terminal 에 print() 함수 내에 있는 text들이 찍히도록 할까요?

그럼 이미지 하나만 더 불러와서 addBody를 하죠.

local ground = display.newImage( "ground.png" )
ground.x = display.contentWidth / 2
ground.y = 445
ground.myName = "ground"

physics.addBody( ground, "static", { friction=0.5, bounce=0.3 } )

위 코드를 physics.start() 밑에 넣으세요.
박스들이 땅에 부딪히면 멈추는 걸 할 거예요.
여기서 addBody에 다른 부분이 있는데 바로 "static"이 들어가 있는 겁니다.
이것은 중력의 영향을 받아서 움직이는게 아니라 고정돼 있게 하라는 뜻입니다.


이제 실행시키면 위의 화면 처럼 박스가 떨어지다가 땅에 부딪히고 몇번 살짝 바운스 하다가 설 겁니다. 터미널에는 print함수에 있는 내용이 찍히구요.

physics 엔진을 실감하기 위해 crate1을 살짝 옆으로 옮겨 볼까요?
local crate1 = display.newImage( "crate.png", 130, 200 )
이렇게 x좌표를 130으로 고치고 실행해 보세요.

박스가 내려오다 밑의 박스 옆쪽으로 부딪혀서 자연스럽게 돌다가 떨어질 거예요.
이 외에 setGravity 값이나, density, friction, bounce 값들을 많이 고쳐보면서 테스트 해 보세요. 많이 도움이 되실 겁니다.

지금까지 전체 소스는 아래와 같습니다.

local physics = require "physics"
physics.start()

local ground = display.newImage( "ground.png" )
ground.x = display.contentWidth / 2
ground.y = 445
ground.myName = "ground"

physics.addBody( ground, "static", { friction=0.5, bounce=0.3 } )

local crate1 = display.newImage( "crate.png", 130, 200 )
physics.addBody( crate1, { density=3.0, friction=0.5, bounce=0.3 } )
crate1.myName = "first crate"
crate1.setGravity = 2
 
local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density=3.0, friction=0.5, bounce=0.3 } )
crate2.myName = "second crate"
 
local function onLocalCollision( self, event )
        if ( event.phase == "began" ) then
                print( self.myName .. ": collision began with " .. event.other.myName )
        elseif ( event.phase == "ended" ) then
                print( self.myName .. ": collision ended with " .. event.other.myName )
        end
end
 
crate1.collision = onLocalCollision
crate1:addEventListener( "collision", crate1 )
       
crate2.collision = onLocalCollision
crate2:addEventListener( "collision", crate2 )

게임을 만들다 보면 적군하고 아군하고는 부딪히면 터지고 아군끼리나 적군끼리는 안 터지고 뭐 그렇게 해야 할 때도 있죠?

이렇게 어떨 때는 collision이 일어나고 어떨 때는 안 일어나야 할 때는
Collision categories, masking, and groups 를 사용합니다.

이 예제는 코로나 샘플 예제의 CollisionFilter 를 보시면 알 수 있습니다.


위 앱인데요. 풍선들이 화면 테두리에 맞으면 튕깁니다.
그리고 같은 색의 풍선끼리 맞아도 튕기구요.
대신 빨강하고 파랑 풍선은 서로 맞아도 튕기지 않습니다.



위 세개 이미지가 필요합니다.

코드는 아래와 같습니다.

**** physics를 require 하고 start 시킵니다.
local physics = require("physics")
physics.start()

***** 60 픽셀당 1미터로 지정합니다.
physics.setScale( 60 )

**** 아이폰의 statusBar를 없앱니다. 안드로이드에는 적용이 안 됩니다.
**** 그리고 안드로이드는 기본이 full screen 입니다.
display.setStatusBar( display.HiddenStatusBar )

**** 배경화면을 그려 줍니다.
local bkg = display.newImage( "stripes.png" )

**** 상하좌우 border 에 대한 categoryBits와 maskBits를 선언하고 여기에 friction과 bounce 값을 지정합니다.
borderCollisionFilter = { categoryBits = 1, maskBits = 6 } -- collides with (4 & 2) only
borderBodyElement = { friction=0.4, bounce=0.8, filter=borderCollisionFilter }

**** 상하좌우 경계선을 설정하고 static 으로 addBody 한 후 borderBodyElement를 적용합니다.
local borderTop = display.newRect( 0, 0, 320, 1 )
borderTop:setFillColor( 0, 0, 0, 0)        -- make invisible
physics.addBody( borderTop, "static", borderBodyElement )

local borderBottom = display.newRect( 0, 479, 320, 1 )
borderBottom:setFillColor( 0, 0, 0, 0)        -- make invisible
physics.addBody( borderBottom, "static", borderBodyElement )

local borderLeft = display.newRect( 0, 1, 1, 480 )
borderLeft:setFillColor( 0, 0, 0, 0)        -- make invisible
physics.addBody( borderLeft, "static", borderBodyElement )

local borderRight = display.newRect( 319, 1, 1, 480 )
borderRight:setFillColor( 0, 0, 0, 0)        -- make invisible
physics.addBody( borderRight, "static", borderBodyElement )

**** red와 blue라는 두개의 테이블(배열)을 선언합니다.
local red = {}
local blue = {}

** 두개의 categoryBits와 maskBits를 선언합니다.
local redCollisionFilter = { categoryBits = 2, maskBits = 3 } -- collides with (2 & 1) only
local blueCollisionFilter = { categoryBits = 4, maskBits = 5 } -- collides with (4 & 1) only

**** redBody와  blueBody를 선언하고 density,friction,bounce값을 지정합니다.
**** 두 object의 범위는 radius 43.0 입니다.
**** 이 둘을 각각 redCollisionFilter와 blueCollisionFilter 에 적용시킵니다.
local redBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=redCollisionFilter }
local blueBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=blueCollisionFilter }

**** 빨간 풍선과 파란 풍선을 4개 생성하는데 그 위치는 random하게 정합니다.
**** 여기에 addBody를 하고 redBody,blueBody를 적용해서 red,blue 테이블에 담습니다.
for i = 1,4 do
    red[i] = display.newImage( "red_balloon.png", (80*i)-60, 50 + math.random(20) )
    physics.addBody( red[i], redBody )
**** 부딪혀도 이미지가 돌지 않고 아래 위가 고정되게 합니다.
    red[i].isFixedRotation = true
   
    blue[i] = display.newImage( "blue_balloon.png", (80*i)-60, 250 + math.random(20) )
    physics.addBody( blue[i], blueBody )
    blue[i].isFixedRotation = true
end

이제 실행해 보세요.


다른 색의 풍선과 부딪히지는 않고 벽이나 같은 색의 풍선하고만 부딪힙니다.

categoryBits와 maskBits 은 바이너리 값입니다. 그런데 이것을 지금 10진수로 하고 있어서 많이 헛갈리네요.
코로나에서는 다음 버전에서는 바이너리 값이나 헥사코드값으로 할 예정이라고 합니다.

저도 값들을 이것 저것 고쳐가면서 해 봤는데 그래도 확실히 잘 이해를 못 하겠네요.
아래 샘플 테이블이 있습니다.


자세한 설명은 아래에 있습니다.
http://developer.anscamobile.com/forum/2010/10/25/collision-filters-helper-chart

혹시 이거 보시고 한국말로 자세히 설명해 주실 수 있으신 분 도움 부탁드립니다.

Collision Detection 엔 이것 외에 groupIndex와 propagation 이란 개념이 있습니다.

지금은 이 개념에 대해 자세히 살펴 볼 시간이 없네요.
관심 있으신 분은 아래 링크 참조하세요.

http://developer.anscamobile.com/content/game-edition-collision-detection

다음 시간은 Physics Bodies 에 대해 알아 보겠습니다.
지금까지는 단순히 object 에 addBody만 해 봤는데요.
좀 더 세밀한 것 까지 한번 살펴 보겠습니다.


반응형