[Unity] Item, ItemManager : (아이템, 인벤토리)

Item

Item의 역할

  • 아이템 타입을 명시해 준다.
  • 아이템과 플레이어가 충돌했을 경우 아이템이 흡수되는(자석 아이템 효과)를 만들어준다.
  • 아이템이 획득되었을 경우 효과를 담당한다.(현재는 기능이 비어있음)

 

 

필요한 변수들 선언

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;

public class Item : MonoBehaviour
{
    public enum ItemType
    {
        Coin1, Coin5, Coin10, Coin15, Key, CardPack, ForcedDeletion, ProgramRemove, ProgramRecycle, Heal, TemHp, Shiled, Spark
    }

    public int itemScore;
    private GameManager gameManager;
    private StatusManager statusManager;
    private ItemManager itemManager;
    public ItemType itemType;
    public string ItemName;
    public string ItemInfomation;
    public int ItemSize;
    public bool IsUsable = true;
    public bool IsDeletable = true;
    private bool isPickedUp = false;

    // 물리력
    public float PushForce;
    public float DragForce; 

    Rigidbody2D rb = null;

    // 아이템 흡수 효과
    public float followSpeed = 10f;
    public float GetDistance = 1f; // 흡수 거리 (Destroy 조건)
    public float maxDistance = 10f; // 최대 추적 거리
    private Transform playerTransform;
    private bool isTracking = false;
    public bool isDroped = false;

    ...
}

중요한 변수는 다음과 같다.

  • isTracking : 아이템이 흡수되는 효과를 주기 시작하는 Flag
  • isDroped : 아이템이 버려진 후 바로 흡수되는 것을 막기 위한 Flag

 

 

기본 함수(Start, Update)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;

public class Item : MonoBehaviour
{
    ...
    
    // Start is called before the first frame update
    void Start()
    {
        gameManager = GameManager.Instance;
        statusManager = StatusManager.Instance;
        itemManager = ItemManager.Instance;
        rb = GetComponent<Rigidbody2D>();
    }
     
    // Update is called once per frame
    void Update()
    {
        if (isTracking && playerTransform != null)
        {
            float distanceToPlayer = Vector2.Distance(transform.position, playerTransform.position);

            if (distanceToPlayer <= GetDistance)
            {
                // 흡수 범위 이내: 아이템 획득
                ItemTypeToFun();
            }
            else if (distanceToPlayer > maxDistance)
            {
                // 최대 거리 이상: 추적 중단
                isTracking = false;
            }
            else
            {
                // 추적: 플레이어 추적
                Vector3 directionToPlayer = (playerTransform.position - transform.position).normalized;
                transform.position += directionToPlayer * followSpeed * Time.deltaTime;
            }
        }
    }
    
    ...
}

Start()

HP를 표시하는 UI에서 StatusManager에서 HP를 가지고 와서 사용해야 하기 때문에 싱글톤 인스턴스에 접근하도록 만들고, HP UI를 세팅해 주는 함수를 호출했다.

Update()

흡수 효과 Flag가 활성화되어 있을 때 프레임 단위로 플레이어를 추적한다.

 

 

OnTriggerEnter2D

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;

public class Item : MonoBehaviour
{
    ...
    
    private void OnTriggerEnter2D(Collider2D collision)
    {
        if (isPickedUp) return;

        if (collision.gameObject.CompareTag("Player"))
        {
            // 용량 부족한 경우
            if (statusManager.MaxStorage - statusManager.CurrentStorage < ItemSize)
            {
                isTracking = false;
                if (rb != null)
                {
                    Vector2 pushDirection = (transform.position - collision.transform.position).normalized;

                    // Add a small force to move the item
                    float pushForce = PushForce;
                    rb.drag = DragForce;
                    rb.AddForce(pushDirection * pushForce);
                    StartCoroutine(StopAfterDelay(0.5f));
                }
                else
                {
                    Debug.LogError("rb is null");
                }
            }
            else
            {
                Debug.Log("흡수 효과 on");
                // 흡수 효과 
                playerTransform = collision.transform;
                if(isDroped == false)
                    isTracking = true;
            }
        }
    }
    
    ...
}

인벤토리 용량이 충분한 경우 Rigidbody2D를 이용해 오브젝트를 이동시킨다.

 

 

아이템 획득 처리

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;

public class Item : MonoBehaviour
{
    ...
    

