Carrot
본문 바로가기
Unity/멋쟁이사자처럼 부트캠프

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(23일차) - 캡슐화 및 인터페이스&상속 실습

by 독기품은토끼 2025. 6. 17.
✅ 오늘의 학습 목표
1. 캡슐화와 프로퍼티에 대해 알아보기
2. 인터페이스를 활용해서 아이템 줍기 / 사용 / 버리기 기능 구현해보기
3. 상속과 인터페이스를 활용해서 몬스터 움직임 구현해보기
4. 번외) 깃허브 날짜 변경

 

1. 캡슐화

1. 개념

외부로부터 데이터 접근 및 수정을 보호하는 방법으로 프로퍼티를 통해 데이터를 읽거나 바꾸도록 해주는 개념이다.

  • Property (프로퍼티) : 캡슐화한 데이터
private int number1 = 10;

 

number1은 private라서 외부 클래스에서 직접 접근이 불가하다.

public class StudyProperty : MonoBehaviour
{
    private int number1 = 10;
    public int number2 = 20;
    
    public int Number1
    {
        get
        { 
            return number1;
        }
        set
        {
            number1 = value;
        }
    }
    public int Number2 { get; set; } = 20;
    public int Number3 { get; private set; } = 30;
}
// 타 클래스에서 접근 가능
using UnityEngine;

public class ExternalClass : MonoBehaviour
{
    public StudyProperty studyProperty;

    void Start()
    {
        // 프로퍼티 접근으로 읽고 쓰기 가능
        int num1 = studyProperty.Number1;
        studyProperty.Number1 = 100;

        // public 필드라 그냥 접근 가능
        int num2 = studyProperty.number2;
    }
}

 

이렇게 프로퍼티를 만들어주면 다른 클래스에서는 프로퍼티명으로 접근이 가능하게 된다.

 

2. 상태 변경 로직

캡슐화는 단순히 데이터를 보호하는 것뿐만 아니라 값이 바뀔 때 처리해야 하는 로직도 같이 처리할 수 있다.

using Unity.Android.Gradle.Manifest;
using UnityEngine;

public class StudyProperty : MonoBehaviour
{
    private float hp = 100f;
    public float Hp
    {
        get {  return hp; }

        // private set { hp = value; } // 외부에서 수정 불가 = Hit 메서드 실행 불가
        set
        {
            if (value < 0)
            {
                hp = 0f;
                Death();
            }
            else
            {
                hp = value;
            }
        }
    }

    public void Hit(float damage)
    {
        Hp -= damage;
    }

    public void Death()
    {
        Debug.Log("몬스터 죽음");
    }
}

 

이렇게 Hp를 통해 체력이 깎이고, 0 밑으로 내려가면 Death()를 호출하는 로직을 만들 수 있다는 뜻이다!

 

▶ set을 private로 선언할 경우

// private set { hp = value; }

 

이렇게 되면 외부에서는 Hp 값을 바꿀 수 없게 된다.

그러면 위에서 구현한 Hit() 메서드에서 Hp -= damage; 이 부분이 에러가 나게 된다는 뜻이다. (Hit()도 클래스 외부 입장이라 접근이 불가능)

 

3. SerializeField (시리얼라이즈)

private로 선언한 변수를 유니티 인스펙터 창에 노출시키고 싶을 때 사용하는 키워드다.

[SerializeField] private float moveSpeed = 20f;

 

이렇게 접근제한자 앞에 [SerializeField]를 붙여주면 코드 상에서는 private라 외부 접근은 불가능하지만

유니티 인스펙터 창에서는 값 수정이 가능하다.

 

// 프로퍼티는 유니티 에디터에 나타나지 않음
private float moveSpeed2 = 10f;
public float MoveSpeed2
{
    get { return moveSpeed2; }
    set { moveSpeed2 = value; }
}

 

반면에 프로퍼티는 아무리 public 이어도 기본적으로 인스펙터에 보이지 않는다.

 

4. 그 외 Attribute

SerializeField 외에도 많이 사용되는 애트리뷰트를 정리해 보았다.

Attribute 기능
[Header("제목")] 인스펙터에서 섹션 제목 달기
[SerializeField] private 필드도 인스펙터에 노출
[Range(min, max)] 슬라이더로 숫자 범위 제한
[Space(n)] 인스펙터 간격 주기

 

 

2. IDropItem 실습

인터페이스를 활용해서 아이템을 줍고, 사용하고, 버리는 기능을 만들어 볼 것이다.

  • 총(Gun)과 손전등(FlashLight) 같은 아이템을 구현
  • 캐릭터가 아이템 근처에 가면 자동으로 아이템을 줍기
  • 클릭이나 스페이스바로 상호작용되도록 구현
