본문 바로가기
코스웨어/11년 내장형하드웨어

[내장형]황세선_2011.10.11일일보고서

by 알 수 없는 사용자 2011. 10. 11.
728x90
반응형

소코반 게임

어제 부터 소코반 게임 만들기가 진행중이다. 혹시 나같은 사람이 없을까 해서 적어 본다.
소코반 이란??

소코반은 일본어 이다. 일본어로 이렇게!


쓴다고 한다. 그럼 그 뜻이 궁굼한데...


짜잔 ~ 바로 창고지기!! 위 사진은 귀여워서 넣어 보았다.  만약 저 애가 창고지기를 한다면 우오오, 다 부셔버릴것 같애.
...
소코반은 참 아류작이 많다. 음.. 혹시 소코반의 원조가 무엇인지 궁굼하지 않은가? 궁굼하지 않아도 내 마음대로 찾아 보았다.

짠~


소코반! SINCE 1982! 윽 내가 태어나기도 전에 먼저 출생한 게임이 아닌가! 이 게임은 싱킹 래빗(シンキングラビット) 사에서 만든 PC 용 퍼즐게임 이란다.

비교도 당할겸 내가 반쯤 만든 소코반 게임화면 한장 투척 한다.



SINCE 1982 년산과 비교를 불허하는 퀄리티를 자랑한다. 위 화면은 이래뵈도 CM 선생님께서 잘 그렸다고 칭찬한 외관이다. 졸라맨 빼고! 라는 말씀이 있으셨지만 말이다.

이제 본론으로 들어가자. 우리가 소코반 게임을 구현하기 위해 신경써야할 부분은? 이라는 질문에 나는 움직이는 모든것 이라 답변하겠다. 그렇다! 게임에서의 움직임은 상당히 중요하다. 위에서 움직이는 것들을 찾아보면 아래와 같다.



바로 짐(박스)!, 그리고 졸라맨(사람)? 이다. 졸라맨 부터 살펴 보자.

졸라맨은 일단 아래와 같은 스텝을 밟을수 있다.



상, 하, 좌, 우 의 4방향 스텝. 이 스텝을 구현하기 위한 소스코드를 보자. 간단하다.

 case WM_KEYDOWN :
  // 이동전 원래 자리 지우기
  ucMap[uiYMan][uiXMan] = ' ';
  switch(wParam)
  {
    case VK_LEFT :
      --uiXMan;
      break;
    case VK_RIGHT :
      ++uiXMan;
      break;
    case VK_UP :
      --uiYMan;
      break;
    case VK_DOWN :
      ++uiYMan;
      break;
  }
  // 이동후 그리기
  ucMap[uiYMan][uiXMan] = '@';
  InvalidateRect(hWnd, NULL, TRUE);
  return 0;

소스는 정말 쉽다. 일일이 다 설명하면 지루할테니 간단하게 줄여 그림으로 보자. 위 코드로 다음과 같은 역할을 한다.



이게 전부다. 단! 위 그림은 그래픽을 입힌 것이고 실제로는 다음과 같을 것이다.



맵에 대한 설명이 늦었는데 WM_PAINT 메시지에서 이 맵을 기반으로 그래픽을 출력해 준다. 이 배열로된 맵이 곧 출력될 화면을 뜻한다. 어떻게 출력되는지는 나중에 보도록 하자.

이제 캐릭터는 이동이 된다. 하지만 매우매우 심각한 문제가 있으니.. 사진으로 보자.



그렇다. 바로 벽을 뚫어 버린다. 이것은 정말 심각한 문제다. 고쳐야 한다. 위의 예는 위쪽을 예로 들었지만 다른 세방향 모두 뚫어 버릴 것이다.

이것을 막을려면 어떻게 하냐? 각 case 문에 다음과 같은 코드를 맨위에 넣어 주면 된다.

 // 위가 벽일때
