6 minute read

첫 구상

  • 클래스를 구현하기 전에 러프하게 생각해본 변수와 기능들이다.
    • 아이템 데이터 클래스
      • 아이템들에 대한 데이터
    • 아이템 클래스
      • 아이템 이름
      • 아이템 유형 - enum
      • 아이템 이미지
      • 아이템 프리팹
      • 아이템 개수
      • 아이템 효과(기능)

      • 아이템 획득
        • 가까이 다가가면 키 활성화 + 획득
        • (콜라는 충돌하면 획득 가능..?)
      • 아이템 사용
        • 키를 누르면 상황에 맞는 아이템이 자동으로 빠져나감
        • 콜라30개 모아 페인트로 변환한다고 할때, 어떤 방식으로 변환시킬지. 예를 들면 더블클릭 뭐 오른쪽 버튼 눌러서 바디페인팅으로 변환 등
      • 아이템 인식 영역(Raycast)
      • 아이템 드래그
      • 아이템 클릭시
    • 아이템 툴팁(설명)
      • 인벤토리 내에서 마우스 위로 올리면 설명 볼 수 있음
    • 인벤토리 클래스
      • 아이템 추가
      • 이이템 사용(감소 및 삭제)
      • 아이템 드래그 및 위치변경
    • 슬롯


ActivatingKeyTrigger

  • 상호작용하는 오브젝트 가까이 다가가면 활성화 됨

  • 아이템을 키를 누르면 획득하도록 함

💻전체 코드

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

    [ExecuteInEditMode]
    [RequireComponent(typeof(SphereCollider))]
    public class ActivatingKeyTrigger : MonoBehaviour
    {
        #region VARIABLES & PROPERTIES
            private SphereCollider _trigger;
            private CanvasGroup _ui;

            [Range(0.1f, 100.0f)] public float radius = 1.0f;

            public KeyTriggerType type;
            public List<KeyCode> pressKey;
            public bool isDone = false;
            public bool isActive = false;
            public Action callback = null;
            public ItemObject itemObj = null;

        #endregion

        #region UNITY_EVENTS
            private void Awake() {
                _trigger = this.GetComponent<SphereCollider>();
                _trigger.isTrigger=true;

                if(!itemObj) itemObj = GetComponent<ItemObject>();
            }

            private void Start() {
                
            }

            private void Update() {
                _trigger.radius = radius;
                
                if(!isDone&&isActive&&Input.GetKey(pressKey[0])) {
                    isDone = true;
                    isActive = false;
                    switch(type) {
                        case KeyTriggerType.GetItem:
                        {
                            ItemManager.Inst.itemInventory.AddItem(itemObj.penguin.Id);
                            Destroy(itemObj.gameObject);
                            break;
                        }
                    }
                    if(callback!=null) {
                        callback();
                    } 
                }
            }
            
            private void OnTriggerEnter(Collider other) {
                if(!isDone&&other.gameObject!=this) {
                    switch(type) {
                        case KeyTriggerType.GetItem:
                        {
                            isActive=true;
                            break;
                        }
                    }
                }
            }

            
            private void OnTriggerExit(Collider other) {
                if(other.gameObject!=this) {
                    switch(type) {
                        case KeyTriggerType.GetItem:
                        {
                            isActive=false;
                            break;
                        }
                    }
                }
            }

        #endregion
    }


    [ExecuteInEditMode]
    [RequireComponent(typeof(SphereCollider))]
  • 편집 모드(Edit Mode): 게임에 변경을 가한다
  • 플레이 모드(Play Mode): 게임을 플레이함으로써 해당 변경을 테스트한다
  • 일반적으로, 유니티에서 스크립트를 구성하는 객체들은 플레이 모드에서만 실행되지만, [ExecuteInEditMode]는 편집모드에서도 Monobehaviour 스크립트의 각 이벤트 함수가 호출되도록 해준다

  • [RequireComponent(typeof())]을 사용하는 스크립트를 오브젝트에 추가하면 필요하느 컴포넌트가 자동으로 오브젝트에 추가된다. 따라서 아이템 오브젝트에 이 스크립트를 추가하면 자동으로 SphereCollider 컴포넌트가 추가된다


