티스토리 뷰

이제부터 다산다난했던 퍼즐게임 제작기를 작성합니다...

 

마지막 챕터 '집으로...' 는

사냥을 마친 사냥꾼이 멍청하고 게으른 말과 함께

집으로 돌아간다는 내용의 게임입니다. 

언레일드와, 닌텐도 스위치의 월드와이즈 51이라는 게임에 수록된 슬라이딩 퍼즐을 참고하여 기획하였습니다.

 

크게 보면 슬라이딩 퍼즐과 차이가 없지만

나름의 차별 요소를 두기 위해

언레일드의 작업 요소를 조금 추가하였습니다.

플레이어 캐릭터를 조작하고 이동하여 맵을 수정하고

장애물을 제거하는 작업들 말입니다.


8. 게임 설명

게임의 목표는 단순합니다. 도로를 따라 이동하는 말이 집까지 갈 수 있도록

도로 퍼즐을 연결해 길을 완성하는 것입니다.

 

말은 길을 따라 스스로 천천히 이동합니다.

플레이어는 말이 계속해서 앞으로 나아갈 수 있게

도로 퍼즐을 이어나가야 합니다.

 

도로 퍼즐은 6 종류가 있어서,

원하는 방향으로 말을 이동시키려면

퍼즐의 텍스처 이미지를 잘 보고 연결 해야 합니다.

도로가 이어지지 않고 끊기거나 갈 수 없는 곳과 연결되면 게임 오버 됩니다.

 

또한 다양한 장애물도 있어서,

장애물을 피해 길을 이어 나가야 합니다.

레벨 디자인이라고 볼 수 있는데,

플레이어는 여러번의 플레이로 최적의 빌드를 완성해야 합니다.

 

정리하자면 기능 구현 과제는 이러합니다.

1. 퍼즐을 조작하기,

2. 길을 따라 이동하는 말,

3. 장애물에 의한 게임 오버와 아이템을 사용하여 장애물을 제거...

 


9. 퍼즐(타일) 만들기

말이 이동할 수 있는 도로 퍼즐,

게임의 목적지가 되는 집 퍼즐,

말이 밟으면 게임오버 되는 데드존 퍼즐을 기획했습니다.

 

실제 작업에는 퍼즐이 아니라 Tile 이라고 불렀으므로 지금부터는 '타일'이라 부르겠습니다.

 

각 타일은 부모 클래스를 상속하여 고유의 기능을 따로 구현하였습니다.

TileEvent 라는 메서드가 virtual 이므로

Tile 클래스를 상속 받은 타일은 이를 override 하는 것입니다.

말이 바로 앞의 타일을 검출한다면

모든 타일엔 override 된 TileEvent 가 공통적으로 있기 때문에

타일의 종류에 상관없이 TileEvent 를 호출할 수 있는 것입니다.

 

이 중에서 가장 핵심적인 요소가 바로 도로 타일입니다.

 

참고한 닌텐도의 슬라이딩 퍼즐처럼 

가로와 세로, 좌회전과 우회전 타일이 필요했습니다.

특별히 슬라이딩 퍼즐에선 십자 방향의 다용도 퍼즐이 있었지만,

그대로 구현한다면 적은 퍼즐로도 게임을 쉽게 진행할 수 있기에

각 도로를 한 방향으로만 진행할 수 있게 만들었습니다.

다양한 방향으로 길을 이어나가려면 최소 6 개의 타일이 필요한 것입니다.

 

위와 같이 다른 텍스처를 사용하는 총 아홉 종류의 타일을 만들었습니다. 

여기서 말이 이동할 수 있는 타일은 여섯 가지입니다.

타일의 도로는 두 변을 잇고 있어서,

한 곳으로 들어오면 다른 한 곳으로 나가게 되어있습니다.

이것을 bool 값 배열로 저장하였습니다.

각 타일마다 도로의 출입구가 되는 두 방향을 Bool 값으로 저장합니다.

이 bool 값을 사용하여 말이 올바른 길로 들어섰는지를 검사할 수 있습니다.

예를들어 서쪽과 북쪽에서 들어올 수 있는 타일에 말이 진입했을 때,

말이 서쪽과 북쪽에서 들어온 것이 아니라면

말은 잘못된 길로 들어 온 것입니다.

각 도로 타일마다 텍스처에 맞춰 값을 세팅하였습니다.

 

 

말의 이동 방향을 검출하기 위해

타일의 위치에서 말의 위치를 뺀 후 나온 vector2 값을

if문으로 구분하였습니다.

    int GetEnteringDirection(Vector3 horsePosition)
    {
        Vector3 v = horsePosition - transform.position;
        float enterX = Mathf.RoundToInt(v.x);
        float enterZ = Mathf.RoundToInt(v.z);

        if (enterX > 0 && gate[0]) return 0; // 동쪽
        if (enterX < 0 && gate[1]) return 1; // 서쪽
        if (enterZ < 0 && gate[2]) return 2; // 남쪽
        if (enterZ > 0 && gate[3]) return 3; // 북쪽

        return -1; // 조건에 맞지 않는 경우
    }

 