    private void ItemTypeToFun()
    {
        // 아이템 기능
        switch (itemType)
        {
            case ItemType.Coin1:
            case ItemType.Coin5:
            case ItemType.Coin10:
            case ItemType.Coin15:
                CoinItem();
                break;
            case ItemType.Key:
                KeyItem();
                break;
            case ItemType.CardPack:
                CardPackItem();
                break;
            case ItemType.ForcedDeletion:
                ForcedDeletionItem();
                break;
            case ItemType.ProgramRemove:
                ProgramRemoveItem();
                break;
            case ItemType.ProgramRecycle:
                ProgramRecycleItem();
                break;
            case ItemType.Heal:
                HealItem();
                break;
            case ItemType.TemHp:
                TemHpItem();
                break;
            case ItemType.Shiled:
                ShiledItem();
                break;
            case ItemType.Spark:
                SparkItem();
                break;
        }
    }

    private void AddItem()
    {
        if (itemManager != null)
        {
            if (itemManager.AddItem(this))
            {
                Destroy(gameObject);
                isPickedUp = true;
            }
            else
                Debug.Log("Do not add item");
        }
        else
        {
            Debug.Log("ItemManager is not find");
        }
    }


    IEnumerator StopAfterDelay(float delay)
    {
        yield return new WaitForSecondsRealtime(delay);
        rb.velocity = Vector2.zero;
    }


}

ItemTypeToFun에서 획득한 아이템의 타입별로 함수를 나눠 실행해 준다.

각각의 기능을 수행한 후 AddItem을 이용해 Manager에 아이템을 넘겨준다.

 

 

아이템 타입별 획득 기능

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;

public class Item : MonoBehaviour
{
    ...

    // Item Usage Effect Section
    private void CoinItem()
    {
        Debug.Log("Item CoinItem");
        statusManager.CoinUp(itemScore);
        if (itemManager != null)
        {
            if (itemManager.AddItem(this))
            {
                Destroy(gameObject);
                isPickedUp = true;
            }
            else
                Debug.Log("Do not add item");
        }
        else
        {
            Debug.Log("ItemManager is not find");
        }
    }

    private void KeyItem()
    {
        AddItem();
        Debug.Log("키 기능 구현 안되어 있음");
    }

    private void CardPackItem()
    {
        AddItem();
        Debug.Log("카드팩 기능 구현 안되어 있음");
    }

    private void ForcedDeletionItem()
    {
        AddItem();
        Debug.Log("강제삭제 기능 구현 안되어 있음");
    }

    private void ProgramRemoveItem()
    {
        AddItem();
        Debug.Log("제거툴 기능 구현 안되어 있음");
    }

    private void ProgramRecycleItem()
    {
        AddItem();
        Debug.Log("프로그램 재활용 기능 구현 안되어 있음");
    }

    private void HealItem()
    {
        Debug.Log("힐 아이템 기능 구현 안되어 있음");
    }

    private void TemHpItem()
    {
        Debug.Log("임시체력 아이템 기능 구현 안되어 있음");
    }

    private void ShiledItem()
    {
        Debug.Log("실드 아이템 기능 구현 안되어 있음");
    }

    private void SparkItem()
    {
        Debug.Log("전기 아이템 기능 구현 안되어 있음");
    }
}

아직은 기능이 기획되지 않아서 별다른 코드는 없고, 미리 함수만 만들어뒀다.

 

 

ItemManager 

ItemManager의 역할

  • 획득한 아이템 리스트를 관리한다.
  • 아이템의 획득/삭제를 담당한다.
  • 아이템을 드래그 앤 드롭으로 버리기와 버리기 버튼을 사용해서 버리는 기능을 구현한다.
  •  HUD에 아이템의 개수를 띄워주는 데 사용하는 아이템 개수를 카운트해 준다.
  • 획득된 아이템 리스트들을 정렬해 준다.
  • MyDocument에서 사용할 이미지 경로를 지정해 준다.

 

 

