공부 기록/유니티 Unity

[Unity/TIL] 유니티 숙련 과정에서 알게 된 것들

톰마토 2025. 3. 5. 22:58
반응형
목차
Rigidbody - ForceMode 종류
Raycast
InputAction에서 마우스 변화량 받아오기
매니저클래스 방어코드
UI - Image Type Filled 사용, LayoutGroup 사용
UI - Layout Group 사용
ScriptableObject
Depth Only Camera - 특정 오브젝트만 찍기
애니메이션에 이벤트 추가

Rigidbody - ForceMode 종류

점프를 구현하면서 ForceMode.Impulse를 사용했다. 힘을 주는 방법에 종류가 있었다!!

public void OnJump(InputAction.CallbackContext context)
{
    if(context.phase == InputActionPhase.Started && IsGrounded())
    {
        _rigidbody.AddForce(Vector2.up * jumpPower, ForceMode.Impulse);
    }
}
  1. Force - 힘을 지속적으로 적용
    Rigidbody.AddForce(Vector3 force, ForceMode.Force);
  2. Acceleration - 가속도를 적용. 이전 힘의 누적에 따라 점진적으로 빠르게 움직임.
    Rigidbody.AddForce(Vector3 force, ForceMode.Acceleraion);
  3. Impulse - 순간적인 힘을 적용. 짧은 시간에 갑작스러운 움직임 발생.
    Rigidbody.AddForce(Vector3 force, ForceMode.Impulse);
  4. VelocityChange - 변화하는 속도를 적용. 현재 속도를 변경하면서 움직임.
    Rigidbody.AddForce(Vector3 force, ForceMode.VelocityChange);
알아보니 ForceMode2D도 있다. 지난 'Legend Ninja' 2D 프로젝트에서 원거리 몬스터의 발사체를 날릴 때 rigidbody의 velocity를 설정해 주는 방식으로 발사했는데, ForceMode2D.Impulse를 사용했다면 좋을 것 같다!! 

Raycast

Ray. 즉 광선 쏘기

  • 쏘는 데 필요한 것들 : 직선의 시작점(origin)과 방향(direction)

시작점과 방향을 지정해서 쏠 수 있고, 카메라를 기준으로 쏠 수 있다. 

Ray ray = new Ray(transform.position, transform.forward); // 이 스크립트가 달려있는 오브젝트 기준
Ray ray = Camera.main.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0)); // 카메라의 중심 기준
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); // 마우스 기준

Raycast

Ray에 맞은 물체 정보를 판단한 뒤 여러 가지 후처리를 하는 방식. 

Ray, RaycastHit, MaxDistance, LayerMask 등의 옵션을 넣어줄 수 있다. LayerMask로 특정 Layer들만 충돌하게 하는 것이 간편하다고 느껴졌다.

 

RaycastHit에 검출된 객체의 정보들이 담긴다. 

  • RaycastHit.point : 레이캐스팅이 감지된 위치
  • RaycastHit.distance : Ray의 원점에서 충돌 지점까지의 거리
  • RasycastHit.transform : 충돌 객체의 transform에 대한 참조 
  • RasycastHit.normal : 충돌한 표면의 법선 벡터 
  • 등등.. 

Raycast 예시 1

Attack 애니메이션 실행 중에 호출되는 OnHit이벤트이다. 화면 중앙에서 ray를 쏘고 부딪힌 것이 자원인지(나무 등) 확인해서 게임 자원을 채취하는 코드이다. (공격도 추가될 예정)

Raycast에 attackDistance로 Ray의 길이를 지정해주고 있다.

 

Raycast 예시 2

플레이어 바닥으로 4개의 Ray를 쏴서 땅 위에 있는지 체크하는 코드이다. 이때 LayerMask를 사용했다.

IsGround() 예시

Raycast에 옵션으로 넣어준 groundLayerMask는 인스펙터 창에서 지정해 줬다. 

LayerMask 예시


Input Actions에서 마우스 변화량 받아오기

마우스로 플레이어가 바라보는 방향을 회전시키기 위해 InputAction 만들 때 Value 타입의 Delta로 넣어준다. 마우스 변화량으로 회전값을 넣어주기 위함이다. 

Value - Delta
Delta 받아오는 코드

받아온 mouseDelta로 좌우나 상하 회전을 적용시켜준다. 상하 회전은 고개만 돌리는 느낌을 주기 위해 카메라를 회전시켜 주고 좌우 회전은 캐릭터의 Y축 회전을 주었다. 

Look 회전 적용하는 코드


매니저클래스 방어코드

싱글톤으로 Manager 클래스를 만들 때, 매니저 오브젝트를 무조건 직접 추가해줘야 하는 줄 알았는데 아래 코드와 같이 getter에서 null이라면 GameObject를 새로 만들어주는 방식도 가능했다. 까먹고 넣지 않아도 오류가 나지 않을 수 있도록 방어코드를 작성하는 것이다.

Awake 함수에서는 이미 게임오브젝트가 만들어져서 실행됐다는 것일테니 또 만들어 줄 필요 없이 _instance = this; 를 해주지만 만약 _instance가 null이 아닌데 현재 나와 같지 않다면 파괴해 주는 코드를 추가했다. 

