�빫�� ����� ���

Posts tagged ‘iphone apps 만들기’

만들어봐요 iPhone 게임 만들기 8 – Cocos2D for iPhone 위키 번역(프로그래밍 가이드)


한동안 블로그를 쓸 수 없었습니다. SI라면 항상 바쁠텐데 전 SM이라.. 가끔만 바쁩니다. ㅎㅎ 3월초까지 바쁠 것 같은데 잠깐 짬을 내어서 그 전에 위키 번역한 내용들을 정리해둡니다.

* 그냥 안해도 되겠다 싶은 내용은 영문 그대로 두었습니다.

이 번역한 내용은 프로그래밍 가이드 부분일 뿐입니다. 공식 위키에는 더 많은 내용이 있으며 아직 v1.0이 아니기 때문에 앞으로 많이 추가될 엔진이지요. 아 얼마 전에 v.99가 릴리즈 되었습니다. iPad 적용과 CC 네임 스페이스 등 추가 개선된 내용이 많으니 릴리즈 노트를 꼭 보시는 것이 좋겠죠?

아직 바쁜 일이 끝나지 않았으니.. 3월 초 까지는 손 놓아야 하겠네요. 한참 제작중이던 앱도 중지된 상태고요. 뭐, 내일은 또 내일의 태양이 뜨니까요. ㅎㅎ

P.S 이제 봄이 오고 있습니다. 괴기들이 꿈틀꿈틀하는 소리가 막 들려요. 본격적인 낚시 시즌이 시작될 것 같습니다아~~~

만들어봐요 iPhone 게임 만들기 7 – Cocos2D for iPhone 튜토리얼 : 액션과 이벤트

cocos2d의 액션과 이벤트를 테스트해 보았습니다. 관련 문서들은 cocos2d 위키 번역해서 올린 것으로 대강 정리되었고 이 포스트에 참조할 문서는

이 정도가 되겠습니다.(API reference 번역은 불가능이예요. doxygen으로 만들어진 문서라 -_-;)
제가 만든 소스는 ActionTest.zip으로 압축해 놓았으니 다운받아서 참조하시고요, 그 소스의 설명이 이 포스트의 전부입니다.

cocos2d는 다양한 액션을 제공하는데, 이동, 크기변환 등의 기본 액션(특이하게도 점프 액션이 따로 있답니다.)과 기본 액션들을 결합하거나 반복하는 액션이 제공됩니다.(자세한 내용은 Basic ActionComposition actions를 참조하세요.) 보통 순서는 레이어를 만들고, 레이어에 스프라이트와 액션을 만들고, 스프라이트에 액션을 동작시키는 순입니다.

먼저 이번 테스트를 위한 구상은 동그란 이미지 하나를 스프라이트로 만든 뒤에 화면을 터치하면 액션하는 것입니다. 이미지는 이걸 사용했죠.

XCode와 iPhone SDK, 그리고 cocos2d의 templete을 설치(자세한 것은 프로그래밍 가이드를 참조하세요) 하셨으면 새 프로젝트에 cocos2d templete이 보일 겁니다. 새 프로젝트를 ActionTest라고 만드셨다면 ActionTestAppDelegate.h, ActionTestAppDelegate.m, HelloWorldScene.h, HelloWorldScene.m(요 두개는 기억이 가물가물해서..)가 생성됩니다. HelloWorldScene 두 파일을 LabelsLayerScene으로 바꾸시고, FireElementScene라는 새 Objective-c 파일을 추가하세요. LabelsLayerScene은 그냥 화면에 배경 글자를 표시하게 하고, 실제 액션은 FireElementScene 파일에서 작업할 겁니다.

Classes 파일 중 ActionTestAppDelegate 코딩을 살펴보죠.

ActionTestAppDelegate.h

#import <UIKit/UIKit.h>
 
@interface ActionTestAppDelegate : NSObject <UIApplicationDelegate, UIAlertViewDelegate, UITextFieldDelegate> {
	UIWindow *window;
}
 
@property (nonatomic, retain) UIWindow *window;
 
@end

템플릿에서 생성한 것을 거의 그대로 쓰는데 UIAlertViewDelegate, UITextFieldDelegate 두 개의 프로토콜을 추가해 줍니다.(UITextField야.. 화면에 찍어보려고 한 거고, UIAlertView도 좀 해보려고 했는데 이번엔 사용하지 않았네요.) 이것 말고는 더 한 것이 없어서 다음 .m 파일로 가죠.

