Crosshair 만들기, 아이템 만들기, 아이템과 상호작용

유승아·2024년 5월 28일

내일배움캠프

목록 보기
51/69

1. 화면 중앙에 Crosshair 만들기

강의에서는 작은 점을 만들어서 화면에 Crosshair를 표시했지만
난 무료에셋을 사용해서 에임을 꾸며주고 싶었다.

하지만 그냥 바로 사용하려니 컴포넌트 값으로 드로그 앤 드랍이 안 됐다.

사용하고자 하는 이미지 인스펙터 창에서 텍스처 타입을 Sprite (2D and UI)로 변경해줬다.

사용할 수 있게 되었다. 😊

마음에 드는 이미지와 색상, 크기까지 적용!

이 에임과 아이템의 위치가 맞을 때 아이템 정보 출력, 획득하는 기능을 만들어볼 것이다.

이제 카메라의 정중앙(Crosshair)에서 Ray를 쏘게 하고 싶은데,
그 전에 Ray에 맞을 아이템을 만들어야 한다.


2. 아이템 만들기

빈 오브젝트 생성 후 Log 프리팹 넣어주고 프리팹 언팩하기

안에 있는 캡슐 콜라이더 삭제

부모 오브젝트에 박스 콜라이더 추가 후 사이즈 조절하기

ItemObject 스크립트 생성 후 붙여준 다음 복붙!

아이템 도끼, 당근, 돌, 검 추가 생성

만든 아이템들 프리팹으로 만들어주기

1) 아이템과의 상호작용을 위한 스트립트 작성

아이템들의 데이터 세팅해줄 곳 생성

ItemData.cs

public enum ItemType
{
    Equipable, // 장착 가능
    Consumable, // 소비, 섭취 가능
    Resource // 단순 자원
}

public enum ConsumableType // 어떤 종류의 소비, 섭취
{
    Health,
    Hunger
}

[Serializable]
public class ItemDataConsumable
{
    public ConsumableType type; // ConsumableType 저장
    public float value; // 아이템 사용 시 회복되는 값
}

// 에디터에서 쉽게 사용할 수 있도록 메뉴를 만듦
// ScriptableObject를 만들 때 빠르게 만들 수 있도록 메뉴창에 추가해 줌
[CreateAssetMenu(fileName = "Item", menuName = "New Item")]
public class ItemData : ScriptableObject
{
    [Header("Info")] // 아이템에 들어갈 정보들
    public string displayName; // 이름
    public string description; // 설명
    public ItemType type; // 활용 방법에 따른 타입, 체력 회복/공격용/채집용 등
    public Sprite icon; // 아이콘 정보
    public GameObject dropPrefab; // 프리팹 정보

    [Header("Stacking")] // 단일 소유, 중복 소유 여부
    public bool canStack; // 여부 체크
    public int maxStackAmount; // 최대 보유값

    [Header("Consumable")]
    public ItemDataConsumable[] consumables;
}

2) 각 아이템 데이터 세팅

Create - New Item

New Item으로 아이템 5개 세팅하기

ItemObject에서 이 데이터를 가질 수 있게 만들기

ItemObject.cs

public class ItemObject : MonoBehaviour
{
    public ItemData data;
}

아이템에 맞는 데이터 넣어주기

프리팹에도 적용될 수 있게 Apply All 하기

여기까지 했다면 아이템 상호작용에 필요한 사전 세팅 완료!

Player.cs

public class Player : MonoBehaviour
{
    public PlayerController controller;
    public PlayerCondition condition; // 새로운 기능(UI)이 추가됐으니 작성해주기

    public ItemData itemData; // 현재 Interaction된 아이템 데이터를 넣어줄 변수
    public Action addItem;

    private void Awake()
    {
        CharacterManager.Instance.Player = this;
        controller = GetComponent<PlayerController>();
        condition = GetComponent<PlayerCondition>();
    }
}

ItemObject.cs

// 어떤 아이템인지 하나하나 조건문으로 검사할 수 없으니까 인터페이스 만들기
public interface IInteractable
{
    // 미리 정의해주기
    public string GetInteractPrompt(); // 화면에 띄워줄 Prompt
    public void OnInteract(); // Interact 됐을 때 어떤 효과를 발생시키게 할 것인지
}
public class ItemObject : MonoBehaviour, IInteractable
{
    public ItemData data; // 아이템 데이터

    /*
        어떤 아이템이든 간에 일단 IInteractable이라고 하는 Component를 찾게 되면
        없다면(Interaction이 가능하지 않은 아이템) 그냥 넘어가면 되고, 
        IInteractable이 GetComponent 됐다면 그 안에 있는 기능(아래에 있는 함수)들을 쓸 수 있는 것
    */

    // 상호작용할 때 필요한 기능들
    public string GetInteractPrompt()
    {
        // Prompt에 띄워줄 정보 보내기
        string str = $"{data.displayName}\n{data.description}"; // 이름\n설명
        return str;
    }