📝VARIABLES & PROPERTIES

    private SphereCollider _trigger;
    private CanvasGroup _ui;

    [Range(0.1f, 100.0f)] public float radius = 1.0f;

    public KeyTriggerType type;
    public List<KeyCode> pressKey;
    public bool isDone = false;     
    public bool isActive = false;
    public Action callback = null;
    public ItemObject itemObj = null;
  • SphereCollider : 트리거로 쓰이게 됨. isTrigger. 아이템이 범위 내에 들어오면 주울 수 있도록 함
  • CanvasGroup
  • [Range(0.1f, 100.0f)] public float radius : 트리거 범위
  • public KeyTriggerType type; : enum형으로 cs public enum KeyTriggerType { GetItem, TalkAction, InterAction, StopSlowAction, } 아이템 얻는데 쓰이는지, 엔피시와 대화할때 쓰이는지 등 구분한다

  • public List<KeyCode> pressKey : 아이템 먹을 키

  • public Action callback : Action은 미리 선언된 델리게이트 변수이다.
    • Func은 반환값이 있는 메소드를 참조하는 델리게이트 변수
    • Action은 반환값이 없고, 매개변수가 없는 메소드를 참조하는 델리게이트 변수


📝UNITY_EVENTS

✏️Awake()

private void Awake() {
    _trigger = this.GetComponent<SphereCollider>();
    _trigger.isTrigger=true;

    if(!itemObj) itemObj = GetComponent<ItemObject>();
}
  • Awake()는 Start보다 더 먼저 호출되는 함수로 초기화할때 쓰임
  • 트리거는 플레이어가 아이템 주울 수 있는 범위 안에 들어왔는지 확인하는 용도
  • 오브젝트의 콜라이더 컴포넌트를 가져오고, isTrigger를 체크해줌
  • 만약 아이템이 없으면 오브젝트의 ItemObject 컴포넌트를 가져옴


✏️Update()

private void Update() {
    _trigger.radius = radius;
            
    if(!isDone&&isActive&&Input.GetKey(pressKey[0])) {
        isDone = true;
        isActive = false;
        
        switch(type) {
            case KeyTriggerType.GetItem:
                {
                    ItemManager.Inst.itemInventory.AddItem(itemObj.penguin.Id);
                    Destroy(itemObj.gameObject);
                    break;
                }
        }
        if(callback!=null) {
            callback();
        } 
    }
}
  • 매 프레임마다 호출
  • 지금 이 스크립트는 편집모드에서 변경사항을 반영해야하기 때문에 radius도 새롭게 받아준다
  • 만약 아이템이 범위내에 있고, 먹지 않았고 해당 키를 누른다면, 상호작용을 시작한다
    • 키 용도가 아이템을 얻는 것이라면 인벤토리에 아이템을 추가하고, 해당 아이템은 씬에서 삭제시킨다


✏️onTriggerEnter / onTrigerExit

private void OnTriggerEnter(Collider other) {
    if(!isDone&&other.gameObject!=this) {
        switch(type) {
            case KeyTriggerType.GetItem:
            {
                isActive=true;
                break;
            }
        }
    }
}

            
private void OnTriggerExit(Collider other) {
    if(other.gameObject!=this) {
        switch(type) {
            case KeyTriggerType.GetItem:
            {
                isActive=false;
                break;
            }
        }
    }
}

  • 유니티 이벤트 함수
  • 이 함수를 통해 충돌을 감지한다. 여기선 아이템 인식 범위 내에 플레이어가 들어왔는지 안왔는지의 용도로 쓰인다

  • OnTriggerEnter
    • 두 개의 게임오브젝트가 충돌할 때 호출
    • 만약 먹지 않았고, 충돌체가 다른 아이템이 아닌경우, 즉 플레이어와 충돌된 경우, 키트리거타입이 아이템을 얻는 용도이면 아이템을 먹을 수 있도록 isActive를 활성화시킨다
  • OnTriggerExit
    • 두 개의 게임오브젝트가 충돌을 멈춘 후 호출
    • 플레이어와 아이템간의 충돌이 사라지면 isActive를 꺼서 줍지못하도록 한다


Inventory

💻전체 코드

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

