NPC 만들기

NPC 오브젝트 생성

  • 케릭터와 비슷하게 NPC 오브젝트를 원하는 위치에 생성

스크립트 작성

  • NPC의 경우도 Side Bar에 접속자로 표시 해주어야 하므로 GameManager.Instance.User 이벤트를 사용한다
public class Npc : MonoBehaviour  
{  
	[SerializeField] private string npcNameText = "NPC\n뚱이에오";  
	[SerializeField] private TextMeshProUGUI npcName;  
	[SerializeField] private Sprite npcSprite;  
	private Rigidbody2D _rigidbody2D;  
	private BoxCollider2D _boxCollider2D;  
	  
	private void Awake()  
	{  
		_rigidbody2D = GetComponent<Rigidbody2D>();  
		_boxCollider2D = GetComponent<BoxCollider2D>();  
		npcName.text = npcNameText;  
	}  
	  
	private void Start()  
	{  
		GameManager.Instance.User = new Dictionary<string, Sprite>  
		{  
			{npcNameText, npcSprite }  
		};  
	}  
}

문제점

  • 내가 원하는 실행 순서는
      1. GameManager 인스턴스화
      1. StautsBarScript 에서 OnUser 이벤트 구독
      1. Npc 의 SetupNpc()
  • 위의 순서대로 실행 되어야지만 정상적으로 NPC의 Users 인스턴스를 생성됩니다.
  • 하지만 실제 실행 순서는 다음과 같습니다.

  1. 게임 매니저 인스턴스화
  2. NPC 스크립트 SetupNpc
  3. StatusBarScript 이벤트 구독
  • 위의 순서로 실행되어 NPC의 이벤트 호출이 무시 되어 버려 NPC의 인스터스 생성이 되지 않고있습니다.

해결방안

지연실행

  • 지연실행의 겨우 코루틴을 이용하여 Wait For Time 을 이용하여 딜레이를 강제로 발생시켜 해당 인스턴스들의 초기화를 지연시키는 방법입니다.
  • 하지만 해당 방법의 문제는 해당 지연실행으로 인해서 추후 스크립트 확장시에 다른 문제점이 발생할 수 있으면 이때는 더 큰 딜레이를 주어야 하므로 추천하지 않는 방법입니다.

종속성 주입 방법

  • Service Locator Pattern을 이용한 종속성 주입(Dependency Injection)
  • 해당 방법은 GameManager가 Singleton화 될 때 문제가 있다라고 판단하여 해당 패턴을 사용 해보기로함

스크립트 실행순서 변경

  • Script Execution Order
  • Unity 에서는 스크립트의 실행 순서를 변경할 수 있는 기능이 있습니다.
  • 실행 순서에 맞추어 스크립트의 순서를 정렬한 차례로 실행시키며 실행 순서를 조절합니다.
  • 해당 방법은 매우 간단하며 종종 현업에서도 사용되는 방법입니다.

종속성 주입 방법

Singleton Pattern || Service Locator Pattern

  • 먼저 SingletonLocator Pattern의 차이를 알아야 합니다.
  • 두 패턴은 종속성, 접근성 및 아키텍처 설계를 관리하는 접근 방식의 차이가 있습니다.

Singleton

  • Singleton Patten의 경우 클래스에 인스턴스가 하나만 있음을 보장하고 static 속성이나 method 를 통해 클래스 전역에 액세스 지점을 제공

특성

  • 전역 액세스: 인스턴스는 다른 모든 클래스에서 전역적으로 액세스할 수 있으며 이는 간단하지만 긴밀한 결합으로 이어질 수 있습니다.
  • 사용 용이성: 구현 및 사용이 쉽습니다. 특히 개체가 여러 스크립트를 통해 자주 액세스되는 Unity에서는 더욱 그렇습니다.
  • 수명 관리: 싱글톤은 일반적으로 자체 생성 및 수명을 담당합니다.
  • 테스트 과제: 싱글톤은 전역 상태와 모의 문제로 인해 단위 테스트를 어렵게 만들 수 있습니다.

Service Locator Pattern

  • Service Locator Pattenr는 서비스와 구성 요소가 등록되고 나중에 필요할 때 검색되는 중앙 레지스트리 역할을 합니다.

특성

  • 디커플링: 클래스가 서로에 대한 참조를 직접 인스턴스화하거나 보유하지 않으므로 클래스 간의 결합을 줄이지만 클래스 종속성을 숨길 수 있습니다.
  • 유연성: 서비스 구현을 더 쉽게 교체할 수 있으며, 다양한 구현을 사용하려는 테스트와 같은 시나리오에 유용합니다.
  • 중앙 집중식 관리: 서비스는 중앙 집중식 위치에서 등록 및 관리되는데, 이는 장점(관리 측면에서)이자 단점(실패 지점이 됨)이 될 수 있습니다.
  • 간접: 종속성이 클래스의 생성자나 속성에 명시적으로 명시되어 있지 않기 때문에 코드를 추적하고 이해하기 어렵게 만들 수 있는 간접 수준을 추가합니다.

