Coding Feature.

[Unity 3D] Vampire Survivors 같은 게임 만들기 #3 카드 UI 관련(Drag & Drop), 카드 정보 및 효과 (Scriptable Object), 적 스폰 스크립트 구현 본문

Toy Project/Bust'em, Igor! [Unity3D]

[Unity 3D] Vampire Survivors 같은 게임 만들기 #3 카드 UI 관련(Drag & Drop), 카드 정보 및 효과 (Scriptable Object), 적 스폰 스크립트 구현

codingfeature 2024. 2. 7. 17:38

우선 적을 스폰할 때 플레이어의 주변을 원형으로 스폰되도록 구현해보겠습니다.

 

    public void SpawnNewSkeleton(float hp, float damage, float speed, float exp)
    {
        GameObject newSkeleton = Instantiate(originalSkeleton);
..
        float randomAngle = Random.Range(0f, 360f);

        float x = Mathf.Cos(randomAngle) * spawnDistance + player.transform.position.x;
        float z = Mathf.Sin(randomAngle) * spawnDistance + player.transform.position.z;

        newSkeleton.transform.position = new Vector3(x, newSkeleton.transform.position.y, z);

        skeletons.Add(newSkeleton);

        enemyCount++;
    }

 

위는 이전에 EnemyManager에서 구현했던 코드입니다. 위에 randomAngle을 통해 플레이어의 위치에 대해서 x, z 값을 더하여 원형으로 스폰되도록 했습니다.

 

 

그리고 게임의 주요 매커니즘 중 하나인 카드와 관련된 UI를 구현해보겠습니다.

 

카드는 플레이어가 자신의 Hand에 가지고 있을 수 있고, 소모가 가능한 카드인 경우, 화면 중앙에 끌어다놓아 효과를 발동할 수 있도록 할 것입니다. 또한 필요없는 카드인 경우 Trashbin으로 끌어다놓아 삭제되도록 할 것입니다.

 

우선 UI를 캔버스에 생성했습니다.

 

Hand 이미지에 아래와 같이 Horizontal Layout Grounp 컴포넌트를 설정해서 카드가 정렬되도록 했습니다.

 

 

그리고 각 카드에 적용될 CardScript와 카드가 플레이될 공간(Hand, Trashbin, Table Top)에 적용될 CardDropZoneScript를 아래와 같이 작성했습니다.

 

// CardScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class CardScript : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    [HideInInspector]
    public Transform originalParent;

    public CardObject cardObject;

    public Text nameText;
    public Text descriptionText;
    
    void Start()
    {
        // 카드 UI Text 설정.
        nameText.text = cardObject.cardName;
        descriptionText.text = cardObject.description;
    }

    public void OnBeginDrag(PointerEventData eventData)
    {
        originalParent = this.transform.parent;
        this.transform.SetParent(this.transform.root); // Canvas를 Parent로 설정.

        GetComponent<CanvasGroup>().blocksRaycasts = false;
    }

    public void OnDrag(PointerEventData eventData)
    {
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        this.transform.SetParent(originalParent);

        GetComponent<CanvasGroup>().blocksRaycasts = true;
    }
}

 

위 코드에서 OnBeginDrag, OnDrag, OnEndDrag 함수가 정의됩니다.

 

이때 PointerEventData에는 드래그 될 때의 마우스 커서에 대한 정보가 담기게 됩니다.

 

OnBeginDrag 함수는 카드를 드래그 하는 순간 호출되는 함수입니다. 우선 카드의 이전 parent에 대한 정보를 저장하고 카드 게임 오브젝트의 현재 parent를 Canvas(this.transform.root)로 옮깁니다. 그리고 아래와 같이 카드에 추가된 Canvas Group의 Blocks Raycasts 를 false로 바꿉니다. 이로써 마우스를 클릭하고 드래그할 때 발사되는 Raycast를 카드 UI가 막는 것을 방지합니다. 마우스 커서의 raycast가 카드를 뚫고 아래 UI까지 통과되어야 카드가 어느 부분에 드래그되는지 알 수 있어야 하기 때문입니다.

 

