클래스 설계 및 함수

클래스는 전체적인 게임 플레이를 진행하고, 맵을 관리할 GameManager와 지렁이의 각 파트를 담당할 Snake, 적군을 담당할 Enermy로 이루어져있습니다.


1.GameManager

 // 전체적인 게임 플레이와 맵 관리.
public class GameManager : MonoBehaviour {

    int[,] map;
    bool isOut;
    List<snake> snake;
    List<enermy> enermy;

    // 매 틱마다 지렁이와 적군을 움직입니다.
    // 지렁이 헤드 위치에 따라 먹이를 먹거나, 게임을 종료시킵니다.
    // 지렁이를 방향으로 이동, 애니메이션 진행 후 
    // 만약 밖으로 나와있다면 적군의 경로 탐색으로 이동 좌표를 얻은다음 배열에 반영-> 애니메이션
    // 밖으로 나오는 버튼을 클릭하지 않은 상태면 목표지점에 지렁이가 밖에 있는지 확인후 위의 플로우 진행.
    public IEnumerator Tick()
    {

        yield return null;
    }
    // 맵을 랜덤하게 생성.
    public void MakeMap(ref int[,] _map)
    {

    }

    // 먹이를 먹었을 때, Snake 길이를 늘리는 함수
    public void Eat()
    {

    }

    // 버튼을 통해 지렁이의 이동방향을 변경합니다.
    // 동,남,서,북 차례로 방향 배열을 만들고, +- 1을 해준다.
    public void TurnLeftButton()
    {

    }
    public void TurnRightButton()
    {

    }

    // 지상으로 나가기 버튼 
    // 버튼을 누른동안에는 true, 아니라면 false
    public void TurnShowButton(bool isDown)
    {

    }
}


2. Snake

 // 지렁이의 각 부분을 담당하는 클래스
// 이동 좌표를 저장하고 좌표로 이동하는 애니메이션을 동작한다.
public class Snake : MonoBehaviour {
    // 이동할 좌표.
    IntVector2 currentPosition;
    // 밖으로 나왔는가
    bool isOut;


	public void SetSnakePosition(IntVector2 _current,bool _isOut)
    {

    }

    // 배정된 좌표로 이미지를 이동시킨다.
    public IEnumerator MoveSnake()
    {

        yield return null;
    }
}




3.Enermy


 // 움직이는 방해물
public class Enermy : MonoBehaviour {
    IntVector2 currentPosition;
    IntVector2 nextPosition;

    IntVector2 destination;
    int[,] findMap;

    // 목표물로부터 최단거리를 검색해 다음 이동할 타일을 반환.
    // findMap에서 주변 4방향 중 가장 작은 값이 있는곳으로 이동. (자신이 지나온곳은 100으로 변경)
    public IntVector2 FindNextTile(int[,] _mainMap,IntVector2 _destination)
    {
        return new IntVector2();
    }
    // 목표지점이 바뀌면 findMap을 새로 생성한다.(너비탐색)
    public void MakeFindMap(ref int[,] _findMap, int[,] _mainMap, IntVector2 _startPos, IntVector2 _destination)
    {

    }
    // 목표지점에 여전히 지렁이가 지상에 있는지 확인.
    public bool IsHide(int _map)
    {
        return false;
    }
    // 배정된 좌표로 이미지를 이동시킨다.
    public IEnumerator MoveEnermy()
    {

        yield return null;
    }
}




* 지적은 언제나 환영입니다.

Posted by 검은거북

고전의 고전의 고전 게임 지렁이 게임!!!  다만 지렁이 게임 자체의 로직은 단순하고 아마 검색하면 많이 나오지않을까..?싶네요. 그래서 저는 조금 룰을 추가하고, 연출을 추가해서 만들도록 하겠습니다. (별반 차이는 없을거 같지만)


1. 기획

