지난 포스트에서 약속했던바와 같이 이번 포스트에서는 정말 cocos2d의 기초 부분을 살짝 확인해보는 데 의미를 두는 글을 쓰도록 하겠다.  앞으로 게임을 개발 하든, 어플을 개발하든 기본, 기초만 알고 있으면, 나머지 필요한 부분은 각종 인터넷 매뉴얼을 본다거나, 샘플 코드를 보면서 따라 해도 아무런 무리가 될것이 없다.

본인 역시도, cocos2d를 사용하면서, "내가 하고 싶은것이 이런건데, 이건 대체 어떻게 해야 하나???" 고민하는 것보다, 인터넷 검색을 해서 바로 필요한 부분을 찾고, 기초적인 내용을 조합해서 해당 기능이 동작하도록 한 것이 대부분이다.

게임 개발하면서 가장 중요했던 부분이 자료구조인데, 이부분은 C/C++의 것을 그대로 사용할까 하다가, 이왕이면 Objective C의 것을 사용하고자 마음먹고, 이부분도 cocos2d를 보면서 같이 공부했는데 아무런 어려움이 없었다. 여러분들도 그러할것이다.!!

그러면, 아래 소스를 한번 보자.

// main.m file.
#import
<UIKit/UIKit.h>


int main(int argc, char *argv[]) {

    

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int retVal = UIApplicationMain(argc, argv, nil, @"AppDelegate");

    [pool release];

    return retVal;

}


위 파일은 프로젝트를 만들면 자동으로 생성되는 main.m파일인다, 우리가 이전에 C언어를 사용하면, 하나의 프로그램에 엔트리를 만드는데 보통 main함수를 만들어 썼다. objective c 도 마찬가지이다. 위에서 처럼, 자동해제해주는 메모리 풀을 만들어주고,  AppDelegate를 찾아서 실제 제어를 넘겨준다.

우리는 앞으로 main.m을 건드릴은 거의 없다. 본인은 전혀 없었다.

// AppDelegate.h file
#import
<UIKit/UIKit.h>


@class RootViewController;


@interface AppDelegate : NSObject <UIApplicationDelegate> {

UIWindow *window;

RootViewController *viewController;

}


@property (nonatomic, retain) UIWindow *window;


@end

 위 파일은 바로전에 실제 제어를 넘겨받는 AppDelegate의 헤더 파일이다. 어떤 내용이 있다 궁금하니까 한번 열어보는 정도의 의미를 두면 되는데, AppDelegate 는 NSObject를 상속받고, 그 멤버로는 window와 viewController가 있다. 아래는 @property는 나중에 쓰다보면, 알게 되지만, 간단하게 말해서 우리가 프로그래밍을 할때, 어떠한 클래스의 멤버 변수를 셋팅하거나 가져오거나 할수 있도록 만드는데, 그것을 objective C에서는 위와 같은 방법으로 선언하고, 실제 구현파일 즉 *.m 에서 @synthesize 를 이용해서 그 바디를 자동으로 구현하도록 한다. 나중에 외부 클래스에서 window라는 변수를 접근할수 있게 해주는 것이다.  이걸 setter와 getter라고 한다.

여기서 retain이라고 하는것도 굉장히 중요한데, 이부분은 나중에 따로 메모리 관리를 이야기 하면서 하나의 주제로 포스팅하겠다. 메모리 릭 나오고, 죽고 하는 문제 때문에 정말 피똥싼적있는데,. 다 저런것들 때문이었다..

그러면 이제는 실제 구현되는 부분으로 가보자.

//

//  AppDelegate.m

//  


//

//  AppDelegate.m

//  cocos2dTest

//

//  Created by Yongsu Kim on 11. 7. 11..

//  Copyright iinov.com 2011. All rights reserved.

//


#import "cocos2d.h"


#import "AppDelegate.h"

#import "GameConfig.h"

#import "HelloWorldLayer.h"

#import "RootViewController.h"


@implementation AppDelegate


@synthesize window;


- (void) removeStartupFlicker

{

//

// THIS CODE REMOVES THE STARTUP FLICKER

//

// Uncomment the following code if you Application only supports landscape mode

//

#if GAME_AUTOROTATION == kGameAutorotationUIViewController


// CC_ENABLE_DEFAULT_GL_STATES();

// CCDirector *director = [CCDirector sharedDirector];

// CGSize size = [director winSize];

// CCSprite *sprite = [CCSprite spriteWithFile:@"Default.png"];

// sprite.position = ccp(size.width/2, size.height/2);

// sprite.rotation = -90;

// [sprite visit];

// [[director openGLView] swapBuffers];

// CC_ENABLE_DEFAULT_GL_STATES();

#endif // GAME_AUTOROTATION == kGameAutorotationUIViewController

}

