Unity Chapter 2. 게임 엔진의 원리

Date:     Updated:

카테고리:

태그:

인프런에 있는 이제민님의 레트로의 유니티 C# 게임 프로그래밍 에센스 강의를 듣고 정리한 필기입니다. 😀
🌜 [레트로의 유니티 C# 게임 프로그래밍 에센스] 강의 들으러 가기!


Chapter 2. 게임 엔진의 원리

게임 오브젝트와 컴포넌트

유니티 말고도 언리얼 같은 다른 게임 엔진에도 적용할 수 있는 개념이다. 😁

게임 엔진을 사용하는 이유

게임 엔진은 Low-Level을 감싸고 있는 이미 완성된 기반 코드를 제공한다. 개발자는 게임 개발에 필요한 도구들을 직접 개발할 필요 없이 게임 엔진에서 이미 구현되어 제공되는 기능들을 재사용하기만 하면 되어 편리하다.

상속

  • 컴포넌트는 재사용상속 개념과 밀접해있다.
  • 상속이란 이미 만들어진 클래스를 기반(부모클래스, Base Class)으로 자신만의 새로운 기능을 덧붙여서 새로운 클래스(자식클래스, Derived Class)를 만드는 방법. 자식 클래스에서 부모 클래스로부터 물려받는 부분은 다시 쓸 필요가 없다.
  • 클래스란 묘사할 대상과 관련된 대상들을 묶어놓은 단위.
    • 예를 들어 Moster란 클래스가 있다면 Monster와 관련된 변수 혹은 기능들만 들어가야 함.
    • 예를 들어 게임 플레이어 캐릭터의 공통적인 모든 요소(ex. 인공지능, 크기, 기본 동작 등등)들은 부모 클래스로서 ‘플레이어 클래스’에 묶어 넣으면 되고 전사, 마법사, 궁수 등등 다양한 플레이어들이 이 기본 플레이어의 공통적인 요소들은 ‘플레이어 클래스’를 상속 받아 재사용 하고 ‘전사 클래스’, ‘마법사 클래스’ 같은 자식 클래스들에서 각각 그들만의 고유한 기능들만 붙이면 된다.
    • 공통적이고 기본적인 플레이어 요소를 구현하는 코드는 다시 작성할 필요가 없게 된다.
  • 그러나 상속이 항상 잘 작동하는 것은 아니다.
    • 완벽하게 순수한 Base Class를 만드는 것은 힘들기 때문이다.
      • 자식 클래스가 필요로 하지 않는 기능도 물려줄 수 있다. 선택적으로 상속할 수는 없기 때문에.
    • 예시
      • 캐릭터라는 부모클래스와 이를 상속받는 플레이어, NPC, 몬스터 이렇게 3가지 자식 클래스를 만들었다고 해보자.
        • 캐릭터라는 클래스에 체력 시스템, 공격 시스템, 애니메이션이 구현되어 있다고 해보자.
        • 플레이어 클래스에서는 이것들이 상속되는게 문제가 안되지만 체력 시스템과 공격 시스템이 필요가 없는 NPC 클래스에서는 이 시스템들이 상속되면 체력이 깎이고 죽을 수도 있게 되는 문제가 생길 수 있다.
        • 몬스터 클래스도 캐릭터 클래스를 상속 받으니 플레이어와 공통적인 애니메이션을 갖게 되지만 몬스터의 애니메이션을 플레이어의 애니메이션과 다르게 하고 싶다면 또 문제가 생길 수 있다.
          • 다시 몬스터만의 애니메이션을 재 작성해서 덮어 씌워야하는 작업이 필요.
    • 즉, 이처럼 코드를 상속에만 의지하면 오히려 코드를 재사용하기 힘들어진다.
      • +, - 해야할게 많음.
      • 미래에 사용될 최소 필수 집합을 미리 예상한느 것은 힘들기 때문이다.
        • 모든 캐릭터는 동일한 체력 시스템을 갖출 줄 알았는데 예상치 못하게 체력시스템이 필요 없는 NPC라는 것을 미래에 구현하게 된 경우 같은 예시.
  • ⭐⭐⭐ 따라서 A is B(상속) 구조가 아닌 A has B 구조로서 빈 컨테이너에 필요할 때마다 원하는 기능을 갖다 붙여서 포함시키는 방식을 게임 오브젝트 - 컴포넌트 라고 한다.⭐⭐⭐

게임 오브젝트

  • 단순 홀더, 빈 껍데기
    • 스스로 필요한 기능을 가지고 있지 않음.
  • 물건 하나가 게임 오브젝트 하나에 대응된다고 생각하면 된다.

컴포넌트

  • 게임 오브젝트에 붙일 수 있는 미리 만들어진(재사용할 수 있는) 부품
  • 스스로 동작하는 독립적인 부품이다.
    • 게임 오브젝트에 붙어있는 컴포넌트들은 서로 연관이 없고 독립적이다.
  • 각각의 대표 기능을 가진다.
  • 예시
    • 고양이라는 빈 오브젝트를 생성한 후
    • 고양이에 걸맞는 기능과 속성들을 나타내는 컴포넌트들을 각각 붙여준다.
      • 위치, 물리, 털, 다리, 수염, 네 발로 걷기, 그르릉 거리기. 이런 컴포넌트들을 붙여주는 방식.
  • 🚍 컴포넌트의 장점
    1. 유연한 재사용
      • 상속만 사용했을 때는 부모의 불필요한 부분까지 물려받았어야 했던 반면 컴포넌트는 그냥 각각의 독립적인 기능들을 가져다 붙이며 완성하는 구조라서
    2. 기획자의 프로그래머 의존도가 낮아짐
    3. 컴포넌트의 독립성 덕분에 추가와 삭제가 쉽다.
      • 체력 시스템이라는 컴포넌트가 플레이어 오브젝트와 NPC 오브젝트에 붙어있다고 할 때, NPC 오브젝트에 붙어있는 체력 시스템 컴포넌트를 뗀다고 해서 플레이어 오브젝트에 영향이 가는건 아니다. 기존 시스템에 영향이 가지 않음.
      • 체력 시스템이 두 오브젝트에 상속되는 것이었다면 플레이어 오브젝트에도 영향이 가 문제가 발생했을 것.

C# 스크립트도 컴포넌트의 일종이다.

  • 오브젝트에 붙여 어떤 기능을 수행하게 하는 것이니까.
  • 기본적인 오브젝트의 ‘위치’ 이런 것도 다 컴포넌트다.
  • C# 스크립트 작성시 이미 유니티 내에 내장되어 있는 컴포넌트 객체들을 생성해서 그 컴포넌트의 기능들을 사용할 수 있다
      public Rigidbody myRigidbody; 
      // myRigidbody 라는 변수로 Rigidbody 컴포넌트의 기능들을 가져와 사용할 것.
    
      void Start()
      {
          myRigidbody.AddForce(0, 500, 0); 
          // Rigidbody 컴포넌트에선 AddForce라는 함수를 제공한다. x, y, z 축 방향으로 각각 오브젝트에 힘을 주는 기능.
      }
      void Update()
      {
    
      }
    

    image

  • 유니티 인터페이스에서 위 스크립트를 원하는 오브젝트에 붙여주고 myRigidbody 변수 자리에 Rigidbody 컴포넌트를 붙여주면 스크립트대로 오브젝트가 동작하게 된다.
  • 어려운 물리학 지식을 알고 이를 구현하는 수고를 감수해야할 필요가 없이 그냥 미리 구현되어 있는 컴포넌트를 붙이기만 하면 된다.


메시지와 브로드캐스팅

  • 컴포넌트들은 외부의 간섭을 받지 않는 독립적인 부품들인데 외부에서 컴포넌트들이 수행할 기능을 어떻게 알아차릴 것인가?

MonoBehaviour

  • 컴포넌트들은 모두 MonoBehaviour이라는 클래스를 상속받는다.
    • 모든 컴포넌트들이 필요한 필수적인 기초 기능들을 제공한다.
      • 유니티의 통제를 받는다.
      • 유니티 이벤트 메세지를 감지할 수 있게 된다.
    • 그 자체의 컴포넌트로서 게임 오브젝트에게 추가될 수도 있다. 얘도 컴포넌트니까!
  • 모든 컴포넌트들은 MonoBehaviour을 상속 받고, MonoBehavior은 Behavior을 상속 받고, Behavior은 Component를 상속 받고, Component는 Object를 상속 받는다.
  • 해당 C# 클래스 스크립트를 유니티 오브젝트에 붙이려면 반드시 MonoBehaviour을 상속 받아야 한다.
    • 그래야 컴포넌트로 인식 된다.
  • 일반 C# 스크립트 클래스
    • MonoBehaviour을 상속 받지 않는다.
    • 타 스크립트에서 new를 이용해 객체로 생성할 수 있다.
  • 컴포넌트로 사용될 C# 스크립트 클래스
    • MonoBehaviour를 상속 받아야 한다. 그래야 Component로서 유니티 오브젝트에 붙일 수 있다.
    • 타 스크립트에서 new를 이용해 객체로 생성할 수 없다.

메세지

MonoBehaviour을 상속 받아야 유니티의 브로드캐스팅 메세지를 받을 수 있다.

MonoBehaviour을 상속받아야지만 Start(), Update() 함수 등등 이벤트 함수를 발생시킬 수 있다.

  • 보내는 쪽은 누가 받는지 신경 쓰지 않는다.
  • 받는 쪽은 누가 보냈는지 신경 쓰지 않는다.
  • 컴포넌트 끼리는 서로의 존재를 모른다.
  • 메세지에 명시된 기능을 가지고 있으면 실행하고 없으면 무시한다.
    • 즉, 컴포넌트가 유니티가 보내는 메세지를 듣고 해당 기능을 가지고 있으면 '내 얘기네?'하는 해당되는 컴포넌트들만 반응하여 각자 알아서 스스로 실행하는 것.
      • 해당되는 컴포넌트들이 일괄 실행된다.
      • 컴포넌트의 독립성이 보장된다.
  • 예시
    • 유니티에서 Start! 하고 메세지를 온 오브젝트들에게 뿌리면 Start 함수를 가지고 있는 컴포넌트들이 ‘내 얘기다!’ 하고 반응하여 메세지에 적힌 내용대로 실행한다.
      • Monobehaviour은 유니티의 통제를 받고 메세지를 감지할 수 있는 기능을 제공하기 때문이다. 모든 컴포넌트들은 이를 상속받고 있기 때문에 이 기능이 있는 것.

브로드 캐스팅

  • 메세지를 무차별적으로 많이 보내는 것
  • 100 개의 오브젝트가 있으면 100개의 메세지를 뿌린다. 해당되는 컴포넌트들 반응하라고.

컴포넌트가 가지고 있는 기능

  1. 본인의 고유한 핵심 기능 ex) Transform 컴포넌트라면 위치와 관련된 기능들
  2. Start
    • 컴포넌트 초기화 부분
    • 게임이 처음 활성화 되는 순간에 유니티가 Start 메세지를 브로드캐스팅 하여 뿌려 온 컴포넌트들을 각자 구현된 Start 내용대로 초기화 시킨다.
    • 유니티는 게임 시작할때 Start 메세지를 뿌린다.
  3. Update
    • 1초에 수십번씩 자신의 상태를 갱신하고 주기적으로 계속 실행
    • 외부에서 직접 찾아 실행할 필요 없음. 스스로 매 프레임마다 실행 됨.

유니티 이벤트 메서드

  • 메세지를 통해 이름만 맞춰 구현하면, 실행되야 할 해당 타이밍에 자동으로 컴포넌트가 실행된다.
  • ex) Start, Update, OnTriggerEnter 등등…
    • Start : Unity 게임이 시작되는 타이밍. 게임이 시작될 때 유니티가 보내는 메세지.
    • Update : 그냥 매 프레임마다 계속 실행됨
    • OntrigerEnter : 충돌이 발생되어 인식될 때 유니티가 보내는 메세지.
      • 두 컴포넌트가 충돌한다면 두 컴포넌트는 서로 충돌했는지도 모른다.
      • 다만 유니티가 전체 컴포넌트에게 OntrigerEnter 메세지를 보낸다면 이 메소드를 갖고 있는 두 컴포넌트만 반응하여 충돌 처리를 한다.
        • 두 컴포넌트는 서로 충돌했는지도 모르지만 충돌시 저 메세지에 반응하여 충돌 처리를 하게 되는 것.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기