Unity

[Unity] Unity 2D 게임 개발 정리 (2)

sunlight-dby 2025. 5. 29. 00:27

 

이 글은 해당 유튜브를 보고 진행한 것에 대해 공부한 것을 정리한 글입니다.

 


 

트랜스폼과 인스펙터 유용 팁


Transform의 Scale 속성 : 비율 고정 기능

Unity의 Transform 컴포넌트에서 Scale을 조절할 때, 체인 아이콘을 활성화하면 한 축의 스케일 값을 변경할 때 다른 축도 비율에 맞춰 자동으로 변경됩니다.


Inspector의 자물쇠 아이콘

인스펙터 오른쪽 상단의 자물쇠를 클릭하면 선택한 오브젝트의 인스펙터 창을 고정할 수있습니다.

여러 오브젝트를 클릭해서 비교하거나 특정 오브젝트를 계속 수정할 때 유용합니다.


Header 속성

스크립트에서 [Header("타이틀")]을 사용하면 인스펙터에서 속성들을 깔끔하게 그룹화할 수 있어 가독성이 향상됩니다.

[Header("# Game Control")]
public float gameTime;
public float maxGameTime = 2 * 10f;

[Header("# Player Info")]
public int level;
public int kill;
public int exp;
public int[] nextExp = { 3, 5, 10, 100, 150, 210, 280, 360, 450, 600 };

[Header("# Game Object")]
public PoolManager pool;
public PlayerController player;

Game Manager Component의 Inspector 창


직렬화 (Serialization)

클래스를 [System.Serializable]로 선언하면 Inspector에서 변수들을 확인하고 수정할 수 있습니다.

MonoBehaviour를 상속받지 않는 클래스는 원래 Inspector 창에서 변수를 확인할 수 없는데, 해당 애트리뷰트를 통해 확인하는 것이 가능합니다.

[System.Serializable]
public class SpawnData
{
    public int spriteType;
    public float spawnTime;
    public int health;
    public float speed;
}

콜라이더 컴포넌트 리셋

콜라이더 컴포넌트를 Reset해주면 크기에 맞게 자동으로 설정이 됩니다.

Collider Component


프리팹과 오브젝트 풀링


프리팹 생성

기존 프리팹을 기반으로 새 프리팹을 생성할 때, 두 가지 옵션으로 생성이 가능합니다.

  • Original Prefab
    • 기존 프리팹을 기반으로 아예 새로운 프리팹을 생성합니다.
  • Prefab Variant
    • 기존 프리팹을 기반으로 일부 속성만 수정한 새로운 프리팹을 생성할 수 있습니다.
    • 원본은 유지하면서 다양한 변형을 손쉽게 만들 수 있습니다.

프리팹의 한계

프리팹 내부에서는 씬에 존재하는 오브젝트에 직접 접근할 수 없습니다.

따라서 OnEnable( ) 등 Unity 이벤트 함수에서 외부 참조를 초기화하는 방식이 필요합니다.


오브젝트 풀링

Unity의 Instantiate( )와 Destroy( )는 사용이 잦을 경우 메모리 단편화 및 GC로 인해 성능 저하를 초래할 수 있습니다.


 

Instantiate( )

GameObject newObj = Instantiate(originalObj);

// 오버로드

// 1. 기본 생성
Instantiate(prefab);

// 2. 위치와 회전 설정
Instantiate(prefab, position, rotation);

// 3. 부모 지정
Instantiate(prefab, parent);
Instantiate(prefab, position, rotation, parent);
  • 프리팹 또는 기존 오브젝트를 복제하여 새로운 오브젝트를 생성하는 함수입니다.
  • 오버로드
    • 기본 생성 : 현재 위치와 회전으로 생성합니다.
    • 위치와 회전 설정 : 특정 위치와 회전값을 설정해 생성합니다.
    • 부모 지정 : 생성한 오브젝트를 특정 부모 오브젝트의 자식으로 설정합니다.
  • 반환값
    • 생성된 게임 오브젝트의 참조를 반환합니다.

 

Destroy( )

// 1. 즉시 제거
Destroy(obj);

