✅ 기능
보통 게임에서는 인벤토리에 있는 장비 아이템을 우클릭하여 사용하면 아이템을 소모하는것이 아닌 특별하게 장비 아이템으로 장착을 하는 인터페이스를 제공합니다. 이번 글에서는 아이템을 착용하거나, 해제하고 장비 아이템의 효과를 적용하여 실제로 게임 플레이에 영향을 주도록 구현하고 정리하였습니다.
✅ 흐름도
✅ 사용 예시
✅ 구현
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 인벤토리 슬롯 하나를 담당
/// </summary>
public class InventorySlot : MonoBehaviour, IPointerClickHandler, IBeginDragHandler, IDragHandler, IEndDragHandler, IDropHandler, IPointerEnterHandler, IPointerExitHandler
{
/// <summary>
/// 마우스 클릭 오버라이드나 외부에서 해당 슬롯을 대상으로 직접 사용하도록 호출
/// </summary>
public void UseItem()
{
if (mItem != null) //해당 슬롯의 아이템이 null이라면 return
{
//상호작용이 불가능한 (사용이 불가능한) 아이템이라면 리턴
if (!mItem.IsInteractivity) { return; }
//쿨타임이 0보다 큰경우 (현재 쿨타임이 돌고있는경우)라면 리턴한다.
if (ItemCooltimeManager.Instance.GetCurrentCooltime(mItem.ItemID) > 0) { return; }
//아이템 사용 함수 호출
//만약 아이템 함수 호출인 상태에서 false가 리턴되면, 현재 사용 불가능 상태이기에 리턴한다.
if (!mItemActionManager.UseItem(mItem, this)) { return; }
//아이템의 쿨타임이 설정되어있으면 쿨타임 적용
if (mItem != null && mItem.Cooltime > 0f) { ItemCooltimeManager.Instance.AddCooltimeQueue(mItem.ItemID, mItem.Cooltime); }
//아이템이 소모성이면 한개씩 개수를 줄임
if (mItem != null && mItem.IsConsumable) { UpdateSlotCount(-1); }
//아이템을 다쓴경우, UpdateSlotCount로 인해 mItem이 null이 되는 경우에 UI를 끈다.
if (mItem == null) { mItemDescription.DisableToolTip(); }
}
}
...
//마우스 클릭 오버라이드
public void OnPointerClick(PointerEventData eventData)
{
if (eventData.button == PointerEventData.InputButton.Right) //버튼 우클릭시
{
if (mSlotMask == ItemType.SKILL) { return; }
UseItem();
}
}
}
- 인벤토리 슬롯에서 우클릭으로 아이템을 사용합니다.
- [인벤토리 슬롯 아이템 사용] 글에서 해당 내용을 다룹니다.
using UnityEngine;
/// <summary>
/// 씬 내의 매니저 오브젝트에 할당
/// 아이템(또는 정적 물체)과 상호작용하거나, 인벤토리에서 아이템을 사용하면 특수 이벤트를 발생시킴
/// </summary>
public class ItemActionManager : MonoBehaviour
{
...
[Space(30)]
[Header("인벤토리")]
[SerializeField] private EquipmentInventory mEquipmentInventory;
[SerializeField] private InventoryMain mMainInventory;
...
/// <summary>
/// 아이템 사용 이벤트 호출
/// 각 아이템마다 실행되는 기능을 수행
/// </summary>
/// <param name="item"></param>
/// <returns>실행이 정상적으로 이루어 졌는가?</returns>
public bool UseItem(Item item, InventorySlot calledSlot = null)
{
Debug.Log("UseItemEvent");
switch (item.Type)
{
...
case ItemType.Equipment_HELMET:
case ItemType.Equipment_ARMORPLATE:
case ItemType.Equipment_GLOVE:
case ItemType.Equipment_PANTS:
case ItemType.Equipment_SHOES:
{
//case 1: 아이템 사용을 호출한 슬롯이 장비창이라면?
//장비를 장착 해제해야한다
if (Item.CheckEquipmentType(calledSlot.SlotMask))
{
//인벤토리 슬롯에서 아이템을 획득할 수 있는지 확인
InventorySlot mainSlot = mMainInventory.IsCanAquireItem(item);
if (mainSlot != null)
{
calledSlot.ClearSlot(); //장비 아이템을 장착 해제
mMainInventory.AcquireItem(item, mainSlot); //현재 장비 아이템을 인벤토리에 획득
}
}
//case 2: 아이템 사용을 호출한 슬롯이 인벤토리라면?
//장비를 장착해야한다.
else
{
//장비 인벤토리에서 현재 유형에 맞는 인벤토리 가져오기
InventorySlot equipmentSlot = mEquipmentInventory.GetEquipmentSlot(item.Type);
//이미 장착중인 아이템을 스왑을 위해 임시 저장
Item tempItem = equipmentSlot.Item;
//호출한 아이템으로 장비 아이템을 변경
equipmentSlot.AddItem(item);
//호출한 슬롯에 아이템을 추가(착용하던 기존 아이템)하거나 지움
if (tempItem != null) { calledSlot.AddItem(tempItem); }
else { calledSlot.ClearSlot(); }
}
//장비 착용 효과음 재생
SoundManager.Instance.PlaySound2D("Equipment " + SoundManager.Range(1, 2));
//장비 효과 계산
mEquipmentInventory.CalculateEffect();
break;
}
}
return true;
}
...
/// <summary>
/// 아이템을 슬롯에 드롭하는경우 발생하는 이벤트이다.
/// </summary>
public void SlotOnDropEvent(InventorySlot fromSlot, InventorySlot toSlot)
{
Debug.Log("SlotOnDropEvent");
//변경한 슬롯이 장비슬롯과 관련이 있다면?
if (Item.CheckEquipmentType(fromSlot.SlotMask) || Item.CheckEquipmentType(toSlot.SlotMask))
{
//장비 착용 효과음 재생
SoundManager.Instance.PlaySound2D("Equipment " + SoundManager.Range(1, 2));
//장비 효과 계산
mEquipmentInventory.CalculateEffect();
}
}
}
public enum ItemCode
{
SMALL_HEALTH_POTION,
SMALL_MANA_POTION,
}
case ItemType.Equipment_HELMET:
case ItemType.Equipment_ARMORPLATE:
case ItemType.Equipment_GLOVE:
case ItemType.Equipment_PANTS:
case ItemType.Equipment_SHOES:
- switch문에서 아이템 코드를 확인하여 현재 사용한 아이템이 장비 아이템이라면 특정 기능을 수행하도록 합니다.
//case 1: 아이템 사용을 호출한 슬롯이 장비창이라면?
//장비를 장착 해제해야한다
if (Item.CheckEquipmentType(calledSlot.SlotMask))
/// <summary>
/// 이 아이템은 장비 타입의 아이템인가?
/// </summary>
public static bool CheckEquipmentType(ItemType type)
{
return type >= ItemType.Equipment_HELMET && type <= ItemType.Equipment_SHOES;
}
- Item 클래스입니다. 조건을 확인하기 위한 전역 스태틱 함수로 선언하였습니다.
- 해당 아이템을 사용한 아이템슬롯이 장비 아이템 슬롯인지 확인합니다.
- 만약 장비 아이템 슬롯이라면, 장비 아이템 착용을 해제하는 기능을 수행해야합니다.
using UnityEngine;
/// <summary>
/// 여러 아이템을 담을 가장 기본적인 인벤토리
/// </summary>
public class InventoryMain : InventoryBase
{
...
/// <summary>
/// 해당 아이템을 획득할 수 있는가?
/// </summary>
/// <param name="item">획득하고자 하는 아이템</param>
/// <returns>획득이 가능한 해당 슬롯의 위치</returns>
public InventorySlot IsCanAquireItem(Item item)
{
foreach(InventorySlot slot in base.mSlots)
{
//비어있는 슬롯을 발견한경우
if(slot.Item == null) { return slot; }
//중첩이 가능한 아이템이 같은 아이템의 슬롯을 발견한경우
if(item.CanOverlap && slot.Item.Type == item.Type) {return slot;}
}
return null;
}
}
- InventoryMain 클래스입니다.
- 메인 인벤토리에 여유 공간이 있는지 확인합니다. 여유 공간이 있다면 해당 슬롯을 리턴하고, 해당 슬롯에 장비 아이템을 놓습니다. 기존 장비아이템이 있는 장비 슬롯의 아이템은 사라집니다.
//case 2: 아이템 사용을 호출한 슬롯이 인벤토리라면?
//장비를 장착해야한다.
else
- 장비 아이템 장착 이벤트를 호출한 슬롯이 장비 슬롯이 아닌 일반 아이템 슬롯이라면 장비 아이템을 장착해야합니다.
- 현재 타입에 맞는 장비 아이템슬롯을 획득한 후 일반슬롯의 아이템과 장비슬롯의 아이템을 스왑합니다.
- 장비 슬롯에는 아이템이 없을수도(착용하지 않은 상태)있고, 이미 다른 아이템을 착용중일 수 있습니다. 하지만 어떤 상태에든 상관 없이 아이템을 스왑합니다.
mEquipmentInventory.CalculateEffect();
- 장비 아이템 효과를 계산합니다.
using UnityEngine;
using TMPro;
public class EquipmentInventory : InventoryBase
{
public static bool IsInventoryActive = false; // 인벤토리 활성화 되었는가?
[Header("현재 계산된 수치를 표현할 텍스트 라벨들")]
[SerializeField] private TextMeshProUGUI mDamageLabel;
[SerializeField] private TextMeshProUGUI mDefenseLabel;
private EquipmentEffect mCurrentEquipmentEffect;
/// <summary>
/// 현재 장비 아이템으로 인해 받은 추가 효과
/// </summary>
/// <value></value>
public EquipmentEffect CurrentEquipmentEffect
{
get
{
return mCurrentEquipmentEffect;
}
}
new private void Awake()
{
base.Awake();
}
/// <summary>
/// 장비 아이템을 교환하면 현재 장착한 장비들에 맞게 추가효과를 재 계산
/// </summary>
public void CalculateEffect()
{
EquipmentEffect calcedEffect = new EquipmentEffect();
foreach(InventorySlot slot in mSlots)
{
if(slot.Item == null) { continue; }
calcedEffect += ((Item_Equipment)slot.Item).Effect;
}
mCurrentEquipmentEffect = calcedEffect;
mDamageLabel.text = mCurrentEquipmentEffect.Damage.ToString();
mDefenseLabel.text = mCurrentEquipmentEffect.Defense.ToString();
}
/// <summary>
/// 아이템 타입에 맞는 아이템슬롯을 리턴
/// </summary>
/// <param name="type">리턴받을 슬롯의 아이템 타입</param>
/// <returns></returns>
public InventorySlot GetEquipmentSlot(ItemType type)
{
switch(type)
{
case ItemType.Equipment_HELMET:
{
return mSlots[0];
}
case ItemType.Equipment_ARMORPLATE:
{
return mSlots[1];
}
case ItemType.Equipment_GLOVE:
{
//장갑 슬롯은 두개이기때문에, 빈 슬롯을 우선으로 리턴하도록 한다.
if(mSlots[2].Item == null) { return mSlots[2]; }
if(mSlots[3].Item == null) { return mSlots[3]; }
//둘다 빈 슬롯이 아닌경우 mSlot[2]를 리턴한다.
return mSlots[2];
}
case ItemType.Equipment_PANTS:
{
return mSlots[4];
}
case ItemType.Equipment_SHOES:
{
return mSlots[5];
}
}
Debug.Log("없음");
return null;
}
private void Update()
{
//옵션이 켜져있는경우 비활성화
if(GameMenuManager.IsOptionActive) { return; }
if (Input.GetKeyDown(KeyManager.Instance.GetKeyCode("Equipment")))
{
if (mInventoryBase.activeInHierarchy)
{
mInventoryBase.SetActive(false);
IsInventoryActive = false;
UtilityManager.TryLockCursor();
}
else
{
mInventoryBase.SetActive(true);
IsInventoryActive = true;
UtilityManager.UnlockCursor();
}
}
}
}
public void CalculateEffect()
- 장비 아이템의 효과를 계산합니다.
- 장비 아이템 효과를 담는 구조는 다음과 같습니다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
/// <summary>
/// 장비 효과
/// </summary>
[Serializable] public struct EquipmentEffect
{
[Header("추가 공격력")]
[SerializeField] private float mDamage;
public float Damage
{
get
{
return mDamage;
}
}
[Header("추가 방어력")]
[SerializeField] private float mDefense;
public float Defense
{
get
{
return mDefense;
}
}
public static EquipmentEffect operator +(EquipmentEffect param1, EquipmentEffect param2)
{
EquipmentEffect calcedEffect;
calcedEffect.mDamage = param1.mDamage + param2.mDamage;
calcedEffect.mDefense = param1.mDefense + param2.mDefense;
return calcedEffect;
}
}
[CreateAssetMenu(fileName = "Item", menuName = "Add Item/Item(Equipment)")]
public class Item_Equipment : Item
{
[Space(50)] [Header("장비 아이템 효과 (착용시 발동)")]
[SerializeField] private EquipmentEffect mEffect;
/// <summary>
/// 해당 아이템을 장착했을 때 받는 추가 효과
/// </summary>
/// <value></value>
public EquipmentEffect Effect
{
get
{
return mEffect;
}
}
}
foreach(InventorySlot slot in mSlots)
{
if(slot.Item == null) { continue; }
calcedEffect += ((Item_Equipment)slot.Item).Effect;
}
mCurrentEquipmentEffect = calcedEffect;
- 장비 인벤토리를 모두 확인하여 아이템이 있다면, 해당 아이템에서 효과를 받아 모두 더하여 mCurrentEquipmentEffect에 replace해줍니다.
- += 연산을 위해 연산자 오버라이드를 해주었습니다.
✅ 사용
//PlayerController.cs
...
public class PlayerController : BaseFSM
{
...
new public float AttackDamage
{
get
{
return base.AttackDamage + mEquipmentInventory.CurrentEquipmentEffect.Damage;
}
}
[Space(10)]
[Header("플레이어 장비 아이템의 효과")]
[SerializeField] private EquipmentInventory mEquipmentInventory;
...
}
- 플레이어 컨트롤러에서 EquipmentInventory를 참조하여 계산된 값을 더하여 공격력을 리턴해줍니다.
✅ 테스트
- 아이템을 착용하면 공격력이 100이 올라 늑대 몬스터가 바로 죽은것을 볼 수 있습니다.
'unity game modules' 카테고리의 다른 글
[유니티] 다이얼로그박스 시스템 (스크립트로 만드는 다이얼로그박스) (0) | 2023.02.10 |
---|---|
[유니티] 특정 오브젝트 바라보게 하기 - Animation Rigging (0) | 2023.02.07 |
[유니티] 인벤토리 시스템(번외) - 아이템 툴팁 (0) | 2023.02.06 |
[유니티] 레터박스(Lettering Box) 구현하기 (0) | 2023.01.06 |
[유니티] VideoPlayer URL 재생 및 관리 (0) | 2023.01.02 |