티스토리 뷰


이전에 지뢰찾기를 만들기 위해 구글 play 게임의 무료 게임을 참고했었다.

그 외에도 틈틈히 즐길만한 재미있는 게임이 많이 있었는데,

공부에 조금 도움 되겠다 싶어 '뱀 게임(Snake Game)' 이라는 걸 만들기로 했다.

 

 

조작은 간단하다. 터치 드래그로 방향을 바꾸는 것이다.

처음 봤을 때부터 어떻게 만든거지? 하며 궁금했던 것은

꺾이는 부분을 둥글게 만든 것이었다.

유니티의 Trail을 사용하면 될까 고민해 봤지만

...일단 나중에 고민해 보기로 하고...

우선 기본 게임 구현을 우선하기로 했다.

 

 

이번 작업물은 아래의 주소에서 다운로드할 수 있다.

https://drive.google.com/file/d/11o7p7OoSwE6S5RcgCs6pL-LcnxcP-DrF/view?usp=sharing

 

Minesweeper.zip

 

drive.google.com

 


1. 구현할 것들...

 

 

먼저 내가 만들어야 하는 것을 정리해 보았다.

 

뱀의 움직임 구현이 첫 번째였다.

스무스한 이동이 아니라 한 번에 한 칸씩 이동하는 방법으로 정했고

사과를 먹을 때마다 점점 빨라지며,

특히 꼬리 부분은 머리가 지나간 경로를 따라가도록 만들어야 했다.

일단 조작은 방향키를 사용하는 방식으로 만들었다.

 

두 번째로 사과를 먹으면 사과를 새로운 위치로 이동시켜야 했다.

사과는 머리와 꼬리 위치로 이동해서는 안되며,

뱀의 직진 방향으로도 이동해서는 안된다.

 

마지막으로 사과를 먹었을 때 몸이 길어지게 만들어야 했다.

 

구현 목표를 확실히 잡으니 작업이 수월해지는 것을 느꼈다.

당연한 거지만...


2. 이동

//뱀의 이동방향을 저장할 변수. Normalized, 정규화하여 사용한다.
Vector3 dir;

