본문 바로가기

유니티(상속/인터페이스 연습/layer) _ 멋쟁이사자처럼 유니티 부트캠프 후기 24회차

@salmu2025. 6. 18. 15:57
반응형

[24회차수업내용] 250618

1. 몬스터 처치 + 아이템 드롭 실습

2. Layer 이해하기

 

1.1 몬스터 클릭 시 대미지 적용

using System.Collections;
using UnityEngine;

// 몬스터 기본 동작을 정의하는 추상 클래스
public abstract class Monster : MonoBehaviour
{
    private SpriteRenderer sRenderer; // 몬스터의 스프라이트 렌더러
    private Animator animator;        // 몬스터의 애니메이터

    [SerializeField] protected float hp = 3f;        // 체력 (외부에서 수정 가능)
    [SerializeField] protected float moveSpeed = 3f; // 이동 속도

    private int dir = 1;          // 이동 방향 (1: 오른쪽, -1: 왼쪽)
    private bool isMove = true;   // 이동 중인지 여부
    private bool isHit = false;   // 피격 중인지 여부

    public abstract void Init();  // 자식 클래스에서 반드시 구현해야 하는 초기화 메서드

    void Start()
    {
        // 컴포넌트 가져오기
        sRenderer = GetComponent<SpriteRenderer>();
        animator = GetComponent<Animator>();

        Init(); // 자식 클래스가 구현한 초기화 실행
    }

    // 마우스 클릭 시 실행됨 (테스트용 피격)
    void OnMouseDown()
    {
        StartCoroutine(Hit(1)); // 코루틴 실행해서 1 데미지 입히기
    }

    void Update()
    {
        Move(); // 매 프레임 이동 처리
    }

    // 좌우 반복 이동하는 로직
    void Move()
    {
        if (!isMove)
            return; // isMove가 false면 아래 코드 실행 안 하고 함수 종료

        // 이동 처리
        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;
        }
    }

    // 피격 처리 (코루틴 사용, 일정 시간 멈춤)
    IEnumerator Hit(float damage)
    {
        if (isHit)
            yield break; // 이미 피격 중이면 중복 실행 방지

        isHit = true;
        isMove = false; // 이동 중지

        hp -= damage; // 체력 감소

        // 체력이 0 이하가 되면 몬스터 오브젝트 제거
        if (hp <= 0)
        {
             animator.SetTrigger("Death"); // 죽음 애니메이션 실행
            
            yield return new WaitForSeconds(3f); // 3초 후 삭제
            Destroy(gameObject);
            yield break;
        }

		 animator.SetTrigger("Hit"); // 피격 애니메이션 실행

        yield return new WaitForSeconds(0.65f); // 피격 애니메이션 대기 시간
        isHit = false;
        isMove = true; // 이동 재개
    }
}

 

* 리마인드

  • IEnumerator는 C#에서 코루틴(Coroutine)을 만들 때 사용하는 반환형
  • 코루틴 = 시간이 걸리는 작업을 여러 프레임에 나눠서 실행하는 함수
  • 여기서는 중복클릭/실행을 막기 위해 사용
IEnumerator 함수이름(float 값)
{
    // 실행할 코드
    yield return new WaitForSeconds(값);
}
StartCoroutine(함수이름(3f));  // 3초 대기하는 코루틴 호출

 

 

*애니메이터 연결

* 클릭되려면, 즉 OnMouseDown() 메서드가 정상적으로 호출되려면 해당 게임 오브젝트에 Collider가 반드시 있어야 함.

 

 

1.2 몬스터 생성 범위 조정 in Unity

* camera size 

  • Orthographic 카메라(2D 게임 등 평면적 게임에서 사용) 일 때만 활성화되는 값.
  • 화면에 보이는 절반 높이를 의미.
  • 이 실습에서는 (-8 ~ 8)을 범위로 잡았으니 그에 맞게 조정

 

 

1.3 스포너매니저 _ 몬스터 스폰 + 아이템 드롭

using System.Collections;
using UnityEngine;

public class SpawnManager : MonoBehaviour

{ // 개수가 정해져있으니까 리스트보다 더 빠른 배열을 사용
    [SerializeField] private GameObject[] monsters; // 생성할 몬스터 프리팹 배열 (에디터에서 할당)
    [SerializeField] private GameObject[] items;     // 생성할 아이템 프리팹 배열 (에디터에서 할당)

