유니티 프로젝트/케이크게임

[개발일지] 8. 씨앗 선택창 심기 버튼 연동 & 씨앗 구매하기 로직 추가

dubu0721 2024. 7. 18. 18:31

[구현한 기능]

  • 씨앗 선택창 만들기
  • 심기 버튼이랑 씨앗 선택창이랑 연동하기
  • 씨앗 선택창에서 버튼 누르면 씨앗 심어지도록 하기
  • 씨앗 구매하기 버튼 게임 매니저의 money 변수 이용해서 로직 추가하기(구매한 씨앗은 인덱스 번호에 맞는 배열의 요소 개수 증가하도록 -> 씨앗 구매하자마자 인스턴스 생성되는 거 아님! 씨앗을 심어야 비로소 인스턴스 생성)
  • 다 자란 과일 수확하면 인덱스 번호에 맞는 배열의 요소 개수 증가하도록 하기

 

[현재 프로젝트 클래스 종류]

FarmingManager: 농사의 전반적인 시스템을 관리하는 클래스

Seed: 씨앗 클래스

Fruit: 과일 클래스

SeedContainer: 씨앗 프리팹을 배열에 담아놓고, 씨앗을 생성할 때마다(땅에 심을 때) 풀에 넣어놓는 클래스

FruitContainer: 과일 프리팹을 배열에 담아놓고, 과일을 생성할 때마다 풀에 넣어놓는 클래스

BuySeedUIManager -> SeedUIManager (이름 변경): 씨앗 구매창, 선택창 UI 를 관리하는 클래스

SlotManager -> BuySeedSlotManager (이름 변경): 씨앗 구매창의 슬롯을 관리하는 클래스

PlantSeedSlotManager: 씨앗 선택창의 슬롯을 관리하는 클래스

FarmingData(MonoBehaviour 상속 받지 않는 클래스): 타일이 가지는 농사 데이터 클래스

 

  • FarmingManager
using JetBrains.Annotations;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Tilemaps;
using UnityEngine.UI;
using static UnityEditor.PlayerSettings;


// 타일이 가지는 농사 데이터
class FarmingData
{
    public Seed seed; // 타일이 가지는 씨앗 정보
    //public bool seedOnTile; // 타일 위에 씨앗이 있는지 여부 확인용(씨앗이 있으면 밭을 갈 수 없음)
    public bool plowEnableState; // 밭을 갈 수 있는 상태인지 여부 확인용(밭이 안 갈린 상태)
    public bool plantEnableState; // 씨앗을 심을 수 있는 상태인지 여부 확인용
    public bool harvestEnableState; // 작물이 다 자란 상태인지 여부 확인용
    public Button stateButton; // 타일을 누르면 타일 위에 뜨도록 하는 버튼
    public Button[] buttons; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼

    public string currentState = "None"; // 현재 상태(초기에는 아무것도 안 한 상태니까 None 으로.. -> plow: 밭 갈린 상태, plant: 씨앗 심은 상태, harvest: 다 자란 상태)
}


public class FarmingManager : MonoBehaviour
{
    [Header("Game Data")]
    public Camera mainCamera; // 마우스 좌표를 게임 월드 좌표로 변환하기 위해 필요한 변수(카메라 오브젝트 할당해줄 것)
    public SeedContainer seedContainer; // 현재 가진 씨앗을 가져오기 위해 필요한 변수(씨앗 컨테이너 게임 오브젝트 할당해줄 것)
    public FruitContainer fruitContainer; // 수확한 과일을 저장하기 위해 필요한 변수(과일 컨테이너 게임 오브젝트 할당해줄 것)

    [Header("Tile")]
    public TileBase grassTile; // 밭 갈기 전 상태
    public TileBase farmTile; // 밭 간 후 상태
    public TileBase plantTile; // 씨앗 심은 후 상태
    public TileBase harvestTile; // 과일 다 자란 상태
    public Vector3Int prevSelectTile; // 이전 클릭된 타일

    [Header("Tilemap")]
    public Tilemap farmEnableZoneTilemap; // 농사 가능 부지를 나타내는 타일맵
    public Tilemap farmTilemap; // 진짜로 현재 타일의 상태에 따라 타일이 변경되는 타일맵

    [Header("Farm interaction Button")]
    // 버튼을 프리팹으로 만들어 놓은 다음 동적으로 생성해서 쓸 것.
    public GameObject[] buttonPrefabs; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼
    public GameObject buttonParent; // 버튼 생성할 때 부모 지정하기 위한 변수

    [Header("Farm interaction Panel")]
    public GameObject growTimePanel; // 다 자라기까지 남은 시간 보여주는 판넬
    public Text growTimeText; // 다 자라기까지 남은 시간

    [Header("Farming Data")]
    public Vector2 clickPosition; // 현재 마우스 위치를 게임 월드 위치로 바꿔서 저장
    public Vector3Int cellPosition; // 게임 월드 위치를 타일 맵의 타일 셀 위치로 변환
    Dictionary<Vector3Int, FarmingData> farmingData;

    [Header("PlantSeed Information")]
    public GameObject plantSeedPanel; // 씨앗 선택창
    public int selectedSeedIdx; // 현재 심을 씨앗 종류
    public bool clickedSelectedSeedButton = false; // 이 값이 true 가 되면 씨앗 심기 함수 호출하도록(씨앗 심기 함수에서는 이 값을 다시 false 로 돌림).. 


    private void Awake()
    {
        farmingData = new Dictionary<Vector3Int, FarmingData>();
        clickPosition = Vector2.zero;
    }

