[구현한 기능]
- 씨앗 선택창 만들기
- 심기 버튼이랑 씨앗 선택창이랑 연동하기
- 씨앗 선택창에서 버튼 누르면 씨앗 심어지도록 하기
- 씨앗 구매하기 버튼 게임 매니저의 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 변수 이용해서 로직 추가하기
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;
}
}