1. 갤러그를 만들자 (1) - 본격 멘붕 갤러그에서 콜옵까지


솔직히 고백하겠다.

나는 C++을 얕봤다.

아니, 얕보지 않았었지만 충분히 얕보지 않았던 것이다.

내 전 글에 많은 분들이 감사하게도 격려와 우려를 나타내 주셨는데, 처음에는 흥~ 그 정도 쯤이야, 하면서도 회사에서 일하는 내내 머릿속을 맴돌았던 리플이 있었다.






네에, 그 말씀대로!

쉽게 끝나리라 예상은 안 했지만 이렇게까지 힘들게 될 줄은 예상 못했음.



그를 언급하기에 앞서, 일단 소스코드 전체를 개편하기로 했다. 전처리문은 헤더를 따로 만들어서 메인 소스코드를 조금이나마 짧게 만들고, 비행기의 제어 클래스를 없애버렸다. 가만 생각해보니 Awing은 제어 클래스를 따로 만들어야 할 만큼 복잡한 멤버를 지닌 클래스가 아니었던 것. 괜히 소스만 복잡해져봐야 좋은 소스코드가 될 수 없겠지. 

하지만 나중에 적 비행기를 만들 때를 대비해서 적 비행기, 내 비행기, 적 총알, 내 총알들이 상속받을 OBJ 클래스는 추가로 만들기로 했다.  각각 클래스는 OBJ에서 상속받은 x, y 좌표값을 가지게 되고, 그 외 나중에 어떤 멤버를 가질지는 모르는 일이다.

또한 kbhit()함수의 존재를 배우고 그걸로 입력을 대기하느라 게임을 멈추는 현상을 해결할 수 있었다. 이건 상당히 큰 수확이라고 자부한다.

벽이나 배경 따위는 나중에 그려넣기로 하고 좀 더 게임으로서 근본적인 부분에 먼저 접근을 해 보자.

멤버나 함수 따위를 미리 다 계획해서 한번에 써 넣는다면 물론 퍽이나 좋겠지만, 그렇게 할 수 있다면 이미 나는 아마추어 프로그래머 이상의 무언가일테지. 이 멤버가 컴파일이 될지, 버그는 없을지 전혀 예상할 수 없기 때문에 그런 식으로 계획을 짜 두어봤자 십중팔구 나중에 또다시 계획을 모조리 개편해야 할 일이 생길 것이다.

가령 이런 것이다.



나는 Awing클래스를 선언하고, 그 아래에는 Awing이 발사할 Laser 클래스를 선언했다. 그리고 Awing클래스 내에서 레이저를 발싸하는 함수를 만들어 그 안에서 Laser를 인스턴스하도록 조정했다.

컴파일 결과는 어떻게 됐을까?

Laser라는 식별자를 찾을 수가 없댄다. 순간 나는 내가 학원을 헛다닌 건가 하는 자괴감에 빠져 한참동안 멘붕에 빠져 있었다. 이게 어제 저녁 퇴근 후에 있었던 일이다.

문제는 뭐였을까? 아시는 분이야 아실테지만, 이렇게 하려면 Laser가 Awing보다 위에 선언이 되어 있어야 했던 것이다. 알아서들 찾아오지 쫌... 쯧.


즉 체계적으로 계획을 잡고 짜기에는 내 지식이 너무나도 부족한 상황이다. 그러니까 말했듯이, 사포 벽에 몸을 문대서 벽을 지워버리겠다는 마음가짐으로 덤벼들 수밖에 없다, 지금은. 솔직히 언제 한계에 봉착할지 모를 아슬아슬한 상황이다, 지금은. 주말에는 교보문고라도 찾아가서 게임 제작 기초에 관한 책을 찾아봐야 할 것 같다. 그 때까지 이 갤러그 프로젝트는 조금 지연될지도 모르겠다.



#include "galag.h"

class OBJ{
public:
int coX, coY;
char ob;
OBJ(int x, int y) {
this->coX = x; this->coY = y;
}
};

class Laser : public OBJ {
public :
char las[2][1];
Laser(int x, int y) : OBJ(x, y) {
}
};

