Unity 프로그래머가 자주 사용하는 단어들 #개발 용어

Injection 

의존성 주입(Dependency Injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 기술이라고 합니다.

 

의존성 주입 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 소프트웨어 엔지니어링에서 의존성 주입(dependency injection)은 하나의 객체가 다른 객체의 의존성을 제공하는 테크닉이다. "의존성"은 예를 들어 서비스로 사용할

ko.wikipedia.org

 

 

유니티를 위한 DI 프레임워크, VContainer이란?

[VContainer이란?] VContainer는 Unity를 위한 가볍고 빠른 DI(Dependency Injection) 프레임워크로, 종속성 및 개체 생성을 쉽게 관리할 수 있도록 도와줍니다. DI는 종속성의 생성 및 관리를 분리하는 프로그래

wlsdn629.tistory.com

 

클라이언트가 어떤 서비스를 사용할 것인지 정해주는 대신, 클라이언트에게 어떤 서비스를 사용할 것인지 물어보는 거라고 생각하면 됩니다.

 

클라이언트는 서비스의 구성을 알지 못하는 대신, 책임을 주입자에게 위임합니다.

반대로, 주입자는 서비스를 클라이언트에게 주입하고, 사용할 수 있는(public class) 서비스를 제공합니다.

 

의존성 주입은 다음 네 가지 역할을 담당하는 "객체" 및 "인터페이스"를 전제로 한다고 합니다.

  • 사용될 서비스 객체
  • 사용하는 서비스에 의존하는 클라이언트 객체
  • 클라이언트의 서비스 사용 방법을 정의하는 인터페이스
  • 서비스를 생성하고 클라이언트로 주입하는 책임을 갖는 주입자

 

예시는 다음과 같습니다.

  • 서비스 - 슬라임, 오크, 엘프 등의 몬스터
  • 클라이언트 - 몬스터 종류에 상관 없이 동일하게 몬스터를 공격할 수 있는 모험가
  • 인터페이스 - 모험가가 몬스터의 세부 사항을 이해할 필요가 없도록 보장해 주는 무기 
  • 주입자 - 모험가에게 어떤 몬스터를 처치할지 결정해주는 NPC

예시를 위와 같이 들었지만, 예전에 제가 햄버거 레시피로 설명했었던 적이 있습니다. 그 글에 더 자세히 설명해 놨으니 관심 있으면 한 번 읽어주세요!

 

유니티(Unity) 인터페이스(Interface) 완전 쉽게 설명 / DI원칙

인터페이스란?예를 들어, 우리가 컴퓨터 키보드에 글자를 입력하면, 키보드는 글자를 컴퓨터로 전달합니다. 이때 키보드와 컴퓨터 사이의 인터페이스는 키보드의 물리적 디자인과 컴퓨터가 키

wlsdn629.tistory.com

 

 

DI의 핵심은 클라이언트는 의존성의 특정 구현에 대한 구체적인 지식이 필요없으며, 인터페이스의 이름과 API만 알면 됩니다.

결과적으로, 인터페이스가 변경되어도 클라이언트는 몇 가지 예외를 제외하고 수정할 필요가 없습니다.


Hook

기존에 진행 중인 시스템이나 프로그램의 특정 부분에 개발자가 "개입"하거나 "상호작용"하거나 "변경"할 수 있게 해주는 기술을 의미합니다.

 

특정 이벤트나 상황에 대응하여 코드를 실행하거나 시스템의 동작을 변경하는 데 사용하므로, 프로그램의 행동을 사용자 정의하거나 확장하는데 유용하게 사용할 수 있습니다.

 

예시

  1. 사용자 정의 동작 추가: 플레이어가 특정 아이템을 획득할 때마다 보상을 주는 시스템을 구현하고 싶다면, 아이템 획득 이벤트에 Hook을 걸어 해당 동작을 추가할 수 있습니다.
  2. 디버깅: 프로그램의 특정 부분에서 문제가 발생하는 경우, 해당 부분에 Hook을 걸어 문제가 발생하는 상황을 자세히 조사할 수 있습니다.
  3. 이벤트 리스너: 사용자 인터페이스에서 특정 이벤트(버튼 클릭, 키보드 입력 등)가 발생했을 때 특정 동작을 수행하도록 할 수 있습니다.

각 상황 예시에 대한 코드는 펼쳐서 보시면 됩니다!

 

더보기
using UnityEngine;

public class Player : MonoBehaviour
{
    public delegate void ItemCollectedHandler(Item item);
    public event ItemCollectedHandler OnItemCollected;

    public void CollectItem(Item item)
    {
        // 아이템 획득 로직...

        OnItemCollected?.Invoke(item);
    }
}

public class RewardSystem : MonoBehaviour
{
    private void Start()
    {
        // 플레이어의 아이템 획득 이벤트에 Hook을 걸어 보상 시스템을 연결
        FindObjectOfType<Player>().OnItemCollected += GiveReward;
    }

    private void GiveReward(Item item)
    {
        // 보상 로직...
    }
}
더보기
using UnityEngine;

public class DebugHook : MonoBehaviour
{
    private void OnEnable()
    {
        Application.logMessageReceived += HandleLog;
    }

    private void OnDisable()
    {
        Application.logMessageReceived -= HandleLog;
    }

    void HandleLog(string logString, string stackTrace, LogType type)
    {
        // 로그 메시지에 따라 다른 동작을 수행
        switch (type)
        {
            case LogType.Error:
            case LogType.Exception:
                Debug.LogError("Error: " + logString + "\n" + stackTrace);
                break;
            case LogType.Warning:
                Debug.LogWarning("Warning: " + logString);
                break;
            default:
                Debug.Log("Log: " + logString);
                break;
        }
    }
}
더보기
using UnityEngine;
using UnityEngine.UI;

public class ButtonHook : MonoBehaviour
{
    public Button myButton;

    void Start()
    {
        // 버튼 클릭 이벤트에 Hook을 걸어 OnButtonClick 함수를 실행하도록 합니다.
        myButton.onClick.AddListener(OnButtonClick);
    }

    void OnButtonClick()
    {
        Debug.Log("Button clicked!");
    }
}

 


Composition(조합)

Composition을 Composite개념과 헷갈리시면 안됩니다. 

 

Composite(Pattern)는 객체들의 관계를 "부분 - 전체 관계의 트리 구조"로 구성하여, 사용자가 단일 객체와 복합 객체 모두 동일하게 다루도록 하는 방식을 뜻합니다.

 

코드 예시는 펼쳐서 보세요!

더보기

Composite 패턴을 사용하지 않고 코드를 작성하면, 각 능력을 개별적으로 관리해야 합니다.

다음은 Composite 패턴을 사용하지 않고 작성된 코드입니다.

 

using UnityEngine;

public class Fireball : MonoBehaviour
{
    public int GetPower()
    {
        // 구현부
        return 100;
    }

    public int GetCost()
    {
        // 구현부
        return 50;
    }
}

public class IceShield : MonoBehaviour
{
    public int GetPower()
    {
        // 구현부
        return 50;
    }

    public int GetCost()
    {
        // 구현부
        return 30;
    }
}

public class Character : MonoBehaviour
{
    private Fireball fireball;
    private IceShield iceShield;

    void Start()
    {
        fireball = GetComponent<Fireball>();
        iceShield = GetComponent<IceShield>();
    }

    public int GetTotalPower()
    {
        int total = 0;
        if (fireball != null)
        {
            total += fireball.GetPower();
        }
        if (iceShield != null)
        {
            total += iceShield.GetPower();
        }
        return total;
    }

    public int GetTotalCost()
    {
        int total = 0;
        if (fireball != null)
        {
            total += fireball.GetCost();
        }
        if (iceShield != null)
        {
            total += iceShield.GetCost();
        }
        return total;
    }
}

// 사용 예
Character character = new Character();
Debug.Log("Total Power: " + character.GetTotalPower());
Debug.Log("Total Cost: " + character.GetTotalCost());

 

이렇게 코드를 작성하다보면 코드의 복잡성이 증가하고, 새로운 능력을 추가하거나 기존 능력을 제거하는 일이 어려워집니다. 왜냐하면 각 클래스마다 공통되는 기능들은 일일이 작성해줘야 할 뿐 아니라, 공통된 타입이 없어 하나의 자료구조에 담을 수 없기 때문입니다.

 

다음은 Composite 패턴을 사용한 코드입니다.

using UnityEngine;

public abstract class CharacterAbility : MonoBehaviour
{
    public abstract int GetPower();
    public abstract int GetCost();
}

public class Fireball : CharacterAbility
{
    public override int GetPower()
    {
        // 구현부
        return 100;
    }

    public override int GetCost()
    {
        // 구현부
        return 50;
    }
}

public class IceShield : CharacterAbility
{
    public override int GetPower()
    {
        // 구현부
        return 50;
    }

    public override int GetCost()
    {
        // 구현부
        return 30;
    }
}

public class Character : MonoBehaviour
{
    private List<CharacterAbility> abilities = new List<CharacterAbility>();

    public void AddAbility(CharacterAbility ability)
    {
        abilities.Add(ability);
    }

    public int GetTotalPower()
    {
        int total = 0;
        foreach (CharacterAbility ability in abilities)
        {
            total += ability.GetPower();
        }
        return total;
    }

    public int GetTotalCost()
    {
        int total = 0;
        foreach (CharacterAbility ability in abilities)
        {
            total += ability.GetCost();
        }
        return total;
    }
}

// 사용 예
Character character = new Character();
character.AddAbility(new Fireball());
character.AddAbility(new IceShield());

Debug.Log("Total Power: " + character.GetTotalPower());
Debug.Log("Total Cost: " + character.GetTotalCost());

 

이렇게 코드를 작성하면 능력을 추가하거나 빼는 작업이 쉬워집니다.

 

다른 예시로 컴퓨터, 자동차, 인간(휴머노이드?ㅋㅋ) 등을 생각해보세요!

 

Composition(Pattern)은 한 클래스가 다른 클래스들의 인스턴스를 포함하는 방식을 의미합니다. 쉽게 생각하면 미리 만들어진 컴포넌트들을 빈 게임 오브젝트에서 조립하는 방식이라고 생각하면 됩니다.

 

유니티 개발을 하면 가장 많이 사용되는 방법 중 하나입니다.

 

using UnityEngine;

public class Engine : MonoBehaviour
{
    public void StartEngine()
    {
        Debug.Log("Engine is starting...");
    }
}

public class Car : MonoBehaviour
{
    // Car 클래스는 Engine 클래스의 인스턴스를 포함합니다.
    private Engine engine;

    void Start()
    {
        engine = GetComponent<Engine>();
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // Car의 StartEngine 메서드는 Engine의 StartEngine 메서드를 사용합니다.
            engine.StartEngine();
        }
    }
}