    private void Start()
    {
        // 농사 가능 구역만 farmingData 에 저장할 것임.
        foreach (Vector3Int pos in farmEnableZoneTilemap.cellBounds.allPositionsWithin)
        {
            if (!farmEnableZoneTilemap.HasTile(pos)) continue;

            // 유니티에서는 new 를 쓰려면 class 가 MonoBehaviour 를 상속 받으면 안 됨.
            farmingData[pos] = new FarmingData();
            farmingData[pos].buttons = new Button[3]; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼

            // 각 타일마다 세 개의 버튼을 가지고 시작하도록..
            for (int i = 0; i < buttonPrefabs.Length; i++)
            {
                // 클로저 문제를 피하기 위해서 값을 변수에 저장해놓고 이 변수를 사용함..
                int index = i;
                Vector3Int tilePos = pos;
                farmingData[pos].buttons[i] = CreateButton(index, tilePos).GetComponent<Button>();

                if (index == 0)
                {
                    // 버튼에 함수를 저장해놓음(tilePos 도 같이 저장해놓기)
                    farmingData[tilePos].buttons[index].onClick.AddListener(() => PlowTile(tilePos));
                }
                else if (index == 1)
                {
                    //farmingData[tilePos].buttons[index].onClick.AddListener(() => PlantTile(tilePos));
                    farmingData[tilePos].buttons[index].onClick.AddListener(() => OpenPlantSeedPanel(tilePos)); // 씨앗 선택창 화면에 띄우는 함수 연결시키기
                }
                else if (index == 2)
                {
                    farmingData[tilePos].buttons[index].onClick.AddListener(() => HarvestTile(tilePos));
                }
            }

            // 맨 처음에는 plow 버튼을 저장하고 있도록
            farmingData[pos].stateButton = farmingData[pos].buttons[0];

            prevSelectTile = pos;
        }
    }

    void Update()
    {
        // 버튼 눌렀을 때 뒤에 있는 타일 못 누르도록 하기 위한 구문..
        if (IsPointerOverUIObject()) return;

        // 땅을 왼쪽 마우스키로 누르면..
        if (Input.GetMouseButtonDown(0))
        {
            growTimePanel.SetActive(false); // 누르면 이전에 켜진 판넬 꺼지도록..

            // 땅에 아무것도 안 한 상태는 plow 버튼을 갖고, 갈린 상태는 버튼으로 plant 버튼을 갖는다.
            // 다른 땅을 클릭하면 전에 클릭한 땅의 버튼은 안 보여야 하므로 SetActive 로 안보이게 조정한다..
            // 수확하기 버튼은 과일이 다 자라면 계속 보여야함..
            if (farmEnableZoneTilemap.HasTile(prevSelectTile))
            {
                if (farmingData[prevSelectTile].currentState == "None" || farmingData[prevSelectTile].currentState == "plow")
                {
                    farmingData[prevSelectTile].stateButton.gameObject.SetActive(false);
                }
            }

            // 현재 마우스 위치를 게임 월드 위치로 바꿔서 저장
            clickPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
            // 게임 월드 위치를 타일 맵의 타일 셀 위치로 변환
            cellPosition = farmTilemap.WorldToCell(clickPosition);


            foreach (Vector3Int pos in farmingData.Keys)
            {
                // 저장해놓은 타일 중에 현재 마우스로 클릭한 위치랑 같은 타일이 있으면
                if (pos == cellPosition)
                {
                    // 밭이 안 갈린 상태면 눌렀을 때 버튼 뜰 수 있도록
                    if (farmingData[cellPosition].plowEnableState)
                    {
                        farmingData[cellPosition].stateButton.gameObject.SetActive(true);
                    }
                    else
                    {
                        // 씨앗이 안 심어져 있을 때 또는 씨앗이 다 자랐을 때 버튼 뜰 수 있도록
                        if (farmingData[cellPosition].seed == null || (farmingData[cellPosition].seed.isGrown))
                        {
                            farmingData[cellPosition].stateButton.gameObject.SetActive(true);
                        }
                        // 씨앗이 자라는 중이면 남은 시간 나타내는 판넬 뜨도록
                        else if (!farmingData[cellPosition].seed.isGrown)
                        {
                            // 판넬 위치를 현재 클릭한 타일 위치로..
                            growTimePanel.transform.position = mainCamera.WorldToScreenPoint(farmTilemap.CellToWorld(cellPosition)) + new Vector3(0, 50, 0);
                            growTimePanel.SetActive(true);
                            growTimeText.text = "남은시간\n" + (int)(farmingData[cellPosition].seed.growTime - farmingData[cellPosition].seed.currentTime);
                        }
                    }
                }
            }

            prevSelectTile = cellPosition; // 지금 누른 타일을 이전에 누른 타일 위치를 저장하는 변수에 저장.. 
        }

        // 자라는데 남은 시간이 계속 업데이트 되어야 하므로..
        if (farmEnableZoneTilemap.HasTile(cellPosition) && farmingData[cellPosition].seed != null)
        {
            if (!farmingData[cellPosition].seed.isGrown)
                growTimeText.text = "남은시간\n" + (int)(farmingData[cellPosition].seed.growTime - farmingData[cellPosition].seed.currentTime);
            else
                growTimePanel.SetActive(false); // 다 자라면 남은시간 나타내는 판넬 꺼지도록..
        }



        foreach (Vector3Int pos in farmingData.Keys)
        {
            if (farmingData[pos].seed != null)
            {
                if (farmingData[pos].seed.isGrown)
                {
                    farmTilemap.SetTile(pos, harvestTile); // 타일을 과일이 다 자란 상태로 변경
                    farmingData[pos].harvestEnableState = true; // 작물 수확할 수 있는 상태
                    farmingData[pos].stateButton.gameObject.SetActive(true); // 수확하기 버튼은 항상 떠있어야 함
                    farmingData[pos].currentState = "harvest";
                }
            }
        }

        // 씨앗 선택창에서 버튼 클릭하면 진입하도록..
        if (clickedSelectedSeedButton)
        {
            // 씨앗 심는 함수 호출
            PlantTile(cellPosition, selectedSeedIdx);
        }
    } 

