경일게임아카데미 멀티 디바이스 메타버스 플랫폼 개발자 양성과정 20220718 2022/04/04~2022/12/13

Jinho Lee·2022년 7월 18일
0

경일 메타버스 20220718 16주차 1일 수업내용. 유니티 - 유니런 (마우스 입력 Input, 오디오 관리 AudioSource, 배경 스크롤링), Profiler(진단 도구), C# 문법 - 상수 등, 싱글톤 패턴

Input

AudioSource

Profiler

  • 진단 도구, 프로파일링 툴 - Profiler, Profiling Tool

    • 자원(메모리, CPU 등)을 얼마나 사용하는지 디버거로 체크할 수 있는 도구.

C# 문법

C#에서 상수 만드는 방법 (상수 키워드)

  1. cosnt :
    컴파일 중에 평가, 그렇기에 상수에만 주로 사용
  • ex) 매직 넘버 등
    private const int MaxJumpCount = 2;
  1. readonly :
    런타임 중에 평가, 한번 할당이 되면 변경 불가
  • ex) private readonly int ANIM_ID_DIE = Animator.StringToHash("Die");

  • 둘의 차이 : 평가 시점

C# 정적 멤버 (static 키워드)

  • 앞에 static 키워드를 붙여 정적 멤버를 만들 수 있다.

  • 함수 내에서는 static 키워드를 사용할 수 없다.

C# 기호 상수와 열거형

  • 이름 공간 (namespace)이나 클래스기호 상수를 묶어 관리할 수 있다.

  • 열거형컴파일 단위에서 사용 → C#의 특성 상 자주 사용하지 않는다.

  • 이번 케이스에서 namespace가 아닌 class를 사용하는 이유 :

    1. namespace는 using문으로 생략이 가능하다.

    2. 생략을 하면 “강직성”이 손상될 수 있다.

    3. 따라서 생략이 불가class를 사용, 강직성을 보존한다.

    private static class AnimationID
    {
        public static readonly int IS_ON_GROUND = Animator.StringToHash("IsOnGround"); 
            // string을 참조하지 않기 위해 해시 값으로 변환 // 최적화, 리팩토링에 해당하는 부분
        public static readonly int DIE = Animator.StringToHash("Die"); 
            // string을 참조하지 않기 위해 해시 값으로 변환 // 최적화, 리팩토링에 해당하는 부분
    }

배경 스크롤링

  • Sprite Renderer

    • 그리는 순서를 지정하자

    • Sorting Layer :

      1. 레이어를 여러 개 지정하여 나눈다.

      2. 각 레이어의 위치와 움직이는 속도를 다르게 하여
        원근감을 표현할 수 있다.

    • Order in Layer :

      1. 레이어 내에서 그림을 그리는 순서를 지정한다.

      2. 어떤 그림이 앞에 나오는 지 지정한다고 보면 된다.

디자인 패턴

  • 객체 지향적 설계를 도와주기 위한 패턴

  • 객체 지향 프로그래밍

    • 객체프로그램을 제작한다.

    • 객체 :
      상태(데이터)행동(기능)을 갖고 있는 어떤

      • 실질적, 추상적과 무관하게 모델링 할 수 있다.
    • 현실 세계를 그대로 모델링 할 수 있다는 장점이 있다.

    • 특징

      1. 캡슐화 :
        데이터와 함수를 함께 적을 수 있는 것

      2. 상속 :
        코드를 물려받는 것. 즉, 재사용할 수 있는 것

      3. 추상화 :
        일반적인 인터페이스를 정의하는 것

      4. 다형성 :
        하나의 인터페이스로 다양한 동작을 할 수 있는 것

  • 디자인 패턴은 크게 3가지로 나뉜다.

    1. 생성 패턴

      • 객체의 생성에 관련

      • ex) 단일체 (Singleton) 패턴

    2. 구조 패턴

      • 구조를 잡을 때 다형성을 잘 활용할 수 있도록 설계

      • ex) 컴포넌트복합체 (Composite) 패턴

    3. 행동 패턴

      • 동작과 관련