Facade

하위 시스템의 일련의 인터페이스에 대해 통합된, 단순화된 인터페이스를 제공하는 방법을 의미합니다.

복잡한 건물 내부 구조를 숨기는 건물의 정면을 본 따 만들어진 패턴 이름이 Facade Pattern입니다!

 

예시는 햄버거를 주문하는 우리의 모습을 떠오르시면 될 것 같습니다.

햄버거를 주문하기만 하면 햄버거 재료들이 준비되고, 요리되고, 포장되고, 나오는 과정을 모두 알 필요 없이 기다리기만 하면 됩니다!

 

다음은 햄버거를 제작하는 예시 코드입니다.

using UnityEngine;

public interface IBurgerSubsystem
{
    void Execute();
}

public class IngredientPreparation : IBurgerSubsystem
{
    public void Execute()
    {
        Debug.Log("재료 준비하기...");
    }
}

public class Cooking : IBurgerSubsystem
{
    public void Execute()
    {
        Debug.Log("버거 요리하기...");
    }
}

public class Packing : IBurgerSubsystem
{
    public void Execute()
    {
        Debug.Log("버거 포장하기...");
    }
}

public class BurgerMaker
{
    private IBurgerSubsystem ingredientPreparation;
    private IBurgerSubsystem cooking;
    private IBurgerSubsystem packing;

