멋사교육

멋쟁이사자처럼부트캠프 유니티 게임 개발 5기 21일차

나른한여우 2025. 6. 13. 17:14
  • 유니티 C# 프로그래밍

형변환 / 객체지향프로그래밍

public enum GameState
    {
        Menu,       // 0
        Playing,    // 1
        Paused,     // 2
        GameOver    // 3
    }
   
void Start()
{
	// 1. 기본 명시적 형변환
    GameState currentState = GameState.Playing;
    int stateValue = (int)currentState;
    Debug.Log($"GameState.Playing의 값: {stateValue}"); // 출력: 1
    
     // 2. Convert.ToInt32() 사용
    GameState menuState = GameState.Menu;
    int menuValue = System.Convert.ToInt32(menuState);
    Debug.Log($"Convert.ToInt32로 변환: {menuValue}"); // 출력: 0
}

 

enum을 int로 형변환

enum은 기본적으로 정수 타입을 기반으로 하므로, 명시적 형변환을 통해 int로 변환 가능

 

 

int와 float의 형변환할때의 암시적 형변환과 명시적 형변환

// ========== INT → FLOAT ==========
int intValue = 42;

// 암시적 (자동 변환, 데이터 손실 없음)
float result1 = intValue;
Debug.Log($"int→float 암시적: {intValue} → {result1}");

// 명시적 (의도 명확화)
float result2 = (float)intValue;
float result3 = Convert.ToSingle(intValue);
Debug.Log($"int→float 명시적: {result2}, {result3}");


// ========== FLOAT → INT ==========
float floatValue = 42.7f;

// 암시적 변환 불가능 (컴파일 에러)
// int error = floatValue; // ❌ 에러!

// 명시적 변환만 가능 (소수점 절삭, 데이터 손실 가능)
int result4 = (int)floatValue;           // 42 (절삭)
int result5 = Convert.ToInt32(floatValue); // 43 (반올림)
int result6 = Mathf.FloorToInt(floatValue); // 42 (내림)
int result7 = Mathf.CeilToInt(floatValue);  // 43 (올림)
int result8 = Mathf.RoundToInt(floatValue); // 43 (반올림)

Debug.Log($"float→int: {floatValue} → 캐스트:{result4}, Convert:{result5}");
Debug.Log($"Mathf: Floor:{result6}, Ceil:{result7}, Round:{result8}");

INT → FLOAT

  • 암시적: float f = intValue; ✅ (데이터 손실 없음)
  • 명시적: (float)intValue, Convert.ToSingle()

FLOAT → INT

  • 암시적: 불가능 ❌ (컴파일 에러)
  • 명시적:
    • (int)floatValue - 소수점 절삭
    • Convert.ToInt32() - 반올림
    • Mathf.RoundToInt() - 반올림 (Unity)
    • Mathf.FloorToInt() - 내림 (Unity)
    • Mathf.CeilToInt() - 올림 (Unity)

핵심 차이점

  • int→float: 안전해서 암시적 변환 가능
  • float→int: 데이터 손실 위험으로 명시적 변환만 가능

 

Parse, Convert.ToBoolean, Convert.ToInt32의 특징

Parse (문자열 → 특정 타입)

  • int.Parse("123") → 123
  • float.Parse("45.67") → 45.67f
  • bool.Parse("true") → true
  • 실패 시: 예외 발생 ⚠️

Convert.ToBoolean (다양한 타입 → bool)

  • Convert.ToBoolean(1) → true (0이 아니면 true)
  • Convert.ToBoolean("False") → false
  • Convert.ToBoolean(0) → false

Convert.ToInt32 (다양한 타입 → int)

  • Convert.ToInt32(42.8f) → 43 (반올림)
  • Convert.ToInt32(true) → 1
  • Convert.ToInt32("999") → 999

핵심 차이점

  • Parse: 문자열 전용, 실패 시 예외
  • Convert: 다양한 타입 지원, null 안전
  • ToInt32: 반올림 처리 (캐스팅은 절삭)

 

 

 

상속과 클래스 형변환

