3D 공룡 달리기 - 선택 문과 열거형

열거형(enum)으로 문의 종류를 분류하고, SerializeField 어트리뷰트와 SpriteRenderer로 문을 시각적으로 구성하는 방법을 학습합니다

공룡이 달려가다 만나는 "선택 문(Select Doors)"을 구성하는 스크립트입니다. 각 문에는 덧셈·뺄셈·곱셈·나눗셈 중 하나의 연산 타입(type)과 숫자가 표시되며, 플레이어가 어느 문을 통과하느냐에 따라 공룡 수가 바뀝니다. SelectDoors는 두 문의 색상과 텍스트를 초기화하고, 충돌 시 연산 정보를 제공하는 역할을 합니다.


게임 구조

스크립트 역할
SelectDoors 문 타입(enum) 설정, 색상·텍스트 초기화, 좌/우 연산 정보 제공
DinoController 공룡 이동 및 문 충돌 감지 (튜토리얼 52)
DinoPositionController 문 통과 후 공룡 수 증감 처리 (튜토리얼 52)

SelectDoors - 전체 코드

using UnityEngine;
using TMPro;

public enum DoorType { Plus, Minus, Times, Division }

public class SelectDoors : MonoBehaviour
{
    public SpriteRenderer rightDoorSpriteRD;
    public SpriteRenderer leftDoorSpriteRD;
    public TextMeshPro rightDoorText;
    public TextMeshPro leftDoorText;

    [SerializeField] private DoorType rightDoorType;
    public int rightDoorNumber;
    [SerializeField] private DoorType leftDoorType;
    public int leftDoorNumber;

    public Color goodColor;
    public Color badColor;

    void Start() { SettingDoors(); }

    public void SettingDoors()
    {
        if (rightDoorType.Equals(DoorType.Plus)) { rightDoorSpriteRD.color = goodColor; rightDoorText.text = "+" + rightDoorNumber; }
        else if (rightDoorType.Equals(DoorType.Minus)) { rightDoorSpriteRD.color = badColor; rightDoorText.text = "-" + rightDoorNumber; }
        else if (rightDoorType.Equals(DoorType.Times)) { rightDoorSpriteRD.color = goodColor; rightDoorText.text = "x" + rightDoorNumber; }
        else if (rightDoorType.Equals(DoorType.Division)) { rightDoorSpriteRD.color = badColor; rightDoorText.text = "÷" + rightDoorNumber; }

        if (leftDoorType.Equals(DoorType.Plus)) { leftDoorSpriteRD.color = goodColor; leftDoorText.text = "+" + leftDoorNumber; }
        else if (leftDoorType.Equals(DoorType.Minus)) { leftDoorSpriteRD.color = badColor; leftDoorText.text = "-" + leftDoorNumber; }
        else if (leftDoorType.Equals(DoorType.Times)) { leftDoorSpriteRD.color = goodColor; leftDoorText.text = "x" + leftDoorNumber; }
        else if (leftDoorType.Equals(DoorType.Division)) { leftDoorSpriteRD.color = badColor; leftDoorText.text = "÷" + leftDoorNumber; }
    }

    public DoorType GetDoorType(float xPos) { return xPos > 0 ? rightDoorType : leftDoorType; }
    public int GetDoorNumber(float xPos) { return xPos > 0 ? rightDoorNumber : leftDoorNumber; }
}

핵심 개념 설명

1. enum (열거형(enum)) - DoorType

public enum DoorType { Plus, Minus, Times, Division }

**열거형(enum)**은 관련된 상수(constant)들에 이름을 붙여 묶어 놓은 타입(type)입니다. 숫자 대신 의미 있는 이름을 사용할 수 있어 코드 가독성이 크게 높아집니다.

// enum 없이 숫자로 표현하는 경우 (의미를 알기 어려움)
int doorType = 0;  // 0이 Plus인지 Minus인지 코드만 봐서는 모름

// enum으로 표현하는 경우 (의미가 명확)
DoorType doorType = DoorType.Plus;  // 바로 이해 가능

enum의 내부 값: 기본적으로 0부터 시작하는 정수(integer)값을 가집니다.

이름 내부 정수(integer)
DoorType.Plus 0
DoorType.Minus 1
DoorType.Times 2
DoorType.Division 3

