Chapter 5-4. 인벤토리 : 아이템 버리기, Input Field

Date:     Updated:

카테고리:

태그:

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

Chapter 5. 인벤토리


🚖 인벤토리 영역을 벗어난 곳에 드롭하면 아이템이 버려지도록

  • 아이템 버리려면
    • 버리려는 아이템이 있는 슬롯을 인벤토리 바깥 영역으로 드래그 앤 드롭
    • 바깥 영역으로 드롭 하면
      • 아이템 몇 개를 버릴건지 숫자를 작성할 수 있는 Input Field 를 띄운다.
    • 갯수를 입력하면
      • 아이템 오브젝트들이 버려진 갯수 만큼 플레이어의 앞에서 생성된다.
      • 해당 슬롯의 아이템 갯수는 버려진 갯수만큼 차감된다.

📜Slot.cs

    // 마우스 드래그가 끝났을 때 발생하는 이벤트
    private Rect baseRect;  // Inventory_Base 이미지의 Rect 정보 받아 옴.

    void Start()
    {
        baseRect = transform.parent.parent.GetComponent<RectTransform>().rect;
        theWeaponManager = FindObjectOfType<WeaponManager>();
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        if (DragSlot.instance.transform.localPosition.x < baseRect.xMin 
            || DragSlot.instance.transform.localPosition.x > baseRect.xMax
            || DragSlot.instance.transform.localPosition.y < baseRect.yMin
            || DragSlot.instance.transform.localPosition.y > baseRect.yMax)
        {
            Instantiate(DragSlot.instance.dragSlot.item.itemPrefab, 
                theWeaponManager.transform.position + theWeaponManager.transform.forward,
                Quaternion.identity);
            DragSlot.instance.dragSlot.ClearSlot();

        }
        DragSlot.instance.SetColor(0);
        DragSlot.instance.dragSlot = null;
    }

슬롯이 인벤토리 영역이 아닌 곳에 드롭 됐다면 아이템을 버리는 것으로 간주하여 처리해야 한다.

  • 📜Slot.cs이 붙는 Slot은 프리팹이며 게임 플레이 중에 생성되므로 [SerializeField]를 사용하지 않고 FIndObjectOfType을 통해 baseRect를 초기화 해주었다.
    • RectTransform 에서 Rect 가져옴.
  • OnEndDrag(PointerEventData eventData)
    • 드래그가 끝나면, 마우스 드래그 대상이 되었던 슬롯에서 호출
    • 드래그가 끝난 위치가 인벤토리 베이스 이미지를 벗어난 곳이라면 아이템 버리는 처리를 해야 함

Rect

image

  • RectTransform 은 Rect 를 가진다.
  • 인벤토리 베이스 이미지의 2 D 사각형 정보를 Rect로 가져 온다.
    • 2 D 사각형으로서의 넓이, 높이 등등의 정보가 담겨 있다.
    • Rect에서는 왼쪽 상단을 x, y 시작점으로 본다.
  • 다음과 같은 조건일 때, 아이템을 버리고자 한다고 본다.
    • 드래그가 끝났을 때의 슬롯의 위치의 x 좌표가 인벤토리 베이스 이미지의 왼쪽 상단 (xMin)을 더 왼쪽으로 벗어났을 때
    • OR 드래그가 끝났을 때의 슬롯의 위치의 x 좌표가 인벤토리 베이스 이미지의 오른쪽 상단 (xMax)을 더 오른쪽으로 벗어났을 때
    • OR 드래그가 끝났을 때의 슬롯의 위치의 y 좌표가 인벤토리 베이스 이미지의 왼쪽 상단 (yMin)을 위쪽으로 벗어났을 때
    • OR 드래그가 끝났을 때의 슬롯의 위치의 y 좌표가 인벤토리 베이스 이미지의 왼쪽 하단 (yMax)을 더 아래쪽으로 벗어났을 때
  • xMin은 앵커(원점)을 기준으로 width/2 만큼 왼쪽으로 떨어진 곳의 x 좌표라고 할 수 있겠다.
    • 원점이 중앙이라면 xMin은 음수가 된다.
  • xMax은 앵커(원점)을 기준으로 width/2 만큼 오른쪽으로 떨어진 곳의 x 좌표라고 할 수 있겠다.
  • yMin은 앵커(원점)을 기준으로 height/2 만큼 위쪽으로 떨어진 곳의 y 좌표라고 할 수 있겠다.
    • 원점이 중앙이라면 yMin은 음수가 된다.
  • yMax은 앵커(원점)을 기준으로 height/2 만큼 아래쪽으로 떨어진 곳의 x 좌표라고 할 수 있겠다.