// 2. 일정 시간 후 제거
Destroy(obj, delayTime);
  • 게임 오브젝트 또는 컴포넌트를 제거하는 함수입니다.
    • Destroy( )는 실제로 즉시 제거되지 않고 호출된 프레임이 끝날 때 제거되므로, null 체크 등 주의를 요합니다.
  • 오버로드
    • 즉시 제거 : GameObject 또는 Component를 즉시 제거합니다.
    • 일정 시간 후 제거 : 지정한 시간(delay Time) 이후 제거합니다.

 


오브젝트 풀링은 잦은 Instantiate( )와 Destroy( )의 사용으로 성능 저하를 방지하기 위해 사용되는 기법입니다.

 

미리 오브젝트를 일정 개수 생성해두고 비활성화 / 재활성화하면서 이미 생성된 오브젝트를 재사용합니다.

오브젝트 풀링을 위해서는 프리팹(오브젝트)와 풀을 담당할 리스트가 필요한데,

각 프리팹 변수와 리스트는 1:1로 구성해야 하며, 프리팹을 보관할 변수가 많을 수록 그에 맞게 풀 리스트도 필요합니다.

또한, 프리팹을 보관할 변수를 배열로 선언했다면, 풀 리스트 역시 배열로 선언해야 합니다.


GetComponentsInChildren<T>( )

GetComponentsInChilderen<T>( )는 자식 트랜스폼들을 배열로 받아올 수 있습니다.

주의할 점은 받아온 배열의 0번 인덱스는 자기 자신이라는 것입니다.


Null 체크

해당 오브젝트가 null인지 체크하기 위해서 보통 object == null을 통한 비교 연산을 합니다.

이 방법 외에 !object를 통해서도 null 체크가 가능합니다.

if (obj == null)
{
    // null 체크
}

if (!obj)
{
    // null 체크
}

애니메이터 설정

스크립트에서 Animator Controller에 접근하기 위해서는 아래와 같이 RuntimeAnimatorController 타입으로 접근해야 합니다.

// Animator Controller 변수 선언
public RuntimeAnimatorController[] animController;

// Animator Component를 통한 접근
Animator animator = GetComponent<Animator>();
animator.runtimeAnimatorController;

 

애니메이션 상태를 비교하기 위해서는 GetCurrentAnimatorStateInfo를 통해 에니메이터 레이어의 정보에 접근한 후, IsName을 통해 애니메이션에 접근합니다.

아래의 코드에서 괄호 안에 들어가는 animatorLayer는 Animator 창의 Layers 중 Layer의 인덱스를 의미합니다.

Animator 창

Animator animator = GetComponent<Animator>();

int animatorLayer;
animator.GetCurrentAnimatorStateInfo(animatorLayer).IsName("Animation's Name")

부모 오브젝트

부모 오브젝트의 컴포넌트를 가져오기 위해서는 GetComponentInParent<T>( ) 함수를 사용합니다.

// 해당 스크립트가 적용된 오브젝트가 Player의 자식 오브젝트일 때
PlayerController player = GetComponentInParent<PlayerController>();

 

또한 parent 속성으로 부모 오브젝트를 변경하는 것도 가능합니다.

transform.parent = otherTransform;

스프라이트의 레이어 순서 설정

SpriteRenderer.sortingOrder를 통해 스프라이트의 레이어 순서를 설정할 수 있습니다.

SpriteRenderer spriter = GetComponent<SpriteRenderer>();

spriter.sortingOrder = 1;

레이어 (Layer)

레이어는 물리, 시스템 상으로 구분짓기 위한 요소로, 충돌 처리나 렌더링 마스크 등 시스템적 기능에 사용됩니다.

태그와 비슷하다고 느낄 수 있지만, 태그는 단순 식별자로 구분되는 것에 비해 레이어는 시스템 상으로 구분됩니다.

Layer가 Enemy로 적용된 Enemy 프리팹의 Inspector 창

 

레이어는 스크립트에서 LayerMask 타입으로 접근 가능합니다.

레이어 전용 변수로서 특정 레이어의 감지에만 반응하도록 설정할 수 있습니다.

LayerMask layermask;

AddForce( )

