Chapter 5-4. 인벤토리 : 아이템 버리기, Input Field
카테고리: Unity Lesson 3
태그: Unity Game Engine
인프런에 있는 케이디님의 [유니티 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
가져옴.
- RectTransform 에서
- OnEndDrag(PointerEventData eventData)
- 드래그가 끝나면, 마우스 드래그 대상이 되었던 슬롯에서 호출
- 드래그가 끝난 위치가 인벤토리 베이스 이미지를 벗어난 곳이라면 아이템 버리는 처리를 해야 함
Rect
- 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 만들기
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, 비활성화 상태일 때 Falsetext_Preview
: Placeholder 의 텍스트. 드롭된 아이템의 갯수가 표시되도록 할 것이다.- 👉 사용자가 별다른 입력을 하지 않으면 해당 슬롯에 있던 아이템 갯수만큼 전부 다 버려지도록.
text_Input
: 사용자가 실제로 입력한 문자열이 들어갈 텍스트.if_text
: Input Field 의 텍스트를 참조하기 위해 Input Field 타입으로 선언.- 입력 필드가 활성화 되면 기존에 사용자가 썼었던 텍스트는 지우고 공백으로 초기화 해야 한다.
text_Input
를 “” 공백으로 만드는 방법도 있지만 입력 필드 컴포넌트의text
속성을 자식인Text
에게 덮어 씌우는 방식이기 때문에, Text인text_Input
를 “” 공백으로 바꿔도 입력 필드 컴포넌트의text
속성에는 사용자가 이 전에 입력했던 값이 남아 있으므로 여전히 사용자가 이 전에 입력했던 값이Text
에게 덮어 씌워져 불러와지게 된다.- 따라서 텍스트 초기화를 입력 필드의 자식인
Text
에서 하지 않고 입력 필드 컴포넌트의text
속성 값을 초기화 하기 위해Input Field
타입으로 불러 온 것이다.
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() 함수 등록
- 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() 함수 등록
activate
False 설정- 드래그 했던 슬롯의 색상을 다시 투명하게
- 입력 필드 비활성화
- 드래그 했던 슬롯을 참조하던
dragSlot
초기화
📜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) 각각 만들고 할당
풀 설정
- 풀을 벨 땐 따로 아이템을 생성하지 않고 자동으로 풀을 베자마자 인벤토리에 풀 아이템이 들어오도록 한다.
- 인벤토리의 풀을 버릴 떈 버려진 아이템으로 생성되지 않고 그냥 날라가도록 한다.
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);
- 풀은 파괴하자마자 자동으로 인벤토리에 들어가도록 한다.
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) 인 경우에만 아이템 프리팹을 생성하도록 하였다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글 남기기