Chapter 5-3. 인벤토리 : 인벤토리 드래그 앤 드롭, UnityEngine.EventSystems

Date:     Updated:

카테고리:

태그:

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

Chapter 5. 인벤토리


🚖 인벤토리에서 우클하면 장착 및 소비

  • 슬롯에 있는 아이템 우클
    • 무기라면 해당 무기로 장착
    • 그냥 아이템이라면 소비

마우스 클릭 이벤트

using UnityEngine.EventSystems;
public class Slot : MonoBehaviour, IPointerClickHandler
{
    // 오버라이딩 하기
}
  • IPointerClickHandler 인터페이스
    • 이 인터페이스를 상속 받은 스크립트라면 마우스 클릭 이벤트를 받을 수 있다.
    • 마우스 클릭 될 때 발생하는 이벤트 함수를 오버라이딩 해야 한다.
      • OnPointerClick(PointerEventData eventData)
        • 이 스크립트가 붙은 오브젝트에 마우스 클릭 이벤트 발생시 호출
  • 인터페이스 이름 - 우클 - 빠른 작업 및 리팩터링
    • 오버라이딩 해야 할 함수들의 자동으로 써주어서 편하다.
  • EventSystem
    • 이벤트에 대한 Raycast는 Canvas의 Graphic Raycaster 컴포넌트에서 쏴주고
    • 어떤 종류의 이벤트인지에 대한 답을 Raycast 로 쏴주는건 EventSystem 오브젝트이다.
    • 이 이벤트에 대한 Raycast를 받기 위해선 UI들은 Raycast Target 가 체크 되어 있어야 하며 일반 오브젝트들은 Collider 가 붙어 있어야 한다.


📜Slot.cs

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

public class Slot : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
{
    // ... 

    private WeaponManager theWeaponManager;

    void Start()
    {
        theWeaponManager = FindObjectOfType<WeaponManager>();
    }

    // ... 

    public void OnPointerClick(PointerEventData eventData)
    {
        if(eventData.button == PointerEventData.InputButton.Right)
        {
            if (item != null)
            {
                if(item.itemType == Item.ItemType.Equipment)
                {
                    // 장착
                    StartCoroutine(theWeaponManager.ChangeWeaponCoroutine(item.weaponType, item.itemName));
                }
                else
                {
                    // 소비
                    Debug.Log(item.itemName + " 을 사용했습니다.");
                    SetSlotCount(-1);
                }
            }
        }
    }
}
  • theWeaponManager을 [SerializeField]를 사용하여 직접 할당하지 않고 FindObjectOfType 로 할당 해준 이유
    • 게임 도중 Instantiate 될 예정인 🟦프리팹들은 [SerializeField] 인 것들은 자기 자신 안에 있는 객체(프리팹)들만 참조 가능하기 때문에 어차피 게임이 시작되면 None이 되버려서 그렇다.
    • 이 스크립트 📜Slot.cs가 붙는 대상은 🟦프리팹인 Slot 들이다.
      • 게임 시작전에 배치 되는 오브젝트가 아니라 인벤토리를 활성화 해야만 생성되는 애들이기 때문에 게임 시작 전에 [SerializeField]인 멤버에 다른 타 오브젝트를 할당하면 어차피 게임 시작 되더라도 찾을 수 없다고 None이 되어버린다.
    • 아직 에셋으로만 존재하고 오브젝트화 되지 않은 프리팹에선 Hierarchy에 있는 오브젝트들을 serializefiled 슬롯에 넣을 수 없다. (넣을 순 있긴 하지만 게임 시작되면 None이 되어버린다.)
    • Hierarchy에 있는 것들을 serializefield에 넣어 봤자 소용 없다. 프리팹이니까.
    • 물론 이미 Hierarchy에 꺼내놓은 프리팹 즉 이미 오브젝트화 되있는건 상관 X Instantiate으로 생성할 경우의 얘기
  • 따라서 위와 같이 게임이 시작되면 theWeaponManagerFindObjectOfType 로 찾아 📜WeaponManager.cs 를 가진 오브젝트를 할당해주는 것이다.

게임 시작부터 오브젝트로 존재하는 프리팹이라면 👉 외부 오브젝트를 [SerializeField] 로 프리팹 오브젝트에 드래그 앤 드롭 할당 가능.