필요한 변수들 선언

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    public static ItemManager Instance;

    public SortedDictionary<string, List<Item>> itemList;

    private StatusManager statusManager;
    private UIManager uIManager;
    private UI_0_HUD ui_0_HUD;
    private Player player;
    Rigidbody2D rb;

    // 아이템 프리펩
    public GameObject P_Coin1;
    public GameObject P_Coin5;
    public GameObject P_Coin10;
    public GameObject P_Coin15;
    public GameObject P_Key;
    public GameObject P_CardPack;
    public GameObject P_ForcedDeletion;
    public GameObject P_ProgramRemove;
    public GameObject P_ProgramRecycle;
    public GameObject P_Heal;
    public GameObject P_TemHp;
    public GameObject P_Shiled;
    public GameObject P_Spark;

    GameObject SpawnObject = null;
    GameObject droppedItem = null;

    // ItemType을 키로, 이미지 인덱스를 값으로 저장하는 Dictionary
    public static Dictionary<ItemType, int> ImageIndexMap = new Dictionary<ItemType, int>()
    {
        { ItemType.Coin1, 0 },
        { ItemType.Coin5, 1 },
        { ItemType.Coin10, 6 },
        { ItemType.Coin15, 11 },
        { ItemType.Key, 2 },
        { ItemType.CardPack, 1 },
        { ItemType.ForcedDeletion, 4 },
        { ItemType.ProgramRemove, 0 },
        { ItemType.ProgramRecycle, 3 },
        { ItemType.Heal, 7 }

    };

    ...
}

중요한 변수는 딱히 없다.

 

 

기본 함수(Awake, Start, Update)

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    private void Awake()
    {
        if (Instance == null)
        {
            Instance = this;
            itemList = new SortedDictionary<string, List<Item>>(new CoinComparer());
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }

    void Start()
    {
        statusManager = StatusManager.Instance;
        uIManager = UIManager.Instance;
        ui_0_HUD = UI_0_HUD.Instance;
        player = GameManager.Instance.GetPlayer();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    
    ...
}

Awake()

싱글톤 인스턴스 설정과 씬 전환 시 객체를 유지하게 만들고, 현재 오브젝트가 기존 인스턴스와 다른 경우 파괴하도록 만들어줬다. 추가로 list를 생성해서 할당해 줬다.

Start()

다양한 매니저를 사용하기 위해 할당해 줬다.

 

 

AddItem, RemoveItem

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    public bool AddItem(Item item)
    {
        if (statusManager.CurrentStorage + item.ItemSize > statusManager.MaxStorage)
        {
            Debug.Log("공간부족으로 아이템 먹기 불가능");
            return false;
        }

        Debug.Log("ItemManager AddItem"); 
        if (itemList.ContainsKey(item.ItemName))
        {
            // 같은 이름의 아이템이 이미 존재하는 경우 리스트에 추가
            itemList[item.ItemName].Add(item);
        }
        else
        {
            // 새로운 이름의 아이템이면 리스트 생성 후 추가
            itemList[item.ItemName] = new List<Item> { item };
        }

        ui_0_HUD.UpdateHUD();
        statusManager.CurrentStorage += item.ItemSize;
        return true;
    }

    public void RemoveItem(Item item)
    {
        if (itemList.ContainsKey(item.ItemName))
        {
            statusManager.CurrentStorage -= item.ItemSize;
            itemList[item.ItemName].Remove(item);
            if (itemList[item.ItemName].Count == 0)
            {
                itemList.Remove(item.ItemName);
                ui_0_HUD.UpdateHUD();
            }
        }
    }

    ...
}

여기서 중요하게 볼 부분은 아이템의 리스트가 존재하는 경우에 이름을 이용해 추가하고, 없는 경우에는 이름을 가지고 생성해서 추가한다는 부분이다.

이후 HUD에서 사용되는 아이템이 추가된 경우를 위해 HUD를 Update해주는 함수 호출해 줬다.

RemoveItem은 아이템을 버리거나 사용했을 경우 마지막에 호출되는 함수로, 리스트에서 아이템을 제거하고 저장공간을 되돌리는 기능만을 모아둔 함수이다.

 

 

