1.8.0 프로젝트 개요
이번 장에서는 NSView와 NSImage를 이용해서 간단한 슈팅게임을 만들어 보겠습니다.

사용자 삽입 이미지
좌측과 같이 배경을 출력하고, 우주선을 좌우로 조종하고, 미사일을 발사하는  작업까지 하겠습니다.

키입력에서 다중으로 입력 받을 수가 없어 이동시에 미사일을 발사하면 이동이 중지 됩니다. 이문제는 다음 장에서 해결하도록 하겠습니다.





1.8.1 프로젝트 생성
Xcode를 실행하고 아래와 같이 SimpleShot 코코아 프로젝트를 생성합니다.
  1. 메뉴바에서 File/New Project...를 선택합니다.
  2. Application/Cocoa Application을 선택합니다.
  3. project Name에 SimpleShot를 입력하고 finish 버튼을 클릭합니다.

1.8.2 인터페이스 빌더에서 작업
1) 서브 클래스 생성

사용자 삽입 이미지
Nib 파일을 클릭하여 인터페이스빌더를 열고 NSView 클래스의 서브클래스를 만들고(아래의 창에서 NSView에 마우스 우클릭 합니다. 그 다음 Subclass NSView 클릭) 이름을 StageView로 변경합니다


사용자 삽입 이미지
StageView를 우클릭 하고 좌측과 같이 Create Files for StageView를 클릭하여 소스파일을 생성합니다.





2) NSView 생성 및 속성 설정

윈도우를 열고 팔레트의 Containers 항목에서  CustomeView를 드래그하여 아래와 같이 윈도우에 배치 합니다. View의 크기를 300X320으로 변경하고,  Custom Class 항목에서 StageView로 선택합니다.
사용자 삽입 이미지사용자 삽입 이미지
(클릭하면 이미지가 확대됩니다.)

1.8.3 이미지 파일

1) 이미지 생성

이제 배경, 우주선, 미사일에 쓰일 이미지를 준비합니다. 배경은 상관없지만 우주선과 미사일은 윤곽선이외에는 투명색으로 보여야 하기 때문에 png를 사용했습니다.

사용자 삽입 이미지
저는 도스의 디럭스페인트 이후로는 그래픽 작업이 불가능하므로 초등학생인 아들에게 이미지를 부탁하였습니다. 그래픽툴들을 다루실 수 있는 분들은 새로 만드셔도 되고, 아니면 첨부파일에서 다운로드 받으셔서 사용하시면 됩니다.

좌측은 배경화면 이미지입니다. StageView와 같은 300X320의 크기를 가지고 있습니다.



사용자 삽입 이미지
우주선 이미지 입니다. 하얗게 보이는 부분을 투명하게 처리해야 하단의 배경화면에서 올바르게 보입니다. 이를 위해 png파일 포맷을 사용했습니다.

사용자 삽입 이미지
미사일 이미지 입니다. 위와 같이 png파일 포맷을 사용했습니다.

2) 이미지 등록

사용자 삽입 이미지
준비된 이미지 파일을 드래그하여 Xcode 좌측의  Group & Files 목록의 Resources에 가져다 놓습니다.



드래그 시 나오는 판넬에서 아래와 같이 Copy items... 항목의 체크를 확인합니다.
사용자 삽입 이미지

1.8.4 소스코드 작성

1) StageView.h 수정

#import <Cocoa/Cocoa.h>

@interface StageView : NSView
{
    NSTimer *timer;
    NSImage *backgroundImage;
   
    NSRect heroRect;
    NSImage *heroImage;

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
}

- (void)processGame;
- (void)fireMissile;
- (void)keyDown:(NSEvent *)event;

@end


2) StageView.m 수정

#import "StageView.h"

#define SCREEN_WIDTH        300
#define SCREEN_HEIGHT        320

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 총알발사 속도 */

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        // Add initialization code here
       
        isFire = NO;
       
        /* 배경 이미지 로드 */
        NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
        backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

        /* 우주선 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"hero" ofType:@"png"];
        heroImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        heroRect.size = [heroImage size];
        heroRect.origin = NSMakePoint((SCREEN_WIDTH - heroRect.size.width)/2, 0);
       
        /* 미사일 이미지 로드 & 초기 좌표 설정 */
        imageName = [[NSBundle mainBundle] pathForResource:@"missile" ofType:@"png"];
        missileImage = [[NSImage alloc] initWithContentsOfFile:imageName];
        missileRect.size = [missileImage size];
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];
    }
    return self;
}