맵 타일 16*16 (벽 타일을 고려하여 18*18)

  1.  맵은 먹이, 고정 방해물과 움직이는 방해물로 이루어져있다.
  2.  먹이는 하나를 먹으면 새로 하나가 랜덤한 곳에 배치된다.
  3.  방해물은 최초 배치된 갯수가 유지되며, 움직이는 방해물은 지렁이가 2칸 이동할때 1칸 이동한다.
  4.  지렁이는 직선으로만 이동하고, 오른쪽 왼쪽 버튼을 통해 직각으로 방향전환을 할 수 있다.
    1.  지렁이는 항상 중앙에서 위로 출발하며 시작한다.
  5.  지렁이는 평소 땅 속에 있다가 버튼을 통해 지상으로 나온다. 지상으로 나오면 먹이를 먹을 수 있지만, 움직이는 방해물이 쫒아온다.
  6.  움직이는 방해물은 지렁이가 땅속에 있는 동안에는 추적하거나 공격하지 못한다. (지나가도 죽지않는다.
  7.  고정 방해물은 지렁이가 땅속에 있어도 부딪히면 죽는다.
  8.  지렁이는 최초 길이가 2이고, 먹이를 먹으면 길이가 1 늘어나며, 방해물에 닿거나 자신의 몸에 닿으면 게임이 종료된다.
  9.  지렁이의 몸체 길이가 16 이상이면 새 스테이지로 넘어간다.
  10. 스테이지에 따라 게임속도와 장애물 갯수가 늘어난다.(최대 2배속 ,장애물 20개)



2. 플로우

유저

  1.  맵은 랜덤하게  생성한다. ( 먹이는 1개, 방해물은 고정 방해물 8~12개, 움직이는 방해물은 1개)
    • 하나의 맵 배열에 이동가능은 0 , 먹이는 1, 방해물은 2, 움직이는 방해물은 3으로 생성.
    • 먹이나 지상의 방해물은 지하의 지렁이와 겹칠 수 있다. bit 단위로 맵을 관리.
      • 이동가능 - 0 , 먹이 - 1 , 방해물 -2, 움직이는 방해물 -4, 지렁이 -8, 지상지렁이 -16
      • 겹칠때는 &, | 연산을 통해 해당 bit만 더하고 뺀다.
  2. 지렁이는 특정 방향으로 매초마다 움직인다. 
  3. 유저가 좌, 우 버튼 클릭시 지렁이는 해당 방향의 직각으로 이동하고, 지상 버튼 클릭하는 동안에는 지상으로 나와서 이동한다. 
    • 1) 지렁이는 좌표 리스트로 이루어진다. 리스트의 맨 뒤가 꼬리, 맨 앞이 머리이다. 
      • 이동시에는  리스트의 맨 앞의 좌표(꼬리)를 제거하고, 맨 뒤에 다음 이동할 좌표를 추가한다. 
      • 이동시에는 리스트의 맨 뒤(꼬리)부터 맨 앞(머리)까지 자신보다 뒤에있는 좌표를 배정하고, 이동 애니메이션을 실행한다. 
      • 새로 추가되는 머리는 리스트의 마지막 좌표에 현재 향하고 있는 방향을 더한다. (동서남북 중 하나)
      • 일정 시간마다 다음 칸으로 이동을 진행한다. (코루틴)
        • 몸통과 머리, 꼬리 좌표를 배정하고 난뒤 각 오브젝트는 다음 이동 좌표로 이동한다. ( 러프 이동)
        • 이동하는 애니메이션을 위해 지렁이의 몸체는 각각 오브젝트로 사용.
      • 먹이를 먹으면 길이를 1늘리며, 리스트 맨 뒤에 기존의 꼬리와 동일한 오브젝트를 추가한다.
      • 이동은 180도로 할 수 없어야하므로, 플래그를 두어 좌 우 버튼은 매 틱 한 번만 클릭이 되도록한다.
    • 2) 지렁이를 큐를 이용해 만든다. 
      • 꼬리가 제거되고, 머리가 추가되는 식으로 한다면, 큐가 맞지만, 각 몸통이 다음 타일로 이동하는 애니메이션을 진행하려면 큐는 맞지않아 보임.
  4.  먹이를 먹으면 길이가 1씩 늘고, 길이가 총 16이 되면 다음 스테이지로 이동하고, 자신의 몸이나 방해물에 닿으면 종료된다.
    1. 먹이를 먹을때 종료 판단. 이동시 배열의 값에따라 종료 판단.