    public BurgerMaker()
    {
        ingredientPreparation = new IngredientPreparation();
        cooking = new Cooking();
        packing = new Packing();
    }

    public void MakeBurger()
    {
        ingredientPreparation.Execute();
        cooking.Execute();
        packing.Execute();
        Debug.Log("버거 준비됐어요!!");
    }
}

 

위에서 굉장히 복잡한 과정을 통해 햄버거를 만들어냈습니다!

근데...이 복잡한 과정을 소비자인 우리가 알 필요가 있을까요?

아뇨! 우리는 주문하기만 하면 됩니다! 바로 아래 코드처럼 말이죠!

using UnityEngine;

public class Client : MonoBehaviour
{
    private BurgerMaker burgerMaker;

    private void Start()
    {
        burgerMaker = new BurgerMaker();
        burgerMaker.MakeBurger();
    }
}

 

우리는 주문을 하기만 하면 모든 과정이 끝납니다!

이처럼 복잡한 과정을 모두 숨기는 방식을 FacadePattern이라고 합니다.


Proxy

컴퓨터 네트워크에서 다른 컴퓨터 또는 서버로부터 요청을 대신 전달하는 중간 서버를 의미합니다.

클라이언트가 직접 서버에 접속하지 않고 프록시를 통해 요청을 처리할 수 있는 메커니즘입니다.