언제 enum을 쓰는가?

  • 정해진 몇 가지 선택지 중 하나를 나타낼 때
  • 방향(상/하/좌/우), 상태(대기/이동/공격), 종류(불/물/풀 속성(property)) 등
  • 조건문(conditional statement)에서 분기를 명확하게 구분하고 싶을 때
// enum 활용 예시
public enum GameState { Playing, Paused, GameOver }
public enum AttackType { Melee, Ranged, Magic }

.Equals vs == 비교

// .Equals 사용 (이 코드에서 사용한 방식)
if (rightDoorType.Equals(DoorType.Plus)) { ... }

// == 연산자 사용
if (rightDoorType == DoorType.Plus) { ... }

두 방식 모두 enum 비교에서 동일하게 동작합니다. enum은 값 타입(Value Type)이므로 참조 비교가 아닌 값 비교를 수행합니다.

비교 방식 특징
.Equals() 메서드(method) 호출 방식, null 안전하지 않음 (enum은 null 불가이므로 실제로는 문제 없음)
== 연산자(operator) 더 간결한 표현, 가독성 좋음

2. SerializeField - private 필드(field)를 Inspector에서 편집

[SerializeField] private DoorType rightDoorType;
[SerializeField] private DoorType leftDoorType;

[SerializeField] 어트리뷰트(attribute)를 붙이면 private 필드(field)임에도 Unity Inspector에서 확인하고 편집할 수 있습니다.

왜 public 대신 SerializeField를 쓰는가?

public으로 선언하면 Inspector에서도 편집할 수 있지만, 다른 스크립트에서도 자유롭게 접근·수정이 가능해집니다. 문의 타입(type)은 이 오브젝트 내부에서만 관리해야 하므로 private으로 캡슐화(encapsulation)하되, 디자이너가 Inspector에서 설정할 수 있도록 [SerializeField]를 사용합니다.

선언 방식 Inspector 편집 외부 스크립트 접근 사용 시기
public 가능 가능 다른 스크립트에서도 읽거나 써야 할 때
[SerializeField] private 가능 불가능 Inspector에서만 설정하고 외부 접근은 막을 때
private 불가능 불가능 완전히 내부 전용 데이터일 때
// 비교 예시
public int score;                    // Inspector ○, 외부 접근 ○
[SerializeField] private int score;  // Inspector ○, 외부 접근 ✗
private int score;                   // Inspector ✗, 외부 접근 ✗

Inspector에서 DoorType enum 필드(field)는 드롭다운 메뉴로 표시되어 Plus / Minus / Times / Division 중 선택할 수 있습니다.


3. SpriteRenderer.color - 문 색상 설정

public SpriteRenderer rightDoorSpriteRD;
public Color goodColor;
public Color badColor;

rightDoorSpriteRD.color = goodColor;  // 좋은 문: goodColor로 칠하기
rightDoorSpriteRD.color = badColor;   // 나쁜 문: badColor로 칠하기

SpriteRenderer: 2D 스프라이트(이미지)를 렌더링하는 컴포넌트입니다. .color 프로퍼티로 스프라이트에 색상을 곱해서 적용합니다. 흰색 스프라이트에 색상을 곱하면 그 색상 그대로 표시됩니다.

// Color 사용 예시
spriteRenderer.color = Color.red;          // 빨간색
spriteRenderer.color = Color.blue;         // 파란색
spriteRenderer.color = new Color(1, 0.5f, 0, 1);  // RGBA로 직접 지정 (주황색)
spriteRenderer.color = Color.white;        // 원본 색상 (색상 곱셈의 항등원)

goodColor / badColor를 사용하는 이유: 어떤 색이 "좋은 문"인지 코드에 하드코딩하지 않고 Inspector에서 설정합니다. 게임 디자이너가 색상을 바꾸고 싶을 때 코드를 수정하지 않아도 됩니다.

문 타입(type) 색상 이유
Plus (덧셈) goodColor 공룡 수 증가 → 유리한 문
Times (곱셈) goodColor 공룡 수 증가 → 유리한 문
Minus (뺄셈) badColor 공룡 수 감소 → 불리한 문
Division (나눗셈) badColor 공룡 수 감소 → 불리한 문

