저에게 많은 도움이 된 서적들을 정리해 보았습니다. 연식이 오래되다 보니 이전에 열심히 보았던 책들은 잘 기억도 안나고 절판된 것들이 대부분이네요.

초기에는 게임 프로그래밍을 위한 최적화, 알고리즘, 실제 적용할 수 있는 소스가 있는 책들을 많이 보았는데, 나이가 조금 드니 개발 방법론이나 수필 형식의 가볍게 읽을 수 있는 책들을 많이 보게 되네요. 특정 언어에 관한된 책들은 제외하고 비교적 가볍게 읽을 수 있는 책들로 골라 보았습니다.

요 몇년 사이에는 생업으로 바쁘다는 핑계로 프로그램 관련 서적을 멀리 하였는데, 코코아 프로그래밍을 배우면서 근래에 나온 책들을 조금씩 읽어 볼려고 합니다.

(책의 이미지는 yes24에서 가지고 왔습니다.)

사용자 삽입 이미지
- 리팩토링
저자: 마틴 파울러, 역자: 윤성준, 조재박,  출판: 대청
이 책은 구입하지는 않았지만, 이전에 다니던 회사의 책꽂이에 있어 날림으로 읽어 본 책입니다. 당시 저는 일단 윗 분들과 빡빡한 기간에 맞추기 위해 프로토타입으로 대충 만들어 놓고, 나중에 시간있을 때 생각해 놨던 방법과 대충 만들 때의 시행착오를 참고해 소스를 자주 뒤업었었는데, 저에게 많은 방법을 알려주고 생각 하게 해준 책이었습니다.


사용자 삽입 이미지
- 누가 소프트웨어의 심장을 만들었는가
저자: 박지훈, 출판: 한빛미디어
컴퓨터, 프로그래밍 등의 관련 서적에서 자주 언급되는 전설적인 분들에 관한 내용입니다. 엘런튜닝을 시작으로 마크 앤드리슨까지 인물에 대해 조명하고 업적에 대해 설명되어 있습니다.  축구선수라면 펠레나 요한 크라이프의 이름은 알고 있듯이, 프로그래머라면 이 책에 나오는 인물들의 이름은 알고 있어야 하지 않을까요.



사용자 삽입 이미지
- 조엘 온 소프트웨어
저자: 조엘 스폴스키, 역자: 박재호, 이해영, 출판:에이콘
나온지 시간이 꽤 된 책이 지만 최근에 읽고 있는 책입니다. 화장실에 가져다 놓고 틈틈이 편한게 읽고 있습니다. 프로그래밍에 대한 어련고 무거운 책은 아니고, 부담없이 읽기에 재미있는 책입니다.

개인적으로는 감명 깊은 책은 아니지만 하도 많은 분들이 추천 하는 책이라서 올려 보았습니다.


사용자 삽입 이미지
- Writing solid code
저자: 스티브 맥과이어, 역자: 나윤석, 이을재, 출판: 높이깊이
버그 없는 코드와 디버깅의 기술을 알려 주는 좋은 책입니다. 당시에 열심히 읽었는데 assert를 많이 쓰라는 것 빼고는 내용이 잘 기억이 나지 않네요. 그래도 무의식적인 코딩습관에 많이 배여있을 거라 생각됩니다.




사용자 삽입 이미지
- 프로그래밍 윈도우즈
저자: 찰스 페졸드, 역자: 김선우, 출판: 한빛미디어
Win16 시절부터 윈도우 프로그래밍의 교과서라고 불리우는 책입니다.  당시에는 페쫄드 책으로 불리웠던 기억이 나네요.  VC++/MFC로 인해 이전 만큼의 명성은 누리지 못하는 것 같지만, 윈도우 프로그래밍의 기초를 알게 해주는데 가장 좋은 책 같습니다.



사용자 삽입 이미지
- Code complete
저자: 스티브 맥코넬, 역자: 서우석, 출판: 정보문화사
저는 프로그래밍에 관한 서적 중 이 책을 항상 1순위로 추천합니다. 프로그래밍에 익숙해 지면 익숙해 질 수록 읽을 때마다 새롭게 다가옵니다. 언어에 익숙해지면 반드시 읽고 넘어가야 할 책으로 생각됩니다. 혹시 이 책을 아직도 보시지 않았다면 반드시 구입해 보세요.