🥕 예행 작업
1. Scene 생성 (InterfaceItem)
2. Script 생성 (IDropItem, CharacterControl, Gun, Flashlight)
3. Assets 다운로드
 

PM-40 Gun | 3D | Unity Asset Store

Elevate your workflow with PM-40 Gun asset from Helica. Find this & other great 3D options on the Unity Asset Store.

assetstore.unity.com

 

Free-FlashLight | 3D 전자제품 | Unity Asset Store

Elevate your workflow with the Free-FlashLight asset from kuropen. Find this & other 전자제품 options on the Unity Asset Store.

assetstore.unity.com

 

1. 인터페이스

public interface IDropItem
{
    void Grab(Transform grabPos); // 아이템 줍기
    void Use();                   // 아이템 사용하기
    void Drop();                  // 아이템 버리기
}

 

우선 총이든 손전등이든 [아이템]이라는 큰 틀은 동일하게 다룰 수 있도록 인터페이스를 구현해 준다.

(인터페이스는 접근제한자를 붙이지 않음)

 

2. 캐릭터

using UnityEngine;

public class CharacterControl : MonoBehaviour
{
    private IDropItem currentItem;

    [SerializeField] private float moveSpeed = 3f;
    [SerializeField] private Transform grabPos;

    void Update()
    {
        Move();
        Interaction();
    }

    private void Move()
    {
        float h = Input.GetAxis("Horizontal"); // x축 오른쪽/왼쪽
        float v = Input.GetAxis("Vertical"); // z축 앞/뒤

        // normalized를 사용해야 크기가 1인 값을 갖고옴 (= 방향만 갖고옴)
        Vector3 dir = new Vector3(h, 0, v).normalized;

        transform.position += dir * moveSpeed * Time.deltaTime;
    }

    private void Interaction()
    {
        if (currentItem == null)
            return;

        if (Input.GetMouseButtonDown(0))
        {
            currentItem.Use();
        }

        if (Input.GetKeyDown(KeyCode.Space))
        {
            currentItem.Drop();
            currentItem = null;
        }
    }

    private void OnTriggerEnter(Collider other)
    {
        if (other.GetComponent<IDropItem>() != null)
        {
            currentItem = other.GetComponent<IDropItem>();

            currentItem.Grab(grabPos);
        }
    }
}

 

  • 캐릭터의 움직임 구현 (바라보는 방향은 구현하지 않았다)
  • 캐릭터는 그냥 IDropItem만 알고 있으면 된다.
  • 아이템 근처로 가면 OnTriggerEnter로 감지해서 Grab() 메서드를 호출
  • 마우스 왼 클릭을 하면 Use() 호출
  • 스페이스바를 누르면 Drop()을 호출하고 아이템을 손에서 놓도록 구현

 

3. 아이템

아이템마다 Use() 동작을 다르게 구현해 볼 것이다.

  • 손전등 : 라이트를 On/Off 하도록 구현
  • 총 : 총알 발사 구현
using UnityEngine;

public class FlashLight : MonoBehaviour, IDropItem
{
    public GameObject lightObj;

    public void Drop()
    {
        transform.SetParent(null);
        transform.position = Vector3.zero;

        Debug.Log("라이트를 버린다.");
    }

    public void Grab(Transform grabPos)
    {
        transform.SetParent(grabPos);
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.identity;

        Debug.Log("라이트를 주운다.");
    }

    public void Use()
    {
        lightObj.SetActive(!lightObj.activeSelf); // 반대로 작동되게
        Debug.Log("라이트를 켠다.");
    }
}
using UnityEngine;

public class Gun : MonoBehaviour, IDropItem
{
    public GameObject bulletPrefab;
    public Transform shootPos;

    public void Drop()
    {
        transform.SetParent(null);
        transform.position = Vector3.zero;
        Debug.Log("총을 버린다.");
    }

    public void Grab(Transform grabPos)
    {
        transform.SetParent(grabPos);
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.identity;

        // Debug.Log("총을 주운다.");
    }

    public void Use()
    {
        // GameObject bullet = Instantiate(bulletPrefab); // 원점에 생성됨
        GameObject bullet = Instantiate(bulletPrefab, shootPos.position, Quaternion.identity);
        Rigidbody bulletRb = bullet.GetComponent();

        // bulletRb.AddForce(transform.forward * 100f, ForceMode.Impulse);
        bulletRb.AddForce(shootPos.forward * 100f, ForceMode.Impulse);

        // Debug.Log("총을 발사한다.");
    }
}

 

구분 Light (손전등) Gun (총)
차이점 손전등 On/Off 총알 발사
공통점 Grab, Drop 기능은 동일

 

 

 