움직이는 방해물
  1.  평소에는 배정된 타일에 가만히 있다가 지렁이가 밖으로 나오면 지렁이의 1/2 속도로 쫒아온다.
    1. 지렁이가 밖으로 나온는 버튼을 유저가 클릭하면 지렁이의 머리가 있는 타일을 움직이는 방해물에게 전달한다.
      • 전달된 경로를 향해 최단경로 탐색을 진행한 후 다음 타일을 저장 후, 이동한다.
      • 최단 경로 탐색 알고리즘은 무엇을?
        • A* 알고리즘 - 휴리스틱이 의미없다
        • 다익스트라
        • 플로이드
        • 너비 탐색 - 경로간 가중치도 없으니 너비탐색으로 하겠습니다.
          • 목표지점부터 시작지점까지 너비 탐색을 하여  칸마다 +1을 하여 별도의 맵에 저장한다. (시작에 도착하면 종료)
          • 시작지점에서는 저장된 맵을 보고 4방향 (자신이 지나온곳을 제외한) 중 가장 작은 값으로 이동한다. 





* 지적은 언제나 환영입니다.

Posted by 검은거북

Flood 영역을 탐색하는 로직은 아래와 같습니다.


1. SearchFlood

 
public void SearchFlood(ref int[,] _tileArray, ref bool[,] _tileSelect, int _searchTile)
    {
        IntVector2[] checkPosition =
        {
            new IntVector2(1,0),
            new IntVector2(0,1),
            new IntVector2(-1,0),
            new IntVector2(0,-1)
        };
        bool[,] visitArray = new bool[HEIGHT,WIDTH];
        IntVector2 tile = new IntVector2(0,0);

        Queue<intvector2> tileQueue = new Queue<intvector2>();
        tileQueue.Enqueue(tile);
        visitArray[tile.y, tile.x] = true;

        while (tileQueue.Count > 0)
        {
            tile = tileQueue.Dequeue();

            // 큐에서 꺼낼 때 타일을 변경한다.
            _tileArray[tile.y, tile.x] = _searchTile;
            _tileSelect[tile.y, tile.x] = true;

            for (int i = 0; i < 4; i++)
            {
                IntVector2 checkTile = new IntVector2(tile.x + checkPosition[i].x, tile.y + checkPosition[i].y);

                // 예외처리 위치가 벗어났는지, 이미 접근했던 곳인지
                if (checkTile.x<0 || checkTile.y <0 || checkTile.x>=WIDTH || checkTile.y>=HEIGHT || visitArray[checkTile.y,checkTile.x] == true)
                {
                    continue;
                }
                // 접근했다는 표식을 남긴다.
                visitArray[checkTile.y, checkTile.x] = true;

                // 선택영역이거나 클릭된 타일이면 큐에 넣는다.
                if(_tileSelect[checkTile.y,checkTile.x] == true)
                {
                    tileQueue.Enqueue(checkTile);
                }else if(_tileArray[checkTile.y, checkTile.x] == _searchTile)
                {
                    tileQueue.Enqueue(checkTile);
                }
            }

        }
    }

 최초 (0,0)의 타일을 큐에 넣고, 큐에서 하나씩 꺼내어 4방향으로 탐색하여, 선택영역이거나 클릭된 타일과 동일한 타일이면 큐에 넣습니다. 큐에 더 이상 타일이 없을 때까지 반복하여 4방향 탐색을 진행합니다.( 너비탐색이죠.)

 만약 2번째 방식 (테두리를 별도로 저장)으로 하려면 선택 영역이거나 클릭된 타일을 큐에 넣는 곳에서 그 외일때 tile 위치를 테두리 큐에 별도로 저장하면 되겠네요.


 * 위의 함수는 최초 생성 후에 최초 선택영역을 지을때도 사용가능합니다. (0,0)타일을 매개변수로 넘기면 되니까요. 