    private bool IsPointerOverUIObject()
    {
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current)
        {
            position = new Vector2(Input.mousePosition.x, Input.mousePosition.y)
        };
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        return results.Count > 0;
    }

    public GameObject CreateButton(int buttonNumber, Vector3Int pos)
    {
        // 타일 맵 초기 설정할 때 쓰는 함수
        // 타일마다 버튼을 미리 만들어놓고 사용할 것임

        GameObject button = Instantiate(buttonPrefabs[buttonNumber], buttonParent.transform);

        // 셀 좌표를 월드 좌표로 바꿔서 저장
        Vector3 worldPos = farmTilemap.CellToWorld(pos);
        // 월드 좌표를 스크린 좌표로 바꿔서 저장
        Vector3 screenPos = Camera.main.WorldToScreenPoint(worldPos);
        // 버튼의 좌표 설정
        button.transform.position = screenPos;
        button.transform.position += new Vector3(0, 50, 0);

        return button;
    }

    public void PlowTile(Vector3Int pos)
    {
        // 밭을 가는 함수

        farmTilemap.SetTile(pos, farmTile); // 타일 모습은 밭 간 상태로 바꿔주기
        farmingData[pos].plowEnableState = false; // 갈았으니까 이제 갈 수 없는 상태를 나타내기 위해 false 로 값 변경
        farmingData[pos].plantEnableState = true; // 씨앗을 심을 수 있는 상태를 나타내기 위해 true 로 값 변경
        farmingData[pos].currentState = "plow"; // 갈린 상태니까 plow 로 바꿔주기

        farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..

        farmingData[pos].stateButton = farmingData[pos].buttons[1]; // plant 버튼으로 변경..
    }

    public void OpenPlantSeedPanel(Vector3Int pos)
    {
        // 심기 버튼이랑 연결해줘야함.
        farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..

        plantSeedPanel.SetActive(true); // 심기 버튼 눌렀을 때 씨앗 선택창 뜨도록 하기 위함
    }

    public void PlantTile(Vector3Int pos, int seedIdx)
    {
        // 씨앗을 심는 함수
        // 이 함수는 씨앗 선택창에서 씨앗 버튼 눌렀을 때 호출되도록..

        farmingData[pos].seed = seedContainer.GetSeed(seedIdx).GetComponent<Seed>();

        farmTilemap.SetTile(pos, plantTile); // 타일 모습을 씨앗 심은 상태로 바꿔주기
        farmingData[pos].plantEnableState = true; // 씨앗을 심을 수 없는 상태를 나타내기 위해 false 로 변경
        farmingData[pos].currentState = "plant"; // 씨앗 심은 상태니까 plant 로 바꿔주기

        farmingData[pos].stateButton = farmingData[pos].buttons[2]; // harvest 버튼을 가지고 있도록..

        clickedSelectedSeedButton = false; // 한 번 심고 난 다음에 바로 변수값 false 로 바꿔주기
    }

    public void HarvestTile(Vector3Int pos)
    {
        // 과일을 수확하는 함수
        // 생각해보니까 씨앗 인덱스 여기로 안 보내줘도 pos 보내줬으니까, pos 가 가지는 씨앗 인스턴스의 씨앗 인덱스 이용하면 될 듯.

        farmingData[pos].plowEnableState = true;
        farmingData[pos].currentState = "None"; // 과일을 수확한 상태니까 None 으로 바꿔주기

        fruitContainer.GetFruit(farmingData[pos].seed.seedIdx); // 씨앗의 인덱스와 같은 과일 생성
        fruitContainer.fruitCount[farmingData[pos].seed.seedIdx]++; // 씨앗의 인덱스와 같은 과일의 수 증가시키기

        farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..
        farmingData[pos].stateButton = farmingData[pos].buttons[0]; // plow 버튼을 가지고 있도록..

        farmingData[pos].seed = null; // 수확 완료 했으니까 타일의 seed 변수를 다시 null 로 설정해주기..

        farmTilemap.SetTile(pos, grassTile); // 타일 모습을 초기 상태의로 바꿔주기
    }


    public void BuySeed(int count, int idx)
    {
        // 돈이 부족하면 씨앗 못사!
        if (GameManager.instance.money < seedContainer.prefabs[idx].GetComponent<Seed>().seedPrice * count) {
            Debug.Log("돈 없어!!!");
            return; 
        }

        seedContainer.seedCount[idx] += count; // 씨앗의 개수를 저장하고 있는 배열의 인덱스 요소에 구매한 씨앗의 개수만큼 더해주기
        GameManager.instance.money -= seedContainer.prefabs[idx].GetComponent<Seed>().seedPrice * count; // 가진 돈에서 차감!
    }
}

 

  • Seed
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Seed : MonoBehaviour
{
    // 씨앗은 다 프리팹으로 만들어 놓을 것
    // 만들어놓은 프리팹은 SeedContainer 에 저장할 것..

    public string seedName;
    public int seedPrice;
    public float growTime; // 성장하는데 걸리는 시간
    public float currentTime; // 심은 후부터 현재까지 시간
    public bool isGrown = false; // 다 자랐는지 여부 확인용 변수

    public int seedIdx; // 씨앗 인덱스


    private void OnEnable()
    {
        isGrown = false;
        currentTime = 0;
        Debug.Log("씨앗을 얻었다!");
    }

    private void Update()
    {
        if (currentTime >= growTime)
        {
            isGrown = true;
            Debug.Log("다 자랐다!");
            transform.gameObject.SetActive(!isGrown);
        }
        currentTime += Time.deltaTime;
    }
}

 

  • Fruit
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Fruit : MonoBehaviour
{
    // 과일은 다 프리팹으로 만들어 놓을 것
    // 만들어놓은 프리팹은 FruitContainer 에 저장할 것..

    public string fruitName;
    public float fruitPrice;
    public bool isEnabled = false;

    public int fruitIdx = 0; // 과일 인덱스

    private void OnEnable()
    {
        Debug.Log("과일을 얻었다!");
    }
}

 

  • SeedContainer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SeedContainer : MonoBehaviour
{
    public GameObject[] prefabs; // 씨앗 프리팹을 저장해놓기 위한 배열
    public List<GameObject>[] pools; // 씨앗을 심을 때 인스턴스를 생성하면 저장하는데 사용할 배열 
    public int[] seedCount; // 게임 상에서 어떤 씨앗이 몇 개 있는지 저장할 배열

    private void Awake()
    {
        // [0]:사과, [1]:바나나, [2]:체리, [3]:오렌지, [4]:딸기
        seedCount = new int[prefabs.Length]; // 프리팹의 개수만큼 배열 크기 지정

        pools = new List<GameObject>[prefabs.Length]; // 프리팹의 개수만큼 리스트 배열 생성

        for (int idx = 0; idx < pools.Length; idx++)
        {
            pools[idx] = new List<GameObject>(); // 리스트 배열 요소에 리스트 생성
        }
    }

    public GameObject GetSeed(int idx)
    {
        // GetSeed 함수는 씨앗 심기 했을 때 호출되는 함수임.
        // 즉, 씨앗을 구매하자마자 인스턴스가 생기는게 아님.

        GameObject select = null;

        // 선택된 인덱스에 들어있는 게임 오브젝트들을 for 문을 통해 모두 확인함.
        foreach (GameObject gameObj in pools[idx])
        {
            // 만약 놀고 있는 게임 오브젝트가 있다면(활성화 되지 않은 게임 오브젝트가 있다면) 가져오기
            if (gameObj.activeSelf == false)
            {
                select = gameObj;
                select.SetActive(true);
                break;
            }
        }

        // 놀고 있는 게임 오브젝트가 없다면(다 활성화 되어서 게임상에서 사용되고 있다면) 새로 만들기
        if (!select)
        {
            select = Instantiate(prefabs[idx], transform);
            pools[idx].Add(select);
        }

        return select;
    }
}

 

  • FruitContainer
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FruitContainer : MonoBehaviour
{
    public GameObject[] prefabs; // 과일 프리팹을 저장하기 위한 배열
    public List<GameObject>[] pools; // 과일이 생성되면 저장할 공간
    public int[] fruitCount; // 게임 상에서 어떤 과일이 몇 개 있는지 저장할 배열

    public int totalFruitCount;

    private void Awake()
    {
        // [0]:사과, [1]:바나나, [2]:체리, [3]:오렌지, [4]:딸기
        fruitCount = new int[prefabs.Length]; // 프리팹의 개수만큼 배열 크기 지정

        totalFruitCount = 0;
        pools = new List<GameObject>[prefabs.Length]; // 프리팹의 개수만큼 리스트 배열 생성

        for (int idx = 0; idx < prefabs.Length; idx++)
        {
            pools[idx] = new List<GameObject>(); // 리스트 배열 요소에 리스트 생성
        }
    }

    public GameObject GetFruit(int idx)
    {
        GameObject select = null;

        // 선택된 인덱스에 들어있는 게임 오브젝트들을 for 문을 통해 모두 확인함.
        foreach (GameObject obj in pools[idx])
        {
            // 만약 놀고 있는 게임 오브젝트가 있다면(활성화 되지 않은 게임 오브젝트가 있다면) 가져오기
            if (!obj.activeSelf)
            {
                select = obj;
                obj.SetActive(true);
                break;
            }
        }

        // 놀고 있는 게임 오브젝트가 없다면(다 활성화 되어서 게임상에서 사용되고 있다면) 새로 만들기
        if (!select)
        {
            select = Instantiate(prefabs[idx], transform);
            pools[idx].Add(select);
        }

        totalFruitCount++;

        return select;
    }
}

 

  • SeedUIManager
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class SeedUIManager : MonoBehaviour
{
    [Header("Seed Info")]
    public FarmingManager farmingManager; // 구매하기 버튼이랑 farmingManager 에서 SeedContainer 의 GetSeed 함수랑 연동하기 위해서..
    public SeedContainer seedInfo;
    public Sprite[] seedImages; // 스프라이트는 미리 배열에 넣어놔야 사용할 수 있음..

    [Header("Buy Seed UI")]
    public GameObject buySeedPanel;
    public GameObject buySlotContainer; // 구매 버튼 슬롯 가지고 있는 게임 오브젝트
    public List<Button> buySeedSlots; // 씨앗 구매창의 슬롯들 저장

    [Header("Plant Seed UI")]
    public GameObject plantSlotContainer; // 씨앗 선택 버튼 슬롯 가지고 있는 게임 오브젝트
    public List<Button> plantSeedSlots; // 씨앗 선택창의 슬롯들 저장(씨앗 심기 버튼 눌렀을 때 씨앗 선택창 뜸)

    [Header("Current Button")]
    public Button selectSlot;

    private void Awake()
    {
        // 현재 씨앗 구매 판넬에 존재하는 슬롯들을 가져와서 저장함.
        // 자식만 가져와야 하기 때문에 (자손은 가져오면 안 됨) GetComponentsInChildren 못 씀.
        for (int i = 0; i < buySlotContainer.transform.childCount; i++)
        {
            Transform child = buySlotContainer.transform.GetChild(i);
            buySeedSlots.Add(child.GetComponent<Button>());
        }

        // 현재 게임 상 존재하는 씨앗 구매 버튼 정보 설정
        for (int i = 0; i < buySeedSlots.Count; i++)
        {
            BuySeedSlotManager slot = buySeedSlots[i].GetComponent<BuySeedSlotManager>();
            Seed slotSeedInfo = seedInfo.prefabs[i].GetComponent<Seed>();

            slot.seedImage.sprite = seedImages[i];
            slot.seedName.text = slotSeedInfo.seedName;
            slot.totalPrice.text = "가격: " + slotSeedInfo.seedPrice;
            slot.seedIdx = slotSeedInfo.seedIdx;
            slot.seedCountText.text = "1";
        }


        // 얘는 그냥 GetComponentsInChildren 써도 되긴 하는데 그냥 통일감 주려고..
        for (int i = 0; i < plantSlotContainer.transform.childCount; i++)
        {
            Transform child = plantSlotContainer.transform.GetChild(i);
            plantSeedSlots.Add(child.GetComponent<Button>());
        }

        // 현재 게임 상 존재하는 씨앗 선택 버튼 정보 설정
        for (int i=0; i < plantSeedSlots.Count; i++)
        {
            PlantSeedSlotManager slot = plantSeedSlots[i].GetComponent<PlantSeedSlotManager>();
            Seed slotSeedInfo = seedInfo.prefabs[i].GetComponent<Seed>();

            slot.seedImage.sprite = seedImages[i];
            slot.seedNameText.text = slotSeedInfo.seedName;
            slot.seedCountText.text = seedInfo.seedCount[i] + "";
            slot.seedIdx = slotSeedInfo.seedIdx;
        }
    }

    private void Update()
    {
        // 임시로 W 키 누르면 구매창 켜지도록..
        if (Input.GetKeyDown(KeyCode.W))
            buySeedPanel.SetActive(true);


        for (int i = 0; i < buySeedSlots.Count; i++)
        {
            BuySeedSlotManager slot = buySeedSlots[i].GetComponent<BuySeedSlotManager>();
            Seed slotSeedInfo = seedInfo.prefabs[i].GetComponent<Seed>();

            // BuySlot 이 활성화 되어 있는 슬롯의 정보만 계속해서 변경해줄 것
            if (slot.BuySlot.activeSelf)
            {
                // 선택된 과일 개수랑 총 가격만 계속해서 업데이트 해주면 됨.
                slot.seedCountText.text = slot.seedCount + "";
                slot.totalPrice.text = "가격: " + (int)(slot.seedCount * slotSeedInfo.seedPrice);
            }
        }


        for (int i=0; i<plantSeedSlots.Count; i++)
        {
            // 씨앗의 개수만 계속해서 업데이트 해주면 됨..
            PlantSeedSlotManager slot = plantSeedSlots[i].GetComponent<PlantSeedSlotManager>();
            slot.seedCountText.text = farmingManager.seedContainer.seedCount[i] + "";
        }
    }

    public void CloseBuySlot()
    {
        for (int i = 0; i < buySeedSlots.Count; i++)
        {
            BuySeedSlotManager slot = buySeedSlots[i].GetComponent<BuySeedSlotManager>();
            slot.ResetData(); // 슬롯 데이터 한번 리셋해주기(껐다 켜졌는데 상태 그대로면 이상하니까)
            slot.BuySlot.SetActive(false);
        }
    }

    public void SlotClick()
    {
        CloseBuySlot(); // 슬롯 버튼 눌렀을 때, 다른 슬롯의 구매 슬롯이 켜져있으면 다 끄고 시작..
    }


    public void ExitButton()
    {
        buySeedPanel.SetActive(false); // 구매 창 없어지도록..

        CloseBuySlot(); // 나가기 버튼 누르면 켜져있던 구매 슬롯 없어지도록..
    }
}

 

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


