Unity 내배캠 TIL

Unity 게임 개발 입문 (4주차 2일)

오늘도즐겨 2024. 10. 10. 09:00

🔥 10/8 화요일 목표 🔥

 

🔎  16강 까지 듣기📝

🔎  이전 학습 복습하기

 

09:00 어제 학습 복습 ❗️ (6강, 8강 이해하기)

14:00 9강부터 강의시작 💛

 

📝 Study  📝

 

⭐️ 열거형(Enum) : 쓰는 이유? int 기반이지만 상수형 값에 의미를 부여하기 위해 사용함.

 

⭐️ 스크립터블 오브젝트(Sciptable Object) : 유연한 데이터 컨테이너

     게임에 재사용가능한 데이터 또는 설정을 저장하는 사용

     코드와 데이터를 분리해 코드를 더 깔끔하게 관리하기 쉽게만듬

     하나의 스크립터블 오브젝트를 여러 게임 오브젝트에서 참조하거나 재사용 할 수 있음

     Unity에디터와 통합되어 인스펙터 창에서 직접 수정하고 관리 할 수 있음.

스크립터블 오브젝트(Sciptable Object) 예시

 

⭐️ protected virtual void Awake()  상속을 받을때는 protected 이상을 써야만 메서드를 상속 받을수 있다.

       protected override void Awake()

 

👀 실제구현코드

public partial class TopDownController : MonoBehaviour
{
	protected CharacterStatsHandler stats { get; private set; }
	
	protected virtual void Awake()
	{
    	stats = GetComponent<CharacterStatsHandler>();
	}
}

 

public class PlayerInputController : TopDownController
{
	protected override void Awake()
	{
   		base.Awake(); //TopDowmController를 상속받았기에 base = 부모가 됨        
	}
}

     

 

⭐️ 비트 연산자, 레이어 마스크

레이어를 사용하는 이유화살이 캐릭터가 쏜건 몬스터만, 몬스터가 쏜건 캐릭터만 맞을 수 있게 하기 위해

레이어가 32개인 이유 : int는 32bit (4Byte)

 

비트연산자 사용 예시1<<6  // 1을 왼쪽으로 6번 보내라  = 1000000 라는 결과값이 나옴.

 

레이어는 비트마스크를 생성하는데 사용됨

 

비트마스크란?

이진수(비트)를 사용하여 여러 상태를 하나의 변수에 저장하고 조작하는 방법이다

주로 효율적으로 여러 상태를 관리하거나 특정 상태를 빠르게 확인, 설정, 변경하는 데 사용된다

 

비트마스크의 기본 개념

비트 단위로 상태 표현: 각 비트가 하나의 상태(혹은 플래그)를 나타냅니다.

                                          예를 들어 8비트 변수에서 각 비트는 0 또는 1이 될 수 있으므로, 8개의 상태를 동시에 표현할 수 있습니다.

논리 연산 사용: 비트마스크는 AND, OR, XOR, NOT과 같은 비트 연산을 통해 상태를 설정하거나 확인합니다.

 

👀 레이어와 비트의 상관관계

더보기

Unity와 같은 게임 엔진에서 레이어(Layer)와 비트(Bit)는 비트마스크를 통해 밀접하게 연결됩니다.

레이어와 비트의 관계를 이해하면 특정 레이어에 대한 처리, 충돌 감지, 렌더링 등을 효율적으로 관리할 수 있습니다.

 

레이어는 비트마스크의 비트 위치로 표현:

Unity에서 각 레이어는 고유한 비트 위치를 가집니다.

예를 들어, Layer 0은 비트마스크의 첫 번째 비트에 대응하고, Layer 1은 두 번째 비트에 대응하는 식입니다.

이렇게 각 레이어가 특정 비트에 대응하기 때문에, 여러 레이어에 대한 처리를 한 번에 수행할 수 있습니다.

 

비트마스크로 다수의 레이어 선택:

여러 레이어에 대해 충돌이나 렌더링 같은 작업을 설정하려면 비트마스크를 사용해 특정 레이어들을 포함하는 마스크를 설정할 수 있습니다.

예를 들어, Layer 0과 Layer 2만 선택하고 싶다면 0b00000101처럼 특정 비트만 1로 설정하여 마스크를 만듭니다.

이 비트마스크는 다양한 작업에 적용되며, 비트 연산을 통해 설정된 레이어만 처리하게 합니다.

 

충돌 감지에서 비트마스크 사용:

Unity에서는 LayerMask라는 기능으로 충돌할 레이어를 지정하는데, 내부적으로는 비트마스크로 표현됩니다.

예를 들어 Physics.Raycast에서 특정 레이어에만 반응하게 하려면, LayerMask를 사용하여 필요한 레이어만 비트로 활성화할 수 있습니다.

👀 특정 레이어에 대한 충돌 감지 예시

int layerMask = (1 << 3) | (1 << 5); // Layer 3과 Layer 5만 활성화
if (Physics.Raycast(ray, out hit, maxDistance, layerMask)) {
    // Layer 3이나 Layer 5에 속한 오브젝트와 충돌 시 처리
}

 

 

⭐️쿼터니언과 벡터의 곱셈

    쿼터니언은 회전과 관련됨

    쿼터니언을 활용해 벡터도 회전이 가능함

    ex/ 캐릭터의 오른쪽에 있다고 할때, 캐릭터가 회전하면 그에 맞게 벡터도 회전해야함

    Quaternion과 Vector3의 곱셈을 지원 하기때문에 Q*V 순서만 맞춰서 곱하면 된다

 

