Chapter 5-1. 인벤토리 : 아이템 획득, ScriptableObject

Date:     Updated:

카테고리:

태그:

인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click

Chapter 5. 인벤토리

아이템 획득

🚖 바위 아이템 프리팹

바위, 나무 같은 오브젝트를 파괴하면 생성되는 아이템을 만들자. 나무는 통나무를 추후 아이템으로 삼으면 되지만 바위는 파괴 후 생성되는게 없기 때문에 바위의 경우 아이템으로 주울 수 있는 프리팹을 생성하자.

바위 아이템 프리팹 만들기

image

이름은 Rock_Item

  • 바위가 깨지면 인벤토리에 담아 주울 수 있는 아이템으로서 생성할 것이다.
    • 기존에 섰던 바위 3D 모델을 가져와 크기만 둘여서 만듬
      • 크기는 당연히 일반 바위 오브젝트보다 작아야 하므로 Scale을 (30, 30, 30) 정도로 해주었다.
    • 태그는 Item 으로, 레이어 또한 Item 으로 해주었다.
      • 태그 레이어 둘다 Item 추가
      • 레이어로 불, 건물, 나무, 바위 등등 게임 내에서 상호작용 할 수 있는 레이어인지를 인수로서 LayerMask를 설정하여 종류에 따라 다르게 처리할 것이고
      • 태그 또한 레이어로 충돌 한 대상이 이런 태그를 갖고 있다면 처리를 다르게 할 것이다.
    • Rigidbody 붙이기
      • 중력이나 여러 마찰력 등등 물리적 효과 받기.
      • 질량은 0.01로 가볍게 해줌.
    • Box Collider 붙이기
      • 중력에 의해 바닥을 뚫고 떨어지지 않도록
    • 📜ItemPickUp.cs 붙이기
      • 설명 밑에 참고


📜Item.cs

오브젝트 어디에도 붙일 수 없다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[CreateAssetMenu(fileName = "New Item", menuName = "New Item/item")]
public class Item : ScriptableObject  // 게임 오브젝트에 붙일 필요 X 
{
    public enum ItemType  // 아이템 유형
    {
        Equipment,
        Used,
        Ingredient,
        ETC,
    }

    public string itemName; // 아이템의 이름
    public ItemType itemType; // 아이템 유형
    public Sprite itemImage; // 아이템의 이미지(인벤 토리 안에서 띄울)
    public GameObject itemPrefab;  // 아이템의 프리팹 (아이템 생성시 프리팹으로 찍어냄)

    public string weaponType;  // 무기 유형
}

여러 아이템의 공통적인 데이터를 담는다.

  • 아이템 기본 정보
    • 아이템 이름
    • 아이템 유형
      • 무기/장비
      • 소모품
      • 재료
        • 바위 깨면 나오는 아이템은 재료 유형에 해당할 것
      • 기타
    • 아이템 이미지
      • Sprite다. 월드 어디서든 배치될 수 있도록
        • '인벤토리'에서 아이템을 이미지로서 보기 위한, 아이템을 대표하는 이미지
          • 마인크래프트 인벤토리처럼!
          • 인벤토리에서 사용되기 위해 필요
    • 아이템 프리팹
      • 이 프리팹으로 아이템을 생성
    • 무기 유형
      • 아이템을 주울 때 들고 있는 현재 무기에 따라 아이템 줍는 애니메이션을 재생해야 하므로 필요한 정보
  • Sprite 와 Image의 차이
    • Image 👉 Canvas 위 에서만 이미지를 띄울 수 있다.
    • Sprite 👉 Canvas와 상관없이 월드 어디에서든 이미지를 띄울 수 있다.


ScriptableObject

데이터들을 저장하는데 사용할 수 있는 데이터 컨테이너 에셋

image

각각의 좀비 오브젝트들은 체력, 시야, 속도 값이 모두 같다고 가정해보자. 좀비 프리팹으로 좀비 오브젝트 3개를 찍어내면 동일한 이 체력, 시야, 속도 값이 3 개나 사본으로 생성되는 셈이다. 어차피 동일한 데이터를 사용하니까 체력, 시야, 속도 값을 한군데 만들어두고 이를 인스턴스들이 이를 참조하게 만들면 메모리를 더 아낄 수 있다.

image