public class BuySeedSlotManager : MonoBehaviour
{
    [Header("FarmingManager")]
    public FarmingManager farmingManager;

    [Header("Slot Button UI")]
    public Image seedImage;
    public Text seedName;
    public GameObject BuySlot;
    public Text totalPrice;
    public Text seedCountText;
    public Button leftButton;
    public Button rightButton;
    public Button buySeedButton;

    [Header("Slot Imformation")]
    public int prevSeedCount = 1;
    public int seedCount = 1;
    public int maxCount = 64;
    public int minCount = 1;
    public int seedIdx; // BuySeedUIManager 에서 값 설정해줄 것..


    private void Start()
    {
        farmingManager = FindObjectOfType<FarmingManager>(); // 음.. 처음에는 BuySeedUIManager 에서 슬롯 정보 초기화 해줄 때 이 값도 초기화 해주려고 했는데 안됨. 왜?? 그래서 일단 find 함수 사용
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(1, seedIdx)); // 일단 초기 함수 연결 해놓기
    }

    private void Update()
    {

        if (IsPointerOverUIObject()) return;
        else
        {
            // UI 가 아닌 부분을 클릭하면 그냥 꺼지도록..
            if (Input.GetMouseButtonDown(0))
            {
                BuySlot.SetActive(false);
            }
        }
    }


    private bool IsPointerOverUIObject()
    {
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current)
        {
            position = new Vector2(Input.mousePosition.x, Input.mousePosition.y)
        };
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        return results.Count > 0;
    }

    public void minusSeedCount()
    {
        if (seedCount <= minCount)
        {
            // 현재 구매하려고 하는 씨앗 개수가 씨앗 구매 최소 개수보다 작아지는 순간 최댓값으로 넘어가도록
            seedCount = maxCount + 1;
        }

        seedCount--;

        buySeedButton.onClick.RemoveAllListeners(); // 현재 선택 씨앗 개수가 변경되었으므로 모든 Listener 를 제거하고 시작
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(seedCount, seedIdx));
    }

    public void plusSeedCount()
    {
        // 현재 구매하려고 하는 씨앗 개수가 씨앗 구매 최대 개수보다 커지는 순간 최솟값으로 넘어가도록
        if (seedCount >= maxCount)
        {
            seedCount = minCount - 1;
        }

        seedCount++;

        buySeedButton.onClick.RemoveAllListeners(); // 현재 선택 씨앗 개수가 변경되었으므로 모든 Listener 를 제거하고 시작
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(seedCount, seedIdx));
    }


    public void ResetData()
    {
        seedCount = 1;
    }
}

 

  • PlantSeedSlotManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class PlantSeedSlotManager : MonoBehaviour
{
    [Header("FarmingManager")]
    public FarmingManager farmingManager;

    [Header("Slot Information")]
    public Text seedNameText; // 씨앗 이름 텍스트
    public Text seedCountText; // 씨앗 개수 텍스트
    public Image seedImage; // 씨앗 이미지
    public int seedIdx; // 씨앗 인덱스

    private void Start()
    {
        farmingManager = FindObjectOfType<FarmingManager>();
        transform.GetComponent<Button>().onClick.AddListener(ClickedPlantSeedButton);
    }

    public void ClickedPlantSeedButton()
    {
        farmingManager.plantSeedPanel.gameObject.SetActive(false); // 버튼 눌리는 즉시에 판넬 꺼버리기

        // 씨앗의 개수가 0보다 작거나 같으면 그냥 빠져나가도록..
        if (farmingManager.seedContainer.seedCount[seedIdx] <= 0) {
            Debug.Log("씨앗 없어!!!!");
            return; 
        } 

        farmingManager.selectedSeedIdx = seedIdx; // 현재 심을 씨앗 인덱스를 설정
        farmingManager.clickedSelectedSeedButton = true; // 버튼이 클릭됐다는 걸 알려줌..
        farmingManager.seedContainer.seedCount[seedIdx]--; // 버튼 클릭하면 씨앗 심는거니까 씨앗 개수 줄어들도록
    }
}

 

  • FarmingData