* 아래는 오브젝트와 연출이 추가된 버전입니다.





* 지적은 언제나 환영입니다.

Posted by 검은거북

기획

타일은 5개, 칸 수는 16 * 16

1. 타일은 랜덤하게 생성된다.

2. 왼쪽 위의 첫번째 타일을 선택 영역으로 정하고, 게임이 진행된다.

3. 타일을 선택하면 선택 영역이 선택된 타일로 변경된다. 선택 영역의 이웃(4방향)에 선택한 타일과 동일한 타일의 영역이 있을경우 해당 영역도 선택 영역이 된다.

4. 타일은 변경시 왼쪽 위를 기준으로 동,남 방향으로 변경된다.

5. 전체 타일이 선택 영역이 되면 종료된다.

  



플로우

1. 각 칸에 랜덤하게 타일을 생성한다.

2. 최초 타일 생성 완료시 왼쪽 위의 첫 타일을 기준으로 차례로 비교하여 선택 영역을 설정한다.

 2-1) 자신을 기준으로 4방향으로 탐색하여 자신과 동일하면, 큐에 넣고 선택영역으로 설정한다. 큐에 타일이 존재하면 위의 작업을 반복한다.

3. 타일 클릭시 선택 영역과 선택 영역의 이웃한 타일 중 클릭타일과 동일한 영역을 클릭한 타일로 변경하고, 선택 영역으로 설정한다.

3-1) 왼쪽 위 첫 타일을 시작점으로 4방향 탐색을 하여, 선택 영역이거나, 클릭한 타일과 같으면 큐에 넣으며 선택 영역으로 체크, 한 사이클 완료시 큐에서 차례로 빼서 동일한 로직 진행. ( 접근했던 타일은 별도의 배열에 표시한다. - 중복되게 큐에 넣지 않기 위해)


3-2)  선택 영역의 테두리를 별도로 저장해놓는다. 선택 영역의 테두리만을 큐에 넣고, 큐를 1번과 같이 4방향 탐색과 큐를 이용하여 선택 영역으로 변경한다.  선택 영역에 대해 타일을 변경한다.

    - 테두리 판단 - 큐에서 꺼내어 4방향 판별을 할 때, 한 방향이라도 선댁영역, 클릭된 타일이 아니라면 테두리로 판단.


 * 3-1 선택 - 3-2는 선택 영역의 비교를 줄이고자 제안했으나, 타일을 변경하는 과정에서 결국 동일한 비교문이 사용된다. 비교적 간단한 로직 선택


4. 전체 영역이 선택 영역이 되면 종료된다.






* 지적은 언제나 환영입니다.

Posted by 검은거북

 이전 포스팅에 예고한대로 매칭, 제거, 리필 로직에 대해 포스팅하겠습니다.

포스팅은 각 로직에 해당하는 함수를 소개하고, 각 함수에 대해 설명하는 식으로 진행하겠습니다.


