RPG 스타일 무한 이동 타일맵 구현
by 세계의끝 on 3.11, 2011, under 게임 제작
얼마전에 AS3.0의 3D 쿼터 뷰 화면에서 자동 Depth Manager 클래스(이하 “Depth Manager 포스트”)를 구현에 관한 포스팅을 하면서 예제를 포함했습니다. 눈치채신 분이 있을지 모르겠지만 예제 swf 파일은 실제로 동작하는 파일이고, 현재 작업중인 게임의 프로토타입 입니다.

대표적인 rpg 스타일의 타일 맵 구조인 디아블로 3의 한 장면
Depth Manager 포스트에 이미 썼지만, 게임은 Diablo나 WOW 스타일의 롤 플레잉 게임(RPG) 이기 때문에, 중앙에 위치한 플레이어 캐릭터는 화면상에서 계속 중앙에 고정되어 있어야 하고, 사용자가 wasd 키를 이용해 방향을 조작하면 바닥 맵이 움직이도록 기능되어야 합니다.
맵 뿐만 아니라 몬스터도 맵을 기반으로 움직이고, 그 위에 날아다니는 스킬(총알 또는 미사일) 역시 마찬가지 입니다. 그러므로 화면 자체는 1인칭 주인공 시점이고 나(플레이어)를 제외한 세계의 모든 것이 동시에 같은 방향으로 움직이게 됩니다.
Depth Manager 포스트에 이미 상세하게 썼으므로 레이어 구조가 궁금하신 분은 읽어보시기 바라고, 이 포스트에서는 바닥의 맵에 집중해서 알아보겠습니다.
맵의 크기에는 물리적인 제한이 존재한다.
가장 먼저 알아볼 것은 플래시의 몇 가지 한계점 중의 하나인 비트맵의 표시 면적(픽셀) 한계수치에 관한 내용입니다. BitmapData 클래스에 대한 레퍼런스를 보면,
AIR 1.5 및 Flash Player 10에서는 BitmapData 객체의 최대 크기가 8,191픽셀(폭 또는 높이)이며 총 픽셀 수는 16,777,215픽셀을 초과할 수 없습니다. 따라서 BitmapData 객체의 폭이 8,191픽셀이면 높이가 2,048픽셀 이하여야 합니다. Flash Player 9 이전 버전 및 AIR 1.1 이전 버전에서는 이 제한이 높이 2,880픽셀 및 폭 2,880픽셀입니다.
이런 내용을 볼 수 있습니다. 즉, 바닥의 맵은 위의 제한 내의 크기로 만들어져야 한다는 겁니다. 설사 이런 제한이 없다손 치더라도 저런 엄청난 크기의 비트맵을 움직이려면 상당한 수치의 cpu 를 사용해야 한다는 이야기고, 이것은 맵을 움직이는 것만으로 성능상 상당한 문제를 일으키게 됩니다.
그러므로 이런 조건에 맞게 맵을 만드려면 맵을 타일과 같이 쪼개야만 한다는 잠정적인 결론에 이릅니다. 화면이 움직일때 맵이 표시되는 면적만을 갱신해서 표시하고 화면 바깥에 있는 맵은 아무 일을 하지 않게, 아예 visible 을 끌 수 있으면 꺼서 최대한 cpu 자원을 적게 소모하도록 해 줄 필요가 있습니다.
A. 사용자의 컨트롤에 대한 처리
자 그럼 좀더 들어가서…
사용자가 키보드 A 키보드를 눌러 캐릭터를 왼쪽으로 이동 시키는 조작을 했다고 가정합니다.
이 경우 맵은 그 반대 방향인 오른쪽으로 이동시켜야겠죠. 이동후에는 왼쪽에 비어있는 맵을 보충하고자 오른쪽에 있는 맵 타일을 가져다 왼쪽에 놓습니다. 이것을 쉽게 그림으로 표현해 보죠.
A-1. 수평이동