모든 좀비들이 가지는 동일한 데이터들을 ScriptableObject 로서 한 군데에 에셋으로서 모아 관리하고 생성되는 오브젝트들이 이 동일한 에셋을 참조하면 메모리를 효율적으로 쓸 수 있게 된다. 예를 들어 ScriptableObject 을 상속 받는 ZombieData 클래스를 만들고 이 곳에 좀비가 가지는 데이터들을 모아 두고 이를 에셋으로 생성하여서 좀비 오브젝트들이 이 곳을 참조하게 하면 된다!

  • 메모리 사용을 줄인다. 👉 여러 사본들이 생성되는 것을 방지
    • ScriptableObject을 참조하게 된다.
  • MonoBehavior 대신 ScriptableObject를 상속 받는다면
    • 다른 스크립트와 달리 오브젝트에 컴포넌트로서 붙일 수 없다.⭐ MonoBehavior를 상속 받지 못 했으니까!
    • 이벤트는 OnEnable, OnDisable, OnDestroy 만 받을 수 있다.
  • 스크립트는 아닌 에셋이다. 어떤 고유한 파일로서.
    • 클래스이름.CreateInstance클래스이름.CreateAsset 으로, 이 클래스의 인스턴스를 만들고 이를 하나의 에셋으로서 생성할 수 있다.
    • 그냥 스크립트나 폴더 추가하듯이 [CreateAssetMenu]로 에셋 생성 메뉴에 쉽게 추가할 수 있도록 할 수도 있다.
      [CreateAssetMenu](filename, menuName, order)
      // filename 이 에셋을 생성하게 되면 기본적으로 지어질 이름
      // menuName 유니티 에셋-우클-Create- 메뉴에 보일 이름
      // order 메뉴에 보일 순서 (기본적으론 첫 번째)
      
[CreateAssetMenu(fileName = "New Item", menuName = "New Item/item")]
public class Item : ScriptableObject  
{

}
  • Item 클래스는 ScriptableObject 를 상속받는다.
    • 다른 스크립트와 달리 오브젝트에 컴포넌트로서 붙일 수가 없다.
    • 아이템들이 가지는 기본적인 데이터들을 관리한다.
    • 에셋으로서 만들어 둘 수 있다.

image

에셋에서 우클 - Create 에 New Item/item이 생긴 것을 볼 수 있다. 이제 New Item/item 메뉴가 생겨 이것으로 📜Item.cs을 ScriptableObject 타입의 에셋으로서 생성할 수 있게 됨.

image

에셋으로 생성해주면 fileName으로 설정했었던 “New Item” 이름을 기본적으로 하여 생성 된다.

image

📜Item.cs 에서 정의한 데이터 속성들을 확인할 수 있다.

image

모든 바위 아이템들이 공통적으로 같은 값을 가지는 데이터 에셋으로서 사용하기 위하여 이름을 Rock 으로 바꾸고 바위 아이템 값에 맞는 데이터들을 할당해주었다. 이제 이 Rock ScriptableObject 에셋을 참조하는 모든 오브젝트는 동일한 이 Rock ScriptableObject 에셋 한 군데를 참조하게 되는 것이고, 이에 따라 모두 동일한 데이터 값을 가지게 된다. 모든 바위 아이템들은 Item Name이 Rock 이며 Item Type이 Ingredient 가 될 것이다.

  • itemImage에 할당할 이미지는 모두 Textrue Type을 Sprite(2D and UI)로 하여 넣어준다.
  • itemPrefab에는 위에서 만든 Rock_Item 바위 아이템 프리팹을 넣어준다.


📜ItemPickUp.cs

Rock_Item 에 붙여 준다. 바위 아이템 프리팹.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ItemPickUp : MonoBehaviour
{
    public Item item;
}

📜Item.cs ScriptableObject 에셋을 참조할 수 있는 용도의 스크립트를 만들었다. 📜Item.cs 자체로는 오브젝트에 컴포넌트로서 붙일 수가 없기 때문에! 이렇게 📜Item.cs 을 할당 받을 수 있는 컴포넌트를 만들었다. 앞으로 아이템들마다 📜ItemPickUp.cs을 붙여 알맞는 📜Item.cs 타입의 데이터 에셋을 📜ItemPickUp.cs의 item에 할당해줄 것이다.

image

itemRock ScriptableObject 에셋을 할당 해준다. 모든 바위 아이템 오브젝트들이 공통적으로 참조하게 될 데이터 에셋.


🚖 아이템 주울 수 있는 텍스트 띄우기 + 아이템 습득

텍스트 UI

image

  • ShowText 👉 빈 오브젝트
  • actionText 👉 텍스트 UI

image

아이템을 주울 수 있는 상태가 되면 자동으로 위와 같은 텍스트 actionText가 활성화 되도록 한다. 위치 잡고 색상 흰색 가운데 정렬.


📜ActionController.cs

