본문 바로가기
프로그래밍/C#

[C#]🔧 유니티 핵심 완벽 이해: 게임 오브젝트, 컴포넌트, 프리팹 파헤치기 (feat. 트랜스폼 & 스크립트 제어)

by 다다면체 2025. 4. 8.
728x90
반응형

안녕하세요 여러분! 지난 [유니티 C# 스크립팅 기초](이전 글 링크 삽입) 시간에는 코드의 기본 문법과 유니티에서 스크립트를 활용하는 법을 배웠습니다. 스크립트가 게임 오브젝트의 '두뇌' 역할을 한다고 말씀드렸죠? 🧠 오늘은 그 스크립트가 살아 숨 쉴 몸체, 즉 유니티 게임 개발의 가장 근본적인 뼈대인 **게임 오브젝트(GameObject)**와 **컴포넌트(Component)**에 대해 깊이 알아보는 시간을 갖겠습니다.

유니티에서 여러분이 보는 화면 속 모든 것(캐릭터, 배경, UI, 빛, 카메라 등)은 바로 이 게임 오브젝트와 컴포넌트의 조합으로 이루어집니다. 마치 레고 블록처럼, 기본 뼈대에 다양한 기능 블록을 붙여 원하는 것을 만드는 방식이죠! 🧱 이 개념을 확실히 이해하면 유니티 개발이 훨씬 쉽고 재미있어질 겁니다. 효율적인 개발을 위한 **프리팹(Prefab)**과 모든 오브젝트의 기본인 트랜스폼(Transform), 그리고 스크립트로 이들을 제어하는 방법까지! 핵심만 쏙쏙 뽑아 알려드릴게요. 시작해볼까요? ✨

반응형

1. 게임 오브젝트(GameObject)란? 유니티 세상의 모든 것! 🌍

  • 정의: 게임 오브젝트는 유니티 씬(Scene)을 구성하는 가장 기본적인 '단위' 또는 '개체'입니다. 그 자체로는 아무런 형태나 기능이 없는 **텅 빈 컨테이너(Empty Container)**와 같습니다. 이 컨테이너에 다양한 '부품' 즉, 컴포넌트를 부착함으로써 비로소 의미와 기능(모양, 소리, 움직임, 상호작용 등)을 갖게 됩니다.
  • 빈 게임 오브젝트 (Empty GameObject):
    • GameObject > Create Empty 메뉴를 통해 생성할 수 있습니다. 이름처럼 정말 텅 비어있고, 씬 뷰에서도 보이지 않습니다. (기즈모는 보일 수 있음)
    • 활용 예시:
      • 정리용: 여러 관련 오브젝트들을 자식으로 묶어 계층 구조(Hierarchy)를 깔끔하게 정리하는 폴더 역할. (예: 'Enemy Spawners' 오브젝트 밑에 여러 스폰 포인트 오브젝트 두기)
      • 스크립트 홀더: 특정 게임 로직(게임 매니저, UI 관리자 등)을 담은 스크립트를 붙여두는 용도. 화면에 보일 필요는 없지만 게임 전체에 영향을 주는 스크립트를 관리하기 좋습니다.
      • 기준점(Pivot): 복잡한 움직임이나 회전의 중심점 역할을 하도록 빈 오브젝트를 활용하기도 합니다.
  • 프리팹 (Prefab): 게임 오브젝트의 청사진! ⭐ (매우 중요!)
    • 개념: 잘 만들어진 게임 오브젝트(컴포넌트 구성, 설정값 포함)를 **재사용 가능한 템플릿(Template)**으로 저장해 둔 것입니다. 마치 붕어빵 틀이나 쿠키 커터와 같죠! 🍪
    • 생성 방법: 하이어라키 창의 게임 오브젝트를 프로젝트(Project) 창으로 드래그 앤 드롭하면 파란색 큐브 아이콘의 프리팹 에셋이 생성됩니다.
    • 활용 (장점):
      • 효율성: 똑같은 오브젝트(총알, 적 캐릭터, 나무, 동전 등)를 여러 번 만들어야 할 때, 프리팹을 찍어내듯 인스턴스화(Instantiate)하여 빠르게 배치할 수 있습니다. (프로젝트 창의 프리팹을 씬이나 하이어라키 창으로 드래그)
      • 일괄 수정: 원본 프리팹을 수정하면, 그 프리팹으로부터 생성된 모든 인스턴스(복제본)에 변경 사항이 자동으로 반영됩니다! 예를 들어, 적 캐릭터 프리팹의 체력을 올리면 씬에 배치된 모든 해당 적 캐릭터의 체력이 같이 올라갑니다. 유지보수가 매우 편리해지죠.
      • 동적 생성: 게임 실행 중에 스크립트를 이용해 프리팹으로부터 새로운 게임 오브젝트를 동적으로 생성할 수 있습니다. (예: 총알 발사, 몬스터 소환)
    • 프리팹은 유니티 개발의 핵심 워크플로우입니다. 꼭 익숙해지세요!

2. 컴포넌트 기반 구조 🧩

유니티의 핵심 철학은 바로 이 **컴포넌트 기반 구조(Component-Based Architecture)**입니다.

  • 개념: 텅 빈 게임 오브젝트에 필요한 기능(컴포넌트)들을 레고 블록처럼 조립하여 원하는 개체를 만드는 방식입니다. 예를 들어, '보이는' 기능을 원하면 Renderer 컴포넌트를, '물리적 충돌' 기능을 원하면 Collider 컴포넌트를, '사용자 정의 행동'을 원하면 직접 만든 Script 컴포넌트를 붙이는 식이죠.
  • 장점: 매우 유연하고 확장성이 좋습니다. 필요한 기능을 부품처럼 추가/제거/교체하기 쉽고, 코드 재사용성도 높아집니다.
  • 컴포넌트 추가 및 설정:
    • 게임 오브젝트를 선택하고 인스펙터(Inspector) 창 하단의 Add Component 버튼을 클릭합니다.
    • 검색창에 원하는 컴포넌트 이름(예: Box Collider, Rigidbody, Audio Source)을 입력하거나 카테고리에서 찾아 선택합니다.
    • 추가된 컴포넌트의 세부 속성(값)들은 인스펙터 창에서 바로 수정할 수 있습니다. (예: Collider 크기 조절, Rigidbody 질량 설정)
  • 자주 사용되는 기본 컴포넌트:
    • Transform: 모든 게임 오브젝트가 반드시 가지고 있는 컴포넌트. 위치, 회전, 크기를 담당합니다. (아래에서 자세히!)
    • Mesh Renderer: 3D 모델(Mesh)을 화면에 그리는 역할을 합니다. 어떤 머티리얼(색상, 질감 등)을 사용할지 등을 설정합니다.
    • Sprite Renderer: 2D 이미지(Sprite)를 화면에 그리는 역할을 합니다. 색상, 정렬 순서 등을 설정합니다.
    • Collider (Box, Sphere, Capsule, Mesh 등): 물리적인 형태를 정의하여 다른 Collider와 충돌을 감지할 수 있게 합니다. 눈에 보이지는 않지만 물리 엔진에 필요합니다. (2D 게임에서는 Collider 2D 계열 사용)
    • Rigidbody: 오브젝트가 유니티 물리 엔진(중력, 힘, 충돌 반응 등)의 영향을 받도록 합니다. (2D 게임에서는 Rigidbody 2D 사용)
    • Audio Source: 사운드 클립을 재생하는 역할을 합니다. 볼륨, 피치 등을 설정합니다.
    • Camera: 게임 월드를 플레이어에게 보여주는 '눈' 역할을 합니다. 씬에는 반드시 하나 이상의 활성화된 카메라가 필요합니다.
    • Script Component: 우리가 C#으로 작성한 바로 그 스크립트! 게임 오브젝트에 커스텀 동작과 로직을 부여합니다.

3. 트랜스폼(Transform) 컴포넌트 📐

모든 게임 오브젝트는 '존재'하기 위해 공간(씬) 안에서의 위치, 회전 상태, 크기 정보를 가져야 합니다. 이 정보를 담고 관리하는 것이 바로 Transform 컴포넌트입니다.

  • 핵심 속성:
    • Position: 씬의 월드 좌표계(World Space) 기준 X, Y, Z 위치 값.
    • Rotation: 각 축(X, Y, Z) 기준 회전 값 (도 단위).
    • Scale: 각 축(X, Y, Z) 기준 크기 배율 (1이 기본 크기).
    • 이 값들은 인스펙터 창에서 직접 수정하거나, 씬 뷰의 이동(Move), 회전(Rotate), 크기(Scale) 기즈모(Gizmo) 도구를 사용해 시각적으로 조작할 수 있습니다. (W, E, R 단축키 기억하시죠?)
  • 부모-자식 관계 (Parent-Child Relationship):
    • 설정: 하이어라키 창에서 게임 오브젝트 A를 드래그하여 다른 게임 오브젝트 B 위에 놓으면, A는 B의 **자식(Child)**이 되고 B는 A의 **부모(Parent)**가 됩니다.
    • 특징:
      • 상대적 좌표: 자식 오브젝트의 Transform 값(Position, Rotation, Scale)은 부모 오브젝트를 기준으로 한 상대적인 값(Local 좌표)으로 표시되고 계산됩니다.
      • 종속적 변환: 부모 오브젝트를 이동/회전/크기 조절하면, 자식 오브젝트도 그에 맞춰 함께 변형됩니다. (단, 자식의 Local Transform 값은 변하지 않습니다.)
    • 활용 예시: 플레이어 캐릭터(부모)의 손 위치에 무기(자식)를 붙여두면, 플레이어가 움직이거나 회전할 때 무기도 자연스럽게 따라 움직입니다. 자동차(부모)에 바퀴(자식)들을 붙여두는 것도 마찬가지죠. 계층 구조를 잘 활용하면 복잡한 오브젝트 구성과 움직임을 효율적으로 관리할 수 있습니다.
  • Local vs. World 좌표계:
    • 월드(World) 좌표: 씬 전체의 절대적인 기준 좌표계입니다. 부모가 없는 최상위 오브젝트의 Transform 값은 월드 좌표를 나타냅니다.
    • 로컬(Local) 좌표: 부모 오브젝트를 기준으로 한 상대적인 좌표계입니다. 자식 오브젝트의 인스펙터 창에 표시되는 Position, Rotation, Scale 값은 기본적으로 이 로컬 값입니다. 스크립트에서는 transform.position, transform.rotation (월드)과 transform.localPosition, transform.localRotation (로컬) 등으로 구분하여 접근할 수 있습니다.

4. 스크립트로 컴포넌트 제어하기 🤝

이제 스크립트(이것도 컴포넌트!)를 이용해 같은 게임 오브젝트에 붙어있는 다른 컴포넌트들을 제어하는 방법을 알아봅시다. 이게 가능해야 코드로 오브젝트를 움직이거나, 색을 바꾸거나, 물리 효과를 주는 등 실질적인 게임 로직 구현이 가능해집니다.

  • 핵심 함수: GetComponent<T>()
    • 스크립트 내에서 같은 게임 오브젝트에 붙어있는 다른 컴포넌트를 찾아 접근할 때 사용하는 필수 함수입니다.
    • <T> 부분에는 가져오고 싶은 컴포넌트의 **타입(자료형)**을 적어줍니다. (예: SpriteRenderer, Rigidbody, BoxCollider)
    • 함수는 해당 타입의 컴포넌트를 찾아 **참조(Reference)**를 반환해줍니다. 이 참조를 변수에 저장해두고 사용합니다.
using UnityEngine;

public class ComponentController : MonoBehaviour {
    // 다른 컴포넌트 참조를 저장할 변수들 (private으로 선언하고 Start에서 찾아 할당하는 것이 좋음)
    private SpriteRenderer myRenderer;
    private Rigidbody2D rb2d;
    private BoxCollider2D boxCollider;

    void Start() {
        // GetComponent<T>()를 사용해 같은 게임 오브젝트의 다른 컴포넌트 찾아오기
        myRenderer = GetComponent<SpriteRenderer>();
        rb2d = GetComponent<Rigidbody2D>();         // 2D 물리용 Rigidbody
        boxCollider = GetComponent<BoxCollider2D>(); // 2D 충돌용 Collider

        // Tip: 컴포넌트가 없을 경우를 대비해 null 체크를 해주는 것이 안전합니다.
        if (myRenderer == null) {
            Debug.LogError("SpriteRenderer 컴포넌트를 찾을 수 없습니다!");
        }
        if (rb2d == null) {
             Debug.LogWarning("Rigidbody2D 컴포넌트가 없습니다. 물리 효과를 사용하지 않을 수 있습니다.");
        }
    }

    void Update() {
        // 예제: 컴포넌트 속성 제어
        if (Input.GetKeyDown(KeyCode.Space)) { // 스페이스 바를 누르면
            if (myRenderer != null) {
                myRenderer.color = Color.red; // 스프라이트 색상을 빨간색으로 변경
            }
        }

        if (Input.GetKeyUp(KeyCode.Space)) { // 스페이스 바를 떼면
             if (myRenderer != null) {
                myRenderer.color = Color.white; // 원래 색상(흰색)으로 복구
             }
        }

        // 예제: 컴포넌트 메서드 호출 (Rigidbody 2D에 위쪽으로 힘 가하기)
        if (Input.GetKeyDown(KeyCode.UpArrow)) {
            if (rb2d != null) {
                rb2d.AddForce(Vector2.up * 5f, ForceMode2D.Impulse); // 위쪽으로 순간적인 힘 가하기
            }
        }

        // 예제: 컴포넌트 활성화/비활성화
        if (Input.GetKeyDown(KeyCode.C)) {
            if (boxCollider != null) {
                boxCollider.enabled = !boxCollider.enabled; // Collider 켜고 끄기 토글
                Debug.Log("Collider 활성화 상태: " + boxCollider.enabled);
            }
        }
    }
}
  • 성능 팁: GetComponent<T>()는 매번 호출될 때마다 검색 작업을 수행하므로, Update() 함수처럼 매 프레임 호출되는 곳에서 반복적으로 사용하는 것은 성능에 좋지 않습니다. 가급적 Start()나 Awake() 함수에서 한 번만 호출하여 그 결과를 변수에 저장해두고, 필요할 때 변수를 사용하는 것이 훨씬 효율적입니다! 👍

마무리하며 & 다음 단계 예고 🏁

휴! 오늘은 유니티의 심장이라고 할 수 있는 게임 오브젝트와 컴포넌트 시스템, 효율적인 개발을 위한 프리팹, 모든 오브젝트의 기준이 되는 트랜스폼, 그리고 스크립트로 이들을 제어하는 방법까지 정말 중요한 내용들을 다뤘습니다. 🎉 이 모듈식 조립 방식 이야말로 유니티의 강력함과 유연함의 원천이랍니다.

 

이제 여러분은 빈 게임 오브젝트에 원하는 컴포넌트들을 착착 붙여나가며 상상하는 무엇이든 만들 수 있는 기본기를 갖추게 되었습니다! 또한, 스크립트로 트랜스폼 컴포넌트의 위치(Position) 값을 바꾸거나 Rigidbody 컴포넌트에 힘을 가하는 방법을 배웠으니… 다음엔 무엇을 할 수 있을까요?

 

맞습니다! 드디어 캐릭터를 직접 움직여 볼 차례입니다! ⌨️🖱️ 다음 시간에는 오늘 배운 내용을 바탕으로, C# 스크립트를 이용하여 게임 오브젝트를 키보드나 마우스로 조작하는 다양한 방법에 대해 자세히 알아볼 예정입니다. 기대되시죠?

💬 오늘 배운 게임 오브젝트와 컴포넌트들을 조합해서 어떤 재미있는 것을 만들어보고 싶으신가요? 여러분의 기발한 아이디어를 댓글로 남겨주세요!

728x90
반응형