1. Match

 생성된 모든 타일에 대해서 매칭을 판별합니다.

    // 모든 타일에 대해 매칭 판별하는 함수
    // 매칭에 해당하는 모든 리스트를 반환한다.
    private int[,] tileArray;
    public List<TileLine> CheckAllMatch()
    {
        List<TileLine> matchLines = new ListList<TileLine>();
        // 좌 -> 우
        matchLines.AddRange(CheckLineMatchAll(HEIGHT,WIDTH,false,new IntVector2(1,0)));
        // 상 -> 하
        matchLines.AddRange(CheckLineMatchAll(WIDTH, HEIGHT, true, new IntVector2(0, 1)));

        return matchLines;
    }
    
    private List<TileLine> CheckLineMatchAll(int line1, int line2, bool isVertical, IntVector2 checkPos)
    {
        List<TileLine> matchLines = new List<TileLine>();
        for (int i = 0; i < line1; i++)
        {
            for (int j = 0; j < line2; j++)
            {
                int count = (isVertical) ? CheckLineMatchFromOneTile(i, j, checkPos) : CheckLineMatchFromOneTile(j, i, checkPos);
                if (count >= 2)
                {
                    TileLine line = new TileLine(isVertical, i, j, j+count);
                    matchLines.Add(line);
                }
                j += count;
            }
        }
        return matchLines;
    }
    // 기준타일로부터 방향(_checkPos)에 같은 타일이 몇개 있는지 반환
    private int CheckLineMatchFromOneTile(int _x, int _y, IntVector2 _checkPos)
    {
        int deltaX = _checkPos.x + _x;
        int deltaY = _checkPos.y + _y;
        int count = 0;
        while (!isOutArray(deltaX, deltaY))
        {
            if (tileArray[_y, _x] == tileArray[deltaY, deltaX])
            {
                count++;
            }
            else
            {
                return count;
            }
            deltaX += _checkPos.x;
            deltaY += _checkPos.y;
        }
        return count;
    }

 위의 함수는 현재 왼쪽 -> 오른쪽, 위쪽 -> 아래쪽으로 타일을 비교하며 3개 이상의 매칭을 판별하고 리턴해줍니다. (코드를 간결하게 짜야하는데...)

 CheckLineMatchFromOneTile 로직은 기준 타일에서 제시된 방향으로 차례로 돌며, 현재 타일이 기준 타일과 동일한 종류의 타일이면 count 갯수를 증가시켜 반환하고, CheckLineMatchAll 에서는 count가 2 이상일 경우 3match로 판단합니다.

 CheckLineMatchFromOneTile 의 경우는 유저의 swap에 따른 4방향 판별에도 사용됩니다.


2. Remove

    // 매칭된 타일을 제거하는 함수
    public void RemoveMatchTile(ref int[,] _tileArray,List<TileLine> _tileLines)
    {
        foreach (TileLine tile in _tileLines)
        {
            IntVector2[] points = tile.GetLinePoints();
            for(int i = 0; i < points.Length; i++)
            {
                _tileArray[points.y,points.x] = -1;
            }
        }
    }

TileLine
    public bool[] GetUseWidthTile(bool[] refillArray)
    {
        if (isVertical)
        {
            refillArray[lineIndex] = true;
        }
        else
        {
            for (int j = startIndex; j <= endIndex; j++)
            {
                refillArray[j] = true;
            }
        }
        return refillArray;
    }

 위의 함수는 매칭된 리스트를 읽어와  -1 ( 빈공간 취급) 으로 값을 변경하고 있습니다. 

 이건 로직은 너무 단순해서... TileLine 즉, 매칭 리스트를 어떻게 저장할 거냐에 따라 달라지겠네요. 저 같은 경우는 가로세로 여부, 라인 번호, 시작 인덱스, 종료 인덱스를 저장하고, 시작~종료까지 공란으로 바꾸는 식으로 진행했습니다.


3. Refill


    // 제거된 타일의 빈공간을 채우는 함수.
    public void RefillTile(ref int[,] _tileArray, List<TileLine> _tileLines)
    {
        // 리필을 할 영역(세로 라인) 표시
        bool[] refillArray = new bool[WIDTH];
        foreach (TileLine tile in _tileLines)
        {
            refillArray = tile.GetUseWidthTile(refillArray);
        }
        //리필 영역에 위에 있는 타일을 내려 빈공간을 채우고, 채우고 남은 최상단 빈 공간의 갯수를 저장한다.
        int[] spaceArray = new int[WIDTH];
        for(int i = 0; i < WIDTH; i++)
        {
            if (refillArray[i])
            {
                int space = 0;
                for(int j = HEIGHT - 1; j >= 0; j--)
                {
                    if(_tileArray[j,i] == -1)
                    {
                        space++;
                    }
                    else
                    {
                        if (space > 0)
                        {
                            _tileArray[j+space,i] = _tileArray[j, i];
                            _tileArray[j, i] = -1;
                        }
                    }
                }
                spaceArray[i] = space;
            }
        }
        // 최상단 빈 공간에 타일을 새로 채운다. 
        for (int i = 0; i < WIDTH; i++)
        {
            if (refillArray[i])
            {
                for (int j = 0; j < spaceArray[i]; j++)
                {
                    if (_tileArray[j, i] == -1)
                    {
                        int index = Random.Range(0, TileCount);
                        _tileArray[j, i] = index;

                    }
                }
                    
            }
        }
       
    }