ActionTestAppDelegate.m

#import "ActionTestAppDelegate.h"
#import "cocos2d.h"
#import "LabelsLayerScene.h"
#import "FireElementScene.h"
 
@implementation ActionTestAppDelegate
 
@synthesize window;
 
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
	window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 
	[window setUserInteractionEnabled:YES];	
	[window setMultipleTouchEnabled:YES];
 
	if( ! [CCDirector setDirectorType:CCDirectorTypeDisplayLink] )
		[CCDirector setDirectorType:CCDirectorTypeDefault];
 
	[[CCDirector sharedDirector] setPixelFormat:kPixelFormatRGBA8888];
	[CCTexture2D setDefaultAlphaPixelFormat:kTexture2DPixelFormat_RGBA8888];
	[[CCDirector sharedDirector] setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
	[[CCDirector sharedDirector] setAnimationInterval:1.0/60];
	[[CCDirector sharedDirector] setDisplayFPS:YES];
	[[CCDirector sharedDirector] attachInView:window];	
	[window makeKeyAndVisible];		
	CCScene *scene = [CCScene node];
	CCLayer *labelLayer = [LabelsLayer node];
	[scene addChild:labelLayer];
	CCLayer *fireElementLayer = [FireElement node];
	[scene addChild:fireElementLayer];
 
	[[CCDirector sharedDirector] runWithScene: scene];
}
 
- (void)applicationWillResignActive:(UIApplication *)application {
	[[CCDirector sharedDirector] pause];
}
 
- (void)applicationDidBecomeActive:(UIApplication *)application {
	[[CCDirector sharedDirector] resume];
}
 
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
	[[CCTextureCache sharedTextureCache] removeUnusedTextures];
}
 
- (void)applicationWillTerminate:(UIApplication *)application {
	[[CCDirector sharedDirector] end];
}
 
- (void)applicationSignificantTimeChange:(UIApplication *)application {
	[[CCDirector sharedDirector] setNextDeltaTimeZero:YES];
}
 
- (void)dealloc {
	[[CCDirector sharedDirector] release];
	[window release];
	[super dealloc];
}
 
@end

여기에 몇가지 중요한 코드들이 있습니다. 각 메소드 골격은 그대로 유지하고, app이 동작을 시작하기 직전에 applicationDidFinishLaunching 메소드가 호출(자동으로)되게 되는데요, 이 메소드가 거의 모든 초기화를 담당하거나 다른 초기화 메소드를 호출하게 됩니다.

	[window setUserInteractionEnabled:YES];	
	[window setMultipleTouchEnabled:YES];

setUserInteractionEnabled이 YES이면 터치가 가능하고 setMultipleTouchEnabled이 YES이면 멀티터치가 가능해집니다. 통상 UIView 기반의 app들은 Controller 클래스에 동작을 정의하고 뷰의 nib에 정의된 UI를 아웃렛이 정의된 Delegate로 연결해서 상호동작하게 하는데, cocos2d에서는 그저 속성을 셋팅하면 사용자의 UI 이벤트(터치나 흔들기 등)에 따라서 미리 정의된 이름의 메소드를 호출합니다. 아웃렛과 델리게이트의 마우스질을 안하게 되지요.(제가 좋아하는 스타일이기도 합니다. ㅎㅎ) setUserInteractionEnabled을 셋팅하였기 때문에 화면을 터치하였을 때 ccTouchesEnded가 호출될 수 있는 것이죠.

	CCScene *scene = [CCScene node];
	CCLayer *labelLayer = [LabelsLayer node];
	[scene addChild:labelLayer];
	CCLayer *fireElementLayer = [FireElement node];
	[scene addChild:fireElementLayer];