사용자 삽입 이미지
- 유닉스 네트워크 프로그래밍
저자: W. 리차드 스티븐스, 역자: 김치하, 이재용, 출판: 교보문고
이 책이 처음 번역되었을 때 본 분들은 패킷을 보쌈으로 번역한 역자의 무리한(?) 번역에 원서 보다 더 이해하기가 힘들었을 겁니다. 두번째 번역판에서는 많이 나아졌는데, 고집은 버리시지 않았더군요. 소켓, 유닉스 시스템, 네트워크 프로그램을 배우는데 교과서 적인 책입니다. 요새는 다른 좋은 책들도 많이 나와 있는 것 같습니다.


사용자 삽입 이미지
- 성공과 실패를 결정하는 1%의 프로그래밍 원리
저자: 히사오 야자와, 역자: 예승철, 출판: 성안당
메모리, CPU등의 하드웨어 및 시스템에 관한 내용을 그림들과 함께 쉽게 설명해준 책입니다. 프로그래밍에 대한 깊은 이해를 위한 토대를 다질 수 있습니다.  오래전 절판된 Inside IBM PC이란 명서의 현대판입니다.




사용자 삽입 이미지
- 소프트웨어 비즈니스
저자: 에릭싱크, 역자: 이성희, 송흥욱, 출판: 사이텍미디어
이 책은 프로그래밍과는 직접적인 관련이 없는 책 입니다. 소규모의 소프트웨어 회사를 창립하고 운영하는데 준비하고 필요한 사항들을 저자의 경험에 비추어 설명 해 놓은 책입니다. 개발자로서의 정년이 길지 않은 우리나라의 여건에서 소규모 소프트웨어 창업을 꿈꾸거나 꼭 창업을 하지 않더라도 개발자들에게도 많은 도움이 되는 책입니다.



AND

올해 5월 초. 맥 프로그래밍을 공부하기로 마음먹고 저에게 동기를 부여하기 위해  티스토리에서 이 블로그를 만들었습니다.

방문객이 많지 않은 블로그이지만, 이제 통계를 낼 수 있을 정도의 방문자 수가 되어 간단히 방문자 통계를 정리를 해 보았습니다. 아래는 구글 analytics에서 확인한 통계들입니다. 아직도 원인을 찾지 못하고 있지만, 티스토리 자체에서 로봇을 제외한 방문객 수에서 반 또는 2/3 정도만 통계로 잡히고 있습니다.

혹시 http://cocoadev.tistory.com으로 도메인을 설정해 cocoadev.co.kr로 들어 오는 방문객 수가 안잡히나 했는데, 확실하지는 않지만 그건 아닌 것 같고... 방문객 수에 그다지 신경을 쓰지 않으니 일단은 무시하고 수가 더 적은 구글 통계를 믿기로 했습니다.

1. 종합
사용자 삽입 이미지
150 일 정도 운영하면서 총 방문자 수는 8,554명입니다. 하루 평균 방문자가 57명 정도 됩니다. 이 블로그의 방문자는 대부분 검색엔진을 통해 들어오는데, 검색 키워드를 보면 이 중에서 2/3 정도만 원하는 것 또는 유사한 것을 맞게 찾아 온 것 같습니다. 다른 분들은 잘 못 온 것입니다. 검색 키워드 중에선 야동 맛보기가 인상 깊네요.

주로 유입되는 키워드는 Xcode, 타자게임, Objective-C, 계산기 프로그램, 맥 게임 프로그래밍, iterm 한글 등입니다.
 
위의 그래프를 보면 알 수 있 듯이, 방문객 수는 포스팅이 거의 없었던 9월을 제외 하고는 크게 늘지도 줄지도 않고 꾸준히 적습니다. ^^;;

2. 유입경로
사용자 삽입 이미지
역시 naver가 압도적으로 많습니다. 이는 초기 계산기 프로그램이란 검색어로 네이버에서 많은 사용자가 유입되어서 그렇습니다. 현재(9월~)는 구글에서의 검색이 더 앞서고 있습니다.

