티스토리 뷰

Project

#007 : Unlight Copycat [DAY #08]

BaeMinCheon 2018. 3. 1. 23:15

Project Note #007


Unlight Copycat DAY #08

개요

  • Window API의 일종인 pclaf를 활용해 게임을 만드는 과정을 정리합니다.
  • 참고
    ※ https://github.com/BaeMinCheon/unlight-copycat (Github, "v0.6.0" 태그)

환경

  • Visual Studio 2015 Professional
  • Windows 10 Home
  • pclaf (C/C++)

5일차 글을 작성한 뒤, 학교 기숙사 입사 일정과 겹쳐 제대로 손보지 못하다가 이제야 올리네요. 제가 게으른 탓도 있지만 아무튼 열심히 하고 있습니다ㅜㅡ....이번에는 많은 부분을 수정하는 바람에 시간이 오래 걸렸습니다. 그래도 그만큼 지저분한 코드는 줄어들었으니 지난 글의 코드와의 차이점에 주목해주세요.


"메뉴버튼"은 시작/삭제/포기 버튼을, "맵목록"은 Quest 시퀀스 우측에 위치하는 맵의 목록이라 하고, "맵정보"를 맵이름 그리고 맵블럭을 통틀어 부르는 말이라 합시다. 이전에는 맵목록과 맵정보를 따로 나누어 처리하려 했지만, 골치아파지는 부분이 많아져 하나의 클래스로 묶어보려 합니다. Map이라는 클래스 내부에 MapList와 MapBlock을 정의할 것입니다.


MapList는 말 그대로 맵목록에 해당하고, MapBlock은 맵블럭과 맵블럭 옆에 띄울 맵이름에 해당합니다. 즉, 맵을 크게 3가지 분류(메뉴버튼/맵목록/맵정보)로 나누고 맵목록과 맵정보의 경우 개별적인 클래스로 나눕니다. 메뉴버튼의 경우, Quest 시퀀스 내에서 하나만 존재해도 무방하기 때문에, Map 클래스에 정적 멤버변수로 정의하겠습니다.


그리고 메뉴버튼을 위한 함수를 작성해봅시다. menuIn()과 menuOut()으로 메뉴버튼을 화면 안으로 가져왔다가 화면 밖으로 내보내는 기능을 구현합시다. 단순히 Button 클래스에서 작성했던 moveTo()를 활용하면 됩니다. menuClick()과 menuDraw()도 만들어, 메뉴버튼에 대한 클릭 검출과 출력을 구현합니다. 물론, 이 함수들도 정적 멤버함수로 정의하는 것이 좋겠습니다.


MapList는 RectButton을 상속받아 작성하는데, 멤버변수로 AP소요를 저장할 int cost;를 추가적으로 정의해줍니다. 생성자에서 매개변수로 받아 해당 변수 초기화를 진행하구요. isClick()의 경우, RectButton에서 상속받았고 그대로 사용해도 무방하니 재정의하지 않습니다. draw()의 경우, AP소요도 출력해야하므로 재정의하도록 합시다. AP소요를 왼쪽, 맵이름을 오른쪽으로 붙여 출력합니다. 그리고 맵을 삭제하는 경우, 맵목록을 위로 당겨줘야할 수도 있으니 Y값을 감소시키는 upList()도 작성해줍니다.


MapBlock은 Button을 상속받아 작성합니다. 마름모 모양으로 만들어야하기 때문입니다. 우리가 만들 맵블럭은 내각이 모두 90도이면서 모든 변이 동일한 길이인 마름모이기 때문에, CircButton의 isClick() 내용을 사용해도 무방합니다. draw()에서는 polygon()을 사용해 마름모를 그립니다. 맵목록도 화면 내외로 이동할 수 있어야하므로 blockIn()과 blockOut()을 작성해줍니다.


맵목록과 맵정보에는 중요한 차이점이 있습니다. 맵목록은 항상 출력되지만, 맵정보는 맵목록을 클릭했을 때 나타나게 됩니다. 그러므로 맵목록과 맵정보의 클릭 검출과 출력을 나누는 것이 좋겠네요. 맵목록을 위해서 listDraw()와 listClick()을, 맵정보를 위해서 blockDraw()와 blockClick()을 Map 클래스에 작성합니다.


listDraw()와 listClick()의 경우, 맵목록 객체 하나에 대한 클릭 검출과 출력이므로 간단합니다. 맵블럭의 경우, blockClick()은 모든 맵블럭에 대한 클릭 검출만 작성하면 되지만 blockDraw()는 신경써야할 게 늘어납니다. 모든 맵블럭을 출력함과 동시에 각 맵블럭이 이어져있다는 시각적 표현을 위해 맵블럭 밑으로 사각형을 그립니다. 또 맵이름도 같이 출력합니다.


Map 클래스에서 맵목록과 맵정보를 멤버변수로 정의하는 데에 있어서, 각 맵은 맵목록을 하나만 가진다는 점에 주목합시다. 즉, 굳이 동적할당을 할 필요는 없으니 포인터 변수를 사용하지않습니다(MapList list;). 맵블럭은 여러 개를 가질 수 있으니, 맵블럭을 저장할 vector를 정의해줍니다. 그리고 맵정보를 출력할 때 맵이름이 필요할 테니, 맵이름인 name을 반환하는 getName()도 만들어둡니다.