여기까지는 쉽죠? 타일의 갯수가 많으면 기하급수적으로 헷갈리기 때문에 가장 간단한 타일 조합인 3*3으로 예시를 했습니다. 그리고 타일은 배열을 사용할 것이므로 타일 넘버링은 0 부터 시작함이 마땅하나 역시 매우 헷갈리기 때문에 1부터 시작했습니다.
A-2. 수평+수직 이동
그러면 사용자가 A 키와 W 키를 동시에 눌러 왼쪽 위 방향의 대각선으로 조작했을 경우는 어떨까요? 역시 마찬가지로 왼쪽이 비면 오른쪽에 있는 타일을 가져다 왼쪽에 놓고, 위쪽에 빈 공간을 아래쪽의 타일을 가져다 놓으면 됩니다.

타일이 대각선으로 이동 하다가 이동 조건을 먼저 충족시킨 수평 이동 규칙이 먼저 적용 됩니다. 그리고 곧 이어 W 키에 의해 수직 이동 조건도 충족되어 가장 아랫줄 전체가 가장 위로 이동하게 되죠.

꼼꼼한 분들 중에는 정확히 W키와 A키를 동시에 눌러 왼쪽으로 보내는 함수와 위쪽으로 보내는 함수가 동시에 호출되면 어떻게 되는거냐. 라고 의문을 품으실 분이 계실 것 같습니다만, 다행인지 불행인지 플래시 플레이어는 싱글 쓰레드 이기 때문에 동일한 함수가 동시에 호출되는 일은 일어나지 않습니다. 1 나노초라도 먼저 호출 되는 쪽의 함수가 실행되고 이후의 호출은 대기를 타게 되므로, 특히 9번 타일에 대해서는 걱정하지 않으셔도 됩니다.
자 그래서 플레이어 캐릭터는 가만히 있어도 움직이는 세계를 만들어 봤습니다. 실제로는 러닝머신과 같은 형태이긴 합니다만 ^^
이론은 쉽죠? 그러나 일정한 조건이 되면 감지해서 타일 위치를 변경하는 코드는 꽤나 복잡하기도 하고 한 가지의 정답이 아닌 여러 가지 방법으로 구현이 됩니다. 최적화가 덜 되있긴 하지만 제 수준에서는 TileSet.as 파일의 코드가 200라인 약간 넘는 정도가 나오네요.
A-3. 함수 목록
로직의 흐름을 따라 필요한 함수를 간단하게 정리해 볼까요?
- 타일을 담고 있는 컨테이너의 x, y 값을 매 프레임마다 업데이트 해 주는 함수(호스트 코드에서 호출)
- 이동하면서 타일이동 조건에 부합하는지 검사
- 검사 조건에 충족하면 4가지의 이동( 왼->우, 우->왼, 하->상, 상->하 ) 명령을 호출하는 함수
- 각 조건에 따라 (어느쪽 끝이 될지는 모르겠지만) 한쪽 끝 모서리의 타일들의 목록을 리턴하는 함수
- 리턴받은 타일 목록을 4가지 유형에 따라 픽셀 이동
이 밖에도 아래와 같은 부차적인 함수도 필요합니다.
- 각 모서리 위치에 있는 타일들이 어떤 녀석들인지 우리는 눈으로 보면 알 수 있지만 컴퓨터는 어떻게 알 수 있나요? 이를 위해 2차원 배열로 타일 목록을 관리할 필요도 있고,
- 타일이 이동한다면 얼마만큼의 픽셀이 움직여야 하는지 미리 계산 되어 있어야 하며,
- 2차원 배열을 관리 할때 첫 번째 원소를 빼내서( Array.shift() ) 마지막 원소로 넣어주는( Array.push() ) 함수와,
- 그 반대의 함수도 필요합니다. ( Array.pop() 과 Array.unshift() )
여러분도 직접 만들어 보세요. 여러분의 실력 향상에 분명 도움이 될겁니다.
B. 최종 컴파일 결과
그럼 최종 컴파일 결과물을 보고, 플래시 화면을 한번 찍은 후 WASD 키보드도 움직여 보세요. 맵이 움직이는것을 확실히 증명하기 위해 5*5 타일중 정 중앙의 타일 색상을 살짝 바꿔 놓았습니다.
C. 기능 개선
현재의 결과물은 타일의 크기가 가로세로 각각 100픽셀 정도의 작은 타일이라 모든 타일들이 한 화면에 표시되지만, 타일의 크기가 좀 더 커야 할 경우를 고려한 구조도 필요할것 같습니다.
또한 타일 갯수가 3*3, 5*5, 7*7 외에도 4*4, 6*6 과 같은 짝수 타일의 경우에도 문제 없이 동작할 수 있도록 해야겠고, 가로와 세로의 타일 수가 일치 하지 않는 경우 역시 마찬가지 되겠습니다.
Blog under the Creative Commons Attribution-NoDerivs 3.0 License
3월 11th, 2011 on am 9:34
언젠간 저에게도 꼭 도움이 될것같은…
전편에 이어 재밌게 잘 보고 있습니다.
다음글도 기대할께요~
3월 11th, 2011 on pm 2:27
시행착오를 줄여주는 것만으로도 이 포스트는 충분한 역할을 하는거죠 ^^
3월 11th, 2011 on am 10:30
오오 멋져요 ㅋ RPG 만드시는거에여?
3월 11th, 2011 on am 11:45
버디러시 때문에 김 팍 샜지만, 일단 시작했으니 뭔가 플레이는 되게 만들어 봐야겠지 ㅎㅎ
3월 11th, 2011 on pm 12:57
으으… 무슨 포스팅의 퀄리티가 출판물에 견줄만큼 매끈한가요? 조만간 플래시판 디아블로가 하나 나오겠군요. 재미있게 잘 읽었습니다. ㅎㅎ
3월 11th, 2011 on pm 2:29
포스팅에 개그 요소를 좀더 많이 넣기 위해 노력 하고 있습니다. ㅎㅎ
10월 15th, 2011 on pm 6:03
심도있는 글을 잘 쓰시는듯 ㅋㅋ
저는 개인적으로 끝님의 내용이 이해가 잘되어 좋은데요 ^^
3월 12th, 2011 on am 11:38
우앙 멋지세요 +_+
예전에 시도 해봤다가 포기했었는데….
잘 봐뒀다가 써먹어볼께요 +_+
3월 14th, 2011 on pm 12:57
지금 이 TileSet 클래스 코드를 공개하지 않고 메서드 목록만 나열한 이유가, 다른 클래스에 대한 의존도를 제로로 만들지 못했기 때문인데요. 그냥 제대로 돌아가는 코드 구현도 물론 중요하지만, 어떻게 하면 나를 포함한 다른사람이 사용하기 편하게 만드느냐가 훨씬더 중요하다고 할 수 있죠.
3월 13th, 2011 on pm 3:12
플생사모에서 게시글 보고 구경 왔습니다.
프로토타입만 봐도 두근두근 하네요.
결과물 기대하겠습니다. 화이팅!
본문 재밌게 잘 읽었습니다. ^^b
3월 14th, 2011 on pm 1:15
게임 로직 보다도 네트웍 부분 처리가 벽인데, 잘 해결해 봐야죠.
3월 28th, 2011 on pm 4:31
안녕하세요! 글 잘 보았습니다!
저도 언젠가는 이런 게임을 만들고 싶네요!
좋은 글 고맙습니다.
3월 29th, 2011 on pm 10:16
재미있는 게임 많이 만드세요.
4월 7th, 2011 on am 11:46
오오오 멋져요 ㅎㅎ
4월 7th, 2011 on pm 2:35
이 포스트를 지금 본거야? 쓴지 좀 됐는데 ㅎㅎ
7월 4th, 2011 on pm 1:35
음….그냥카메라움직이듯이 root.x,y좌표를 캐릭터에맛추면안되나요;?
7월 5th, 2011 on pm 5:17
root 를 움직여버리면 여러가지로 곤란항 상황이 발생할 수 있습니다.
x, y 좌표계가 변경되면 안되는 ui 부터도 root 에 addChild() 되어 있다는 거죠.
게임을 ui 없이 여기까지만 만들고 말 것이 아니라면 말입니다. ^^