TileLine
    public IntVector2[] GetLinePoints()
    {
        IntVector2[] points = new IntVector2[endIndex - startIndex +1];
        if (isVertical)
        {
            for (int i = startIndex; i <= endIndex; i++)
            {
                points[i - startIndex].x = lineIndex;
                points[i - startIndex].y = i;
            }
        }
        else
        {
            for (int i = startIndex; i <= endIndex; i++)
            {
                points[i - startIndex].x = i;
                points[i - startIndex].y = lineIndex;
            }
        }

        return points;
    }

 제거된 타일의 빈 공간을 채우는 함수는 세 단계로 이루어집니다.

1. 제거된 빈 공간을 채울 세로 라인 넘버를 저장한다.

2. 저장된 라인 넘버에 대해서 아래부터 위로 차례대로 타일을 내려 빈 공간을 채운다.

3. 빈 공간을 채우고 남은 최상단에 새로운 타일을 넣는다.



상기 코드는 로직만을 구현이 되어있고, 실질적으로 눈에 보이는 것이 없습니다. (오브젝트와 연출) 이 부분은 개별적으로 진행하는 것으로하고, 제가 가장 신경쓰이는 것은 이게 과연 효율적인 로직이냐 입니다.... 오브젝트, 연출까지해서 총 3일이 걸렸는데, 매 코드를 건들 때마다 못미덥네요....ㅠㅠ 쉽게 생각나는 알고리즘은 비효율일 경우가 많아서.. 조금이라도 더 효율적인 로직이 있을거라 생각됩니다. 우선 현재는 이렇게 진행하고, 앞으로는 좀 더 효율적인 로직이 있을지 리서칭을 해보고, 찾는다면 4번째 포스팅에서 로직 개선을 진행하겠습니다. ( 라는 말은 결국 좋은 로직을 찾을 때까지 무기한 연장이라는...)



완성된 후의 VS2015 클래스 다이어그램 (클릭하면 원본 이미지)


 주요 클래스만 설명하자면...

TilePanelModel - 타일들에 대한 로직 담당 (타일 생성,추가,리필 등)

TilePanelView - 타일들의 애니메이션 담당

TileObject - 유저와의 인터렉티브 담당 (클릭 드래그) / 페이즈(Phase) 상태에 따라 행동 위임

ScoreManager - 점수 처리 및 View 담당




* 함수별로 테스트는 완료되었으나 포스트로 옮기는 과정에서 오타가 있을 수 있습니다. 문제 있을시 알려주세요.


* 위 로직 + 오브젝트와 연출의 결과물입니다.














* 지적은 언제나 환영입니다.

Posted by 검은거북

우선 MatchManager를 통해서 전체 게임을 진행하겠습니다.