if
('#' == ucMap[uiYMan - 1][uiXMan])
{
  break;  // 아무것도 처리 안함
}

이 코드는 위쪽에 벽이 있을 경우다. 빨간색 - 1 이 보인다. 이것은 졸라맨 입장에서 본 벽돌의 좌표가 된다. 그러니까 다른 세방향에 대해서는 이쪽 코드가 달라질 것이다. 다들 아실 거라 생각합니다.ㅎㅎ;

자. 그럼 졸라맨 스텝은 여기서 끝~

다음 짐(박스)의 움직임을 보자. 먼저 짐은 어떻게 해야 움직이는지 살펴 보아야 한다. 참고로 졸라맨은 방향키로 움직였다. 그렇다면 짐(박스)은 ? 짐(박스)은 혼자 움직일리가 없다.! 그렇다면 누군가가 움직여 줘야하는데 그일은 우리의 창고지기 졸라맨 씨가해준다. 또 참고로 졸라맨은 방향키로 움직였다. 그렇다면 또 박스의 움직임은 방향키 에서 처리해 줘야 한다는 결론이 나온다. 그말은 아직까진 간단했던 WM_KEYDOWN 메시지 내용이 길고 복잡해 진다는 말이다.;

그럼 일단 가장 간단한 졸라맨이 박스를 움직일 경우를 살펴보자. 그림을 보자.



경우의 수로 보면 단 4가지. 졸라맨 스텝 수와 동일하다. 동일 하긴 하지만! 역시 게임이나 현실이나 짐을 옮기는 저런 중노동은 결코 쉬운 일이 아니다. 아래와 같은 난관에 부딪히게 된다.



위그림은 위쪽으로 짐(박스)을 옮길때의 경우로 다른 세 경우도 방향만 틀리지 마찬가지다. 그림을 보면 딱 알수 있다. 난관이 딱 2개 보인다. 짐이 두개 겹쳐 있을 경우, 그리고 짐뒤에 벽이 있을 경우. 이 두경우에는 짐을 옮기지 못한다.

이를 소스로 구현해 보자. 아래와 같다.

 case VK_UP :
  // 모션 변경(상)
  hBitPerson = hBitPersonU;
  // 위가 벽일때
  if('#' == ucMap[uiYMan - 1][uiXMan])
  {
    break;  // 아무것도 처리 안함
  }
  // 위가 짐일때
  if('$' == ucMap[uiYMan - 1][uiXMan])
  {  // 짐 위가 벽일때
    if('#' == ucMap[uiYMan - 2][uiXMan])
    {
      break;  // 아무것도 처리 안함
    }
    // 짐 위가 짐 일때
    else if('$' == ucMap[uiYMan - 2][uiXMan])
    {
      break;  // 아무것도 처리 안함
    }
    // 짐 위가 공백 일때
    else
    {
      //짐 위로 1칸 이동
      ucMap[uiYMan - 2][uiXMan] = '$';
    }
  }
  // 위쪽이 길일때
  --uiYMan;

위 코드 역시 그림에 맞춘 위쪽 방향키에 대한 소스다. 위 소스를 간단하게 그림으로 보자.



소스의 핵심은 위 그림과 같다. 단순 C 코드의 if 문 임으로 이해 못할것도 없다.

이렇게 되면 짐(박스) 이동 끝~

마지막으로 출력에 관해서..
위에서 잠깐 언급했지만 배열로 지정된 맵을 통해 비트맵이 출력된다고 하였다. 그럼 맵은 어떤 과정에서 생성되고 어떻게 해서 출력 되는지 한번 알아 봐야겠다.

