지난시간에 이어서 

오늘은 유닛들이 런타임에서 실시간으로 길을 찾아갈수 있도록 하겠습니다.


1. 실시간으로 벽만들기.

가장 처음으로 게임실행시 맵클릭으로, 길을 막았다 다시 만드는 스크립트를 작성하겠습니다.


GameManager.cs 에


public BgCellDisplayer bgGrid;
public Camera mainCam;

를 추가하고 인스팩터에 각각 BG와 camera를 드래그 합니다.




아래와 같이 Update(), 와 switchWall(Point p)을 추가 해줍니다.


이번에는 

BgCellDisplayer.cs파일에 refreshDisplay()함수를 추가해줍니다.

기존 코드와 겹치는 부분이 있어서,showBgCell()부분도 수정을 하였습니다.


using UnityEngine;
using System.Collections;

public class BgCellDisplayer : MonoBehaviour {
	public BgCell bgcell;

	private BgCell[,] bgCellArr;
	private GameManager gm;

	// Use this for initialization
	void Start () {
		gm = GameManager.instance;
		showBgCells();
	}
	private void showBgCells()
	{
		int _w = gm.wallMap.GetLength(0);
		int _h = gm.wallMap.GetLength(1);
		bgCellArr = new BgCell[_w,_h];
		int x,y;
		for (x = 0; x < _w; x++)
		{
			for(y = 0; y < _h; y++)
			{
				BgCell bc = Instantiate (bgcell) as BgCell;
				bc.transform.parent = this.transform;
				bc.transform.localPosition = new Vector3( 40 * x, -40 * y, 0);
				bgCellArr[x,y] = bc;
			}
		}
		refreshDisplay();
	}
	public void refreshDisplay()
	{
		int _w = gm.wallMap.GetLength(0);
		int _h = gm.wallMap.GetLength(1);
		int x,y;
		for (x = 0; x < _w; x++)
		{
			for(y = 0; y < _h; y++)
			{
				
				BgCell bc = bgCellArr[x,y];
				if(gm.wallMap[x,y] < 10 && gm.wallMap[x,y] != 0)
				{
					bc.isVisible = false;
				}
				else if(gm.wallMap[x,y] >= 10 && gm.wallMap[x,y] <= 100)
				{
					bc.isVisible = true;
					bc.setStart();
				}
				else if(gm.wallMap[x,y] >= 110)
				{
					bc.isVisible = true;
					bc.setGoal();
				}
				else
				{
					bc.isVisible = true;
					bool isBlack = ((x + (y%2))%2 == 1);
					bc.setBlack(isBlack);
				}
			}
		}
	}
}

이상태로 플레이를 해보면 화면상의 map을 클릭했을때, 길이 없어졌다 생겼다 하는것을 볼수 있습니다.

이상태에서 유닛을 추가하면, 새로 추가된 유닛들은 제대로 길을찾아 가지만, 기존에 이미 추가된 유닛들은 벽을 무시하고 원래 검색된 경로로 움직이는것을 볼수 있습니다.

또한, 시작점에서 도착지까지 갈수 있는길을 없애버리고, 유닛을 추가하면 에러가 발생합니다.

이번에는 GameManager.cs, UnitBase.cs파일을 수정해서, 시작점에서 도착점까지 갈수 없는 일이 생기지 않고, 화면상의 모든유닛들에게 맵이변경될경우 현재 위치부터 맵을 재 검색하도록 수정하겠습니다. 


UnitBase.cs 파일에서 간단하게 getPath() 함수만 void형에서 bool 형을 리턴하도록 변경하여, 
갈수 있는 길이 있으면 true, 갈수 있는길이 없을땐 false를 리턴하게 할겁니다.



GameManager.cs


using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using common;

public class GameManager : MonoBehaviour {
	private static GameManager _instance = null;
	public static GameManager instance
	{
		get{
			if(_instance == null)Debug.LogError("GameManager is NULL");
			return _instance;
		}
	}

