티스토리 뷰

어떤 게임이던지 훌륭한 이펙트는 게임의 재미요소 중 하나로 작용합니다.
(그렇다고 위의 이펙트가 멋지고 훌륭한 이펙트라는 것은 아닙니다. 제가 처음으로 친구와 게임 만들면서 손에 쥐게 된 이펙트니까요.)
이런 이펙트들은 파티클을 파티클 엔진을 통해서 만들어낼 수도 있지만
역시 2D 게임에서 가장 많이 사용하는 방법은 위의 사진처럼 여러 장의 사진을 빠르게 교체하여 보여주는 방식입니다.
이것을 스프라이트 애니메이션(Sprite Animation)이라고 합니다.

Corona SDK에서도 마찬가지로 스프라이트 애니메이션을 지원하기 위한 API들이 있습니다.
Sample Project들을 보면 알 수 있는데요. (Horse Animation, Jungle Scene)
여기서 나오는 애니메이션들은 모두다 마찬가지로 '계속해서 반복하여 재생'됩니다.

공격이펙트는 계속해서 반복되는 효과가 아닙니다.
한번 재생되고 나면 화면에서 사라져야하지요.
따라서 기존의 소스를 조금 수정하고 제 상황에 적합한 소스를 짜기 위해 고민을 좀 했습니다.
사실 C++이나 Java로 제가 만든 프레임워크에서 직접 구현을 한다면 그다지 어려운 문제는 아닙니다만
Corona는 자신들의 환경 내부에서 뛰놀게 만들어져 있어서 원하는 내용을 찾는데 있어 조금 곤란함이 있었습니다.
(미약한 검색 능력과 언어적 장벽 때문에 힘들었던 것도 있겠지요.) 

제 게임에서는 터치를 했을 때 몬스터가 있는 곳이라면 공격 이펙트를 보여줍니다. 그리고 앞에서 말한 것 처럼 한 번 재생하고나면 사라져야합니다.
따라서 어떤 곳에 재생을 할 지 정할 수 있어야 하며,  재생이 끝났을 때 스스로를 지워줘야 합니다.
이것을 직접 소스로 구현해봅시다.

1. 터치를 감지하는 리스너 생성
터치를 했을 때 마다 몬스터가 있는지 체크를 하여 이펙트 재생 여부를 결정해줘야합니다.

local hitCheck = function( event )

if event.phase == "began" then 

local x, y = event.x, event.y

for i = gGroup.numChildren, 1, -1 do

local object = gGroup[i]

if object.id == "trash" then

--trash touch check

local bounds = object.contentBounds

local isWithinBounds = 

bounds.xMin <= x and bounds.xMax >= x and bounds.yMin <= y and bounds.yMax >= y

if isWithinBounds == true then 

object:damaged( baseDamage )

hitEffect( x, y )

end

end

end

end

end


Runtime:addEventListener( "touch", hitCheck ) 


Runtime에 addEventListener로 touch일 경우 hitCheck 함수를 호출하도록 설정하였습니다.
처음에 x, y 변수에 터치한 곳의 위치를 담았구요.
그리고 gGroup(Scene의 뷰 그룹)에 담겨있는 객체들을 순서대로 체크하면서 '쓰레기(몬스터)'인지, '터치한 곳이 쓰레기 위인지' 확인합니다.
여기서 가장 중요한 점은 for문을 gGroup의 뒤에서부터 확인한다는 점입니다. 
그 이유는 이전에 포스팅했었던 Corona로 프로그래밍을 하면서 저지르기 쉬운 실수 중에서 나왔었는데요.
간단히 말해 저 for문을 돌면서 gGroup의 자식 수가 바뀌었을 때 문제가 발생할 수 있기 때문입니다.
isWithinBounds는 Corona의 ui.lua 파일에서 영감을 얻었습니다.
잘 보면 x, y의 값과 object의 범위를 확인하여 그 범위 안에 있다면 true가 됩니다.
isWithinBounds가 true이면 터치를 한 곳이 쓰레기 위라면 쓰레기에게 데미지를 주고 hitEffect(이펙트를 재생하는 함수)를 호출합니다. 

2. 실제 애니메이션 구동 부분
이젠 이펙트를 재생하는 함수 부분입니다. 위에서 터치한 곳의 x, y 좌표를 전송해주었으니
그 위치에 애니메이션을 구동하기만 하면 끝입니다.

local sheet1 = sprite.newSpriteSheet( "Image/game/hit.png", 60, 60 )


local spriteSet1 = sprite.newSpriteSet(sheet1, 1, 5)

sprite.add( spriteSet1, "hit", 1, 5, 100, 1 )


local function hitEffect( x, y )


local sprInst = sprite.newSprite( spriteSet1 )

sprInst.x = x

sprInst.y = y

sprInst:addEventListener( "sprite", function(event)

if event.phase == "end" then

event.target:removeSelf()

event.target = nil

end

  end )


sprInst:prepare("hit")

sprInst:play()


end


먼저 공격이펙트 그림을 SpriteSheet로 생성합니다.
newSpriteSheet의 두번째 파라미터는 프레임의 가로 크기, 세번째 파라미터는 프레임의 세로 크기 입니다.
그리고 만들어진 SpriteSheet로 SpriteSet을 생성합니다.
newSpriteSet의 두번째 파라미터는 시작 프레임 인덱스, 세번째 파라미터는 마지막 프레임 인덱스입니다.
이것을 함수 밖으로 꺼낸 이유는 매 함수 호출 마다 SpriteSheet와 SpriteSet을 생성한다면 불필요하게 자원을 낭비할 수 있기 때문에 함수 밖으로 꺼낸 것입니다.
마지막으로 실제 화면에 표시될 애니메이션이 될 Sprite Instance를 생성합니다.
만든 SpriteSet을 이용하여 인스턴스를 만들고 표시될 위치를 정합니다.
그리고 가장 중요한 '한 번만 재생되고 화면에서 사라지게'하기 위해 리스너를 등록합니다.
sprite 이벤트가 생성될 때 마다 이 리스너를 실행하는데 두번째 파라미터로 들어간 함수는 밖으로 꺼내서 따로 구현하여도 상관없습니다.
(저는 코드 미관상 저렇게 만들었습니다.)
리스너 내부에서는 현재 이벤트를 체크하고 한 애니메이션이 다 끝난 이벤트를 확인하면 자기 자신을 삭제합니다.
마지막으로 재생을 위해 prepare로 애니메이션을 준비시키고 paly로 재생을 합니다.


언듯 생각하기에는 간단한 내용이지만 실제로 구현하려니 은근히 장애물이 많네요.
이렇게 공격 이펙트 덕분에 더 멋진 게임이 만들어 질 수 있을겁니다.
저 hitEffect에서 공격 이펙트를 재생한 다음 공격했을 때의 사운드를 출력한다면 더욱 좋겠죠?
다음엔 사운드 재생 부분을 포스팅하도록 하겠습니다.

그럼 오늘도 즐프 :) 
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday