티스토리 뷰

- Tile based games (http://www.tonypa.pri.ee/tbw/start.html)
위 튜토리얼을 기본으로 하여 Android에 사용될 수 있게끔 공부한 포스트 입니다.




몇가지 생각하고 있는게 있다보니 잠깐 베이스가 되는 소스를 도망나와봤다. 5x5 pixel의 작은 사이즈라 오래 보다보니 토할뻔 했다;;; 타일의 사이즈가 얼마만큼 작아질 수 있는가 테스트도좀 해볼겸 클래스도 좀 수정을 해보았다. 

때문에 이번 Hit the wall은 두가지 과정으로 나뉘어 질 예정이다. 이 부분이 좀더 간단하니 이걸 보고 본과정으로 들어가면 훨씬 수월할 것이다.
 

1. 내 주변 반경 검색, 이론부분


본래 Tile based games에 나오는 것은 타일 크기에 비해 Hero의 크기가 작아 4귀퉁이를 일일이 체크해줘야 한다. 하지만 우리는 난이도가 그에 비해 좀 적다.
우리는 그냥 4방향만 체크하면 된다!!

난이도가 확 줄어든 느낌이지만 이로 인해 알고리즘 수식을 좀 변경해야 한다는 약간의 귀차니즘이 생긴다. (추신! 그림에 써있는 배열 번호가 각자 구현에 따라 다를 수 있다. 그에 맞춰 작성하도록 한다.)

[변수 설정]
posX : 현재 위치 X를 가지고 있는 변수
posY : 현재 위치 Y를 가지고 있는 변수

currentX : posX가 위치하고 있는 실제 px값, posX * Tile Width
currentY : posY가 위치하고 있는 실제 px값, posY * Tile Height

그러면 본인 위치 (1,1)에서 4방향을 체크해주려면 다음처럼 단순 증감으로 가능하겠다.

왼쪽 : posX - 1
오른쪽 : posX + 1
위쪽 : posY - 1
아래쪽 : posY + 1

하지만 앞으로의 유용성이나 기타 몇몇을 고려하자면 Tile Based games에 나온 수식을 참고하는 것이 좋다고 생각한다. 다음은 튜토리얼에 나온 수식 내용이다.

[내 주변의 위치 X,Y 값 가져오는 수식]
위쪽 Y = (currentY - Tile Height) / Hero Height;
아래쪽 Y = (currentY + Tile Height) / Hero Height;
왼쪽 X = (currentX - Tile Width) / Hero Width;
오른쪽 X = (currentX + Tile Width) / Hero Width; 


위에서 각 X,Y 값을 구했으면 이제 각각 위치가 이동이 가능한 곳인지 체크해야 한다. 이는 단순히 Map Data의 배열 정보를 읽어와 1인지 0인지만 확인 하는 내용이므로 단순하게 map[x][y].isWalkable 을 리턴해주면 된다. 물론 배열의 index를 넘길 수 있으므로 이에 따른 예외처리는 필수다.

[벽 체크 변수 셋팅]
left_isWall = map[leftX][posY].isWalkable;
right_isWall = map[rightX][posY].isWalkable;
up_isWall = map[posX][upY].isWalkable;
down_isWall = map[posX][downY].isWalkable; 


이렇게 해서 wall 체크를 담은 변수를 완성했다. 

그렇다면 이제 이 Wall 변수를 이용해서 Hero 이동을 제한해야 한다. Move Event를 받아 이동되게 만들어 놓은 Move() 메소드에서 이 체크 변수를 활용하여 캐릭터를 이동시킨다. 이번 이동에서 체크해야 할 점은 벽을 만났을 때이다.

왼쪽, 위쪽으로 이동하는 경우 벽이나 지도의 끝에 도착하게 되면 0이 될 수 있으며 현재 자신의 배열 위치에서 이동되어야 한다. 배열 위치에서의 이동은 오른쪽, 아래쪽도 동일하다. 하지만 여기서는 우리는 타일의 크기가 Hero 크기랑 동일하므로 타일 안에서의 이동은 생각하지 않아도 된다.

[정리]
- 왼쪽, 위쪽으로 이동시 최초 배열 크기는 0이다.
- 타일 안에서의 이동은 필요 없으므로 currentX = posX * Tile Width 와 같다. (currentY도 동일)
- 왼쪽, 위쪽의 0일 경우를 생각하면 posX * Tile Width 는 무조건 0이 되므로 + 1을 해준 뒤 다시 Tile Width 만큼 빼주자.

[결과]
- 왼쪽 : ((posX + 1)  * Tile Width) - Tile Width, 이렇게 하면 현재 위치의 오른쪽에서 타일 크기만큼 빼게 되니 자기 자신의 위치를 얻을 수 있다.


이렇게 해서 각각 코너를 체크 할 수 있다. 이것으로 간단한 이론 정리를 해보았다. 
타일안에서의 이동도 되지 않고 각 꼭지점마다 체크 할 필요 없으니 일단은 이것으로 먼저 체크 시도를 해보는 것도 좋을 거라 본다.

적고 보니 일부러 말되게 적어 놓은 것에 지나지 않지만... 나중에 따로 정리 할 일 있으면 정리를 해보도록 하겠다.

2. 소스 처리


이번에 적용하려고 평소 잘 쓰지 않는 상속을 사용해봤다. 아직 실력이 높지 않고 객체지향적인 프로그래밍도 해본적이 얼마 없어서 참고할 정도는 아닌데 막상 이렇게 쓰고 싶다 싶어서 얼추 구현해봤더니 되길래 함께 적어 놓는다. 분명 1년 뒤에 다시 보면 엄청 부끄러울테지만...

먼저 기존에 만들었던 MAP 클래스에서 필요한 정보만을 뽑아 내어 최상위 클래스를 만들었다. 소스에 대해 설명할 내용이 많이는 없고 이 최상위 클래스에 isWalkable() 메소드를 추가해두었다.

public class MAP {
	public int MAP_X_COUNT = 0;
	public int MAP_Y_COUNT = 0;
	public MAP_Tile[][] map;
	
	public MAP(int MAP_X, int MAP_Y) {
		MAP_X_COUNT = MAP_X;
		MAP_Y_COUNT = MAP_Y;
		
		map = new MAP_Tile[MAP_Y_COUNT][MAP_X_COUNT];
	}
	
	public void Render(Canvas canvas) { 
		
		int x=0;
		int y=0;
		
		for(y=0; y < MAP_Y_COUNT; y++) {
			for(x=0; x < MAP_X_COUNT; x++) {
				 //canvas.drawBitmap(DataMgr.MAP_TILE[map[y][x].mFrame], map[y][x].mX, map[y][x].mY, null); 
				canvas.drawBitmap(DataMgr.MAP_TILE[1], map[y][x].mX, map[y][x].mY, null);
			 } 
		} 
	}
	
		
	public int isWalkable(int x, int y) {
		int posX = x;
		int posY = y;

		Log.d("MAP","posX("+x+")="+posX+" posY("+y+")="+posY);
		Log.d("MAP","MAP_X_COUNT="+MAP_X_COUNT+" MAP_Y_COUNT="+MAP_Y_COUNT);
		
		if(posX > (MAP_X_COUNT-1)) {
			Log.d("MAP","isBig if(posX > (MAP_X_COUNT-1))");
			return 0;
		}else if(posX < 0) {
			return 0;
		}
		
		if(posY > (MAP_Y_COUNT-1)) {
			return 0;
		}else if(posY < 0){
			return 0;
		}
		
		return map[posY][posX].mWalkable;
	}
}

별것도 아닌 단순한 예외처리를 보여주려니 부끄럽다;
이 MAP 클래스를 상속받아 MAP_Stage_1을 재 구성했다.


public class MAP_Stage_1 extends MAP{
	
	public int [][] MAP_STAGE_1;
	
	public MAP_Stage_1(int MAP_X, int MAP_Y) {
		super(MAP_X, MAP_Y);
		
		MAP_STAGE_1 = new int[MAP_Y_COUNT][MAP_X_COUNT];
		buildMap();
	}

	public void buildMap() {
		int x = 0;
		int y = 0; // Build MAP 
		
		for(y=0; y < MAP_Y_COUNT; y++) { 
			for(x=0; x < MAP_X_COUNT; x++) {
				MAP_STAGE_1[y][x] = 1;
				map[y][x] = new MAP_Tile(MAP_STAGE_1[y][x], MAP_STAGE_1[y][x], DataMgr.MAP_TILE_WIDTH, DataMgr.MAP_TILE_HEIGHT);
				//map[y][x] = new MAP_Tile(MAP_DATA[y][x], MAP_DATA[y][x], DataMgr.MAP_TILE_WIDTH, DataMgr.MAP_TILE_HEIGHT); 
				map[y][x].setPosition(map[y][x].mWidth*x, map[y][x].mHeight*y); 
			} 
		}
	}
}

생성자 정도로만 구성해 놓고 끝.
Hero Class는 수정된 부분만 일부 끌어다 놓는 것으로 하겠다.


public void getMyCorner(MAP map) {
		downY 	= (int) Math.floor((currentY+DataMgr.HERO_HEIGHT)/DataMgr.MAP_TILE_HEIGHT);
		upY 	= (int) Math.floor((currentY-DataMgr.HERO_HEIGHT)/DataMgr.MAP_TILE_HEIGHT);
		leftX 	= (int) Math.floor((currentX-DataMgr.HERO_WIDTH)/DataMgr.MAP_TILE_HEIGHT);
		rightX 	= (int) Math.floor((currentX+DataMgr.HERO_WIDTH)/DataMgr.MAP_TILE_HEIGHT);
		
		Log.d("YSK", "downY="+downY+" upY="+upY+" leftX="+leftX+" rightX="+rightX);
		Log.d("YSK", "posX="+posX+" posY="+posY);
		
		wall_left 	= map.isWalkable(leftX, posY);
		wall_right 	= map.isWalkable(rightX, posY);
		wall_up 	= map.isWalkable(posX, upY);
		wall_down 	= map.isWalkable(posX, downY);
	}

public void Move()
	{
		state = StateZ.MOVE;
		
		int resultX = currentX;
		int resultY = currentY;
		Log.d("Hero","posX="+posX+" posY="+posY);
		Log.d("Hero","wall_left="+wall_left+" wall_up="+wall_up);
		Log.d("Hero","wall_right="+wall_right+" wall_down="+wall_down);
		
		switch(DataMgr.CURRENT_KEYPAD_DIRECTION) {
		case UI_Enum.KEY_DIRECTION_LEFT:
			if(wall_left == 1)
				resultX-=speed;
			else 
				resultX = ((posX+1) * DataMgr.MAP_TILE_WIDTH) - DataMgr.HERO_WIDTH;				
			break;
			
		case UI_Enum.KEY_DIRECTION_TOP:
			if(wall_up == 1)
				resultY-=speed;
			else 
				resultY = ((posY+1) * DataMgr.MAP_TILE_HEIGHT) - DataMgr.HERO_HEIGHT;				
			break;
		
		case UI_Enum.KEY_DIRECTION_RIGHT:
			if(wall_right == 1)
				resultX+=speed;
			else
				resultX = (posX * DataMgr.MAP_TILE_WIDTH);
			break;
			
		case UI_Enum.KEY_DIRECTION_BOTTOM:
			if(wall_down == 1)
				resultY+=speed;
			else 
				resultY = (posY * DataMgr.MAP_TILE_HEIGHT);
			break;
			
		default:
			break;
		}
		
		Log.d("Hero","resultX="+resultX+" resultY="+resultY);
		setCurrentPos(resultX, resultY);
	}


GetMyCorner()는 상,하,좌,우를 체크하는 함수며 MAP 클래스를 인자로 받고 있어 MAP_Stage_1 클래스를 받을 수 있도록 처리했다. 이를 위해 MAP을 상속받게 처리 했다. Object를 써서 넘기면 되겠는데 아직 그 부분은 해보질 않았고 Object라는 것으로서 소스의 위험이 가는게 싫었다. Move()는 이론에서 설명한 내용이니 따로 이야기 하진 않겠다.


기본 변수는 Tile Based Game을 참고하여 작성했는데 언젠간 내 입맛대로 바꾸는 날이 올것이다.
이제 응용편은 끝났으니 이제 튜토리얼의 내용을 따라해봐야겠다.
댓글
댓글쓰기 폼