만들다 보니 각 오브젝트의 위치 동일 여부를 매번 if(a.x == b.x && a.y == b.y)와 같이 비교하는것이 여간 귀찮은 일이 아닙니다. 그리고 위치를 리턴할 때도 애매합니다. 리턴 값은 한번에 하나 밖에 할수 없기 때문에 x 좌표와 y 좌표를 얻어 오는 함수를 각각 만들수 밖에 없습니다. 너무 번거롭고 귀찮은 일입니다. 그래서 이번 장에서는 좌표를 추상화한 'Position' 클래스를 만들겠습니다. 그리고 추가적으로 C#의 '참조 일치'와 '값 일치'에 대한 개념을 배워 보도록 하겠습니다.
먼저 Position 클래스를 정의해보겠습니다.
위치를 나타내는 x와 y를 가진 단순한 클래스 입니다. 그런데 우리의 불편함이 뭐였죠? 네, 좌표의 비교였습니다.
if(a.x == b.x && a.y == b.y) 가 아닌 if(pos1 == pos2) 처럼 간단하게 비교 구문을 작성하는 것이었습니다. 위의 코드까지만 작성하더라도 아래와 같은 비교 구문에서 아무런 에러 없이 컴파일이 되긴합니다. 하지만 결과는 우리가 원하는 형태가 아닙니다.
원인은 C#의 일치에는 '참조 일치(reference equal)'와 '값 일치(value equal)'라는 두 종류의 '일치'가 있기 때문이고 우리가 == 오퍼레이터에 기대하는 결과는 값 일치이지만 C#클래스의 == 디폴트 오퍼레이터는 참조 일치를 평가 합니다.
만일 두개의 객체가 동일한 값을 가지고 있다고 하더라도 이것은 객체가 같음을 의미하는 것은 아닙니다. 기본적으로 C#에서 객체에 대한 == 또는 != 비교 연산은 '참조 일치'를 비교하는 것이지 '값 일치를' 비교하는 것이 아니기 때문에 위와 같은 현상이 발생한것 입니다.
"참조가 일치하면 값도 일치하지만, 값이 일치한다고 반드시 참조가 일치하는 것은 아니다."
우리가 지금 필요한 것은 Position 클래스의 == 혹은 != 연산자를 사용했을 때 값 일치를 비교하는 것입니다. 이를 위해서 연산자 오버로딩을 이용하여 참조 비교를 값 비교로 변경해 주도록 하겠습니다.
잠깐 ReferenceEquals 살펴 보도록 하겠습니다. 객체를 null과 비교 할때 if(null == rhs) 와 같은 방법이 아닌 ReferenceEquals 와 같은 별도의 비교 함수를 이용했습니다. 이는 '==' 오퍼레이터의 재귀적 호출을 막기 위해서 입니다. 만일 '==' 연산자를 사용한다면 재귀적으로 == 오퍼레이터 오버라이딩 부분을 스택 오버플로우가 발생 할때 까지 호출하게 됩니다. null은 아무런 객체를 참조하지 않고 있다라는 의미이고 이런 참조 일치는 'ReferenceEquals'를 통해 체크합니다. 참고로 값 일치를 체크하는 함수는 'Equals'입니다.
Position 클래스를 Dictionary 와 같은 자료구조에서 키로 사용 할 수 있도록 하기위해 GetHashCode도 재정의 해줍니다.
하는 김에 Position 클래스를 유니티에서 기본 좌표 객체로 사용되는 Vector2로 묵시적 변환 해주는 오퍼레이터도 추가했습니다.위 오퍼레이터로 인해 Vector2.Distance과 같은 Vector2를 인자로 받는 유니티 함수들을 Vector2.Distance(new Position(0, 0), new Position(1, 1))과 같이 별도의 자료구조 변환 코드 없이 사용 할 수 있습니다.
이상으로 좌표를 추상화한 Position 클래스를 만들었습니다. 이제 이 클래스를 기존 플레이어와 몬스터 클래스에 적용해 보도록 하겠습니다. 다행히도 우리는 이전 장에서 Character 클래스를 통해 플레이어와 몬스터 클래스의 공통부분을 분리해 두었습니다. 리펙토링 이전이라면 두 클래스에 각각의 변경을 적용해 주어야 하지만 이젠 Character클래스의 수정만으로 두 자식 클래스에 변경을 적용할 수 있습니다.
Character.cs