Player의 자식인 Main Camera에 붙인다.

  • 카메라와의 일정 사정거리 안에 아이템이 들어오면 (Raycast)
    • actionText 텍스트를 활성화 시키고
    • 아이템을 주울 수 있는 상태가 되도록 한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ActionController : MonoBehaviour
{
    [SerializeField]
    private float range;  // 아이템 습득이 가능한 최대 거리

    private bool pickupActivated = false;  // 아이템 습득 가능할시 True 

    private RaycastHit hitInfo;  // 충돌체 정보 저장

    [SerializeField]
    private LayerMask layerMask;  // 특정 레이어를 가진 오브젝트에 대해서만 습득할 수 있어야 한다.

    [SerializeField]
    private Text actionText;  // 행동을 보여 줄 텍스트

    void Update()
    {
        CheckItem();
        TryAction();
    }

    private void TryAction()
    {
        if(Input.GetKeyDown(KeyCode.E))
        {
            CheckItem();
            CanPickUp();
        }
    }
    
    private void CheckItem()
    {
        if (Physics.Raycast(transform.position, transform.forward, out hitInfo, range, layerMask))
        {
            if (hitInfo.transform.tag == "Item")
            {
                ItemInfoAppear();
            }
        }
        else
            ItemInfoDisappear();
    }

    private void ItemInfoAppear()
    {
        pickupActivated = true;
        actionText.gameObject.SetActive(true);
        actionText.text = hitInfo.transform.GetComponent<ItemPickUp>().item.itemName + " 획득 " + "<color=yellow>" + "(E)" + "</color>";
    }

    private void ItemInfoDisappear()
    {
        pickupActivated = false;
        actionText.gameObject.SetActive(false);
    }

    private void CanPickUp()
    {
        if(pickupActivated)
        {
            if(hitInfo.transform != null)
            {
                Debug.Log(hitInfo.transform.GetComponent<ItemPickUp>().item.itemName + " 획득 했습니다.");  // 인벤토리 넣기
                Destroy(hitInfo.transform.gameObject);
                ItemInfoDisappear();
            }
        }
    }
}

  • 매 프레임마다
    • CheckItem() 👉 항상 아이템이 사정 거리 안에 있는지를 체크한다. (E키 누르는게 아니더라도)
    • TryAction() 👉 E 키 입력이 들어왔는지를 검사한다. E 키가 들어 온다면
      • CheckItem() 👉 아이템이 사정 거리 안에 있는지를 체크한다.
      • CanPickUp() 👉 아이템을 줍는 처리.
  • CheckItem()
    • 메인카메라의 위치 transform.position로부터, 앞 방향으로, 사정 거리 range에 달하는 광선을 쐈을 때 layerMask 레이어 마스크에 해당 되는 충돌된 오브젝트가 있다면 이 충돌 정보를 hitInfo에 담고 True를 리턴한다.
      • 사정 거리 내에 layerMask 레이어를 가진 오브젝트가 있다면
        • 그 오브젝트의 태그가 “Item” 이라면
          • ItemInfoAppear() 👉 아이템 주울 수 있는 상태. 아이템 주우라는 텍스트 띄우기
            • 아이템을 주울 수 있는 상태라는 것을 표시하는 pickupActivated True 설정.
              • CanPickUp() 에서 이 값이 True 면 아이템 줍는 처리를 하기 때문에
            • 텍스트 활성화
            • 텍스트 (아이템 이름 + ` 획득 ` + (E)) 형식의 텍스트 출력
              • (E)는 노란색으로 표시
                • 텍스트에 이렇게 HTML 태그도 쓸 수 있나보다..!
      • 사정 거리 내에 layerMask 레이어를 가진 오브젝트가 없다면
        • ItemInfoDisappear() 👉 아이템 주울 수 없는 상태. 아이템 주우라는 텍스트 비활성화
          • 아이템 못 줍도록 pickupActivated False 설정.
          • 텍스트 비활성화
  • TryAction()
    • E 키 입력이 들어오면
      • 위의 CheckItem() 과정을 또 한다. 아이템 주울 수 있는 상태인지를 검사
      • CanPickUp() 👉 실제로 아이템을 줍는 처리를 한다.
        • pickupActivated가 True일 때만 다음 과정을 실행한다.
          • 충돌 오브젝트가 존재할 때만 처리한다. (혹시 모를 오류 방지)
            • 인벤토리에 아이템 추가하는 처리 (아직 구현 X)
            • 월드에 배치된 해당 아이템은 파괴. (주워서 인벤토리에 넣었으니까)
            • ItemInfoDisappear() 실행으로 아이템 주울 수 없는 상태로 만듬. 아이템 주우라는 텍스트 비활성화.


Main Camera 설정

image

  • 사정 거리는 3
  • layerMask에 Item 설정
  • 활성화/비활성화 할 텍스트는 actionText

image

Culling Mask 에 Item 레이어를 추가로 등록해주어야 Item 레이어를 가진 아이템 오브젝트들도 렌더링 되어 게임 화면에 그려질 수 있다!! 해주지 않으면 게임에 나오지 않는다..


🚖 바위 깨면 아이템 생성

📜Rock.cs

    [SerializeField]
    private GameObject go_rock_item_prefab; // 바위 파괴시 생성할 아이템 프리팹

    [SerializeField]
    private int count;  // 바위 파괴시 생성할 아이템 갯수

    //...

    private void Destruction()
    {
        SoundManager.instance.PlaySE(destroy_Sound);

        col.enabled = false;

        for (int i = 0; i < count; i++)
        {
            Instantiate(go_rock_item_prefab, go_rock.transform.position, Quaternion.identity);
        }

        Destroy(go_rock);

        go_debris.SetActive(true);
        Destroy(go_debris, destroyTime);
    }

바위가 파괴되면 바위 아이템 프리팹을 count 갯수만큼 생성한다.

image

go_rock_item_prefab, count 5 할당

image

바위가 깨지니 5 개의 아이템이 생성되고 아이템 주우라는 메세지가 뜨는 것을 확인할 수 있다. 아이템을 E키로 주우면 아이템이 파괴된다.



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Unity Lesson 3 카테고리 내 다른 글 보러가기

댓글 남기기