그런 이유로 해서 네이버에서 온 방문객들은 반송율이 84.16%로 가장 높습니다. 이는 계산기 프로그램을 구하러 왔다가 요구와 맞지 않기 때문에 바로 나간 경우인 것 같습니다.

반대로 직접 찾아 오거나 구글, 올블로그와 맥 관련 커뮤니티에서 온 방문객들은 반송율이 상대적으로 낮고 방문시간이 높았습니다.
 
3. OS/부라우저
사용자 삽입 이미지
Windows 사용자가 역시 가장 많지만 맥관련 블로그라 맥 사용자가 26% 정도 입니다. 이 역시 초기 네이버 검색으로 인한 결과이며, 현재는 맥 사용자가 40%를 넘습니다.

사용자 삽입 이미지
역시 익스플로어 사용자가 많으며 파이어폭스와 사파리 사용자들이 15%씩 사이좋게 차지하고 있습니다.  현재는 익스플로어가 52%, 사파리가 25%, 파폭이 18%, 오페라가 3% 정도입니다.

4. 기타
사용자 삽입 이미지
좌측은 티스토리 관리자 페이지에서 본 통계입니다. 40개의 포스트가 있습니다. 평균 4일에 한번씩 포스팅을 하고 있습니다.

댓글 83개, 방명록 20개.

제가 답변한 것을 빼면 저 수치의 반 밖에는 안되지만 이 블로그를 운영할 수 있도록 많은 동기부여와 보람, 즐거움을 주신 고마운 댓글/방명록입니다.

위에서 언급한 바와 같이 구글과는 틀리게 전체가 만오천명으로 나와있습니다. 로봇을 제외 시켜 놓았는데 완벽하게 제거를 못하는 것 같습니다.


블로그를 하시는 다른 분들에 비해 비교도 안되게 작은 수치지만 그래도 방문해 주시는 분들이 계시고,  블로그를 운영한지 어느 정도 지나고 해서 한가한 주말 오후를 이용해 간단히 정리 해 보았습니다.

방문해 주시고 좋은 말씀 해주신 많은 분들께 깊은 감사를 드립니다.



AND

사용자 삽입 이미지
이번 장에서는 적기들을 출력하고 동작하게 하는 작업을 해보겠습니다.

적기는 좌측과 같이 세 종류로 상단에 위치해 있으며 인베이더 게임과 같이 좌우로 움직이며 아래로 내려 옵니다.

마지막 적기가 화면에서 사라지면 다음 스테이지로 넘어가며, 스테이지가 진행될 수록 적기의 움직임이 빨라 집니다.




1.8.5 소스 파일 추가

1) global.h

게임에서 사용되는 여러 속성들의 공유를 위해 global.h 헤더파일을 작성합니다. XCode의 메뉴에서 File/New File을 클릭한 후, BSD/Header File을 선택하고 [Next] 버튼을 클릭합니다. 파일명을 global.h로 입력하고  [Finish] 버튼을 클릭합니다.

이제 에디터에서 global.h를 열어서 아래와 같이 내용을 입력합니다.
#define SCREEN_WIDTH        300        /* 화면 너비 */
#define SCREEN_HEIGHT       320        /* 화면 길이 */

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

#define MAX_ENEMY1            5        /* 적기1 갯수 */
#define MAX_ENEMY2            3        /* 적기2 갯수 */
#define MAX_ENEMY3            1        /* 적기3 갯수 */

#define ENEMY_DOWN            20        /* 적기 하강 픽셀 */

이전 StageView.m에 있던 SCREEN_WIDTH및 위와 중복되는 define된 부분 (검은색)을StageView.m에서 삭제 합니다.  그리고 StageView.m 상단에 아래와 같이 global.h를 임포트 합니다.
 
#import "global.h"
#import "StageView.h"


2) Enemy 오브젝트 추가

최대한 간단하게 만들기 위해 class 추가 없이 StageView.m에서 다 해결 할라고 했는데, 적기 때문에 너무 복잡해 질 것 같아 Enemy 클래스를 추가하기로 하였습니다.
 