    // 상호작용 키를 누르면 함수 호출해주기
    public void OnInteract() 
    {
        // 정보를 Player에 직접적으로 넘겨줄 수 없기 때문에 CharacterManager를 통해서 Player의 변수 만들기
        CharacterManager.Instance.Player.itemData = data; // Player itemData에 내가 가지고 있는 data 넣어주기
        CharacterManager.Instance.Player.addItem?.Invoke();
        Destroy(gameObject); // 상호작용 키를 누르면 맵에서 사라지게 하기
    }
}

3) 상호작용에 필요한 기능 만들기

Interaction.cs

public class Interaction : MonoBehaviour
{
    public float checkRate = 0.05f; // 얼마나 자주 Update 해서 검출할지(얼마나 자주 최신화할 것인지), 검출 주기
    private float lastCheckTime; // 마지막으로 체크한 시간
    public float maxCheckDistance; // 얼마나 멀리 있는 것을 체크할 것인지
    public LayerMask layerMask; // 어떤 레이어가 달린 게임 오브젝트를 추출할 것인지

    // 캐싱하는 자료가 담겨있는 변수들
    public GameObject curInteractGameObject; // Interaction에 성공했다면 현재 Interaction된 게임 오브젝트의 정보
    private IInteractable curInteractable; // 인터페이스 캐싱

    // Prompt에 띄워주기
    public TextMeshProUGUI promptText; // promptText 직접적으로 가지고 있기
    private Camera camera; // 카메라 정보

    void Start()
    {
        camera = Camera.main; // 메인 카메라
    }

    void Update()
    {
        // Update에 있으면 매 프레임마다 Ray를 쏘게 되므로
        // if문으로 얼마나 자주 호출하게 할 것인지 정해주기
        if (Time.time - lastCheckTime > checkRate)
        {
            lastCheckTime = Time.time; // 현재 시간 넣어주기

            // 계속 Interactable이 가능할 수 있게 Ray 쏘기
            // 카메라가 찍고 있는 그 방향 자체가 기본적으로 있기 때문에 중심점(Ray가 나가는 시작점)만 잡아주면 됨
            Ray ray = camera.ScreenPointToRay(new Vector3(Screen.width / 2, Screen.height / 2)); // 화면의 정중앙
            RaycastHit hit; // 부딪힌 오브젝트 정보를 담아놓을 RaycastHit

            // ray 정보 담기, 충돌이 된 물체가 있다면 hit 변수에 정보 넘겨주기, 길이, layerMask
            if (Physics.Raycast(ray, out hit, maxCheckDistance, layerMask))
            {
                // 충돌이 됐을 때
                // 기존에 상호작용된 게임 오브젝트와 다를 때
                if (hit.collider.gameObject != curInteractGameObject)
                {
                    // 새로운 정보 넣어주기
                    curInteractGameObject = hit.collider.gameObject;
                    curInteractable = hit.collider.GetComponent<IInteractable>();

                    // prompt 출력
                    SetPromptText();
                }
            }
            else
            {
                // 충돌이 안 됐을 때(빈 공간에 Ray 쏘기)
                // 정보 없애주기
                curInteractGameObject = null;
                curInteractable = null;
                promptText.gameObject.SetActive(false);
            }
        }
    }

    // prompt 세팅
    private void SetPromptText()
    {
        promptText.gameObject.SetActive(true);

        // 정보 넣어주기
        promptText.text = curInteractable.GetInteractPrompt(); // 아이템 이름, 설명 문자열 text에 넣기
    }

    // 상호작용 키를 누르면 
    // Player Action에 이벤트 달아주기
    public void OnInteractInput(InputAction.CallbackContext context)
    {
        // 키가 눌렸을 때 && curInteractable에 정보가 있을 때
        if(context.phase == InputActionPhase.Started && curInteractable != null)
        {
            curInteractable.OnInteract();
            curInteractGameObject = null;
            curInteractable = null;
            promptText.gameObject.SetActive(false);
        }
    }
}

ScreenToViewportPoint
터치, 눌렀을 때 기준

ScreenPointToRay
카메라 기준

아이템 프리팹들 레이어 Interactable로 변경

Player에 Interaction.cs 붙여주기

TextMeshPro 생성
한글 폰트 안 깨지게 폰트 에셋 파일로 만들어주기

Character Sequence
32-126: 알파벳
44032-55203: 한글 조합 글자
12593-12643: 한글 모음, 자음
8200-9900: 특수문자

만든 폰트 적용하기

아이템 이름, 설명 한글로 잘 뜨고
여기서 E 키를 눌러서 아이템을 먹으면!

Player의 Item Data에 정보가 들어가있는 것을 확인할 수 있다.

인벤토리 만들 때 저 값을 null로 만들어 주면 된다.

👀 실행 결과

0개의 댓글