Quest::draw()에서는 메뉴버튼 출력, 맵목록 출력, (클릭된 맵의) 맵블럭 출력만 작성해주면 됩니다. 이쪽은 간단하지만, 클릭 검출에서는 조금 복잡해집니다. 우선 메뉴버튼이 클릭되었는지부터 검사합니다(메뉴버튼에는 맵정보 배경으로 만든 버튼(Quest::buttonVector[3])이 있기 때문에, 메뉴버튼에 대한 클릭 검출을 하는 것은 맵정보에 대한 클릭을 검출하는 것을 포함합니다). 클릭되지 않았다면, 맵목록 또는 그 이외의 위치를 클릭한 것이기 때문에 메뉴버튼과 맵정보의 위치를 화면 밖으로 옮긴 뒤 선택된 맵이 없음을 mapListIndex = -1;로 적용합니다.


그리고 맵목록에 대한 클릭을 검사하고, 맵목록이 클릭되었을 경우 해당 맵의 맵정보와 메뉴버튼을 화면 안으로 가져옵니다. 이전에 선택된 맵이 없었다면, mapListIndex = i;로 적용합니다. 이런 순서로 검사하지않을 경우, 맵목록을 누를 때마다 첫번째 맵목록의 맵정보가 뜨게 됩니다(경험담ㅜㅜ). 드디어 이전 글에서 구현했던 기능까지 다시 돌아왔습니다. 기억 속의 Unlight의 기능을 떠올리며 작성하다 보니, 막상 구현하면서 이 부분은 아니었던 게 뒤늦게 떠올라 이렇게 꼬이는 부분이 생기네요. 오늘은 맵 삭제까지만 작성해보고자 합니다.


맵을 삭제하기 위해서는 메뉴버튼에 대한 내용을 작성해야겠습니다. Quest::init()에서 Map::menuVector로 메뉴버튼을 추가하는데, 이중에서 이름이 DELETE인 버튼에 맵을 삭제하기 위한 람다표현식을 전달합니다. mapVector의 원소는 std::shared_ptr<Map>이기 때문에, nullptr이 대입되면(동시에 참조카운터가 0이 된다면) 해당 원소의 메모리는 해제됩니다. 그리고 해제된 자리는 뒤의 원소들을 앞당겨오고(for문), 맨뒤의 원소를 pop_back() 합니다(어차피 한 칸씩 앞으로 이동했기 때문임). 그리고 현재 선택된 맵이 없다는 것을 mapListIndex = -1;로 적용하고, Map::menuOut()으로 메뉴버튼을 화면 밖으로 옮깁니다.


Quest::doubleClick()에서 메뉴버튼에 대해 클릭검출을 하고, 각 버튼의 doWork()을 호출하도록 작성합시다. 별도의 창을 띄우는 것보다는, 다양한 입력방식의 장점을 활용해봅시다. 이럴 때가 아니면 언제 써먹겠나요. 단발 클릭이 아닌 연속 클릭이기 때문에 고의적으로 누르지않는 이상, 의도치않은 실수도 없을 것입니다.


그럼 이제 실행해봅니다. DELETE 버튼을 더블클릭했을 때, 해당 맵이 잘 사라지는 것을 볼 수 있습니다. 이 다음으로는 맵을 시작하고 포기하는 기능을 구현하는 과정을 진행하겠습니다. 여유가 된다면 Battle 시퀀스로의 진입도 구현하여 Quest 시퀀스를 마무리하겠습니다. 이상으로 DAY #08 노트를 마칩니다.


추가

  • 깃헙의 본 레포지터리의 README.markdown에 각 시퀀스의 멤버변수와 멤버함수를 정리해놓았습니다. 프로그램을 해석하는 데에 조금이나마 도움이 될 거라고 생각합니다.

  • 맵 클릭에 대한 동작 구분을 해보았습니다. 생각보다 복잡하네요.

    • if( 맵 클릭 )

      • 맵의 맵정보 위치이동
      • for( 메뉴버튼 )
        • if( 시작버튼 더블클릭 )
          • 시작버튼과 삭제버튼 위치이동 및 포기버튼 위치이동
          • SD캐릭터를 맵블럭의 첫번째 칸으로 위치이동
          • (Quest 시퀀스 내에서) 포기버튼과 맵블럭만 클릭가능하도록 조정
        • if( 삭제버튼 더블클릭 )
          • 맵 클래스 삭제
          • mapVector에서 해당 원소 삭제
        • if( 포기버튼 더블클릭 )
          • 맵 클래스 삭제
          • mapVector에서 해당 원소 삭제
      • if( 맵블럭[다음위치] )
        • SD캐릭터를 다음위치의 맵블럭으로 위치이동
        • switch( 맵의 종류 )
          • case 0: (빈 블럭)
            • 아무것도 하지않음
          • case 1: (아이템 블럭)
            • 아이템 획득을 알려주는 버튼 생성
            • player에 대해 아이템 획득을 적용
            • 맵블럭에 있던 아이템 출력을 제거
          • case 2: (몬스터 또는 캐릭터 블럭)
            • Battle 시퀀스의 init()을 호출
            • Battle 시퀀스로 전환
    • else

      • 맵의 맵정보 위치이동


'Project' 카테고리의 다른 글

#009 : Unlight Copycat [DAY #18]  (0) 2018.03.11
#008 : Unlight Copycat [DAY #09]  (0) 2018.03.02
#006 : Unlight Copycat [DAY #05]  (0) 2018.02.26
#005 : Unlight Copycat [DAY #04]  (0) 2018.02.25
#004 : Unlight Copycat [DAY #03]  (0) 2018.02.25
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함