철권스러운 액션 게임을 기대하고 받았으나 그냥 캐릭터 모으는 게임 ㅠㅠ

대전 액션으로 터치, 드래그 등으로 방어와 공격, 강공격을 하는 게임입니다. 이런 류의 게임으로 마블 올스타 배틀이나 인저스티스가 있죠. 그런 게임에 캐릭터만 철권으로 변경됬다고 생각하면 되나 싶지만 당연히 약간씩 다른 점이 있습니다.




특징

  1. 에너지가 없는 대신 캐릭터의 체력이 모든 스테이지에 공유된다.
    • 플레이 타임을 줄이는 큰 요소!!! 
    • 방어시 데미지가 안 달지만, 이런 게임은 아무리 잘해도 한 두대씩 맞을 수밖에 없을 거라고 생각합니다. 근데 모든 스테이지에서 체력이 공유되다보니 한 두판 하면 더 하기가 힘듭니다ㅠㅠ
  2. 스킬 시스템이 카드 제도로, 스킬의 종류와 순서를 변경할 수 있다.
    • 이건 개인적으로 장점으로 생각하는데, 스킬을 커스터마이징 할 수 있습니다. 조금 전략적인 요소를 추가해준 느낌?
    • 스킬의 종류는 방어파괴, 스턴, 띄우기, 스트라이크 로 철권의 콤보를 의도해서 연계가 가능합니다.

그리고 무엇보다 헤이아치가 없습니다. 생각보다 캐릭터가 적고, 캐릭터 얻기도 키우기도 너무 힘듭니다 ㅠ 

 개인적인 생각으로 이런 단조로운 게임의 락이 캐릭터 수집인데, 플레이타임이 적다보니 하고싶어도 못하고, 흥미를 잃어가는 느낌입니다 ㅠ







이런 게임들이 그렇듯이 라이브 이벤트는 자주해서 플레이를 유도하는 점도 하나의 장점이죠. 다만 캐릭터의 체력이 공유되다보니 할 엄두가 안나요... 전 금손이 아니라 매번 두들겨 맞으면서 승리하거든요. 그래서 라이브 이벤트 한 번 돌면 플레이 타임 끝이에요. 물론 저의 경우입니다.


개인적으로 캐릭터 모으고, 야금야금 키우는 게임을 좋아하는데, 철권 캐릭터에는 큰 애정이 있진 않다보니 별로더군요. 이거할바엔 차라리 마블 올스타 배틀이나 하는게 낫겠다 싶어서 결국 마블 올스타 배틀을 다시 깔았다는....(인피니티 워를 본 것도 있고)


리뷰쓰고 지워야지...



개인 평점 : 5.4 / 10.0


추천 

  • 철권의 팬!
  • 캐릭터 수집욕이 강한 분.


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 검은거북

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

공지사항

Yesterday
Today
Total

달력

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

최근에 올라온 글

최근에 달린 댓글

글 보관함