요즘은 어디 나갈 때 핸드폰보다 터치를 더 챙기게 되었습니다. NDS도 있지만 아기자기한 게임들을 기울기와 터치로 즐기는 색다른 재미에, 가족들 사이에서도 터치의 게임이 훨씬 더 인기가 좋습니다. 제가 요즘 터치에서 가장 즐겨하는 게임은 Smallware에서 나온 Sol Free라는 카드게임입니다. (유료지만 무료 버젼도 있습니다.) 화장실에도 들고 갑니다. ^^;;


이전에 윈도우에서도 심심풀이로 많이 했지만, 터치에서는 직접 손으로 카드를 옮기고 넘기는 손맛때문에 한층 재미가 더 한 것 같습니다. 컴퓨터의 마우스로는 이 맛이 느낄 수 없어 재미가 없습니다.

다음으로 Rocking Porket Games에서 나온 Blue Skies란 게임도 간혹 하고 있습니다. 유료게임이지만 무료로된 lite 버젼을 설치하였습니다. 대공과 대지 공격을 하는 헬리콥터를 조정하여 적들을 찾아 파괴하는 게임입니다. 기울여서 방향을 조종하기 때문에 조금 반응이 늦는 감은 있지만, 헬리콥터의 움직임과는 잘 어울리는 것 같습니다.

그리고 오늘 App 스토어에서 또 다른 재미있는 게임을 찾았습니다. 작년 초에 아이맥에서 가끔 하고 저의 다른 블로그에서도 소개했던 3D 슈팅게임인 sauerbraten이 아이폰, 터치용으로 포팅이 되어 sauerbraten의 이전 이름인 Cube란 게임으로 나왔습니다.


위는 터치에서 캡쳐한 게임화면입니다. 터치를 돌리면서 진행 방향을 정하고 아래와 같이 외각을 터치해서 움직이고 총을 쏘고 점프를 할 수 있습니다.


이전에 컴퓨터에서 했던 게임을 똑같이 터치에서 할 수 있다는게 신기하긴 하지만, 컨트롤이 터치에서 제가 하기엔 너무 어려운 것 같습니다. 익숙해지면 좀 나아질지 모르겠습니다. PC용 버젼은 전체소스가 공개되어 있는데 터치용 버젼의 소스는 공개가 되지 않은 것 같습니다.

한번씩 App 스토어를 들어가면 이런 재미있는 공짜 게임들이 많이 올라오니, 사용자 입장에선 참 행복하고 고마운 일입니다. 이제는 초기에 비해서 완성도가 꽤나 높은 게임들이 무료나 lite 버젼으로 많이 올라오고 있는 것 같습니다.

애플은 아이폰 개발자 유니버시티 프로그램을 추가하여 대학생들이 공부를 목적으로 무료로 아이폰 어플리케이션을 사용하고 기기에서 테스트 해 볼 수 있는 지원을 하고 있습니다. 이와 마찬가지로 공개 소프트웨어 개발자에게도 무료 개발자 프로그램 등록과 같은 혜택을 주었으면 좋겠습니다.

사용자 삽입 이미지
최근에 애플 사이트에서 다운로드 받아서 시작한 게임입니다. 애플에서 링크된 파일은 인텔맥 전용으로 윈도우 또는 PPC는 핑구스 홈페이지에 가서 각각의 버젼을 다운로드 받으시면 됩니다. 팽귄을 보시면 짐작하시겠지만 GNU GPL을 따르는 무료 게임입니다.

나이가 좀 있으신(?) 분들은 오래전 도스에서 재미있게 플레이 하던 레밍스를 기억하실 것입니다. 해당 사이트의 소개를 보면 1998년 부터 레밍스 게임의 무료 버젼을 만들기 위해서 시작된 프로젝트라고 합니다. 레밍스와 같이 이게임도 팽귄들에게 적당한 기술을 지정하여 난관을 뚫고 집으로 돌아가도록 하는 게임입니다.
 
치 후에 게임을 실행하면 해상도를 설정하는 창이 뜹니다. 적당한 해상도 또는 전체화면을 선택하고 Play 버튼을 클릭합니다. 전체화면이 아닐 경우 800X600에서 최적화 되어 보입니다. 창 크기에 맞게 확대/축소를 하지 않아 800X600이하에서는 화면이 잘려 나오고 그 이상에서는 빈 공간이 나옵니다. 아래는 시작화면 입니다.
사용자 삽입 이미지