Instantiate 으로 게임 도중에 생성하는 프리팹이라면 [SerializeField] 로 선언된 변수에 직접 외부 오브젝트를 드래그 앤 드롭 해주는게 의미가 없다. 게임 시작하자마자 None이 되버림. 이런 경우엔 FindObjectOfType 밖에 답이 없음.


📜Slot.cs 가 붙어 있는 오브젝트에 (Slot에) 마우스 클릭 이벤트가 발생하면 OnPointerClick 이벤트 함수가 자동 실행된다.

  • PointerEventData
    • 마우스 혹은 터치 입력 이벤트에 관한 정보들이 담겨 있다. 이벤트가 들어온 버튼, 클릭 수, 마우스 위치, 현재 마우스 움직이고 있는지 여부 등등 여러가지를 담고 있다
  • 해당 슬롯에 마우스 우클릭 이벤트가 들어 오면
    if(eventData.button == PointerEventData.InputButton.Right)
    
    • 해당 아이템이 무기라면
      • 👉 장착한다.
        • 📜WeaponManager.cs 의 ChangeWeaponCoroutine 함수 실행
    • 해당 아이템이 무기가 아니라면
      • 👉 소비한다.
        • 해당 슬롯의 아이템 갯수 1 감소 시키기 SetSlotCount(-1)
          • 알아서 이 함수 안에서 슬롯의 아이템 개수가 0 개 이하면 해당 슬롯을 클리어 시키는 ClearSlot() 함수도 호출 한다.


🚖 인벤토리 드래그

인벤토리의 슬롯에 있는 아이템을 드래그 앤 드롭 하면 슬롯에 든 아이템의 위치를 다른 슬롯으로 바꿀 수 있게 한다. 이미 다른 아이템이 있는 위치에 드래그 앤 드롭 하면 두 아이템 위치를 맞바꾼다.


DragSlot

image

슬롯을 드래그 할 때 고려해야 할 점

  1. 위 사진과 같이 드래그 하려는 슬롯이 다른 슬롯들 보다 뒤에 가려진다. 보기 안좋음!
    • 이유 👉 UI 요소들의 렌더링 순서는 Hierarchy 상에서 위에 있는 UI 부터 렌더링 되어서 그렇다. 첫번째 슬롯은 Hierarchy 상에서 슬롯들 중 가장 위에 있기 때문에 가장 먼저 그려진다. 따라서 위와 같이 첫 번째 슬롯을 드래그 하면, 제일 먼저 그려지기 때문에 다른 슬롯들 보다 뒤에서 그려지는 것이다.
  2. 드래그 한 슬롯의 원래 자리는 비워진다. 보기 안좋음!
    • 드래그 한 슬롯은 다른 공간에 복사될 필요가 있다.
    • 원래 슬롯도 그대로 그려지고 사본 슬롯이 드래그 되도록.

image

  • 위와 같이 1️⃣ 뒤에 가려지지도 않고 2️⃣ 원래 자리가 비워지지 않도록 하려면
    • 1️⃣ 다른 슬롯들보다 먼저 그려져야 한다. Hierarchy 상에서 위에 위치 하도록 해야 한다.
    • 2️⃣ 드래그 된 슬롯은 사본으로 만들어야 한다.

image

  • DragSlot 이라는 이름의 이미지 UI 를 Inventory_Base 자식으로 추가한다.
    • 1️⃣ Grid Setting 보다 아래에 위치하도록 한다.
      • 다른 슬롯들 보다 나중에 그려져 뒤에 묻히지 않도록
    • 2️⃣ 드래그 된 슬롯은 이 DragSlot에서 사본으로서 참조 되도록 한다.
  • 이 또한 다른 SlotItem_Image들과 마찬가지로 투명도 0 값을 기본으로 해둔다.
    • 드래그 할 때만 보여야 하는 이미지 이므로 (슬롯의 사본으로서)
  • 사이즈를 슬롯과 동일하게 해 준다.


마우스 드래그 이벤트