class Awing : public OBJ {
public :
Awing(int x, int y) : OBJ(x, y) { }
void showAwing() {
gotoxy(this->coX, this->coY);
cout<<"A";
gotoxy(this->coX-1, this->coY+1);
cout<<"<O>";
}
void shoot(){
Laser laser(this->coX, this->coY);
while(laser.coY>3) {
gotoxy(laser.coX, laser.coY-3);
char las[2][2] = {"I"," "};
cout<<*las;
gotoxy(laser.coX, laser.coY-2);
cout<<las[1];
laser.coY--;
Sleep(30);
if(laser.coY==2){
las[0][0] = ' ';
cout<<*las;
}
}
}
};

int main() {
Awing awing(35, 20);
while(1) {
awing.showAwing();
if(kbhit()) { //실험용으로 선언해 둔 부분이다. 어디서 함수화할지 고민 중. 아마 Awing에서 할 것 같은데
int n = getch();
switch(n) {
case 'z': awing.shoot(); break;
}
}
}
return 0;
}


문제가 되는 부분은 굉장히 여럿 있다. 우선 사소한 부분으로는 Awing의 shoot()함수가 골칫덩이인데, 사실상 Laser 클래스에서 하는 일이라곤 자기 좌표를 가지는 것 외에는 거의 의미가 없는 탓에 좋지 않은 함수가 돼 버렸다.

하지만 함수의 퀄리티 따위는 아무래도 좋을 문제가 여럿 생겼다. 남들은 잘만 해결하는 모양인데, 나는 레이저의 궤적을 지우느라 죽을 똥을 싸는 중이다


이걸 말이다.

결국 레이저 자체를 2차원배열로 아래칸에 공백을 넣는 방법을 취할 수밖에 없었다.

그런데 그나마도 잘 안 된다.


저 I 하나가 안 지워져.... shoot() 함수 밑부분에 if문을 만들어둔 것이 저걸 없애보려 하던 흔적이다. Laser의 소멸자에 CLS를 넣어버리는 고육지책도 생각해봤지만 이건 나중에 해결하기로 했다.

이건 또 이것대로 그나마 사소한 문제다. 슈팅게임에서 레이저가 쏘는 즉시 화면 끝부분에 도달해버리는 것은 좀 문제가 있는 방식이기 때문에, Sleep()으로 날아가는 궤적을 조금씩 지연시켰다.

그런데 저게 날아가는 동안에는 비행기 좌우 컨트롤이 먹히질 않는다...... 이게 가장 큰 문제점이다. while문 내에서 Sleep을 돌리는  함수가 진행되고 있는 동안에도 다른 객체들이 리얼타임으로 움직이는 방법을 찾지 않으면 이것은 슈팅 게임으로서 낙제점이다. 구글링을 해 보아도 딱히 이렇다할 문제가 떠오르지 않는 난제가 되고 말았다.

방법은 일단 두 가지를 생각하고 있다. 레이저의 좌표이동을 while이나 for를 통한 반복문을 이용하지 않고 해결하는 것이 가장 유력한 해법으로 예상되고, 그렇지 않다면 while이 도는 동안에도 키가 먹히도록 조치를 취하는 게 차선책이다.

이불에 드러누워 생각을 진행시키다보면 뭔가 해법이 뜨지 않을까 생각하며 컴퓨터를 끌까 한다. 짧은 지식으로 백날을 고민해본들 해법을 찾기 힘들 가능성이 높지만 이런 고민들이 시간 낭비가 아니라 부디 나중에는 뼈와 살을 분리...가 아니고 피와 살이 되는 지식이 되기를 바랄 뿐이다.

