2025.03.06 (목)

윤혜진·2025년 3월 6일
post-thumbnail

📍오늘의 학습 키워드

  • unity 게임 개발 숙련 1주차 (1-11 ~ 1-17)
    • 인벤토리 구현 (1-10 ~ 1-12)
      • Grid Layout Group로 인벤토리 UI 정렬
      • childCount
      • TextMeshPro-Text의 Auto Size
      • activeInHierarchy 로 인벤토리 토글 만들기
      • Instantiate 로 아이템 드랍하기
      • 장비 카메라 (EquipCamera)
    • 자원 캐기 구현 (1-13 ~ 1-14)
      • Animation Event로 메서드 호출하기
      • Raycast 와 RaycastHit 로 오브젝트 감지하기

📍학습 내용

  • 인벤토리 구현 (1-10 ~ 1-12)

    • Grid Layout Group

      • 자식 오브젝트들을 일정한 크기의 격자(Grid) 형태로 자동 정렬하는 레이아웃 컴포넌트

      • 버튼, 아이콘, 아이템 슬롯, 인벤토리 등 격자 형태로 정렬할 때 유용하다.

      • Grid Layout Group 의 주요 속성

        • Cell Size : 각 아이템(버튼, 아이콘) 크기
        • Spacing : 아이템 사이 간격 조절
        • Start Corner : 아이템 배치 시작 위치 (왼쪽 위, 오른쪽 아래 등)
        • Start Axis : 가로 또는 세로 방향으로 채워지는 방식 설정
        • Constraint : 열 개수 또는 행 개수를 고정
        • Child Alignment : 셀 내부에서 아이템의 정렬 방식
      • Grid Layout Group 사용 방법:

      • 1. Canvas 안에 Panel 또는 Empty GameObject를 만든다.

      • 2. Inspector 창에서 Add Component → GridLayoutGroup 검색 후 추가.

      • 3. GridLayoutGroup이 추가된 오브젝트에 자식 오브젝트(UI 버튼, 이미지 등)를 추가하면 자동으로 정렬된다.

      • 아래는 GridLayoutGroup 오브젝트에 자식 오브젝트를 추가하고 그대로 복사한 모습. 자동으로 잘 정렬되는 것을 볼 수 있다 :

      • Spacing 을 통해 가로 세로 간격을 각각 15로 맞춰준 모습:

    • TextMeshPro-Text의 Auto Size

      • 인벤토리의 아래에는 설명 텍스트가 들어갈 예정인데, 이 설명이 길어지면 아래 사진처럼 UI창을 넘어가는 경우가 생길 수 있음
      • 이런 경우, TextMeshPro-Text의 Auto Size 를 체크하면 설명이 길어질 때 설정한 min size까지 폰트 크기를 줄여 UI를 넘어가지 않도록 설정할 수 있다.
      • 아래는 같은 텍스트에 Auto Size 를 체크하고, Min을 18로 조정한 모습:
    • childCount : 해당 오브젝트의 자식 오브젝트가 몇 개인지 갯수를 받아올 수 있다. (UIInventory.cs)

      void Start()
      {
          //초기화
          controller = CharacterManager.Instance.Player.controller;
          condition = CharacterManager.Instance.Player.condition;
      
          //처음에 인벤토리가 보이면 안되므로 꺼진 상태로 시작
          inventoryWindow.SetActive(false);
      
          //아이템 슬롯은 슬롯 판넬의 자식 오브젝트로 들어가 있음
          //childCount을 통해 슬롯 판넬의 자식 오브젝트 개수를 받아온 뒤 그만큼 슬롯을 만들어준다.
          slots = new ItemSlot[slotPanel.childCount];
      }
    • activeInHierarchy

      • 해당 오브젝트가 계층 구조에서 최종적으로 활성화되었는지를 반환하는 메서드
      • activeSelf 와 다른점:
      • 아래는 activeInHierarchy 가 사용된 스크립트 (UIInventory.cs) :
        //인벤토리는 Tab키를 눌러서 열고 닫을 수 있도록 한다.
        //열려있으면 닫고, 닫혀있으면 열도록 한다.
        public void Toggle()
        {
            if (IsOpen())
            {
                inventoryWindow.SetActive(false);
            }
            else
            {
                inventoryWindow.SetActive(true);
            }
        }
        //창이 열려있는지 확인하는 메서드
        public bool IsOpen()
        {
            return inventoryWindow.activeInHierarchy;
        }
    • Instantiate

      • 게임 오브젝트를 동적으로 생성하는 데 사용된다.
      • 이 함수를 사용하면 코드를 통해 원하는 상황이나 위치에 오브젝트를 생성할 수 있다.
      • 일반적으로 프리팹을 생성할 때 자주 사용됨.
      • 참고한 사이트 👉 [개념 콕] 유니티 Instantiate 함수 - 내일배움캠프 블로그
      • 아래는 Instantiate를 아이템 드랍 메서드에 사용한 코드 (UIInventory.cs) :
        //인벤토리에서 선택한 아이템을 버리는 메서드
        public void ThrowItem(ItemData data)
        {
            //아이템을 버리면 땅에 떨어져야 하므로 해당 아이템을 인스턴스화 시킨다.
            //Instantiate(찍어낼 오브젝트, 찍어낼 위치, 찍을때 회전값)
            Instantiate(data.dropPrefab, dropPosition.position, Quaternion.Euler(Vector3.one * Random.value * 360));
        }
    • 장비 카메라 (EquipCamera)

      • 카메라를 하나만 쓰는 경우 장착된 무기가 가려지는 문제가 발생한다.

      • 때문에 장비만을 찍는 카메라를 따로 만들어 주어야함!

      • 특정 오브젝트만을 찍는 카메라를 만드는 방법은 다음과 같다:

        • 카메라에서 Clear Flags 속성을 Depth only 로 바꾸면, 아래 Culling Mask 항목에서 선택한 레이어 속성만 촬영할 수 있게 된다.
        • 아래는 Culling Mask 에 Equip 을 추가한 모습. 카메라에 레이어 속성이 Equip 인 도끼만 찍히는 것을 확인할 수 있다. (레이어 속성을 여러개 선택하는 것도 가능하다.)
        • 보다시피 Clear Flags 속성을 바꿔준 EquipCamera 는 장비만 찍어주고 있지만, 실 게임에서는 아래와 같이 MainCamera 의 화면과 합쳐서 보여진다.
  • 자원 캐기 구현 (1-13 ~ 1-14)

    • Animation Event로 메서드 호출하기

      • 플레이어가 장작을 팬다고 가정할 때, 도끼로 나무를 치는 순간 장작이 떨어져야지 나무를 치고 도끼를 내려놓았을 때 장작이 떨어지면 부자연스러울 것임.

      • 때문에 아이템을 드랍하는 메서드는 애니메이션이 끝나고 난 뒤가 아니라, 애니메이션 실행 중에 메서드를 호출해주어야 하는데, 이런 상황에서 필요한 것이 바로 Animation Event이다.

      • Animation Event 를 추가하는 방법은 다음과 같다:

          1. Window > AnimationAnimation 을 클릭해 Animation 창을 연다 (또는 Ctrl+6)
        • 2. Animator Componont포함된 오브젝트(이 경우에는 도끼)를 선택한다.

          1. 내려치는 모션 타이밍에 맞춰 클릭 > 오른쪽 마우스 버튼 클릭 > Add Animation Event 클릭으로 Animation Event 추가 (혹은 왼쪽의 추가 버튼 클릭)

        • 추가된 Animation Event 을 클릭하면 Inspector창에서 선택한 타이밍에 호출할 메서드를 선택할 수 있다.

  • Raycast 와 RaycastHit 로 오브젝트 감지하기

    • Raycast
      • 레이저를 쏘듯이 가상의 광선을 발사하여 충돌 여부를 감지하는 기능
        • 충돌이 감지되면: true를 반환하고, RaycastHit 에 충돌한 오브젝트 정보를 저장한다.
        • 충돌이 없으면: false 반환.
      • 3D에서는 Physics.Raycast() , 2D에서는 Physics2D.Raycast()
    • RaycastHit
      • 광선이 충돌한 오브젝트의 정보를 저장하는 구조체
    • 기본 형태:
      bool isHit = Physics.Raycast(출발점, 방향, out RaycastHit 충돌정보, 최대 거리, 충돌할 레이어);
      기능설명
      Physics.Raycast()특정 방향으로 Ray를 발사하여 충돌 감지
      Physics.RaycastAll()광선이 충돌한 모든 오브젝트 감지
      RaycastHit충돌한 오브젝트의 정보를 저장하는 구조체
      LayerMask특정 레이어만 감지하도록 필터링
      Physics2D.Raycast()2D 물리 엔진에서 Raycast 사용
    • 참고 사이트
    • Raycast 와 RaycastHit 로 오브젝트 감지하기 :
      public void OnHit()
      {
          //카메라의 중심에서 ray를 쏴서 공격 거리 내에 있는 오브젝트를 찾는다.
          Ray ray = camera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2, 0));
      
          //ray와 충돌한 오브젝트의 정보를 저장할 변수
          RaycastHit hit;
      
          if (Physics.Raycast(ray, out hit, attackDistance))
          {
              //자원 수집이 가능한 장비이고, 충돌한 오브젝트가 Resource 컴포넌트를 가지고 있다면
              if (doesGatherResources && hit.collider.TryGetComponent(out Resource resource))
              {
                  //해당 자원을 
                  resource.Gather(hit.point, hit.normal);
              }
          }
      }