XCode의 메뉴에서 File/New File을 클릭한 후, Cocoa/Objective-C class를 선택하고 [Next] 버튼을 클릭합니다. 파일명을 Enemy.m으로 입력하고  [Finish] 버튼을 클릭합니다.

설명은 소스에 간단한 주석으로 대치 합니다.

Enemy.h
#import <Cocoa/Cocoa.h>

@interface Enemy : NSObject {
    int type;
    int energy;
    int state;
   
    NSImage* image;
    NSPoint position;
}

- (id)initWithImage:(NSImage*)img
               type:(int)t;

- (int)state;
- (void)setState:(int)s;
- (void)setPosition:(NSPoint)pos;

- (void)down;
- (BOOL)moveAndDisplay:(int)dir;

@end
type과 energy는 아직 사용하지 않습니다.

Enemy.m
#import "global.h"
#import "Enemy.h"

@implementation Enemy

/** 적기 타입과 이미지를 설정 */
- (id)initWithImage:(NSImage*)img
           type:(int)t
{
    [super init];
   
    type = t;
    image = img;

    return self;
}

- (void)setPosition:(NSPoint)pos
{
    position = pos;
}

- (int)state
{
    return state;
}

- (void)setState:(int)s
{
    state = s;
}

/** 적기를 한단계 아래로 옮기고, 화면에 안 나올 경우에는 상태를 0으로 변경한다. */
- (void)down
{
    position.y -= ENEMY_DOWN;
    if(position.y + [image size].height < 0)
        state = 0;
}

- (BOOL)moveAndDisplay:(int)dir
{
    BOOL isChange = false;
    NSRect drawRect;
    NSRect imgRect;
       
    drawRect.size = [image size];
   
    /** 적기를 이동하고 좌우 화면의 경계를 넘었을 경우에는 아래로 이동하도록 isChange를 1로 세팅 해서 반환. */
    position.x += dir;
    if(position.x < 0)
    {
        isChange = true;
    }   
    if(position.x >= SCREEN_WIDTH - drawRect.size.width)
    {
        isChange = true;
    }
   
    /** 적기를 출력한다. */
    drawRect.origin.x = position.x;
    drawRect.origin.y = position.y;
   
    imgRect.origin = NSZeroPoint;
    imgRect.size = [image size];
   
    [image drawInRect:drawRect
                       fromRect:imgRect
                      operation:NSCompositeSourceOver
                       fraction:1.0];   
   
    return isChange;
}

@end


1.8.6 StageView 변경
다음은 스테이지를 설정하고, 적기를 출력 하기 위해 Stageview 클래스를 변경해 보겠습니다. 스테이지가 증가할 수록 적기의 이동속도가 빨라 집니다.

StageView.h
/* StageView */

#import <Cocoa/Cocoa.h>

@class Enemy;

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

    BOOL isFire;
    NSRect missileRect;
    NSImage *missileImage;
   
    int curStage;
    int enemyDir;

    NSImage *enemyImage1;
    NSImage *enemyImage2;
    NSImage *enemyImage3;
   
    Enemy *Enemy1[MAX_ENEMY1];
    Enemy *Enemy2[MAX_ENEMY2];
    Enemy *Enemy3[MAX_ENEMY3];
}

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

@end

StageView.m
#import "global.h"
#import "StageView.h"
#import "Enemy.h"

@implementation StageView

- (id)initWithFrame:(NSRect)frameRect
{
    if ((self = [super initWithFrame:frameRect]) != nil) {
        /* 배경 이미지 로드 */
        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);
       
        /* 적기1 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy1" ofType:@"png"];
        enemyImage1 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기2 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy2" ofType:@"png"];
        enemyImage2 = [[NSImage alloc] initWithContentsOfFile:imageName];
       
        /* 적기3 이미지 로드 */
        imageName = [[NSBundle mainBundle] pathForResource:@"enemy3" ofType:@"png"];
        enemyImage3 = [[NSImage alloc] initWithContentsOfFile:imageName];
               
        int i;
       
        /** 적기 오브젝트 생성 */
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            Enemy1[i] = [[Enemy alloc] initWithImage:enemyImage1
                                                type:1];
        }
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            Enemy2[i] = [[Enemy alloc] initWithImage:enemyImage2
                                                type:2];
        }
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            Enemy3[i] = [[Enemy alloc] initWithImage:enemyImage3
                                                type:3];
        }
       
        /** 1 스테이지 설정 */
        curStage = 0;
        [self setStage];
       
        /* 30프레임으로 타이머 설정 */
        timer = [[NSTimer scheduledTimerWithTimeInterval: (1.0f / 30.0f)
                                                  target: self
                                                selector:@selector(processGame)
                                                userInfo:self
                                                 repeats:true] retain];       
    }
    return self;
}