싱글톤 패턴(Singleton Pattern)

  • 오직 한 개의 인스턴스만을 갖도록 보장하고, 이에 대한 전역적인 접근점을 제공하는 패턴

    • 객체 지향적으로 전역 변수를 만드는 것

    • 추상 클래스인 Singleton 클래스를 상속 받아 싱글톤 클래스를 만드는 것이 일반적

      • 상속 받는 메소드 ⇒ GetInstance()
  • 구현 방법은 상황에 따라 달라질 수 있다.
    아래는 참고용

모던 C++ (C++11 이상) 예시 코드

template <typename T>
class Singleton abstract
{
public:
    Singleton(const Singleton&) = delete;
    Singleton(Singleton&&) = delete;
    Singleton& operator=(const Singleton&) = delete;
    Singleton& operator=(Singleton&&) = delete;

		Singleton& GetInstance()
    {
         static T instance;
         return instance;
    }
};  

// 사용할 때는 상속을 받는다.
class GameManager : public Singleton<GameManager> { };

C예시 코드

public abstract class Singleton<T>
{
    private static T _instance = new T();
    static Singleton()
    {

    }

    public static T Instance
    {
        get
        {
            return _instance;
        }
    }
}

// 사용할 때는 아래와 같이 사용한다.
public class Temp : Singleton<Temp>{}

장점

  1. 인스턴스의 유일성을 보장한다.

    • 인스턴스가 유일하도록 컴파일 단계에서 강제한다.

    • 인스턴스의 개수를 늘리고자 할 때도 유연하게 바꿀 수 있다.
      → 물론, 이때 유일성은 깨지고 전역적인 접근점만 제공한다.

  2. 게으른 초기화(Lazy Initialization)가 일어난다.

    • 사용하지 않는다면 생성되지 않는다.

    • 런타임에 초기화가 된다.

  3. 어디서든 쉽게 접근할 수 있다.

    • 전역적인 접근점을 제공하기 때문
  4. 이름 공간을 좁힌다.

    • 전역 이름 공간 대신 클래스 이름 공간을 사용하기에 이름 공간이 더럽혀지는 것을 방지할 수 있다.

단점

  1. 결국 전역 변수

    • 전역 변수보다 조금 낫지만 결국 전역 변수와 같다.

    • 전역 변수는 코드 간 결합도를 높여 유지 보수를 어렵게 한다.

    • 스레드 동기화 문제가 있다.

  2. 게으른 초기화를 방지할 수 없다.

    • 초기화 시점을 제어해야 하는 경우

      • 초기화에 오랜 시간이 걸릴 경우

      • 메모리 단편화를 방지하기 위해 메모리 풀링(Memory Pooling)을 사용하는 경우

    • 하지만 싱글톤 패턴에서는 초기화 시점의 제어가 불가능하다.

      • 물론 정적 변수를 사용하여 우회를 할 순 있다.
  3. 두 가지 문제를 풀려고 한다.

    • 싱글톤 패턴은 인스턴스의 유일성도 보장하고 전역적인 접근점도 제공한다.

    • 둘 중 하나의 문제만 해결하고자 한다면 곤란해진다.