- (void) applicationDidFinishLaunching:(UIApplication*)application

{

// Init the window

window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

// Try to use CADisplayLink director

// if it fails (SDK < 3.1) use the default director

if( ! [CCDirector setDirectorType:kCCDirectorTypeDisplayLink] )

[CCDirector setDirectorType:kCCDirectorTypeDefault];

CCDirector *director = [CCDirector sharedDirector];

// Init the View Controller

viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];

viewController.wantsFullScreenLayout = YES;

//

// Create the EAGLView manually

//  1. Create a RGB565 format. Alternative: RGBA8

// 2. depth format of 0 bit. Use 16 or 24 bit for 3d effects, like CCPageTurnTransition

//

//

EAGLView *glView = [EAGLView viewWithFrame:[window bounds]

  pixelFormat:kEAGLColorFormatRGB565 // kEAGLColorFormatRGBA8

  depthFormat:0 // GL_DEPTH_COMPONENT16_OES

];

// attach the openglView to the director

[director setOpenGLView:glView];

// // 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");

//

// VERY IMPORTANT:

// If the rotation is going to be controlled by a UIViewController

// then the device orientation should be "Portrait".

//

// IMPORTANT:

// By default, this template only supports Landscape orientations.

// Edit the RootViewController.m file to edit the supported orientations.

//

#if GAME_AUTOROTATION == kGameAutorotationUIViewController

[director setDeviceOrientation:kCCDeviceOrientationPortrait];

#else

[director setDeviceOrientation:kCCDeviceOrientationLandscapeLeft];

#endif

[director setAnimationInterval:1.0/60];

[director setDisplayFPS:YES];

// make the OpenGLView a child of the view controller

[viewController setView:glView];

// make the View Controller a child of the main window

[window addSubview: viewController.view];

[window makeKeyAndVisible];

// Default texture format for PNG/BMP/TIFF/JPEG/GIF images

// It can be RGBA8888, RGBA4444, RGB5_A1, RGB565

// You can change anytime.

[CCTexture2D setDefaultAlphaPixelFormat:kCCTexture2DPixelFormat_RGBA8888];


// Removes the startup flicker

[self removeStartupFlicker];

// Run the intro Scene

[[CCDirector sharedDirector] runWithScene: [HelloWorldLayer scene]];

}



- (void)applicationWillResignActive:(UIApplication *)application {

[[CCDirector sharedDirector] pause];

}


- (void)applicationDidBecomeActive:(UIApplication *)application {

[[CCDirector sharedDirector] resume];

}


- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {

[[CCDirector sharedDirector] purgeCachedData];

}


-(void) applicationDidEnterBackground:(UIApplication*)application {

[[CCDirector sharedDirector] stopAnimation];

}


-(void) applicationWillEnterForeground:(UIApplication*)application {

[[CCDirector sharedDirector] startAnimation];

}


- (void)applicationWillTerminate:(UIApplication *)application {

CCDirector *director = [CCDirector sharedDirector];

[[director openGLView] removeFromSuperview];

[viewController release];

[window release];

[director end];

}


- (void)applicationSignificantTimeChange:(UIApplication *)application {

[[CCDirector sharedDirector] setNextDeltaTimeZero:YES];

}


- (void)dealloc {

[[CCDirector sharedDirector] end];

[window release];

[super dealloc];

}


@end

 
본인은 보면서 "이게 뭐지???" 라는 생각부터 들었다. 대체 어디로 제어권이 넘어가는거야!! 하고 버럭 버럭 하고 앉았었으나, 그건  iOS 어플이 어떻게 돌아가는지 몰라서 그런거였고.. 지금 보면 굉장히 심플한 것이었다, applicationDidFinishLaunching 이것이 바로, "론치가 끝나면 여기로 오세요"를 의미하는 함수이다.

사실 여기 부분도, 기존의 부분은 거의 수정할것이 없다. 본인은 추가는 생각보다 많이 했다. 나중에 소스를 하나씩 하나씩 보면서 설명하겠지만, 다른 메뉴의 전환, 레벨 조절, 점수 저장 등등등 설계자체가 너무 좀 크게 해서 그런지, 그들을 서로 연결해주는 게이트웨이가 필요했는데, 본인은 그 게이트웨이를 여기 이 delegate로 정했다. 다른 게임을 설계할때에도 하나의 파일에 전부다 구현할수가 없고, 하나의 클래스에 전부다 넣을수 없으며, cocos2d의  scene이나 layer하나에 전부 넣을수 없다. 그때 서로 다른 클래스, scene, layer간의 필요한 정보의 교환을 할수 있는 곳이 필요한데, 바로 이런곳에서 하면 된다. 


