프로그래밍/Cocos2D2012. 4. 8. 19:49

프로젝트 빌드시에 


CLScoreServerPost.m

 Deprecations

 'uniqueIdentifier' is deprecated


CLScoreServerRequest.m

 Deprecations

 'uniqueIdentifier' is deprecated


의 2개 Warning이 발생할 경우, 프로젝트 트리에서 루트 옆의 M자 아이콘을 클릭한다. 

옆에 나오는 프로젝트 세팅에서, Info 옆의 Build Settings를 클릭하고, 아래의 Deploment > iOS Deployment Target을 iOS 4.x대로 변경해 낮추면 위 Warning이 발생하지 않는다.

이것은 딱히 하지 않아도 Warning이므로 프로젝트 실행에 지장은 없다. 

Posted by windship
프로그래밍/Cocos2D2012. 2. 9. 02:13

plist 프레임 키값을 알아서 가져오는 방법입니다.
 TexturePacker를 사용하여 생성된 plist 프레임 키값을 가져올 때 사용하였습니다. 
        

 NSMutableArray *animFrames = [NSMutableArray array];
        
 NSString *framePath = [[NSBundle mainBundle] pathForResource:@"1st_bad_animation" ofType:@"plist"];
 NSDictionary *dictionary = [[NSDictionary alloc] initWithContentsOfFile:framePath];
 NSDictionary *framesDict = [dictionary objectForKey:@"frames"];

 // Dictionary는 순서가 뒤섞여 있어서 정렬하기 위해 Array에 새로 담습니다.
 NSMutableArray *sortFrames = [[NSMutableArray alloc] initWithCapacity:[framesDict count]];
        
 for (NSString *frameDictKey in framesDict) 
 {
       [sortFrames addObject:frameDictKey];
 }
        
 [sortFrames sortUsingSelector:@selector(compare:)]; // 정렬 
  // compare 는 기본으로 제공되는 함수이고 커스텀이 가능합니다.
        
  for(NSString *frameDictKey in sortFrames) 
  {
       CCSpriteFrame *frame = [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:frameDictKey];
       [animFrames addObject:frame];
  }
Posted by windship
프로그래밍/Cocos2D2012. 2. 8. 01:15
1. CCActionGrid.m 파일의
    
"Incompatible pointer types assigning "CCActionInterval*" from "CCAction*" 에러

other = [action retain]; 부분을
other = (CCActionInterval*) [action retain]; 로 변경  
  



2. ZAttributedString.m 파일의 에러

