유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (3)

2025. 5. 23. 20:42·스파르타코딩클럽_Unity개발과정

인벤토리, 아이템 사용, 버프 효과 적용

당근을 사용하면 체력 회복 + 이동 속도 버프

using System.Collections.Generic;
using UnityEngine;

public class Inventory : MonoBehaviour
{
    [SerializeField, Tooltip("인벤토리 보이는 부분 전체")] private Transform visiblePart;
    [SerializeField, Tooltip("슬롯의 프리펩")] private Transform slotPrefab;
    [SerializeField, Tooltip("입벤토리 정렬 패널")] private Transform layoutPanel;
    [SerializeField, Tooltip("아이템 정보 출력 패널")] private InfoPanel infoPanel;
    
    // 인벤토리 아이템 정보
    List<Slot> slots = new List<Slot>();
    
    public List<Slot> GetSlots { get { return slots; } }

    void Start()
    {
        // 아이템 추가 메서드를 플레이어의 액션에 등록
        GameManager.Instance.Player.addItem += AddItem;
    }

    // 인벤토리 활성/비활성
    public void ActiveChange()
    {
        // 활성/비활성 전환
        visiblePart.gameObject.SetActive(!visiblePart.gameObject.activeSelf);
        
        // 인벤토리 출현 때는 마우스가 나타나고 닫을 때는 마우스가 사라지도록
        if(visiblePart.gameObject.activeSelf)
            Cursor.lockState = CursorLockMode.None;
        else
            Cursor.lockState = CursorLockMode.Locked;
    }

    // 인벤토리에 아이템 추가
    void AddItem(InventorySlot addItemInfo)
    {
        // 쌓을 수 있는 아이템이라면
        if (addItemInfo.item.canStack)
        {
            // 얼만큼 쌓아야하는지 추적하기 위한 변수
            int stackAmount = addItemInfo.count;
            // 기존의 칸들에 동일한 아이템 슬롯이 있다면 쌓기
            for (int i = 0; i < slots.Count; i++)
            {
                // 동일한 아이템이면
                if (slots[i].slotInfo.item.id == addItemInfo.item.id)
                {
                    // 해당 칸에 쌓고 남는 값 반환
                    stackAmount = slots[i].AddCount(addItemInfo.count);
                    // 해당 칸에 다 쌓아서 남는 갯수가 없다면, 더 이상 루프를 진행할 필요가 없기에 break
                    if(stackAmount.Equals(0))
                        break;
                }
            }
            
            // 새로 슬롯을 생성하면서 넣어주기
            // 쌓으려는 수를 모두 인벤토리에 넣었다면 끝!
            while (stackAmount > 0)
            {
                // 새 칸 생성 및 아이템 정보 표시, 적재하지 못한 값을 반환
                stackAmount = CreateASlot(addItemInfo);
            }
        }
        // 적재 불가능한 아이템이라면 새 슬롯 생성
        else
        {
            CreateASlot(addItemInfo);
        }
    }

    int CreateASlot(InventorySlot addItemInfo)
    {
        int exceedAmount = 0;
        // 인벤토리 새로 한칸 생성하여 인벤토리 자동정렬, Slot 컴포넌트를 가져오는데 성공하였다면
        if (Instantiate(slotPrefab.gameObject, layoutPanel).TryGetComponent(out Slot slot))
        {
            // 정보를 넘겨주고 이를 표시
            exceedAmount = slot.CreateSlot(addItemInfo);
            // 슬롯 리스트에 추가
            slots.Add(slot);
        }
        
        return exceedAmount;
    }

    public void OpenInfoPanel(int slotIndex)
    {
        // 해당 슬롯으로 위치 이동
        infoPanel.transform.position = slots[slotIndex].transform.position;
        // 활성화
        infoPanel.gameObject.SetActive(true);
        // 정보 표시
        ItemData data = slots[slotIndex].slotInfo.item;
        infoPanel.InitInfos(data.itemName, data.description);
    }
    public void CloseInfoPanel()
    {
        infoPanel.gameObject.SetActive(false);
    }
}

public class InventorySlot
{
    public ItemData item; // 아이템 정보
    public int count; // 보유 갯수