위의 코드가 cocos2d의 Scene-Layer-Sprite의 핵심입니다. (자세한 것은 cocos2d 기본 컨셉를 참조하세요) 전체적으로 여러 노드를 포함하는 트리구조를 이루어 Scene과 Layer는 node가 되고, 각 Sprite는 Layer의 child가 되는 방법이지요. 위의 코드는 LabelsLayerScene으로 Scene을 생성하고(실행까지), 그 scene에 FireElementScene을 붙이는 겁니다. (저도 처음에는 Scene, Layer, Node가 영 헷갈렸는데 직접 코딩해 보니깐 이해가 가더군요.) 액션과 이벤트는 FireElement에 정의하게 되지만 그 이벤트가 일어나기 위해서는 위에서 언급한 setUserInteractionEnabled를 셋팅하여야 했습니다.

LabelsLayerScene.h

#import "cocos2d.h"
 
@interface LabelsLayer : CCLayer
{
}
 
+(id) scene;
 
@end

LabelsLayerScene 는 CCLayer를 상속한 것 말고는 거의 한 게 없어요.

LabelsLayerScene.m

#import "LabelsLayerScene.h"
 
@implementation LabelsLayer
 
+(id) scene
{
	CCScene *scene = [CCScene node];
	LabelsLayer *layer = [LabelsLayer node];
	[scene addChild: layer];
	return scene;
}
 
-(id) init
{
	if( (self=[super init] )) {
		CCLabel* label = [CCLabel labelWithString:@"Action Test" fontName:@"Marker Felt" fontSize:64];
		CGSize size = [[CCDirector sharedDirector] winSize];
		label.position =  ccp( size.width /2 , size.height/2 );
		[self addChild: label];
	}
	return self;
}
 
- (void) dealloc
{
	[super dealloc];
}
@end

물론 “Action Test”라는 CCLabel 인스턴스를 만들어서 레이어(자기자신)에 붙인 건 있죠.

FireElementScene.h

#import "cocos2d.h"
 
@interface FireElement : CCLayer {
 
}
 
+(id) scene;
 
@end
 
enum {
	kTagSprite = 1,
};

kTagSprite를 정의한 enum 타입은 Sprite를 편하게 찾아오기 위해서입니다. 일단 태그처럼 enum 타입을 하나 정해놓고 sprite 인스턴스를 layer에 붙일 때 알려주면 다른 메소드가 해당 레이어를 사용할 때 스프라이트를 알아낼 수 있죠.

FireElementScene.m

#import "FireElementScene.h"
 
@implementation FireElement
 
+(id) scene
{
	CCScene *scene = [CCScene node];
	FireElement *layer = [FireElement node];
	[scene addChild: layer];
	return scene;
}
 
-(id) init
{
	if( (self=[super init] )) {
		self.isTouchEnabled = YES;
		CCSprite *sprite = [CCSprite spriteWithFile:@"element_fire.png"];
		CGSize size = [[CCDirector sharedDirector] winSize];
		sprite.position = ccp(size.width / 2, size.height / 2);
		[self addChild:sprite z:1 tag:kTagSprite];
	}
	return self;
}
 
-(BOOL)ccTouchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
	UITouch *touch = [touches anyObject];
	if(touch) {
		CGPoint location = [touch locationInView:[touch view]];
		CGPoint convertedPoint = [[CCDirector sharedDirector] convertToGL:location];
		CCNode *sprite = [self getChildByTag:kTagSprite];
		[sprite stopAllActions];
		id action1 = [CCMoveTo actionWithDuration:0.4f position:convertedPoint];
		id action2 = [CCJumpTo actionWithDuration:0.6f position:ccp(convertedPoint.x + 80.0f, convertedPoint.y) height:50.0f jumps:1];
		id action3 = [CCRotateBy actionWithDuration:0.6f angle:90.0f];
		id action4 = [CCBlink actionWithDuration:1.0f blinks:3];
		[sprite runAction:[CCSequence actions:action1, action2, action3, action4, nil]];
	}
}
 
- (void) dealloc
{
	[super dealloc];
}
 
@end
		[self addChild:sprite z:1 tag:kTagSprite];

이 코드가 layer에 sprite를 붙일 때 tag를 넣어주는 것이고

		CCNode *sprite = [self getChildByTag:kTagSprite];

이 코드가 layer에서 sprite를 찾아오는 코드입니다.

		id action1 = [CCMoveTo actionWithDuration:0.4f position:convertedPoint];
		id action2 = [CCJumpTo actionWithDuration:0.6f position:ccp(convertedPoint.x + 80.0f, convertedPoint.y) height:50.0f jumps:1];
		id action3 = [CCRotateBy actionWithDuration:0.6f angle:90.0f];
		id action4 = [CCBlink actionWithDuration:1.0f blinks:3];
		[sprite runAction:[CCSequence actions:action1, action2, action3, action4, nil]];