// 타일이 가지는 농사 데이터
class FarmingData
{
    public Seed seed; // 타일이 가지는 씨앗 정보
    //public bool seedOnTile; // 타일 위에 씨앗이 있는지 여부 확인용(씨앗이 있으면 밭을 갈 수 없음)
    public bool plowEnableState; // 밭을 갈 수 있는 상태인지 여부 확인용(밭이 안 갈린 상태)
    public bool plantEnableState; // 씨앗을 심을 수 있는 상태인지 여부 확인용
    public bool harvestEnableState; // 작물이 다 자란 상태인지 여부 확인용
    public Button stateButton; // 타일을 누르면 타일 위에 뜨도록 하는 버튼
    public Button[] buttons; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼

    public string currentState = "None"; // 현재 상태(초기에는 아무것도 안 한 상태니까 None 으로.. -> plow: 밭 갈린 상태, plant: 씨앗 심은 상태, harvest: 다 자란 상태)
}

 


 

[주요 기능 설명]

 

1. 씨앗 구매하기 버튼 게임 매니저의 money 변수 이용해서 로직 추가하기

---> 참고자료: https://moblieandlife.tistory.com/entry/UI-Button-OnClick-%EB%A6%AC%EC%8A%A4%EB%84%88-%EC%8A%A4%ED%81%AC%EB%A6%BD%ED%8A%B8%EC%97%90%EC%84%9C-%EC%A7%80%EC%A0%95%ED%95%98%EA%B8%B0#google_vignette

 

