11/08 TIL 디자인패턴

Cheorii·2024년 11월 8일
post-thumbnail

오늘 배운것
디자인패턴

  • 싱글턴 패턴
    - 고수들은 잘 안쓴다는..? 근데 또 의외로 편하다고 한다
  • 프로토 타입 패턴
    - 유니티에서 프리팹?
    - 객체를 하나만들어놓고, 그 객체를 복사해서 쓰는 것
  • 이벤트 버스 패턴
  • 팩토리 패턴
    - 객체 생성 로직을 분리해서 클라이언트가 직접 생성하지 않고, 공장처럼 클래스나 메서드에 맡겨 찍어내는 패턴

싱글턴 패턴

싱글턴이라는건 하나의 인스턴스가 유지되어야 한다.

static SpawnManager instance;
static public SpawnManager Instance
{
    get
    {
        return instance;
    }
}

private void Awake()
{
    instance = this;    // 이런식으로 처리를 하면 싱글턴이 아니다.싱글턴이라는건 하나의 인스턴스가 유지되어야 한다.

    DontDestroyOnLoad(gameObject); // Scene이 바뀌어도 Scene이 유지가 된다. //씬이 생성될떄마다 게임 오브젝트가 새로 생기고 하면 안됌. 오직 하나만 유지되어야 한다.

}

그림처럼 한개가 생겨야 싱글턴인데, NewScene을 계속 누르니 SpawnManger가 계속 생기는 경우, 이건 싱글턴이라고 할 수 없다.
그래서 아래 코드처럼 수정해야 한다.

static SpawnManager instance;
static public SpawnManager Instance
{
    get
    {
        return instance;
    }
}
private void Awake()
{
    if(instance!=null)
    {
        Destroy(gameObject);
    }
    else
    {	// 이렇게 해줘야 새로 생기는 SpawnManger 오브젝트를 삭제 함으로서, 싱글턴으로 만든다.
        instance = this;
        DontDestroyOnLoad(gameObject);
    }
}

Awake(), OnEnable(), Instance순서

public class EventBus : MonoBehaviour
{
 // EvnetBus는 굳이 싱글턴일 필요가 없음
 // 한 GameObject내에서의 통신을 위해 GameObject의 컴포넌트로 사용할 수도 있다.
 // 여기서 싱글턴으로 만든 이유는 그냥 싱글턴의 다양한 구현 방법을 보여주기 위해 작성했다.
    static EventBus instance;
    public static EventBus Instance
    {
        get
        {
            if(instance == null)
            {
                GameObject go = new GameObject("EventBus");
                instance = go.AddComponent<EventBus>(); // 이 문장에서 Awake,OnEnable실행하고, 인스턴스를 할당한것,
                Debug.Log("Instance");
                DontDestroyOnLoad(go);
            }
            return instance;
        } 
    }
    // Awake는 생성자와 같은 시간? / 어웨이크와OnEnable과 연달아서 실행됨.
    // 스타트는 생성될때 보다 한참 뒤, 
    // 생성자 타입에 뭘 하려고 한다, 그러면 Awake()에서 하면됨.
    private void Awake()
    {
        Debug.Log("Awake");
    }
    private void OnEnable()
    {
        Debug.Log("OnEnable");
    }
    public void EmptyMethod()
    {

    }
}

실행 순서를 보면 Awake, OnEnable이 연달아 실행되고, Instance가 실행된다.

팩토리 패턴(Factory Pattern)

  • 객체 생성 로직을 분리해서 클라이언트가 직접 생성하지 않고, 공장처럼 클래스나 메서드에 맡겨 찍어내는 패턴

장점
- 유지보수 하기 편해지고,
- 오브젝트 풀링과 함께 작업할 때, 효율적으로 초기화 하고 재사용 가능
- 새로운 객체가 필요하면, 팩토리 클래스에만 생성로직을 추가하면된다. 그래서 수정사항이 적어진다.

유니티에서 제공하는 오브젝트풀

using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Pool;

public class FactoryBarbarianA : UnitFactory
{
    public GameObject productPrefab;

    //public override IUnitProduct GetProduct(Vector3 position)
    //{
    //    GameObject gameobject = Instantiate(productPrefab, position, Quaternion.identity);
    //    ProductBaBarianA newProduct = gameobject.GetComponent<ProductBaBarianA>();
    //    newProduct.Initialize();

    //    return newProduct;
    //}
 
    ObjectPool<ProductBarbarianA> pool;

    private void Awake()
    {
        pool = new ObjectPool<ProductBarbarianA>(
            createFunc: CreateNewProduct,
            actionOnGet: product => product.gameObject.SetActive(true),
            actionOnRelease: product => product.gameObject.SetActive(false),
            actionOnDestroy: product => Destroy(product.gameObject),
            collectionCheck: false,
            defaultCapacity: 10
            );
        // Unity의 오브젝트풀은 미리 생성은 안해준다.
        // 만약 미리 생성까지 미리 해두고 싶다면??
        List<ProductBarbarianA> tempList = new List<ProductBarbarianA>();
        for (int i = 0; i < 10; i++)
        {
            tempList.Add(pool.Get());
        }
        for (int i = 0; i < 10; i++)
        {
            pool.Release(tempList[i]);
        }
    }
    public override IUnitProduct GetProduct(Vector3 position)
    {
        ProductBarbarianA product = pool.Get();
        product.transform.position = position;
        product.Initialize();
        return product;
    }
    ProductBarbarianA CreateNewProduct()
    {
        GameObject go = Instantiate(productPrefab);
        return go.GetComponent<ProductBarbarianA>();
    }
    public override void ReturnProduct(IUnitProduct product)
    {
        if (product is ProductBarbarianA)
        {
            pool.Release(product as ProductBarbarianA);
        }
    }
}


위에 스크립트를 사용해서 Pool에 생성된 Clone들을 아래의 스크립트로 반환 시킨다.

// UnitFactory.cs
// 반환 
using UnityEngine;

public abstract class UnitFactory : MonoBehaviour
{
public abstract IUnitProduct GetProduct(Vector3 position);
// 생산 요청에 대한 대응 - Factory Pattern
public abstract void ReturnProduct(IUnitProduct product);
}

>
```cs
// FactoryBarbarianA.cs
public override void ReturnProduct(IUnitProduct product)
{
    if(product is ProductBarbarianA)
    {
        pool.Release(product as ProductBarbarianA);
    }
}
public void Spawn()
{
    Vector3 viewportPoint = new Vector3(Random.Range(0f, 1f), Random.Range(0f, 0.5f), 0);
    // viewport는 화면을 x와 y축으로 나타냈을 때 0,0부터 1,1로 나타낼 수 있다.
    Vector3 hitpoint = GetTerrainHitPoint(viewportPoint);

    //Instantiate(SpawnPrefab, hitpoint, Quaternion.Euler(0, Random.Range(0, 360), 0));
    // 랜덤하게 y축을 기준으로 회전시킨 오브젝트 생성
    UnitFactory factory = unitFactories[0];
    IUnitProduct product = factory.GetProduct(hitpoint);

    //10초후 반환
    StartCoroutine(ReturnAfterDelay(factory, product, 10));
}

IEnumerator ReturnAfterDelay(UnitFactory factory, IUnitProduct product, float delay)
{
    yield return new WaitForSeconds(delay);
    if (factory is FactoryBarbarianA factoryA)
    {
        factoryA.ReturnProduct(product);
    }
}
profile
코딩테스트는 진짜 못하겠어요 ㅠㅠ

0개의 댓글