📕학습 개요

유니티 캠프 18일차.

오늘은 이번주 차 강의를 끝까지 들었다. 강의의 진도가 조금 빠른 편이라 보면서 코드를 따라서 쓰는데도 약간 벅찬 느낌이 들었다. 그래도 내용 자체는 이해하는 데 큰 어려움은 없었고, 그동안 유니티를 다루면서 혼자 고민했던 부분들에 대해 많은 해결 방안을 배운 것 같아서 매우 유익한 강의였다.

📖학습 내용

1. [SerializeField], [Header]

유니티에서 [SerializeField]와 [Header]는 인스펙터 창에 변수 정보를 표시하거나 조작하는 방식을 제어하는 데 사용되는 특성(Attribute)이다.

[SerializeField]

여태 유니티 인스펙터 창에서 변수를 확인하고 싶으면 접근 제한자를 public으로 설정하는 방법을 사용했다. 하지만 보안이나 캡슐화를 위해 private으로 설정하면 인스펙터 창에서 확인이 불가하여 디버그 모드에서나 확인할 수 있었다.

[SerializeField]를 사용하면 비공개(private)변수라도 인스펙터 창에서 보이게 하고 설정할 수 있도록 해준다.

public class StatHandler : MonoBehaviour
{
    [Range(1, 100)][SerializeField] private int health = 10;
    public int Health
    {
        get => health;
        set => health = Math.Clamp(value, 0, 100);
    }

    [Range(1f, 20f)][SerializeField] private float speed = 3;

    public float Speed
    {
        get => speed;
        set => speed = Math.Clamp(value, 0, 100);
    }
}

img_1

[Header]

[Header]는 인스펙터에서 변수 그룹에 게목(구분선)을 붙여주는 역할을 한다. 즉 가독성을 높이고 항목을 분류할 때 유용한 기능이다.

[Header("Ranged Attack Data")]
    [SerializeField] private Transform projectileSpawnPosition;

    [SerializeField] private int bulletIndex;
    public int BulletIndex { get { return bulletIndex; } }

이런 식으로 [SerializeField]와 같이 사용하는 것도 당연히 가능하다.

img_2

이 두 특성은 유니티 에디터에서 코드를 깔끔하게 유지하면서도 필요한 정보를 편하게 조작할 수 있게 해주므로, 앞으로 적극적으로 사용해야겠다.


2. 기능 분리, 폴더 정리의 중요성

이번 강의를 들으면서 가장 인상적인 부분을 고르자면, 구현하고자 하는 기능을 자연스럽게 모듈화하여 각 기능별로 스크립트를 깔끔하게 구분하신 점이었다. 단일책임의 원칙을 철저히 지킨 스크립트들은 잘 나누어진 폴더에 또 한번 정리되었고, 평소에 내가 작업하던 공간보다 훨씬 정돈되고 체계적인 프로젝트 뷰가 완성되었다. 폴더 정리의 소중함을 다시 한번 느끼게 되었다.

img_3

위와 같이 플레이어의 모든 기능을 하나의 스크립트 파일에 집어넣는 것이 아니라 움직임, 애니메이션, 스탯 관리 등 기능을 세분화하여 만들어주는 것이 유지보수에 훨씬 용이하다.

img_4

이런 식으로 폴더도 기능별로 여러번 나누어주니 에셋 관리가 굉장히 편해졌다.


3. readonly VS const

readonly는 런타임 시점에 한 번만 값을 설정할 수 있는 필드를 의미한다. 선언된 필드는 생성자 또는 선언 시에만 값을 설정할 수 있으며, 주로 불변 데이터를 보호하거나객체 생성 이후 변경되면 안 되는 값에 사용한다.

private static readonly int IsMoving = Animator.StringToHash("IsMove");
private static readonly int IsDamage = Animator.StringToHash("IsDamage");

위 코드에서는 애니메이션 컨트롤러의 파라미터를 안정적으로 사용하기 위해 readonly 필드를 사용하는 예시이다.

값을 설정한 뒤 변경할 수 없다는 점에서 readonlyconst 키워드는 비슷한 부분이 존재한다. 하지만 const는 선언 시에만 초기화가 가능하고, 컴파일 타임에 완전히 값이 고정된다는 점이 다르다. 그러므로 위 예시에서는 const를 사용할 수 없다. 런타임에 실행되는 메서드로 값을 불러오기 때문이다.

readonly VS const

구분 readonly const
초기화 시점 선언 시 또는 생성자에서 선언 시에만
값 변경 이후 변경 불가 절대 변경 불가
사용 대상 런타임 상수 (예: 외부 입력 기반 값 등) 컴파일 타임 상수 (고정된 수치 등)
예시 readonly int maxHP = GetDefaultHP(); const int MaxLevel = 100;

4. 애니메이션에 이벤트 연결

애니메이션을 찍을 때, 이벤트 추가 기능으로 원하는 메서드를 애니메이션에 간단히 추가할 수 있다.

img_5

이벤트룰 추가한 뒤, 원하는 메서드를 연걸해주면 끝이다.

img_6


5. 레이어 관리

유니티에서는 각 레이어를 내부적으로 0~31번까지 번호로 관리되며, 이 번호는 int 값의 비트하나에 대응한다. 예를 들어, 레이어 번호가 2이면 비트 마스크 값이 100, 3이면 1000 이런 식으로 대응한다.

따라서 레이어 번호를 비교하고자 하면, 비트 연산으로 처리하는 방식이 성능적으로 괜찮은 선택이 될 수 있다.

   if (levelCollisionLayer.value == (levelCollisionLayer.value | (1 << collision.gameObject.layer)))
        {
            DestroyProjectile(collision.ClosestPoint(transform.position) - direction * .2f, fxOnDestroy);
        }

위 코드에서는 collision.gameObject의 레이어가 levelCollisionLayer에 포함되어 있는가 확인하고 있다.

  • levelCollisionLayer.value는 비트 마스트이며, 여러 레이어를 하나로 묶어 표현한 정수이다.
  • 1 << collision.gameObject.layer는 충돌한 오브젝트의 레이어를 비트 마스크 하나로 변환한 것이다.
  • 즉 비트 OR 연산으로 해당 레이어가 이미 포함되어 있는지 확인할 수 있다.

🏁오늘 배운 핵심 내용 정리

  • [SerializeField] 를 사용하여 필요한 정보를 인스펙터 창에서 조작함과 동시에 보안도 챙길 수 있다.
  • 같은 오브젝트의 기능을 한 스크립트에 모으지 말고, 기능 별로 스크립트를 분리하여 관리하자.
  • const 상수는 컴파일 타임에 고정되므로, 런타임에 초기화해줄 수 없다.
  • 애니메이션에 이벤트를 연결하여 더욱 다양한 연출을 구상할 수 있다.
  • 레이어 값은 2진수 비트마스크로 관리되며, 이를 활용하면 코드를 최적화 시키는데 도움이 될 수 있다.

댓글남기기