부모클래스와 자식클래스의 상속 관계 및 업캐스팅(Upcasting), 다운캐스팅(Downcasting)

 

// 부모 클래스
public class Monster
{
    public int hp = 100;
    public string name;
    
    public virtual void Attack()
    {
        Debug.Log($"{name}이(가) 공격!");
    }
    
    public void TakeDamage(int damage)
    {
        hp -= damage;
        Debug.Log($"{name} HP: {hp}");
    }
}

// 자식 클래스 1
public class Orc : Monster
{
    public int rage = 50;
    
    public Orc()
    {
        name = "오크";
        hp = 150;
    }
    
    public override void Attack()
    {
        Debug.Log($"{name}이(가) 도끼로 강력하게 공격!");
    }
    
    public void Charge()
    {
        Debug.Log($"{name}이(가) 돌진 공격!");
    }
}

// 자식 클래스 2
public class Goblin : Monster
{
    public int agility = 80;
    
    public Goblin()
    {
        name = "고블린";
        hp = 80;
    }
    
    public override void Attack()
    {
        Debug.Log($"{name}이(가) 단검으로 빠르게 공격!");
    }
    
    public void Stealth()
    {
        Debug.Log($"{name}이(가) 은신!");
    }
}

public class InheritanceCastingExample : MonoBehaviour
{
    void Start()
    {
        // ========== 업캐스팅 (Upcasting) - 자식 → 부모 ==========
        
        // 암시적 형변환 (자동, 안전)
        Orc orc = new Orc();
        Monster monster1 = orc;  // 암시적 업캐스팅 ✅
        
        Goblin goblin = new Goblin();
        Monster monster2 = goblin;  // 암시적 업캐스팅 ✅
        
        Debug.Log("=== 업캐스팅 ===");
        monster1.Attack();  // 오크의 Attack() 호출 (다형성)
        monster2.Attack();  // 고블린의 Attack() 호출 (다형성)
        
        // 부모 타입으로는 자식 고유 메서드 접근 불가
        // monster1.Charge();  // ❌ 컴파일 에러!
        // monster2.Stealth(); // ❌ 컴파일 에러!
        
        
        // ========== 다운캐스팅 (Downcasting) - 부모 → 자식 ==========
        
        Debug.Log("\n=== 다운캐스팅 ===");
        
        // 명시적 형변환 (위험, 실행시 에러 가능)
        Monster[] monsters = { new Orc(), new Goblin(), new Orc() };
        
        foreach (Monster monster in monsters)
        {
            // 타입 확인 후 다운캐스팅
            if (monster is Orc)
            {
                Orc downcastOrc = (Orc)monster;  // 명시적 다운캐스팅
                downcastOrc.Charge();
                Debug.Log($"오크 분노: {downcastOrc.rage}");
            }
            else if (monster is Goblin)
            {
                Goblin downcastGoblin = (Goblin)monster;  // 명시적 다운캐스팅
                downcastGoblin.Stealth();
                Debug.Log($"고블린 민첩: {downcastGoblin.agility}");
            }
        }
        
        
        // ========== 안전한 형변환 방법들 ==========
        
        Debug.Log("\n=== 안전한 형변환 ===");
        
        Monster testMonster = new Goblin();
        
        // 1. as 연산자 (실패 시 null 반환)
        Orc asOrc = testMonster as Orc;        // null (고블린이므로)
        Goblin asGoblin = testMonster as Goblin; // 성공
        
        if (asOrc != null)
        {
            asOrc.Charge();
        }
        else
        {
            Debug.Log("오크가 아님");
        }
        
        if (asGoblin != null)
        {
            asGoblin.Stealth();
            Debug.Log("고블린 형변환 성공!");
        }
        
        // 2. is 연산자로 타입 확인
        if (testMonster is Goblin specificGoblin)
        {
            specificGoblin.Stealth();
            Debug.Log($"패턴 매칭으로 변환: {specificGoblin.name}");
        }
    }
}

업캐스팅 (Upcasting) - 자식 → 부모

  • 암시적 형변환 ✅: Monster monster = new Orc();
  • 안전함: 항상 성공 (자식은 부모의 모든 기능 보유)
  • 제한: 부모 타입으로는 자식 고유 메서드 접근 불가