private static CharacterManager _instance;
public static CharacterManager Instance
{
    get
    {
        if(_instance == null)
        {
            _instance = new GameObject("CharacterManager").AddComponent<CharacterManager>();
        }
        return _instance;
    }
}

private void Awake()
{
    if(_instance == null)
    {
        _instance = this;
        DontDestroyOnLoad(gameObject);
    }
    else
    {
        if(_instance != this)
        {
            Destroy(gameObject);
        }
    }
}

UI - Image Type Filled 사용, LayoutGroup 사용

체력바 표시할 때 scale로 하지 않고 다른 방법 사용해 봤다.

3D 프로젝트니까 Package Manager에서 2D Sprite를 다운받고, 2D Sprite를 하나 생성해서 체력바에 넣어줬다.

  • Image Type을 Filled
  • Fill Method를 Horizontal로 변경

 

fill은 Image를 받아와서 fillAnount에 0에서 1사이의 값으로 넣어주면 된다. 

public Image uiBar;

private void Update()
{
    uiBar.fillAmount = GetPercentage();
}

float GetPercentage()
{
    return curValue / maxValue;
}

UI - Layout Group 사용

체력바, 배고픔 정도 등의 상태창을 묶어둔 Conditions 오브젝트에 Vertical Layout Group 컴포넌트를 추가해 준다. 그럼 하위 오브젝트들이 알아서 정리된다. 

VerticalLayout Group 적용 모습

Vertical외에도 Horizontal, Grid 등 여러 가지 있다.  인벤토리에는 Grid Layout Group을 사용해 봤다. 

인벤토리에 Grip Layout Group 적용한 모습


ScriptableObject 

  • ScriptableObject로 데이터를 에디터내에서 저장하고 관리할 수 있다.
  • 게임 오브젝트에 붙이지 않고 독립적인 데이터 에셋으로 사용할 수 있다. 그래서 로드가 한 번만 돼서 메모리 절약 효과가 있다고 함. 
  • 만드는 방법은 아래처럼 ScriptableObject을 상속받아서 만들면 된다. [CreateAssetMenu] 애트리뷰트를 사용해서 Create 할 때 메뉴에서 선택할 수 있게 만들어줬다.
[CreateAssetMenu(fileName = "Item", menuName = "New Item")]
public class ItemData : ScriptableObject
{
    [Header("Info")]
    public string displayName;
    public string description;
    public ItemType type;
    public Sprite icon;
    public GameObject dropPrefab;

    [Header("Stacking")]
    public bool canStack;
    public int maxStackAmount;

    [Header("Consumable")]
    public ItemDataConsumable[] consumables;

    [Header("Equip")]
    public GameObject equipPrefab;
}

Create > New Item을 선택하면 된다. 

Create > New Item 생김

그럼 에디터에서 데이터를 입력하고 관리해줄 수 있다!

Scriptable Object 사용 예시 - 아이템 정보

 

지난 프로젝트에서 데이터 테이블을 만들어 사용하는 것이 과하다는 생각이 들었었는데, 여러 오브젝트가 같은 데이터를 공유할 때는 앞으로 SO를 유용하게 사용할 수 있을 것 같다!!

=> 라고 적어놨었는데 (튜터님의 피드백을 받고) 좀 더 옳은 사용 방식을 기대해보자면

  1. 여러 오브젝트에 같은 데이터(SO)를 각각 박아주는 방식은 좋지 않음.
    ex) 두 곳에서 Item_Ax를 사용한다면 SO가 두 번 복사되어 들어감.
  2. 한 곳에 박아두더라도 만약 그 오브젝트가 여러 개 생성되어야 한다면 그만큼 SO도 복사됨.
    ex) 프리팹에 한 번 박아뒀다고 SO가 한 번 복사되고 끝나는 것이 아님. 프리팹의 인스턴스들에 각각 복사되어 생기는 거임.

=> 그래서 효율적인 것뿐만 아니라 관리 측면에서도 SO든 DataTable이든 한 곳에 모아두고 그곳에서 값만 받아다가 사용하는 것이 좋다. 


DepthOnly Camera - 특정 오브젝트만 찍기

플레이어가 장비를 들고있을 때, 메인 카메라에서 장비가 잘려 보이는 현상을 해결하기 위해 들고 있는 장비를 보여주는 카메라를 따로 만들어주기로 했다.

 

플레이어에 장비용 카메라를 달아주고, Clear Flags를 Depth only로 변경했다. 그럼 보조 카메라로 찍는 장면이 메인 카메라의 화면 위에 덧씌워진다. 이때 Culling Mask를 Equip으로 설정해서 장착한 오브젝트만 렌더링 하도록 할 수 있다.

장비 카메라

장비를 장착했을 때 EquipCamera에 까만 화면에 장비만 찍히고 있는 모습을 볼 수 있다. MainCamera에서는 함께 보인다. 


애니메이션에 이벤트 추가

애니메이션 클립에서 Add Animation Event을 해준다.

Clip 편집 창

애니메이션 중간에 실행하게 할 메서드를 Function에 추가해주면 된다. 

반응형