Unity3D/TowerDefence 2014.09.04 00:43

오늘은 길찾기 마지막 시간으로 최적화 를 하도록 하겠습니다.

유니티가 느려지는데는 여러가지 원인이 있습니다. 

복잡한 계산을 하거나, 많은양의 데이터를 처리하거나, 많은수의 개체를 화면에 그리거나(DrawCall)
Debug.Log로 많은양의 로그를 남기거나, 많은양의 계산을 하는일 모두 포퍼먼스를 저하시키게 됩니다.

길찾기로직에서 유닛이 많아지면 많아질수록 길찾기 연산에 소요되는 시간이 늘어나게 됩니다.

이 계산시간을 줄이는 방법을 생각해보면,
한번 했던 계산을 다시 하지 않는다면.. 필요없는 계산을 줄일수 있습니다.

우리가 만든 길찾기 로직은 시작위치목표지점 인덱스 만을 가지고 길을 찾고 있습니다.

다시말해서, 같은 목표지점과, 같은 시작위치라면 몇번을 계산해도 똑같은 경로를 가지게 됩니다.

그래서, 한번 검색한 경로의 시작위치와, 목표지점인덱스을 저장해두고, 계산을 하기전에 이미 계산한 경로가 있으면 그경로를 가져다 쓰도록 만드는게 목표입니다.

여기에 덧붙여서 실시간으로 벽을 만들때, 벽을 만들수 있는지 없는지도 지난시간에 체크해서 판단하도록 하였습니다.

그래서 도달할수 있는지를 체크하는 체크모드를 만들어서, 체크모드를 통과할때만 새로운 경로데이터를 갱신하는 모듈까지 함께 만들도록하겠습니다.


PathFinder.cs 파일에 코드를 추가합니다.



우선 조금 복잡한 Dicrionary 형의 checkDic 과 pathDic을 넣어줍니다.

checkDic은 유효한 맵인지 검사하기 위한 임시 Dictionary 이고, 유효한 맵일경우 pathDic으로 복사, 유효하지 않을경우 패기 시켜버릴겁니다.

checkDic 과, pathDic을 데이터 형을 좀 살펴보면..

우선 목표지점 인덱스로 구분할수 있게, int 형 "key" 와 또다시 Dictionary 형의 "value" 가 있습니다.

value에 들어 있는 Dictionary 를 다시 뜯어보면, 시작 포인트좌표로 구분할수 있게, Point 형 "key"와 경로 포인트들을 가지고 있는 List<Point> 형이 "value"로 들어있습니다.

풀어서 반대로 이야기해보면,

탐색한 경로를 시작 포인트를 key로 가지는 그룹을 만들고, 이 그룹들을 도착점을 key로 가지는 그룹으로 만들어 놓는겁니다.

이러면 key값 이 있는지 2번만 검사하면 해당되는 경로가 있는지 없는지 알수 있습니다.



윗쪽 사각형은 체크모드인지 아닌지를 설정합니다. 위에서 말했듯이 checkDic은 임시로 저장해놓은 Dictionary 이기에 Clear시켜줍니다.

아랫쪽의 setPath()는 유효한 맵일경우 기존의 pathDic을 초기화 시키고 checkDic의 값 (유효화 검사를 하면서 검색한 결과값)을 pathDic으로 Copy합니다.


마지막으로 경로를 탐색할때, 기존처럼 검색을 하기전에 이미 검색한 Dictionary 중에 해당되는 조건이 있는지 검색해보고, 해당되는 경로가 있으면 미리 저장된 경로를 리턴시켜줍니다.

만일 해당조건과 맞는 경로가 없다면 기존 방법대로 검색하고, 검색결과를 Dictionary에 저장해줍니다.
이러면, 다음번에 동일한 조건으로 검색을 시도하면 방금 저장한 경로를 리턴시켜 주게 됩니다.


전체 소스를 다시 적어보면.. 아래와 같습니다.


PathFinder.cs


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

using Debug = UnityEngine.Debug;

public class PathFinder{
	private static PathFinder _instance = null;
	public static PathFinder instance
	{
		get{
			if(_instance == null)_instance = new PathFinder();
			return _instance;
		}
	}
	public PathFinder()
	{
		init();
	}

	private Dictionary<int,Dictionary<Point, List<Point>>> checkDic = new Dictionary<int, Dictionary<Point, List<Point>>>();
	private Dictionary<int,Dictionary<Point, List<Point>>> pathDic = new Dictionary<int, Dictionary<Point, List<Point>>>();

	private MapData mapData;
	private int[,] orgMap;
	private bool isSearching = false;
	private int searchCount = 0;
	private Point goalPoint;
	
	public void init()
	{
		mapData = new MapData();
		isSearching = false;
		searchCount =0;
	}
	
