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 }
};
}
}
문제점
- 내가 원하는 실행 순서는
-
- GameManager 인스턴스화
-
- StautsBarScript 에서 OnUser 이벤트 구독
-
- Npc 의 SetupNpc()
-
- 위의 순서대로 실행 되어야지만 정상적으로 NPC의 Users 인스턴스를 생성됩니다.
- 하지만 실제 실행 순서는 다음과 같습니다.
- 게임 매니저 인스턴스화
- NPC 스크립트 SetupNpc
- StatusBarScript 이벤트 구독
- 위의 순서로 실행되어 NPC의 이벤트 호출이 무시 되어 버려 NPC의 인스터스 생성이 되지 않고있습니다.
해결방안
지연실행
- 지연실행의 겨우 코루틴을 이용하여 Wait For Time 을 이용하여 딜레이를 강제로 발생시켜 해당 인스턴스들의 초기화를 지연시키는 방법입니다.
- 하지만 해당 방법의 문제는 해당 지연실행으로 인해서 추후 스크립트 확장시에 다른 문제점이 발생할 수 있으면 이때는 더 큰 딜레이를 주어야 하므로 추천하지 않는 방법입니다.
종속성 주입 방법
- Service Locator Pattern을 이용한 종속성 주입(Dependency Injection)
- 해당 방법은 GameManager가 Singleton화 될 때 문제가 있다라고 판단하여 해당 패턴을 사용 해보기로함
스크립트 실행순서 변경
- Script Execution Order
- Unity 에서는 스크립트의 실행 순서를 변경할 수 있는 기능이 있습니다.
- 실행 순서에 맞추어 스크립트의 순서를 정렬한 차례로 실행시키며 실행 순서를 조절합니다.
- 해당 방법은 매우 간단하며 종종 현업에서도 사용되는 방법입니다.
종속성 주입 방법
Singleton Pattern || Service Locator Pattern
- 먼저
Singleton
과Locator Pattern
의 차이를 알아야 합니다. - 두 패턴은 종속성, 접근성 및 아키텍처 설계를 관리하는 접근 방식의 차이가 있습니다.
Singleton
-
Singleton Patten
의 경우 클래스에 인스턴스가 하나만 있음을 보장하고 static 속성이나 method 를 통해 클래스 전역에 액세스 지점을 제공
특성
- 전역 액세스: 인스턴스는 다른 모든 클래스에서 전역적으로 액세스할 수 있으며 이는 간단하지만 긴밀한 결합으로 이어질 수 있습니다.
- 사용 용이성: 구현 및 사용이 쉽습니다. 특히 개체가 여러 스크립트를 통해 자주 액세스되는 Unity에서는 더욱 그렇습니다.
- 수명 관리: 싱글톤은 일반적으로 자체 생성 및 수명을 담당합니다.
- 테스트 과제: 싱글톤은 전역 상태와 모의 문제로 인해 단위 테스트를 어렵게 만들 수 있습니다.
Service Locator Pattern
-
Service Locator Pattenr
는 서비스와 구성 요소가 등록되고 나중에 필요할 때 검색되는 중앙 레지스트리 역할을 합니다.
특성
- 디커플링: 클래스가 서로에 대한 참조를 직접 인스턴스화하거나 보유하지 않으므로 클래스 간의 결합을 줄이지만 클래스 종속성을 숨길 수 있습니다.
- 유연성: 서비스 구현을 더 쉽게 교체할 수 있으며, 다양한 구현을 사용하려는 테스트와 같은 시나리오에 유용합니다.
- 중앙 집중식 관리: 서비스는 중앙 집중식 위치에서 등록 및 관리되는데, 이는 장점(관리 측면에서)이자 단점(실패 지점이 됨)이 될 수 있습니다.
- 간접: 종속성이 클래스의 생성자나 속성에 명시적으로 명시되어 있지 않기 때문에 코드를 추적하고 이해하기 어렵게 만들 수 있는 간접 수준을 추가합니다.
비교
-
커플링:
- Singleton: 클래스가 싱글톤에 직접적으로 의존하므로 결합도가 높아질 수 있습니다.
- Service Locator: 직접적인 결합을 줄이지만 종속성을 모호하게 할 수 있습니다.
-
테스트 가능성:
- Singleton: 전역 상태로 인해 단위 테스트를 모의하기가 더 어렵습니다.
- Service Locator: 패턴 자체는 숨겨진 종속성으로 인해 테스트에서 안티 패턴으로 보일 수 있지만 테스트를 위해 구현을 교체하기가 더 쉽습니다.
-
관리성:
- Singleton: 소규모 프로젝트나 특정 사용 사례에서 사용 및 관리가 간단합니다.
- Service Locator: 구현 관리 및 교체에 더 많은 유연성을 제공하므로 대규모 프로젝트나 종속성이 복잡한 프로젝트에 유용합니다.
-
구조적 영향:
- 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초 뒤에 해당 스크립트가 실행 됩니다.
-
테스트를 해보면서 시간을 조절하면 됩니다.
- 이제 스크립트가 원하는 순서로 실행되는 것을 보실 수 있습니다.