🚖 아이템 버릴 때 버릴 갯수 입력 필드

InputField UI 만들기

image

image

  • InputNumber 빈 오브젝트. 📜InputNumber.cs 붙일 예정
    • InputField 👉 Input Field UI
      • 아이템을 버리고자 할 때만 활성화 되기 위해 비활성화를 디폴트로.
      • Placeholder
        • “Enter text…” 처럼 입력하기 전에 미리 필드에 쓰여지며, 실제 입력을 시작하면 없어지는 텍스트.
      • Text
        • 필드에 실제로 사용자가 입력한 Text가 들어감
        • 버리고자 하는 아이템의 갯수를 입력
    • OK Button 👉 Button UI
      • 갯수 입력 후 OK 버튼 누르면 아이템 버리는 실제 처리가 이루어지도록.
    • Cancel Button 👉 Cancel UI
      • X 버튼
      • 입력 필드 닫음


📜InputNumber.cs

InputNumber에 붙인다.

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

public class InputNumber : MonoBehaviour
{
    private bool activated = false;

    [SerializeField]
    private Text text_Preview;  
    [SerializeField]
    private Text text_Input;  
    [SerializeField]
    private InputField if_text;

    [SerializeField]
    private GameObject go_Base;

    [SerializeField]
    private ActionController thePlayer;

    void Update()
    {
        if (activated)
        {
            if (Input.GetKeyDown(KeyCode.Return))
                OK();
            else if (Input.GetKeyDown(KeyCode.Escape))
                Cancel();
        }
    }

    public void Call()
    {
        go_Base.SetActive(true);
        activated = true;
        if_text.text = "";
        text_Preview.text = DragSlot.instance.dragSlot.itemCount.ToString();
    }

    public void Cancel()
    {
        activated = false;
        DragSlot.instance.SetColor(0);
        go_Base.SetActive(false);
        DragSlot.instance.dragSlot = null;
    }

    public void OK()
    {
        DragSlot.instance.SetColor(0);

        int num;
        if (text_Input.text != "")
        {
            if (CheckNumber(text_Input.text))
            {
                num = int.Parse(text_Input.text);
                if (num > DragSlot.instance.dragSlot.itemCount)
                    num = DragSlot.instance.dragSlot.itemCount;
            }
            else
                num = 1;
        }
        else
            num = int.Parse(text_Preview.text);

        StartCoroutine(DropItemCorountine(num));
    }

    IEnumerator DropItemCorountine(int _num)
    {
        for (int i = 0; i < _num; i++)
        {
            Instantiate(DragSlot.instance.dragSlot.item.itemPrefab,
                thePlayer.transform.position + thePlayer.transform.forward,
                Quaternion.identity);
            DragSlot.instance.dragSlot.SetSlotCount(-1);
            yield return new WaitForSeconds(0.05f);
        }
        
        DragSlot.instance.dragSlot = null;
        go_Base.SetActive(false);
        activated = false;
    }