void Update()
{
     //이동방향의 x값이 0이 아니면 화면기준 좌우 이동 중이므로 상하 조작만 가능
    if (dir.normalized.x != 0)
    {
        if (Input.GetKeyDown(KeyCode.UpArrow))
        {
            dir = Vector3.up;
        }
        if (Input.GetKeyDown(KeyCode.DownArrow))
        {
            dir = Vector3.down;
        }
    }

    //이동방향의 y값이 0이 아니면 화면기준 상하 이동 중이므로 좌우 조작만 가능
    else if (dir.normalized.y != 0)
    {
        if (Input.GetKeyDown(KeyCode.LeftArrow))
        {
            dir = Vector3.left;
        }
        if (Input.GetKeyDown(KeyCode.RightArrow))
        {
            dir = Vector3.right;
        }
    }

	//이동 속도를 조작하기 위해 이동 주기를 적용하였다.
    if (Time.time <= nextMoveTime)
        return;

    //머리를 이동하기 전 위치를 저장        
    lastHeadPos = transform.position;
    //머리를 먼저 이동
    transform.position += dir;
    //혹시 모르니 위치를 정수화하여 계산에 소숫점 오차가 없도록 한다.
    transform.position = new Vector3(Mathf.RoundToInt(transform.position.x), Mathf.RoundToInt(transform.position.y), 0);
    
    //꼬리 끝에서부터 바로 앞의 몸통 위치로 한칸씩 당겨온다.
    for (int i = Tails.Count - 1; i > 0; i--)
    {
    	Tails[i].position = Tails[i - 1].position;
        Tails[i].position = new Vector3(Mathf.RoundToInt(Tails[i].position.x), Mathf.RoundToInt(Tails[i].position.y), 0);
    }

	//머리는 Tails 리스트에 포함되지 않았으니
    //따로 이동시켰다.
    Tails[0].position = lastHeadPos;

//이동을 마치면 이동주기에 따라 다음 이동 시간을 저장한다.
 nextMoveTime = Time.time + movingCycle;
... ...

 

머리 한 칸을 이동시키는 데 어려움은 없었다.

다만 주의할 점은 조작에 있어서 좌우 조작만 가능하다는 것이다.

뱀이 세로로 이동할 땐 가로 조작만,

가로로 이동할 땐 세로 조작만 가능한 것이다.

 


3. 사과를 뱀과 겹치지 않는 위치로 이동시키기

 

    // 머리만 먼저 이동한 후 검사를 진행한다.
    
    // 1)사과가 있는 경우
    // 사과를 이동 *몸이 없는 곳에서 생성 되어야함, 머리 직선 경로에 있으면 안 됨
    // 무작위로 이동하고 나서 생성하고 나서 반복문을 사용해 체크 + 머리 직선 경로에 있는지 체크
    // + 몸이 길어짐
    
    //만약 뱀의 머리 위치(스크립트가 추가된 객체)와 사과 위치가 일치하면...
    if (transform.position == apple.position)
    {
    	//먹는 소리 재생
        AudioPlay(audioSource, eating);
        //점수 획득
        score++;
        //점수를 보여주는 text ui 갱신
        bgScoreUI.text = string.Format("{0:D2}",score);
        //이동 주기 간격을 감소시켜 이동 속도를 높인다.
        movingCycle *= accel;
        
        //반복문 탈출을 위해 bool값 준비
        bool notOverlapping = true;
        //랜덤한 위치 생성을 위한 변수 준비
        int randX =0, randY =0;
        
        //랜덤으로 생성한 위치가 뱀과 겹치지 않는 곳이라면 while문을 종료하기로 한다.
        while (notOverlapping)
        {
            //랜덤 위치는 가로로 -15 ~ 15, 세로 -8 ~ 8 내이다.
            //게임은 16:9의 고정된 윈도우 창으로 플레이할 수 있다.
            //따라서 카메라 중심기준 자로 16, 우로 16, 위로 9, 아래로 9의 영역이 화면에 보여진다.
            randX = Random.Range(-15, 16);
            randY = Random.Range(-8, 8);

            //생성한 무작위 위치가 머리 오브젝트의 x와 y값과 겹치면 안된다.
            if (randX == transform.position.x || randY == transform.position.y) continue;
            //모든 꼬리와 위치를 비교
            //꼬리 객체의 위치 값을 저장할 리스트를 전부 하나씩 대조해본다.
            //꼬리의 리스트는 늘어날 수 있으니 List<Transform>을 사용했다.
            for (int i = 0; i < Tails.Count; i++)
            {
                if (Tails[i].position == new Vector3(randX, randY, 0))
                {
                    //위치가 같은 부위가 나오면 While문을 반복하기 위해 bool값을 true로 유지하고
                    //break로 for문을 즉시종료
                    notOverlapping = true;
                    break;
                }
                
                //전부 다른 위치라면 While문을 종료하기 위해 bool값을 false로 바꾼다.
                //for문을 끝까지 반복해야하므로 break는 쓰지 않는다.
                else { notOverlapping = false; }
            }
        }

        //While문을 종료했다면 새 위치로 사과를 옮긴다.
        apple.position = new Vector3(randX, randY, 0);
    }
    
    
... ...

 

사과를 먹었을 때 사과를 새로운 위치로 랜덤 하게 이동시키는 부분이다.

while문과 for문을 중복사용하여

반복적인 작업을 특정 조건이 만족할 때까지 반복시킬 수 있었다.

for문 안의 break로는 for문의 반복문 밖에 종료할 수 없으니

while문의 조건식을 불만족시키는 새로운 조건을 생각해내야 했다.

안전하게 bool 변수를 새로 만들어 해결할 수 있었다.

 

 

여기서 머리만 먼저 이동시키는 이유는

먼저 이동한 후 그 위치에  사과가 없다면 그다음 꼬리가 따라 이동하면 되지만,

사과가 있다면 머리 바로 다음 자리에 새로운 몸을 생성해야 해서였다.


4. 사과를 먹으면 몸이 길어지게 만들기

 

 

이 경우 머리는 이동했지만 기존의 꼬리는 실제로 이동하지 않는 것이다.

 

//머리와 두 번째 몸통사이에 새 몸을 생성
Transform tail = new GameObject().transform;
tail.AddComponent<SpriteRenderer>().sprite = GetComponent<SpriteRenderer>().sprite;

//머리를 이동시키기 전에 저장했던 위치 값을 할당
tail.position = lastHeadPos;

//꼬리 리스트의 첫번째 순서에 '삽입'
Tails.Insert(0, tail);

 

제일 중요한 건 새로 생성한 꼬리를 List의 첫 번째에 삽입하는 것이다.

이땐 Add()가 아닌 Insert(index, obj) 함수를 사용한다.

 

        else
        {
            for (int i = Tails.Count - 1; i > 0; i--)
            {
                Tails[i].position = Tails[i - 1].position;
                Tails[i].position = new Vector3(Mathf.RoundToInt(Tails[i].position.x), Mathf.RoundToInt(Tails[i].position.y), 0);
            }

            Tails[0].position = lastHeadPos;
        }

 

때문에 기존의 꼬리를 한 칸씩 당기는 코드를 else 처리하여

사과를 먹지 않았을 때만 작동하도록 하였다.

 

  void SetTailScale()
  {
      for(int i = 0; i < Tails.Count; i++)
          Tails[i].transform.localScale = Vector3.Slerp(Vector3.one, lastTailScale, (float) i / Tails.Count);
  }

 

추가적으로 뱀의 꼬리가 갱신될 때마다 끝으로 갈수록 몸이 가늘어지게 만들었다.

Vector3.SlerpVector3.Lerp 를 사용하면 되는 것 같다.

    //2) 자기 몸이나 벽에 부딪힌 경우
    //머리가 이동한 위치가 영역 밖이라면 벽에 부딪힌 것으로 간주한다.
    int x = Mathf.RoundToInt(transform.position.x);
    int y = Mathf.RoundToInt(transform.position.y);
    if (x >= 16 || x <= -16 || y >= 9 || y <= -9)
    {
        GameOverEvent();
    }
    
	//머리 위치와 모든 꼬리 위치를 비교한다.
    for (int i = 1; i < Tails.Count; i++)
    {
        //머리 위치와 같은 몸이 있는 경우
        //자기 꼬리에 부딪힌 것으로 간주한다.
        if (transform.position == Tails[i].position)
        {
            GameOverEvent();
        }
    }
	
    //이동 작업이 마무리 되었으니 다음 이동할 시간을 설정한다.
    nextMoveTime = Time.time + movingCycle;
}

 

게임오버 조건도 이동 작업에 끝에 작성하였다.

 

 

나무위키에서 찾은 AI 가 게임을 클리어하는 영상...

... 하지만 인간으로서는 이 게임을 클리어하지 못할 거라 상정한지라

클리어 조건은 만들지 않았다;;

 

 

이번 주부터 학원을 다니게 되었다.

매우 바쁘게 공부해야 할 것이기에

짧은 시간 안에 완성할 간단한 과제로 선택한 뱀 게임이다.

비록 참고한 게임처럼 부드러운 이동이나

곡선으로 꺾이는 몸은 생략했지만

아마 다른 게임을 만들어야 할 때 고민해야 할 과제일지도 모른다.

공지사항
최근에 올라온 글
최근에 달린 댓글
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
글 보관함