public void AddForce(force);
public void AddForce(force, ForceMode mode);
  • 매개변수
    • force
      • 2D : Vector2 타입으로
      • 3D : Vector3 타입으로
    • ForceMode : 힘을 어떻게 적용할지 방식을 지정합니다. 기본값은 ForceMode.Force입니다.
      • 2D  : Force, Impulse
        • Force : 물리 시간마다 누적 적용합니다. 질량에 따라 느려지며, 가속도와 비슷한 느낌을 느낄 수 있습니다.
        • Impulse : 순간적인 힘을 적용합니다. 질량에 영향을 받지만 느려지지 않으며, 점프, 총알 반동 등에 적합합니다.
      • 3D : Force, Impulse
        • VelocityChange : 질량을 무시합니다. 순간 속도의 변화가 가능하며, 가벼운 물체에도 동일하게 작용합니다.
        • Acceleration : 질량을 무시하고 지속적인 가속도를 부여합니다. 일정한 가속 효과가 가능합니다.

FromToRotation( )

Quaternion.FromToRotation(Vector3 from, Vector3 to);

 

한 벡터에서 다른 벡터로 회전하는 쿼터니언을 반환합니다.


충돌체 감지를 위한 Cast 계열 함수

해당 프로젝트에서는 플레이어를 기준으로 원형 범위 내의 충돌체를 감지하기 위해 CircleCastAll 함수를 사용하였습니다.

 

Unity에서 충돌체(Collision)를 감지하기 위해 사용하는 Cast 계열 함수들은 이외에도 다양하게 있습니다.


Ray Cast

한 점에서 직선 방향으로 광선을 쏘아 충돌체를 감지합니다.

3D일 경우  Physics에서 사용하며, 반환값은 RaycastHit이 됩니다.

bool Physics2D.Raycast(Vector2 origin, Vector2 direction);
bool Physics2D.Raycast(Vector2 origin, Vector2 direction, float distance);
bool Physics2D.Raycast(Vector2 origin, Vector2 direction, float distance, int layerMask);

RaycastHit2D Physics2D.Raycast(Vector2 origin, Vector2 direction);
RaycastHit2D Physics2D.Raycast(Vector2 origin, Vector2 direction, float distance);
RaycastHit2D Physics2D.Raycast(Vector2 origin, Vector2 direction, float distance, int layerMask);
  • 주요 매개변수
    • origin : 시작 지점
    • direction : 이동할 방향 (단위 벡터일 필요는 없습니다.)
    • distance : 감지 거리
    • layerMask : 감지를 원하는 특정 레이어
  • 반환값
    • bool 또는 RaycastHit2D

Box Cast

박스 형태의 Collider를 지정 방향으로 던져 충돌 여부를 확인합니다.

3D일 경우  Physics에서 사용하며, 반환값은 RaycastHit이 됩니다.

RaycastHit2D Physics2D.BoxCast(Vector2 origin, Vector2 size, float angle, Vector2 direction);
RaycastHit2D Physics2D.BoxCast(Vector2 origin, Vector2 size, float angle, Vector2 direction, float distance);
RaycastHit2D Physics2D.BoxCast(Vector2 origin, Vector2 size, float angle, Vector2 direction, float distance, int layerMask);
  • 주요 매개변수
    • origin : 시작 지점
    • size : 박스의 너비와 높이를 담은 Vector2
    • angle : 회전 각도
    • direction : 이동할 방향 (단위 벡터일 필요는 없습니다.)
    • distance : 이동 거리
    • layerMask : 감지를 원하는 특정 레이어
  • 반환값
    • RaycastHit2D

Circle Cast

원형 Collider를 지정 방향으로 던져 충돌 여부를 확인합니다.

3D일 경우  Physics에서 사용하며, 반환값은 RaycastHit이 됩니다.

RaycastHit2D Physics2D.CircleCast(Vector2 origin, float radius, Vector2 direction);
RaycastHit2D Physics2D.CircleCast(Vector2 origin, float radius, Vector2 direction, float distance);
RaycastHit2D Physics2D.CircleCast(Vector2 origin, float radius, Vector2 direction, float distance, int layerMask);

 

  • 주요 매개변수
    • origin : 원의 중심이자 시작 지점
    • radius : 반지름
    • direction : 이동할 방향 (단위 벡터일 필요는 없습니다.)
    • distance : 이동 거리
    • layerMask : 감지를 원하는 특정 레이어
  • 반환값
    • RaycastHit2D

Capsule Cast

캡슐 형태의 Collider를 지정 방향으로 던져 충돌 여부를 확인합니다. 3D에서만 사용이 가능합니다.