비교

  1. 커플링:
    • Singleton: 클래스가 싱글톤에 직접적으로 의존하므로 결합도가 높아질 수 있습니다.
    • Service Locator: 직접적인 결합을 줄이지만 종속성을 모호하게 할 수 있습니다.
  2. 테스트 가능성:
    • Singleton: 전역 상태로 인해 단위 테스트를 모의하기가 더 어렵습니다.
    • Service Locator: 패턴 자체는 숨겨진 종속성으로 인해 테스트에서 안티 패턴으로 보일 수 있지만 테스트를 위해 구현을 교체하기가 더 쉽습니다.
  3. 관리성:
    • Singleton: 소규모 프로젝트나 특정 사용 사례에서 사용 및 관리가 간단합니다.
    • Service Locator: 구현 관리 및 교체에 더 많은 유연성을 제공하므로 대규모 프로젝트나 종속성이 복잡한 프로젝트에 유용합니다.
  4. 구조적 영향:
    • Singleton: 시스템의 많은 부분이 싱글톤에 긴밀하게 결합된 아키텍처로 이어질 수 있습니다.
    • Service Locator: 보다 모듈화된 아키텍처를 장려하지만 비용 절감으로 이어질 수 있습니다.

구현

  • Service Locator Class 생성
public static class ServiceLocator  
{  
	// 서비스들을 저장하는 Dictionary. Type을 키로 하고, 서비스 객체를 값으로 함.  
	private static readonly Dictionary<Type, object> Services = new Dictionary<Type, object>();  
  
	/// <summary>  
	/// 서비스를 등록하는 메서드.  
	/// 제네릭 타입 T를 사용하여 어떤 타입의 서비스든 등록할 수 있음.  
	/// </summary>  
	/// <param name="service">등록할 서비스 객체</param>  
	public static void RegisterService<T>(T service)  
	{  
		// 서비스 객체를 Dictionary에 추가. 이미 존재하는 경우 해당 타입의 서비스를 덮어씀.  
		Services[typeof(T)] = service;  
	}  
  
	/// <summary>  
	/// 등록된 서비스를 검색하는 메서드.  
	/// 제네릭 타입 T를 사용하여 원하는 타입의 서비스를 검색할 수 있음.  
	/// </summary>  
	/// <returns>검색된 서비스 객체. 해당 타입의 서비스가 없으면 예외 발생.</returns>  
	public static T GetService<T>()  
	{  
		// 요청된 타입의 서비스를 Dictionary에서 찾아 반환.  
		// 해당 타입의 서비스가 없는 경우 예외가 발생할 수 있음.  
		return (T)Services[typeof(T)];  
	}  
}
  • 기존 Singleton 패턴을 지우고 GameManager Service 를 등록 합니다.
#region Service Locator to GameManager  
/// <summary>  
/// Service Locator Pattern 구현  
/// </summary>  
private void Awake()  
{  
	Debug.Log("GameManager.cs - Register Service");  
	ServiceLocator.RegisterService(this);  
}  
#endregion
  • 다른 Class 에 주입(서비스 검색)
private GameManager _gameManager;

_gameManager = ServiceLocator.GetService<GameManager>();
  • 이렇게 간단하게 Service Locator Pattern을 구현할 수 있습니다.
  • 커플링을 느슨하게 하고 전역적으로 어떤 타입이던지 Service Register 하면 Singleton 보다 쉽게 사용이 가능합니다.

❗️하지만 이 방법으로도 스크립트 실행 순서 문제를 해결하지 못해 강제로 스크립트 실행 순서를 설정합니다.

스크립트 실행 순서 설정 (Script Execution Order)

  • Edit => Project Setting => Script Execution Order

  • 왼쪽 하단에 ➕ 선택하여 원하는 스크립트를 등록합니다.

  • 여기서는 GameManager, NPC, StatusBarScript를 선택 합니다.

  • 우측에 있는 숫자는 실행시에 언제 실행될것인지 설정하는 Parameter 입니다.
  • 단위는 mm/s 로 1000 이면 앞에 스크립트가 실행 후 1초 뒤에 해당 스크립트가 실행 됩니다.
  • 테스트를 해보면서 시간을 조절하면 됩니다.

  • 이제 스크립트가 원하는 순서로 실행되는 것을 보실 수 있습니다.