    public InventorySlot(ItemData _item, int _count)
    {
        item = _item;
        count = _count;
    }
}

인벤토리는 아이템을 획득함에 따라 슬롯을 생성합니다.

한 칸에 쌓는 양을 초과한 아이템은 새로운 슬롯을 생성하여 그곳에 쌓습니다.

 

using UnityEngine;
using UnityEngine.UI;
using TMPro;
using UnityEngine.EventSystems;

public class Slot : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler, IDragHandler, IEndDragHandler
{
    [SerializeField] private Image icon;
    [SerializeField] private TextMeshProUGUI count;

    public InventorySlot slotInfo { get; private set; }

    // 아이템 슬롯을 생성할 때 호출
    public int CreateSlot(InventorySlot _inventorySlot)
    {
        // 아이템 정보 가지고 있게끔
        slotInfo = _inventorySlot;
        // 아이콘 표시
        icon.sprite = slotInfo.item.icon;
        // 수량 표시
        int exceedAmount = AddCount(0);
        // 넘치는 수량을 반환
        return exceedAmount;
    }
    
    // 아이템 수량 증가
    public int AddCount(int amount)
    {
        // 수량 증가, 표시
        slotInfo.count += amount;
        count.text = Mathf.Clamp(slotInfo.count, 0, slotInfo.item.maxStack).ToString();

        // 한 칸에 최대로 쌓을 수 있는 갯수를 초과하는 아이템 수
        int exceedAmount = slotInfo.count - slotInfo.item.maxStack;
        // 최대 적재량을 넘었다면 그만큼을 반환
        if (exceedAmount > 0)
            return exceedAmount;
        // 넘지 않았다면 0을 반환
        else
            return 0;
    }

    void UseOrEquipItem()
    {
        switch (slotInfo.item.type)
        {
            // 소비 아이템
            case ItemType.Consume:
                // 해당 아이템 특수 효과 발동
                slotInfo.item.UseEffect?.Invoke();
                // 해당 소비 아이템의 소비 속성 수만큼
                for(int i = 0; i < slotInfo.item.consumables.Length; i++)
                {
                    // 해당 속성을 value에 맞게 회복
                    switch (slotInfo.item.consumables[i].type)
                    {
                        case ConsumableType.Health:
                            GameManager.Instance.Player.status.Heal(slotInfo.item.consumables[i].value);
                            break;
                    }
                }
                
                // 수량 1개 감소 >> 0개 이하가 되었다면 
                if (--slotInfo.count <= 0)
                {
                    // 아이템 설명 패널 사라지게
                    GameManager.Instance.Inventory.CloseInfoPanel();
                    // 해당 슬롯 파괴
                    GameManager.Instance.Inventory.GetSlots.Remove(this);
                    Destroy(gameObject);
                }
                // 수량 감소 표시
                else
                    count.text = slotInfo.count.ToString();
                break;
            
            // 장착 아이템
            case ItemType.Equip:
                break;
            default:
                break;
        }
    }

    // 해당 슬롯 위에 마우스가 올라갔을 때
    public void OnPointerEnter(PointerEventData eventData)
    {
        GameManager.Instance.Inventory.OpenInfoPanel(transform.GetSiblingIndex());
    }

    // 해당 슬롯 위에서 마우스가 벗어났을 때
    public void OnPointerExit(PointerEventData eventData)
    {
        GameManager.Instance.Inventory.CloseInfoPanel();
    }

    public void OnPointerClick(PointerEventData eventData)
    {
        if (eventData.button == PointerEventData.InputButton.Right)
        {
            UseOrEquipItem();
        }
    }

    // 해당 오브젝트 드래그 도중
    public void OnDrag(PointerEventData eventData)
    {
        // 마우스 위치를 오브젝트가 따라다니게끔
    }

    // 드래그가 끝날 때
    public void OnEndDrag(PointerEventData eventData)
    {
        // 마우스 위치가 인벤토리 밖이라면
        // 아이템 버리기
    }
}

인벤토리 슬롯은 기능을 만들던 도중에 제출 시간이 되어 여기까지 했습니다.