먼저 맵은 스테이지를 로드해 오면서 생성된다. 그럼 스테이지가 뭐냐? 위 그림에서 보았을 텐데 바로 아래의 3차원 배열이 스테이지 이다.

 static unsigned char ucStage[][15][25 + 1= 
{
  { "#########################"
   ,"#########################"
   ,"#########################"
   ,"########   ##############"
   ,"########$  ##############"
   ,"########  $##############"
   ,"######  $ $ #############"
   ,"###### # ## #############"
   ,"####   # ## #####    ####"
   ,"#### $  $       @    ####"
   ,"######## ### # ##    ####"
   ,"########     ############"
   ,"#########################"
   ,"#########################"
   ,"#########################"
  },
  { "#########################"
   ,"#########################"
   ,"#########################"
   ,"########   ##############"
   ,"########   ##############"
   ,"########   ##############"
   ,"######      #############"
   ,"###### # ## #############"
   ,"####   # ## #####    ####"
   ,"####         @       ####"
   ,"######## ### # ##    ####"
   ,"########     ############"
   ,"#########################"
   ,"#########################"
   ,"#########################"
  }
};

그럼 스테이지를 맵에 로드 해 온다는 것은 무엇이냐? 바로 아래와 같은 코드다.

 // 스테이지 로드
memcpy(ucMap, ucStage[0], sizeof(ucMap));

memcpy 함수가 나온다. 메모리 카피다. 이름만 봐도 무엇인지 알수 있다. 그냥 지나치면 서운하니 원형 하나 적어 놓는다.

 void *memcpy(void *dest, const void *src, size_t n);

원형은 아무래도 src 의 번지에 있는 메모리 영역을 n 바이트 만큼 dest 번지의 메모리에 복사한다는 의미로 해석 되는듯 하다.

그런데 스테이지를 맵에 로드하는 이유는? 스테이지를 바로 써버리면 짐의 위치나 졸라맨의 위치가 바뀌어 버려서 나중에 다시 활용할수 없기 때문이다.

자.. 이제 출력 부분. 일단 소스 부터 올려 놓는다.

 for(uiCnt1 = 0 ; 15 * 30 > uiCnt1 ; uiCnt1 = uiCnt1 + 30)
{
  for(uiCnt2 = 0 ; 25 * 30 > uiCnt2 ; uiCnt2 = uiCnt2 + 30)
  {
    // 벽돌 그리기
    if('#' == ucMap[uiCnt1 / 30][uiCnt2 / 30])
    {
      BitBlt(hdc, uiCnt2, uiCnt1, 5050, hMemDC, 00, SRCCOPY);
    }
    // 사람 그리기
    else if('@' == ucMap[uiCnt1 / 30][uiCnt2 / 30])
    {
      uiXMan = uiCnt2 / 30;
      uiYMan = uiCnt1 / 30;
      SelectObject(hMemDC, hBitPerson);
      BitBlt(hdc, uiCnt2, uiCnt1, 5050, hMemDC, 00, SRCCOPY);
      SelectObject(hMemDC, hBitBlock);
    }
    // 짐 그리기
    else if('$' == ucMap[uiCnt1 / 30][uiCnt2 / 30])
    {
      SelectObject(hMemDC, hBitLuggage);
      BitBlt(hdc, uiCnt2, uiCnt1, 5050, hMemDC, 00, SRCCOPY);
      SelectObject(hMemDC, hBitBlock);
    }
  }        
}

뭐..코드도 어렵지 않다. 대충 스토리는 이러하다. 일단 2중 for 문을 돌면서 30 * 30 비트맵을 쭉~ 출력한다.  출력을 하는데 ucMap 배열을 참고하여 출력을 한다.
음..참고를 하였더니 '#'이다. 이건 벽돌이군.. 벽돌을 찍는다. 또 참고를 하였더니 이번에는 '@' 이다. 음.. 이건 졸라맨이군.. 졸라맨을 찍는다. 또 또 참고 하였더니 '$'다. 음.. 이건 짐(박스)이군.. 짐(박스)를 찍는다. 뭐.. 대충 이런 스토리다.

이걸로 오늘 분량은 끝~바통 터치~

728x90