	public void setMapData(int[,] _map)
	{
		orgMap = _map;
		mapData.setMapData(orgMap);
	}
	private bool isCheckMode = false;
	public void setCheckMode(bool tf)
	{
		checkDic.Clear();
		isCheckMode = tf;
	}
	public void setPath()
	{
		pathDic.Clear();
		pathDic = new Dictionary<int, Dictionary<Point, List<Point>>>(checkDic);
		
	}
	public Point[] getPath(Point current, int targetIdx)
	{
		Dictionary<int, Dictionary<Point, List<Point>>> myDic;
		if(isCheckMode)myDic = checkDic;
		else myDic = pathDic;
		if(myDic.ContainsKey(targetIdx))
		{
			Dictionary<Point,List<Point>> dic = myDic[targetIdx];
			if(dic.ContainsKey(current))
			{
				return dic[current].ToArray();
			}
		}
		if(!checkInMapPoint(current)||!checkInMapIndex(targetIdx))return null;
		mapData.setMapData(orgMap);
		//Debug.Log("START SEARCHING.. ");
		isSearching = true;
		searchCount = 1;
		setPathCount(new List<Point>(){current}, targetIdx, 100);
		if(goalPoint == null) return null;
		List<Point> pList = findPath();
		if(pList == null) return null;
	
		Dictionary<Point, List<Point>> pDic;

		if(myDic.ContainsKey(targetIdx))
		{
			pDic = myDic[targetIdx];
		}
		else
		{
			pDic = new Dictionary<Point, List<Point>>();
			myDic.Add(targetIdx, pDic);
		}
		pDic.Add(current, pList);
		return pList.ToArray();
	}
	private void setPathCount(List<Point> pList, int targetIdx)
	{
		if(orgMap==null)return;
		setPathCount(pList, targetIdx, orgMap.GetLength(0) * orgMap.GetLength(1));
	}
	private void setPathCount(List<Point> pList, int targetIdx, int max)
	{
		if(!isSearching)return;
		List<Point> _List = new List<Point>();
		foreach(Point p in pList)
		{
			List<Point> retList = recordPath(p, targetIdx);
			if(retList!=null&&retList.Count>0){_List.AddRange(retList);}
		}
		if(_List.Count==0){return;}
		searchCount++;
		if(searchCount>=max){isSearching = false; return;}
		
		setPathCount(_List, targetIdx, max);
	}
	private List<Point> recordPath(Point p, int targetIdx)
	{
		if(!isSearching)return null;
		if(orgMap[p.x, p.y] == targetIdx)
		{
			isSearching = false;
			mapData.map[p.x,p.y] = searchCount;
			goalPoint = p;
			//Debug.Log("SEACHING COMPLETE!!!!   : "+searchCount);
			return null;
		}
		List<Point> rList = null;
		if(mapData.map[p.x,p.y] == 0)
		{
			mapData.map[p.x,p.y] = searchCount;
			rList = new List<Point>();
			getNeighbours(p, ref rList);
		}
		return rList;
	}
	private void getNeighbours(Point p, ref List<Point> rList)
	{
		if(p.x>0 && mapData.map[p.x - 1,p.y] == 0)rList.Add(new Point(p.x-1,p.y));
		if(p.y>0 && mapData.map[p.x,p.y - 1] == 0)rList.Add(new Point(p.x,p.y-1));
		if(p.x<orgMap.GetLength(0)-1 && mapData.map[p.x + 1, p.y] == 0)rList.Add(new Point(p.x+1,p.y));
		if(p.y<orgMap.GetLength(1)-1 && mapData.map[p.x, p.y + 1] == 0)rList.Add(new Point(p.x,p.y+1));
	}
	private List<Point> findPath()
	{
		List<Point> pathList = new List<Point>();
		Point temPoint = goalPoint;
		int _count = searchCount - 1;
		while(_count>0)
		{
			pathList.Insert(0,temPoint.clone());
			getPathPoint(ref temPoint, _count);
			_count --;
		}
		pathList.Insert(0,temPoint);
		if(isSearching)
		{
			isSearching = false;
			return null;
		}
		return pathList;
	}
	private void getPathPoint(ref Point p, int count)
	{
		if(p.y<mapData.map.GetLength(1)-1 && mapData.map[p.x, p.y+1] == count)
		{
			p.y += 1;
			return;
		}
		if(p.x<mapData.map.GetLength(0)-1 && mapData.map[p.x+1, p.y] == count)
		{
			p.x += 1;
			return;
		}
		if(p.y>0 && mapData.map[p.x, p.y-1] == count)
		{
			p.y -= 1;
			return;
		}
		if(p.x>0 && mapData.map[p.x-1, p.y] == count)
		{
			p.x -= 1;
			return;
		}
	}
	private bool checkInMapPoint(Point p)
	{
		if(orgMap==null)return false;
		if(p.x<0||p.y<0)return false;
		if(p.x >= orgMap.GetLength(0) ||p.y >= orgMap.GetLength(1))return false;
		return true;
	}
	private bool checkInMapIndex(int idx)
	{
		if(orgMap==null)return false;
		foreach(int _idx in orgMap)
		{
			if(idx == _idx)return true;
		}
		return false;
	}
}


public class MapData
{
	public int[,] map;
	public MapData()
	{
	}
	public void setMapData(int[,] _map)
	{
		int w = _map.GetLength(0);
		int h = _map.GetLength(1);
		map = new int[w, h];
		int x,y;
		
		for(x = 0; x < w; x++)
		{
			for(y = 0; y < h; y++)
			{
				if(_map[x,y] > 0 && _map[x,y] < 10)
					map[x,y] = -1;
				else
					map[x,y] = 0;
			}
		}
	}
}



마지막으로 GameManager.cs의 checkReachAble() 함수에 다음과 같이 체크모드를 활성, 비활성 화해주는 코드를 추가합니다.



이제 다시 플레이해보면 이전보다 훨씬 부드러워진 유닛들을 볼수 있습니다.




신고
posted by andwhy