    private bool CheckNumber(string _argString)
    {
        char[] _tempCharArray = _argString.ToCharArray();
        bool isNumber = true;

        for (int i = 0; i < _tempCharArray.Length; i++)
        {
            if (_tempCharArray[i] >= 48 && _tempCharArray[i] <= 57)
                continue;
            isNumber = false;
        }
        return isNumber;
    }
}
  • activated : 입력 필드가 활성화 되있을 때 True, 비활성화 상태일 때 False
  • text_Preview : Placeholder 의 텍스트. 드롭된 아이템의 갯수가 표시되도록 할 것이다.
    • 👉 사용자가 별다른 입력을 하지 않으면 해당 슬롯에 있던 아이템 갯수만큼 전부 다 버려지도록.
  • text_Input : 사용자가 실제로 입력한 문자열이 들어갈 텍스트.
  • if_text : Input Field 의 텍스트를 참조하기 위해 Input Field 타입으로 선언.
    • 입력 필드가 활성화 되면 기존에 사용자가 썼었던 텍스트는 지우고 공백으로 초기화 해야 한다.
    • text_Input를 “” 공백으로 만드는 방법도 있지만 입력 필드 컴포넌트의 text 속성을 자식인 Text 에게 덮어 씌우는 방식이기 때문에, Text인 text_Input를 “” 공백으로 바꿔도 입력 필드 컴포넌트의 text 속성에는 사용자가 이 전에 입력했던 값이 남아 있으므로 여전히 사용자가 이 전에 입력했던 값이 Text 에게 덮어 씌워져 불러와지게 된다.
    • 따라서 텍스트 초기화를 입력 필드의 자식인 Text에서 하지 않고 입력 필드 컴포넌트의 text 속성 값을 초기화 하기 위해 Input Field 타입으로 불러 온 것이다.

image

  • go_Base : Input Field 입력 필드 UI 오브젝트가 할당 될 것. 입력 필드 활성화/비활성화를 위해 GameObject로 불러 옴.
  • thePlayer : 카메라의 앞에서 버린 아이템을 생성하기 위해 📜ActionController.cs 참조


Call() 입력 필드 불러오기

    public void Call()
    {
        go_Base.SetActive(true);
        activated = true;
        if_text.text = "";
        text_Preview.text = DragSlot.instance.dragSlot.itemCount.ToString();
    }

이 함수는 슬롯의 드래그가 끝날 때, 즉 📜Slot.cs 의 OnEndDrag 에서 호출될 것이다.

  • 입력 필드 활성화
  • activate True 설정
    • 입력 필드 활성화 먼저 해주고난 후에 해주어야 런타임 에러 안남
    • Update 함수에서 activate 값이 사용하여 입력 필드 조작을 할거라서
  • 입력 필드의 텍스트 공백으로 초기화
    • 이전에 한번 버린적 있다면 입력했던게 남아 있을테니
  • Placeholder의 텍스트를 드래그 된 슬롯의 아이템 갯수로 한다.
    • 별다른 입력이 없다면 해당 슬롯의 아이템을 모두 버리도록 하기 위해


OK() 입력한 갯수만큼 아이템 버리는 실제 처리를 시작


    public void OK()
    {
        DragSlot.instance.SetColor(0);

        int num;
        if (text_Input.text != "")
        {
            if (CheckNumber(text_Input.text))
            {
                num = int.Parse(text_Input.text);
                if (num > DragSlot.instance.dragSlot.itemCount)
                    num = DragSlot.instance.dragSlot.itemCount;
            }
            else
                num = 1;
        }
        else
            num = int.Parse(text_Preview.text);

        StartCoroutine(DropItemCorountine(num));
    }

    IEnumerator DropItemCorountine(int _num)
    {
        for (int i = 0; i < _num; i++)
        {
            Instantiate(DragSlot.instance.dragSlot.item.itemPrefab,
                thePlayer.transform.position + thePlayer.transform.forward,
                Quaternion.identity);
            DragSlot.instance.dragSlot.SetSlotCount(-1);
            yield return new WaitForSeconds(0.05f);
        }
        
        DragSlot.instance.dragSlot = null;
        go_Base.SetActive(false);
        activated = false;
    }

    private bool CheckNumber(string _argString)
    {
        char[] _tempCharArray = _argString.ToCharArray();
        bool isNumber = true;

        for (int i = 0; i < _tempCharArray.Length; i++)
        {
            if (_tempCharArray[i] >= 48 && _tempCharArray[i] <= 57)
                continue;
            isNumber = false;
        }
        return isNumber;
    }

    void Update()
    {
        if (activated)
        {
            if (Input.GetKeyDown(KeyCode.Return))
                OK();
            else if (Input.GetKeyDown(KeyCode.Escape))
                Cancel();
        }
    }
    

