내일배움캠프 Unity 6일차 TIL - C# 문법 종합반 1, 2, 3주차

Wooooo·2023년 11월 6일
0

내일배움캠프Unity

목록 보기
8/94
post-thumbnail

오늘의 키워드

C# 문법 종합반 강의 1, 2, 3주차를 들었다.
각 주차마다 Console에서 실행되는 게임을 만드는 과제가 있는데, 2주차 과제까진 할만 했는데 3주차 과제부터는 조금 어려웠다.
3주차 과제는 지렁이 게임과 블랙잭을 구현해야했는데, 구현 과정에서 어려웠던 점과 어떻게 해결했는지를 정리해보려한다.

지렁이 게임


키보드로 지렁이가 진행하는 방향을 바꿔가며 먹이를 먹고 지렁이를 키워나가는 유명한 게임이다.

구현하기 전에,

정답 코드도 같이 동봉되어 있었는데, 이거를 보고 따라 치는 것 보다는, 코드를 보지 않고 먼저 빌드를 돌려서 게임이 어떻게 돌아가는지 확인을 해봤다. 그 결과 알게된 사항은 다음과 같다.

  • x축은 2칸을 합쳐서 1칸으로 친다. y축은 Console의 줄바꿈 때문에 기본 간격이 존재해서 x축을 2칸씩 잡아야 x축과 y축의 크기가 알맞았다.
  • 맵의 외곽선을 그려주어야한다.

과제에서 정답 코드 말고도 기본으로 제공해주는 코드가 있었다. 이 코드에 살을 붙여서 구현된 결과물을 제출하는 방식. 제공된 코드의 기능은 다음과 같았다.

  • 화면에 점을 찍기 위한 Point 클래스
    • Point끼리 접촉했는지 검사하는 메서드
    • 화면 상에 Point를 그리거나 지워주는 메서드
  • Main에서 돌아가는 while문과 Thread를 이용한 게임루프
    • 방향키 입력

위의 상황에서 과제를 완수하기 위해 내가 구현해야 하는 기능을 정리해보면 다음과 같다.

  • 먹이를 랜덤한 곳에 뿌려주는 FoodCreator 클래스
    • 단, 맵 바깥으로는 뿌리면 안된다.
    • x축은 2단위로 1칸이라, 홀수 칸에 뿌리면 안된다.
  • 플레이어가 조작할 게임의 주인공인 Snake 클래스
    • Point 클래스를 이용해야한다.
    • Snake의 이동, 충돌, 먹이먹기
  • 게임루프 안에서 진행 될 게임 로직
    • 바로 뒤돌지 못하게 방향키 입력 막기 (커브만 가능함)
    • 뱀의 이동, 충돌, 먹이 먹기, 먹이 재생성 등등 ..
    • 현재 게임 진행 상태 출력하기 (먹은 먹이 수, 뱀의 길이)
    • 게임 종료

Snake 클래스 구현

가장 먼저 Snake 클래스를 건드렸다. 일단 지렁이가 움직여야 테스트가 가능하기 때문이다.

public class Snake
{
    private List<Point> body;

    public int Length
    {
        get
        {
            return body.Count;
        }
    }
    public Point Head
    {
        get
        {
            return body.Last();
        }
    }

    public Point Tail
    {
        get
        {
            return body.First();
        }
    }
    public Direction dir;

    ...

지렁이의 몸은 Point들로 이루어져있다. 따라서 List<Point> 변수를 하나 만들어줬다. 그리고 지렁이의 길이와 머리, 꼬리를 받아오기 위한 프로퍼티들도 만들어줬다. 이제 지렁이의 움직임을 구현할 Move() 메서드를 작성하면 된다. 여기서 문제가 생겼다.

문제 발견

Snake.Move()

지렁이가 움직일 때, 모든 Point들을 다같이 이동시키려 하면 처리할 양이 많아지고, 몸이 꺾이는 부분에서의 처리가 어려워진다. 따라서 나는 다음과 같은 방법으로 구현하려고 했다.

지렁이의 꼬리를 떼어내서, 머리에 붙인다.

이 방법으로 구현하면, 몸이 꺾이는 부분에서의 처리도 생각하지 않아도 되고, 지렁이의 길이가 몇이던간에 처리할 양도 변하지 않는다.

따라서, 다음과 같이 Snake.Move() 메서드를 작성했다.

