프로그래밍/Cocos2D2010. 7. 2. 17:54
MammothHunting은 우에서 좌로 움직이는 맘모스를 잡는 게임이다. 이제 타겟인 맘모스와 맘모스를 사냥하기 위한 총알 발사에 관해 살펴 보자.

타겟 추가
타겟은 화면 우측의 랜덤한 Y축을 기준으로 화면 좌측으로 이동한다. MammothHuntingScene.m에 다음의 addTarget 메소드를 init 메소드 이전에 추가한다.

-(void)addTarget {

CCSprite *target = [CCSprite spriteWithFile:@"target.png" rect:CGRectMake(007070)]; 

// 타겟(맘모스) Y 위치 결정.

CGSize winSize = [[CCDirector sharedDirectorwinSize];

int minY = target.contentSize.height/2;

int maxY = winSize.height - target.contentSize.height/2;

int rangeY = maxY - minY;

int actualY = (arc4random() % rangeY) + minY;

// 오른쪽  화면 밖에 X 위치와 위에서 결정한 Y축으로 타켓의 포지션 결정.

target.position = ccp(winSize.width + (target.contentSize.width/2), actualY);

[self addChild:target];

// 타겟의 속도 결정.

int minDuration = 2.0;

int maxDuration = 4.0;

int rangeDuration = maxDuration - minDuration;

int actualDuration = (arc4random() % rangeDuration) + minDuration;

// 액션 생성.

id actionMove = [CCMoveTo actionWithDuration:actualDuration position:ccp(-target.contentSize.width/2, actualY)];

id actionMoveDone = [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)];

[target runAction:[CCSequence actions:actionMove, actionMoveDone, nil]];

}


위 메소드 중 새로 등장하는 액션에 대해서 잠깐 정리하고 넘어가자. Cocos2D는 다양한 액션을 제공한다. 액션 클래스는 CocosNode 클래스를 상속한 클래스로 애니메이션 처리를 위한 클래이다. 대부분 스프라이트 클래스의 애내메이션 처리를 위해 사용된다.

액션 클래스를 이용한 애니메이션은 시간과 동작에 따라 나눌 수 있다. 우선 시간에 따라서는 다음과 같에 세 가지로 구분된다.

InstanceAction: 한 번만 실행된다.
IntervalAction: 주어진 시간 동안 같은 동작을 반복해서 실행한다.
RepeatAction: 일정한 시간 동안 일정한 패턴을 반복한다.

동작에 따른 구분은 다음과 같다.

Transformation Action: Move, Rotate, Scale, Jump 등.
Composable Action: Sequence, Spawn, Repeat, Revers.
Ease Action: Exp, Sin, Cubic 등.
Etc Action: CallFun, Orbit.

Transformation Action은 스프라이트의 이동, 회전 등과 같은 움직임의 일회성 처리나 효과 처리를 위한 액션이다. Composable Action은 Transformation Action 여러 개가 순차적으로 또든 동시에  애니메이션의 실행이 필요한 경우 사용한다. Ease Action은 애니메이션을 구현하는 속도와 관련된 액션들이다. Transformation Action과 Composable Action이 기본 속도 1로 동작한다면, Ease Action은 애니메이션을 점점 빠르게 하거나, 갑자기 빠르게 바꾸는 것과 같은 처리를 할 수 있다. 그 외에 콜백 메소드를 호출해서 실행하는 CallFunc나 궤도를 따라 움직이도록 할 수 있는 Orbit과 같은 액션들이 있다. CallFunc의 콜백 기능을 이용하면 애니메이션이 끝난 다음에 액션을 발생시키는 등의 처리를 쉽게 할 수 있다.

다음은 액션을 생성한 부분의 CCCallFuncN에 생성한 콜백 함수인 spriteMoveFinished 메소드를 addTarget 메소드 이전에 추가해야 한다.

-(void)spriteMoveFinished:(id)sender {

CCSprite *sprite = (CCSprite *)sender;

[self removeChild:sprite cleanup:YES];

}


spriteMoveFinished 메소드는 화면 밖으로 사라진 스프라이트를 제거하는데  사용된다. 메모리 릭과 관련하여 더 좋은 방법을 선택해야 하지만, 간단한 게임이므로 생략한다.

마지막으로, 실제 타겟을 추가하기 위해 다음 구문을 init 메소드 마지막(if 구문의...)에 추가하고, 실제 gameLogic 메소드를 addTarget 메소드 다음에 구현한다.