덧글

  • costzero 2013/08/01 23:44 # 답글

    항상 응원중 내공부족이라 뭐라 할말이 없어서 보기만 할 뿐...
    엑셀 매크로로 갤로그 짠 걸 보고 놀라기도 했지만 서도.
  • 힘세고강한왈도 2013/08/01 23:53 #

    감사합니다. 저도 학원 2개월차밖에 안 되는지라 내공이랄 게 없는 처지에 그냥 대가리 들이밀고 있는 거네요
  • 힘세고강한왈도 2013/08/01 23:52 # 답글

    컴 끄고 자기 전 오줌 누다가 퍼뜩 한가지 아이디어가 떠올라 폰으로 리플을 남겨 기록해둡니다. 어차피 메인함수에서도 while이 돌고 있기 때문에 레이저가 인스턴스되면 메인함수에서 레이저 y좌표값을 옮기는 함수를 만들어 호출시키면 될 것 같네요.회사에서 몰래몰래 해봐야지
  • agkdc 2013/08/02 08:21 # 삭제 답글

    sleep이 도는 동안 다른 함수를 실행시키는 간단한 방법은 스레드를 나눠서 돌리는 겁니다.

    쓰고보니 오히려 더 복잡해졌....
  • 힘세고강한왈도 2013/08/02 09:18 #

    와하하하 쓰레드는 못 배웠습니다!
  • 퍼렁머리 2013/08/02 10:28 # 답글

    쓰레드 나누기는 골치아프니 와일 문 내에서 시간을 재는 함수를 써서 if 문 돌려서 시간이 어느이상 지나면 한칸 움직이게 하는 방법도 좋지요
  • 은이 2013/08/02 10:58 # 답글

    sleep 하면 해당 코드가 그대로 기절(...) 하기때문에
    스레드 안 쓰고 sleep 이나 타이머 등으로 딜레이 주실려면 구현하기가 좀..ㅠㅠ

    안쓸려고 한다면 타이머로 100ms 마다 체크를 해서 카운트를 올리고 카운트가 3이되면 300ms 마다 레이져가 앞으로 나가고..
    체크를 할 때 키가 눌려진상태면 이동하게 하는걸 쓰면 어떻게 될진 모르겠군요.
    원활하게 돌릴려면 딜레이를 10ms 쯤으로 확 줄이면.. 으음..
    C쪽은 메인이 아니라 뚝딱 만들어 내진 못하겠네요 ;ㅁ;ㅎㅎ
  • 힘세고강한왈도 2013/08/02 12:10 #

    C쪽 타이머 사용법을 아신다면 조언 좀 더 부탁드립니다
  • 은이 2013/08/02 12:26 #

    이상적인건 스레드를 돌려서
    1번은 키보드 입력-이동을 처리 2번은 레이져 발사-이동을 처리 하는게 가장 좋습니다.
    이렇게 하면
    1 이동~ : ------------------
    2.레이져 : ------------------
    으로 동시에 작업을 하지만

    타이머나 슬립으로 돌리면 스레드가 1개만 돌아가는 상황이됩니다. 즉..
    1. 이동~ : ????????????? 내 작업은?
    2. 레이져 : ------------- 발사중 < 이렇게 발사중에는 아무것도 못하게 되죠. (이동이 안되시는것 처럼)
    (일단 - 는 작업 ? 는 idle 상태입니다)

    그걸 해결할려면 땜빵인데..
    - 나 ? 를 1개의 간격을 100 ms 로 잡고 레이져를 300ms 에 한번씩이동하고, 이동키는 계~속 누르고 있다고 가정하면..

    1.이동~ : ?--?--?--?--?--?--
    2.레이져 : -??-??-??-??-??-??
    100ms마다 이동, 또는 레이져 작업을 처리하기위해 체크하는데,
    레이져 처리해야하는 300ms 마다 레이져를 그리는 작업을 하게 됩니다.
    이렇게 하면 레이져가 발사중에도 이동은 가능하나, 레이져 작업중엔 이동이 안되지요.
    (대략적인 설명입니다. 실제로 작업 배분은 알고리즘, 처리구문에 따라 좀 더 달라지겠죠)

    이를 극복할려면 작업 체크 간격을 줄여야합니다. 100ms 를 1ms 로 바꾸면
    반응속도가 압도적으로 빨라지겠지고 레이져 작업시 이동이 멈추는것도 최소한으로 줄어듭니다.

    순차처리니 어쩌고 하는것으로 프로그래밍 언어론 쪽에서 나오는 부분이죠.
    (막상 그걸 실무에 유용하게 쓰는거야 이제 하고 있지만요 ㅠㅠ )

    타이머는..
    1. 타이머 객체 생성
    2. 타이머가 완료(complete?) 이벤트 때 처리할 함수 연결
    3. 해당 함수에는 레이져를 체크해야 하는지를 체크.
    4. 타이머가 멈추면 해당 함수를 호출-레이져 체크후 타이버를 리셋, 다시 돌림.

    슈팅버튼은 레이져 발사 최초 처리만 하고 타이머를 돌리기만 하면 됩니다.
    타이머는 이제 돌아가면서 필요할 때마다 레이져를 움직이겠죠! @_@

댓글 입력 영역