    public void Move()
    {
        Tail.Clear();
        var newHead = Tail;
        body.RemoveAt(0);
        switch (dir)
        {
            case Direction.DOWN:
                newHead.y = Head.y++;
                break;
            case Direction.UP:
                newHead.y = Head.y--;
                break;
            case Direction.LEFT:
                newHead.x = Head.x -= 2;
                break;
            case Direction.RIGHT:
                newHead.x = Head.x += 2;
                break;
        }
        body.Add(newHead);
        Head.Draw();
    }

Point.Clear()는 화면상에서 현재 Point를 지워주는 메서드이다. 이 메서드를 이용해 꼬리를 화면에서 지워주고, body에서 꺼낸다음, 진행방향을 계산한 좌표로 바꿔서 새로운 머리로 넣어주고, 화면에 그려준다.

실행해보니 이동은 하지않고 꼬리부터 하나씩 사라졌다! 꼬리를 떼서 머리에 붙이는 것에는 문제가 없는 것 같은데 말이다.

문제 원인

문제의 원인은 Point.Clear() 메서드에 있었다.

    public void Clear()
    {
        sym = ' ';
        Draw();
    }

sym은 이 Point가 어떤 문자로 그려질지를 저장한 변수이다. 그렇다. 이 메서드는 sym을 공백으로 바꾸고, 화면에 공백을 그려내는 것으로 화면에서 Point를 그려내고 있었다. 이 메서드를 호출하고 나면 이 Pointsym에는 공백이 들어가 있는 것이다!!

나는 꼬리를 화면에서 지우는 과정에서 이 메서드를 불러놓고 다시 머리로 넣어줄 때 sym 값을 다시 바꿔주지 않았으니 결과적으로 꼬리가 하나씩 공백으로 그려지면서 결국 투명지렁이가 되는 것이었다.

C#에서 class는 reference type인걸 망각한 실수인 셈.

문제 해결

문제의 원인을 찾았으니 이제 해결해야한다. 다음과 같은 방법으로 해결하기로 했다.

  • 꼬리를 떼고, 화면에서 지운다(Point.Clear())
  • 새 머리를 만들어서 머리로 넣어주고, 그려준다. (sym이 공백값이 아닌 새로운 Point)
    public void Move()
    {
        Tail.Clear();
        body.Remove(Tail);
        AddBody();
        Head.Draw();
    }

알맞는 진행방향에 새로운 Point를 머리로 넣어주는 Addbody()를 이용했다.

    public void AddBody()
    {
        Point p = new Point(Head.x, Head.y, '*');
        switch (dir)
        {
            case Direction.DOWN:
                p.y++;
                break;
            case Direction.UP:
                p.y--;
                break;
            case Direction.LEFT:
                p.x -= 2;
                break;
            case Direction.RIGHT:
                p.x += 2;
                break;
        
        body.Add(p);
    }

Reference Type / Value Type

C#에선 classReference Type인걸 망각한 실수라고 했는데, Reference TypeValue Type을 다시 정리해보자.

  • Value Type으로 동작하는 자료형
    • int, float, long, double, bool, char 등 일반적인 자료형
    • struct, enum등으로 정의한 자료형
  • Reference Type으로 동작하는 자료형
    • Array
      • string, List, int[] 등과 같은 배열 자체
        • 배열의 원소를 말하는 것이 아니라, Array그 자체를 말함
        • 배열의 원소는 또 그 자료형에 따라 Value Type일수도, Reference Type일수도 있음
    • class로 정의한 자료형

Value Type의 동작

Value Type은 a = b와 같은 대입을 실행할 떄, 값의 복사가 일어난다.
메모리 상에 서로 다른 a의 주소에 b의 주소에 있는 값을 복사해서 할당하는 방식이다.
따라서, 대입 이후에 a의 값을 바꾸거나 해도, b의 값이 바뀌거나 하는 일은 없다.

Reference Type의 동작

Reference Type은 a = b와 같은 대입을 실행할 떄, a의 값에는 b의 인스턴스의 주소값을 할당한다.
따라서, 대입 이후에 a의 멤버변수를 조작하거나 한다면, a가 가리키는 인스턴스의 멤버변수를 조작하는 것이므로, 같은 인스턴스를 가리키는 b의 인스턴스 값도 따라서 바뀐다!
C++의 포인터를 생각하면 되겠다. 대신에 C#의 Reference Type은 포인터와 달리 *나 &와 같이 어려운 개념을 알아서 해주는 것이라고 이해하면 될 것 같다.

profile
game developer

0개의 댓글