3.1. Gun 스크립트 추가 설명

public void Use()
{
    GameObject bullet = Instantiate(bulletPrefab, shootPos.position, Quaternion.identity);
    Rigidbody bulletRb = bullet.GetComponent<Rigidbody>();
    bulletRb.AddForce(shootPos.forward * 100f, ForceMode.Impulse);
}

 

  • Instantiate()
    • 프리팹이나 다른 오브젝트를 복제해서 씬에 생성하는 메서드
    • bulletPrefab : 미리 만들어둔 총알 오브젝트(Prefab)
    • shootPos.position : 총알이 생성될 위치 (총구)
    • Quaternion.identity : 회전 없이 기본 방향으로 생성
  • GetComponent<Rigidbody>()
    • 생성된 총알이 움직이려면 유니티의 물리 시스템인 Rigidbody를 사용해주어야 한다.
    • 총알 프리팹에 반드시 Rigidbody 컴포넌트가 있어야 함! (3.5. 내용 참고)
  • AddForce()
    • 총알을 힘으로 날려줄 것이다.
    • shootPos.forward : 총구 방향 앞으로
    • 100f : 힘 세기
    • ForceMode.Impulse : 순간적인 충격 (Cat 씬에서 점프 구현할 때 사용해 주었다. 아래 포스팅 참고!)
 

[멋쟁이사자처럼부트캠프] 유니티 게임 개발 5기(12일차) - Collider 및 Rigidbody 실습

✅ 오늘의 학습 목표1. Collider 및 Rigidbody 학습 - 2D Car 구현2. Collider 및 Rigidbody 활용 - 고양이 무한 맵3. Physics Material 맛보기 1. 2D 자동차🥕 예행 작업1. Scene 생성 (Car 2D)2. Script 생성 (CarMovement) 1. Colli

toxicbunny.tistory.com

 

🚨 참고로 총알, 손전등 모두 isTrigger 체크해주어야 한다.

 

 

3.2. Marterial 깨질 때

총 에셋의 경우 옛날 쉐이더로 적용되었기 때문에 프리팹이 모두 마젠타 색깔로 깨져있다.

이럴 때에는 머테리얼을 업그레이드해주면 된다

 

 

3.3. 게임 씬 조명 끄는 방법

 

게임 씬이 밝다 보니까 손전등이 제대로 비치고 있는지 확인이 어렵다면

Directional Light 오브젝트를 클릭 후 Light를 비활성화해주면 된다.

 

 

3.4. Gun(총) 부모 오브젝트 생성

 

불러온 총 에셋의 경우 총구의 방향이 위로되어있다.

총알이 발사되는 것을 구현하기 위해 총구의 방향을 Z축을 바라보게 만들어줄 것이다.

부모 오브젝트를 생성하고 오브젝트를 기준으로 총구의 방향을 맞추어주면 된다.

 

 

3.5. 총알 이펙트 구현

 

총알은 그냥 Sphere로 대충 만들어주었고

총알이 발사되고 있는 걸 효과적으로 보여주기 위해 Effects - Trail을 구현해 주었다.

 

 

총알은 발사될 때만 나타나면 되니까 프리팹으로 적용해 주었다.

 

3. Monster 실습

이번에는 몬스터 관련 기능을 깔끔하게 묶어보도록 한다.

기본적인 공통 기능(움직임, 피격 처리 등)은 Monster라는 추상 클래스에 구현하고,

개별 몬스터마다 체력이나 속도 같은 설정은 따로 구현해 줄 것이다.

🥕 예행 작업
1. Scene 생성 (Monster Inheritance)
2. Script 생성 (Skeleton, FlyingEye, Mushroom / Goblin은 기존에 만든 코드 사용하였다.)
3. Assets 다운로드
 

Monsters_Creatures_Fantasy | 2D 캐릭터 | Unity Asset Store

Elevate your workflow with the Monsters_Creatures_Fantasy asset from Luiz Melo. Find this & more 캐릭터 on the Unity Asset Store.

assetstore.unity.com

 

2D Pixel Item Asset Pack | 2D 아이콘 | Unity Asset Store

Elevate your workflow with the 2D Pixel Item Asset Pack asset from Startled Pixels. Browse more 2D GUI on the Unity Asset Store.

assetstore.unity.com

 

1. 상속

using UnityEngine;

public abstract class Monster : MonoBehaviour
{
    private SpriteRenderer sRenderer;

    [SerializeField] protected float hp = 3f;
    [SerializeField] protected float moveSpeed = 3f;

    // 화면 끝까지 가면 방향을 바꾸기 위한 값
    private int dir = 1;

    public abstract void Init();