우선 필요한 로직을 함수 단위로 나누고, 함수를 채우는 식으로 진행할 예정입니다.

    
    public struct TileLine
    {
        private bool isVertical;
        private int lineIndex;
        private int startIndex;
        private int endIndex;
    }
    // 전체 타일을 랜덤하게 생성하는 함수
    // 최초 시작시와 매칭 할 수 있는게 없을 경우 호출한다.
    public void MakeAllTile(ref int[,] _tileArray)
    {

    }

    // 모든 타일에 대해 매칭 판별하는 함수
    // 매칭에 해당하는 모든 리스트를 반환한다.
    public List<TileLine> CheckAllMatch(int[,] _tileArray)
    {
        return null;
    }

    // 매칭된 타일을 제거하는 함수
    public void RemoveMatchTile(ref int[,] _tileArray,List<TileLine> _tileLines)
    {
        
    }

    // 제거된 타일의 빈공간을 채우는 함수.
    public void RefillTile(ref int[,] _tileArray, List<TileLine> _tileLines)
    {

    }

    // 채워진 타일이 매칭 가능한 패널이 존재하는지 확인.
    // 존재하지 않는다면 타일 생성을 다시해야한다.
    public bool CheckCanMatch(int[,] _tileArray)
    {
        return false;
    }
    
    // 특정 위치에 있는 타일에 대해 4방향으로 매칭이 있는지 확인하는 함수.
    // 유저가 swap 시 swap된 두 타일에 대해 사용한다.
    public List<TileLine> CheckOneMatch(int[,] _tileArray, int _x,int _y)
    {
        return null;
    }

이전 포스팅에서 나온 플로우에 따라 함수를 작성하였습니다. 위 함수는 아래와 같이 매칭됩니다.

1) 최초 타일 생성 - MakeAllTile

2) 매칭 판단 - CheckAllMatch

3) 매칭 제거 - RemoveMatchTile

4) 타일 리필 - RefillTile

5) 매칭 리스트가 없을때까지 1~4 반복

6) 매칭 할게 없는지 판단 - CheckCanMatch


7) 유저 인터렉트에 따른 부분 매칭 판단 - CheckOneMatch



위 함수들을 플로우에 맞게 최초 생성 로직 (1~6)을 짜보면 아래와 같습니다. 

    const int WIDTH = 9;
    const int HEIGHT = 9;

    int[,] tileGrid = new int[HEIGHT,WIDTH];
    
    void Start () {
        do
        {
            // 최초 전체 타일 생성.
            // 타일에 match 할 수 있는게 없다면 다시 타일 전체 생성.
            MakeAllTile(ref tileGrid);

            bool isMatch = true;
            while (isMatch)
            {
                // 전체 타일 중 match되는 타일 판별
                List<TileLine> matchLine = CheckAllMatch(tileGrid);

                //match가 있다면 제거하고 타일을 리필한 다음 다시 전체 match 판별 진행.
                if (match.Count > 0)
                {
                    isNotMatch = true;
                    RemoveMatchTile(ref tileGrid, matchLine);

                    RefillTile(ref tileGrid, matchLine);
                }
                else
                {
                    isMatch = false;
                }
            }
        } while (!CheckCanMatch(tileGrid));
    }


 7번 유저로직에 따른 진행은 GameManager가 아닌 Tile 클래스에서 터치이벤트에 따라 별도로 동작을 정의하였으나, 중요로직은 GameManager에 있다고 생각되어 앞으로 포스팅은 GameManager의 중요 로직이라고 생각하는 부분만 올리도록하겠습니다.

아마 매칭 로직과 제거, 리필 로직만 소개하게 될 거 같습니다.



* 지적은 언제나 환영입니다.

Posted by 검은거북

기획 

 타일 : 9 X 9 , 타일종류 : 5개

 1. 가로 세로로 3개 이상 동일한 문양이 연속되어 있다면 제거된다.

 2. 제거되는 문양은 각각 한 타일당 10점이다.

 3. 동시에 여러 문양의 연쇄가 발생한다면, 모두 제거되고, 동일한 콤보의 점수 (1콤보일때 10점)로 판정한다.

 4. 제거된 뒤에는 위에서부터 한 칸씩 내려온다. 빈 공간에는 새로운 문양들이 내려온다.

 5. 빈 공간이 다 채워진 뒤에도 가로 세로 3개 이상 동일한 문양이 있다면 제거되며 이후 부터는 콤보로 친다. (2콤보 - 20점)



플로우