bool Physics.CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction);
bool Physics.CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo);
bool Physics.CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float distance);
bool Physics.CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float distance, int layerMask);
bool Physics.CapsuleCast(Vector3 point1, Vector3 point2, float radius, Vector3 direction, out RaycastHit hitInfo, float distance, int layerMask, QueryTriggerInteraction queryTriggerInteraction);

 

  • 주요 매개변수
    • point1, point2 : 캡슐의 양 끝의 중심 좌표 (캡슐의 세로 축 정의)
    • radius : 캡슐의 반지름
    • direction : 이동할 방향 (단위 벡터일 필요는 없습니다.)
    • distance : 이동 거리
    • layerMask : 감지를 원하는 특정 레이어
    • queryTriggetInteraction : Trigger와의 충돌도 감지할지 여부
  • 반환값
    • bool 또는 RaycastHit2D

Cast 계열 All 함수

각각의 Cast 방식 함수 뒤에 All을 붙이면, 단일 충돌이 아닌 모든 충돌체를 배열로 반환합니다.

이때 반환값은 RaycastHit2D[ ]로 충돌한 모든 오브젝트 정보가 배열에 담깁니다.


RaycastHit

뒤에 2D를 붙이면 2D에 대한 충돌 결과를, 아무것도 붙이지 않았을 때는 3D에 대한 충돌 결과를 나타냅니다.

충돌한 오브젝트에 대해 Hit된 Collider 정보, 위치, 법선 벡터, 거리 등의 정보가 구조체로 담기게되며, Hit 정보가 없다면 .collider는 null이 됩니다.


Collider.Cast( )

Collider 자체를 움직였을 때, 어떤 충돌이 발생할지 예측하는 함수입니다.

기존 Collider의 모양과 방향을 기준으로 감지합니다. 또한 2D에서만 사용이 가능합니다.

 

실제로는 Collider를 움직이지 않으며, 단순히 해당 Collider를 지정한 거리만큼 움직이면 무슨 일이 일어날지를 시뮬레이션 해주는 것입니다.

// Box Collider에 대한 Cast 함수 예시
int hitCount = boxCollider.Cast(Vector2.right, castResults, distance);
  • 주요 매개변수
    • direction : 이동할 방향
    • castResults : 결과를 저장할 RaycastHit 배열
    • distance : 이동 거리
  • 반환값
    • int 타입 : 충돌한 오브젝트의 개수

Overlap 계열 함수

Cast는 움직이면서 감지를 했다면, Overlap은 위치상 겹치는지를 감지합니다.

움직이지 않으면서 지정된 위치나 범위 내에 다른 콜라이더가 있는지(겹치는지)를 체크합니다.

Physics2D 대신 Physics를 사용하면 3D에서 사용이 가능합니다.

Physics2D.OverlapCircle
Physics2D.OverlapBox
Physics2D.OverlapArea
Physics2D.OverlapPoint
Physics2D.OverlapCapsule

넉백 기능을 위한 코루틴 (Coroutine) 활용

해당 프로젝트에서 넉백을 구현하기 위해 플레이어를 쫓아오는 적을 잠깐 대기하게 만들었습니다.

이를 위해 코루틴을 사용하였습니다.

WaitForFixedUpdate wait;

//...

void OnTriggerEnter2D(Collider2D collision)
{
    //...
    
    StartCoroutine(KnockBack());
    
    //...
}

IEnumerator KnockBack()
{
    yield return wait;
    
    Vector3 playerPos = GameManager.instance.player.transform.position;
    Vector3 dirVec = transform.position - playerPos;
    
    rigid.AddForce(dirVec.normalized * 3, ForceMode2D.Impulse);
}

 

코루틴의 yield 반환 키워드를 사용할 때 얼마나 대기할지를 설정할 수 있습니다.

  • yield return null;
    • 1 프레임 대기
  • yield return new WaitForSeconds(초);
    • 지정한 시간(초)만큼 대기
    • 해당 방식은 WaitForSeconds 타입을 new를 통해 계속 생성하기 때문에 성능 최적화를 위해 미리 변수로 생성해두고 재사용하는 것이 좋습니다.

Git

 

GitHub - Dobby-yhs/Undead-Survivor

Contribute to Dobby-yhs/Undead-Survivor development by creating an account on GitHub.

github.com