	void Awake()
	{
		_instance = this;
		init();
	}
	//=========================================
	public float cellSize = 40.0f;
	public int[,] wallMap = 
	{{1, 1, 1, 1, 1, 11, 11, 11, 1, 1, 1, 1, 1},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
		{1, 1, 1, 1, 1, 111, 111, 111, 1, 1, 1, 1, 1}};
	[HideInInspector]
	public List<UnitBase> unitList = new List<UnitBase>();
	public Transform unit_field;
	public UnitBase unit_marine;
	public BgCellDisplayer bgGrid;
	public Camera mainCam;

	private void init()
	{
		unitList.Clear();

		initPathFinder();
	}
	void addUnit()
	{
		UnitBase unit = Instantiate(unit_marine) as UnitBase;
		unitList.Add(unit);
		unit.transform.parent = unit_field;
		Point startPoint = getStartPoint();
		unit.transform.localPosition = new Vector3(startPoint.x * cellSize + cellSize/2.0f, -startPoint.y * cellSize - cellSize/2.0f);
		unit.setStartPoint(startPoint);

	}
	public void removeUnit(UnitBase ub)
	{
		unitList.Remove(ub);
	}
	void researchPathUnits()
	{
		foreach(UnitBase ub in unitList)
		{
			if(ub!=null)
			{
				ub.getPath();
			}
		}
	}
	Point getStartPoint()
	{
		List<Point> startPointList = getStartPointList();
		if(startPointList.Count == 0)
		{
			Debug.LogError("Not Found Start Position");
			return null;
		}
		int ranIdx = Random.Range(0,startPointList.Count);
		return startPointList[ranIdx];
	}
	List<Point> getStartPointList()
	{
		//check startPoints
		List<Point> startPointList = new List<Point>();
		int _w = wallMap.GetLength(0);
		int _h = wallMap.GetLength(1);
		int x,y;
		for (x = 0; x < _w; x++)
		{
			for(y = 0; y < _h; y++)
			{
				if(wallMap[x,y] >= 10 && wallMap[x,y] <= 100)
				{
					startPointList.Add(new Point(x,y));
				}
			}
		}
		return startPointList;
	}
	public void initPathFinder()
	{
		PathFinder.instance.setMapData(wallMap);
	}
	void OnGUI()
	{
		if(GUI.Button( new Rect( 10, 10, 100, 40), "Add Unit"))
		{
			addUnit();
		}
	}
	void Update()
	{
		if(Input.GetMouseButtonDown(0))
		{
			Vector2 pos = Input.mousePosition;
			Vector3 mouseP = mainCam.ScreenToWorldPoint(pos) - unit_field.TransformPoint(Vector3.zero);
			Point myPos = new Point((int)(mouseP.x/cellSize), -(int)(mouseP.y/cellSize));
			switchWall(myPos);
		}
	}
	bool checkReachAble(Point p)
	{
		foreach(Point sp in getStartPointList())
		{
			if(PathFinder.instance.getPath(sp,100+wallMap[sp.x,sp.y]) == null)
			{
				Debug.Log("StartPoint Path NULL");
				return false;
			}
		}
		foreach(UnitBase unit in unitList)
		{
			if(!unit.getPath())
			{
				Debug.Log("Unit Path NULL");
				return false;
			}
		}
		
		return true;
	}
	void switchWall(Point p)
	{
		if(p.x<0||p.y<0 || p.x >= wallMap.GetLength(0) || p.y >= wallMap.GetLength(1))return;
		int prevIndex = wallMap[p.x, p.y];
		
		if(wallMap[p.x, p.y] == 0)wallMap[p.x, p.y] = 2;
		else if(wallMap[p.x, p.y] == 2)wallMap[p.x, p.y] = 0;
		
		
		if(checkReachAble(p))
		{
			bgGrid.refreshDisplay();
			researchPathUnits();
		}
		else 
		{
			wallMap[p.x, p.y] = prevIndex;
		}
	}
}


함수들

void addUnit()
유닛이 추가될때마다 
unitList에 추가 시킵니다.

