본문 바로가기
유니티 이야기

[Unity] 간단한 머지 게임 만들기

by novices 2022. 9. 2.

이전 시간에 포스팅한 유니티 Drag 이벤트를 통해 간단한 숫자 머지 게임을 작성해보겠습니다. 게임의 내용은 숫자를 가지고 있는 아이템이 병합될 때 숫자가 상승하며 새로운 아이템 생성되는 내용입니다. 사실 게임이라고 하기에는 내용이 부족하지만 merge 게임의 핵심기능이 포함되어있는 포스팅입니다.

 

완성된 게임 gif
완성된 게임

 

전에 작성한 UI Object 드래그하는 방법에 있는 코드를 놓고 추가 작성했으므로 UI 구성 등 기본 스크립트에 대한 자세한 내용이 필요하시다면 이전 포스팅을 참고해 주시기 바랍니다. 

 

 

1. Item 프리팹 생성 및 Slot 게임 오브젝트에 Grid Layout Group 추가 하기

이전 포스팅의 Icon을 대체하는 프리팹으로  Icon과 동일하게 UI > Image로 오브젝트를 생성한 후 Canvas Group, DragManager.cs, Item.cs를 컴포넌트로 추가하고 인스펙터를 통해 Item의 colors 배열에 9가지 색을 지정해 줬습니다.  마지막으로 자식으로 UI > Legacy > Text 컨포넌트를 추가하고 프리팹으로 만듭니다.

 

Item prefab 설정 이미지
Item prefab 설정하기

 

모든 슬롯 게임 오브젝트에 Grid Layout Group을 추가합니다. 이유는 아이템의 생성 이동 등 슬롯과 위치 보정이 필요한데 이런 부분을 코드 없이 처리하기 위함입니다. Grid Layout Group은 자식으로 들어오는 transform을 자동 정렬합니다.

 

슬롯에 Grid Layout Group추가 이미지
슬롯에 Grid Layout Group추가

 

 

2. 추가 스크립트 및 변경 내역

Item.cs (숫자 정보를 담고 있는 클래스 - 아이템 아이콘의 색상과 숫자를 설정하는 기능)

InventoryManager.cs (인벤토리를 관리하는 클래스 - 빈 슬롯을 확인하고 아이템을 생성하는 기능)

DragManager.cs (드래그 이벤트를 관리하는 클래스 - 이전 포스팅의 IconDrag 클래스 )

Slot.cs (드롭 이벤트 감지하는 클래스 - 드롭 이벤트에서  판단하여 아이템을 장착, 병합, 위치교환 기능을 수행)

 

▶Item.cs

아이템의 숫자와 색을 지정하는 함수로 되어있고 색은 숫자를 기준으로 변하도록 되어있습니다. 추가로 숫자 설정 시 transform을 받아서 부모로 지정합니다. 

using UnityEngine;
using UnityEngine.UI;
// 이 스크립트는 드래그매니저를 필수 요구함.
[RequireComponent (typeof (DragManager))] 
public class Item : MonoBehaviour
{
    // 아이템 숫자를 저장 할 변수
    public int number;
    // 아이템의 적용할 색 배열
    [SerializeField] Color[] colors;

    // 생성된 아이템의 설정 함수
    public void SetItem(int newValue, Transform newParent)
    {
        number = newValue;
        // 아이템 배경색 할당
        GetComponent<Image>().color = SetColor(number); 
        // 자식의 텍스트 컴포넌트에 숫자 할당
        GetComponentInChildren<Text>().text = number.ToString();

        transform.SetParent(newParent);
    }

    //숫자 별로 색 할당을 위한 함수
    public Color SetColor(int colorValue)
    {
        if(colorValue < 10)
            return colors[colorValue -1];
        else
            return Color.black;
    }
}

 

InventoryManager.cs

아이템 생성이나 병합은 코딩 관점에서 어차피 생성으로 통일되기 때문에 주 기능은 아이템 생성입니다. 또한 아이템을 생성하려면 빈 슬롯을 인지해야 하기 때문에 빈 슬롯의 gameobject를 리스트로 받아서 배열로 변환 후 반환하는 함수가 포함되어있습니다. 생성 시는 빈 슬롯을 참고하고 병합 시에는 시점상 생성돼야 할 슬롯을 Drop 이벤트를 통해 알고 있으므로 Slot스크립트에서 transform 값을 받아와 병합되어 생성되는 신규 아이템 설정 함수의 인자로 전달합니다.

using System.Collections.Generic;
using UnityEngine;

public class InventoryManager : MonoBehaviour
{
    //InventoryManager 인스턴스에 바로 접근하기 위한 static 변수
    public static InventoryManager inst;
    // 슬롯들
    Slot[] inventorySlots;
    //Slot 컨포넌트들을 참조할때 기준점으로 사용할 부모 트랜스폼
    public Transform innerPanelTransform;
    // Item의 프리팹
    public GameObject  itemPrefab;
    void Start()
    {
        inst = this;
        // 모든 슬롯 참조하기
        inventorySlots = innerPanelTransform.GetComponentsInChildren<Slot>();
    }
    // 빈슬롯을 배열로 반환하는 함수
    GameObject[] GetEmptyInventorySlots()
    {
        List<GameObject> emptySlots = new List<GameObject>();

        foreach(Slot s in inventorySlots)
        {
            if(s.item == null)
                emptySlots.Add(s.gameObject);
        }

        if(emptySlots.Count == 0)
            return null;
        else
            return emptySlots.ToArray();
    }