물론 그 계층 구조를 어떻게 만드냐에 따라서, layer들은 상위  scene을 통해서 하면되고, scene들만 delete를 통해서 의사소통하면 된다. 본인의 경우에는 이러한 구조로 작업을 하였으며, 만들고 나서보니 하나의 게임 프레임웍이 되어, 다른 게임을 또 하나 만들고 있다. 

위 소스에서 가장 중요한 부분은 바로 다음 부분이다.

// Run the intro Scene

[[CCDirector sharedDirectorrunWithScene: [HelloWorldLayer scene]];


이곳이 이제 실제로 cocos2d의 개념 이해가 필요한 부분인데.

위의 코드가 뜻하는 바는, "HelloWorldLayer의 Scene으로 넘어가라."이다.


이제 scene이 의미하는 것은 대체 뭔가 궁금해질것이다. 궁금해야 올바른 개발자이다. 처음에 scene의 개념을 모호하게 이해 못해서, 좀 어리 버리한짓을 많이 했는데, 나름 그 개념을 이해하고나, 현실에서 우리가 흔히 말하는 영화의 씬~, 애니메이션의 씬~, 드라마의 씬~ 바로 그것과 똑같은 것이다. 영화를 예를 들면, 촬영을 할때, 무슨씬 무슨 컷 이렇게 촬열이 들어간다면, 그 씬은 하나의 큰 주제를 의미할수 있다. 가령, 철수와 영희의 격투씬 을찍으면 그 씬은 정해져있고, 그씬안에 들어갈 내용들을 촬영하고 스토리로 나누게 되는데, 그 씬안에 들어가는 것들이 layer다.


여러분은 이제 cocos2d를 구성하는 scene과 layer의 의미를 이해했다. 물론 이론적으로정확히 설명을 하면 아래와 같이 설명할수 있다. 

Scenes

A “scene” in Cocos2d is just a special sort of node that acts as the ultimate parent for all other nodes that are visible. --> 이것이 핵심!! 

You can use this however you like, but typical usage is probably to make one scene for the “real” playable part of your game, and then use other scenes for the title page, high scores list, options, and so on. A scene is represented by the CCScene class.

A scene is “running” if it is the scene that is visible, has actions that are progressing, and so on. Only one scene can be running at a time. However, it's possible to push another scene on top of the current one, pausing the current one and running the new one; then later pop that new one and resume running the first. Or, you can have one scene entirely replace another (which is usually preferable, since it uses less memory).

This pushing/popping or replacing of scenes is done by the director (CCDirector). You've already seen this if you've poked around much in the project template. Look in the application delegate (e.g., in Lesson1AppDelegate.m or whatever it's called in your project). At the bottom of the applicationDidFinishLaunching method, you'll find a line like this:

	[[CCDirector sharedDirector] runWithScene: [HelloWorld scene]];

This tells the director to start running the given scene (HelloWorld).

To replace the running scene with another – for example, when the user taps the “Play” button on the menu screen, or when the game is over and you want to go back to the main menu – simply call replaceScene on the director:

	[[CCDirector sharedDirector] replaceScene: [SomeOtherScene scene]];

This terminates the current scenes, and starts the next one. You could later restart the first (or any other) by calling replaceScene again.

If you wanted to just pause the current scene instead, use pushScene instead of replaceScene; then you would call popScene later to terminate the new one, and resume the old. But use this sparingly, since memory on the iPhone is limited, and all scenes on the stack stay in memory.


이제는 helloWorldLayer로 가보자.

// HelloWorldLayer.h

#import
"cocos2d.h"


// HelloWorldLayer

@interface HelloWorldLayer : CCLayer

{

}


// returns a CCScene that contains the HelloWorldLayer as the only child

+(CCScene *) scene;


@end


뭐 큰내용이 없다. Layer이기 때문에, CCLayer를 상속받았고, 클래스 메소드로 scene이라는 메소드를 가지고 있다. + 표시가 바로 이 함수는 클래스 메소드 이다. 즉, "해당 클래스가 생성될때 새로 생성되는 것이 아닌(Instance가 아닌), static 메소드입니다." 라는 것이다. 해당 클래스를 생성하지 않아도 호출할수 있는 함수 정도로 이해해두자.

scene함수는 아래소스를 보면 그 body가 구현되어 있다. Scene을 하나 만들어서, 거기 밑에다가 HelloWorldLayer를 하나 만들어서 자식으로 달고, scene을 반환하도록 되어 있다. 좀 더 복잡한 소스를 나중에 보면서 좀더 자세히 설명하겠지만, 지금은 그냥 저렇게 하위에 레이어를 하나 달아주는것이구나정도로 이해하면 된다. 위에서 말했던 것처럼 Scene아래에 layer가 있으므로, 우리는 아래 코드로 Scene을 만들고,Layer도 하나 만들어서 사용할수 있게 한것이다.

//

//  HelloWorldLayer.m

//  



// 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;

}


// 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])) {

// create and initialize a Label

CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];