UGUI - UI Button OnClick 리스너 스크립트에서 지정하기

inspector에서 버튼에 호출함수를 넣기 어려운 상황이라면 스크립트에서 호출함수를 AddListener를 통하여 연결시켜 줄수있다. 주의할점 : 초기화 RemoveAllListeners (); RemoveListener(callFunction); 를 통해서

moblieandlife.tistory.com

 

  • BuySeedSlotManager 클래스에 FarmingManager 타입의 farmingManager 변수를 선언해서 사용한다.
  • buySeedButton 변수에 farmingManager 의 BuySeed 함수를 연결한다.
  • BuySeed 함수는 매개변수로 구매하려는 씨앗 개수와, 씨앗 인덱스를 전달받는다.
  • BuySeed 함수에서는 현재 돈이 구매하려는 씨앗의 가격보다 적으면 그대로 빠져나오도록 했다.
  • seedContainer 변수의 seedCount 배열의 현재 구매한 씨앗의 인덱스 요소를 구매한 씨앗의 개수만큼 증가시켰다(seedCount 배열은 새로 추가한 변수이다. 씨앗을 구매하자마자 씨앗 인스턴스를 생성하는 게 아니라 배열에 저장해놨다가 심기 버튼을 눌렀을 때 생성되도록 하기 위해서 선언했다). 
  • 맨 처음에는 buySeedButton 에 BuySeed(1, seedIdx); 구문으로 현재 슬롯이 가리키고 있는 씨앗을 1개 구매하는 함수와 연결했다.
  • buySeedButton 에 연결한 함수의 매개변수는 씨앗의 개수가 씨앗의 개수를 조정하는 버튼을 눌렀을 때 계속해서 바뀌어야 하므로 minusSeedCount, plusSeedCount 함수에 현재 구매 버튼에 연결되어 있는 함수를 모두 삭제하고 새로운 함수를 연결하는 구문을 추가했다.
public void BuySeed(int count, int idx)
{
    // 돈이 부족하면 씨앗 못사!
    if (GameManager.instance.money < seedContainer.prefabs[idx].GetComponent<Seed>().seedPrice * count) {
        Debug.Log("돈 없어!!!");
        return; 
    }

    seedContainer.seedCount[idx] += count; // 씨앗의 개수를 저장하고 있는 배열의 인덱스 요소에 구매한 씨앗의 개수만큼 더해주기
    GameManager.instance.money -= seedContainer.prefabs[idx].GetComponent<Seed>().seedPrice * count; // 가진 돈에서 차감!
}

 

(SeedContainer 클래스 일부분 뜯어옴)

public GameObject[] prefabs; // 씨앗 프리팹을 저장해놓기 위한 배열
public List<GameObject>[] pools; // 씨앗을 심을 때 인스턴스를 생성하면 저장하는데 사용할 배열 
public int[] seedCount; // 게임 상에서 어떤 씨앗이 몇 개 있는지 저장할 배열

private void Awake()
{
    // [0]:사과, [1]:바나나, [2]:체리, [3]:오렌지, [4]:딸기
    seedCount = new int[prefabs.Length]; // 프리팹의 개수만큼 배열 크기 지정

    pools = new List<GameObject>[prefabs.Length]; // 프리팹의 개수만큼 리스트 배열 생성

    for (int idx = 0; idx < pools.Length; idx++)
    {
        pools[idx] = new List<GameObject>(); // 리스트 배열 요소에 리스트 생성
    }
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;


public class BuySeedSlotManager : MonoBehaviour
{
    [Header("FarmingManager")]
    public FarmingManager farmingManager;

    [Header("Slot Button UI")]
    public Image seedImage;
    public Text seedName;
    public GameObject BuySlot;
    public Text totalPrice;
    public Text seedCountText;
    public Button leftButton;
    public Button rightButton;
    public Button buySeedButton;

    [Header("Slot Imformation")]
    public int prevSeedCount = 1;
    public int seedCount = 1;
    public int maxCount = 64;
    public int minCount = 1;
    public int seedIdx; // BuySeedUIManager 에서 값 설정해줄 것..


    private void Start()
    {
        farmingManager = FindObjectOfType<FarmingManager>(); // 음.. 처음에는 BuySeedUIManager 에서 슬롯 정보 초기화 해줄 때 이 값도 초기화 해주려고 했는데 안됨. 왜?? 그래서 일단 find 함수 사용
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(1, seedIdx)); // 일단 초기 함수 연결 해놓기
    }

    private void Update()
    {

        if (IsPointerOverUIObject()) return;
        else
        {
            // UI 가 아닌 부분을 클릭하면 그냥 꺼지도록..
            if (Input.GetMouseButtonDown(0))
            {
                BuySlot.SetActive(false);
            }
        }
    }


    private bool IsPointerOverUIObject()
    {
        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current)
        {
            position = new Vector2(Input.mousePosition.x, Input.mousePosition.y)
        };
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        return results.Count > 0;
    }

    public void minusSeedCount()
    {
        if (seedCount <= minCount)
        {
            // 현재 구매하려고 하는 씨앗 개수가 씨앗 구매 최소 개수보다 작아지는 순간 최댓값으로 넘어가도록
            seedCount = maxCount + 1;
        }

        seedCount--;

        buySeedButton.onClick.RemoveAllListeners(); // 현재 선택 씨앗 개수가 변경되었으므로 모든 Listener 를 제거하고 시작
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(seedCount, seedIdx));
    }

    public void plusSeedCount()
    {
        // 현재 구매하려고 하는 씨앗 개수가 씨앗 구매 최대 개수보다 커지는 순간 최솟값으로 넘어가도록
        if (seedCount >= maxCount)
        {
            seedCount = minCount - 1;
        }

        seedCount++;

        buySeedButton.onClick.RemoveAllListeners(); // 현재 선택 씨앗 개수가 변경되었으므로 모든 Listener 를 제거하고 시작
        buySeedButton.onClick.AddListener(() => farmingManager.BuySeed(seedCount, seedIdx));
    }


    public void ResetData()
    {
        seedCount = 1;
    }
}

 

 