작하면 'Tutorial Island' 레벨부터 시작됩니다. 패키지 내부를 보니 levels 디렉토리 밑에 'tutorial'외에 'halloween', 'volcano', 'wip', 'playable'등의 디렉토리가 있습니다. 아마'Tutorial Island'를 클리어 하면 다른 레벨들도 플레이할 수 있을 것 같은데 확실하지는 않습니다. 10년동안 진행된 프로젝트가 아직도 0.7 버젼을 달고 있는 것을 보면 안될 수도 있다는 생각이 듭니다. 튜토리알을 완료해서 확인해보고 포스팅에 추가 하겠습니다.
사용자 삽입 이미지


단 좌측의 'Show Story?' 버튼을 클릭하면 스토리가 소개됩니다. 다 읽지는 않았지만 기온이 따뜻해져 팽귄이 이주 한다는 이야기 인 것 같습니다. 대부분 사람들이 케릭터가 팽귄이고 대규모로 몰려 다녀야 한다면 요새 지구 온난화도 문제가 되니 비슷한 스토리를 생각해 낼 것 같습니다. 스토리에 동화되기 쉽습니다. ^^;;
사용자 삽입 이미지


임이 시작된 모습니다. 좌측에 보이는 팽귄 모양의 버튼들이 각 스테이지에서 사용할 수 있는 팽귄의 기술들입니다. 벽을 파고, 바닥을 뚫고, 자폭하고, 다른 팽귄들이 못가도록 막고, 다리를 놓고, 점프하는 등의 기술을 선택할 수 있으며, 옆의 숫자는 기술을 사용할 수 있는 남은 횟수입니다.

마우스 왼쪽 버튼은 기술이나 해당 팽귄을 선택할 수 있고, 오른쪽 버튼을 클릭한 채로 드래그 하면 맵의 다른 영역으로 이동합니다.
사용자 삽입 이미지

사용자 삽입 이미지
우측 하단에 있는 이 버튼은 게임을 잠시 중지(Pause) 시킬 때 사용합니다.



사용자 삽입 이미지
이  버튼은 빠른 속도로 게임을 진행시킬 때 사용합니다. 팽귄의 진로가 확보되었으면 이 버튼을 클릭하여 빠르게 해당 스테이지를 종료 할 수 있습니다.


사용자 삽입 이미지
이 버튼을 클릭하면 화면상의 모든 팽귄이 자폭 합니다. 게임을 종료할 경우나 진로를 막는 기술(Blocker)이 적용되어 정지된 팽귄들을 제거하여 다음 스테이지로 진행해야될 경우에 사용합니다.

사용자 삽입 이미지
좌측 하단에는 전체 맵의 모습과 현재 위치를 확인할 수 있는 미니맵이 있습니다. 스타크래프트와 동일한 위치에 있어 확인이 쉽습니다.



임 시작 화면에서 Editor를 클릭하면 아래와 같이 스테이지 에디터로 사용자가 직접 스테이지를 작성할 수 있습니다.
사용자 삽입 이미지

* 2008.07.24 추가
초기화면에서 Levelsets란 메뉴를 그냥 넘겨 보았는데, 다시 해보니 이 메뉴가 정식 레벨인 것 같습니다. 클릭하면 Halloween 2007이란 레벨을 선택할 수 있습니다. 8개의 스테이지가 있고 잠깐 해 보았는데 역시 튜토리얼보다 어려운 것 같습니다. 플레이를 더 해보고 내용을 보충하겠습니다. 
사용자 삽입 이미지사용자 삽입 이미지사용자 삽입 이미지

캐릭터도 귀엽고 레밍스와 게임방법도 동일하고 텀블벅스에 이어 다시 한번 즐길 수 있는 게임을 찾은 것 같습니다.

작년 여름에 마지막 12 스테이지로 들어 간지 10여개월만에 드디어 클리어 했습니다(작년 포스팅). 매일 점심먹거나 머리아플 때등 보통 서너판씩 꾸준히 했는데 오늘 드디어 감격적인 엔딩을 보았습니다.

사용자 삽입 이미지

한 일년을 이 녀석과 함께 재미있게 보냈으니 기쁘기도 하고 섭섭하기도 하네요. 이제 슬슬 다른 게임을 찾아 보아야 할 것 같습니다. 요런 아기자기하고 재미있는 맥용 게임 추천해 주실 분 안계신가요?

'이야기들 > 소소한 이야기' 카테고리의 다른 글

스크래치 강좌 끝~  (8) 2008.07.28
5년만에 강림하신 지름신  (16) 2008.07.15
Tumblebugs 드디어 클리어!  (2) 2008.06.18
맥북 DVI 어댑터  (2) 2008.06.16
휴~ 아이폰 3G  (4) 2008.06.10
맥세이프 전원 아답터  (6) 2008.05.20

