본문 바로가기

유니티(캡슐화,인터페이스/상속 실습 - 몬스터, 아이템) _ 멋쟁이사자처럼 유니티 부트캠프 후기 23회차

@salmu2025. 6. 17. 17:37
반응형

[23일차 수업내용] 250617

1.캡슐화

2.인터페이스 - 아이템 줍기 / 사용 / 버리기 

3.상속, 인터페이스 - 몬스터 상속과 아이템 줍기 실습

 

 

1. 캡슐화

데이터(변수)와 기능(메서드)을 하나로 묶고, 외부에서 직접 접근하지 못하게 숨기는 것

 

1.1 Getter Setter -> 과거에 쓰던 방식

  •  Getter: 값을 읽는 메서드
  • Setter: 값을 설정하는 메서드
private int hp;   // 숨긴 변수 (필드)

public int Hp     // 외부에 노출하는 '프로퍼티'
{
    get { return hp; }         // 읽기
    set { hp = value; }        // 쓰기 (value는 입력값)
}

 

class Player
{
    private int hp;

    public int GetHp() { return hp; }
    public void SetHp(int value) { hp = value; }
}

 

1.2. Property 프로퍼티 = 캡슐화한 데이터

  • get : 값을 읽을 때 사용
  • set : 값을 설정할 때 사용
  • Get / Set -> 누가 어디서 썼는지 알 수 있다는 장점
  • 보안성 높이고 선택지를 늘린다.

[예시]

private int hp;  // 필드 (데이터 저장)

public int Hp    // 프로퍼티 (접근 통로)
{
    get { return hp; }              // 읽기
    set { if (value >= 0) hp = value; }  // 쓰기 (0 이상일 때만 허용)
}

 

- 수업 코드 예시 

public class StudyProperty : MonoBehaviour
{
    private int number1 = 10; // 내부에서 관리하는 변수 (외부 접근 불가)
    
    // 프로퍼티 → 외부에서는 읽기 가능, 쓰기는 내부(private)에서만 가능
    public int Number1
    {
        get { return number1; } // 읽기
        private set { number1 = value; } // 쓰기(private)
    }
    
    // 읽기 전용 프로퍼티 (초기값 20, 변경 불가)
    public int Number2 { get; } = 20;
    
     // 외부에서 읽기 가능, 쓰기는 내부(private)에서만 가능 (초기값 30)
    public int Number3 { get; private set; } = 30;
   
    
    private float hp = 100f; // 내부 값
    
     // 프로퍼티 → 외부에서 읽기 가능, set은 존재하나 현재 내용 없음
    public float Hp
    {
        get { return hp; }
        set
        { 
              // 일부러 비워둠 → 외부에서 값을 바꿔도 실제 hp 값 변화 없음
        }
    }
    
    private SoundManager sound;  // 사운드 매니저 객체 저장용 (초기엔 null)
    
     // Sound 프로퍼티 → 필요할 때 찾아서 캐싱(Lazy Loading) = 최적화
    public SoundManager Sound
    {
        get
        {
         	  // 아직 할당되지 않았다면 → 씬 내에서 첫 번째 SoundManager 찾기
            if (sound == null)
                sound = FindFirstObjectByType<SoundManager>();
            
            return sound; // 반환 (이미 찾았으면 저장된 값 반환)
        }
    }
}

 

 

 

1.2.1 private set

public int Hp { get; private set; }

외부에서는 읽을 수만 있고, 바꿀 수 없음. (읽기 전용)

내부(클래스 안)에서는 값 변경 가능

 

 

1.3 접근제한자 정리

public 어디서든 접근 가능
private 클래스 내부에서만 접근 가능
protected 클래스 내부 + 자식 클래스 접근 가능
[SerializeField] private 변수 Unity 인스펙터에 노출
[field: SerializeField] 프로퍼티 인스펙터에 노출할 때 사용
property get/set 형태의 깔끔한 접근 방식 제공

 

public int hp;        // 어디서나 접근 가능
private int mana;     // 클래스 안에서만 접근 가능
protected int gold;   // 상속받은 클래스에서 접근 가능

 

[SerializeField]

    • Unity 전용
    • private인데 인스펙터 창에 보이게 하고 싶을 때 사용 → 주로 프로퍼티랑 같이 씀
[field: SerializeField]
public int Hp { get; private set; }

 

 

2. 인터페이스 - 아이템 줍기 / 사용 / 버리기

인터페이스 (interface)

  • “이걸 구현하는 클래스는 반드시 이 기능을 구현해야 한다” 라고 강제함
  • 클래스는 여러 개의 인터페이스를 “상속”(구현)할 수 있음 (다중 상속 가능)
  • 보통 ‘I’로 시작 (ex. IAttack, IMovable)

[선언]

interface IAttack
{
    void Attack();   // 선언만 있음, 구현 ❌
}

 

[구현]

interface IAttack
{
    void Attack();
}

class Orc : IAttack
{
    public void Attack()   // 반드시 구현해야 함!
    {
        Console.WriteLine("오크가 공격한다!");
    }
}

 

[실습내용]

Character 생성

+ Gun Flashlight -> IDropItem 안에 Grab(), Use(), Drop() 공통 함수를 가지게 됨

public interface IDropItem
{
    void Grab();   // 줍기
    void Use();    // 사용
    void Drop();   // 버리기
}

 

 

[실습 코드] 