public void removeUnit(UnitBase ub)
끝까지 도착한 유닛을 
unitList에서 제외시킵니다.

void researchPathUnits()
unitList에 등록된 Unit들에게 현재 위치에서 목표지점까지의 경로검색을 다시 명령합니다.

Point getStartPoint(),List<PointgetStartPointList()
기존엔 
getStartPoint()하나였지만, StartPoint들의 위치가 필요해서 나눴습니다.

bool checkReachAble()
현재 맵상태에서 모든시작점에서 목적지까지 갈수 있는 경로가 있는지 검사하고, 모든 유닛들이 목적지까지 갈수 있는 경로가 있는지 검사합니다.

void switchWall(Point p)
p좌표의 맵을 벽으로 바꿔도 되는지 검사하고, 땅은 벽으로, 벽은 땅으로 스위치 시킵니다.
(시작전부터 벽이였던 공간과 구분하기 위해 "2"로 셋팅합니다.)

이상태로 플레이를 해보면, 길을 없게 땅을 바꿀수도 없고, 이미 추가된 유닛들도 맵이 변형될때마다 경로를 다시 검색하는것을 볼수 있습니다.


하지만, 유닛들이 움직임이 어딘가 이상해서 수정하도록하겠습니다.


UnitBase.cs 의 update부분을 

에서 



처럼 수정합니다.

코드를 정리했을뿐 크게 수정된 부분은 없습니다.

추가된 부분은 표시된 부분처럼 더이상 갈 경로가 없을때( 목적지에 도착했을때..) GameManager의 UnitList 에서 자기 자신을 제외시킵니다.


가장 중요하게 바뀐부분은 getPath() 부분인데,


표시된 두 부분이 추가되었습니다.

윗쪽부분의 내용은 현재 유닛이 서있는땅이 벽일경우(유닛위에 벽이 만들어질경우) 경로를 재탐색하지 않고, 기존경로를 유지하도록 해주었고(탐색해봤자 갈곳이 없어서 에러가 발생합니다.)

아랫쪽부분의 경우 현재 유닛이 재탐색한 경로의 첫부분과, 기존경로의 첫부분이 같다면 가던길을 가게 하고, 다르다면 유닛을 되돌아갈수있도록 경로설정을 해주었습니다.


만일 아래 그림처럼 유닛의 기존경로가 (1, 1), (2, 1), (3, 1)..... 의 경로로 움직이는상황에서,
단기적으로 봤을때 유닛의 현재 시작점과 목적지는 (1, 1) 과 (1, 2) 가 됩니다.


새로운 경로를 탐색했을때 (1,1), (1, 0)... 의 순으로 가야한다면 유닛은 아래그림처럼 시작점과 도착점을 재설정하고, 먼저 시작점으로 돌아가야합니다.



하지만, 재탐색 했을떄 기존경로와 동일하게, (1, 1), (2, 1).... 순으로 경로가 지정된다면 시작점으로 돌아가는것이 오히려 어색하게 됩니다.

이런경우를 막기 위해 재탐색 전의 목적지와, 재탐색후의 목적지가 동일하면 목적지로 지정하고, 
목적지가 다르다면 시작점으로 돌아가서 새로운 목적지를 찾아가도록 변경하였습니다.


전체소스는 다음과 같습니다.



UnitBase.cs


using UnityEngine;
using System.Collections;
using common;

public class UnitBase : MonoBehaviour {