/** 할당된 메모리 반환 */
- (void) dealloc
{
    int i;
   
    [backgroundImage release];
    [heroImage release];
    [missileImage release];
   
    [enemyImage1 release];
    [enemyImage2 release];
    [enemyImage3 release];

    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] release];
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] release];
    }
   
    [super dealloc];
}

- (void) setStage
{
    int i;
    NSPoint pos;
   
    /* 현재 스테이지를 1 증가 시킨다. */
    curStage++;
       
    /* 적기의 좌/우 움직이는 속도와 방향 설정. */
    enemyDir = curStage;

    isFire = NO;
   
    /** 적기들의 초기 위치와 상태를 설정한다. */
    pos.x = 45;
    pos.y = 190;
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        [Enemy1[i] setPosition:pos];
        [Enemy1[i] setState:1];
   
        pos.x += 45;
    }

    pos.x = 78;
    pos.y = 225;
    for(i = 0; i < MAX_ENEMY2; i++)
    {
        [Enemy2[i] setPosition:pos];
        [Enemy2[i] setState:1];
   
        pos.x += 55;
    }

    pos.x = 122;
    pos.y = 265;
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        [Enemy3[i] setPosition:pos];
        [Enemy3[i] setState:1];
   
        pos.x += 80;
    }
}

/** 키입력을 받기 위한 설정 */
- (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];   
   
   
    int i;
    int isChange = 0;
    int enemyCount = 0;
   
    /* 적기들을 출력하고 좌우 경계선을 넘어간 적기가 있을 경우를 체크한다. */
    for(i = 0; i < MAX_ENEMY1; i++)
    {
        if([Enemy1[i] state])
        {
            enemyCount++;
            isChange |= [Enemy1[i] moveAndDisplay:enemyDir];
        }   
    }

    for(i = 0; i < MAX_ENEMY2; i++)
    {
        if([Enemy2[i] state])
        {
            enemyCount++;
            isChange |= [Enemy2[i] moveAndDisplay:enemyDir];
        }   
    }
   
    for(i = 0; i < MAX_ENEMY3; i++)
    {
        if([Enemy3[i] state])
        {
            enemyCount++;
            isChange |= [Enemy3[i] moveAndDisplay:enemyDir];
        }
    }
   
    /** 남은 적기가 없을 경우에는 다음 스테이지로 넘어 간다. */
    if(enemyCount == 0)
    {
        [self setStage];
        return;   
    }
       
    if(isFire == YES)
    {
        /* 미사일 출력 */
        imgRect.origin = NSZeroPoint;
        imgRect.size = [missileImage size];
       
        [missileImage drawInRect:missileRect
                        fromRect:imgRect
                       operation:NSCompositeSourceOver
                        fraction:1.0];   
    }
   
    /** 적기가 좌우측의 경계를 넘었을 경우에는 아래로 이동한다. */
    if(isChange)
    {
        enemyDir *= -1;
        for(i = 0; i < MAX_ENEMY1; i++)
        {
            if([Enemy1[i] state])
                [Enemy1[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY2; i++)
        {
            if([Enemy2[i] state])
                [Enemy2[i] down];
        }
       
        for(i = 0; i < MAX_ENEMY3; i++)
        {
            if([Enemy3[i] state])
                [Enemy3[i] down];
        }
    }
}
@end

이번장에서 사용된 이미지와 전체 프로젝트 파일은 아래에서 다운 받으실 수 있습니다.

다음장에서는 충돌 검사, 스테이지/점수/에너지 표시등을 넣고 몇 가지 사항을 수정하여 초 간단한 게임을  완료해 보겠습니다.

AND