public class Inventory : MonoBehaviour
{
    #region VARIABLES & PROPERTIES
    GameObject _inventoryPanel;
    GameObject _slotPanel;
    public GameObject itemSlot;

    private int _slotSize;
    public List<Item> itemList = new List<Item>();
    public List<GameObject> slotObjList = new List<GameObject>();
    public List<Slot> slotList = new List<Slot>();
    #endregion

    #region UNITY_EVENTS
    private void Start() {
        _slotSize = DataManager.SLOT_SIZE;
        _inventoryPanel = GameObject.Find("InventoryPopup");
        _slotPanel = GameObject.Find("Content").gameObject;

        for (int i = 0; i < _slotSize; i++) {
            GameObject slotPrefab = Instantiate(itemSlot);
            slotObjList.Add(slotPrefab);
            var slot = slotObjList[i].GetComponent<Slot>();
            slot.Id = i;
            slotList.Add(slot);
            slotObjList[i].transform.SetParent(_slotPanel.transform, false);
        }
    }

    private void Update() {

    }
    #endregion

    #region MAIN_FUNCTIONS
    public void AddItem(int id) {
        Item itemToAdd = ItemManager.Inst.GetItemById(id);

        // 아이템이 인벤토리에 있고, 쌓을 수 있는 경우
        if (itemToAdd.isStackable && CheckIfItemIsInInventory(itemToAdd)) {
            for (int i = 0; i < slotList.Count; i++) {
                if (itemToAdd.Id == slotList[i].item.Id) {
                    ItemUIObject data = slotObjList[i].transform.GetChild(0).GetComponent<ItemUIObject>();
                    data.Amount += itemToAdd.value;
                    break;
                }
            }
        }

        // 아이템이 인벤토리에 없는 경우
        else {
            for (int i = 0; i < slotList.Count; i++) {
                if(slotList[i].item == null) {
                    var itemUI = Instantiate(Resources.Load<ItemUIObject>("Prefabs/ItemUIObject"), slotList[i].transform);
                    itemUI.Amount = itemToAdd.value;
                    itemUI.itemImage.sprite = itemToAdd.sprite;
                    itemUI.item = itemToAdd;
                    itemUI.slotId = i;
                    slotList[i].item = itemToAdd;
                    itemList.Add(itemToAdd);
                    break;
                } 
            }
        }
    }

    public bool CheckIfItemIsInInventory(Item item) {
        for (int i = 0; i < itemList.Count; i++) {
            if (item.Id == itemList[i].Id) {
                return true;
            }
        }
        return false;
    }
    #endregion
}

📝VARIABLES & PROPERTIES

    GameObject _inventoryPanel;
    GameObject _slotPanel;
    public GameObject itemSlot;

    private int _slotSize;
    public List<Item> itemList = new List<Item>();
    public List<GameObject> slotObjList = new List<GameObject>();
    public List<Slot> slotList = new List<Slot>();
  • GameObject _inventoryPanel; : 인벤토리 패널, 여기선 변수를 만들어 놓긴 했지만 쓰이진 않음.. 지워야하나
  • GameObject _slotPanel; : 슬롯 패널
  • public GameObject itemSlot; : 슬롯 변수

  • private int _slotSize; : 슬롯 사이즈 변수
  • public List<Item> itemList = new List<Item>(); : 인벤토리에 들어있는 아이템 리스트
  • public List<GameObject> slotObjList = new List<GameObject>(); : 슬롯 프리팹 가지고 있는 슬롯 오브젝트 리스트
  • public List<Slot> slotList = new List<Slot>(); : 슬롯 리스트


📝UNITY_EVENTS