액션을 정의하고 동작시키는 부분입니다. ccTouchesEnded 메소드는 터치할 때 마다 호출 될 거고(그래서 [sprite stopAllActions]으로 동작하고 있는 액션을 터치했을 때 일단 멈춰주는 거죠. 안그러면 하던 액션을 다 하고 나서 터치된 새 액션이 실행되니까요.) CCSequence 액션이 실행됩니다. Sequence 액션은 전달받은 액션들을 순서대로 실행하는 이해하기 쉬운 액션이죠.
또 주의할 점이 iPhone SDK의 그래픽은 Top-Left(좌상단)가 원점이 되는 좌표계를 쓰지만, cocos2d는 OpenGL 기반이라 좌하단이 원점이 되는 좌표계를 사용합니다. 그래서 위치를 계산하기 전에 convertToGL 메소드로 좌표들을 변환해야 합니다.

		CGPoint convertedPoint = [[CCDirector sharedDirector] convertToGL:location];

이 코드가 변환하는 코드입니다.

기본 액션(위에서는 Move, Jump, Rotate)들은 To와 By가 있는데요, To는 쉽게 생각되죠. 이동을 원하는 지점이 (80, 80)이면 [CCMoveTo position:ccp(80,80)]이라고 하면 되니깐요. By는 해당 node가 있는 지점에서 얼마큼 더 가느냐 하는 겁니다. To를 By로 바꾸시거나 By를 To로 바꾸시면서 동작을 확인해보시면 쉽게 이해하실 수 있을 겁니다.

만들어봐요 iPhone 게임 만들기 6 – Cocos2D for iPhone HelloWorld 분석

cocos2d for iPhone 샘플 코드를 작성하다가 위키를 또 그냥 번역하게 되었습니다.

이것 말고도 번역한 게 몇가지 더 있지만 일단 정리가 필요한 시점이니.. 코드에 몇가지 설명을 덧붙이겠습니다.

여기부터는 위키에 번역한 것을 고대로 갖다 붙여 놓겠습니다.

이 예제는 단순히 스크린에 라벨을 렌더링하는 기본 단계를 보여줄 뿐입니다. 초기화를 수행하는 다른 옵션들은 설명을 단순화하기 위해 이 예제에서는 설명하지 않을 것입니다. 이 예제를 이해하신다면 여러분은 hello_actions 예제를 보실 수 있을 겁니다.

구현 파일

FileHelloWorld.m

Import headers

c / c++에서와 같이 헤더를 임포트합니다.

// UIWindow, NSAutoReleasePool, 다른 객체를 사용하기 때문에
#import 
 
// 인터페이스 헤더
#import "HelloWorld.h"

레이어

HelloWorld 구현. 마법이 일어나는 곳입니다.

// HelloWorld implementation
@implementation HelloWorld
 
// 인스턴스 초기화 "init"
-(id) init
{
	// 항상 "super" init 호출하셔야 합니다.
	// 애플은 "super"의 리턴값을 "self"에 할당하는 것을 권장합니다.
	if( (self=[super init] )) {
 
		// 라벨 생성 및 초기화
		Label* label = [Label labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];
 
		// 디렉터에 윈도우 사이즈를 알아본다.
		CGSize size = [[Director sharedDirector] winSize];
 
		// 스크린의 한 가운데에 라벨을 위치시킨다.
		// 'ccp' is a helper macro that creates a point. It means: 'CoCos Point'
		label.position =  ccp( size.width /2 , size.height/2 );
 
		// 본 레이어의 자손으로 라벨 추가
		[self addChild: label];
	}
	return self;
}
 
// 할당된 객체들을 릴리즈할 때 "dealloc" 호출
- (void) dealloc
{
	// 이번 케이스에는 릴리즈할 필요가 전혀 없는 특별한 예제이므로 dealloc 할 것이 없습니다.
	// cocos2d는 자동으로 자손을 릴리즈합니다.(Label)
 
	// "super dealloc" 호출을 잊으시면 안됩니다.
	[super dealloc];
}
@end