DropItem, DropDownItem

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    public void DropItem(Item item, bool isRight)
    {
        RemoveItem(item);

        DesignateGameObject(item.itemType);

        Vector3 offset = player.transform.position + (isRight ? new Vector3(0.9f, 0, 0) : new Vector3(-0.8f, 0, 0));
        droppedItem = Instantiate(SpawnObject, offset, Quaternion.identity);

        droppedItem.GetComponent<Item>().isDroped = true;

        Vector3 pushDirection = (droppedItem.transform.position - player.transform.position).normalized;
        rb = droppedItem.GetComponent<Rigidbody2D>();
        rb.drag = item.DragForce;
        rb.AddForce(pushDirection * item.PushForce, ForceMode2D.Impulse);
        StartCoroutine(StopAfterDelay(0.3f));
    }

    public void DropDownItem(Item item)
    {
        RemoveItem(item);

        DesignateGameObject(item.itemType);

        Vector3 offset = player.transform.position + new Vector3(0, -1.2f, 0); 
        droppedItem = Instantiate(SpawnObject, offset, Quaternion.identity);

        droppedItem.GetComponent<Item>().isDroped = true;

        Vector3 pushDirection = (droppedItem.transform.position - player.transform.position).normalized;
        rb = droppedItem.GetComponent<Rigidbody2D>();
        rb.AddForce(pushDirection * 1.5f, ForceMode2D.Impulse);
        
        StartCoroutine(StopAfterDelay(0.3f));
    }

    private void DesignateGameObject(Item.ItemType itemType)
    {
        switch (itemType)
        {
            case Item.ItemType.Coin1: SpawnObject = P_Coin1; break;
            case Item.ItemType.Coin5: SpawnObject = P_Coin5; break;
            case Item.ItemType.Coin10: SpawnObject = P_Coin10; break;
            case Item.ItemType.Key: SpawnObject = P_Key; break;
            case Item.ItemType.CardPack: SpawnObject = P_CardPack; break;
            case Item.ItemType.ForcedDeletion: SpawnObject = P_ForcedDeletion; break;
            case Item.ItemType.ProgramRemove: SpawnObject = P_ProgramRemove; break;
            case Item.ItemType.ProgramRecycle: SpawnObject = P_ProgramRecycle; break;
            case Item.ItemType.Heal: SpawnObject = P_Heal; break;
            case Item.ItemType.TemHp: SpawnObject = P_TemHp; break;
            case Item.ItemType.Shiled: SpawnObject = P_Shiled; break;
            case Item.ItemType.Spark: SpawnObject = P_Spark; break;
        }
    }

    IEnumerator StopAfterDelay(float delay)
    {
        yield return new WaitForSeconds(delay);
        droppedItem.GetComponent<Item>().isDroped = false;
        rb.velocity = Vector2.zero;
    }
    
    ...
}

이 부분은 HUD를 업데이트해 주는 부분이다.

 

 

정렬 관련 함수

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    // 코인 순서에 맞춰 정렬하기 위한 비교기
    public class CoinComparer : IComparer<string>
    {
        private readonly Dictionary<string, int> customOrder = new Dictionary<string, int>
        {
            { "(1) 메가 바이트 코인", 1 },
            { "(5) 메가 바이트 코인", 2 },
            { "(10) 메가 바이트 코인", 3 },
            { "(15) 메가 바이트 코인", 4 }
        };

        public int Compare(string x, string y)
        {
            // 커스텀 정렬 순서가 있는 경우, 해당 순서로 정렬
            if (customOrder.ContainsKey(x) && customOrder.ContainsKey(y))
            {
                return customOrder[x].CompareTo(customOrder[y]);
            }
            // 코인이 아닌 경우 사전 순으로 정렬
            return x.CompareTo(y);
        }
    }


    // 내림차순 정렬을 위한 클래스
    public class DescendingComparer<T> : IComparer<T> where T : IComparable<T>
    {
        public int Compare(T x, T y)
        {
            return y.CompareTo(x);
        }
    }
    
    ...
}

단순히 리스트를 정렬해 주는 함수이다.코인은 별도로 순서를 정렬해 주고, 나머지는 사전순으로 정렬한다.

 

 