[self schedule:@selector(gameLogic:) interval:1.0];


-(void)gameLogic:(ccTime)dt {

[self addTarget];

}


다시 빌드앤런 하면 다음과 같이 맘모스가 우에서 좌를 이동하는 화면을 볼 수 있다.




총알 발사
실제 맘모스를 잡기위해서는 꽤 묵직한 총과 총알이 필요하겠지만, 간단한 예제 이므로, 맘모스는 케익을 던져 잡는 것으로 처리하기로 한다. 물론 케익은 고릴라가 던질 것이다.

케익을 던지는 방법은 여러 가지가 있겠지만, MammothHunting 게임에서는 화면을 터치하는 방식을 선택했다. 총알 발사 처리를 위해서는 간단한 수학 공식이 필요하다. 우선 다음 그림을 보자.


총알 발사 처리를 위해 CCMoveTo 액션을 사용할 것이다. CCMoveTo 액션의 API를 보면 액션 지정을 위해 시간과 거리가 필요하다. 플레이어(고릴라)가 발사(터치)한 총알(케익)까지의 거리를 계산하기 위해 먼저 한 가지 사실을 가정해야 한다. 발사된 총알은 터치한 지점을 지나 화면 밖까지 이동한다는 점이다. 이 내용을 기초로 그림을 그려 본 것이 위의 그림이다. 그림의 내용을 상기하면서 다음 단계로 넘어 가자. 작은 직삼각형과 같은 비율의 큰 직삼각형을 기억하라.

우선 터치로 총알을 발사하기 위해 다음 코드를 init 메소드(if 구문 상단에...)에 추가하자. 레이어에 터치 이벤트를 활성화 한 것이다.

self.isTouchEnabled = YES;


그리고 터치 이벤트를 처리하기 위한 콜백 메소드(ccTouchesEnded)를 다음과 같이 구현한다.