private void Start() {
        _slotSize = DataManager.SLOT_SIZE;
        _inventoryPanel = GameObject.Find("InventoryPopup");
        _slotPanel = GameObject.Find("Content").gameObject;

        for (int i = 0; i < _slotSize; i++) {
            GameObject slotPrefab = Instantiate(itemSlot);
            slotObjList.Add(slotPrefab);
            var slot = slotObjList[i].GetComponent<Slot>();
            slot.Id = i;
            slotList.Add(slot);
            slotObjList[i].transform.SetParent(_slotPanel.transform, false);
        }
    }
  • 슬롯 사이즈는 DataManager에 상수로 만들어둠. 거기서 바꿀 수 있음
  • 게임 오브젝트중 인벤토리 패널을 찾아와 인벤토리패널 변수에 저장(여기선 안쓰임)
  • Content가 슬롯이 붙을 바로 위 부모 오브젝트로 슬롯패널 변수에 저장

  • 정해둔 슬롯 사이즈만큼 패널에 슬롯을 붙여주어야 함
    • itemSlot은 슬롯의 UI프리팹이다. 이것을 생성하여 슬롯 오브젝트 리스트에 넣어준다.
    • itemSlot 프리팹에는 Slot클래스가 붙어있는데 이것이 슬롯이다. 이 슬롯이 가지고 있는 아이디에 번호를 붙여주고 슬롯리스트에도 넣어준다.
    • 만들어준 슬롯 프리팹(slotObjList[i])을 패널에 붙여준다.
    • 정리하면, 슬롯 프리팹을 slotObjList에 넣어주고, 프리팹 안에 있는 slot에 아이디 부여, slot만 따로 리스트에 또 추가해준 것


📝MAIN_FUNCTIONS

✏️AddItem

 public void AddItem(int id) {
        Item itemToAdd = ItemManager.Inst.GetItemById(id);      // 아이디로 이 아이템이 콜라인지, 펭귄인지 뭔지 알아온다

        // 아이템이 인벤토리에 있고, 쌓을 수 있는 경우
        if (itemToAdd.isStackable && CheckIfItemIsInInventory(itemToAdd)) {   
            for (int i = 0; i < slotList.Count; i++) {          // 슬롯 개수만큼 돌면서 해당 아이템을 찾는다
                if (itemToAdd.Id == slotList[i].item.Id) {      // 찾을 땐 슬롯리스트의 아이디 이용
                    ItemUIObject data = slotObjList[i].transform.GetChild(0).GetComponent<ItemUIObject>();  // 해당 슬롯UI를 가져옴
                    data.Amount += itemToAdd.value;         // 개수를 나타내는 amount를 value만큼 증가시켜줌
                    break;
                }
            }
        }

        // 아이템이 인벤토리에 없는 경우
        else {
            for (int i = 0; i < slotList.Count; i++) {
                if(slotList[i].item == null) {      // 비어있는 슬롯을 찾는다
                    var itemUI = Instantiate(Resources.Load<ItemUIObject>("Prefabs/ItemUIObject"), slotList[i].transform);      // 슬롯 프리팹을 가져와 붙인다
                    itemUI.Amount = itemToAdd.value;            // 이때 프리팹의 변수를 초기화 시켜준다. 개수는 value만큼
                    itemUI.itemImage.sprite = itemToAdd.sprite; // 이미지는 아이템 이미지 가져오고
                    itemUI.item = itemToAdd;                    
                    itemUI.slotId = i;                         
                    slotList[i].item = itemToAdd;               // 슬롯 리스트에 아이템도 할당
                    itemList.Add(itemToAdd);                    // 인벤토리에 들어있는 아이템을 담은 리스트에도 추가
                    break;
                } 
            }
        }
    }


✏️CheckIfItemIsInInventory

  • 인벤토리에 아이템이 있는지 검사하는 함수
    public bool CheckIfItemIsInInventory(Item item) {
        for (int i = 0; i < itemList.Count; i++) {      // 인벤토리에 들어있는 아이템을 담은 리스트를 검사한다
            if (item.Id == itemList[i].Id) {            // 아이디로 아이템 검사하는데, 아이디 일치 즉 아이템 있으면 true 리턴
                return true;                            
            }
        }
        return false;
    }


😃결과

fail to bring 임시 캐릭터지만 귀여운 북극곰


fail to bring 같이 일하는 상사같은 오빠가 만든 인벤토리


fail to bring Tab을 누르면 주변의 아이템을 먹을 수 있다. 임시로 생선과 펭귄을 아이템으로 설정해놓았다. 펭귄은 한번 먹을 때 2개씩 자원이 쌓이고, 생선은 하나씩 쌓이도록 값(item.value)을 설정했다.


fail to bring 이제 오브젝트 풀링으로 아이템을 생성하고 쓰고 없애는 것으로 다시 바꿔볼 예정이다