고려 사항

  • 싱글톤은 굉장히 조심해서 써야 하는 패턴이다.

  • 특히나 편하다는 이유로 남용되기 쉬운 패턴이다.

  1. 꼭 클래스가 필요한가?

    • 다른 객체를 관리하는 매니저 객체를 싱글톤으로 만드는 경우

      → 이는 OOP를 해칠 수 있다.

      • 객체 자체가 판단할 수 있는 것은 객체에 캡슐화 하자.
  2. 정적 변수를 이용해 인스턴스 개수를 제한

    • 런타임에 평가한다는 게 단점
  3. 전역적인 접근점 대신에 다른 방법을 사용

    1. 의존성 주입(Dependency Injection)을 사용하자.

      • 의존성 주입은 매개변수를 이용해 의존성을 넘겨주는 방식
    2. 상위 클래스를 이용

      • 상위 클래스에 정적 변수를 만들어 하위 클래스에서 사용하게 할 수 있다.
    3. 이미 전역인 객체를 이용

      • 기존 전역 객체에 정적 변수를 만들어 이용하게 할 수 있다.
    4. 중재자 패턴을 사용

      • 구체 클래스를 직접 사용하는 대신, 중재자를 두어 결합도를 낮출 수 있다.
  4. 모노스테이트(Monostate)를 활용할 수도 있다.

    • 모노스테이트는 싱글톤 패턴을 살짝 튼 것으로 모든 멤버나 혹은 필드만 정적으로 두는 것

      • Unity의 Time 클래스나 Random 클래스를 생각할 수 있다.
    • 싱글톤 패턴이 꼭 필요한 경우는 생각보다 많이 없다.

      • 싱글톤 인스턴스를 참조하는 경우에도 싱글톤을 직접 가져다 쓰기 보다,
        우회하여 결합도를 낮출 수 있을지 생각하자.

Unity에 싱글톤 패턴 적용

  • 구현 :
    https://haedallog.tistory.com/193

    • Generic :
      C#에서의 템플릿(C++)

    • where 절 :
      제네릭(Generic)을 썼을 때만 사용할 수 있는 제약 조건

      • 의미 :
        Generic에 들어오는 T는 뒤의 클래스를 상속 받아야만 한다.
          public class SingletonBehaviour<T> : MonoBehaviour where T : MonoBehaviour
    • 인스턴스 초기화 시점

      • 주의 :
        Unity 컴포넌트생성자를 이용하여 초기화하면 안된다.
        Unity 엔진이 알아서 생성하고 관리하기 때문.
        https://docs.unity3d.com/kr/current/Manual/CreatingAndUsingScripts.html

        • 따라서 스크립트에 있는 멤버의 초기화는 Unity 이벤트 함수의 실행 순서에 따라 Awake, OnEnable, Start 중에서 하게 된다.

        • 초기화 시점에 적절한 것은 Awake

        • Awake, OnEnable, Start의 차이점
          https://haedallog.tistory.com/192

          1. Awake :
            스크립트가 비활성화 되어 있더라도 게임 오브젝트가 활성화 되어 있으면 무조건 호출

          2. OnEnable :
            게임 오브젝트가 활성화 된 경우에만 게임 오브젝트 혹은 스크립트가 비활성화된 상태에서 활성화될 때 호출

          3. Start :
            게임 오브젝트와 스크립트 모두 활성화된 경우에만 호출

          • 메소드 DontDestroyOnLoad() :
            대상 오브젝트가 씬이 바뀌어도 소멸(파괴)되지 않도록 방지
            따로 대상 오브젝트를 관리함으로써 소멸을 방지
    • SingletonBehaviour를 상속 받는 게임 오브젝트가 다른 모든 게임 오브젝트보다 먼저 Awake()를 처리(즉, 초기화)해야 한다.
      ⇒ 하지만 이 순서를 강제할 수 없다.

      • 그래서 속성을 선언하며 할당한다!
    • Awake()는 가상 함수가 아니다
      → 부모와 자식 클래스의 Awake()가 겹칠 수 있다
      접근 한정자를 바꿔준다.

    • 추가 : 게임 매니저가 씬 자체에 없다면, 동적으로 만들어서 배치하는 방법도 있다.

      • 실수로 오브젝트를 생성하지 않는 경우를 방지한다.
      void Awake()
      {
      	// (생략)
      	// 만약 깜빡하고 해당 오브젝트를 생성하지 않았다면 만들어 준다.
      		if(instance == null)
      		{
      	    GameObject go = new GameObject("@GameManager");
      	    instance = go.AddComponent<T>();
      		}
      }

자료

https://docs.unity3d.com/kr/current/ScriptReference/index.html

[SM's Development Log:티스토리]

https://haedallog.tistory.com/184

https://haedallog.tistory.com/192

https://haedallog.tistory.com/193

0개의 댓글