OK Button의 이벤트 함수에 📜InputNumber.cs의 public void OK() 함수 등록

image

  • OK() 👉 OK 버튼 누르면 활성화
    • 사용자가 입력한 텍스트가 있다면
      • 전부 숫자라면
        • 그대로 그 입력한 수만큼 버리도록 한다.
        • 단, 드래그 된 슬롯의 아이템 갯수를 초과한다면 드래그 된 슬롯의 아이템 갯수만큼 버리도록 한다.
      • 숫자 문자 섞여있거나 문자라면
        • 1 개만 버리도록 한다.
    • 사용자가 입력한 텍스트가 없다면
      • 별다른 입력이 없다면 해당 슬롯의 아이템을 모두 버리도록 드래그 된 슬롯의 아이템 갯수로 했었던 Placeholder의 텍스트만큼 버린다.
    • 실제로 아이템을 입력한 갯수만큼 버리는 DropItemCorountine(int _num) 함수를 실행한다.
  • CheckNumber(string _argString) 👉 텍스트에 문자가 섞여있다면 false, 전부 숫자라면 true 리턴
    • 인수로 받은 텍스트를 char 타입의 문자열 배열로 변환하고 한글자 한글자 검사한다.
      • 아스키 코드가 47 ~ 57 사이의 문자면 숫자라는 의미
      • 아니라면 문자라는 의미
  • DropItemCorountine(int _num)
    • 입력한 아이템 갯수만큼
      • 아이템 프리팹을 실제로 생성한다. 카메라보다 조금 앞에.
        thePlayer.transform.position + thePlayer.transform.forward
        
      • 드래그 된 슬롯의 아이템 갯수를 1 만큼씩 줄인다. (입력한 아이템 갯수만큼 반복)
      • 0.05 초씩 대기
    • 드래그 된 슬롯 참조 초기화
    • 입력 필드 비활성화
    • activate False 설정


Cancel() 입력 필드 닫기

    void Update()
    {
        if (activated)
        {
            if (Input.GetKeyDown(KeyCode.Return))
                OK();
            else if (Input.GetKeyDown(KeyCode.Escape))
                Cancel();
        }
    }

    public void Cancel()
    {
        activated = false;
        DragSlot.instance.SetColor(0);
        go_Base.SetActive(false);
        DragSlot.instance.dragSlot = null;
    }

Cancel Button의 이벤트 함수에 📜InputNumber.cs의 public void Candel() 함수 등록

image

  • activate False 설정
  • 드래그 했던 슬롯의 색상을 다시 투명하게
  • 입력 필드 비활성화
  • 드래그 했던 슬롯을 참조하던 dragSlot 초기화

image


📜Slot.cs

    private InputNumber theInputNumber;

    void Start()
    {
        baseRect = transform.parent.parent.GetComponent<RectTransform>().rect;
        theWeaponManager = FindObjectOfType<WeaponManager>();
        theInputNumber = FindObjectOfType<InputNumber>();
    }

    // 마우스 드래그가 끝났을 때 발생하는 이벤트
    public void OnEndDrag(PointerEventData eventData)
    {
        if (DragSlot.instance.transform.localPosition.x < baseRect.xMin 
            || DragSlot.instance.transform.localPosition.x > baseRect.xMax
            || DragSlot.instance.transform.localPosition.y < baseRect.yMin
            || DragSlot.instance.transform.localPosition.y > baseRect.yMax)
        {
            if (DragSlot.instance.dragSlot != null)
                theInputNumber.Call();
        }
        else
        {
            DragSlot.instance.SetColor(0);
            DragSlot.instance.dragSlot = null;
        }
    }
  • 드래그를 끝낸 DragSlot의 위치가 인벤토리 베이스 이미지를 벗어났다면
    • 또한 DragSlot에서 참조하는 슬롯이 존재 한다면
      • 인풋 필드를 활성화 하기 위해 📜InputNumber.cs 의 Call() 호출
  • 드래그를 끝낸 DragSlot의 위치가 인벤토리 베이스 이미지를 벗어나지 않았다면
    • 드롭 위치 대상이 된 슬롯에서 OnDrop 을 호출해 자리를 맞바꾸는 처리를 마친 후
    • DragSlot은 초기화 한다.
    • 이미지 불투명하게
    • 드래그 대상이 된 슬롯에 대한 참조 끊음