⭐️ as키워드에 대한 이해

 

C#에서 as다운캐스팅(downcasting)을 수행하는 방법 중 하나

 

👀 실제구현코드

RangedAttackSo rangedAttackSo = attackSo as RangedAttackSo;

 

attackSo는 RangedAttackSo 클래스의 부모 클래스 타입(예를 들어, AttackSo 클래스)으로 선언된 객체로 추정

as 키워드를 사용하여 attackSo 객체를 RangedAttackSo 타입으로 캐스팅하고, 결과를 rangedAttackSo 변수에 저장한다

 

as 키워드는 안전한 형 변환을 위해 사용된다

형 변환이 실패할 경우 InvalidCastException 예외를 발생시키는 대신, null을 반환하는 것이 특징

attackSo가 실제로 RangedAttackSo 타입이라면, rangedAttackSo 변수는 이 객체를 가리키게 된다

attackSo가 RangedAttackSo 타입이 아닐 경우, 형 변환에 실패하여 rangedAttackSo는 null이 된다

 

⭐️ 오브젝트 풀링

게임 성능 개선을 위해 사용

- 객체를 미리 생성해두고 필요할 때 가져다 사용, 사용 완료 시 풀에 반납하는 방식

- 생성(Instrantiate)과 소멸(Destroy)이라는 비용이 큰 작업을 최소화함

- 빈번하게 생성되고, 파괴하는 객체에 주로 사용하고, 풀에 저장하고 재사용함으로써

   메모리 할당과 가비지 컬렉션에 따른 성능 저하를 방지

- 적절히 사용하면 큰 성능 개선을 가져올수 있지만,

   불필요한 메모리 사용을 증가 시킬 수 있으므로 사용시에는 신중해야함.

 

👀실제 강의 사용코드

 

더보기
public class ObjectPool : MonoBehaviour
{
    // 오브젝트 풀 데이터를 정의할 데이터 모음 정의
    [System.Serializable]
    public class Pool
    {
        public string tag;
        public GameObject prefab;
        public int size;
    }

    public List<Pool> Pools;
    public Dictionary<string, Queue<GameObject>> PoolDictionary;

    private void Awake()
    {
        // 인스펙터창의 Pools를 바탕으로 오브젝트풀을 만들 것. 
        // 오브젝트풀은 오브젝트마다 따로이며, pool개수를 넘어가면 강제로 끄고 새로운 오브젝트에게 할당.
        PoolDictionary = new Dictionary<string, Queue<GameObject>>(); //초기화
        
        foreach (var pool in Pools)
        {
            // 큐는 FIFO(First-in First-out) 구조, 가장 오래 줄 선(enqueue) 가장 먼저 빠져 나올(dequeue)
            Queue<GameObject> objectPool = new Queue<GameObject>();
            for (int i = 0; i < pool.size; i++)
            {
                // Awake하는 순간 오브젝트풀에 들어갈 Instantitate 일어나기 때문에 터무니없는 사이즈 조심
                GameObject obj = Instantiate(pool.prefab);
                obj.SetActive(false);
                
                objectPool.Enqueue(obj); // 줄의 가장 마지막에 세움
            }            
            PoolDictionary.Add(pool.tag, objectPool); // 접근이 편한 Dictionary에 등록
        }
    }

    public GameObject SpawnFromPool(string tag)
    {        
        if (!PoolDictionary.ContainsKey(tag)) // 애초에 Pool이 존재하지 않는 경우
            return null;

        // 제일 오래된 객체를 재활용
        GameObject obj = PoolDictionary[tag].Dequeue();
        PoolDictionary[tag].Enqueue(obj);
        obj.SetActive(true);
        return obj;
    }
}

 

 

⭐️ StringToHash : 문자열을 고유한 정수 값인 해시 값으로 변환하여 연산 비용을 크게 줄일 수 있음.

     애니메이션 파라미터를 지정할때 문자열 대신 해시 값을 사용하면 CPU시간을 절약하고 애니메이션 성능을 향상시킬수 있음

    💡 주의점  동일한 문자열은 항상 동일한 해시 값을 반환하지만,

    반대로 동일한 해시 값이 항상 동일한 문자열을 반환하지 않음

    이 점으로 인해 해시 충돌이 발생할 수 있음.

 

👀 사용예제 (그냥 int의 변수명만 사용가능)

private static readonly int IsWalking = Animator.StringToHash("IsWalking");
private static readonly int IsHit = Animator.StringToHash("IsHit");
private static readonly int Attack = Animator.StringToHash("Attack");

private void Hit()
{
    animator.SetBool(IsHit, true);
}

 

⭐️ 싱글턴

    하나만 존재하게 보장하는 디자인 패턴.

    전역접근을 제공하는 패턴.

 

 

🔥 10/9일 수요일 목표 🔥 

🔎  이전 학습 복습하기📝

🔎  개인 과제 시작하기

 

⭐️ 2D 심화 과정 진행 해보기 (9강)

      화살의 발사방향을 Arm의 회전에 맞게 조정하기

      화살이 앞으로 나갈 수 있게 만들어 보기

      화살이 벽에 닿은 경우 사라지게 만들어보기