[Unity] 컴포넌트 조합과 상속의 차이: 이동·체력 기능 나누기
Unity 컴포넌트 조합과 C# 상속의 차이를 비교해요.
![]() |
| ChatGPT 생성 이미지 |
Unity 컴포넌트 조합과 상속은 역할이 달라요
움직이는 적과 제자리에 있는 표적에 같은 체력 코드를 쓰고 싶은데 클래스를 어떻게 나눠야 할지 헷갈리시나요?
기능의 유무가 대상마다 다르면 컴포넌트 조합으로 시작하는 편이 이해하기 쉬워요.
상속은 부모 타입의 약속을 자식 타입도 지키는 관계를 표현할 때 검토해요.
컴포넌트 조합은 Unity의 GameObject에 필요한 기능을 붙이는 구성 방식이에요.
C# 상속은 그 컴포넌트를 작성하는 코드에서도 함께 쓰여요. Health가 MonoBehaviour를 상속하면서 이동 컴포넌트와 조합될 수 있으니 두 방법이 서로 배타적인 것은 아니에요.
최신 LTS인 Unity 6.3(6000.3) 문서를 기준으로 해요. 큐브 두 개로 이동과 체력의 독립성을 비교하는 실습 하나만 진행하니 처음이어도 부담 없이 따라올 수 있어요. 제시한 수치와 로그는 코드에 따른 예상 결과이며 실제 실행 측정값은 아니에요.
조합·상속·책임·의존 관계 비교
대상 이름보다 ‘어떤 기능이 함께 바뀌는가’를 살피면 클래스를 불필요하게 키우지 않을 수 있어요.
1. 컴포넌트 조합 — 필요한 기능을 골라 붙이기
움직이지 않는 표적에도 체력을 주려면 기능을 선택할 수 있어야 해요.
조합(Composition)은 여러 기능을 모아 하나의 대상을 구성하는 방식이에요. 도시락에 반찬을 골라 넣듯 이동과 체력을 필요한 대상에만 붙여요.
Unity에서는 ① 왼쪽 [Hierarchy]에서 대상 선택 → ② 오른쪽 [Inspector] 맨 아래 [Add Component] 클릭 → ③ 원하는 기능 선택 순서로 추가해요. 이번 예제는 움직이는 큐브에 이동과 체력을, 고정 큐브에는 체력만 넣어요.
2. 상속 — 부모 타입의 약속을 이어받기
공통된 약속을 가진 타입들을 같은 방식으로 다루려면 상속을 검토할 수 있어요.
상속(Inheritance)은 기반 클래스의 접근 가능한 멤버를 파생 클래스가 이어받는 관계예요.
같은 규격의 충전기에 서로 다른 제품을 연결하듯 부모 타입을 받는 코드가 자식도 다룰 수 있어야 해요.
C# 클래스가 직접 상속하는 기반 클래스는 하나예요. Health : MonoBehaviour도 상속의 한 사례이지만, 게임 속 모든 대상을 거대한 Character 클래스의 자식으로 만들 필요는 없어요.
3. 책임 — 한 기능이 관리할 범위
체력 수치를 바꿀 때 이동 코드까지 수정하지 않으려면 책임을 정해야 해요.
책임(Responsibility)은 그 코드가 무엇을 알고 어떤 결과를 만드는지의 범위예요.
계산대가 결제를 맡고 창고가 재고를 맡듯 서로 다른 변화 이유를 나누는 거예요.
이 예제의 Health는 체력의 초기화와 감소만 맡아요. 체력이 0일 때 멈추거나 사라지는 규칙까지 자동으로 생기는 것은 아니에요.
4. 의존 관계 — 다른 기능을 알아야 하는 연결
기능을 잘게 나누기만 하면 끝나는지 판단하려면 의존 관계도 봐야 해요.
의존성(Dependency)은 한 코드가 다른 코드나 데이터에 기대는 관계예요.
플러그를 뽑으면 전등이 꺼지듯 필요한 연결이 없으면 기능이 성립하지 않을 수 있어요.
이번 Health와 MoveAlongX는 서로를 조회하지 않아서 독립적으로 시험할 수 있어요.
사망 후 이동 중단이 필요해지면 두 기능을 연결하는 게임 규칙을 명시적으로 추가해야 해요.
실제 연습: 이동 표적과 고정 표적의 체력 비교
큐브 두 개에 같은 Health를 붙인 뒤 이동을 꺼도 피해 처리가 가능한지 확인해요.
STEP 1. 큐브 두 개와 Scripts 폴더 준비
기본 3D 씬에서 ① 상단 [GameObject] 클릭 → ② [3D Object] → ③ [Cube] 선택을 두 번 반복해요.
왼쪽 [Hierarchy]에서 두 큐브의 이름을 각각 MovingDummy와 StationaryDummy로 바꿔요.
오른쪽 [Inspector] → [Transform] → [Position]을 각각 (-2, 0, 0), (2, 0, 0)으로 맞추세요.
이제 스크립트를 담을 폴더를 만들어요. ① 하단 [Project]에서 [Assets] 폴더 선택 → ② 빈 곳 우클릭 → ③ [Create] → [Folder] 선택 후 이름을 Scripts로 입력해요.
앞선 회전 큐브를 복제했다면 오른쪽 [Rotate Object] 제목 옆 체크를 꺼서 회전이 섞이지 않게 해요.
STEP 2. 이동과 체력을 서로 다른 파일에 작성
먼저 이동 스크립트를 만들어요.
① 하단 [Project]에서 Scripts 폴더 선택 → ② 상단 [Assets] → [Create] → [Scripting] → [MonoBehaviour Script] 선택 → ③ 이름을 MoveAlongX로 입력하고 Enter를 눌러요.
파일 이름은 안에 있는 클래스 이름과 똑같이 맞추세요. 다르면 Unity가 경고를 띄우거나 컴포넌트를 찾지 못할 수 있거든요.
만든 파일을 더블클릭해 C# 편집기에서 열고, 기본 내용을 모두 지운 뒤 아래 코드로 바꿔 저장해요. MoveAlongX는 매 프레임 월드 X 방향으로 이동해요. Vector3.right는 월드 오른쪽 방향이고, Time.deltaTime을 곱해 프레임별 이동량을 계산해요.
using UnityEngine;
public class MoveAlongX : MonoBehaviour
{
[SerializeField] private float unitsPerSecond = 1f;
private void Update()
{
transform.position +=
Vector3.right * unitsPerSecond * Time.deltaTime;
}
}
같은 방법으로 Health 스크립트를 하나 더 만들어요.
Health는 시작할 때 현재 체력을 최대 체력으로 맞추고, 양수 피해만 받으며 0 아래로 내려가지 않아요. ContextMenu는 실습용 호출 메뉴를 컴포넌트에 만들어주는 속성이에요.
using UnityEngine;
public class Health : MonoBehaviour
{
[SerializeField] private int maxHealth = 100;
[SerializeField] private int currentHealth;
public int CurrentHealth => currentHealth;
private void Awake()
{
maxHealth = Mathf.Max(1, maxHealth);
currentHealth = maxHealth;
}
public void TakeDamage(int amount)
{
if (amount <= 0 || currentHealth <= 0)
return;
currentHealth = Mathf.Max(0, currentHealth - amount);
Debug.Log($"{name}: HP {currentHealth}/{maxHealth}", this);
}
[ContextMenu("Take 10 Damage (Play Mode)")]
private void TakeTestDamage()
{
if (!Application.isPlaying)
{
Debug.Log("Enter Play Mode before testing.", this);
return;
}
TakeDamage(10);
}
}
이 코드는 입력이나 물리 충돌을 처리하는 캐릭터 컨트롤러가 아니라 기능 분리를 살피는 연습용이에요.
currentHealth는 관찰을 위해 Inspector에 노출했어요. 이번에는 Inspector 값을 직접 바꾸지 않고 피해 메뉴로만 변경해요.
STEP 3. 서로 다른 조합으로 부착
① 하단 [Project]에서 MoveAlongX 선택 → ② 왼쪽 [Hierarchy] → [MovingDummy] 위로 드래그해요. Health는 MovingDummy와 StationaryDummy에 각각 드래그하세요. 오른쪽 [Inspector]에서 두 표적 모두 [Health]가 있고 MovingDummy에만 [Move Along X]가 있는지 확인해요.
두 표적의 [Health] → [Max Health]를 100으로 맞추고, MovingDummy의 [Move Along X] → [Units Per Second]는 1로 두세요. ① 상단 [File] 클릭 → ② [Save As] 선택 → ③ Assets/Scenes 폴더에 CompositionLab 이름으로 저장해요.
STEP 4. 기준 상태와 피해 입력 확인
상단 가운데 [Play] 버튼(삼각형 ▶)을 눌러요. 왼쪽 [Hierarchy]에서 표적을 하나씩 선택해 오른쪽 [Transform] → [Position]과 [Health] → [Current Health]를 관찰하세요. MovingDummy만 X 값이 증가하고 두 표적의 체력은 각각 100이어야 해요.
① 오른쪽 [Health] 제목 우클릭 → ② [Take 10 Damage (Play Mode)] 선택 순서로 피해를 줘요.
MovingDummy의 체력이 90이 되는지 확인하고 StationaryDummy에서도 같은 메뉴를 실행해요. 상단 [Window] → [General] → [Console]에는 각 이름 뒤에 HP 90/100이 표시되는 것을 기대해요.
STEP 5. ‘이동을 끄면 피해도 멈출 것’이라는 오해 재현
여기서 ‘이동을 껐으니 이제 피해도 안 받겠지?’라고 예상하는 분이 꽤 많아요. 같은 오브젝트에 붙어 있으니 함께 멈출 것처럼 느껴지거든요. 직접 확인해 볼게요.
① MovingDummy 선택 → ② 오른쪽 [Move Along X] 제목 옆 체크 해제 → ③ [Health] 우클릭 → [Take 10 Damage (Play Mode)] 선택 순서로 진행해요. Position X는 멈추지만, 앞 단계에서 한 번 피해를 받았다면 체력은 90에서 80으로 줄어야 해요.
체력이 줄어드는 것은 오류가 아니에요. Health는 이동 컴포넌트의 활성 상태를 검사하지 않기 때문이에요. 이동 정지와 무적을 같은 규칙으로 만들고 싶다면 그 조건을 피해 처리에 별도로 연결해야 해요.
STEP 6. 체력 하한과 재시작 확인
각 표적에서 피해 메뉴를 누적 10번 실행하면 체력이 0이 되는지 확인해요. 한 번 더 실행해도 음수가 되면 안 돼요. 이 코드는 이미 0이면 바로 반환하므로 추가 로그도 나오지 않아요.
① 상단 가운데 [Play]를 눌러 실행 종료 → ② 다시 [Play]를 눌러 재실행해요. 기본 Play Mode 설정에서는 Awake가 다시 실행되어 각 체력이 100으로 시작하고, 꺼 두었던 [Move Along X]도 다시 켜진 상태로 돌아와요. 실행 후에도 0이라면 오브젝트 활성 체크, 컴포넌트 부착, Console 오류를 순서대로 확인해요.
STEP 7. 조합을 선택한 이유 정리
두 표적에서 체력은 같은 방식으로 줄고 이동 여부만 다르면 이번 구성의 목적을 확인한 거예요. [Play]를 눌러 실행을 종료하면 Play 모드 중에 바꾼 체크, 위치, 체력 값은 실행 전 상태로 자동으로 돌아가요. 편집 상태에서 ① 상단 [File] 클릭 → ② [Save] 선택으로 씬을 저장하고, 표적 이름·붙인 컴포넌트·피해 횟수·최종 체력을 기록해 두세요.
이 구조의 이점은 이동하지 않는 대상에 이동 코드가 필요 없다는 점이에요. 대신 ‘죽으면 정지’처럼 기능 사이의 규칙을 연결할 위치는 따로 정해야 해요. 기능 수가 늘 때는 재사용 이점과 연결을 추적하는 비용을 함께 비교하면 돼요.
- 컴포넌트를 끄면 모든 메서드가 실행되지 않나요?
- Behaviour를 비활성화하면 Unity가 그 컴포넌트의 Update를 호출하지 않아요.
- 다른 코드가 해당 인스턴스의 일반 메서드를 직접 호출하는 것까지 자동으로 막지는 않아요.
- 그래서 이동을 끄는 조작과 피해를 받지 않는 상태는 따로 설계해야 해요.
- 이번에는 오른쪽 [Inspector] → [Move Along X]의 체크만 끄고 Health 메뉴를 실행해 차이를 확인해요.
- 무적 규칙이 필요하다면 TakeDamage 안에서 피해 허용 상태를 검사하는 식으로 의도를 명확하게 표현하세요.
- 체력이 0인데 MovingDummy가 계속 움직여요
- 이 예제의 MoveAlongX는 Health를 읽지 않아서 체력이 0이어도 이동해요.
- Health는 수치 계산만 담당하고 사망 시 행동을 결정하지 않아요.
- 오른쪽 [Inspector]에서 [Current Health]가 0이고 [Move Along X]가 켜져 있다면 코드에 맞는 결과예요.
- 게임 규칙으로 정지를 원한다면 대상의 상태를 관리하는 코드에서 두 컴포넌트를 연결해야 해요.
- 제자리 표적도 쓰는 Health에 이동 기능을 무조건 넣기보다 어떤 대상에 연결이 필요한지 먼저 구분하세요.
- Unity에서는 상속을 쓰지 않는 것이 좋나요?
- 상속 자체를 피해야 하는 것은 아니에요.
- 이번 두 컴포넌트도 Unity의 MonoBehaviour를 상속해서 씬에 붙는 기능이 돼요.
- 부모 타입의 약속을 자식 타입도 지키며 공통 구현을 공유한다면 상속이 자연스러울 수 있어요.
- 반대로 이동 유무와 체력 유무가 제각각이면 기능을 골라 조합하는 방식이 단순해요.
- 오른쪽 [Inspector]에서 어떤 조합이 실제로 필요한지 먼저 적어보면 코드 구조를 정하는 데 도움이 돼요.