2. 심기 버튼이랑 씨앗 선택창이랑 연동하기

  • 이전에는 심기 버튼에 씨앗 심기 함수를 연동했는데 현재는 씨앗 선택창을 활성화시키는 함수를 연동시켰다(farmingManager 클래스의 Start 함수 속에서 연동시켜주었다).
  • OpenPlantSeedPanel 함수는 현재 선택한 타일의 위치를 매개변수로 받은 후, 그 위치에 활성화 되어있던 버튼의 활성화를 꺼주었다.
  •  plantSeedPanel 에 씨앗 선택창을 연결해놓았기 때문에 SetActive 함수를 이용해서 활성화시켰다. 
public void OpenPlantSeedPanel(Vector3Int pos)
{
    // 심기 버튼이랑 연결해줘야함.
    farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..

    plantSeedPanel.SetActive(true); // 심기 버튼 눌렀을 때 씨앗 선택창 뜨도록 하기 위함
}

 

(farmingManager 클래스의 Start 함수 속 들어있는 구문)

// 각 타일마다 세 개의 버튼을 가지고 시작하도록..
for (int i = 0; i < buttonPrefabs.Length; i++)
{
    // 클로저 문제를 피하기 위해서 값을 변수에 저장해놓고 이 변수를 사용함..
    int index = i;
    Vector3Int tilePos = pos;
    farmingData[pos].buttons[i] = CreateButton(index, tilePos).GetComponent<Button>();

    if (index == 0)
    {
        // 버튼에 함수를 저장해놓음(tilePos 도 같이 저장해놓기)
        farmingData[tilePos].buttons[index].onClick.AddListener(() => PlowTile(tilePos));
    }
    else if (index == 1)
    {
        //farmingData[tilePos].buttons[index].onClick.AddListener(() => PlantTile(tilePos));
        farmingData[tilePos].buttons[index].onClick.AddListener(() => OpenPlantSeedPanel(tilePos)); // 씨앗 선택창 화면에 띄우는 함수 연결시키기
    }
    else if (index == 2)
    {
        farmingData[tilePos].buttons[index].onClick.AddListener(() => HarvestTile(tilePos));
    }
}

 

 

3. 씨앗 선택창에서 버튼 누르면 씨앗 심어지도록 하기

  • 씨앗 선택창의 버튼의 정보를 관리하기 위해 PlantSeedSlotManager 라는 클래스를 새로 만들었다.
  • 씨앗 버튼은 FarmingManager 의 정보를 이용해야 하므로 FarmingManager 타입의 farmingManager 변수를 선언했다.
  • 게임 플레이 버튼을 누르자마자 farmingManager 변수에 값을 넣어주기 위해 FindObjectOfType 함수를 이용했다.
  • 그리고 씨앗 버튼에 함수를 연결시켰다. 연결시키는 함수는 씨앗 버튼이 클릭 됐을 때 씨앗을 심기위한 로직에 관여하는 함수이다.
  • 함수에 진입하자마자 씨앗 선택창 판넬을 끄도록 하고, 선택한 씨앗의 개수가 0보다 작거나 같으면 그냥 빠져나가도록 하는 로직을 작성했다.
  • 씨앗 버튼이 farmingManager 에서 이용하는 변수는 selectedSeedIdx, clickedSelectedSeedButton, seedContainer 변수의 seedCount 이다.
  • farmingManager 가 현재 심어야하는 씨앗의 인덱스, 씨앗 버튼이 클릭되었는지의 여부를 알 수 있도록 하여 씨앗을 심어야 하는지 말아야 하는지 결정하도록 했다. 
  • clickedSelectedSeedButton 변수의 값이 true 가 되면 조건문에 진입해서 PlantTile 함수를 호출한다.
  • PlantTile 함수는 매개변수로 타일의 위치와, 씨앗 인덱스를 받는다.
  • 함수 맨 마지막 부분에 clickedSelectedSeedButton 변수의 값을 false 로 바꿔서 다시 다음 씨앗 버튼이 눌리기 전까지 조건문에 진입하지 못하도록 했다.
public class PlantSeedSlotManager : MonoBehaviour
{
    [Header("FarmingManager")]
    public FarmingManager farmingManager;

    [Header("Slot Information")]
    public Text seedNameText; // 씨앗 이름 텍스트
    public Text seedCountText; // 씨앗 개수 텍스트
    public Image seedImage; // 씨앗 이미지
    public int seedIdx; // 씨앗 인덱스

    private void Start()
    {
        farmingManager = FindObjectOfType<FarmingManager>();
        transform.GetComponent<Button>().onClick.AddListener(ClickedPlantSeedButton);
    }

