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

최근에 받은 트랙백

글 보관함

Physics의 Collision Event

2011. 9. 26. 08: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만 해 봤는데요.
좀 더 세밀한 것 까지 한번 살펴 보겠습니다.


반응형

Comment