GetCount

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    public int GetKeyCount()
    {
        int KeyCount = 0;
        if (ItemManager.Instance.itemList.ContainsKey("잠금파일 해독 키"))
        {
            KeyCount = ItemManager.Instance.itemList["잠금파일 해독 키"].Count;
        }
        return KeyCount;
    }

    public int GetCoinCount()
    {
        int CoinCount = 0;
        if (ItemManager.Instance.itemList.ContainsKey("(1) 메가 바이트 코인"))
        {
            CoinCount += ItemManager.Instance.itemList["(1) 메가 바이트 코인"].Count;
        }
        if (ItemManager.Instance.itemList.ContainsKey("(5) 메가 바이트 코인"))
        {
            CoinCount += 5 * ItemManager.Instance.itemList["(5) 메가 바이트 코인"].Count;
        }
        if (ItemManager.Instance.itemList.ContainsKey("(10) 메가 바이트 코인"))
        {
            CoinCount += 10 * ItemManager.Instance.itemList["(10) 메가 바이트 코인"].Count;
        }
        if (ItemManager.Instance.itemList.ContainsKey("(15) 메가 바이트 코인"))
        {
            CoinCount += 15 * ItemManager.Instance.itemList["(15) 메가 바이트 코인"].Count;
        }
        return CoinCount;
    }

    public int GetBombCount()
    {
        int BombCount = 0;
        if (itemList.ContainsKey("강제삭제 포로토콜"))
        {
            BombCount = itemList["강제삭제 포로토콜"].Count;
        }
        return BombCount;
    }

    ...
}

 HUD에서 아이템 개수를 업데이트하기 위해 각각의 아이템의 개수를 카운트해 주는 함수이다.

 

 

이미지 관련 함수

using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    public int GetImageIndex(ItemType itemType)
    {
        if (ImageIndexMap.TryGetValue(itemType, out int index))
        {
            return index;
        }

        Debug.LogWarning($"Image index for {itemType} not found.");
        return -1; // 기본값 반환
    }

    public string GetSpriteSheetName(ItemType itemType)
    {
        string spriteSheetName = "";
        switch (itemType)
        {
            case Item.ItemType.Coin1:
            case Item.ItemType.Coin5:
            case Item.ItemType.Coin10:
            case Item.ItemType.Coin15:
                spriteSheetName = "Item/use_Coin";
                break;
            case Item.ItemType.Key:
            case Item.ItemType.CardPack:
            case Item.ItemType.ForcedDeletion:
            case Item.ItemType.ProgramRemove:
            case Item.ItemType.ProgramRecycle:
                spriteSheetName = "Item/use_DropItem";
                break;
            case Item.ItemType.Heal:
            case Item.ItemType.TemHp:
            case Item.ItemType.Shiled:
            case Item.ItemType.Spark:
                spriteSheetName = "Sprites/Items/Heal";
                break;

            default:
                Debug.LogError("Unknown item type.");
                break;
        }
        return spriteSheetName;
    }
    
    ...
}

이미지 타입을 사용해 이미지 인덱스를 불러오고, 아이템 타입별로 리소스 경로를 리턴하는 함수이다.

 

 

타 팀원이 구현한 부분

더보기
using System.Collections;
using System;
using System.Collections.Generic;
using UnityEngine;
using static UnityEditor.Progress;
using static Item;

public class ItemManager : MonoBehaviour
{
    ...

    public bool KeyUse()
    {
        string keyName = "잠금파일 해독 키"; // 찾고자 하는 키의 이름

        // 키 아이템이 itemList에 있고, 해당 리스트에 아이템이 하나 이상 있는지 확인
        if (itemList.ContainsKey(keyName) && itemList[keyName].Count > 0)
        {
            Item keyItem = itemList[keyName][0]; // 첫 번째 아이템 가져오기
            statusManager.CurrentStorage -= keyItem.ItemSize; // 사용 후 저장소 용량 줄이기
            itemList[keyName].Remove(keyItem); // 사용한 아이템 제거

            // 해당 키가 더 이상 없으면 리스트에서 키 자체를 삭제
            if (itemList[keyName].Count == 0)
            {
                itemList.Remove(keyName);
            }

            ui_0_HUD.UpdateHUD(); // HUD 업데이트
            Debug.Log("잠금파일 해독 키 사용됨");
            return true;  // 키 사용 성공
        }
        else
        {
            Debug.Log("사용할 수 있는 잠금파일 해독 키가 없습니다.");
            return false;  // 키가 없으면 실패
        }
    }
}

참고만 해주세요.

 

참고 사항

UI 3 : MyDocument (Inventory UI)

https://gdoo.tistory.com/43

 

[Unity] UI 3 : MyDocument(Inventory UI)

UI MyDocumentUI MyDocument의 역할ItemManager에서 Player가 가지고 있는 아이템 리스트를 시각적으로 생성하여 보여준다.특정 아이템을 클릭했을 때 아이템 정보를 보여준다.아이템을 드래그 앤 드롭으로

gdoo.tistory.com

 

 

 

마무리하며..

아이템은 이후 수정될 부분이 많을 것 같다.