using UnityEngine;
using UnityEngine.EventSystems;
public class Test : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public void OnBeginDrag(PointerEventData eventData)
    {
        // 오버라이딩 하기
    }

    public void OnDrag(PointerEventData eventData)
    {
        // 오버라이딩 하기
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        // 오버라이딩 하기
    }
}
  • IBeginDragHandler 인터페이스
    • OnBeginDrag(PointerEventData eventData)
      • 이 스크립트가 붙은 오브젝트를 마우스 드래그를 시작 했을 때 호출
  • IDragHandler 인터페이스
    • OnDrag(PointerEventData eventData)
      • 이 스크립트가 붙은 오브젝트를 마우스 드래그 중인 동안 계속 호출
  • IEndDragHandler 인터페이스
    • OnEndDrag(PointerEventData eventData)
      • 이 스크립트가 붙은 오브젝트를 마우스 드래그 하는 것을 끝냈을 때 호출


📜DragSlot.cs

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

public class DragSlot : MonoBehaviour
{
    static public DragSlot instance;
    public Slot dragSlot;

    [SerializeField]
    private Image imageItem;

    void Start()
    {
        instance = this;
    }

    public void DragSetImage(Image _itemImage)
    {
        imageItem.sprite = _itemImage.sprite;
        SetColor(1);
    }

    public void SetColor(float _alpha)
    {
        Color color = imageItem.color;
        color.a = _alpha;
        imageItem.color = color;
    }
}

  • instance
    • DragSlot UI 이미지 오브젝트를 담는다. 즉, 자기 자신을 담는다.
    • static이므로 한번 자기 자신을 담은 후로는 게임 내내 메모리에 자기 자신이 유지된다.
      • 동일한 자기 자신에 대해 여러 곳에서 공유할 수 있다.
  • dragSlot
    • 드래그 대상이 되는 Slot을 참조
      • 드래그 대상이 되는 Slot의 Sprite이미지가 복사되어 들어갈 것
  • imageItem
    • 자기 자신의 이미지 컴포넌트
  • Start()
    • 게임 시작 되면 instance에 자기 자신 할당
  • DragSetImage(Image _itemImage)
    • 드래그 되는 슬롯의 이미지가 들어옴.
    • 자기 자신의 이미지에 인수로 들어온 Sprite 이미지 할당
      • 투명도를 다시 1 로 불투명하게
  • SetColor(float _alpha)
    • 본인의 이미지 컴포넌트 imageItem의 이미지 투명도

image

  • dragSlot 는 게임 도중 드래그가 발생할 때 할당될 것이다.
  • imageItemdragSlot 본인의 이미지 컴포넌트.
    • 드래그 대상이 되는 Slot의 Sprite 이미지가 소스 이미지에 할당 될 곳.


📜Slot.cs

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

public class Slot : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
{
    //...

    // 마우스 드래그가 시작 됐을 때 발생하는 이벤트
    public void OnBeginDrag(PointerEventData eventData)
    {
        if(item != null)
        {
            DragSlot.instance.dragSlot = this;
            DragSlot.instance.DragSetImage(itemImage);
            DragSlot.instance.transform.position = eventData.position;
        }
    }

    // 마우스 드래그 중일 때 계속 발생하는 이벤트
    public void OnDrag(PointerEventData eventData)
    {
        if (item != null)
            DragSlot.instance.transform.position = eventData.position;
    }

    // 마우스 드래그가 끝났을 때 발생하는 이벤트
    public void OnEndDrag(PointerEventData eventData)
    {
        DragSlot.instance.SetColor(0);
        DragSlot.instance.dragSlot = null;
    }
}
  • OnBeginDrag(PointerEventData eventData)
    • 드래그 되는 대상인, 드래그가 시작 된 슬롯에서 호출
    • 아이템이 있는 슬롯이라면
      • 드래그 슬롯에 자기 자신 할당.
        • dragSlot에서 자기 자신을 참조하게 된다.
      • 드래그 슬롯의 이미지를 자신의 아이템 이미지로
      • 드래그 슬롯의 위치를 드래그가 발생한 위치로
  • OnDrag(PointerEventData eventData)
    • 드래그 되는 대상인, 드래그 중인 슬롯에서 계속 호출
    • 아이템이 있는 슬롯이라면
      • 드래그 슬롯의 위치를 드래그가 발생한 위치로
        • 드래그 슬롯이 마우스 드래그 위치를 따라 움직임
  • OnEndDrag(PointerEventData eventData)
    • 드래그가 끝났을 때 드래그 되는 대상인 슬롯에서 호출
      • 드래그 슬롯 다시 초기화
        • 투명하게 만들고
        • 참조 중이던 슬롯과의 연결 끊기