어플리케이션 델리게이트

어플리케이션 델리게이트의 구현입니다. 아마 여러분의 모든 게임은 이 어플리케이션 델리게이트와 비슷할겁니다. 아래 코드를 이해하지 못하신다면 당장은 별로 중요하지 않을 겁니다.

@implementation AppController
 
// window 는 속성입니다. @synthesize 는 조상 메소드를 생성할 겁니다.
@synthesize window;
 
// 어플리케이션 엔트리 포인트
- (void) applicationDidFinishLaunching:(UIApplication*)application
{
	// UIWindow 생성/초기화
	window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
 
	// cocos2d를 window에 붙이고
	[[Director sharedDirector] attachInWindow:window];
 
	// 레이어를 생성하기 전에 수평 모드로 셋팅
	[[Director sharedDirector] setDeviceOrientation:CCDeviceOrientationLandscapeLeft];
 
	// 윈도우를 visible로 만들기
	[window makeKeyAndVisible];
 
	// 아무것도 없는 Scene 초기화/생성
	Scene *scene = [Scene node];
 
	// HelloWorld 레이어 초기화/생성
	Layer *layer = [HelloWorld node];
	// HelloWorld 레이어를 메인 scene의 자손으로 붙임
	[scene addChild:layer];
 
	// 실행!
	[[Director sharedDirector] runWithScene: scene];
}
 
- (void) dealloc
{
	[window release];
	[super dealloc];
}
 
@end

메인 엔트리 포인트

objective-c도 c / c++ 처럼 메인 엔트리 포인트가 여전히 main함수입니다.

int main(int argc, char *argv[]) {
	// 보통 아래처럼 그냥 두는게 안전하지요. 이름만 "AppController"로 하는 것 빼고요.
	NSAutoreleasePool *pool = [NSAutoreleasePool new];
	UIApplicationMain(argc, argv, nil, @"AppController");
	[pool release];
	return 0;
}

헤더 파일

그리고 헤더파일:
FileHelloWorld.h

// 이 파일을 임포트 할 때 여러분은 모든 cocos2d 클래스를 임포트하게 됩니다.
#import "cocos2d.h"
 
// 어플리케이션 델리게이트 클래스
@interface AppController : NSObject <UIAccelerometerDelegate, UIAlertViewDelegate, UITextFieldDelegate, UIApplicationDelegate>
{
	// main UIWindow
	// OpenGL 뷰는 UIWindow의 뷰가 됩니다.
	UIWindow *window;
}
 
// 메인 UIWindow를 속성으로 만드세요.
@property (nonatomic, retain) UIWindow *window;
@end
 
// HelloWorld Layer
@interface HelloWorld : Layer
{
}
@end

일반적으로 iPhone App는 UIWindow(UIView)의 nib, Controller, Delegate를 만들어 MVC 패턴을 따르게 됩니다.(위에 처럼 간단한 어플리케이션이라면 Model은 빠지게 되겠죠.) 그래서 보통 Controller 코딩에 많은 시간을 할애하게 되죠. 그런데 cocos2d에서는 Delegate는 View에 CCDirector를 연결하는 것에 지나지 않게 됩니다. 특별히 여러 view를 사용하는지는 아직 모르겠습니다만, 보통 하나의 View에 n개의 Scene, m개의 Layer를 사용하는 것이 보통인 것 같습니다. 카드게임(고도리처럼)같이 간단한 흐름이라면 Scene도 하나만 사용할 수 있겠죠. 그래서 cocos2d 프로그래밍 가이드에 “대부분의 시간이 Layer를 만드는 것에 쓰여지게 됩니다.”라고 써 놓은 건지도 모르겠습니다.
Delegate 선언시 필요한 프로토콜(위에서는 UIAccelerometerDelegate, UIAlertViewDelegate, UITextFieldDelegate, UIApplicationDelegate)을 상속하여주면 cocos2d에서 UI 핸들링(터치와 같은)이 발생했을 때 구현된 메소드를 호출하게 됩니다. HelloWorld에는 UI핸들링이 없어서 예제코드가 없군요. HelloAction이나 HelloEvents에서 확인하실 수 있습니다.

이번엔 여기까지만 하고요, 다음에 이벤트와 액션을 살펴보도록 하지요.