vector2 값의 y값이 0이고 x값이 1이라면 동쪽, -1이라면 서쪽인 것입니다.

그런데 만약 말이 진입한 방향이 도로 텍스처의 어느 방향과도 일치하지 않는다면

말은 도로에 들어오지 못하고 게임 오버하게 됩니다.

만약 텍스처와 일치한 방향으로 진입한다면 말은 다른 출구 쪽으로 이동합니다.

 

        int enteringDirection = GetEnteringDirection(horse.position);

        //말이 잘못된 방향으로 들어온 경우
        if (enteringDirection == -1)
        {
            //게임이 이미 끝난 경우에는 실행하지 않음
            if (!Puzzle_GameManager.instance.IsGameOver)
            {
                Puzzle_GameManager.instance.EndGame?.Invoke();
                Puzzle_GameManager.instance.VCamFollowHorse();
                Puzzle_GameManager.instance.GameOver_Horse(target);
            }
            yield break;
        }
        
        float elapsedTime = 0;
        while (elapsedTime < duration)
        {
            float f = elapsedTime / duration;

            Vector3 bezierPoint = CalculateQuadraticBezierPoint(f, startPos, transform.position, endPos);
            horse.LookAt(bezierPoint, Vector3.up);
            horse.position = bezierPoint;
            elapsedTime += Time.fixedDeltaTime;
            yield return new WaitForFixedUpdate();
        }

        horse.position = endPos;

        target.MoveToNextTile();

계산의 결과로 0부터 3까지의 정수 값을 얻을 수 있습니다.

 

각 방향에 대한 Vector3 값을 배열로 정리해두었습니다.

이 정수 값은 들어온 입구에 대한 위치 값이 될 것입니다.

bool 값이 하나 더 남았으므로 그것은 출구에 대한 위치 값이 될 것입니다.

 

Loop를 사용하여 출구를 찾을 수 있습니다.

  


만약 -1이라면 이동은 하지 않고 즉시 게임이 끝납니다.

 

길을 따라 말을 이동시키는 데에는 베지어 공식이 사용되었습니다.

 

 

베지어 곡선 공식에는 시작점조절점, 끝점이 필요합니다.

휘어진 도로는 정확이 90도의 호로 그려졌습니다.

때문에 시작점끝점은 어떤 두 모서리의 중간점이며

조절점은 항상 타일의 정중앙입니다.

 

이를 코드로 바꾼 것은 아래와 같습니다.

    Vector3 CalculateQuadraticBezierPoint(float t, Vector3 p0, Vector3 p1, Vector3 p2)
    {
        float u = 1 - t;
        float tt = t * t;
        float uu = u * u;

        Vector3 p = uu * p0; // (1-t)^2 * p0
        p += 2 * u * t * p1; // 2(1-t)t * p1
        p += tt * p2;        // t^2 * p2

        return p;
    }

 

말은 7초동안 하나의 타일을 지나갑니다.

코루틴을 사용하여 7초 동안 위 메서드로 계속 말의 위치를 갱신하는 것입니다.

 (duration = 7)

float elapsedTime = 0;
        while (elapsedTime < duration)
        {
            float f = elapsedTime / duration;

            Vector3 bezierPoint = CalculateQuadraticBezierPoint(f, startPos, transform.position, endPos);
            horse.LookAt(bezierPoint, Vector3.up);
            horse.position = bezierPoint;
            elapsedTime += Time.fixedDeltaTime;
            yield return new WaitForFixedUpdate();
        }

        horse.position = endPos;

        target.MoveToNextTile();

 

while 문을 종료하면 target (= horse)은 다음 타일을 검출하여

다시 타일의 TileEvent() 를 실행합니다.

 

이런 방법으로 말이 이동을 구현하였습니다.

어찌보면 말이 능동적으로 이동하는 것이 아니라

타일이 옮겨주는 수동적인 이동을 하고 있습니다.

앞서 말한 것처럼 위치 계산을 타일에게 맡긴 정말 게으른 말입니다.

 

다음은 게으른 말의 최후입니다.

 

 

말이 잘못된 길로 간다면 폭☆8 하게 됩니다.

 

 

 길이 끊어져 있다면 떨어져서 게임오버 됩니다.

 

 

길을 가다 야생동물을 만나면 공격받아 게임오버 됩니다.

 

이 경우는 늑대의 OnTriggerEvent를 통해 말을 감지하면 말을 공격하게 만들었습니다. 

공격을 시작하면 말을 이동시키는 코루틴은 중단될 것입니다.

 

말의 규칙을 정리하자면,

  1. 말은 도로를 따라 앞으로만 전진합니다.
  2. 갈 수 없는 방향이나 타일을 밟으면 폭☆8합니다.
  3. 장애물을 만나도 폭발합니다.

다음에 이어서 계속...

 

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/09   »
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
글 보관함