1. 최초 시작시 9x9의 타일에 각각 랜덤한 타일을 생성한다.

2. 최초에는 전체 타일에 대해 3 매칭 판단을 해야한다.

 2-1)왼쪽부터 오른쪽으로 , 위부터 아래로 차례로 매칭 비교한다. ( 한 타일당 2번비교될듯.  총 162번)

 2-2) 각 타일별로 매칭 판별을 한다. (동서남북 - 한타일당 4번 비교)

 2-3) 2번 스타일로 하되, 동일한 크기의 bool 배열을 만들어 방문한 곳을 체크해 다시 체크하지 않도록 한다. 하나의 라인을 지나가며 주변의 자신과 같은 타일은 큐에 넣고, 매칭 라인을 돌면 큐에서 빼내며 다른 연결된 매칭라인이 있는지 확인한다.  결국 전체 타일을 돌아야한다. 2번에 비해서는 절약할 수 있으나, 1번과는 큰 차이가 없다. (오히려 더 연산이 많아 보인다.)

3. 매칭은 별도로 저장한다.

 3-1 ) 저장 방법 - 하나의 구조체가 가로세로여부, 시작 인덱스와 종료인덱스를 저장. 리스트 형식으로 해당 구조체를 저장.

3-2) - 각각의 매칭된 타일들을 모두 저장한다. 중복을 허용하지 않기위해 set (hashSet)으로 저장.

4. 저장된 매칭 리스트를 제거한다.

5. 빈 공간의 위에있는 타일들을 내려 빈 공간을 채우고, 남은 최상위의 빈 공간에 새로운 타일을 랜덤하게 생성하여 리필한다. 

 - 5-1)  아래에서 위로 탐색하여 빈공간이 나올때마다 내려가야할 칸 ++ 하여 내린다.

 - 5-2) 우리는 제거된 타일의 위치를 알고있다. 그러면 제거된 타일의 위치의 위에 타일들에 +1을 해주면 내려가야 할 칸을 알 수 있다. 이 값을 기반으로 타일을 내려준다.

 - 5-3) 1+2 형식으로 제거된 타일의 세로좌표만 1번 방식으로 진행한다. ( 2번 방식으로는 세로영역에 제거된 라인이 겹칠수록 비효율적인 연산이 늘어난다.)

    리필할 때 새로 타일을 생성하는 것에 사용할 것까지 생각하여 5-3으로 진행합니다.

6. 매칭 리스트가 0이 될때까지 2~5 반복.

 - 매칭 리스트가 0이면 매칭 할 게 더이상 없는지 확인.

- 아래 두 형태가 있는지를 확인하면 되겠네요.총 연산은 대략162*8 = 1296 정도 되겠네요.

                    


  - 매칭할게 없다면 1번으로 되돌아간다.


7. 유저 타일 터치 후 드래그 시 방향 판별을 통해 해당 방향의 타일과 swap하는 형태를 보여주고, 터치를 종료시 swap한다. ( 일정 길이 이상)

8. 유저가 타일을 변경한 경우 두 개의 타일에 대해서 4방향 매칭 판별을 진행한다.

 ( 동일한 문양의 타일일 경우 동일한 방향으로 연쇄 확인)

9. 매칭이 되지 않았을 경우 두 타일을 다시 swap 한다.

10. 매칭이 될 경우 3~5를 진행하고, 6번을 진행한다.


생각할만한 점 :

  •  2번의 알고리즘을 좀 더 개선할 방법은 없을까?  고려 - 마스크 기법
    • 2-1 방법은 총 162번의 연산을 하는데, 마스크 방식으로 하면 마스크에 따라 배수로 늘지 않을까..
  •  더이상 매칭될수 없다면? 단순하게 재배열 ㄱㄱ
  •  매칭할 수없는지 확인하는 로직을 개선할 방법은 없을까?



* 지적은 언제나 환영입니다.

Posted by 검은거북
이전버튼 1 2 이전버튼

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

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

최근에 올라온 글

최근에 달린 댓글

글 보관함