🚖 인벤토리 드롭

📜Slot.cs

    // 해당 슬롯에 무언가가 마우스 드롭 됐을 때 발생하는 이벤트
    public void OnDrop(PointerEventData eventData)
    {
        if (DragSlot.instance.dragSlot != null)
            ChangeSlot();
    }
    
    private void ChangeSlot()
    {
        Item _tempItem = item;
        int _tempItemCount = itemCount;

        AddItem(DragSlot.instance.dragSlot.item, DragSlot.instance.dragSlot.itemCount);

        if (_tempItem != null)
            DragSlot.instance.dragSlot.AddItem(_tempItem, _tempItemCount);
        else
            DragSlot.instance.dragSlot.ClearSlot();
    }
  • OnEndDragOnDrop 의 차이
    • OnEndDrag 👉 나 자신을 드래그 하는 것을 끝냈을 때 호출
      • 내가 드래그 되는 것이 끝났을 때!
      • 드래그가 종료했을 때, 드래그 대상이 되었던 오브젝트에서 호출 됨.
    • OnDrop 👉 누군지 모르겠지만 내 자신한테 드롭 된 무언가가 있을 때 호출.
      • 나한테 누가 드롭 되었을 때!
      • 드래그를 멈춘 위치에 있는 오브젝트에서 호출 됨.
    • 드롭 된 다른 곳의 OnDrop 이, 드래그를 끝낸 내 OnEndDrag 보다 먼저 실행된다.
  • OnDrop(PointerEventData eventData)
    • 드래그 대상이 아니라, 드롭 위치 대상이 된 슬롯에서 호출된다.
    • 현재 드래그 슬롯이 null 이 아니라면 드래그 되고 있는 슬롯이 자신에게 드롭되었다는 뜻이므로 ChangeSlot()을 통하여 슬롯을 서로 바꾼다.
      • 드롭 위치 대상이 된 슬롯이 비어있든 아이템이 있든 그건 상관 없다.
  • ChangeSlot()
    • A 슬롯을 드래그 하여 B 슬롯에 드롭하여, A 슬롯 B 슬롯 서로 자리를 바꾸려면
      • B 슬롯을 복사하여 다른 곳에 일단 보관 _tempItem, _tempItemCount
      • B 슬롯 자리에 A 슬롯 추가
        • A 슬롯은 dragSlot에서 참조 중. ChangeSlot()이 호출되는 즉, 드롭 위치가 된 슬롯에다가 A 슬롯을 추가한다.
          AddItem(DragSlot.instance.dragSlot.item, DragSlot.instance.dragSlot.itemCount);
          
      • A 슬롯 자리에 임시 보관해 놨던 B 슬롯 추가
        • 드롭 위치 대상인 B 슬롯이 빈 슬롯이 아니라면
          • A 슬롯 자리에 B 슬롯을 추가한다.
            DragSlot.instance.dragSlot.AddItem(_tempItem, _tempItemCount)
            
        • B 슬롯이 빈 슬롯이였다면
          • A 슬롯을 비워주기만 하면 된다.
            DragSlot.instance.dragSlot.ClearSlot();
            


마우스 충돌 가리지 않게

위까지 해도, 슬롯을 아무리 드래그 해도 슬롯 교체가 제대로 이루어지지 않는다. 그 이유는 드롭 되는 위치의 대상이 슬롯의 OnDrop 가 제대로 호출되지 않았기 때문이다. ⭐이미지를 드래그 하여 옮기는 과정에서 DropSlot 이미지가 마우스 이벤트 Raycast를 대신 받아 버리기 때문에, DropSlot 뒤에 있는 드롭 되는 위치의 대상이 되는 Slot 이미지가 마우스 이벤트를 받지 못하여 OnDrop 가 제대로 호출되지 않았던 것이다.

image

체크 된 마우스를 놓고 드롭 해주어야만, 즉 직접 드롭 위치가 되는 Slot에다가 직접 마우스를 대고 드롭해주어야만이 그 슬롯의 OnDrop이 호출 될 수 있었다. 근데 사실상 DropSlot를 드래그 해오기 때문에 DropSlot가 가려버리기 쉽상..

