[Unity / C#] 아이템 기능 만들기 #1 - 스캐너

주예성·2025년 6월 25일
post-thumbnail

📋 목차

  1. 현재 QuickSlot의 아이템 들기
  2. 중간 결과 1
  3. 아이템 정보 추가
  4. 스캐너 기능 만들기
  5. 중간 결과 2
  6. 실제 기능 만들기
  7. 최종 결과
  8. 오늘의 배운 점
  9. 다음 계획

🤜 현재 QuickSlot의 아이템 들기

아이템을 쓰려면 현재 QuickSlot에 존재해야하며 플레이어가 들고 있어야 합니다.
저는 이전에 먼저 FreeTestCharacterAsuna라는 무료 패키지를 다운받아 Player로 적용한 상태입니다.
LeftHandRightHand라는 Bone이 존재하는 에셋을 Player로 해주세요.

1. 스크립트 작성

캐릭터가 들고 있는 모션을 관리할 PlayerItemHandler 스크립트를 생성합시다.

using UnityEngine;

public class PlayerItemHandler : MonoBehaviour
{
	// 현재 들고있는 아이템
    [HideInInspector] public ItemData currentItem;

	// 아이템을 달아줄 Bone
    [Header("Hand Bones")]
    public Transform leftHandBone;
    public Transform rightHandBone;

    public void SetHolding(ItemData itemData)
    {
        if (!itemData) return;
        currentItem = itemData;
        switch (itemData.itemType)
        {
            case ItemType.Product:
                if (itemData.itemName == "스캐너")
                {
                    HoldScanner();
                }
                break;
            case ItemType.Weapon:
                // 무기를 들고있는 로직
                break;
            default:
                Debug.LogWarning("알 수 없는 아이템 타입: " + itemData.itemType);
                return;
        }
    }

	// 손에 오브젝트를 생성하는 함수
    private void SpawnAndSetTransform()
    {
    	// 현재 퀵슬롯의 아이템을 오른손(rightHandBone)에 생성
        GameObject holdingItem = Instantiate(currentItem.itemPrefab, rightHandBone.position, rightHandBone.rotation, rightHandBone);

		// 오른손을 부모로 삼고 위치 및 회전 초기화
        holdingItem.transform.SetParent(rightHandBone);
        holdingItem.transform.localPosition = Vector3.zero;
        holdingItem.transform.localRotation = Quaternion.identity;

		// 물리 및 콜리전은 필요없음
        Rigidbody rb = holdingItem.GetComponent<Rigidbody>();
        if (rb) rb.isKinematic = true;

        Collider collider = holdingItem.GetComponent<Collider>();
        if (collider) collider.enabled = false;
    }
	
    // 스캐너를 들고 있는 모션 함수
    private void HoldScanner()
    {
        if (!currentItem) return;
        SpawnAndSetTransform();
        Debug.Log("스캐너를 장착했습니다.");
    }
}

2. Inspector에 Bone적용

각각 Hand_L, Hand_R을 적용시킵니다.


🎮 중간 결과 1

플레이어가 손에 스캐너를 들고 있는 것을 확인할 수 있습니다.


➕ 아이템 정보 추가

1. ID 정보 추가

보통 게임에서 아이템들은 각각 고유 ID를 가지게 됩니다. 그러니 ItemData 스크립트에 public int itemID = -1을 넣어 변수를 추가합시다.
이 ID는 유니티 에디터 내에 임시로 모두 지정합니다.

2. 아이템 배열 초기화

아이템 정보를 불러와야하니 모든 아이템 정보를 담는 배열과 현재 잠금 해제된 아이템을 판별할 배열이 필요합니다. 모든 아이템 정보는 GameManager에 현재 잠금 해제된 아이템은 PlayerState에 넣습니다.

using UnityEngine;

public class GameManager : MonoBehaviour
{
    [Header("Player")]
    public GameObject player;

    public static bool IsUIOpen = false;
    public ItemData[] allItems;

    private PlayerState playerState;
    private void Start()
    {
        playerState = player.GetComponent<PlayerState>();
        GetAllItems();
        InitializeUnlockedItems();
    }

    private void GetAllItems()
    {
    	// Resources 폴더의 Items 폴더안의 'ItemData'정보들을 모두 부름
        allItems = Resources.LoadAll<ItemData>("Items");
        
        // itemID를 기준으로 순서대로 나열함
        // 이렇게 하면 unlockedItems의 인덱스가 allItems의 인덱스, itemID와 같아져 편의성이 높아짐
        Array.Sort(allItems, (a, b) => a.itemID - b.itemID);
    }

    private void InitializeUnlockedItems()
    {
    	// allItems 길이만큼 bool형의 배열 생성
        playerState.unlockedItems = new bool[allItems.Length];
		
        // 처음엔 제작가능 아이템을 모두 false로 지정
        foreach (ItemData item in allItems)
        {
            playerState.unlockedItems[item.itemID] = false;
        }
    }
}

3. 이전 스크립트 수정

기존에는 PlayerStateunlockedItemsItemData의 배열로 저장했었습니다. 그것을 bool형의 배열로 바꾸다보니 BenchSlotBench에서 오류가 발생할 것입니다.
그 이유는 ItemData형에 bool형을 집어넣어서 생긴 일입니다. 그러니 unlockedItems 배열의 각 요소인 인덱스를 이용하여 ItemData 자체가 아니라 allItems의 itemID를 이용하여 ItemData를 불러오는 형식으로 바꿔주세요.


🔍 스캐너 기능 만들기

여기서 스캐너(Scanner)는 제작대에서 제작할 수 있는 아이템 재료를 알아내는 기능으로 한 아이템을 직접 제작하고 싶다면 꼭 필요한 아이템 입니다.
스캐너는 우클릭 시 사용가능하도록 하겠습니다.

1. 우클릭 감지

// PlayerController.cs

void Update()
{
	if (!GameState.IsUIOpen)
    {
    	HandleUseItem();
    }
}

void HandleUseItem()
{
	// 우클릭시 아이템 사용
    if (Input.GetMouseButtonDown(1) && playerItemHandler)
    {
        playerItemHandler.UseItem();
        Debug.Log("아이템을 사용합니다");
    }
}

2. 현재 들고 있는 아이템 정보를 가져오기

// PlayerItemHandler.cs

public void UseItem()
{
    if (!currentItem) return;
    
    if (Input.GetMouseButtonDown(1))
    {
        TryUseItem();
    }
}

private void TryUseItem()
{
    if (!currentItem) return;

    UseItem useItem = GetComponent<UseItem>();
    if (useItem)
    {
        useItem.UseItems(currentItem);
    }
}

3. 아이템 이름을 기준으로 기능 나누기

아이템 사용 스크립트인 UseItem 스크립트를 생성합니다.

// UseItem.cs

using UnityEngine;

public class UseItem : MonoBehaviour
{
    private PlayerState playerState;
    private void Start()
    {
        playerState = GetComponent<PlayerState>();
    }

    
}

4. unlockedItems 정보 변경

// PlayerState.cs

using UnityEngine;

public class UseItem : MonoBehaviour
{
    private PlayerState playerState;
    private void Start()
    {
        playerState = GetComponent<PlayerState>();
    }

    public void UseItems(ItemData itemData)
    {
        switch(itemData.itemName)
        {
            case "스캐너":
                if (!playerState) return;
                UseScanner(itemData);
                break;
            default:
                return;
        }
    }

    private void UseScanner(ItemData itemData)
    {
        playerState.AddUnlockedItems(itemData);
        Debug.Log("스캐너 사용 완료!");
    }
}
// PlayerState.cs

public void AddUnlockedItems(ItemData itemData)
{
    unlockedItems[itemData.itemID] = true; 
}

🎮 중간 결과 2

우클릭을 하면 스캐너를 사용했다는 로그가 뜹니다.


🧐 실제 기능 만들기

이제 바라보는 아이템을 감지하여 해당 아이템 정보를 가져와야 합니다.

1. 바라보는 아이템 감지 및 unlockedItems 값 변경

이미 InteractionDetector에서 GetCurrentTarget이라는 함수가 있습니다. UseScanner에 스캔할때의 동작을 작성합니다.

<스캔조건>
1. 바라보는 대상이 재료가 있거나 아직 스캔을 하지 않은 아이템
2. 우클릭을 2초동안 하고 있을 것

// UseItem.cs

// 현재 스캔하는 중
private bool isScan = false;
// 스캔을 하고 있는 시간
private float scanTime = 0f;
// 스캔하고 있는 대상
private Item scanItem;

private void Update()
{
	// 스캔을 하는 중일때
    if (isScan && Input.GetMouseButton(1))
    {
        scanTime += Time.deltaTime;
        
        // 스캔 완료
        if (scanTime >= 2f)
        {
            isScan = false;
            scanTime = 0f;
            playerState.AddUnlockedItems(scanItem.itemData);
            Debug.Log("아이템 스캔 완료: " + scanItem.itemData.itemName);
        }
        // 스캔 중
        else
        {
            Debug.Log("스캔 중... " + (2f - scanTime) + "초 남음");
        }
    }

	// 스캔이 중도에 끝났으며 스캔한 시간이 조금이라도 남아있으면
    if (isScan && Input.GetMouseButtonUp(1) && scanTime > 0f)
    {
    	// 스캔중이 중단, 시간을 0으로 초기화
        scanTime = 0f;
        isScan = false;
        Debug.Log("스캔이 중단되었습니다.");
    }
}

private void UseScanner()
{
    if (!interactionDetector || !playerState) return;
    scanItem = interactionDetector.GetCurrentTarget();
    if (!scanItem)
    {
        Debug.Log("스캔할 대상이 있지 않습니다.");
        return;
    }
    int id = scanItem.itemData.itemID;
    
    if (playerState.unlockedItems[id])
    {
        Debug.Log("이미 스캔한 아이템 입니다.");
        return;
    }
    if (gameManager.allItems[id].itemType == ItemType.Resources)
    {
        Debug.Log("스캔할 수 없는 아이템입니다.");
        return;
    }

	// 스캔 시작
    isScan = true;
}

2. 제작대 업데이트

현재 제작할 수 있는 아이템을 id 순서대로 제작대에 표시할 수 있게, BenchPanel을 열면 현재 unlockedItems로 리스트가 뜨게금 합니다.

// UIManager.cs

public void ToggleBench()
{
	// 기존 코드...
    
    if (isBenchOpen)
    {
    	InitializeBenchUI()
    }
    
    // 기존 코드...
}

public void InitializeBenchUI()
{
    if (!benchPanel || !benchSlotPanel) return;

    // 기존 제작대 슬롯 초기화
    for (int i = benchSlotPanel.transform.childCount - 1; i >= 0; i--)
    {
        Debug.Log("Destroying child: " + benchSlotPanel.transform.GetChild(i).name);
        Destroy(benchSlotPanel.transform.GetChild(i).gameObject);
    }

    for (int i = 0; i < playerState.unlockedItems.Length; i++)
    {
        if (playerState.unlockedItems[i])
        {
            GameObject slot = Instantiate(benchSlotPrefab, benchSlotPanel.transform);
            BenchSlot slotData = slot.GetComponent<BenchSlot>();

            Image itemImage = slot.transform.Find("ItemIcon").GetComponent<Image>();
            itemImage.sprite = gameManager.allItems[i].icon;
            itemImage.color = Color.white;

            slotData.SetSlotIndex(i);
        }
    }
}

3. Scane UI 업데이트

저는 스캐너를 들고 2초동안 마우스 우클릭을 하고 있으면 스캔이 성공하게 할 겁니다. 그러면 2초 게이지 UI가 필요하겠죠?
HUD_PanelScaneBar를 추가하고 자식으로 Background_Bar, Fill_Bar, Text(스캔 중...) 을 넣습니다.

// UIManager.cs

[Header("Bar")]
public GameObject scanBar;
public Image scanGaugeBar;

public void UpdateScanUI(float time)
{
	scanBar.SetActive(true);
    if (scanBar && scanGaugeBar)
    {
    	scanGaugeBar.fillAmount = time / 2f;
    }
}

위 함수를 UseItemUpdate함수에 적용시킵시다.

아이템 스캔 완료, 중단일 때는 uiManager.scanBar.SetActive(false)
아이템 스캔 중일 때는 uiManager.UpdateScanUI(scanTime) 을 하면 됩니다.


🎮 최종 결과

아이템을 스캔할 수 있으며, 스캔하는 동안에는 UI에 게이지가 오르는 모습을 볼 수 있습니다.

* 흰색으로 바가 끊기는 것은 gif 이미지 오류입니다.


📚 오늘의 배운 점

  • 아이템을 Bone에 적용
  • 고유 ID를 통한 아이템 찾기
  • Sort 활용
  • 실시간 unlockedItem 업데이트 및 제작대 초기화
  • GetMouseButtonGetMouseButtonUp 을 이용한 Scanner 기능 제작
  • 게이지 업데이트

🎯 다음 계획

다음 글에서는:

  1. 애니메이션 적용
    • 스캐너, 무기 등
    • 일반 자원
profile
Unreal Engine & Unity 게임 개발자

0개의 댓글