    // 신규 아이템 생성 함수
    public void CreateItem()
    {
        GameObject[] emptySlots = GetEmptyInventorySlots();

        if(emptySlots != null)
        {
            int randomNum = Random.Range(0, emptySlots.Length);

            var item = Instantiate(itemPrefab, emptySlots[randomNum].transform.position, Quaternion.identity);
            item.GetComponent<Item>().SetItem(1, emptySlots[randomNum].transform);
        }
    }

    // 병합 된 신규 아이템 생성 함수
    public void CreateUpgradeItem(int newNumber, Transform newParent)
    {
        var item = Instantiate(itemPrefab, Vector3.zero, Quaternion.identity);
        item.GetComponent<Item>().SetItem(newNumber, newParent);
    }
}

 

DragManager.cs

전 포스팅 IconDrag.cs랑 동일한 내용입니다. 변경된 부분은 변수 이름과 onDragParent의 참조값을 Inventory의 Transform으로 코드에서 가져오는 내용입니다.

using UnityEngine;
using UnityEngine.EventSystems;

public class DragManager : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    public static GameObject beingDraggedItem;
    Vector3 startPosition;
    Transform onDragParent;
    [HideInInspector]
    public Transform startParent;
    public void OnBeginDrag(PointerEventData eventData)
    {
        beingDraggedItem = gameObject;
        startPosition = transform.position;
        startParent = transform.parent;

        onDragParent = InventoryManager.inst.transform;

        GetComponent<CanvasGroup>().blocksRaycasts = false;

        transform.SetParent(onDragParent);
    }
    public void OnDrag(PointerEventData eventData)
    {
        transform.position = Input.mousePosition;
    }
    public void OnEndDrag(PointerEventData eventData)
    {
        beingDraggedItem = null;
        GetComponent<CanvasGroup>().blocksRaycasts = true;

        if(transform.parent == onDragParent)
        {
            transform.position = startPosition;
            transform.SetParent(startParent);
        }
    }
}

 

Slot.cs

이전 포스팅의 Icon함수를 프로퍼티로 대체하였지만 실제 기능은 변경사항이 없습니다. OnDrop 내부 코드는

기존 빈 슬롯이면 아이템을 장착하는 코드 외에 다른 숫자의 아이템이 드롭됐을 때 위치를 변경하거나 같은 숫자가 드롭되었을 때 병합을 처리하는 코드가 추가되었습니다.

using UnityEngine;
using UnityEngine.EventSystems;
public class Slot : MonoBehaviour, IDropHandler
{
    //이전 포스팅의 Icon함수를 대체 하는 프로퍼티
    public GameObject item
    {
        // item에 접근하려고 할때(읽을 때) 실행
        get{
            // transform (rect transform)에 자식 존재여부 확인
            if(transform.childCount > 0)
            {
                // 슬롯에 아이템이 있으면 아이템의 gameobject를 리턴
                return transform.GetChild(0).gameObject;
            }
            // 슬롯에 아이템이 없다면 null을 리턴
            return null;
        }
    }
    
    // 스크립트가 부착된 RectTransform에 포인터가 놓여질때 해당 이벤트가 발생 
    public void OnDrop(PointerEventData eventData) 
    {
        // 슬롯이 비어있다면 실행
        if(!item)
        {
            // 이전 포스팅에 존재하던 위치보정 코드는 슬롯게임오브젝트에
            // grid layout group을 추가하여 자동 위치 보정되도록 변경 
            DragManager.beingDraggedItem.transform.SetParent(transform);
        }
        else // 슬롯이 비어있지 않다면 실행
        {
            //아이템 간 위치변경 또는 병합을 위한 두 아이템 참조
            Item dragItem = DragManager.beingDraggedItem.GetComponent<Item>();
            Item soltItem =  item.GetComponent<Item>();

            // 두 아이템의 숫자가 동일한 경우 실행
            if(dragItem.number == soltItem.number)
            {
                //숫자 백업 후 병합 될 두 아이템 모두 Destory
                int backupNum = dragItem.number;
                Destroy(dragItem.gameObject); Destroy(soltItem.gameObject);

                //인벤트리 매니저를 통해 상위 아이템을 생성
                InventoryManager.inst.CreateUpgradeItem(backupNum+1, transform);
            }
            else // 두 아이템의 숫자가 다른경우 위치변경
            {
                DragManager.beingDraggedItem.transform.SetParent(transform);
                item.transform.SetParent(DragManager.beingDraggedItem.GetComponent<DragManager>().startParent);
            }
        }
    }
}

 

 

3. 마무리

포스팅에서 작성된 item을 의미 있는 데이터로 구조화하고 확장하면 머지 게임, 인벤토리 구현 외에 많은 게임 제작에 활용될 수 있다고 생각합니다.

 

 

댓글