image

  • DropSlotRaycast Target을 해제 해주어 Raycast 를 못 받게 하면 해결 된다.
    • 어차피 마우스 이벤트는 드래그 시작 대상이 됐었던 슬롯과 드롭 위치 대상이 됐었던 슬롯에서 일어나기 때문에 DropSlot는 이벤트 못 받아도 괜찮다.
    • 이게 체크 되있으면 DropSlot이 드래그 과정에서 드롭 위치 대상이 되는 슬롯의 앞을 가려 OnDrop 호출, 즉 마우스의 충돌을 방해한다.
      • 뒤에 있는 드롭 위치 대상이 되는 슬롯에 마우스가 직접적으로 닿아야 OnDrop 호출이 이루어 지는데 DropSlot이 충돌을 대신 앞에서 받아버리기 때문이다.
  • 체크 해제해주면 DropSlot는 이벤트 Raycast를 받지 못해 그냥 관통되고 DropSlot가 가리고 있더라도 드롭 위치 대상이 되는 슬롯까지 Raycast가 잘 도달 하여 OnDrop가 잘 호출 된다.


🚖 인벤토리 활성화 되면 공격 카메라 회전 등등 비활성화

📜PlyaerController.cs

    void Update()  
    {
        IsGround();
        TryJump();
        TryRun();
        TryCrouch();
        Move();
        MoveCheck();
        if(!Inventory.invectoryActivated)
        {
            CameraRotation();
            CharacterRotation();
        }
    }

인벤토리가 활성화 되면 마우스 움직임에 따른 카메라 회전, 캐릭터 회전 제한.


📜WeaponSway.cs

    void Update()
    {
        if (!Inventory.invectoryActivated)
            TrySway();
    }

인벤토리가 활성화 되면 마우스 움직임에 따른 무기 흔들림 제한.


📜CloseWeaponController.cs

    protected void TryAttack()
    {
        if (!Inventory.invectoryActivated)
        {
            if (Input.GetButton("Fire1"))
            {
                if (!isAttack)
                {
                    if (CheckObject())
                    {
                        if (currentCloseWeapon.isAxe && hitInfo.transform.tag == "Tree")
                        {
                            StartCoroutine(thePlayerController.TreeLookCoroutine(hitInfo.transform.GetComponent<TreeComponent>().GetTreeCenterPosition()));
                            StartCoroutine(AttackCoroutine("Chop", currentCloseWeapon.workDelayA, currentCloseWeapon.workDelayB, currentCloseWeapon.workDelay));
                            return;
                        }
                    }

                    StartCoroutine(AttackCoroutine("Attack", currentCloseWeapon.attackDelayA, currentCloseWeapon.attackDelayB, currentCloseWeapon.attackDelay));
                }
            }
        }
    }

인벤토리가 활성화 되면 근접 무기 좌클릭 제한.


📜GunController.cs

    void Update()
    {
        if (isActivate)
        {
            GunFireRateCalc();
            if (!Inventory.invectoryActivated)
            {
                TryFire();
                TryReload();
                TryFineSight();
            }
        }
    }

인벤토리가 활성화 되면 총 발사, 재장전, 정조준 제한.


📜WeaponManager.cs

    void Update()
    {
        if(!Inventory.invectoryActivated)
        {
            if (!isChangeWeapon)
            {
                if (Input.GetKeyDown(KeyCode.Alpha1)) // 1 누르면 '맨손'으로 무기 교체 실행
                    StartCoroutine(ChangeWeaponCoroutine("HAND", "맨손"));
                else if (Input.GetKeyDown(KeyCode.Alpha2)) // 2 누르면 '서브 머신건'으로 무기 교체 실행
                    StartCoroutine(ChangeWeaponCoroutine("GUN", "SubMachineGun1"));
                else if (Input.GetKeyDown(KeyCode.Alpha3)) // 3 누르면 '도끼'로 무기 교체 실행
                    StartCoroutine(ChangeWeaponCoroutine("AXE", "Axe"));
                else if (Input.GetKeyDown(KeyCode.Alpha4)) // 4 누르면 '곡괭이'로 무기 교체 실행
                    StartCoroutine(ChangeWeaponCoroutine("PICKAXE", "Pickaxe"));
            }
        }
    }

인벤토리가 활성화 되면 무기 교체 제한.


***

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

맨 위로 이동하기

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

댓글 남기기