안녕하세요 투니드입니다!
기본적인 인벤토리와 드롭에 대해서 Stardew Valley like Game in Unity Episode 6 Inventory System Part 2까지 해서 클론코딩을 완료했습니다!
그렇다면 이제 다음 챕터로 넘어가기 전에 제가 언급했던 클릭기능으로만 동작하는 코드를 드래그까지 추가해 보는 시간을 가져보기로 하겠습니다!
제목은 그래서! Part2-1 부록같은 느낌이죠 클론코딩에 목적이 있으신 분들은 지금 이 파트 2-@ 파트는 건너뛰셔도 무방합니다!
그럼 드래그 기능 시작해보기 전에!!!!!!!!!!!
전편에서 만들었던 아이템 드랍 있잖아요? 그거 조금만 손 볼게요 왜냐하면 지금 아이템을 밖으로 던진 다음에 먹으면
아이템이 최신화가 되지를 않아요
이렇게 보면 분명 먹었는데, 인벤토리에는 안 들어오고, 껐다 켜면 들어옵니다!
이것부터 한번 넣어볼게요!
먼저 ItemContainer.cs 파일로 가야 합니다!
여러 가지 방법이 있겠지만 저는 델리게이트를 이용해보려고 해요!
델리게이트에 대한 자세한 설명은 또 ㅠㅠ 나중에 블로그에 따로 포스팅을 해보도록 하겠습니다 ㅠㅠ
아이템을 얻으면 ItemContainer.cs -> Add가 호출됩니다. 이때 이 Add가 호출되면
InventoryPanel.cs -> Show()가 호출이 되었으면 좋겠습니다. 모든 게임이 먹은 아이템은 바로바로 확인이 가능하잖아요?
개인적으론 그냥 당연한 걸 구현한다고 생각합니다!
어쨌든! 위에 설명한 걸 가능하게 해주는 방법 중 하나가 델리게이트입니다!
먼저 ItemContainer.cs에 필드를 하나 추가해 줄 겁니다!
public으로 타입은 Action 으로 하나 선언해 줍니다!
그리고 이걸 어떻게 사용하냐면!
Add메서드에 변경점은 없습니다. 추가점만 한 줄 있어요!
inventoryChanged?.Invoke();가 무슨 뜻이냐 연결된 함수가 '있다면' 그 함수를 실행시켜라 라는 뜻입니다
코드 중간에 ?(물음표) 가 있죠? 이게 '있다면'을 담당해주고 있습니다
연결된 함수가 없으면 그냥 넘어가는 거죠!
아무튼 여기는 이제 다 됐고
InventoryPanel.cs 로 넘어옵니다!
이제 연결을 해줘야 한다고 했잖아요? 연결을 해보도록 하겠습니다!
먼저 알아두시면 좋은데 이 InventoryPanel은 계속 껐다 켰다 하는 오브젝트에요 I 키를 누르면 나오는 인벤토리죠
인벤토리가 활성화되면 안에 있는 코드들이 실행되는 거고 비활성화상태면 당연하게도 안의 내용들은 실행되지 않을 겁니다.
갑자기 이 얘기를 왜 하냐면
유니티에는 OnEnable()과 OnDisable()이라는 내부함수가 있습니다 뜻은 보시다시피 켜졌을 때, 꺼졌을 때입니다
우리는 이 델리게이트를 연결할 때 연결을 했다가 끊었다가를 반복해 줄 거예요!
이 스크립트 자체가 계속 꺼졌다 켜졌다 할 것이기 때문입니다!
연결을 하는 방법은 다행히 아주 간단하게 되어있어요!
먼저 Action타입의 변수를 받아오려면 ItemContainer 클래스 파일을 참조해야 하잖아요? 근데 InventoryPanel 클래스는 이미 참조를 받아오고 있습니다!
그래서, 참조변수를 이용해서 바로 설정이 가능합니다!
이렇게 연결합니다! OnEnable()에서 먼저 Show()를 해주는 건 당연히 이 인벤토리를 열었을 때 사용자한테 보여주기 위한 최신화고, 다음 줄에 있는 inventoryChanged += Show;가 연결을 담당해 줍니다
연결을 할 때는 += 기호(합연산)를 사용해 주고 연결을 해제할 땐 -=기호(뺄셈)을 사용해 주면 됩니다!
(OnDisable()이 이 스크립트가 비활성화될 때 실행시켜 주는 내부함수에요)
이렇게 하고 저장한 다음 실행을 해보면요?
쟈잔~ 이렇게 아이템을 주우면 바로 실행이 됩니다!
여기에서 중요한 얘기(?)가 하나 있습니다! inventoryChanged?.Invoke(); 이 코드에서 ?(물음표)를 안 쓰면 문제가 생길 수 있습니다!
저희가 인벤토리를 항상 키고 아이템을 먹는 게 아니죠? 근데 아이템을 먹으면 연결된 함수가 있다면 항상 Show()를 불러준다고 했잖아요? ?(물음표)를 안 쓴다면 그냥 무조건 실행하기 때문에 에러가 날 수 있습니다! 이 부분 이해가 되시나요?
그럼 이제 드디어 드래그를 시작해 보겠습니다 ㅠㅠ
유튜버가 하던 내용의 기능에 살을 붙이는(?) 느낌이라 좀 오래 걸렸습니다 ㅠㅠ
먼저 완성본부터 보시죠!
이게 드래그를 하면 노란색 클릭표시가 쭈욱 따라가긴 하는데.... 구분이 잘 안 되기는 하네요 ㅠㅠ
기본적으로는 ItemDragAndDropController.cs 파일에서 클론코딩 했던 OnClick()메서드를 토대로 만들었어요! 먼저 실질적인 클릭에 대한 구현이 ItemDragAndDropController.cs에서 이뤄지고 있어서 형식을 맞춰보겠습니다.
드래그는 유니티에서 총 4개로 나눠서 사용을 해줘야 합니다! 드래그를 시작할 때, 드래그 중일 때, 드래그가 끝났을 때, 드래그가 끝났을 때 (오타 아닙니다 밑에서 설명할게요!)
먼저 OnClick()과 마찬가지로 InventoryButton.cs 에서 내부 인터페이스들을 구현해줘야 합니다!
필요한 인터페이스로는 IBeginDragHandler (드래그 시작), IDragHandler(드래그 중), IEndDragHandler(드래그가 끝날 때), IDropHandler(드래그가 끝날 때) 이렇게 4가지가 필요해요!
보시면 드래그가 끝나는 게 왜 2개야? 하실 수 있습니다! 궁금증을 먼저 해결하고 가야겠죠?
InventoryButton.cs 파일은 UI의 캔버스 안(정확히는 Panel)에 붙어있어요! 그쵸?
여기서 클릭도 그렇고, 드래그 인터페이스들은 자신들이 속한 UI에서만 동작을 합니다. 즉 다른 곳을 클릭하고 드래그해도 저희가 구현한 InventoryButton.cs에서는 코드를 처리하지 않죠.
그런데. 딱 하나 다른 게 있습니다 OnEndDrag 이 녀석은 드래그가 시작됐다면, 끝나는 곳이 어디든 드래그가 끝나면 그냥 무조건 호출을 해요.
그렇다면 저희가 해야 할 것이 명확하게 나눠서 구분이 되겠죠? 인벤토리 버튼 안에서 드래그가 끝나면 OnClick과 마찬가지로 처리를 아이템 교환 또는 이동을 해주면 되고, 밖으로 나가면 아이템을 밖에 버리면(?) 되죠
정확히 짚고 넘어가기 위해 디버그를 보시죠
보이시나요? 클릭을 하고 밖을 클릭하면 클릭 디버그가 찍히지 않고, 드래그를 할 때 UI안에서 드래그가 끝나면 드롭과
엔드가 같이 찍히며 밖으로 드래그를 끝내면 엔드만 찍히죠?
이건 반드시 짚고 넘어가셔야 해요!
그러면 밖을 클릭했을 때는 왜 아이템이 드롭이 돼? 라고 궁금해지셨다면 ItemDragAndDropController.cs 파일이 왜 분리가 되어있고 어떻게 동작이 되는 건지 한 번만 더 인벤토리 시스템 구현부를 정독해 주시길 바랍니다
설명이 길었는데 이런 처리과정을 가지고 있다라고 반드시 알고 가셔야 합니다.
위에 인터페이스들을 상속받으면 OnBeginDrag, OnDrag, OnEndDrag, OnDrop 메서드들을 반드시 구현해줘야 합니다
먼저 OnBeginDrag 입니다!
아이템 드래그가 시작할 때는 무조건. 아이템을 들고 다녀야겠죠?
그래서 이렇게 만약 빈 슬롯을 클릭했다면 실행하지 않도록 한번 막아줬습니다.
그리고 OnDragStart를 ItemDragAndDropController.cs 파일에 정의를 해줬는데 사실 OnClick과 내용이 아주 똑같습니다
이렇게 OnClick과 똑같이 하도록 해놨는데, 드래그의 시작을 알리는 isDraging 부울 변수 하나만 필드로 빼서 그걸 True로 만들어줬어요!
그다음은 드래그 중일 때입니다 다시 InventoryButton.cs로 돌아와서
이렇게 빈 메서드에요 인터페이스 구현만 해줬죠 이유는 이게 없으면 드래그가 동작하질 않습니다!
따로 드래그중일 때 무언가 할 필요가 없기에 빈 메서드만 유지했습니다!
그리고 이제 드래그가 끝났을 때입니다!
이렇게 보시면 두 가지 다 정의를 해줬는데 어떤 식으로 동작하는지에 대해서 설명드렸으니 바로
ItemDragAndDropController.cs 로 가보죠!
아이템을 버리는(?) 기능과 이동시키는 기능을 동시에 구현해야 하기 때문에 기능을 분리했습니다
그리고 여기에 필요한 게 isDraging 변수에요!
UI 내에서 드래그를 끝내면 두 개의 메서드가 둘 다(InventoryButton의 이벤트 2개) 호출 돼버려요!
그래서 isDraging에 먼저 false를 주고 인벤토리 안에서 끝나면 아이템을 이동시키거나 바꾸고, 밖에서 끝나면 DropItem()이라고 아이템을 버리는 구문을 따로 메서드로 빼줬습니다!
이렇게요!
Update 문도 조금 변경을 해줬는데
이렇게 바꿨습니다!
고대하던 클릭과 같이 쓸 수 있는 드래그 기능은 여기까지입니다! 디버깅도 충분히 했기도 하고 몇백번 돌려봤는데 충돌은 다행히 안 나더라구요! 그래서 드래그 기능도 해보고 싶다면 여기까지 해보셔도 좋을 것 같습니다