- (void) dealloc
{
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
       
    [super dealloc];
}

/* 키입력을 받기 위한 설정 */
- (BOOL)acceptsFirstResponder
{
    return YES;
}

- (void)keyDown:(NSEvent *)event
{
    int keyCode;
    
    /* 현재 눌려진 키값을 얻어 온다. */
    keyCode = [event keyCode];
   
    /* 좌측으로 이동 */
    if(keyCode == 123)
    {
        heroRect.origin.x -= HERO_SPEED;
        if(heroRect.origin.x < 0)
            heroRect.origin.x = 0;
    }

    /* 우측으로 이동 */
    if(keyCode == 124)
    {   
        heroRect.origin.x += HERO_SPEED;

        if(heroRect.origin.x >= SCREEN_WIDTH - heroRect.size.width)
            heroRect.origin.x = SCREEN_WIDTH - heroRect.size.width;
    }
   
    /* 발사(스페이스) */
    if(keyCode == 49)
    {
        [self fireMissile];
    }
}       

- (void)fireMissile
{
    if(isFire == NO)
    {
        /* 미사일이 처음 발사되었을 경우에 초기위치를 우주선 좌표로 설정 */
        missileRect.origin = NSMakePoint(heroRect.origin.x + heroRect.size.width/2 - missileRect.size.width/2, heroRect.size.height);
    }
    isFire = YES;
}

- (void)processGame
{
    if(isFire == YES)
    {
        /* 미사일이 발사중이면 y좌표를 이동 */
        missileRect.origin.y += MISSILE_SPEED;
        if(missileRect.origin.y >= SCREEN_HEIGHT)
        {
            /* 화면상단에 위치했을 경우에는 미사일을 출력하지 않는다 */
            isFire = NO;
        }
    }
   
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)rect
{
    NSRect imgRect;
    NSRect drawRect;
   
    /* 배경 이미지 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [backgroundImage size];
   
    drawRect = [self bounds];
   
    [backgroundImage drawInRect:drawRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];
   
    /* 우주선 출력 */
    imgRect.origin = NSZeroPoint;
    imgRect.size = [heroImage size];
   
    [heroImage drawInRect:heroRect
               fromRect:imgRect
              operation:NSCompositeSourceOver
               fraction:1.0];   
   
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                     fromRect:imgRect
                    operation:NSCompositeSourceOver
                     fraction:1.0];   
    }   
}

@end

#define HERO_SPEED            3       /* 우주선 속도 */
#define MISSILE_SPEED        4       /* 미사일발사 속도 */

한 프레임에서 우주선과 총알의 움직이는 속도를 설정합니다. 좌/우 방향키를 누르면 각각의 방향으로 우주선이 한 프레임당 3픽셀을 움직입니다. 미사일은 4픽셀씩 상단으로 이동합니다. 숫자를 바꾸어 주면 해당 오브젝트의 속도를 변경할 수 있습니다.

StageView가 생성될 때 자동으로 호출되는 - (id)initWithFrame:(NSRect)frameRect 메소드에서 이미지 파일을 로드하고 타이머를 세팅하는 작업을 합니다.

NSString* imageName = [[NSBundle mainBundle] pathForResource:@"background" ofType:@"png"];
backgroundImage = [[NSImage alloc] initWithContentsOfFile:imageName];

어플리케이션의 리소스 디렉토리에서 background.png 이미지 파일을 로드하고 NSImage를 생성 합니다. 우주선과 미사일 이미지를 차례로 로드합니다.

timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];

1/30초 마다 processGame 함수를 호출하도록 타이머를 설정합니다. processGame 함수에서는 [self setNeedsDisplay:YES];로 설정하여 DrawRect가 호출되어 이미지를 출력하게 합니다.
이제 컴파일을하고 실행을 합니다. 좌우 방향키로 우주선을 움직이며 스페이스키로 미사일을 발사할 수 있습니다. 미사일은 현재 한발씩만 발사 할 수 있습니다. 다음 장에서는 적기들을 만들고 움직여 보도록 하겠습니다.