이번 장에서는 영어 단어를 맞추는 간단한 퀴즈게임을 만들어 보겠습니다. 스크래치 케릭터가 설명하는 내용을 보고 1~4번까지의 보기중에서 정답에 해당하는 번호를 선택하는 게임입니다. 5단계까지 진행되며 정답이면 score가 1점 올라 갑니다.

사용자 삽입 이미지

1. 변수 만들기

사용자 삽입 이미지
좌측과 같이 Variables의 [Make a variable] 버튼을 클릭하여 answer, score, step 3개의 변수를 만듭니다.

answer는 사용자가 선택한 1~4의 값 중 하나를 저장하는 용도록 사용합니다.

score는 점수로 정답과 일치할 때만 1씩 증가됩니다.

step은 현재 진행 단계를 나타내며 총 5개의 문제까지 진행됩니다.

score와 step만 체크 표시를 하여 사용자가 볼 수 있도록 합니다.




2. 사용자 입력 처리

1) 초기화
현재 단계(step)를 1로 점수(score)를 0으로 설정하고, 첫번째 문제인 "A round fruit..."을 출력합니다. change 메시지를 전송하여 첫번째 답들이 출력되도록 합니다.
 
사용자 삽입 이미지


2) 사용자 입력 처리
사용자가 정답을 선택하고 1~4의 숫자를 입력하였을 때 실행되는 스크립트 입니다.

사용자 삽입 이미지

[set answer to (입력 값)]
  사용자가 현재 입력한 값을 answer 변수에 저장합니다.

[broadcast [check] and wait]
  입력한 값을 정답과 비교하고 처리하도록 check 메시지를 전달합니다.

[change step by (1)]
  현재 진행단계를 1 증가합니다.

[broadcast [change] and wait]
  다음 문제를 보여 주도록 change 메시지를 전달합니다.


3. 정답 처리
 
사용자가 정답을 입력하면 check 메시지를 받게되어 실행됩니다. 각 단계의 정답을 검사 하고 정답이면 점수를 1 증가한 후에 'Good'을 2초간 출력하고, 오답이면 'Wrong'을 2초가 출력한 후에 다음 문제를 출력합니다. 

사용자 삽입 이미지

[if <(step)=(단계)>]
  현재 단계를 검사하여 각 단계마다 다른 정답들을 검사하고 다음 문제를 출력 할 수 있도록 합니다. 위의 이미지에서는 1, 2 단계만 나오고 3, 4, 5는 생략되었습니다.

[if <(answer)=(정답)>]
  각 단계마다 정답을 비교합니다. 위의 스크립트를 보면 step이 1일 경우에는 정답은 2이며 step이 2일경우에 정답은 3입니다.
 

4. 답변 처리

1) 스프라이트 생성
페인트 에디터를 클릭하여 스프라이트를 생성합니다. 좌측 도구에서 'T'라고 되어 있는 버튼을 선택 후에 아래와 같이 4개의 답변들을 입력합니다. 이 답변은 첫번째 문제의 답변으로 사용됩니다.

사용자 삽입 이미지

2) 코스튬 추가
아래와 같이 Costumes에서 New costume: [Paint] 버튼을 클릭하여 다른 문제들의 답들을 코스튬으로 추가합니다. 답들은 각 단계에 맞추어 입력하시면 됩니다.

사용자 삽입 이미지

3) 스프라이트 작성
답변 스프라이트의 스크립트는 change 메시지를 받았을 때 현재 단계에 맞는 예문 코스튬을 출력하는 단순한 작업만 합니다. 

사용자 삽입 이미지

이제 완료되었습니다. 시작 버튼을 클릭하여 각 문제에 맞는 답을 선택하여 점수를 확인해 봅니다. 각자 질문과 답을 변경하거나 추가하여 다양한 퀴즈게임을 만들 수 있습니다.

아래의 압축파일을 다운로드 받으시면 전체 소스를 보실 수 있습니다.


웜즈나 포트리스와 비슷한 방식의 턴제로 돌아가며 공격을 하는 게임입니다. 네트워크를 통한 멀티플레이어 게임이 가능하고 Linux, Windows, Mac, FreeBSD의 다양한 OS를 지원하며 소스도 공개되어 있습니다. 물론 공짜입니다.

2명에서 4명까지 플레이할 수 있습니다. 팀이름이 Tux, Gnu, FireFox, Thunderbird, PHP등으로 리눅스 사용자들이 아주 좋아할 만한 이름들이 입니다.

아래와 같이 Teams 메뉴에서 플레이어의 수와 팀, 이름, 케릭터 숫자를 설정할 수 있습니다. Map에서 다양한 맵들을 선택할 수 있습니다.
사용자 삽입 이미지