ZAttributeRun *newRun = [[ZAttributeRun allocinitWithIndex:NSMaxRange(range) attributes:[[_attributes lastObject]attributes]]; 를
ZAttributeRun *newRun = [[ZAttributeRun allocinitWithIndex:NSMaxRange(range) attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes lastObjectattributes]]; 로 변경

ZAttributeRun *newRun = [[ZAttributeRun alloc] initWithIndex:NSMaxRange(range) attributes:[[_attributes objectAtIndex:firstAfter-1] attributes]]; 를

ZAttributeRun *newRun = [[ZAttributeRun allocinitWithIndex:NSMaxRange(range) attributes:(NSDictionary*)[(ZAttributeRun *)[_attributes objectAtIndex:firstAfter-1attributes]]; 로 변경   


3.  CCDirevctorIOS.m 파일의 에러

UIDeviceOrientationPortraitUpsideDown 부분을
UIInterfaceOrientationPortraitUpsideDown 로 변경  

Posted by windship
프로그래밍/Cocos2D2012. 2. 6. 10:52
//
//  HelloWorldLayer.m
//  FontStrokeDemo
//
//  Created by user on 12-2-1.
//  Copyright __MyCompanyName__ 2012亮�. All rights reserved.
//


// Import the interfaces
#import "HelloWorldLayer.h"

// HelloWorldLayer implementation
@implementation HelloWorldLayer

+(CCScene *) scene
{
	// 'scene' is an autorelease object.
	CCScene *scene = [CCScene node];
	
	// 'layer' is an autorelease object.
	HelloWorldLayer *layer = [HelloWorldLayer node];
	
	// add layer as a child to scene
	[scene addChild: layer];
	
	// return the scene
	return scene;
}

-(CCRenderTexture*) createStroke:(CCLabelTTF*) label   
                            size:(float)size   
                           color:(ccColor3B)cor
{
	CCRenderTexture* rt = [CCRenderTexture renderTextureWithWidth:label.texture.contentSize.width+size*2  height:label.texture.contentSize.height+size*2];
	CGPoint originalPos = [label position];
	ccColor3B originalColor = [label color];
	BOOL originalVisibility = [label visible];
	[label setColor:cor];
	[label setVisible:YES];
	ccBlendFunc originalBlend = [label blendFunc];
	[label setBlendFunc:(ccBlendFunc) { GL_SRC_ALPHA, GL_ONE }];
	CGPoint meio = ccp(label.texture.contentSize.width/2+size, label.texture.contentSize.height/2+size);
	[rt begin];
	for (int i=0; i<360; i+=30) // you should optimize that for your needs
	{
		[label setPosition:ccp(meio.x + sin(CC_DEGREES_TO_RADIANS(i))*size, meio.y + cos(CC_DEGREES_TO_RADIANS(i))*size)];
		[label visit];
	}
	[rt end];
	[label setPosition:originalPos];
	[label setColor:originalColor];
	[label setBlendFunc:originalBlend];
	[label setVisible:originalVisibility];
	[rt setPosition:originalPos];
	return rt;
}

// on "init" you need to initialize your instance
-(id) init
{
	// always call "super" init
	// Apple recommends to re-assign "self" with the "super" return value
	if( (self=[super init])) {
		
        //label1
		CCLabelTTF* label = [CCLabelTTF labelWithString:@"FontStrokeDemo"
                                             dimensions:CGSizeMake(305,179) 
                                              alignment:UITextAlignmentLeft
                                               fontName:@"Arial" 
                                               fontSize:38];
        [label setPosition:ccp(240, 160)];
        [label setColor:ccWHITE];
        CCRenderTexture* stroke = [self createStroke:label  
                                                size:3  
                                               color:ccBLUE];
        [self addChild:stroke];
        [self addChild:label];
        
        //label2
		CCLabelTTF* label2 = [CCLabelTTF labelWithString:@"Test String"
                                             dimensions:CGSizeMake(305,179) 
                                              alignment:UITextAlignmentLeft
                                               fontName:@"Arial" 
                                               fontSize:38];
        [label2 setPosition:ccp(240, 100)];
        [label2 setColor:ccWHITE];
        CCRenderTexture* stroke2 = [self createStroke:label2  
                                                size:3  
                                               color:ccRED];
        [self addChild:stroke2];
        [self addChild:label2];
	}
	return self;
}

// on "dealloc" you need to release all your retained objects
- (void) dealloc
{
	// in case you have something to dealloc, do it in this method
	// in this particular example nothing needs to be released.
	// cocos2d will automatically release all the children (Label)
	
	// don't forget to call "super dealloc"
	[super dealloc];
}
@end
Posted by windship
프로그래밍/Cocos2D2012. 2. 5. 15:06
cocos2D에서 레티나, 3GS 2가지 해상도를 지원하기 위해서는

1. retina 사용여부 부분을 주석해제 해야합니다. (appDelegate 안에 주석처리 되어 있음)

// Enables High Res mode(Retina Display) on iPhone 4 and maintains low res on all other devices 
if (! [director enableRetinaDisplay:YES])
 CCLOG(@"Retina Display Not Supported");

2. 이미지는 2가지로 만들어서 넣으면 됩니다.

aaa.png(3GS), aaa-hd.png(레티나)

* @2x는 cocos2d에서는 필요 없습니다. -hd로 대체됩니다. 단 default.png는 default@2x.png로 만들어야 합니다. 
Posted by windship
프로그래밍/Cocos2D2012. 2. 5. 14:16
아이폰 용으로 레티나 디스플래이를 지원할 경우
파일명 뒤에 -hd 를 붙여 사용한다.

이때 아이패드용으로 개발 시 이 레티나용 소스를 사용해서 표현하고 싶다면
ccFileUtils.m  파일을 찾아가

+(NSString*) getDoubleResolutionImage:(NSString*)path

이 함수안에 

if( CC_CONTENT_SCALE_FACTOR() == 2)

위의 부분을 아래와 같이 변경해 주자

if( CC_CONTENT_SCALE_FACTOR() == 2 || (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad))
이는 레티나 디스플래이용으로 설정되었을 때 -hd 파일을 쓴다는 부분인데
뒤에 부가적으로 아이폰 일 경우에도 사용하겠다고 선언해주는 것이다.

UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad
이 부분은 현재 인터페이스가 아이패드인지 확인 하는 부분


추가적으로 이렇게 불러오는 리소스와 더불어
포인트도 아이폰용 포인트를 그대로 사용하고 싶다면 아래와 같이 인라인 함수를 하나 만들어 사용하면 편하다.

/** Returns ipad point.
 @return CGPoint
 */
static inline CGPoint
CGPointMakeBothIPad(float iPhonePoint_X, float iPhonePoint_Y) {
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        return CGPointMake(iPhonePoint_X/320*768,iPhonePoint_Y/480*1024);
    else
        return CGPointMake(iPhonePoint_X,iPhonePoint_Y);
}
Posted by windship
프로그래밍/Cocos2D2012. 1. 29. 00:33
* 라이센스 관련

1. 나의 앱은 소스 코드를 공개하지 않으려고 한다. 그래도 Cocos2D를 사용할 수 있는가?
-> 물론이다. Cocos2D는 MIT 라이센스로 관리되고 있다. 궁금한 점이 있다면 라이센스 관련 항목을 읽어보라.

2. 만약 누가 나의 앱을 구입했을 경우 나는 그에게 소스 코드도 제공해야 하는가?
-> 당신의 게임을 구입했다 하더라도 그 게임의 소스 코드를 제공해야 할 의무는 없다.


* 개발 관련

1. Cocos2D에서 3D 오브젝트를 사용할 수 있는가?
-> 아무 문제 없이 2D 게임에도 3D 오브젝트를 넣어서 그래픽 퀄리티를 높일 수 있다. Cocos2D의 기본 투영 행렬 구조는 3D와 같으므로 아마 변경할 필요도 없을 것이다.

2. 버그를 발견하거나 제안할 사항이 있다. 어떻게 하면 되는가?
-> 이슈 추적기를 사용하라. http://code.google.com/p/cocos2d-iphone/issues/list
또한 포럼에 있는 버그/제안사항 쪽에 글을 써도 된다. 하지만 이슈 추적기를 사용하는 것을 권장한다.

3. 엔진의 개선에 참여하고 싶다면?
-> 당신이 코드에 기여하고 싶다면 다음 단계를 따라주기 바란다.
(만약 당신이 새로운 git 또는 GitHub를 가지고 있다면 Pro Git를 읽어야 한다. 특히 Contributing to a project: Small/Large Public Project 부분을 읽어주시길) (*역주 : GIT는 분산 소스코드 관리 프로그램임)

4. 아이폰용 SDK 3.x와 4.x를 위한 게임을 개발할 수 있는가?
-> 물론이다. Cocos2D는 SDK 2.x, 3.x, 4.x를 지원한다.

5. Cocos2D를 아이폰/아이패드/맥에서 사용할 수 있는가?
-> 그렇다. Cocos2D는 모든 애플 디바이스를 잘 지원한다.
- 아이폰/아이팟 터치 1세대
- 아이폰 3G/아이팟 터치 2세대 
- 아이폰 3Gs/아이팟 터치 3세대
- 아이폰 4/아이팟터치 4세대(고해상도 레티나 모드 선택 지원 가능)
- 아이패드
- Mac OSX

6. 텍스처 사이즈는 어떻게 해야 하는가?
-> 두 가지 고려해야 할 사항이 있다. 비압축 텍스처와 PVRTC 텍스처이다.

비압축 텍스처를 위해 텍스처는 3세대 디바이스에서는 2048 이하의 2제곱수를 가진 2차원 텍스처여야 하고, 그 이전 디바이스에서는 1024 이하여야 한다. 따라서 16x256은 OK. 512x512도 OK. 그러나 240x320은 2의 제곱수가 아니므로 안된다.
이 규칙에 맞지 않는 이미지도 사용은 가능하지만, 텍스처로 변환되어 저장될 때 2의 배수로 자동 저장된다는 점을 기억하라. 240x320의 이미지는 텍스처로 변환될 때 256x512로 바뀔 것이다. 
아이폰 3Gs와 같은 새로운 플랫폼에서는 최대 텍스처 사이즈가 2048x2048까지 가능하다. 아이폰 3G 이하라면 1024x1024까지만 가능하다.

PVRTC 압축 텍스처는 2의 제곱수 크기로 정사각형이어야 한다. 예를 들면 4x4, 32x32, 256x256, 1024x1024와 같은 크기여야 한다. 이 포맷으로 로드된 이미지는 GPU상에서 돌아가는 포맷과 밀접하게 매칭되는 특별한 포맷이다. 따라서 2의 제곱수 크기로 정사각형이어야만 한다. 만약 PVRTC 압축 텍스처를 사용했는데 흰 그림만이 나올 경우 이러한 크기와 정사각형이 아닐 확률이 높다.

7. 스프라이트 시트는 무엇이며 왜 사용해야 하는가?
-> 스프라이트 시트는 큰 스프라이트 상에 정렬된 스프라이트들의 모음이다. 이것을 사용하면 두 가지 장점이 있는데, 첫째로 시트 상의 모든 스프라이트가 한 번의 드로우 콜로 그려질 수 있다는 것이다. 이것은 매우 효율적이다. 두번째로는 메모리의 낭비 없이 2의 제곱수 크기가 아닌 텍스처를 사용할 수 있다는 것이다. 스프라이트 시트 상에 스프라이트를 묶는 가장 좋은 실습은 스프라이트 섹션(http://www.cocos2d-iphone.org/wiki/doku.php/prog_guide:sprites)을 참고하면 더욱 자세한 정보를 얻을 수 있다.

8. 텍스처 상에 렌더링하기(렌더 투 텍스처) 기술을 사용할 수 있는가?
-> 물론이다. 프레임버퍼 오브젝트를 사용할 수 있다. 이것은 Cocos2D에서 이펙트 구현을 위해 사용되고 있다. Grabber.m 또는 RenderTexture.m 클래스를 참고하라.

9. 라이트나 연기 같은 동적 알파 효과를 사용할 수 있는가?
-> 물론이다. 프레임버퍼 오브젝트와 채널 마스킹 조합으로 오직 알파 채널 상에서 적절한 블렌딩 모드를 사용해 그릴 수 있다. 연기 효과를 위한 iSteam(다른 이미지보다 상위에 그려지는 편집 가능한 연기 레이어), 번개, 스크래치 복원 이펙트와 변경 가능한 플레이 필드. 이러한 렌더 텍스처 클래스를 보고 다음의 스레드(http://groups.google.com/group/cocos2d-iphone-discuss/brouwse_frm/thread/dab9b84cd5262d80/ed15695bb0cb3af5?lnk=gst&q=scratch+off#ed15695bb0cb3af5)를 참고하라. 

10. 내 프로젝트에 사운드를 추가하는 방법은?
-> Cocos2D는 여러 가지의 오디오 프레임워크를 가지고 있다. 당신에게 필요한 것을 잘 골라서 사용하기 바란다. 

SimpleAudioEngine 인터페이스는 Cocos Denshion 오디오 라이브러리의 일부로서, 대부분의 게임 어플리케이션에 사용하기에는 충분할 것이다

3D의 공간감이 느껴지는 사운드가 필요하다면 PASoundMgr 인터페이스를 사용하면 된다. PASoundMgr은 Cocos2D의 expermental/sound-engine 디렉토리에 포함되어 있다.

더 많은 고급 오디오 테크닉이 필요하거나 저수준의 OS 오디오 API에 접근하고 싶다면 CocosDenshion 오디오 라이브러리의 CDAudioManager 인터페이스가 좋은 선택이 될 것이다.

매우 간단한 오디오 기능이 필요하다면 OS 오디오 API를 직접 사용하는 것으로 충분할 수도 있다. 예를 들어 만약 사운드 하나만 플레이하고 싶다면 AVAudioPlayer API로 간단하게 SimpleAudioEngine과 CDAudioManager 인터페이스 내에서 배경음악으로 활용할 수 있을 것이다.


* 문제 해결

1. 이미지를 띄웠는데, 시뮬레이터에서 색상이 엉망으로 보인다.
-> 혹시 PowerPC 기반 맥을 사용하고 있지 않는가? 맞다면 이미 보고된 문제이다. PowerPC는 아이폰 개발에 적합한 플랫폼이 아니다.

2.  테스트는 어떻게 해볼 수 있는가?
-> 1. Cocos2D XCode 프로젝트를 연다.
    2. 다음 순서로 클릭한다. XCode > View > Customize toolbar 
    3. Active Target, Active Executable을 드래그해서 당신의 탑 프로젝트 툴바로 옮긴다.
    4. 테스트하고 싶은 뷰를 선택하고 드롭다운 메뉴에서 Build and Go를 선택한다.

3. PNG 이미지가 포토샵에서 보이는 것처럼 보이지 않는다. 어째서인가? 
-> 만약 PNG 이미지가 포토샵에서처럼 깨끗하게 나오지 않는다면 우선은 32비트 픽셀 포맷을 사용하도록 하자.

 [[CCDirector sharedDirector] setPixelFormat:kRGBA8]; // 기본은 RGB565
 [[CCTexture2D setDefaultAlphaPixelFormat:KCCTexture2DPixelFormat_RGBA8888]; // 기본은 RGBA8888

그래도 여전히 문제가 생긴다면 다음 사항을 체크하자.

 * 디바이스 상에서 이미지가 생성될 때 알파 채널이 미리 적용되었는지 여부
 * 미리 적용된 알파 이미지를 정확하게 렌더링하기 위해, Cocos2D는 이 블렌딩함수를 사용한다.

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

그러나 당신이 만약 다른 블렌딩함수를 사용하고 싶다면 다음과 같이 해야 한다.

// Cocos2D v0.7x는 모든 스프라이트에 GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA를 블렌딩 함수로서 사용한다.
// 이 방법은 정확하지 않다. 그러나 만약 당신이 0.7x의 방식을 에뮬레이트하고 싶다면

[sprite setBlendFunc: (ccBlendFunc) {GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}];

블렌딩 함수에 대한 더 많은 정보와 미리 곱해진 알파 이미지에 대해서 알고 싶다면 다음을 읽어보기 바란다.

Premultiplied images in Cocos2D (http://www.cocos2d-iphone.org/wiki/doku.php/release_notes0_8#premultiplied_images)

그리고, 이미지가 혹시 하얗게 나올 경우, 이미지가 미리 곱해진 알파를 가지고 있다면 텍스처가 변환될 때 RGB 컴포넌트가 수정될 것이다. 하지만 만약 이것을 오버라이드하고 싶다면 다음과 같이 하라.

[sprite setOpacityModifyRGB:NO];
 
Posted by windship
프로그래밍/Cocos2D2010. 7. 28. 21:39

Ninjas Going Pew-Pew!

닌자의 공격! 퓻퓻!

 Cocos2D는 아이폰 게임을 만들 때 많은 시간을 절약할 수 있게 해 주는 파워풀한 라이브러리이다. 스프라이트 지원, 멋진 그래픽펙트, 애니메이션, 물리 충돌, 사운드 엔진, 기타 등등 다양한 기능들을 지원한다.

 필자도 Cocos2D를 배우기 시작한 지 얼마 되지 않았기 때문에, 쓸만한 튜터리얼을 찾기 위해 많이 헤매고 다녔다. 하지만 정말 내가 원하는 그런 튜터리얼 - 간단하지만 애니메이션, 충돌, 오디오 등의 기능에 대한 명료한 설명이 있는 - 을 찾기가 무척 어려웠다. 마침내 스스로 간단한 게임을 만들어냈을 때, 나는 내 경험을 바탕으로 하여 다른 초보자들에게 도움이 될 만한 튜터리얼 문서를 쓰기로 마음먹었다.

 이 튜터리얼에서는 Cocos2D로 간단한 아이폰 게임을 만드는 과정을 처음부터 끝까지 훑어볼 것이다. 여러분은 튜터리얼을 그대로 따라올 수도 있고, 아니면 글 마지막에 있는 샘플 프로젝트를 바로 시험해봐도 된다. 보시다시피, 닌자 게임이 될 것이다.


Cocos2D 다운로드와 설치


 구글의 Cocos2D 코드 페이지에서 Cocos2D를 다운받을 수 있다. 이 글이 쓰여질 당시의 최신 버전은 0.99.9-final이다. 이 튜터리얼에서도 이 버전을 사용할 것이다.

 Cocos2D를 다운받았으면 템플릿을 설치해야 한다. 터미널 창을 열고 Cocos2D를 다운로드한 디렉토리로 간 뒤, 다음 명령을 입력한다.


./install_template.sh


 만약 여러분이 맥에 SDK 버전을 여러 개 설치했다든가 해서, XCode가 기본 위치가 아닌 다른 곳에 설치되었다면 명령 뒤쪽에 뭔가 파라미터를 더 붙여줘야 할 수도 있다. 


Hello, Cocos2D!


 그러면 간단한 Hello World 프로젝트로 방금 설치한 Cocos2D 템플릿을 실행해 보자. XCode를 실행하고 cocos2d-0.99.0 Application 템플릿을 골라, 새 Cocos2D 프로젝트를 만든다. 프로젝트 이름은 “Cocos2DSimpleGame” 으로 하겠다.


Cocos2D Templates


 프로젝트가 열렸으면 Build and run 을 눌러 빌드하고 실행시키자. 모두 잘 되었다면 다음처럼 시뮬레이터에 실행되는 화면이 나타날 것이다.


HelloWorld Screenshot


 Cocos2D는 게임의 레벨이나 스크린 등을 "씬"(장면)이라는 개념으로 만들어놓고 있다. 예를 들어, 하나의 게임은 크게 보아 첫 메뉴가 나오는 부분, 게임의 주된 플레이가 이루어지는 부분, 그리고 게임오버 되었을 때의 부분 등으로 나눌 수 있을 것이다. 실제 Cocos2D의 씬에서는, 여러분은 여러 개의 레이어(포토샵의 그 레이어와 비슷하다고 생각하면 된다)를 다루게 되고, 각각의 레이어는 스프라이트, 레이블, 메뉴 등등의 "노드"를 포함하게 된다. 노드는 다른 노드를 포함할 수도 있다(예를 들어 스프라이트는 그 안에 자식 스프라이트를 가질 수 있다).


 만약 여러분이 샘플 프로젝트를 보고 있다면, HelloWorldScene 이라는 1개의 씬만이 사용되고 있는 것을 볼 수 있다. 이제 우리는 바로 거기에다 게임 플레이 부분을 만들게 될 것이다. 소스를 열어 보면 씬에 "Hello World"를 보여주는 레이블을 넣는 메소드, 그리고 그 메소드를 초기화하는 코드가 있다. 우리는 이 코드를 없애 버리고, 대신에 스프라이트를 넣을 것이다.


스프라이트 넣기


 스프라이트를 넣기 전에, 당연히 스프라이트를 구성할 그림이 필요해진다. 여러분이 이 그림들을 직접 만들 수도 있지만, 나의 사랑하는 아내가 만들어준 그림들(플레이어, 표창, )을 쓸 수도 있을 것이다. 

 그림을 준비했다면, 그것들을 드래그해서 XCode의 Resources 폴더에 넣자. 그리고 “Copy items into destination group’s folder (if needed)” 부분이 체크되어 있는지를 확인하자.

 이제 그림이 준비됐다. 우리는 이제 플레이어가 위치할 곳을 생각해봐야 한다. Cocos2D에서는 왼쪽 아래 지점이 (0,0)이며 거기서부터 오른쪽 위로 갈 수록 X와 Y 좌표가 증가한다는 것을 기억하라. 우리는 이 프로젝트를 가로 모드로 할 것이므로, 가장 오른쪽 위의 좌표는 (480, 320)이 될 것이다.


 또한 우리가 오브젝트의 위치를 정할 때, 그림의 중심을 기준으로 삼는다는 것을 기억하라. 캐릭터를 스크린 왼쪽 끝에서 오른쪽 끝으로 이동시킨다고 할 때, 이동의 위치는 이 중심점을 기준으로 사용하게 될 것이다. 즉,


 - 가로 좌표를 주기 위한 가로 기준점은 [그림의 가로 크기]/2

 - 세로 좌표를 주기 위한 세로 기준점은 [그림의 세로 크기]/2


가 된다는 뜻이다.


 좀 더 이해를 쉽게 하기 위해 다음 그림을 보자.


Screen and Sprite Coordinates


 그럼 이제 실제로 만들어 보자. XCode에서 옆의 Classed 폴더를 열고 HelloWorldScene.m 파일을 선택한 뒤, 코드 편집창에서 init 메소드 부분을 다음과 같이 변경한다.


-(id) init
{  
   if( (self=[super init] )) 
   {
       CGSize winSize = [[CCDirector sharedDirector] winSize];
       CCSprite *player = [CCSprite spriteWithFile:@"Player.png"
          rect:CGRectMake(0, 0, 27, 40)];
       player.position = ccp(player.contentSize.width/2, winSize.height/2);
       [self addChild:player];
   }  
   return self;
}


 컴파일하고 실행해 보면 스프라이트가 잘 나타날 것이다. 하지만 아직 배경이 까맣게 나타나고 있다는 점도 알 수 있다. 우리의 닌자 캐릭터를 잘 보이게 하기 위해서는 아무래도 하얀 배경이 훨씬 나을 것이다. Cocos2D에서 이것을 간단하게 처리하고 싶으면 CCColoredLayer 클래스를 이용해 레이어의 배경색을 원하는 색으로 바꾸어 주면 된다. 바로 실험해보자. HelloWorldScene.h 파일을 열고 HelloWorld 인터페이스를 아래와 같이 선언한다.


@interface HelloWorld : CCColorLayer


 그 다음 HelloWorldScene.m 파일을 열고 init 메소드를 조금만 더 수정해주면 배경을 흰색으로 만들 수 있다.


if( (self=[super initWithColor:ccc4(255,255,255,255)] )) {


 수정이 끝났으면 빌드하고 실행해 보자. 이제 스프라이트가 흰 배경에 서 있는 모습이 보일 것이다. 우리의 닌자는 이제 싸울 준비가 됐다!


Sprite Added Screenshot


목표물 움직이기

 다음으로 우리는 목표물, 즉 적들이 화면에 나타나 우리의 닌자를 공격해오도록 해야 한다. 이게 재미있게 느껴지려면 당연히 적들이 움직여야 할 것이다. 자, 그러면 적들이 화면 오른쪽에서 나타나 왼쪽으로 이동해 오도록 만들어 보자.


 다음 코드를 init 메소드 바로 앞에 넣는다.


-(void)addTarget 
{
   CCSprite *target = [CCSprite spriteWithFile:@"Target.png"
     rect:CGRectMake(0, 0, 27, 40)];

   // 적들이 어떤 Y 좌표에서 나타날 것인지를 결정한다
   CGSize winSize = [[CCDirector sharedDirector] winSize];
   int minY = target.contentSize.height/2;
   int maxY = winSize.height - target.contentSize.height/2;
   int rangeY = maxY - minY;
   int actualY = (arc4random() % rangeY) + minY;

   // 적들을 화면 오른쪽 끝에서 나타나게 만든다
   // 위쪽에서 계산한 결과에 따라 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는 이동, 점프, 페이드(사라지거나 나타날 때) 등등, 우리가 스프라이트를 애니메이션시킬 때에 사용할 수 있는 액션을 미리 많이 준비해 놓고 있다. 우리는 여기에서 그 중의 3가지 액션을 적들에게 시킬 것이다.

  • CCMoveTo: CCMoveTo 액션은 오브젝트를 스크린 밖에서부터 왼쪽으로 곧바로 움직이게끔 한다. 전체 움직임이 다 끝날 때까지 걸리는 시간도 지정할 수 있는데, 우리는 2~4초 사이에서 랜덤하게 결정되게끔 할 것이다.
  • CCCallFuncN: CCCallFuncN 함수는 액션이 다 끝났을 때 오브젝트에게 일어나는 콜벡을 정의한다. 여기에서는 "spriteMoveFinished"라는 콜백을 정의할 것인데, 지금은 아직 그 부분을 쓰지 않았으므로 나중에 더 설명한다.
  • CCSequence: CCSequence 액션은 몇 가지 액션을 정해놓은 순서에 따라 행하게끔 만들 수 있다. 우리는 먼저 CCMoveTo 액션을 행하고, 그것이 끝나면 CCCallFuncN 액션을 행하도록 할 것이다.


 이제 위에서 설명한 콜백 함수를 넣는다. addTarget 바로 전 부분에 다음 메소드를 넣자.


-(void)spriteMoveFinished:(id)sender 
{
  CCSprite *sprite = (CCSprite *)sender;
  [self removeChild:sprite cleanup:YES];
}


 알겠지만 이 함수의 목적은 화면 밖으로 나간 스프라이트를 제거하기 위한 것이다. 이 부분은 화면에 보이지 않는 알 수 없는 스프라이트들 때문에 메모리를 낭비하지 않게 하기 위해 매우 중요하다. 물론 스프라이트의 배열을 다시 사용해서 이 문제를 해결하는 다른 좋은 방법도 있지만, 필자의 이 초보자용 트터리얼에서는 위와 같은 간단한 방법을 취할 것이다.


 적을 등장시키기 위해 마지막으로 해야 할 일이 있다.

One last thing before we go. We need to actually call the method to create targets! And to make things fun, let’s have targets continuously spawning over time. We can accomplish this in Cocos2D by scheduling a callback function to be periodically called. Once per second should do for this. So add the following call to your init method before you return:

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

And then implement the callback function simply as follows:

-(void)gameLogic:(ccTime)dt {  [self addTarget];}

That’s it! So now if you compile and run the project, now you should see targets happily moving across the screen:

Targets Screenshot

Shooting Projectiles

At this point, the ninja is just begging for some action – so let’s add shooting! There are many ways we could implement shooting, but for this game we are going to make it so when the user taps the screen, it shoots a projectile from the player in the direction of the tap.

I want to use a CCMoveTo action to implement this to keep things at a beginner level, but in order to use this we have to do a little math. This is because the CCMoveTo requires us to give a destination for the projectile, but we can’t just use the touch point because the touch point represents just the direction to shoot relative to the player. We actually want to keep the bullet moving through the touch point until the bullet goes off-screen.

Here’s a picture that illustrates the matter:

Projectile Triangle

So as you can see, we have a small triangle created by the x and y offset from the origin point to the touch point. We just need to make a big triangle with the same ratio – and we know we want one of the endpoints to be off the screen.

Ok, so onto the code. First we have to enable touches on our layer. Add the following line to your init method:

self.isTouchEnabled = YES;

Since we’ve enabled touches on our layer, we will now receive callbacks on touch events. So let’s implement the ccTouchesEnded method, which is called whenever the user completes a touch, as follows:

- (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
 {
   // Choose one of the touches to work with
  UITouch *touch = [touches anyObject];
  CGPoint location = [touch locationInView:[touch view]];
  location = [[CCDirector sharedDirector] convertToGL:location];
   // Set up initial location of projectile
  CGSize winSize = [[CCDirector sharedDirector] winSize];
  CCSprite *projectile = [CCSprite spriteWithFile:@"Projectile.png"
     rect:CGRectMake(0, 0, 20, 20)];
  projectile.position = ccp(20, winSize.height/2);
   // Determine offset of location to projectile
  int offX = location.x - projectile.position.x;
  int offY = location.y - projectile.position.y;
   // Bail out if we are shooting down or backwards
  if (offX <= 0) return;
   // Ok to add now - we've double checked position
  [self addChild:projectile];
   // Determine where we wish to shoot the projectile to
  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;
   // Move projectile to actual endpoint
  [projectile runAction:[CCSequence actions:
    [CCMoveTo actionWithDuration:realMoveDuration position:realDest],
    [CCCallFuncN actionWithTarget:self selector:@selector(spriteMoveFinished:)],
    nil]]
}

In the first portion, we choose one of the touches to work with, get the location in the current view, then call convertToGL to convert the coordinates to our current layout. This is important to do since we are in landscape mode.

Next we load up the projectile sprite and set the initial position as usual. We then determine where we wish to move the projectile to, using the vector between the player and the touch as a guide, according to the algorithm described previously.

Note that the algorithm isn’t ideal. We’re forcing the bullet to keep moving until it reaches the offscreen X position – even if we would have gone offscreen in the Y position first! There are various ways to address this including checking for the shortest length to go offscreen, having our game logic callback check for offscreen projectiles and removing rather than using the callback method, etc. but for this beginner tutorial we’ll keep it as-is.

The last thing we have to do is determine the duration for the movement. We want the bullet to be shot at a constant rate despite the direction of the shot, so again we have to do a little math. We can figure out how far we’re moving by using the Pythagorean Theorem. Remember from geometry, that is the rule that says the length of the hypotenuse of a triangle is equal to the square root of the sum of the squares of the two sides.

Once we have the distance, we just divide that by the velocity in order to get the duration. This is because velocity = distance over time, or in other words time = distance over velocity.

The rest is setting the actions just like we did for the targets. Compile and run, and now your ninja should be able to fire away at the oncoming hordes!

Projectiles Screenshot

Collision Detection

So now we have shurikens flying everywhere – but what our ninja really wants to do is to lay some smack down. So let’s add in some code to detect when our projectiles intersect our targets.

There are various ways to solve this with Cocos2D, including using one of the included physics libraries: Box2D or Chipmunk. However to keep things simple, we are going to implement simple collision detection ourselves.

To do this, we first need to keep better track of the targets and projectiles currently in the scene. Add the following to your HelloWorldScene class declaration:

NSMutableArray *_targets;NSMutableArray *_projectiles;

And initialize the arrays in your init method:

_targets = [[NSMutableArray alloc] init];_projectiles = [[NSMutableArray alloc] init];

And while we’re thinking of it, clean up the memory in your dealloc method:

[_targets release];_targets = nil;[_projectiles release];_projectiles = nil;

Now, modify your addTarget method to add the new target to the targets array and set a tag for future use:

target.tag = 1;[_targets addObject:target];

And modify your ccTouchesEnded method to add the new projectile to the projectiles array and set a tag for future use:

projectile.tag = 2;[_projectiles addObject:projectile];

Finally, modify your spriteMoveFinished method to remove the sprite from the appropriate array based on the tag:

if (sprite.tag == 1)
{
 // target
  [_targets removeObject:sprite];
}
 else
 if (sprite.tag == 2)
{
 // projectile 
 [_projectiles removeObject:sprite];
}

Compile and run the project to make sure everything is still working OK. There should be no noticeable difference at this point, but now we have the bookkeeping we need to implement some collision detection.

Now add the following method to HelloWorldScene:

- (void)update:(ccTime)dt 
{
   NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];

   for (CCSprite *projectile in _projectiles) 
   {
      CGRect projectileRect = CGRectMake(
         projectile.position.x - (projectile.contentSize.width/2),
         projectile.position.y - (projectile.contentSize.height/2),
         projectile.contentSize.width,
         projectile.contentSize.height);

      NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];

      for (CCSprite *target in _targets) 
      {
         CGRect targetRect = CGRectMake(
            target.position.x - (target.contentSize.width/2),
            target.position.y - (target.contentSize.height/2),
            target.contentSize.width,
            target.contentSize.height);
        if (CGRectIntersectsRect(projectileRect, targetRect))
        {        
           [targetsToDelete addObject:target];
        }
      }

      for (CCSprite *target in targetsToDelete)
      {
         [_targets removeObject:target];
         [self removeChild:target cleanup:YES];
      }

      if (targetsToDelete.count > 0)
      {
         [projectilesToDelete addObject:projectile];
      }

      [targetsToDelete release];
   }

   for (CCSprite *projectile in projectilesToDelete)
   {
      [_projectiles removeObject:projectile];
      [self removeChild:projectile cleanup:YES];
   }

   [projectilesToDelete release];
}

The above should be pretty clear. We just iterate through our projectiles and targets, creating rectangles corresponding to their bounding boxes, and use CGRectIntersectsRect to check for intersections. If any are found, we remove them from the scene and from the arrays. Note that we have to add the objects to a “toDelete” array because you can’t remove an object from an array while you are iterating through it. Again, there are more optimal ways to implement this kind of thing, but I am going for the simple approach.

You just need one more thing before you’re ready to roll – schedule this method to run as often as possible by adding the following line to your init method:

[self schedule:@selector(update:)];

Give it a compile and run, and now when your projectiles intersect targets they should disappear!

Finishing Touches

We’re pretty close to having a workable (but extremely simple) game now. We just need to add some sound effects and music (since what kind of game doesn’t have sound!) and some simple game logic.

If you’ve been following my blog series on audio programming for the iPhone, you’ll be extremely pleased to hear how simple the Cocos2D developers have made it to play basic sound effects in your game.

First, drag some background music and a shooting sound effect into your resources folder. Feel free to use the cool background music I made or my awesome pew-pew sound effect, or make your own.

Then, add the following import to the top of your HelloWorldScene.m:

#import "SimpleAudioEngine.h"

In your init method, start up the background music as follows:

[[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"background-music-aac.caf"];

0.99-final update: It seems there might be a minor bug in 0.99-final where the background music will only play once (whereas it should loop) – either that or I’m doing something wrong. For a workaround, see the comments at the end of this article.

And in your ccTouchesEnded method play the sound effect as follows:

[[SimpleAudioEngine sharedEngine] playEffect:@"pew-pew-lei.caf"];

Now, let’s create a new scene that will serve as our “You Win” or “You Lose” indicator. Click on the Classes folder and go to File\New File, and choose Objective-C class, and make sure subclass of NSObject is selected. Click Next, then type in GameOverScene as the filename, and make sure “Also create GameOverScene.h” is checked.

Then replace GameOverScene.h with the following code:

#import "cocos2d.h" 

@interface GameOverLayer : CCColorLayer 
{
  CCLabel *_label;
}

@property (nonatomic, retain) CCLabel *label;

@end 


@interface GameOverScene : CCScene 
{
  GameOverLayer *_layer;
}

@property (nonatomic, retain) GameOverLayer *layer;

@end

Then replace GameOverScene.m with the following code:

#import "GameOverScene.h"
#import "HelloWorldScene.h" 

@implementation GameOverScene

@synthesize layer = _layer; 

- (id)init 
{
   if ((self = [super init]))
   {
      self.layer = [GameOverLayer node];
      [self addChild:_layer];
   }
   return self;
}

 - (void)dealloc
{
   [_layer release];
   _layer = nil;
   [super dealloc];
} 
@end 

@implementation GameOverLayer

@synthesize label = _label; 

-(id) init
{
  if( (self=[super initWithColor:ccc4(255,255,255,255)] )) 
  {
     CGSize winSize = [[CCDirector sharedDirector] winSize];
     self.label = [CCLabel labelWithString:@"" fontName:@"Arial" fontSize:32];
     _label.color = ccc3(0,0,0);
     _label.position = ccp(winSize.width/2, winSize.height/2);
     [self addChild:_label];
     [self runAction:[CCSequence actions:
      [CCDelayTime actionWithDuration:3],
      [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],
      nil]];
  }
  return self;
}


- (void)gameOverDone 
{
   [[CCDirector sharedDirector] replaceScene:[HelloWorld scene]]
}


- (void)dealloc 
{
  [_label release];
  _label = nil;
  [super dealloc];
} 

@end

Note that there are two different objects here: a scene and a layer. The scene can contain any number of layers, however in this example it just has one. The layer just puts a label in the middle of the screen, and schedules a transition to occur 3 seconds in the future back to the HelloWorld scene.

Finally, let’s add some extremely basic game logic. First, let’s keep track of the projectiles the player has destroyed. Add a member variable to your HelloWorld class in HelloWorldScene.h as follows:

int _projectilesDestroyed;

Inside HelloWorldScene.m, add an import for the GameOverScene class:

#import "GameOverScene.h"

Increment the count and check for the win condition in your update method inside the targetsToDelete loop right after removeChild:target:

_projectilesDestroyed++;if (_projectilesDestroyed > 30) {  GameOverScene *gameOverScene = [GameOverScene node];  [gameOverScene.layer.label setString:@"You Win!"];  [[CCDirector sharedDirector] replaceScene:gameOverScene];}

And finally let’s make it so that if even one target gets by, you lose. Modify the spriteMoveFinished method by adding the following code inside the tag == 1 case right after removeChild:sprite:

GameOverScene *gameOverScene = [GameOverScene node];
[gameOverScene.layer.label setString:@"You Lose :["];
[[CCDirector sharedDirector] replaceScene:gameOverScene];

Go ahead and give it a compile and run, and you should now have win and lose conditions and see a game over scene when appropriate!

Gimme The Code!

And that’s a wrap! Here’s the full code for the simple Cocos2D iPhone game that we developed thus far.

Where To Go From Here?

This project could be a nice basis for playing around some more with Cocos2D by adding some new features into the project. Maybe try adding in a bar chart to show how many more targets you have to destroy before you win (check out the drawPrimitivesTest sample project for examples of how to do that). Maybe add cooler death animations for when the monsters are destroyed (see ActionsTest, EffectsTest, and EffectsAdvancedTest projects for that). Maybe add more sounds, artwork, or gameplay logic just for fun. The sky’s the limit!

If you want to keep going with this tutorial series, check out How To Add A Rotating Turret to this game!

Also, if you’d like to keep learning more about Cocos2D, check out my tutorials on how to create buttons in Cocos2Dintro to Box2D, or how to create a simple Breakout game.

Feel free to chime in if you know of any better ways to do various things with this project or if there are any problems – like I said this is the first time I’ve played with Cocos2D so I have a lot left to learn!

---

출처 : http://www.raywenderlich.com/352/how-to-make-a-simple-iphone-game-with-cocos2d-tutorial

Posted by windship
프로그래밍/Cocos2D2010. 7. 2. 17:57
비록 예제를 위한 게임이지만 3단계까지 진행한 상태에서는 별반 감흥이 없다. 이유는 바로 소리(배경음악, 효과음)가 없기 때문이다. 지금까지 게임 개발의 문외한 이었지만 "게임은 종합 예술"이라는 말을 자주 들었었다. 정말 그렇다. 아주 간단한 게임조차 그래픽과 사운드는 필수 요소이다.

사운드 샘플은 원문의 샘플을 그대로 사용하기로 한다. 아니면 Cocos2D에서 소개하는 게임 관련 리소스(Game Resources)를 이용해도 좋을 것 같다.

배경 음악과 효과음 추가
다음 두 개의 파일을 다운 받아 Xcode 프로젝트의 Resources 폴더에 추가한다.
우선 MammothHuntingScene.h에 다음 임포트 문을 추가한다.

#import "SimpleAudioEngine.h"


init 메소드에 다음과 같이 배경음악을 시작하는 코드를 추가하자. 마지막 부분에 추가하면 된다.

[[SimpleAudioEngine sharedEngineplayBackgroundMusic:@"background-music-aac.caf"];


그리고 ccTouchesEnded 메소드에 총알 발사 효과음을 추가한다.

[[SimpleAudioEngine sharedEngineplayEffect:@"pew-pew-lei.caf"];


빌드앤런! 이제 배경 음악과 효과음을 들을 수 있다.

게임 진행 로직 추가
다음은 간단한 게임 진행 로직을 추가할 것이다. 프로젝트에 GameOverScene 이라고 새로운 클래스를 추가하자. 그리고 다음과 같이 GameOverScene.h와 GameOverScene.m을 수정한다.

[GameOverScene.h]

@interface GameOverLayer : CCColorLayer {

CCLabel *_label;

}


@property (nonatomicretain) CCLabel *label;


@end




@interface GameOverScene : CCScene {

GameOverLayer *_layer;

}


@property (nonatomicretain) GameOverLayer *layer;


@end



[GameOverScene.m]

#import "GameOverScene.h"

#import "MammothHuntingScene.h"



@implementation GameOverLayer


@synthesize label = _label;



-(id) init {

if ((self=[super initWithColor:ccc4(255,255,255,255)])) {

CGSize winSize = [[CCDirector sharedDirectorwinSize];

self.label = [CCLabel labelWithString:@"" fontName:@"Arial" fontSize:32];

_label.color = ccc3(0,0,0);

_label.position = ccp(winSize.width/2, winSize.height/2);

[self addChild:_label];

[self runAction:[CCSequence actions:

 [CCDelayTime actionWithDuration:3],

 [CCCallFunc actionWithTarget:self selector:@selector(gameOverDone)],

 nil]];

}

return self;

}



- (void)gameOverDone {

[[CCDirector sharedDirectorreplaceScene:[MammothHuntingScene scene]];

}



- (void)dealloc {

[_label release];

_label = nil;

[super dealloc];

}



@end



@implementation GameOverScene


@synthesize layer = _layer;



- (id)init {

if ((self = [super init])) {

self.layer = [GameOverLayer node];

[self addChild:_layer];

}

return self;

}



- (void)dealloc {

[_layer release];

_layer = nil;

[super dealloc];

}



@end


위 소스에서 한 가지 주목할 것은 하나의 파일에 두 개의 서로 다른 객체, 즉 Layer와 Scene이 있다는 점이다. 보통 하나의 Scene은 여러 개의 Layer를 포함할 수 있다. 그러나 본 예제에서는 오직 하나의 Layer만 사용한다. 화면의 중앙에 라벨을 그리는 Layer이다.

마지막으로, 단순한 게임 진행 로직 추가를 위해, MammothHuntingScene.h에 다음의 멤버 변수를 추가하자. 

int _projectilesDestroyed;


MammothHuntingScene.m에는 다음 임포트 구문을 추가한다.

#import "GameOverScene.h"


그리고 게임의 승리 조건을 판단하기 위해 update 메소드의 targetsToDelete 루프에 다음 코드를 추가한다.

_projectilesDestroyed++;

if (_projectilesDestroyed > 30) {

GameOverScene *gameOverScene = [GameOverScene node];

[gameOverScene.layer.label setString:@"You Win!"];

[[CCDirector sharedDirectorreplaceScene:gameOverScene];

}


다음 spriteMoveFinished 메소드의 sprite.tag == 1 조건 부분에 아래의 코드를 추가한다.

GameOverScene *gameOverScene = [GameOverScene node];

[gameOverScene.layer.label setString:@"You Lose :["];

[[CCDirector sharedDirectorreplaceScene:gameOverScene];


드디어 아주 아주 간단한 게임 개발이 끝이 났다. 다시 빌드앤런! 추가된 게임 로직이 제대로 작동될 것이다.

MammothHunting 프로젝트 소스
Posted by windship
프로그래밍/Cocos2D2010. 7. 2. 17:55
충돌 검사
이전 포스트까지의 내용대로 개발된 게임은 아무리 총알을 발사해도 타겟을 통과할 뿐이다. 이 문제를 해결하기 위해 충돌 검사라는 것이 필요하다.

케익에 맞은 맘모스는 게임 화면에서 사라져와 한다. 물론 잘 만들어진 게임이라면 총알 맞는 효과 같은 것들이 부수적으로 필요할 것이다. 

그러나 간단한 게임 예제라는 전제로 이런 부분들은 배제하고 간단히 총알과 타겟이 만났을 경우만 처리하는 코드를 작성할 것이다. 게임 물리의 거리, 운동 등의 다양한 면을 담당하는 게임 엔진의 한 부분인 물리 엔진은 사용되지 않는다.

먼저 다음 코드를 MammothHuntingScene.h에 추가하자.

NSMutableArray *_targets;

NSMutableArray *_projectiles;


그리고 MammothHuntingScene.m의 init 메소드의 self.isTouchEnabled = YES; 아래에 다음 코드를 추가한다.

_targets = [[NSMutableArray allocinit];

_projectiles = [[NSMutableArray allocinit];


마지막으로 메모리 관리를 위해 MammothHuntingScene.m의 dealloc 메소드에 다음 코드를 추가한다.

[_targets release];

_targets = nil;

[_projectiles release];

_projectiles = nil;


이제 addTarget 메소드르 수정할 차례이다. 위에서 선언한 _targets 배열에 새로운 target를 추가하는 것이다. 제일 아랫 부분에 다음처럼 추가하자. 새로 설정한 tag는 나중에 사용될 것이다.

target.tag = 1;

[_targets addObject:target];


그리고 ccTouchesEnded 메소드 역시 _projectiles 배열에 새로운 projectile을 추가하게 끔 다음 코드를 메소드 마지막에 추가한다. 새로 설정한 tag는 나중에 사용될 것이다.

projectile.tag = 2;

[_projectiles addObject:projectile];


마지막으로 spriteMoveFinished 메소드에 위에서 설정한 tag를 기반으로 스프라이트를 삭제하는 코드를 추가한다.

if (sprite.tag == 1) { // 타겟.

[_targets removeObject:sprite];

else if (sprite.tag == 2) { // 총알.

[_projectiles removeObject:sprite];

}


빌드앤런 해보자. 그런데 이전과 마찬가지로 실제 사냥이 되지는 않을 것이다. 당연하다. 충돌 검사 코드가 아직 작성되지 않았다. 다음 update: 메소드를 MammothHuntingScene.m에 추가해야 한다.

- (void)update:(ccTime)dt {

NSMutableArray *projectilesToDelete = [[NSMutableArray allocinit];

for (CCSprite *projectile in _projectiles) {

CGRect projectileRect = CGRectMake(

   projectile.position.x - (projectile.contentSize.width/2), 

   projectile.position.y - (projectile.contentSize.height/2), 

   projectile.contentSize.width

   projectile.contentSize.height);

NSMutableArray *targetsToDelete = [[NSMutableArray allocinit];

for (CCSprite *target in _targets) {

CGRect targetRect = CGRectMake(

   target.position.x - (target.contentSize.width/2), 

   target.position.y - (target.contentSize.height/2), 

   target.contentSize.width

   target.contentSize.height);

if (CGRectIntersectsRect(projectileRect, targetRect)) {

[targetsToDelete addObject:target];

}

}

for (CCSprite *target in targetsToDelete) {

[_targets removeObject:target];

[self removeChild:target cleanup:YES];

}

if (targetsToDelete.count > 0) {

[projectilesToDelete addObject:projectile];

}

[targetsToDelete release];

}

for (CCSprite *projectile in projectilesToDelete) {

[_projectiles removeObject:projectile];

[self removeChild:projectile cleanup:YES];

}

[projectilesToDelete release];

}


update: 메소드는 루프를 돌면서 총알과 타겟이 교차하는 지  CGRectIntersectsRect를 통해 검사한다. 만약 교차한다면 화면과 배열에서 제거해 버린다. 
여기서 한 가지 주의할 것이 있다. 충돌 검사 루프를 하는 동안은 배열의 객체를 삭제할 수 없으므로 제거 대상을 배열에 넣었다가 삭제한다.

정말 마지막으로 다음의 코드를 init 메소드 하단에 추가하고 빌드앤런하자. 이제 맘모스를 사냥할 수 있을 것이다.

[self schedule:@selector(update:)];


서두에 간단히 물리 엔진에 대해서 이야기 했던 것처럼, 간단한 그래픽 기반 게임 조차 어느 정도 물리 코드가 필요하다. 이 말은 충돌 검사의 다양한 방법이 있다는 것이다. 다음 기회에 Cocos2D의 물리 엔진인 Box2d와 Chipmunk를 공부할 항목에 추가해야 겠다.

---

Posted by windship