다운캐스팅 (Downcasting) - 부모 → 자식

  • 명시적 형변환 ⚠️: Orc orc = (Orc)monster;
  • 위험함: 잘못된 타입이면 런타임 에러
  • 용도: 자식 고유 기능에 접근할 때

안전한 형변환 방법

  • as 연산자: Orc orc = monster as Orc; (실패 시 null)
  • is 연산자: if (monster is Orc orc) (타입 확인 + 변환)

핵심 포인트

  • 업캐스팅: 암시적, 안전, 다형성 활용
  • 다운캐스팅: 명시적, 위험, 타입 확인 필수
  • 다형성: 부모 타입으로 자식 메서드 호출 가능
  • 실무: as와 is 연산자로 안전하게 처리

Monster클래스에서 Virtual을 사용한 이유

다형성(Polymorphism) 구현

Monster monster = new Orc();
monster.Attack(); // Orc의 Attack() 호출 (부모 타입이지만!)
  • virtual 없으면 항상 부모의 Attack() 호출
  • virtual 있으면 실제 객체(Orc)의 Attack() 호출

 

Virtual vs Abstract 비교

Virtual (가상 메서드)

  • 기본 구현 제공
  • 오버라이드 선택사항 (안 해도 됨)
  • 인스턴스 생성 가능
 
public virtual void Attack() { /* 기본 구현 */ }
// 자식: 오버라이드 해도 되고, 안 해도 됨

Abstract (추상 메서드)

  • 구현 없음
  • 오버라이드 필수 (안 하면 컴파일 에러)
  • 인스턴스 생성 불가
 
public abstract void UseWeapon(); // 구현 없음
// 자식: 반드시 구현해야 함

언제 사용할까?

Virtual 사용 시기

  • 기본 동작이 있지만, 자식마다 다르게 할 수 있을 때
  • 예: Die() - 기본적으로 "쓰러졌다"지만, 몬스터마다 다른 연출 가능

Abstract 사용 시기

  • 자식마다 반드시 다른 구현이 필요할 때
  • 예: UseWeapon() - 무기마다 완전히 다른 사용법

핵심 요약

  • Virtual: "기본은 이거지만, 바꿔도 돼"
  • Abstract: "이건 네가 반드시 만들어야 해"
  • 다형성: 부모 타입으로 자식 메서드 호출 가능

 

 

오버라이딩(Overriding)과 오버로딩(Overloading)의 차이

오버라이딩 (Overriding) - "재정의"

  • 상속 관계에서 부모 메서드를 새롭게 구현
  • 같은 이름, 같은 매개변수
  • 런타임에 어떤 메서드 호출할지 결정 (다형성)
  • 키워드: virtual, override
 
// 부모
public virtual void Attack() { /* 구현 */ }

// 자식  
public override void Attack() { /* 새로운 구현 */ }

오버로딩 (Overloading) - "중복 정의"

  • 같은 클래스에서 같은 이름의 메서드를 여러 개 정의
  • 다른 매개변수 (타입, 개수, 순서)
  • 컴파일 타임에 어떤 메서드 호출할지 결정
  • 키워드: 없음
 
public void Attack() { }                    // 매개변수 없음
public void Attack(int damage) { }          // int 1개
public void Attack(string weapon) { }       // string 1개
public void Attack(int damage, string weapon) { } // 2개

비교표

구분오버라이딩오버로딩

관계 부모-자식 클래스 같은 클래스
목적 기능 재정의 편의성 제공
매개변수 동일해야 함 달라야 함
결정 시점 런타임 컴파일 타임
다형성 지원 ✅ 지원 안함 ❌

실무에서 언제 사용?

오버라이딩 사용 시

  • 몬스터마다 다른 공격 방식
  • UI 요소마다 다른 클릭 이벤트
  • 캐릭터마다 다른 이동 방식

오버로딩 사용 시

  • 함수 호출을 편리하게 (Attack(), Attack(damage))
  • 다양한 타입 지원 (Add(int), Add(float))
  • 선택적 매개변수 효과