2025년, 코딩은 선택이 아닌 필수!

2025년 모든 학교에서 코딩이 시작 됩니다. 먼저 준비하는 사람만이 기술을 선도해 갑니다~

응용프로그래밍/유니티기초

[유니티2D] 배열맵을 이용하여 출발지에서 목표로 이동하는 네비게이션 만들기

파아란기쁨1 2022. 6. 6. 19:06
반응형

2022.06.06 - [응용프로그래밍/유니티기초] - [유니티2D] 타일맵을 이용하여 배열 맵 구축

 

[유니티2D] 타일맵을 이용하여 배열 맵 구축

위의 파일을 다운로드 하여 프로젝트 추가 하이어라키 -> 2D Object -> Tilemap-> Rectangular 선택하여 타일맵 추가 Open Tile Plalette 를 클릭하여 타일맵 팔렛트를 오픈 팔렛트가 뜨면 아래 이미지를 모두..

thinkmath2020.tistory.com

타일맵을 이용하여 배열맵 구축한 것을 이용하여 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()
    {
        
    }
}

 

 

반응형