📍겪은 어려움

  • 실습 중 Tab 키를 눌러도 인벤토리 창이 열리지 않는 문제 발생:

    • Tab 을 누르면 마우스 잠금은 제대로 꺼지는데, 인벤토리 창은 열리지 않음
    • Tab 키가 아닌 다른 키로 설정했는지 확인해보았으나 제대로 Tab 으로 설정해준 것을 확인
    • 메서드 연결도 잘 해주었음을 확인함
    • 연결한 OnInventory 메서드 확인:
      public void OnInventory(InputAction.CallbackContext context)
      {
          //Tab키를 누르면 inventory 델리게이트에 있는 함수를 호출
          if (context.phase == InputActionPhase.Started)
          {
              inventory?.Invoke();
              ToggleCursor();
          }
      }
    • 원인 발견: 델리게이트 inventory에 메서드를 제대로 연결해주지 않아 생긴 문제였음
    • 해결: 인벤토리를 띄워주는 메서드(Toggle)를 가진 클래스 UIInventory.cs 에서, inventory 델리게이트에 Toggle 매서드를 구독시켜주었더니 정상적으로 작동함
    • 아래는 수정한 코드 (UIInventory.cs ) :
      private PlayerController controller; //정보를 주고받을 플레이어 컨트롤러
      
      void Start()
      {
          controller = CharacterManager.Instance.Player.controller;
          controller.inventory += Toggle; //추가한 코드
      }
      
      //인벤토리는 Tab키를 눌러서 열고 닫을 수 있도록 한다.
      //열려있으면 닫고, 닫혀있으면 열도록 한다.
      public void Toggle()
      {
          if (IsOpen())
          {
              inventoryWindow.SetActive(false);
          }
          else
          {
              inventoryWindow.SetActive(true);
          }
      }
      //창이 열려있는지 확인하는 메서드
      public bool IsOpen()
      {
          return inventoryWindow.activeInHierarchy;
      }

📍회고 및 반성

  • Raycast 는 이번에 처음 나온게 아닌 상호작용을 구현할 때도 사용했는데 정리하는 것을 잊었다.
  • 나중에 까먹었을 때 쉽게 떠올릴 수 있도록 설명이나 과정을 세세하게 적으려고 노력하고 있는데, 그러다보니 강의를 듣는 속도나 TIL을 쓰는 시간이 너무 길어지는 것 같다.
  • 복습하고 정리하는 건 좋지만, 역시 속도를 낼 필요가 있을 듯. 시간을 줄일 수 있는 방법을 좀 더 고민해봐야겠다.

0개의 댓글