게임방법은 간단합니다. 플레이어들이 돌아가며 정해진 시간내에 자신의 케릭터들중 하나를 선택하여 상대방을 공격합니다.
사용자 삽입 이미지

간단한 조작 방법은 아래와 같습니다.

  • 좌우 방향키 - 이동
  • 상하 방향키 - 조준 (상세 조준:+shift)
  • space - 발사
  • 마우스 우클릭 - 무기, 아이템 선택
  • Return - 점프
  • b - 뒤로 점프
  • delete (Backspace) - 높이 점프
  • c - 현재 선택된 캐릭터를 화면중앙에 위치

캐릭터들이 귀엽게 생겨 아이들이나 여성분들도 좋아 하는 것 같습니다. 가족이나 친구들끼리 모여서 오손도손 시간 보내기에 좋은 아기자기한 게임 입니다. 현재 0.8b4 버젼(OS X는 0.8b3)까지 나와 있으며 Wormux 홈페이지에서 다운로드 받으실 수 있습니다.

아시는 분들이 많을 것 같은데 맥에서 제가 즐겨하는 게임 하나를 소개합니다. 게임 제목은 Thumblebugs로 이곳에서 다운로드 받으실 수 있습니다. 윈도우용도 있습니다.

이전에는 스타크래프트 같은 게임을 좋아했는데, 요새는 복잡한 게임 보다는 간단하고 짧게 즐길 수 있는 이런 퍼즐류의 게임이 좋아지고 있습니다.

게임 방법은 아래와 같이 구멍을 향해 다가가는 구슬들을 없에는 아주 간단한 게임입니다. 같은 색의 구슬이 세개 이상 쌓이게 되면 구슬이 없어집니다. 물론 게임을 도와주는 다양한 아이템들이 있습니다.

저는  FINISH를 앞두고 있는 12스테이지에서 더이상 진도가 나가지 않네요. 은근히 중독성도 있고 작업하다 간단히 머리 식히기에 좋습니다.

사용자 삽입 이미지

사용자 삽입 이미지

'기타' 카테고리의 다른 글

맥용게임 Doukutsu  (3) 2007.11.21
미국의 맥개발자 구인 내용  (8) 2007.10.20
맥용 게임 Thumblebugs  (0) 2007.10.19
OS X용 MySQL 클라이언트 - CocoaMySQL  (8) 2007.10.19
Xcode, Cocoa, 맥 프로그래밍 관련 국내외 사이트  (2) 2007.05.21
NeXTSTEP에 관하여...  (4) 2007.05.14

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

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

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




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

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

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


별다른 오류가 없는 것 같아 0.5버젼에서 베타를 때고 올립니다. 아래와 같이 몇 가지 변경하여서 올립니다.

1. 글자 배경색 변경
텍스트가 잘 안보인다는 분들이 있어, 배경색을 진하게 변경하였습니다.

2. 효과음 추가
배경음, 효과음은 다음 버젼에서 작업을 할려고 했는데, 구름에 가려질 시에 판별이 어려워 정확히 텍스트를 입력하였을 때와 바다에 떨어질 때의 효과음을 추가하였습니다.

사용자 삽입 이미지

"1.0 새버젼이 나왔습니다"


맥에서 도스시절 베네치아 같이 간단하게 즐길 수 있는 타자 게임을 검색해보다가, 찾을 수가 없어 직접 만들어 보았습니다. 당분간은 일 때문에 더 이상 작업을 할 수가 없을 것 같아, 한가해 지면 에니메이션 효과 추가, 효과음, 디자인 개선, 영문모드, 아이템 등을 추가해 1.0 버젼을 만들어 볼려고 합니다. 
사용자 삽입 이미지사용자 삽입 이미지사용자 삽입 이미지

게임방법은 타이핑만 하면 되니 설명을 생략하겠습니다. ^^; 한글 모드만 있으며, 현재 10개의 스테이지가 있습니다. OS X 10.4 이상에서 사용하실 수 있습니다.

제 PPC iMac과 인텔 미니맥에서는 이상없이 동작하였는데, 단기간에 급조하고 테스트가 부족하여 오류가 있을 것으로 보입니다. 사용해 보시고 오류나 개선 사항을 알려 주시면 수정을 하고 베타 딱지를 땔려고 합니다. 아래의 압축파일을 클릭하고 다운 받으시면 됩니다.
 
"1.0 새버젼이 나왔습니다"

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가 호출되어 이미지를 출력하게 합니다.
이제 컴파일을하고 실행을 합니다. 좌우 방향키로 우주선을 움직이며 스페이스키로 미사일을 발사할 수 있습니다. 미사일은 현재 한발씩만 발사 할 수 있습니다. 다음 장에서는 적기들을 만들고 움직여 보도록 하겠습니다.