4. SettingDoors() - 조건문(conditional statement)으로 문 초기화

public void SettingDoors()
{
    if (rightDoorType.Equals(DoorType.Plus))
    {
        rightDoorSpriteRD.color = goodColor;
        rightDoorText.text = "+" + rightDoorNumber;
    }
    else if (rightDoorType.Equals(DoorType.Minus))
    {
        rightDoorSpriteRD.color = badColor;
        rightDoorText.text = "-" + rightDoorNumber;
    }
    else if (rightDoorType.Equals(DoorType.Times))
    {
        rightDoorSpriteRD.color = goodColor;
        rightDoorText.text = "x" + rightDoorNumber;
    }
    else if (rightDoorType.Equals(DoorType.Division))
    {
        rightDoorSpriteRD.color = badColor;
        rightDoorText.text = "÷" + rightDoorNumber;
    }
    // leftDoorType도 동일한 구조로 처리
}
  • Start()에서 SettingDoors()를 호출하므로, 게임 시작 시 각 문의 색상과 텍스트가 자동으로 초기화됩니다.
  • public으로 선언되어 있어 외부 스크립트(예: 맵 생성 스크립트)에서 문 설정을 바꾼 뒤 다시 호출할 수도 있습니다.
  • 텍스트는 문자열(string) 연결로 구성됩니다: "+" + 3"+3", "x" + 2"x2"

TextMeshPro.text: TextMeshPro 컴포넌트에 표시할 문자열(string)을 설정합니다. .text 프로퍼티에 문자열(string)을 대입하면 즉시 화면에 반영됩니다.


5. GetDoorType / GetDoorNumber - 삼항 연산자(operator)로 좌/우 판단

public DoorType GetDoorType(float xPos) { return xPos > 0 ? rightDoorType : leftDoorType; }
public int GetDoorNumber(float xPos) { return xPos > 0 ? rightDoorNumber : leftDoorNumber; }

공룡이 충돌한 문이 오른쪽인지 왼쪽인지를 공룡의 X 좌표(xPos)로 판단합니다.

월드 좌표 X > 0 → 오른쪽 문
월드 좌표 X < 0 → 왼쪽 문

삼항 연산자(operator): 조건 ? 참일 때 값 : 거짓일 때 값 형태입니다. if-else를 한 줄로 표현할 때 사용합니다.

// if-else 버전
if (xPos > 0)
    return rightDoorType;
else
    return leftDoorType;

// 삼항 연산자 버전 (동일한 동작)
return xPos > 0 ? rightDoorType : leftDoorType;

이 두 메서드(method)DinoController의 충돌 처리 코드에서 호출됩니다(튜토리얼 52에서 학습합니다):

// 호출 예시 (튜토리얼 52에서 등장)
DoorType doorType = doors.GetComponent<SelectDoors>().GetDoorType(transform.position.x);
int doorNum = doors.GetComponent<SelectDoors>().GetDoorNumber(transform.position.x);

Unity 씬 설정 요약

항목 권장 설정
문 프리팹 빈 GameObject에 SelectDoors 부착, 좌/우 문 오브젝트를 자식으로 배치
rightDoorSpriteRD / leftDoorSpriteRD 각 문 오브젝트의 SpriteRenderer 컴포넌트 할당
rightDoorText / leftDoorText 각 문 오브젝트의 TextMeshPro 컴포넌트 할당
rightDoorType / leftDoorType Inspector 드롭다운에서 Plus / Minus / Times / Division 선택
rightDoorNumber / leftDoorNumber Inspector에서 연산에 사용할 숫자 입력
goodColor / badColor Inspector Color Picker에서 색상 지정 (예: 파랑/빨강)

요약

개념 사용 위치 핵심
enum DoorType 클래스(class) 외부 선언 문 종류를 의미 있는 이름으로 관리
[SerializeField] private 필드(field) 선언 Inspector 편집 허용 + 외부 접근 차단(캡슐화(encapsulation))
SpriteRenderer.color SettingDoors() 문 타입(type)에 따라 색상 동적 변경
TextMeshPro.text SettingDoors() 연산 기호와 숫자를 문에 표시
삼항 연산자(operator) GetDoorType, GetDoorNumber X 좌표로 오른쪽/왼쪽 문 구분