	public float moveSpeed = 1.0f;
	public tk2dSpriteAnimator spr;
	private enum CHAR_ANI {UP, DOWN, LEFT, RIGHT, DESTROY};
	private string[] charAniStr = new string[]{"walk_up","walk_down","walk_left","walk_right","destroy"};
	// Use this for initialization
	void Start () {
	}
	private bool isMoveAble = false;
	// Update is called once per frame
	void Update () {
		if(!isMoveAble)return;
		float cellSize = GameManager.instance.cellSize;
		float _speed = cellSize * Time.deltaTime * moveSpeed;
		float rx = (nextPoint.x * cellSize + cellSize/2.0f) - this.transform.localPosition.x;
		float ry = (-nextPoint.y * cellSize - cellSize/2.0f) - this.transform.localPosition.y;
		float dx = _speed * makeNomal(rx);
		float dy = _speed * makeNomal(ry);
		bool isCloseX = false;
		bool isCloseY = false;
		if(Mathf.Abs(dx)>Mathf.Abs(rx)||dx==0){dx = rx;isCloseX = true;}
		if(Mathf.Abs(dy)>Mathf.Abs(ry)||dy==0){dy = ry;isCloseY = true;}
		this.transform.localPosition += new Vector3(dx , dy , 0);
		if(isCloseX && isCloseY)
		{
			if(pathArr.Length <= pathIndex + 1)
			{
				isMoveAble = false;
				GameManager.instance.removeUnit(this);
				Destroy(this.gameObject);
				return;
			}
			setNextPoint();
		}
	}
	int makeNomal(float f)
	{
		float k = 0.1f;
		if(f>k)return 1;
		else if(f<-k)return -1;
		else return 0;
	}
	private Point[] pathArr;
	private Point startPoint;
	private Point nextPoint;
	private int pathIndex =0;
	public void setStartPoint(Point p)
	{
		startPoint = p;
		getPath();
		nextPoint = pathArr[pathIndex];
		showCharDir();
		isMoveAble = true;
	}
	public bool getPath()
	{
		float cellSize = GameManager.instance.cellSize;
		startPoint = new Point((int)(this.transform.localPosition.x/cellSize),-(int)(this.transform.localPosition.y/cellSize) );
		int wallMapIndex = GameManager.instance.wallMap[startPoint.x,startPoint.y];
		if(wallMapIndex > 0 && wallMapIndex < 10)return true;

		Point[] pArr = PathFinder.instance.getPath(startPoint, 111);
		if(pArr == null){Debug.Log("NULL path");return false;}
		pathArr = pArr;

		if(nextPoint != null && pathArr.Length > 1 && nextPoint.isEqual(pathArr[1]))pathIndex = 1;
		else pathIndex = 0;
		nextPoint = pathArr[pathIndex];
		showCharDir();
		return true;
	}
	private void setNextPoint()
	{
		startPoint = nextPoint;
		pathIndex++;
		nextPoint = pathArr[pathIndex];
		showCharDir();
	}
	private void showCharDir()
	{
		
		float cellSize = GameManager.instance.cellSize;
		float nx = (nextPoint.x * cellSize + cellSize/2.0f);
		float ny = (-nextPoint.y * cellSize - cellSize/2.0f);
		if(this.transform.localPosition.x<nx)
			spr.Play (charAniStr[(int)CHAR_ANI.RIGHT]);
		else if(this.transform.localPosition.x>nx)
			spr.Play (charAniStr[(int)CHAR_ANI.LEFT]);
		else if(this.transform.localPosition.y<ny)
			spr.Play (charAniStr[(int)CHAR_ANI.UP]);
		else if(this.transform.localPosition.y>ny)
			spr.Play (charAniStr[(int)CHAR_ANI.DOWN]);
		spr.ClipFps *= moveSpeed;
	}
}


유닛의 방향을 정해주는 함수도, 약간 수정되었는데, 기존은 포인트 정보만으로 방향을 정했지만, 이번엔 실제 유닛의 x,y 좌표를 가지고 방향을 잡아주도록 변경하였습니다.


여기까지 하고 플레이를 해보면,특별한 문제 없이 원하는대로 모든유닛이 길을 잘찾아가는걸 볼수 있습니다.



헌데, 유닛이 많아지면 많아질수록 계산이 느려지는걸 알수 있습니다.

다음시간엔 같은계산을 최소화 시켜서 길찾기를 최적화 해보도록 하겠습니다.



신고
Posted by andwhy

블로그 이미지
andwhy 개인 블로그.
andwhy

공지사항

Yesterday24
Today11
Total108,404

달력

 « |  » 2017.12
          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            

글 보관함