마우스 동작 관련 인터페이스를 이용하여 마우스가 해당 슬롯 위에 있을 때 정보 창이 뜨고

마우스가 슬롯 위를 벗어나면 사라지게끔 했으며

우클릭을 하면 아이템 사용/장착 메서드를 호출했습니다.

 

using UnityEngine;

[CreateAssetMenu(fileName = "ItemUseEffects", menuName = "New ItemEffects")]
public class ItemUseEffects : ScriptableObject
{
    public void Carrot_UseEffect()
    {
        // 이동 속도 2배
        float speed = 2 * GameManager.Instance.Player.control.GetOriginSpeed;
        // 지속 시간
        float time = 10f;
        
        GameManager.Instance.Player.control.Call_Change_MoveSpeed(speed, time);
    }
}

아이템 데이터를 스크립터블 데이터로 관리했는데

아이템에 유니티 이벤트로 메서드를 등록하려 하니 일반적인 스크립트로는 안되어서

특수능력 메서드들을 모아둘 스크립터블 오브젝트를 하나 생성하였습니다.

 

    // 일정 시간 동안 이동속도 증가
    public void Call_Change_MoveSpeed(float _moveSpeed, float _time)
    {
        if(ChangingMoveSpeed != null)
            StopCoroutine(Change_MoveSpeed(_moveSpeed, _time));
        StartCoroutine(Change_MoveSpeed(_moveSpeed, _time));
    }
    
    IEnumerator Change_MoveSpeed(float _moveSpeed, float _time)
    {
        // 바꿀 속도로 변화
        moveSpeed = _moveSpeed;
        // 버프 시간 동안 유지
        yield return new WaitForSeconds(_time);
        // 원래 속도로 되돌아가기
        moveSpeed = moveSpeedOrigin;
    }

이동 속도 변화 버프는 코루틴으로 일정 시간 동안 변화 후 다시 원래대로 돌아오도록 만들었습니다.

아마 시간이 조금 더 있었다면 스텟 종류별 버프를 모아 관리하는 버프매니저를 만들어서 처리하였을 듯 합니다.

저작자표시 비영리 동일조건 (새창열림)

'스파르타코딩클럽_Unity개발과정' 카테고리의 다른 글

유니티 숙련 팀 과제 - 서바이벌 (2)  (0) 2025.05.27
유니티 숙련 팀 과제 - 서바이벌 (1)  (1) 2025.05.26
유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (2)  (0) 2025.05.22
유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (1)  (0) 2025.05.21
유니티 숙련 (3) - 서바이벌 강의 완강, 새로 배운 내용 정리  (0) 2025.05.20
'스파르타코딩클럽_Unity개발과정' 카테고리의 다른 글
  • 유니티 숙련 팀 과제 - 서바이벌 (2)
  • 유니티 숙련 팀 과제 - 서바이벌 (1)
  • 유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (2)
  • 유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (1)
ybbro
ybbro
대부분의 포스팅은 pc에서 작성되었습니다. 모바일에서 볼 때 설명이 잘리면 데스크탑 모드를 사용해보길 바랍니다.
  • ybbro
    어떻게든 굴리는 게임 공방
    ybbro
  • 전체
    오늘
    어제
    • 전체 N
      • 스파르타코딩클럽_Unity개발과정 N
      • Unity 2D
        • 카드게임
        • 플랫포머 게임
        • 뱀서라이크
      • Unity 3D
        • 닷지
        • 유니티 짱
        • 디펜스 게임
      • Unity 에러 노트
      • 기능 구현 방법 정리
      • 셰이더 그래프
        • 2D
        • 3D
      • 프로그래머스
      • 자료구조
      • 기타
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    텍스트매시프로
    룰렛
    세이브
    삭제
    유니티
    잔상
    유니티 애니메이터 파라미터 초기화
    hello
    64비트
    다크모드
    마스크
    대시
    앱이 휴대전화와 호환되지 않아 설치되지 않았습니다
    갤럭시 S24
    unity
    직렬화
    스파인
    sprite mask
    UI
    무료스킨
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
ybbro
유니티 숙련 개인 과제 - 3D 게임 캐릭터 이동과 물리 (3)
상단으로

티스토리툴바