    IEnumerator Start()
    {
        // 무한 반복 → 게임 시작 시 몬스터가 계속 주기적으로 생성됨
        while (true)
        {
            yield return new WaitForSeconds(3f); // 3초 대기 후 다음 실행
            
            var randomIndex = Random.Range(0, monsters.Length); // 몬스터 종류 랜덤 선택
            var randomX = Random.Range(-8, 9); // 생성 위치 X 좌표 랜덤 (-8 ~ 8)
            var randomY = Random.Range(-3, 5); // 생성 위치 Y 좌표 랜덤 (-3 ~ 4)

            var createPos = new Vector3(randomX, randomY, 0); // 생성 위치 설정
            
            Instantiate(monsters[randomIndex], createPos, Quaternion.identity); // 몬스터 생성 (회전 없음)
        }
    }
    
    // 특정 위치에 랜덤 아이템을 생성하는 메서드
    public void DropCoin(Vector3 dropPos)
    {
        // 아이템 배열에서 랜덤 인덱스 선택
        var randomIndex = Random.Range(0, items.Length);

        // 선택된 아이템 프리팹을 지정한 위치에 회전 없이 생성
        Instantiate(items[randomIndex], dropPos, Quaternion.identity);
    }
}

 

*리마인드 / while (true) + yield return new WaitForSeconds() 구조:

  •  while + yield 조합은 반복적으로 일정 시간마다 무언가를 실행할 때 자주 사용
  •  while (true) = 무한반복
IEnumerator SomeCoroutine()
{
    while (true)
    {
        // 반복적으로 실행할 코드

        yield return new WaitForSeconds(시간); 
        // 일정 시간 대기 후 다시 반복
    }
}

 

<주의> 정수형 Random.Range(int min, int max) 

    • min 이상, max 미만 값 반환 :  마지막 값 포함시키려면 +1 해준다
  • -8 이상, 9 미만 → 결과적으로 -8 ~ 8 값 나옴.

<-> 실수형 Random.Range(float min, float max) : 그대로 쓰면 마지막 값 포함됨

  • min 이상, max 이하 값 반환

 

* Instantiate(monsters[randomIndex], createPos, Quaternion.identity);

Instantiate(원본, 위치, 회전);
원본(Prefab) 생성할 오브젝트 (ex. 프리팹, 씬에 존재하는 오브젝트)
위치(Vector3) 생성할 위치
회전(Quaternion) 생성할 회전 값 (보통 Quaternion.identity 사용)

 

  •  Quaternion.identity = 회전 없음 
  • 회전 있다면  = Quaternion.Euler(x축 회전, y축 회전, z 축 회전)

 

1.4 몬스터 코드 (1.1의 코드 + spawner랑 연결)

using System.Collections;
using UnityEngine;

public abstract class Monster : MonoBehaviour
{
    public SpawnManager spawner;  // 스폰 매니저 참조 (몬스터가 코인 드롭 시 사용)
    
    private SpriteRenderer sRenderer; // 스프라이트 렌더러 컴포넌트
    private Animator animator;         // 애니메이터 컴포넌트
    
    [SerializeField] protected float hp = 3f;        // 체력 (외부에서 조정 가능)
    [SerializeField] protected float moveSpeed = 3f; // 이동 속도 (외부에서 조정 가능)

    private int dir = 1;        // 이동 방향 (1: 오른쪽, -1: 왼쪽)
    private bool isMove = true; // 이동 가능 상태 플래그
    private bool isHit = false; // 공격 중복 방지 플래그
    
    public abstract void Init(); // 자식 클래스에서 초기화 구현

    void Start()
    {
        spawner = FindFirstObjectByType<SpawnManager>(); // 씬에서 스폰 매니저 찾기
        
        sRenderer = GetComponent<SpriteRenderer>(); // 스프라이트 렌더러 가져오기
        animator = GetComponent<Animator>();         // 애니메이터 가져오기
        
        Init(); // 자식 클래스 초기화 호출
    }

    void OnMouseDown()
    {
        StartCoroutine(Hit(1)); // 클릭 시 체력 1 감소 코루틴 실행
    }

    void Update()
    {
        Move(); // 매 프레임 이동 처리
    }


    void Move()
    {
        if (!isMove)
            return;

        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; // 스프라이트 원래 상태
        }
    }

   
    IEnumerator Hit(float damage)
    {
        if (isHit)          // 이미 공격 중이면 중복 실행 방지
            yield break;
            
        isHit = true;
        isMove = false;     // 공격받는 동안 이동 정지
        
        hp -= damage;       // 체력 감소
        
        if (hp <= 0)        // 체력이 0 이하일 경우 죽음 처리
        {
            animator.SetTrigger("Death");           // 죽음 애니메이션 실행
            
            
            // 몬스터 사망 시 → 현재 위치에 아이템(코인) 생성 요청
			// (SpawnManager의 DropCoin 메서드 호출, 아이템 랜덤 생성)
            //  transform.position : 현재 몬스터의 위치 값 (죽은 위치)
            spawner.DropCoin(transform.position);  
            
            yield return new WaitForSeconds(3f);    // 죽음 애니메이션 대기
            Destroy(gameObject);                     // 몬스터 오브젝트 제거
            
            yield break;                            // 코루틴 종료
        }
        
        animator.SetTrigger("Hit");    // 히트 애니메이션 실행

        yield return new WaitForSeconds(0.65f); // 히트 애니메이션 대기
        isHit = false;             // 공격 상태 해제
        isMove = true;             // 이동 재개
    }
}

 

