반응형
2022.06.06 - [응용프로그래밍/유니티기초] - [유니티2D] 타일맵을 이용하여 배열 맵 구축
타일맵을 이용하여 배열맵 구축한 것을 이용하여 player와 보물을 두고 player가 최단 거리로 찾아가는 방법에 대해 살펴 보자.
에셋스토어에서 시티 팩-하향식-픽셀 아트 를 다운 받아서 자동차를 player로 사용했다.
시티팩을 적용하면서 기존에 만들었던 씬이 삭제 되어서 다시 대충 만들었다. 좌하에 자동차를 놓았고. 우상에 건물을 놓았다.
자동차를 tag를 player로 두고 건물을 layer에 target 으로 만든다.
GameManager 에 다음과 같이 Target 을 추가
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Target{
public int x,y;
}
public class GameManager : MonoBehaviour
{
// 외부에서 싱글톤 오브젝트를 가져올때 사용할 프로퍼티
public static GameManager instance
{
get
{
// 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
if (m_instance == null)
{
// 씬에서 GameManager 오브젝트를 찾아 할당
m_instance = FindObjectOfType<GameManager>();
}
// 싱글톤 오브젝트를 반환
return m_instance;
}
}
private static GameManager m_instance; // 싱글톤이 할당될 static 변수
private int[,] mapArray;
private float _sizeX=0,_sizeY=0;
private Target target;
public float sizeX{
get{return _sizeX;}
set{
_sizeX=value;
Debug.Log("_sizeX : " + _sizeX.ToString());
}
}
public float sizeY{
get{return _sizeY;}
set{
_sizeY=value;
Debug.Log("_sizeY : " + _sizeY.ToString());
}
}
public void MakeArray()
{
mapArray=new int[(int)sizeX,(int)sizeY];
string output="";
target=new Target();
for (int i = 0; i < (int)sizeX; i++)
{
for (int j = 0; j < (int)sizeY; j++)
{
bool isWall = false;
foreach (Collider2D col in Physics2D.OverlapCircleAll(new Vector2(i, j), 0.4f)){
if (col.gameObject.layer == LayerMask.NameToLayer("wall")){
isWall = true;
//Debug.Log("Wall : " + i.ToString() + "," + j.ToString());
}
else if(col.gameObject.layer == LayerMask.NameToLayer("target")){
target.x=i;
target.y=j;
Debug.Log("Target : " + i.ToString() + "," + j.ToString());
}
else if (col.gameObject.tag == "Player") Debug.Log("Player : " + i.ToString() + "," + j.ToString());
}
mapArray[i, j] = isWall?1:0;
output+=mapArray[i, j].ToString();
}
output+="\n";
}
Debug.Log(output);
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
큐를 활용해서 BFS 방식으로 다음과 같이 출발지에서 도착지까지 가는 경로를 찾아 오자.
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent; //queue 사용하기 위해 추가
using UnityEngine;
public class Target{
public int x,y;
public int preX,preY; //이전 위치
public Target(){}
public Target(int _x,int _y,int _preX,int _preY){
x=_x;
y=_y;
preX=_preX;
preY=_preY;
}
}
public class GameManager : MonoBehaviour
{
// 외부에서 싱글톤 오브젝트를 가져올때 사용할 프로퍼티
public static GameManager instance
{
get
{
// 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
if (m_instance == null)
{
// 씬에서 GameManager 오브젝트를 찾아 할당
m_instance = FindObjectOfType<GameManager>();
}
// 싱글톤 오브젝트를 반환
return m_instance;
}
}
private static GameManager m_instance; // 싱글톤이 할당될 static 변수
private int[,] mapArray,visited;
private float _sizeX=0,_sizeY=0;
private Target target,start;
private Target[,] path;
private List<Target> FinalTargetList;
public float sizeX{
get{return _sizeX;}
set{
_sizeX=value;
Debug.Log("_sizeX : " + _sizeX.ToString());
}
}
public float sizeY{
get{return _sizeY;}
set{
_sizeY=value;
Debug.Log("_sizeY : " + _sizeY.ToString());
}
}
public void MakeArray()
{
mapArray=new int[(int)sizeX,(int)sizeY];
visited=new int[(int)sizeX,(int)sizeY];
path = new Target[(int)sizeX,(int)sizeY];
FinalTargetList = new List<Target>();
string output="";
target=new Target();
start =new Target();
for (int i = 0; i < (int)sizeX; i++)
{
for (int j = 0; j < (int)sizeY; j++)
{
bool isWall = false;
foreach (Collider2D col in Physics2D.OverlapCircleAll(new Vector2(i, j), 0.4f)){
if (col.gameObject.layer == LayerMask.NameToLayer("wall")){
isWall = true;
//Debug.Log("Wall : " + i.ToString() + "," + j.ToString());
}
else if(col.gameObject.layer == LayerMask.NameToLayer("target")){
target.x=i;
target.y=j;
Debug.Log("Target : " + i.ToString() + "," + j.ToString());
}
else if (col.gameObject.tag == "Player") {
start.x=i;
start.y=j;
Debug.Log("Player : " + i.ToString() + "," + j.ToString());
}
}
mapArray[i, j] = isWall?1:0;
output+=mapArray[i, j].ToString();
}
output+="\n";
}
//Debug.Log(output);
}
public List<Target> GetTargetPath(){
Queue<Target> q = new Queue<Target>();
q.Enqueue(new Target(start.x,start.y,-1,-1));
visited[start.x,start.y]=1; //방문 했다.
path[start.x,start.y]=new Target(start.x,start.y,-1,-1);
Target node;
while(q.Count>0){
node = q.Dequeue(); //하나를 뽑는다.
if(node.x==target.x && node.y==target.y){
break; //도착지에 도착 했다.
}
//Debug.Log("GetTargetPath : " + node.x.ToString() + "," + node.y.ToString());
if(node.x + 1 < sizeX){ //범위체크 x축으로 1만큼 이동
if(visited[node.x+1,node.y]==0 && mapArray[node.x+1,node.y]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x+1,node.y,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x+1,node.y]=1;
path[node.x+1,node.y]=new Target(node.x+1,node.y,node.x,node.y);
}
}
if(node.x - 1 >= 0){ //범위체크 x축으로 1만큼 이동
if(visited[node.x-1,node.y]==0 && mapArray[node.x-1,node.y]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x-1,node.y,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x-1,node.y]=1;
path[node.x-1,node.y]=new Target(node.x-1,node.y,node.x,node.y);
}
}
if(node.y + 1 < sizeY){ //범위체크 x축으로 1만큼 이동
if(visited[node.x,node.y+1]==0 && mapArray[node.x,node.y+1]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x,node.y+1,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x,node.y+1]=1;
path[node.x,node.y+1]=new Target(node.x,node.y+1,node.x,node.y);
}
}
if(node.y - 1 >= 0){ //범위체크 x축으로 1만큼 이동
if(visited[node.x,node.y-1]==0 && mapArray[node.x,node.y-1]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x,node.y-1,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x,node.y-1]=1;
path[node.x,node.y-1]=new Target(node.x,node.y-1,node.x,node.y);
}
}
}
MakePath(target.x,target.y);
return FinalTargetList;
}
public void MakePath(int x, int y){
if(x<=0 || y<=0) return;
Debug.Log("MakePath : " + path[x,y].preX.ToString() + "," + path[x,y].preY.ToString());
MakePath(path[x,y].preX,path[x,y].preY);
FinalTargetList.Add(new Target(x,y,0,0));
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
다음과 같이 GetTargetPath() 를 호출하면 이동 경로가 출력 되는 것을 확인 할 수 있다.
List<Target> targetpath = GameManager.instance.GetTargetPath();
for (int i = 0; i < targetpath.Count; i++) Debug.Log(i + "번째는 " + targetpath[i].x + ", " + targetpath[i].y);
자동차에 Gamemanager 스크립트를 추가한 후에 다음과 같이 MoveStart 추가후
background 에서 MoveStart 호출하여 자동차가 이동하는 경로를 살펴 보자.
using System.Collections;
using System.Collections.Generic;
using System.Collections.Concurrent; //queue 사용하기 위해 추가
using UnityEngine;
public class Target{
public int x,y;
public int preX,preY; //이전 위치
public Target(){}
public Target(int _x,int _y,int _preX,int _preY){
x=_x;
y=_y;
preX=_preX;
preY=_preY;
}
}
public class GameManager : MonoBehaviour
{
// 외부에서 싱글톤 오브젝트를 가져올때 사용할 프로퍼티
public static GameManager instance
{
get
{
// 만약 싱글톤 변수에 아직 오브젝트가 할당되지 않았다면
if (m_instance == null)
{
// 씬에서 GameManager 오브젝트를 찾아 할당
m_instance = FindObjectOfType<GameManager>();
}
// 싱글톤 오브젝트를 반환
return m_instance;
}
}
private static GameManager m_instance; // 싱글톤이 할당될 static 변수
private int[,] mapArray,visited;
private float _sizeX=0,_sizeY=0;
private Target target,start;
private Target[,] path;
private List<Target> FinalTargetList;
public float sizeX{
get{return _sizeX;}
set{
_sizeX=value;
Debug.Log("_sizeX : " + _sizeX.ToString());
}
}
public float sizeY{
get{return _sizeY;}
set{
_sizeY=value;
Debug.Log("_sizeY : " + _sizeY.ToString());
}
}
public void MakeArray()
{
mapArray=new int[(int)sizeX,(int)sizeY];
visited=new int[(int)sizeX,(int)sizeY];
path = new Target[(int)sizeX,(int)sizeY];
FinalTargetList = new List<Target>();
string output="";
target=new Target();
start =new Target();
for (int i = 0; i < (int)sizeX; i++)
{
for (int j = 0; j < (int)sizeY; j++)
{
bool isWall = false;
foreach (Collider2D col in Physics2D.OverlapCircleAll(new Vector2(i, j), 0.4f)){
if (col.gameObject.layer == LayerMask.NameToLayer("wall")){
isWall = true;
//Debug.Log("Wall : " + i.ToString() + "," + j.ToString());
}
else if(col.gameObject.layer == LayerMask.NameToLayer("target")){
target.x=i;
target.y=j;
Debug.Log("Target : " + i.ToString() + "," + j.ToString());
}
else if (col.gameObject.tag == "Player") {
start.x=i;
start.y=j;
Debug.Log("Player : " + i.ToString() + "," + j.ToString());
}
}
mapArray[i, j] = isWall?1:0;
output+=mapArray[i, j].ToString();
}
output+="\n";
}
//Debug.Log(output);
}
public List<Target> GetTargetPath(){
Queue<Target> q = new Queue<Target>();
q.Enqueue(new Target(start.x,start.y,-1,-1));
visited[start.x,start.y]=1; //방문 했다.
path[start.x,start.y]=new Target(start.x,start.y,-1,-1);
Target node;
while(q.Count>0){
node = q.Dequeue(); //하나를 뽑는다.
if(node.x==target.x && node.y==target.y){
break; //도착지에 도착 했다.
}
//Debug.Log("GetTargetPath : " + node.x.ToString() + "," + node.y.ToString());
if(node.x + 1 < sizeX){ //범위체크 x축으로 1만큼 이동
if(visited[node.x+1,node.y]==0 && mapArray[node.x+1,node.y]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x+1,node.y,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x+1,node.y]=1;
path[node.x+1,node.y]=new Target(node.x+1,node.y,node.x,node.y);
}
}
if(node.x - 1 >= 0){ //범위체크 x축으로 1만큼 이동
if(visited[node.x-1,node.y]==0 && mapArray[node.x-1,node.y]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x-1,node.y,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x-1,node.y]=1;
path[node.x-1,node.y]=new Target(node.x-1,node.y,node.x,node.y);
}
}
if(node.y + 1 < sizeY){ //범위체크 x축으로 1만큼 이동
if(visited[node.x,node.y+1]==0 && mapArray[node.x,node.y+1]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x,node.y+1,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x,node.y+1]=1;
path[node.x,node.y+1]=new Target(node.x,node.y+1,node.x,node.y);
}
}
if(node.y - 1 >= 0){ //범위체크 x축으로 1만큼 이동
if(visited[node.x,node.y-1]==0 && mapArray[node.x,node.y-1]==0) { //방문하지 않았고 이동 가능하다면
q.Enqueue(new Target(node.x,node.y-1,node.x,node.y)); //큐에 넣고 방문표시
visited[node.x,node.y-1]=1;
path[node.x,node.y-1]=new Target(node.x,node.y-1,node.x,node.y);
}
}
}
MakePath(target.x,target.y);
return FinalTargetList;
}
public void MakePath(int x, int y){
if(x<=0 || y<=0) return;
//Debug.Log("MakePath : " + path[x,y].preX.ToString() + "," + path[x,y].preY.ToString());
MakePath(path[x,y].preX,path[x,y].preY);
FinalTargetList.Add(new Target(x,y,0,0));
}
public void MoveStart(){
Debug.Log("MoveStart");
GetTargetPath(); // Path 를 가져 오자.
StartCoroutine(MoveCar()) ;
}
IEnumerator MoveCar(){
Debug.Log("MoveCar");
for (int i = 0; i < FinalTargetList.Count; i++){
transform.position = new Vector3(FinalTargetList[i].x,FinalTargetList[i].y,0);
Debug.Log(i + "번째는 " + FinalTargetList[i].x + ", " + FinalTargetList[i].y);
yield return new WaitForSeconds(1);
}
}
// Start is called before the first frame update
void Start()
{
}
// Update is called once per frame
void Update()
{
}
}
반응형
'응용프로그래밍 > 유니티기초' 카테고리의 다른 글
[유니티2D] 코루틴을 이용해서 경로를 찾는 것을 표현 (0) | 2022.06.07 |
---|---|
[유니티2D] 네비게이션 길의 경로 표시하기 (0) | 2022.06.07 |
[유니티2D] 타일맵을 이용하여 배열 맵 구축 (0) | 2022.06.06 |
[유니티2D 활용] 테트리스 게임 만들기 (0) | 2022.06.03 |
[유니티2D활용]지뢰찾기 게임 만들기 (0) | 2022.06.01 |