    public void ClickedPlantSeedButton()
    {
        farmingManager.plantSeedPanel.gameObject.SetActive(false); // 버튼 눌리는 즉시에 판넬 꺼버리기

        // 씨앗의 개수가 0보다 작거나 같으면 그냥 빠져나가도록..
        if (farmingManager.seedContainer.seedCount[seedIdx] <= 0) {
            Debug.Log("씨앗 없어!!!!");
            return; 
        } 

        farmingManager.selectedSeedIdx = seedIdx; // 현재 심을 씨앗 인덱스를 설정
        farmingManager.clickedSelectedSeedButton = true; // 버튼이 클릭됐다는 걸 알려줌..
        farmingManager.seedContainer.seedCount[seedIdx]--; // 버튼 클릭하면 씨앗 심는거니까 씨앗 개수 줄어들도록
    }
}

 

(farmingManager 클래스의 Update 함수의 맨 마지막 부분에 위치함)

// 씨앗 선택창에서 버튼 클릭하면 진입하도록..
if (clickedSelectedSeedButton)
{
    // 씨앗 심는 함수 호출
    PlantTile(cellPosition, selectedSeedIdx);
}
public void PlantTile(Vector3Int pos, int seedIdx)
{
    // 씨앗을 심는 함수
    // 이 함수는 씨앗 선택창에서 씨앗 버튼 눌렀을 때 호출되도록..

    farmingData[pos].seed = seedContainer.GetSeed(seedIdx).GetComponent<Seed>();

    farmTilemap.SetTile(pos, plantTile); // 타일 모습을 씨앗 심은 상태로 바꿔주기
    farmingData[pos].plantEnableState = true; // 씨앗을 심을 수 없는 상태를 나타내기 위해 false 로 변경
    farmingData[pos].currentState = "plant"; // 씨앗 심은 상태니까 plant 로 바꿔주기

    farmingData[pos].stateButton = farmingData[pos].buttons[2]; // harvest 버튼을 가지고 있도록..

    clickedSelectedSeedButton = false; // 한 번 심고 난 다음에 바로 변수값 false 로 바꿔주기
}

 

 

4. 다 자란 과일 수확하면 인덱스 번호에 맞는 배열의 요소 개수 증가하도록 하기

  • FruitContainer 클래스에 fruitCount 변수를 새로 만들었다.
  • Harvest 함수에 fruitContainer.fruitCount[farmingData[pos].seed.seedIdx]++; 구문을 통해 현재 수확하는 과일의 인덱스에 맞는 요소의 수를 증가시키도록 했다.

(FruitContainer 클래스 일부분 뜯어옴)

public GameObject[] prefabs; // 과일 프리팹을 저장하기 위한 배열
public List<GameObject>[] pools; // 과일이 생성되면 저장할 공간
public int[] fruitCount; // 게임 상에서 어떤 과일이 몇 개 있는지 저장할 배열

public int totalFruitCount;

private void Awake()
{
    // [0]:사과, [1]:바나나, [2]:체리, [3]:오렌지, [4]:딸기
    fruitCount = new int[prefabs.Length]; // 프리팹의 개수만큼 배열 크기 지정

    totalFruitCount = 0;
    pools = new List<GameObject>[prefabs.Length]; // 프리팹의 개수만큼 리스트 배열 생성

    for (int idx = 0; idx < prefabs.Length; idx++)
    {
        pools[idx] = new List<GameObject>(); // 리스트 배열 요소에 리스트 생성
    }
}
public void HarvestTile(Vector3Int pos)
{
    // 과일을 수확하는 함수
    // 생각해보니까 씨앗 인덱스 여기로 안 보내줘도 pos 보내줬으니까, pos 가 가지는 씨앗 인스턴스의 씨앗 인덱스 이용하면 될 듯.

    farmingData[pos].plowEnableState = true;
    farmingData[pos].currentState = "None"; // 과일을 수확한 상태니까 None 으로 바꿔주기

    fruitContainer.GetFruit(farmingData[pos].seed.seedIdx); // 씨앗의 인덱스와 같은 과일 생성
    fruitContainer.fruitCount[farmingData[pos].seed.seedIdx]++; // 씨앗의 인덱스와 같은 과일의 수 증가시키기

    farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..
    farmingData[pos].stateButton = farmingData[pos].buttons[0]; // plow 버튼을 가지고 있도록..

    farmingData[pos].seed = null; // 수확 완료 했으니까 타일의 seed 변수를 다시 null 로 설정해주기..

    farmTilemap.SetTile(pos, grassTile); // 타일 모습을 초기 상태의로 바꿔주기
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FruitContainer : MonoBehaviour
{
    public GameObject[] prefabs; // 과일 프리팹을 저장하기 위한 배열
    public List<GameObject>[] pools; // 과일이 생성되면 저장할 공간
    public int[] fruitCount; // 게임 상에서 어떤 과일이 몇 개 있는지 저장할 배열

    public int totalFruitCount;

    private void Awake()
    {
        // [0]:사과, [1]:바나나, [2]:체리, [3]:오렌지, [4]:딸기
        fruitCount = new int[prefabs.Length]; // 프리팹의 개수만큼 배열 크기 지정

        totalFruitCount = 0;
        pools = new List<GameObject>[prefabs.Length]; // 프리팹의 개수만큼 리스트 배열 생성

        for (int idx = 0; idx < prefabs.Length; idx++)
        {
            pools[idx] = new List<GameObject>(); // 리스트 배열 요소에 리스트 생성
        }
    }

    public GameObject GetFruit(int idx)
    {
        GameObject select = null;

        // 선택된 인덱스에 들어있는 게임 오브젝트들을 for 문을 통해 모두 확인함.
        foreach (GameObject obj in pools[idx])
        {
            // 만약 놀고 있는 게임 오브젝트가 있다면(활성화 되지 않은 게임 오브젝트가 있다면) 가져오기
            if (!obj.activeSelf)
            {
                select = obj;
                obj.SetActive(true);
                break;
            }
        }

        // 놀고 있는 게임 오브젝트가 없다면(다 활성화 되어서 게임상에서 사용되고 있다면) 새로 만들기
        if (!select)
        {
            select = Instantiate(prefabs[idx], transform);
            pools[idx].Add(select);
        }

        totalFruitCount++;

        return select;
    }
}