싱글톤이랑 인터페이스는 쓰기 좋은것 같음.

세이브 매니저를 예를 들어 보겠음.

public class PlayData
{
public int Level;
public int Score;
public int HighScore;
public int Coin;
}

요러한 게임 플레이 데이터를 로드하는 시스템임.

public class SaveManager : MonoBehaviour
{
public SaveManager Instance { get; private set; }
public PlayData PlayData { get; private set; }

private void Awake()
{
Instance = this;
Load();
}

private void Load()
{
// 세이브 파일에 접근하여 플레이 데이터 로드
}
}

싱글톤으로 만들어서 로드한 데이터를 접근하도록 구현해주었음.


이렇게 구현한 경우

특정 플레이 데이터로 테스트를 하려면

나같은 뉴비들은 세이브파일을 조작하거나

세이브 매니저에 디버그용 함수를 추가해주거나

하는 방식으로 테스트 작업을 수행하게 될 것임.


이러한 경우 인터페이스를 사용하면 좋다고 생각함.

public interface ISaveManager
{
public PlayData PlayData { get; }
public void Load();
}

핵심 함수와 데이터를 인터페이스로 만들어주고

public static class SaveManager
{
public static ISaveManager Instance { get; set; }
}

다른 객체들이 싱글톤으로 인터페이스에 접근.


public class ReleaseSaveManager : MonoBehaviour, ISaveManager
{
public PlayData PlayData { get; private set; }

private void Awake()
{
SaveManager.Instance = this;
Load();
}

public void Load()
{
// 세이브 파일에 접근하여 플레이 데이터 로드
}
}

public class DebugSaveManager : MonoBehaviour, ISaveManager
{
[field: SerializeField] public PlayData PlayData { get; private set; }

private void Awake()
{
SaveManager.Instance = this;
Load();
}

public void Load()
{
// Do nothing
}
}

그리고 이렇게 릴리즈용, 디버그용을 따로 만들어줌.

이제 테스트 할 때는 DebugSaveManager를 사용하고

릴리즈할 때는 ReleaseGameManager를 사용하면 됨.


위 예시에서는

스크립터블 오브젝트를 활용해서 플레이 데이터 프리셋을 만들어준다음

DebugSaveManager에 직렬화 필드로 넣어준 뒤에

로드 함수에서 파일 로드 대신 SO를 PlayData에 복사해주는 방식으로 활용해준다던가.


지금은 인터페이스로 Load함수가 쓸데없이 들어가있는데

상위에 게임 매니저가 다른 매니저 호출 전

ISaveManager 인터페이스를 통해 Load함수를 호출하면 되니

게임 매니저가 어떤 세이브매니저인지 신경써줄 것이 없어짐.


대신 이 모듈을 바꿔끼워주든 해야하는데,

그건 프리팹으로 만들던지

전처리기와 Resources.Load를 사용하여 해결하거나 각자의 방식이 있을 듯.


세이브로드 외에도 싱글톤으로 사용하는 시스템을 이런식으로 만들어보면

뉴비 입장에서 인터페이스 사용이 와닿지 않을까 싶음.



대표적인 예시인 IDamagable 이런 것도 나쁘진 않은데

public class Damagable : MonoBehaviour
{
public Action<int> Damaged;

public void Damage(int damage)
{
Damaged?.Invoke(damage);
}
}

이런식으로 컴포넌트화 한다음에


public class Mosnter : MonoBehaviour
{
[SerializeField] private Damagable damagable;
// health 클래스 등의 변수

// 초기화 시 damagable에 대한 이벤트 등록

private void OnDamage(int damage)
{
// health 클래스를 통한 피해 처리
}
}

public class Bomb : MonoBehaviour
{
[SerializeField] private Damagable damagable;

// 초기화 시 damagable에 대한 이벤트 등록

private void OnDamage(int damage)
{
// 폭발
}
}

이런식으로도 사용할 수 있어서

내 경우에는 이런 예시는 잘 안와닿았었음.


++

예시는 상속받는거랑 별 차이 없긴 한데

상속쓰다보면 어지러워서

is a나 has a는 모르겠고

공통 작업이 많은게 아니라면

차라리 인터페이스쓰는게 마음이 편해져서 써봄.