🚔 다른 아이템들 설정

나뭇가지, 통나무 아이템으로 등록하기

  • 이름 뒤에 Item 붙여주기
  • 태그 레이어 모두 Item
  • 📜ItemPickUp.cs 붙이기
    • Item (ScritableObject) 각각 만들고 할당

풀 설정

  • 풀을 벨 땐 따로 아이템을 생성하지 않고 자동으로 풀을 베자마자 인벤토리에 풀 아이템이 들어오도록 한다.
  • 인벤토리의 풀을 버릴 떈 버려진 아이템으로 생성되지 않고 그냥 날라가도록 한다.

image

Leaf 아이템은 풀을 베면 인벤토리에 생성되며, 딱히 아이템 오브젝트로선 존재하지 않도록 할 것이라서 itemPrefab은 None 이다.


📜Grass.cs

    [SerializeField]
    private Item item_leaf;
    [SerializeField]
    private int leafCount;
    private Inventory theInventory;

    void Start()
    {
        rigidbodys = this.transform.GetComponentsInChildren<Rigidbody>();
        boxColiders = this.transform.GetComponentsInChildren<BoxCollider>();
        theInventory = FindObjectOfType<Inventory>();  
    }

    private void Destruction()
    {
        theInventory.AcquireItem(item_leaf, leafCount);

        for (int i = 0; i < rigidbodys.Length; i++)
        {
            rigidbodys[i].useGravity = true;
            rigidbodys[i].AddExplosionForce(explosionForce, transform.position, 1f); // 제자리에서 중력받아 그냥 떨어지는게 아니라 폭발 효과를 줌 (폭발 세기, 폭발 위치, 폭발 반경)
            boxColiders[i].enabled = true;
        }

        Destroy(this.gameObject, destroyTime);
    }
  • Grass는 프리팹이므로 Grass 영역이 아닌 📜Inventory.cs 를 참조하기 위해선 [SerializeField]보단 게임 시작 후 FindObjectOfType 로 찾아주었다.
  • 풀 파괴시 Destruction()
    theInventory.AcquireItem(item_leaf, leafCount);
    
    • 풀은 파괴하자마자 자동으로 인벤토리에 들어가도록 한다.

image

  • item_leaf에는 Leaf 아이템 할당
  • leafCount 을 3 으로 하면 풀 파괴할 때마다 인벤토리에 3 개씩 들어가게 된다.


📜InputNumber.cs

    IEnumerator DropItemCorountine(int _num)
    {
        for (int i = 0; i < _num; i++)
        {
            if(DragSlot.instance.dragSlot.item.itemPrefab != null)
            {
                Instantiate(DragSlot.instance.dragSlot.item.itemPrefab,
                            thePlayer.transform.position + thePlayer.transform.forward,
                            Quaternion.identity);
            }
            DragSlot.instance.dragSlot.SetSlotCount(-1);
            yield return new WaitForSeconds(0.05f);
        }
        
        DragSlot.instance.dragSlot = null;
        go_Base.SetActive(false);
        activated = false;
    }

Leaf 아이템은 예외적으로 itemPrefab이 없기 때문에 이를 위해 런타임 오류를 방지하기 위하여 f(DragSlot.instance.dragSlot.item.itemPrefab != null) 인 경우에만 아이템 프리팹을 생성하도록 하였다.



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

맨 위로 이동하기

댓글남기기