*리마인드

 

* FindFirstObjectByType<SpawnManager>();  <-> GetComponent<SpriteRenderer>();

: SpawnManager는 별도의 오브젝트에 붙어 있어서, 몬스터 스크립트 내에서 직접 접근할 수 없기에,

   FindFirstObjectByType으로 찾는다. (씬 내에서 해당 타입(SpawnManager)을 가진 첫 번째 게임오브젝트를 찾아 반환)

: 반면 SpriteRenderer 는 현재 게임오브젝트에 붙어있어서 GetComponent 을 사용)

 

 

 

1.5 아이템 위로 뿌리는 기능 (spawner에 DropCoin 수정)

public void DropCoin(Vector3 dropPos)
{
    var randomIndex = Random.Range(0, items.Length); // 랜덤 인덱스 설정
    GameObject item = Instantiate(items[randomIndex], dropPos, Quaternion.identity);

    // 생성된 아이템의 Rigidbody2D 컴포넌트 가져오기
    Rigidbody2D itemRb = item.GetComponent<Rigidbody2D>();

    // 아이템에 X축 방향으로 랜덤한 힘 가하기
    itemRb.AddForceX(Random.Range(-2f, 2f), ForceMode2D.Impulse);

    // 아이템에 Y축 방향으로 위로 솟아오르는 힘 가하기
    itemRb.AddForceY(3f, ForceMode2D.Impulse);

    // 아이템에 랜덤한 회전력(토크) 가하기
    float ranPower = Random.Range(-1.5f, 1.5f);
    itemRb.AddTorque(ranPower, ForceMode2D.Impulse);
}

 

ForceMode2D.Impulse: 힘이 즉각적인 충격(impulse)으로 가해짐 /  폭발이나 점프와 같은 효과에 적합

*Collider / Rigidbody 필요 (Freeze Rotation 활성화)

 

 

 

2.1 Unity Layer 이해하기

  • 게임 오브젝트를 그룹화하는 분류 체계
  • 총 32개 레이어를 만들 수 있음 (0~31번)
  • 주로 카메라 렌더링 제어충돌 검사 필터링에 사용함

-기본레이어

0 Default 기본 레이어. 아무것도 지정하지 않은 오브젝트가 속함. 대부분의 오브젝트 기본값.
1 TransparentFX 투명 효과가 필요한 오브젝트에 사용. 레이캐스트 무시 등 특수 처리가 가능.
2 Ignore Raycast 레이캐스트 검사에서 무시되는 레이어. 예: 플레이어가 쏜 총알이 자신을 맞추지 않게 할 때 사용.
3 (사용자 지정 없음) 비어있음, 필요하면 이름 지정 가능.
4 Water 물 오브젝트용 레이어. 프로젝트에 따라 특별히 처리할 수 있음.
5 UI UI 요소에 할당. UI 렌더링이나 입력 처리에 도움.

 

2.2 충돌 필터링_ 레이어 충돌 매트릭스 (Layer Collision Matrix)

: Add layer로 Monster, Coin Layer 생성 후

  • EditProject SettingsPhysics 2DLayer Collision Matrix에서 설정

"Monster" 레이어에 속한 게임 오브젝트(예: 몬스터 캐릭터)와 "Coin" 레이어에 속한 게임 오브젝트(예: 코인 아이템)가 물리적으로 충돌하지 않게 한다. 

 

  • 2D 물리 시스템에서만 적용됨 (3D 물리는 PhysicsLayer Collision Matrix 따로 있음).
  • Collider 컴포넌트가 있어야 충돌 체크가 진행됨.
  • Rigidbody2D가 함께 있어야 물리 충돌과 상호작용이 잘 됨.

 

2.3 Order in Layer (레이어 내 순서)

숫자가 높을수록 더 나중에 그려지므로, 화면상에서 더 앞에 보임.

 

 

이미 배운 내용들을 다시 끌어와서 이것저것 구현해 보았다. 직접 프로젝트에 적용해 보니 공부한 내용이 더 흥미롭고 재미있게 다가왔다. 하지만 여전히 익숙하지 않아서 복습의 필요성을 느꼈다. 앞으로도 꾸준한 복습과 실습을 통해 이해를 깊게 하고 실력을 확실히 다져야겠다는 다짐을 하게 되었다.

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

목차