// ask director the the window size

CGSize size = [[CCDirector sharedDirector] winSize];

// position the label on the center of the screen

label.positionccp( size.width /2 , size.height/2 );

// add the label as a child to this Layer

[self addChild: label];

}

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


scene함수에서 [HelloWorldLayer node];이 부분이 의미하는 바는 " HelloWorldLayer의 node 메소드를 호출하라." 그러면 cocos2d의 node 함수는 아래와 같이 되어 있다.

+(id) node

{

return [[[self alloc] init] autorelease];

}

여기도 클래스 메소드인데, 바로 자기 자신을 하나 생성하고(HelloWolrdLayer클래스를 위한 공간을 만들고) init 메소드르 호출하라.(autorelease도 다음에 다루겠다.)

init 은 바로 위 소스에 나오는 것처럼, 아래와 같다.

// 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])) {

// create and initialize a Label

CCLabelTTF *label = [CCLabelTTF labelWithString:@"Hello World" fontName:@"Marker Felt" fontSize:64];


// ask director the the window size

CGSize size = [[CCDirector sharedDirectorwinSize];

// position the label on the center of the screen

label.position =  ccp( size.width /2 , size.height/2 );

// add the label as a child to this Layer

[self addChild: label];

}

return self;

}

여기서 -는 이제 일반 함수이다. instance함수.(꼭 생성후 alloc후 사용가능한 함수)

여기가 바로.. 우리가 화면에서 보던 "Hello World"를 찍어주는 부분이다. 코드의 내용의 자세한 부분은 다음에 해당 주제가 나올때 다시 이야기 하기로 하고, 여기까지 왔으면 이제 cocos2d를 이용해서, 문자열을 찍어주는 간단한 어플은 이제 만들수 있게 된다. 전광판과 같은 효과라던가, 그런 화려한 효과는 아니더라도, 내가 찍고자하는 내용을 원하는 규칙에 맞게 이제 콘트롤 가능한 것이다. 

다음 포스트는 sprite에 대해서 이야기 하도록 하겠다. cocos2d의 존재 목적이 바로 sprite이다. 우리가 게임을 만들때, 수많은 이미지들을 사용하고, 각종이펙트나, 애니매이션 효과를 구현할때에도 역시 이미지들 즉, sprite를 이용한다. 그것을 우리가 정말 빡세게 하나 하나, 장인정신으로 한땀 한땀 손수 만들수 있지만, 이미 정말 잘만들어진 솔루션이 있고, 공짜다. 그게 바로  여러분이 보고 있는 cocos2d라는것이다.

다음 주제를 최대한 빨리 준비해서 다시 포스트 하도록 하겠다. 


추신)
본 포스트들은 완전히 개인적인 목적으로 글을 쓰는 것이고, 개발기는 본인이 iOS용 게임 개발을 공부하면서 처음 개발한 BlueOcean을 개발하면서 경험한 것들을 정리하는 내용들이다. BlueOcean은 실제로 아래 링크에서 아직도 살아있다.
http://itunes.apple.com/us/app/blue-ocean/id430842838?l=ko&ls=1&mt=8







 

'코딩하고 > iOS' 카테고리의 다른 글

iOS용 게임 개발기 -5-  (0) 2011.08.10
iOS용 게임 개발기 -4.1-  (0) 2011.08.02
iOS용 게임 개발기 -4-  (0) 2011.08.01
iOS용 게임 개발기 -3.1-  (0) 2011.07.11
iOS용 게임 개발기 -3-  (0) 2011.07.11
iOS용 게임 개발기 -2-  (0) 2011.07.09
블로그 이미지

rekun,ekun 커뉴

이 세상에서 꿈 이상으로 확실한 것을, 인간은 가지고 있는 것일까?