Chapter 5-3. 인벤토리 : 인벤토리 드래그 앤 드롭, UnityEngine.EventSystems
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 3D] 실전! 생존게임 만들기 - Advanced 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click
Chapter 5. 인벤토리
🚖 인벤토리에서 우클하면 장착 및 소비
- 슬롯에 있는 아이템 우클
- 무기라면 해당 무기로 장착
- 그냥 아이템이라면 소비
마우스 클릭 이벤트
using UnityEngine.EventSystems;
public class Slot : MonoBehaviour, IPointerClickHandler
{
// 오버라이딩 하기
}
IPointerClickHandler
인터페이스- 이 인터페이스를 상속 받은 스크립트라면 마우스 클릭 이벤트를 받을 수 있다.
- 마우스 클릭 될 때 발생하는 이벤트 함수를 오버라이딩 해야 한다.
- OnPointerClick(PointerEventData eventData)
- 이 스크립트가 붙은 오브젝트에 마우스 클릭 이벤트 발생시 호출
- 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으로 생성할 경우의 얘기
- 따라서 위와 같이 게임이 시작되면
theWeaponManager
을 FindObjectOfType 로 찾아 📜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() 함수도 호출 한다.
- 해당 슬롯의 아이템 갯수 1 감소 시키기 SetSlotCount(-1)
- 👉 소비한다.
- 해당 아이템이 무기라면
🚖 인벤토리 드래그
인벤토리의 슬롯에 있는 아이템을 드래그 앤 드롭 하면 슬롯에 든 아이템의 위치를 다른 슬롯으로 바꿀 수 있게 한다. 이미 다른 아이템이 있는 위치에 드래그 앤 드롭 하면 두 아이템 위치를 맞바꾼다.
DragSlot
슬롯을 드래그 할 때 고려해야 할 점
- 위 사진과 같이 드래그 하려는 슬롯이 다른 슬롯들 보다 뒤에 가려진다. 보기 안좋음!
- 이유 👉 UI 요소들의 렌더링 순서는 Hierarchy 상에서 위에 있는 UI 부터 렌더링 되어서 그렇다. 첫번째 슬롯은 Hierarchy 상에서 슬롯들 중 가장 위에 있기 때문에 가장 먼저 그려진다. 따라서 위와 같이 첫 번째 슬롯을 드래그 하면, 제일 먼저 그려지기 때문에 다른 슬롯들 보다 뒤에서 그려지는 것이다.
- 드래그 한 슬롯의 원래 자리는 비워진다. 보기 안좋음!
- 드래그 한 슬롯은 다른 공간에 복사될 필요가 있다.
- 원래 슬롯도 그대로 그려지고 사본 슬롯이 드래그 되도록.
- 위와 같이 1️⃣ 뒤에 가려지지도 않고 2️⃣ 원래 자리가 비워지지 않도록 하려면
- 1️⃣ 다른 슬롯들보다 먼저 그려져야 한다. Hierarchy 상에서 위에 위치 하도록 해야 한다.
- 2️⃣ 드래그 된 슬롯은 사본으로 만들어야 한다.
DragSlot
이라는 이름의 이미지 UI 를Inventory_Base
자식으로 추가한다.- 1️⃣
Grid Setting
보다 아래에 위치하도록 한다.- 다른 슬롯들 보다 나중에 그려져 뒤에 묻히지 않도록
- 2️⃣ 드래그 된 슬롯은 이
DragSlot
에서 사본으로서 참조 되도록 한다.
- 1️⃣
- 이 또한 다른
Slot
의Item_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)
- 이 스크립트가 붙은 오브젝트를 마우스 드래그를 시작 했을 때 호출
- OnBeginDrag(PointerEventData eventData)
IDragHandler
인터페이스- OnDrag(PointerEventData eventData)
- 이 스크립트가 붙은 오브젝트를 마우스 드래그 중인 동안 계속 호출
- OnDrag(PointerEventData eventData)
IEndDragHandler
인터페이스- OnEndDrag(PointerEventData eventData)
- 이 스크립트가 붙은 오브젝트를 마우스 드래그 하는 것을 끝냈을 때 호출
- 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
의 이미지 투명도
- 본인의 이미지 컴포넌트
dragSlot
는 게임 도중 드래그가 발생할 때 할당될 것이다.imageItem
는dragSlot
본인의 이미지 컴포넌트.- 드래그 대상이 되는
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();
}
- OnEndDrag 과 OnDrop 의 차이
- OnEndDrag 👉 나 자신을 드래그 하는 것을 끝냈을 때 호출
- 내가 드래그 되는 것이 끝났을 때!
- 드래그가 종료했을 때, 드래그 대상이 되었던 오브젝트에서 호출 됨.
- OnDrop 👉 누군지 모르겠지만 내 자신한테 드롭 된 무언가가 있을 때 호출.
- 나한테 누가 드롭 되었을 때!
- 드래그를 멈춘 위치에 있는 오브젝트에서 호출 됨.
- 드롭 된 다른 곳의 OnDrop 이, 드래그를 끝낸 내 OnEndDrag 보다 먼저 실행된다.
- 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 슬롯은
- A 슬롯 자리에 임시 보관해 놨던 B 슬롯 추가
- 드롭 위치 대상인 B 슬롯이 빈 슬롯이 아니라면
- A 슬롯 자리에 B 슬롯을 추가한다.
DragSlot.instance.dragSlot.AddItem(_tempItem, _tempItemCount)
- A 슬롯 자리에 B 슬롯을 추가한다.
- B 슬롯이 빈 슬롯이였다면
- A 슬롯을 비워주기만 하면 된다.
DragSlot.instance.dragSlot.ClearSlot();
- A 슬롯을 비워주기만 하면 된다.
- 드롭 위치 대상인 B 슬롯이 빈 슬롯이 아니라면
- B 슬롯을 복사하여 다른 곳에 일단 보관
- A 슬롯을 드래그 하여 B 슬롯에 드롭하여, A 슬롯 B 슬롯 서로 자리를 바꾸려면
마우스 충돌 가리지 않게
위까지 해도, 슬롯을 아무리 드래그 해도 슬롯 교체가 제대로 이루어지지 않는다. 그 이유는 드롭 되는 위치의 대상이 슬롯의 OnDrop 가 제대로 호출되지 않았기 때문이다. ⭐이미지를 드래그 하여 옮기는 과정에서 DropSlot
이미지가 마우스 이벤트 Raycast를 대신 받아 버리기 때문에, DropSlot
뒤에 있는 드롭 되는 위치의 대상이 되는 Slot
이미지가 마우스 이벤트를 받지 못하여 OnDrop 가 제대로 호출되지 않았던 것이다.
체크 된 마우스를 놓고 드롭 해주어야만, 즉 직접 드롭 위치가 되는 Slot
에다가 직접 마우스를 대고 드롭해주어야만이 그 슬롯의 OnDrop이 호출 될 수 있었다. 근데 사실상 DropSlot
를 드래그 해오기 때문에 DropSlot
가 가려버리기 쉽상..
DropSlot
의Raycast Target
을 해제 해주어 Raycast 를 못 받게 하면 해결 된다.- 어차피 마우스 이벤트는 드래그 시작 대상이 됐었던 슬롯과 드롭 위치 대상이 됐었던 슬롯에서 일어나기 때문에
DropSlot
는 이벤트 못 받아도 괜찮다. - 이게 체크 되있으면
DropSlot
이 드래그 과정에서 드롭 위치 대상이 되는 슬롯의 앞을 가려 OnDrop 호출, 즉 마우스의 충돌을 방해한다.- 뒤에 있는 드롭 위치 대상이 되는 슬롯에 마우스가 직접적으로 닿아야 OnDrop 호출이 이루어 지는데
DropSlot
이 충돌을 대신 앞에서 받아버리기 때문이다.
- 뒤에 있는 드롭 위치 대상이 되는 슬롯에 마우스가 직접적으로 닿아야 OnDrop 호출이 이루어 지는데
- 어차피 마우스 이벤트는 드래그 시작 대상이 됐었던 슬롯과 드롭 위치 대상이 됐었던 슬롯에서 일어나기 때문에
- 체크 해제해주면
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"));
}
}
}
인벤토리가 활성화 되면 무기 교체 제한.
***
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기