
프로젝트 중에 Scene간 이동이나 Scene내 이동을 할 수 있는 Teleporter 계열의 스크립트가 있었다.
처음 구현은 다음과 같았다.
public class Teleporter : MonoBehaviour
{
[SerializeField] private Vector3 _targetScenePosition = new Vector3();
private void OnTriggerEnter2D(Collider2D other)
{
if(other.gameObject.CompareTag("Player"))
{
other.gameObject.transform.position = _targetScenePosition;
}
}
}
public class SceneTeleporter : MonoBehaviour
{
[SerializeField] private SceneName _targetSceneName = SceneName.Farm;
[SerializeField] private Vector3 _targetScenePosition = new Vector3();
private void OnTriggerEnter2D(Collider2D other)
{
GameManager.Instance.SceneControlM.MoveSceneWithFade(_targetSceneName, _targetScenePosition);
}
하지만, 특정 시간대에 텔레포트를 제한하는 기능을 추가하려다 보니, 두 클래스 간에 코드 중복이 많다는 문제가 발생했다.
이 문제를 해결하기 위해 두 클래스의 상위 추상클래스를 작성하고, 해당 추상클래스를 상속받는 방식으로 바꾸기로 결심했다.
기존의 Teleporter의 이름을 LocalTeleporter로 변경하고, 새로운 추상클래스를 Teleporter로 명명하고 다음과 같이 작성하였다.
public abstract class Teleporter : MonoBehaviour
{
[SerializeField] protected Vector3 _targetScenePosition = new Vector3();
[SerializeField] private int _openTime = 0;
[SerializeField] private int _closeTime = 24;
protected abstract void Teleport();
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player") && IsOpen())
{
Teleport();
}
}
private bool IsOpen()
{
return GameManager.Instance.TimeM.InGameHour >= _openTime && GameManager.Instance.TimeM.InGameHour <= _closeTime;
}
}
Teleporter는 모든 Teleporter에 필요한 기능인, 이동할 좌표와 이동 가능한 시간, 그리고 Player의 충돌 발생시 이동하는 기능을 구현했다.
Teleport 메서드의 경우 하위 클래스에서 구체적으로 정의하기 위해 abstract로 선언했다.
하위 클래스인 SceneTeleporter와 LocalTeleporter는 다음과 같이 작성했다.
public class SceneTeleporter : Teleporter
{
[SerializeField] private SceneName _targetSceneName = SceneName.Farm;
protected override void Teleport()
{
GameManager.Instance.SceneControlM.MoveSceneWithFade(_targetSceneName, _targetScenePosition);
}
}
SceneTeleporter는 Scene간 이동이 필요하기에 Scene목록들을 열거형으로 정리하여 사용하는 필드를 추가 했다.
그리고 override한 Teleport()메서드는 Scene 이동을 해주는 메서드를 호출해 Scene 간 이동을 할 수 있도록 했다.
public class LocalTeleporter : Teleporter
{
protected override void Teleport()
{
GameManager.Instance.StartFade(() => MovePosition());
}
private IEnumerator MovePosition()
{
GameManager.Instance.CharacterM.gameObject.transform.position = _targetScenePosition;
yield return null;
}
}
LocalTeleporter는 같은 Scene내에서 이동을 구현한다.
왜 추상클래스를 사용했는가?
위 기능을 구현하기 위해서는 인터페이스를 쓰거나, 일반적인 클래스를 상속할 수도 있었을 것이다.
하지만 여기서 추상클래스를 사용하기로 마음 먹은 것은 다음과 같다.
1. 인터페이스 대신 추상클래스를 사용한 이유
우선 인터페이스는 필드를 정의할 수가 없다

Teleporter에는 이동할 좌표와 열고 닫는 시간을 저장하는 필드가 있어야한다. 하지만 인터페이스는 해당 필드를 관리할 수 없다.
만약 위 코드대로 ITeleporter라는 인터페이스를 만든다면, Teleport() 메서드를 선언하는 것외에는 할 수 없을 거라 생각한다.
Teleporter는 MonoBehaviour로서 OnTriggerEnter2D같은 메서드도 구현을 했어야 했는데, 인터페이스로는 이런 구현이 불가능할 것이라 생각해 추상클래스를 사용했다.
2. 클래스를 상속해도 됐을탠데 추상클래스를 사용한 이유.
추상클래스가 아닌 일반 클래스로도 다음과 같이 구현할 수 있을 것이다.
public class Teleporter : MonoBehaviour
{
[SerializeField] protected Vector3 _targetScenePosition = new Vector3();
[SerializeField] private int _openTime = 0;
[SerializeField] private int _closeTime = 24;
protected virtual void Teleport()
{
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.gameObject.CompareTag("Player") && IsOpen())
{
Teleport();
}
}
private bool IsOpen()
{
return GameManager.Instance.TimeM.InGameHour >= _openTime && GameManager.Instance.TimeM.InGameHour <= _closeTime;
}
}
차이점은 추상(abstract)클래스가 아니기에 Teleport메서드를 abstract가 아닌 virtual로 선언해야 했고, 위 처럼 본문을 빈 공간으로나마 선언할 필요가 생겼다.
이렇게도 구현을 할 수 있지만, 추상클래스를 사용한 이유는 다음과 같다.
1. 메서드 구현의 강제
abstract와 virtual은 둘 다 override를 통해 메서드를 재정의할 수 있다는 공통점이 있지만,
abstract는 무조건 해당 메서드를 구현해야만 한다. 하지만 virtual에는 그러한 강제성은 없다.

즉, virtual로 할 경우, 하위클래스에서 구현해야만 하는 메서드를 실수로 구현하지 않는 상황이 생길 수도 있다.
현재 SceneTeleporter와 LocalTeleporter 모두 단순한 클래스이다 보니 그런 상황이 있을것 같지는 않지만,
추상클래스의 추상메서드를 이용한다면 이런 구현해야하는 메서드를 누락없이 구현할 수 있다.
2. 불완전한 클래스임을 표시
Teleport를 virtual로 선언하고 내용을 비워놓더라도 개발한 사람 입장에서는 Teleporter가 상속을 통해 구현해야만하는 불완전한 클래스라는 사실을 알고 있지만, 그 외에 사람과 유니티엔진은 빠르게 알아보기 힘들 수도 있다.
하지만 추상클래스로 한다면 해당 클래스가 상속을 해야만하는 불완전한 메서드임을 확실하게 알 수 있다.
또한 유니티에서도 추상클래스인 Teleporter는 Add Component에서 뜨지 않는다.

만약 일반 클래스를 사용했다면 다음과 같이 뜨게 된다

추상클래스를 사용하는 것은 이처럼 해당 클래스가 상속을 필요로하는 불완전한 클래스임을 명확히 표현하고, 그런 설계의도를 드러낼 수 있다.
'내일배움캠프 TIL' 카테고리의 다른 글
| 내일배움캠프 77일차 TIL "ToggleSelect 관련 개선" (0) | 2025.01.07 |
|---|---|
| 내일배움캠프 76일차 TIL "선택한 아이템을 강조하는 법" (0) | 2025.01.06 |
| 내일배움캠프 74일차 TIL "Sprite atlas 관련 이슈 해결" (0) | 2025.01.02 |
| 내일배움캠프 73일차 TIL "취침 및 강제 취침 관련 아이디어" (0) | 2024.12.31 |
| 내일배움캠프 72일차 TIL "Transparency Sort Mode" (0) | 2024.12.30 |