-(void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

// 처리를 위한 터치 선택.

UITouch *touch = [touches anyObject];

CGPoint location = [touch locationInView:[touch view]];

location = [[CCDirector sharedDirectorconvertToGL:location];

// 총알(케익) 초기 위치 설정.

CGSize winSize = [[CCDirector sharedDirectorwinSize];

CCSprite *projectile = [CCSprite spriteWithFile:@"projectile.png" rect:CGRectMake(004040)];

projectile.position = ccp(20, winSize.height/2);

// 총알 위치의 오프셋(offset) 측정.

int offX = location.x - projectile.position.x;

int offY = location.y - projectile.position.y;

// 총알 발사를 / 수직이나 뒤로  경우 탈출함(발사되지 않음).

if (offX <= 0return;

// 포지션 이중 체크.

[self addChild:projectile];

// 총알을 발사할 위치 측정.

int realX = winSize.width + (projectile.contentSize.width/2);

float ratio = (float) offY / (float) offX;

int realY = (realX * ratio) + projectile.position.y;

CGPoint realDest = ccp(realX, realY);

// Determine the length of how far we're shooting

// 발사한 총알의 길이(궤적) 얼마인지 측정.

int offRealX = realX - projectile.position.x;

int offRealY = realY - projectile.position.y;

float length = sqrtf((offRealX*offRealX)+(offRealY*offRealY));

float velocity = 480/1// 480pixels/1sec

float realMoveDuration = length/velocity;

// 총알을 종점(endpoint, 화면 ...)까지 이동 시킴.

[projectile runAction:[CCSequence actions:

   [CCMoveTo actionWithDuration:realMoveDuration position:realDest],

   [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)], 

   nil]];

}


ccTouchesEnded 메소드의 내용의 첫 번째 부분은 처리할 하나의 터치를 선택하여 현재 뷰에서 위치(location)을 확인한다. 그리고 현재 화면 모드가 landscape이므로 convertToGL 메소드로 좌표를 변환한다. 이 것은 이전 포스트에서 언급했던 것으로 주의해야 할 부분이다. 왜냐하면, UIKit(Quartz)의 좌표계 Top-Left(좌상단)가 원점이 되는 좌표계를 쓰지만, Cocos2d는 OpenGL 기반이라 좌하단이 원점이 되는 좌표계를 사용하기 때문이다. 그래서 위치를 계산하기 전에 convertToGL 메소드로 좌표들을 꼭 변환해야 한다.

다음은 총알(케익) 스프라이트를 초기화 한다. 그리고 위에서 언급했던 수학으로 이동 시킬 총알의 위치를 계산해야 한다. 여기서 필요한 것이 바로 피타고라스의 정리이다.

임의의 직각삼각형에서 빗변을 한 변으로 하는 정사각형의 넓이는 다른 두 변을 각각 한 변으로 하는 정사각형의 넓이의 합과 같다.

물론 이 알고리즘은 총알을 발사하는 방법 중 가장 간단한 것의 하나일 뿐이다. 본 예제는 아주 단순한 경우이므로 이정도로 만족하고 넘어간다.

여기에서 중요한 또 한 가지의 내용은, 총알이 발사되는 시간과 속도의 관계를 이해하는 것이다. 학창 시절 배운 물리 법칙과 몇 가지 사항을 기억해 내야 한다.
  • 거리(distance)는 스칼라(scalar) 이고 변위(displacement)는 벡터(vector) 이다.
  • 벡터는 화살표를 사용해 나타낼 수 있다.
  • 거리 = 속도 * 시간
게임 개발을 하려면 기본적으로 수학과 물리학이 필수이다. 그렇다고 다시 교과서를 꺼내 볼 수도 없고... 이 문제를 해결하기 위해 다음 두 권의 책을 추천한다. 한 권은 마침 번역이 되어 있다.

어쨌든 알고리즘 같지 않은 알고리즘을 적용한 것이 액션을 추가하기 전까지의 소스 내용이다. 마지막으로 총알에 액션을 추가하고, 빌드앤런 하자. 이제 화면을 터치하면 총알이 발사될 것이다.


다음 포스트에서는 게임 개발 시 가장 중요한 부분인 충돌 검사에 대해 다룰 것이다. 두 번째 포스트를 마무리하기 전에 [MammothHunting] - Cocos2D를 사용한 아이폰 게임 개발 튜토리얼 1 에서 언급했던 Director, Scene, Layer, Sprite에 대해 정리해 보자.

Cocos2D의 모든 클래스는 CocosNode를 상속한다. CocosNode 클래스는 위치, 색상, 투명도, 크기, 회전, 가시성 여부, 카메라, 그리드 속성을 가지고 있으며 이들 속성을 필요에 따라 변경이 가능하다. CocosNode의 또 다른 특징은 자식 노드를 가질 수 있다는 점이다. 이는 객체들의 계층 구조를 만들 수 있음을 의미한다. 계층 구조가 가능함으로써 부모의 속성이 변할 때 자식의 속성까지 쉽게 변경할 수 있다.

Cocos2D에서 게임 화면을 구성하기 위해 제공되는 네 개의 클래스가 바로 CocosNode를 상속한 Director, Scene, Layer, Sprite 이다.

Director
Director는 싱글톤 클래스이다. Cocos2D는 게임의 화면을 Scene이라는 장면 단위로 구성하여 처리할 수 있는 기능을 제공한다. Director는 바로 이들 장면을 관리한다.

Scene
Scene은 게임에서 하나의 장면을 표현한다. 영화의 장면과 유사하다. 게임 내에서 장면 변경이 필요한 경우, 생성하여 각 장면마다 장면의 Scene 클래스로 처리한다.

Layer
Layer는 Sprite와 함께 Scene을 구성한다. Scene과 Layer의 가장 큰 차이점은, Scene은 사용자의 처치 이벤트를 받아서 처리할 수 없지만 Layer는 TouchEventsDelegate 프로토콜로 사용자의 처치 이벤트를 처리할 수 있다는 점이다. 다음 그림을 보면 이해하기 쉬울 것이다.


Sprite 
Director, Scene, Layer가 추상적이라면, Sprite는 실제로 화면에 출력되는 구체적 개념이다. Sprite는 컴퓨터 그래픽스에서 화면을 구성하는 정적인 이미지나 애니메이션이 되는 일련의 이미지를 가리킨다. 보통 게임에 사용되는 이미지라고 이해하면 된다. 일반적인 이미지와 구별되는 가장 큰 특징 중 하나는 투명색을 가진다는 점이다. 
실제 게임 개발 시 개발의 대부분의 시간이 Sprite를 생성하고 출력한 다음 사용자 입력이나, 기타 이벤트가 발생할 때마다 이들 이벤트에 대응하는 처리를 하는데 소요된다.

---

Posted by windship