아이템 제작 시스템은 게임에 더 많은 선택과 자유도를 부여하며, 캐릭터의 능력을 강화할 수 있고, 게임의 재미와 플레이 시간을 증가시킬 수 있기 때문에 필요합니다. 아이템 제작 시스템을 구현하고 이를 정리하였습니다.
📺 미리보기
💬 목차
- 총 네개의 목차로 구성되어 있습니다.
- 글 별로 아직 언급되지 않은 클래스의 호출이 있을 수 있습니다. 이 사항은 모든 글을 참조하면 충분히 이해할 수 있습니다.
[📌현재 글] 2. [유니티] 제작 시스템(2) - 제작 슬롯
📖 구현 내용
- 플레이어의 인벤토리에 있는 아이템을 재료로 하여 아이템을 제작할 수 있습니다.
- 아이템 제작에는 시간이 소요되며 시간을 기다린 후에만 아이템 교환이 이뤄집니다.
- 아이템 제작 시간 도중 창을 닫는경우 아이템 교환은 이뤄지지 않습니다.
- 아이템을 제작중인경우 현재 제작중인 아이템을 또 제작하거나, 다른 아이템을 제작할 수 없습니다.
✅ 구현
- 이번 글에서는 제작 레시피를 하나의 슬롯으로 보여주기위한 레시피 슬롯을 구현하고 이를 정리하였습니다.
- 이 글에서 다루는 기능에서 사용되는 아이템 슬롯(InventorySlot)은 블로그에서 다룬 [인벤토리 시스템]을 기반으로 합니다.
· CraftingSlot.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
namespace CraftingSystem
{
public class CraftingSlot : MonoBehaviour
{
private static bool mIsCrafting = false;
public static bool IsCrafting
{
get
{
return mIsCrafting;
}
}
[Header("제작 결과 아이템의 슬롯")]
[SerializeField] private InventorySlot mResultItemSlot;
[Header("제작에 필요한 재료 아이템을 담는 슬라이드 콘텐츠 트랜스폼")]
[SerializeField] private Transform mRecipeContentTransform;
[Header("제작 버튼")]
[SerializeField] private Button mCraftingButton;
[Header("제작 시간 텍스트 라벨")]
[SerializeField] private TextMeshProUGUI mCraftingTimeLabel;
[Header("제작 진행도 이미지")]
[SerializeField] private Image mCraftingProgressImage;
[Header("비활성화 상태시 보여줄 이미지 오브젝트")]
[SerializeField] private GameObject mDisableImageGo;
/// <summary>
/// 현재 해당 슬롯이 사용중인 레시피
/// </summary>
[HideInInspector] public CraftingRecipe CurrentRecipe;
private Coroutine? mCoCraftItem; // 제작 연출 및 시간 계산 코루틴
private void OnDisable()
{
if (mCoCraftItem is not null)
StopCoroutine(mCoCraftItem);
mIsCrafting = false;
}
public void Init(CraftingRecipe recipe)
{
CurrentRecipe = recipe;
// 슬롯 활성화
gameObject.SetActive(true);
// 제작 결과 아이템을 슬롯에 등록
mResultItemSlot.ClearSlot();
InventoryMain.Instance.AcquireItem(recipe.resultItem.item, mResultItemSlot, recipe.resultItem.count);
// Ui 요소 초기화
mCraftingTimeLabel.text = $"{recipe.craftingTime.ToString("F1")}s";
mCraftingProgressImage.fillAmount = 1.0f;
mCraftingButton.GetComponent<Image>().sprite = recipe.buttonSprite;
// 제작 레시피의 재료 아이템 슬롯이 부족하면 개수에 맞게 인스턴스
for (int i = mRecipeContentTransform.childCount; i <= recipe.reqItems.Length; ++i)
Instantiate(mResultItemSlot, Vector3.zero, Quaternion.identity, mRecipeContentTransform);
// 모든 재료 아이템 슬롯을 초기화
for (int i = 0; i < mRecipeContentTransform.childCount; ++i)
{
// 슬롯 획득
InventorySlot recipeSlot = mRecipeContentTransform.GetChild(i).GetComponent<InventorySlot>();
// 레시피의 재료 개수보다 작은 인덱스 번호라면?
if (i < recipe.reqItems.Length)
{
// 슬롯에 아이템을 등록
recipeSlot.ClearSlot();
InventoryMain.Instance.AcquireItem(recipe.reqItems[i].item, recipeSlot, recipe.reqItems[i].count);
recipeSlot.gameObject.SetActive(true);
}
else
{
recipeSlot.gameObject.SetActive(false);
}
}
}
public void ToggleSlotState(bool isCraftable)
{
mDisableImageGo.SetActive(!isCraftable);
mCraftingButton.interactable = isCraftable;
}
/// <summary>
/// 재료 아이템을 제거하고, 결과 아이템을 획득
/// </summary>
private void RefreshItems()
{
InventorySlot mainInventoryslot = null;
// 재료 아이템 정보를 확인하여 메인 인벤토리의 아이템을 제거
foreach (CraftingItemInfo info in CurrentRecipe.reqItems)
{
InventoryMain.Instance.HasItemInInventory(info.item.ID, out mainInventoryslot, info.count);
mainInventoryslot.UpdateSlotCount(-info.count);
}
// 제작 후 결과 아이템을 인벤토리에 획득
InventoryMain.Instance.AcquireItem(CurrentRecipe.resultItem.item, CurrentRecipe.resultItem.count);
// 아이템을 교환 후 슬롯들을 업데이트
CraftingManager.Instance.RefreshAllSlots();
}
private IEnumerator CoCraftItem()
{
mIsCrafting = true;
// 사운드 재생
SoundManager.Instance.PlaySound2D("Craft Sound " + SoundManager.Range(1, 2));
float process = 0f;
while (process < 1f)
{
process += Time.deltaTime / CurrentRecipe.craftingTime;
mCraftingProgressImage.fillAmount = Mathf.Lerp(0f, 1f, process);
yield return null;
}
// 아이템 획득
RefreshItems();
mIsCrafting = false;
}
#region Ui
public void BTN_Craft()
{
if (mIsCrafting)
{
DialogBox.DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox("알림", $"이미 제작중입니다.", "확인", DialogBox.DialogBoxController.RESERVED_EVENT_CLOSE, null, 160, 100);
dialogBox.ModifyBottomLayoutPadding(50, 50);
return;
}
if (mCoCraftItem is not null)
StopCoroutine(mCoCraftItem);
mCoCraftItem = StartCoroutine(CoCraftItem());
}
#endregion
}
}
private static bool mIsCrafting = false;
public static bool IsCrafting
{
get
{
return mIsCrafting;
}
}
- 현재 제작을 하고 있는지 확인하기위한 전역 변수입니다.
- 제작을 중복으로 하지 않도록 하기위함, 또한 기타 조건에도 사용할 수 있습니다.
[Header("제작 결과 아이템의 슬롯")]
[SerializeField] private InventorySlot mResultItemSlot;
- 제작 아이템의 결과를 보여줄 아이템 슬롯입니다.
[Header("제작에 필요한 재료 아이템을 담는 슬라이드 콘텐츠 트랜스폼")]
[SerializeField] private Transform mRecipeContentTransform;
- 아이템 제작에 요구되는 재료 아이템들을 보여줄 때 요구되는 아이템의 종류가 많은경우 한 슬롯에 모두 표시할 수 없습니다.
- 이 문제를 개선하기위해 스크롤 뷰를 사용하여 스크롤을 하여 어떤 아이템이 필요한지 모두 확인할 수 있게합니다.
- 아이템 슬롯을 인스턴스할 때 위치시킬 스크롤 뷰의 콘텐츠 트랜스폼을 이곳에 등록합니다.
[Header("제작 버튼")]
[SerializeField] private Button mCraftingButton;
- 제작 버튼을 활성화 / 비활성화 하기 위해 사용합니다.
[Header("제작 시간 텍스트 라벨")]
[SerializeField] private TextMeshProUGUI mCraftingTimeLabel;
- 제작에 소요되는 시간 및 제작 중 남은 시간을 표시하기위한 라벨입니다.
[Header("제작 진행도 이미지")]
[SerializeField] private Image mCraftingProgressImage;
- 제작시 진행중인 진척도를 표시하기위한 이미지입니다.
[Header("비활성화 상태시 보여줄 이미지 오브젝트")]
[SerializeField] private GameObject mDisableImageGo;
- 제작이 불가능할 때 슬롯 위에 이 오브젝트를 덮어 시각적으로 제작이 불가능하다는것을 보여주기위해 사용하는 오브젝트입니다.
private void OnDisable()
{
if (mCoCraftItem is not null)
StopCoroutine(mCoCraftItem);
mIsCrafting = false;
}
- 제작 다이얼로그 창을 닫아 제작을 더 이상 하지 않거나, 이미 제작중인 아이템을 취소하는경우 호출됩니다.
- 현재 실행중인 코루틴을 중단하고 mIsCrafting을 false로 설정합니다.
public void Init(CraftingRecipe recipe)
{
CurrentRecipe = recipe;
// 슬롯 활성화
gameObject.SetActive(true);
// 제작 결과 아이템을 슬롯에 등록
mResultItemSlot.ClearSlot();
InventoryMain.Instance.AcquireItem(recipe.resultItem.item, mResultItemSlot, recipe.resultItem.count);
// Ui 요소 초기화
mCraftingTimeLabel.text = $"{recipe.craftingTime.ToString("F1")}s";
mCraftingProgressImage.fillAmount = 1.0f;
mCraftingButton.GetComponent<Image>().sprite = recipe.buttonSprite;
// 제작 레시피의 재료 아이템 슬롯이 부족하면 개수에 맞게 인스턴스
for (int i = mRecipeContentTransform.childCount; i <= recipe.reqItems.Length; ++i)
Instantiate(mResultItemSlot, Vector3.zero, Quaternion.identity, mRecipeContentTransform);
// 모든 재료 아이템 슬롯을 초기화
for (int i = 0; i < mRecipeContentTransform.childCount; ++i)
{
// 슬롯 획득
InventorySlot recipeSlot = mRecipeContentTransform.GetChild(i).GetComponent<InventorySlot>();
// 레시피의 재료 개수보다 작은 인덱스 번호라면?
if (i < recipe.reqItems.Length)
{
// 슬롯에 아이템을 등록
recipeSlot.ClearSlot();
InventoryMain.Instance.AcquireItem(recipe.reqItems[i].item, recipeSlot, recipe.reqItems[i].count);
recipeSlot.gameObject.SetActive(true);
}
else
{
recipeSlot.gameObject.SetActive(false);
}
}
}
- 제작 슬롯을 초기화합니다.
- 레시피를 매개변수로 사용하여 레시피를 기반으로 이 슬롯을 초기화하도록 합니다.
- 레시피에 필요한 아이템의 개수만큼 인벤토리 슬롯을 생성하고 해당 슬롯에 등록합니다.
- 만약 인벤토리 슬롯이 이미 있다면, 해당 슬롯을 재사용합니다.
public void ToggleSlotState(bool isCraftable)
{
mDisableImageGo.SetActive(!isCraftable);
mCraftingButton.interactable = isCraftable;
}
- 해당 아이템을 제작할 수 있는지 토글합니다.
/// <summary>
/// 재료 아이템을 제거하고, 결과 아이템을 획득
/// </summary>
private void RefreshItems()
{
InventorySlot mainInventoryslot = null;
// 재료 아이템 정보를 확인하여 메인 인벤토리의 아이템을 제거
foreach (CraftingItemInfo info in CurrentRecipe.reqItems)
{
InventoryMain.Instance.HasItemInInventory(info.item.ID, out mainInventoryslot, info.count);
mainInventoryslot.UpdateSlotCount(-info.count);
}
// 제작 후 결과 아이템을 인벤토리에 획득
InventoryMain.Instance.AcquireItem(CurrentRecipe.resultItem.item, CurrentRecipe.resultItem.count);
// 아이템을 교환 후 슬롯들을 업데이트
CraftingManager.Instance.RefreshAllSlots();
}
- 아이템을 제작하여 해당 아이템 제작 소요시간이 지난경우, 아이템을 교환합니다.
- 플레이어의 인벤토리에서 재료 해당 아이템을 제거하고 제작 결과 아이템을 인벤토리에 지급합니다.
private IEnumerator CoCraftItem()
{
mIsCrafting = true;
// 사운드 재생
SoundManager.Instance.PlaySound2D("Craft Sound " + SoundManager.Range(1, 2));
float process = 0f;
while (process < 1f)
{
process += Time.deltaTime / CurrentRecipe.craftingTime;
mCraftingProgressImage.fillAmount = Mathf.Lerp(0f, 1f, process);
yield return null;
}
// 아이템 획득
RefreshItems();
mIsCrafting = false;
}
- 아이템을 제작하는 소요시간만큼 UI요소를 갱신하고 시간이 경과했다면 아이템 교환을 하기위한 코루틴입니다.
public void BTN_Craft()
{
if (mIsCrafting)
{
DialogBox.DialogBoxController dialogBox = DialogBoxGenerator.Instance.CreateSimpleDialogBox("알림", $"이미 제작중입니다.", "확인", DialogBox.DialogBoxController.RESERVED_EVENT_CLOSE, null, 160, 100);
dialogBox.ModifyBottomLayoutPadding(50, 50);
return;
}
if (mCoCraftItem is not null)
StopCoroutine(mCoCraftItem);
mCoCraftItem = StartCoroutine(CoCraftItem());
}
- 버튼을 눌러 아이템 제작을 시도합니다.
- 만약 이미 다른 아이템을 제작중이라면 리턴하며, 다이얼로그 박스를 이용하여 알림을 띄워줍니다.
- 다이얼로그박스 기능은 "[유니티] 다이얼로그박스 시스템 (스크립트로 만드는 다이얼로그박스)"에서 확인할 수 있습니다.
· UI 프리팹
- 레시피를 보여줄 슬롯을 만듭니다.
- 이 슬롯을 프리팹으로 만들고 사용할 수 있도록 합니다.
- 왼쪽의 빈 영역이 재료 아이템이 들어갈 곳, 우측 영역이 지급되는 아이템입니다.
- 망치 모양은 제작 모양입니다. 레시피에서 buttonSprite을 이용하여 해당 이미지를 바꿀 수 있습니다.
- 화살표 이미지는 현재 제작중인 아이템의 진척도를 보여줍니다.
- 화살표 이미지 중심에 텍스트를 이용하여 얼마나 소요되는지, 얼마나 남았는지 볼 수 있습니다.
- 전체적인 구성은 다음과 같습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 제작 시스템(4) - 매니저 (0) | 2023.04.02 |
---|---|
[유니티] 제작 시스템(3) - 제작소 (0) | 2023.04.02 |
[유니티] 제작 시스템(1) - 레시피 (0) | 2023.04.02 |
[유니티] 상점 시스템(3) - 상점 매니저 (0) | 2023.04.02 |
[유니티] 상점 시스템(2) - 상점 (0) | 2023.04.02 |