    void Start()
    {
        sRenderer = GetComponent<SpriteRenderer>();
        Init(); // 자식 클래스에서 정의한 값으로 초기화
    }

    void OnMouseDown()
    {
        Hit(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);
        }
    }
}

 

  • abstract void Init()
    • 자식 클래스에서 꼭 구현해야 되는 abstract 함수를 구현해 주었고
    • 각 몬스터별로 체력이나 속도를 여기서 다르게 설정해 준다.
  • OnMouseDown()
    • 유니티 자체 메서드로 마우스 클릭 시 실행됨
  • Move()
    • 카메라의 크기가 -8부터 8까지라 Move() 메서드에서 (-8, 8) 범위 밖으로 오브젝트가 움직이지 않도록 설정
    • 오브젝트의 방향 값(dir)이 바뀔 때 오브젝트가 바라보는 방향도 바꿔주기 위해 SpriteRenderer 설정
  • Hit() : 몬스터 죽음 구현

 

2. 자식 클래스

public class Goblin : Monster
{
    public override void Init()
    {
        hp = 3f;
        moveSpeed = 3f;
    }
}
////////////////////////////////////////////
public class Skeleton : Monster
{
    public override void Init()
    {
        hp = 10f;
        moveSpeed = 1f;
    }
}
////////////////////////////////////////////
public class FlyingEye : Monster
{
    public override void Init()
    {
        hp = 2f;
        moveSpeed = 5f;
    }
}
////////////////////////////////////////////
public class Mushroom : Monster
{
    public override void Init()
    {
        hp = 5f;
        moveSpeed = 2f;
    }
}

 

  • 부모 클래스에서 선언한 Init() 메서드를 자식 클래스에서 오버라이드하여 각자 다르게 구현
  • 부모 클래스에서 hp와 moveSpeed를 protected 제한자로 선언해 주었기 때문에 hp와 speed 수정 가능

오브젝트 설정하는 부분은 생략했슴다 독기토끼 알잘딱 오네가이 (__)

 

 

4. Github 날짜 변경

깃 커밋하는 걸 자꾸 까먹어서 결국 야매 커밋을 해버렸다

나는 깃 데스크탑을 써본 적이 이번이 처음이라 익숙한 터미널로 커밋해 줄 것이니 참고바람!

(근데 이것도 깃 설정한 거에 따르니 이 방법을 따라 하면 큰일 날 수 있음.. 메인브렌치 잘봐주십쇼)

 

1. Git Bash 열기

 

.git 폴더가 있는 곳에서 빈 공간 우클릭 후 'Open Git Bash here'을 클릭한다.

 

2. 명령어 순서대로 입력

git add .

# 커밋날짜, 커밋명 등 초록색 부분은 꼭 수정하셔야합니다.
GIT_COMMITTER_DATE="2025-06-16T18:00:00" git commit --date="2025-06-16T18:00:00" -m "메세지"

git push origin main

 

 

▶ 명령어 설명

명령어 설명
git add . 모든 변경 파일 스테이징
GIT_COMMITTER_DATE=... 커밋자 날짜(작성한 사람 기준)
--date=... 저자 날짜 (작성 시각)
-m 첫 번째 커밋 메시지 제목 (깃데스크탑 -> Summary)
-m 두 번째 커밋 메시지 본문 (깃데스크탑 ->  Description)
git push origin main main 브랜치로 푸시

 

브랜치 이름이 main이 아닌 경우 main을 master나 다른 브랜치명으로 바꿔주어야 한다.

푸시하려는데 뭐 인증창 나와서 막혔다 & 뭔가 불안하다 하면 푸시는 그냥 깃 데스크탑에서 진행해 주어도 무방하다.

= 커밋 시간만 바꿔서 넣어주면 되는 시스템

 

 

어제 날짜로 잘 커밋되었다!

근데 \n 줄 바꿈 명령어는 안 통하네 ㄷㄷ

이 부분은 담에 또 야매 커밋하는 일 생기면 그때 포스팅 업데이트 하겠슴다!

 

 


 

 

흠냐 오늘 뭐 놀지도 않았는데 포스팅이 왜캐 늦어졌지

오늘은 오랜만에 오브젝트 움직임 구현하려니까 어라 어떻게 했더라?? 상황 발생했구..

중간에 자꾸 null 값 참조했다는 에러가 떠서 오랫동안 씨름했는데 알고보니 그냥 에디터 오류였다..

아무리 찾아도 문제가 없길래 설마? 하고 재접했는데 정상 작동..^-^ 이런식으로 억까하면 화 잔뜩!!!나

암튼 이렇게 다시 되돌아보는 시간 가졌으니 한 단계 성장했다 치고 오늘 하루 끝! 🙄👾