혹시나 에러나 충돌이 나면 댓글로 알려주세요!
InventoryButton.cs
using TMPro;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace MyStardewValleylikeGame
{
public class InventoryButton : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler
{
#region Variables
[SerializeField] private Image icon; // 아이템 아이콘을 나타내는 Image 컴포넌트
[SerializeField] private TextMeshProUGUI text; // 아이템 개수를 표시하는 TextMeshProUGUI 컴포넌트
public int myIndex; // 현재 버튼의 인덱스
private GameManager gameManager; //게임매니저 참조변수
#endregion
//게임 매니저를 필드로 참조
private void Start()
{
//게임매니저 할당
gameManager = GameManager.Instance;
}
// 인덱스를 설정하는 메서드
public void SetIndex(int index) => myIndex = index;
// 슬롯의 아이템 정보를 버튼에 설정하는 메서드
public void SetItem(ItemSlot slot)
{
icon.gameObject.SetActive(true); // 아이콘을 활성화
icon.sprite = slot.item.icon; // 슬롯의 아이템 아이콘 설정
text.gameObject.SetActive(slot.item.stackable); // 아이템이 스택 가능한 경우 텍스트 활성화
text.text = slot.item.stackable ? slot.count.ToString() : ""; // 스택 가능한 경우 개수를 표시, 그렇지 않으면 빈 문자열
}
// 버튼을 초기화하는 메서드
public void Clean()
{
icon.sprite = null; // 아이콘 스프라이트 초기화
icon.gameObject.SetActive(false); // 아이콘 비활성화
text.text = ""; // 텍스트 초기화
text.gameObject.SetActive(false); // 텍스트 비활성화
}
// 포인터 클릭 이벤트 처리 메서드
public void OnPointerClick(PointerEventData eventData)
{
// 드래그 앤 드롭 컨트롤러의 클릭 처리 메서드 호출
gameManager.dragAndDropController.OnClick(gameManager.inventoryContainer.slots[myIndex]);
// 인벤토리 패널을 보여줌
transform.parent.GetComponent<InventoryPanel>().Show();
}
#region 드래그 앤 드롭 기능 구현
// 드래그 시작 이벤트 처리 메서드
public void OnBeginDrag(PointerEventData eventData)
{
// 현재 슬롯이 비어있으면 리턴
if (gameManager.inventoryContainer.slots[myIndex].item == null) return;
// 드래그 앤 드롭 컨트롤러의 드래그 시작 메서드 호출
gameManager.dragAndDropController.OnDragStart(gameManager.inventoryContainer.slots[myIndex]);
// 인벤토리 패널을 보여줌
transform.parent.GetComponent<InventoryPanel>().Show();
}
// 드래그 중 이벤트 처리 메서드 (현재는 빈 메서드)
public void OnDrag(PointerEventData eventData) { }
// 드래그 종료 이벤트 처리 메서드
public void OnEndDrag(PointerEventData eventData)
{
// 드래그 중이 아니면 리턴
if (!gameManager.dragAndDropController.isDraging) return;
// 드래그 앤 드롭 컨트롤러의 드래그 종료 메서드 호출
gameManager.dragAndDropController.OnEndDrag();
}
// 드롭 이벤트 처리 메서드
public void OnDrop(PointerEventData eventData)
{
// 드래그 중이 아니면 리턴
if (!gameManager.dragAndDropController.isDraging) return;
// 드래그 앤 드롭 컨트롤러의 인벤토리에 드롭 메서드 호출
gameManager.dragAndDropController.DropInInventoryUI(gameManager.inventoryContainer.slots[myIndex]);
// 인벤토리 패널을 보여줌
transform.parent.GetComponent<InventoryPanel>().Show();
}
#endregion
}
}
ItemDragAndDropController.cs
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace MyStardewValleylikeGame
{
public class ItemDragAndDropController : MonoBehaviour
{
#region Variables
[SerializeField] private GameObject itemIcon; // 아이템 아이콘을 나타내는 GameObject
private ItemSlot itemSlot = new ItemSlot(); // 드래그 및 드롭을 위한 현재 아이템 슬롯
private RectTransform iconTransform; // 아이콘의 RectTransform (UI 위치 및 크기)
private Image itemIconImage; // 아이콘의 이미지 컴포넌트
public bool isDraging = false; // 드래그 중인지 여부를 나타내는 플래그
#endregion
private void Start()
{
//참조 변수
iconTransform = itemIcon.GetComponent<RectTransform>();
itemIconImage = itemIcon.GetComponent<Image>();
}
private void Update()
{
// 아이콘이 활성화되어 있으면
if (itemIcon.activeInHierarchy)
{
// 아이콘의 위치를 마우스 위치로 업데이트
iconTransform.position = Input.mousePosition;
// 마우스 왼쪽 버튼 클릭 시, UI를 벗어난 경우 아이템을 드롭
if (Input.GetMouseButtonDown(0) && !EventSystem.current.IsPointerOverGameObject())
{
DropItem(); // 아이템 드롭 메서드 호출
}
}
}
// 슬롯 클릭 시 호출되는 메서드
public void OnClick(ItemSlot clickedSlot)
{
// 현재 슬롯에 아이템이 없으면
if (itemSlot.item == null)
{
itemSlot.Copy(clickedSlot); // 클릭한 슬롯의 아이템 정보를 복사
clickedSlot.Clear(); // 클릭한 슬롯을 비움
}
else // 현재 슬롯에 아이템이 있을 경우
{
Item tempItem = clickedSlot.item; // 클릭한 슬롯의 아이템을 임시 변수에 저장
int tempCount = clickedSlot.count; // 클릭한 슬롯의 아이템 개수를 임시 변수에 저장
clickedSlot.Copy(itemSlot); // 현재 슬롯의 아이템 정보를 클릭한 슬롯으로 복사
itemSlot.Set(tempItem, tempCount); // 임시 변수의 아이템 정보를 현재 슬롯에 설정
}
UpdateIcon(); // 아이콘 업데이트 메서드 호출
}
// 아이콘 업데이트 메서드
private void UpdateIcon()
{
itemIcon.SetActive(itemSlot.item != null); // 현재 슬롯에 아이템이 없으면 아이콘 비활성화, 있으면 활성화
if (itemSlot.item != null) itemIconImage.sprite = itemSlot.item.icon; // 아이콘 이미지 설정
}
#region 드래그 앤 드롭 기능 구현
// 드래그 시작 시 호출되는 메서드
public void OnDragStart(ItemSlot draggedSlot)
{
isDraging = true; // 드래그 중 플래그 설정
OnClick(draggedSlot); // 드래그하는 슬롯 클릭 처리
}
// 인벤토리 UI에 드롭 시 호출되는 메서드
public void DropInInventoryUI(ItemSlot targetSlot)
{
isDraging = false; // 드래그 중 플래그 해제
OnClick(targetSlot); // 타겟 슬롯 클릭 처리
}
// 드래그 종료 시 호출되는 메서드
public void OnEndDrag()
{
isDraging = false; // 드래그 중 플래그 해제
DropItem(); // 아이템 드롭 메서드 호출
}
// 아이템 드롭 메서드
private void DropItem()
{
Vector3 worldPosition = Camera.main.ScreenToWorldPoint(Input.mousePosition); // 마우스 위치를 3D 월드 좌표로 변환
worldPosition.z = 0f; // z 값 0으로 설정 (2D 게임을 위해)
ItemSpawnManager.Instance.SpawnItem(worldPosition, itemSlot.item, itemSlot.count); // 아이템을 월드에 드롭
itemSlot.Clear(); // 현재 슬롯 비우기
UpdateIcon(); // 아이콘 업데이트 메서드 호출
}
#endregion
}
}
'StardewValleyLikeGameCloneCoding' 카테고리의 다른 글
Stardew Valley like Game in Unity Episode 7-2 Toolbar (0) | 2025.03.04 |
---|---|
Stardew Valley like Game in Unity Episode 7-1 Toolbar (0) | 2025.03.03 |
Stardew Valley like Game in Unity Episode 6-3 Inventory System Part 2 (0) | 2025.02.20 |
Stardew Valley like Game in Unity Episode 6-2 Inventory System Part 2 (0) | 2025.02.19 |
Stardew Valley like Game in Unity Episode 6-1 Inventory System Part 2 (0) | 2025.02.19 |