OnDrag는 마우스가 드래그 될 때 지속적으로 호출되는 함수로 마우스 커서 위치에 카드를 옮기게 해주었습니다.

 

그리고 OnEndDrag 함수는 드래그가 끝날 때 호출되는 함수로, 다시 parent를 재설정하고 raycast block을 true로 바꾸어줍니다.

 

// CardDropZoneScript

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

public class CardDropZoneScript : MonoBehaviour, IDropHandler
{
    public void OnDrop(PointerEventData eventData)
    {
        // eventData.pointerDrag : 드래그된 아이템 / gameObject : 드래그한 곳
        Debug.Log(eventData.pointerDrag.name + " was Dropped to " + gameObject.name);

        GameObject card = eventData.pointerDrag;
        CardScript cardScript = card.GetComponent<CardScript>();


        //cardScript.originalParent = this.transform;
        if (card != null)
        {
            if (gameObject.CompareTag("TableTop"))
            {
                if(cardScript.cardObject.cardType == Type.consumable)
                {
                    CardManager.Instance.ActivateCardEffect(cardScript.cardObject.cardId, 10f);
                    Destroy(card);
                }
            }
            else if (gameObject.CompareTag("Trashbin"))
            {
                Destroy(card);
            }
        }
    }
}

 

OnDrop은 마우스 커서가 드래그한 아이템을,  스크립트가 존재하는 게임 오브젝트에 놓을 때 호출됩니다.

 

여기서 eventData.pointerDrag로 드래그한 게임 오브젝트를 구할 수 있습니다.

 

저는 드래그한 카드가 존재하고 드래그된 위치가 TableTop인 경우, 즉 화면 가운데일 경우 카드의 type을 비교하고, 소모품일 경우 카드 매니저(아래에 구현)를 통해 카드 효과를 발동시키도록 구현 했습니다. 이때 카드의 ID를 참고해서 효과를 다르게 발동되도록 했습니다.

 

그리고 카드를 삭제하도록 했습니다.

 

 

이제 각 카드에 대한 정보를 담기 위해 Scriptable Object를 만들어보겠습니다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public enum Type
{
    consumable,
    durable
};

[CreateAssetMenu(fileName = "default card", menuName = "Card Object")]
public class CardObject : ScriptableObject
{
    public int cardId;
    public string cardName;
    public Type cardType;
    public Sprite cardImage;
    public string description;
}

 

위 스크립트는 카드가 기본적으로 가지고 있어야 할 정보들을 저장하는 컨테이너를 선언합니다.

그리고 CreateAssetMenu를 작성해주어서 유니티 에디터에서 쉽게 카드를 만들 수 있도록 했습니다.

 

체력 회복 카드 Scriptable Object

 

 

 

그 다음 카드의 효과를 발동시키기 위한 카드 매니저 스크립트를 아래와 같이 구현하였습니다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CardManager : MonoBehaviour
{
    private static CardManager instance;

    public static CardManager Instance
    {
        get
        {
            return instance;
        }
    }

    private void Awake()
    {
        if (instance)
        {
            Destroy(instance);
            return;
        }

        instance = this;
        DontDestroyOnLoad(this.gameObject);
    }

    public void ActivateCardEffect(int cardID)
    {
        switch (cardID)
        {
            case 1:
                GameManager.Instance.PlayerGameObject.GetComponent<PlayerScript>().HealPlayer(50f);
                break;
            case 2:
                GameManager.Instance.PlayerGameObject.GetComponent<PlayerScript>().IncreaseDamage(20f);
                break;
            default:
                break;
        }
    }
}

 

 

카드 효과를 발동시키기 위해 각 카드의 ID를 switch 문으로 비교하고 실행되도록 했습니다.

 

 

 

위 내용을 구현하는 데에는 아래 영상들이 큰 도움이 되었습니다!

https://youtu.be/aPXvoWVabPY?si=8apaONFIlY2WURur

https://youtu.be/bMuYUOIAdnc?si=SOtXnGbTSPYq_s_T

 

https://youtu.be/P66SSOzCqFU?si=4Y5B4gzl31lviQKz