public class CharacterControl : MonoBehaviour
{
    [SerializeField] private float moveSpeed = 3f; // 이동 속도 (유니티 에디터에서 조절 가능)
    private IDropItem currentItem;    // 현재 들고 있는 아이템 (인터페이스 타입)

    void Update()
    {
        Move(); 
        Interaction(); // 매 프레임마다 입력 처리 호출
    }

    private void Move()
    {
      // 키보드 입력 받기 (좌우, 앞뒤)
        float h = Input.GetAxis("Horizontal"); // 좌우 방향키 (A,D 또는 ←,→)
        float v = Input.GetAxis("Vertical");   // 앞뒤 방향키 (W,S 또는 ↑,↓)
        
        // 방향 벡터 생성 (Y축은 0으로 고정)
        Vector3 dir = new Vector3(h, 0, v).normalized;
        
         // 이동량 계산 후 위치에 더하기 (프레임 독립적 움직임)
        transform.position += dir * moveSpeed * Time.deltaTime;
    }

    private void Interaction()
    {
          // 마우스 왼쪽 클릭 시
        if (Input.GetMouseButtonDown(0))
        {
            if (currentItem != null) // 아이템이 있을 때만
                currentItem.Use(); // 아이템 사용
        }

	 // 스페이스바 눌렀을 때
        if (Input.GetKeyDown(KeyCode.Space))
        {
            if (currentItem != null) // 아이템이 있을 때만 버리기 가능
            {
                currentItem.Drop();
                currentItem = null;  // 버린 뒤에는 아이템 없음 처리
            }
        }
    }

 // 트리거 충돌 시 호출되는 함수
    private void OnTriggerEnter(Collider other)
    {
        var item = other.GetComponent<IDropItem>(); // 충돌한 오브젝트가 IDropItem인지 확인
        if (item != null)   // 맞으면
        {
            currentItem = item;
            currentItem.Grab();
        }
    }
}

 

*리마인드

  • var =C#에서 지역 변수(local variable) 선언 시 타입을 명시하지 않고 컴파일러가 자동으로 변수 타입을 추론하
  • OnTriggerEnter() 작동 조건 = Collider + 충돌하는 두 오브젝트 중 하나는 반드시 Rigidbody 포함해야 이벤트 발생함
  • 충돌 대상(GameObject)에 → Collider + Rigidbody + IDropItem 스크립트 필수

 

 

 

 

 

3. 몬스터 상속
몬스터 클래스 상속과 abstract / override를 활용한 필수 구현

public abstract class Monster : MonoBehaviour
{
    private SpriteRenderer sRenderer; // 몬스터의 스프라이트 렌더러 (이미지 뒤집기용)
    
    [SerializeField] protected float hp = 3f;      // 체력 (자식 클래스에서 접근 가능 + 인스펙터에 표시됨)
    [SerializeField] protected float moveSpeed = 3f; // 이동 속도 (자식 클래스에서 접근 가능 + 인스펙터에 표시됨)
 
    private int dir = 1;  //이동 방향 (1: 오른쪽, -1: 왼쪽)

// 추상 메서드: 반드시 하위 클래스가 구현해야 함 (오버라이딩)
    public abstract void Init();

    void Start()
    {
        sRenderer = GetComponent<SpriteRenderer>();  //스프라이트 렌더러 컴포넌트 가져오기
        Init();  // 하위 클래스에서 정의
    }

    void OnMouseDown()
    {
        Hit(1); // 마우스로 클릭하면 1 데미지 주기
    }

    void Update()
    {
        Move(); // 매 프레임마다 이동 실행
    }

    void Move()
    {
        transform.position += Vector3.right * dir * moveSpeed * Time.deltaTime;
 
 // 화면 오른쪽 끝에 도달하면 방향 전환 + 이미지 뒤집기
 
        if (transform.position.x > 8f)
        {
            dir = -1;
            sRenderer.flipX = true; // 왼쪽 방향으로 이미지 반전
        }
        
        // 화면 왼쪽 끝에 도달하면 방향 전환 + 이미지 원상복귀
        else if (transform.position.x < -8f)
        {
            dir = 1;
            sRenderer.flipX = false;
        }
    }


    // 몬스터 피격 처리
    void Hit(float damage)
    {
        hp -= damage;

        if (hp <= 0)
        {
            Debug.Log("몬스터 죽음");
            Destroy(gameObject); // 몬스터 오브젝트 삭제
        }
    }
}

 

* 리마인드

  • 여기서 float damage는 매개변수 = 값은 그 함수 내부에서만 유효
  • 유연성 ↑ → 같은 함수인데도 상황에 따라 다른 값 사용 가능 / 함수 끝나면 사라짐

 

[각 몬스터 별로 구현 ex-goblin]

public class Goblin : Monster
{
    public override void Init()
    {
        hp = 3f;
        moveSpeed = 3f;
    }
}

 

 

 

4. 기타 주의사항

      •  GetComponent<SpriteRenderer>(); -→ 컴포넌트가 반드시 붙어 있어야 함 (없으면 null, 실행 시 에러)
      • OnMouseDown() → Collider 필수 → Collider가 없으면 클릭 이벤트 안 발생함
      • Orthographic 카메라란? 원근(Perspective) 효과가 없는 카메라 = 2D게임

 

게임을 보다 체계적이고 효율적으로 구현하는 방법들을 배워나가는 것 같아서 재미있었다. 갈수록 속도가 더 빨라지는 것 같아 열심히 복습해야겠다. 

반응형
salmu
@salmu :: SMU 각종 기록

목차