[개발일지] 12. 농작물 자라는 로직 변경

2024. 8. 15. 14:05·유니티 프로젝트/케이크게임

[구현한 기능]

  • 시간이 흐름에 따라 자라도록 했던 기능을, 날이 지나면 자라도록 변경했다.

 

[변경된 클래스]

FarmingManager

SeedItemSO

 

 

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




[Serializable]
// 딕셔너리를 클래스 형식으로 key, value 를 만들어서 구성하가ㅣ..
// 다른 Dictionary 도 고려하여 제네릭 타입으로 만들기..
public class DataDictionary<TKey, TValue>
{
    public TKey Key;
    public TValue Value;
}

[Serializable]
public class JsonDataArray<TKey, TValue>
{
    // 임의로 생성한 딕셔너리 값 저장용 리스트
    public List<DataDictionary<TKey, TValue>> data;
}

public static class DictionaryJsonUtility
{

    /// <summary>
    /// Dictionary를 Json으로 파싱하기
    /// </summary>
    /// <typeparam name="TKey">Dictionary Key값 형식</typeparam>
    /// <typeparam name="TValue">Dictionary Value값 형식</typeparam>
    /// <param name="jsonDicData"></param>
    /// <returns></returns>
    public static string ToJson<TKey, TValue>(Dictionary<TKey, TValue> jsonDicData, bool pretty = false)
    {
        List<DataDictionary<TKey, TValue>> dataList = new List<DataDictionary<TKey, TValue>>();
        DataDictionary<TKey, TValue> dictionaryData;
        foreach (TKey key in jsonDicData.Keys)
        {
            dictionaryData = new DataDictionary<TKey, TValue>();
            dictionaryData.Key = key;
            dictionaryData.Value = jsonDicData[key];
            dataList.Add(dictionaryData);
        }
        JsonDataArray<TKey, TValue> arrayJson = new JsonDataArray<TKey, TValue>();
        arrayJson.data = dataList;

        return JsonUtility.ToJson(arrayJson, pretty);
    }

    /// <summary>
    /// Json Data를 다시 Dictionary로 파싱하기
    /// </summary>
    /// <typeparam name="TKey">Dictionary Key값 형식</typeparam>
    /// <typeparam name="TValue">Dictionary Value값 형식</typeparam>
    /// <param name="jsonData">파싱되었던 데이터</param>
    /// <returns></returns>

    public static Dictionary<TKey, TValue> FromJson<TKey, TValue>(string jsonData)
    {
        JsonDataArray<TKey, TValue> arrayJson = JsonUtility.FromJson<JsonDataArray<TKey, TValue>>(jsonData);
        List<DataDictionary<TKey, TValue>> dataList = arrayJson.data;

        Dictionary<TKey, TValue> returnDictionary = new Dictionary<TKey, TValue>();


        for (int i = 0; i < dataList.Count; i++)
        {
            DataDictionary<TKey, TValue> dictionaryData = dataList[i];

            returnDictionary.Add(dictionaryData.Key, dictionaryData.Value);
        }

        return returnDictionary;
    }
}



[Serializable]
// 아예 따로 클래스 만들어서 값을 저장할 때 Vector3Int 대신 PosInt 써야할 것 같다..
public class PosInt
{
    [SerializeField]
    public int x;
    [SerializeField]
    public int y;
    [SerializeField]
    public int z;
}


// 데이터 저장 클래스
[Serializable]
public class SaveFarmingData
{
    [SerializeField]
    public bool seedOnTile; // 타일 위에 씨앗이 있는지 여부 확인용
    [SerializeField]
    public bool plowEnableState; // 밭을 갈 수 있는 상태인지 여부 확인용
    [SerializeField]
    public bool plantEnableState; // 씨앗을 심을 수 있은 상태인지 여부 확인용
    [SerializeField]
    public bool harvestEnableState; // 작물이 다 자란 상태인지 여부 확인용
    [SerializeField]
    public string currentState; // 농사 땅 상태..

    // 씨앗 데이터
    [SerializeField]
    public int seedIdx; // 씨앗 인덱스 저장(종류 저장하기 위함)..
    [SerializeField]
    public bool isGrown; // 씨앗이 다 자랐는지 안자랐는지 여부 저장..


    [SerializeField]
    // 씨앗 마다 자라기까지 걸리는 일수가 있어서 다르게 자라도록?_?
    public int curDay; // 씨앗 심고 지난 시간(씬 전환 될 때마다 1씩 증가하는 식으로 하면 될 것 같다)


    public void PrintData()
    {
        Debug.Log("씨앗 있어여?: " + seedOnTile + " " + plowEnableState + " " + plantEnableState + " " + harvestEnableState + " " + currentState + " " + seedIdx + ", 일수: " + curDay + " " + isGrown);
    }
}


// 타일이 가지는 농사 데이터
[Serializable]
class FarmingData
{
    [SerializeField]
    public bool plowEnableState; // 밭을 갈 수 있는 상태인지 여부 확인용(밭이 안 갈린 상태)
    [SerializeField]
    public bool plantEnableState; // 씨앗을 심을 수 있는 상태인지 여부 확인용
    [SerializeField]
    public bool harvestEnableState; // 작물이 다 자란 상태인지 여부 확인용


    /*
     Button과 같은 Unity 엔진 내장 컴포넌트들은 게임 오브젝트나 컴포넌트로 참조되어 있기 때문에 JSON 직렬화에서 제대로 다룰 수 없다고함..
     Unity의 직렬화 시스템도 이러한 Unity 엔진 내장 객체들을 처리하는 방식과 JSON 직렬화는 다르기 때문에, 이런 컴포넌트들을 JSON으로 저장할 수는 없다고 함..
     => 일반적인 경우는 UI 요소들은 직렬화에서 제외한다고 함..
     */
    [NonSerialized] public Button stateButton; // 타일을 누르면 타일 위에 뜨도록 하는 버튼
    [NonSerialized] public Button[] buttons; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼

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



    [SerializeField]
    public int seedIdx = -1; // 그러면 이제 씨앗 인덱스도 저장해야 할듯(아무것도 안 심어져 있는 상태는 -1을 값으로 가지도록)..
    [SerializeField]
    public bool seedOnTile; // 그럼 이제 그냥 Seed 클래스 사용하지 말고 bool 값으로 씨앗이 있는지 여부 판단하는 게 좋을 듯..
    [SerializeField]
    public bool isGrown; // 다 자랐는지 여부도 이제 얘가 저장하도록 하는게..
    // 이제 시간이 아니라 일수로 관리하도록 수정할 것..
    // 근데 이러면 딱히 시간 안 더해줘도 되니까 씨앗도 그냥 ScriptableObject 클래스로만 관리해야 좋을 것 같은데
    // 원래는 시간 더해줘야 하니까 Monobehaviour 클래스를 상속받아야 했던 거고..
    [SerializeField]
    // 씨앗 마다 자라기까지 걸리는 일수가 있어서 다르게 자라도록?_?
    public int curDay; // 씨앗 심고 지난 일수(씬 전환 될 때마다 1씩 증가하는 식으로 하면 될 것 같다)


    public void SetData()
    {
        plowEnableState = true;
    }
}


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

    // 아이템 스크립터블 오브젝트를 저장해놓기..
    public FruitItemSO[] fruitItems; // [0]: 사과, [1]: 바나나, [2]: 체리, [3]: 오렌지, [4]: 딸기
    public SeedItemSO[] seedItems; // [0]: 사과, [1]: 바나나, [2]: 체리, [3]: 오렌지, [4]: 딸기


    [Header("Tile")]
    public TileBase borderTile; // 제한 구역 상태
    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;
    public int farmLevel = 0; // 농장 레벨. 농장 레벨 업그레이드 함수 호출하면 증가하도록..
    public int expansionSize = 1; // 농장 한 번 업그레이트 할 때 얼마나 확장될 건지.. 일단 임시로 1로 해놨다.. 나중에 변경할 것.

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



    // 데이터 저장
    [Header("Save Data")]
    private string farmingDataFilePath; // 농사 데이터 저장 경로..



    public void SaveFarmingData()
    {
        Dictionary<PosInt, SaveFarmingData> tempDic = new Dictionary<PosInt, SaveFarmingData>();
        foreach (var item in farmingData)
        {
            Debug.Log(item.Key + "저장할겁니다!!");

            // JSON 저장할 때 Vector3Int 가 직렬화가 안되므로 따로 만든 PosString 이용하가ㅣ..
            PosInt pos = new PosInt
            {
                x = item.Key.x,
                y = item.Key.y,
                z = item.Key.z
            };

            SaveFarmingData temp = new SaveFarmingData
            {
                plowEnableState = farmingData[item.Key].plowEnableState,
                plantEnableState = farmingData[item.Key].plantEnableState,
                harvestEnableState = farmingData[item.Key].harvestEnableState,
                currentState = farmingData[item.Key].currentState
            };



            // 이렇게 바꿀거임..

            // 농사 땅 위에 씨앗이 없을 때 진입..
            if (farmingData[item.Key].seedOnTile == false)
            {
                temp.seedOnTile = farmingData[item.Key].seedOnTile;
            }
            // 농사 땅 위에 씨앗 있을 때 진입..
            else
            {
                temp.seedOnTile = farmingData[item.Key].seedOnTile;
                temp.seedIdx = farmingData[item.Key].seedIdx;
                temp.curDay = farmingData[item.Key].curDay;
                temp.isGrown = farmingData[item.Key].isGrown;
            }
            tempDic.Add(pos, temp);
            tempDic[pos].PrintData();
        }


        string json = DictionaryJsonUtility.ToJson(tempDic, true);
        Debug.Log(json);
        Debug.Log("데이터 저장 완료!");


        // 외부 폴더에 접근해서 Json 파일 저장하기
        // Application.persistentDataPath: 특정 운영체제에서 앱이 사용할 수 있도록 허용한 경로
        File.WriteAllText(farmingDataFilePath, json);
    }


    // 씬 로드 된 후에 SetFarmingData 함수 먼저 호출한 후 호출할 함수..
    public void LoadFarmingData()
    {
        // Json 파일 경로 가져오기
        string path = Path.Combine(Application.persistentDataPath, "FarmingData.json");

        // 지정된 경로에 파일이 있는지 확인한다
        if (File.Exists(path))
        {
            Debug.Log("파일 있어여!!");


            // 경로에 파일이 있으면 Json 을 다시 오브젝트로 변환한다.
            string json = File.ReadAllText(path);
            Debug.Log(json);
            Dictionary<PosInt, SaveFarmingData> tempDic = DictionaryJsonUtility.FromJson<PosInt, SaveFarmingData>(json);
            Debug.Log(tempDic.Count + "!!!!!!!!!!!!!!!!!!!!1");


            foreach (var item in tempDic)
            {
                tempDic[item.Key].PrintData();

                Vector3Int pos = new Vector3Int(item.Key.x, item.Key.y, item.Key.z);

                switch (tempDic[item.Key].currentState)
                {
                    // 현재 농사 땅 상태에 맞는 버튼으로 설정해주기..

                    case "None":
                        farmingData[pos].stateButton = farmingData[pos].buttons[0];
                        farmTilemap.SetTile(pos, grassTile); // 타일을 아무것도 안 한 상태로 변경(키 값이 농사땅의 pos 임)
                        break;

                    case "plow":
                        farmingData[pos].stateButton = farmingData[pos].buttons[1];
                        farmTilemap.SetTile(pos, farmTile); // 타일을 밭 갈린 모습으로 변경..
                        break;

                    case "plant":
                        farmingData[pos].stateButton = farmingData[pos].buttons[2];
                        farmTilemap.SetTile(pos, plantTile); // 타일을 씨앗 심은 모습으로 변경..
                        break;

                    case "harvest":
                        farmingData[pos].stateButton = farmingData[pos].buttons[2];
                        farmTilemap.SetTile(pos, harvestTile); // 타일을 다 자란 모습으로 변경..
                        break;
                }


                // 저장해놓은 데이터 가져와서 설정해주기..
                farmingData[pos].plowEnableState = tempDic[item.Key].plowEnableState;
                farmingData[pos].plantEnableState = tempDic[item.Key].plantEnableState;
                farmingData[pos].harvestEnableState = tempDic[item.Key].harvestEnableState;
                farmingData[pos].currentState = tempDic[item.Key].currentState;


                // 이렇게 수정할거임!!
                // 저장 당시 농사 땅 위에 씨앗 있었으면 씨앗 데이터 설정해주기..
                if (tempDic[item.Key].seedOnTile)
                {
                    farmingData[pos].seedOnTile = tempDic[item.Key].seedOnTile;
                    farmingData[pos].seedIdx = tempDic[item.Key].seedIdx;
                    farmingData[pos].curDay = tempDic[item.Key].curDay;
                    farmingData[pos].isGrown = tempDic[item.Key].isGrown;
                }
            }
        }
        // 지정된 경로에 파일이 없으면
        else
        {
            Debug.Log("파일이 없어요!!");
        }
    }


    private void Awake()
    {
        // 데이터 저장 경로 설정..
        farmingDataFilePath = Path.Combine(Application.persistentDataPath, "FarmingData.json"); // 데이터 경로 설정..


        farmingData = new Dictionary<Vector3Int, FarmingData>(); // 딕셔너리 생성
        clickPosition = Vector2.zero;

    }


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

            SetFarmingData(pos); // FarmingData 타입 인스턴스의 정보를 세팅해주는 함수.
        }


        // 농사 땅 레벨 데이터 불러오기..
        farmLevel = PlayerPrefs.GetInt("FarmLevel");
        // 농사 땅 레벨 데이터를 불러온 다음에 레벨 데이터에 맞게끔 땅 업그레이드 해주기.. 
        while (farmLevel > 0)
        {
            SetFarmSize();
            farmLevel--;
        }


        LoadFarmingData(); // 데이터 가져오기..


        // LoadFarmingData 로 데이터를 가져왔으므로 이제 가능
        // 농사 땅을 전부 돌면서 씨앗이 심어져 있으면 일수 증가시킴..
        foreach (var item in farmingData)
        {
            // 만약 씨앗이 심어져 있으면
            if (farmingData[item.Key].seedOnTile)
            {
                Debug.Log("씨앗이 심어져있어용~");

                // 씨앗이 심긴 후 지난 일수가 씨앗이 다 자라는데 걸리는 일수랑 같으면
                if (farmingData[item.Key].curDay == seedItems[farmingData[item.Key].seedIdx].growDay - 1)
                {
                    Debug.Log("다 자랐어용!");

                    // 다 자랐음을 표시하기 위해 isGrown 의 값을 true 로 변경해주기..
                    farmingData[item.Key].isGrown = true;
                }
                // 씨앗이 심긴 후 지난 일수가 씨앗이 다 자라는데 걸리는 일수보다 작으면
                else if (farmingData[item.Key].curDay < seedItems[farmingData[item.Key].seedIdx].growDay - 1)
                {
                    Debug.Log("다 안 자랐어용!");

                    // 일수를 증가시켜주기..
                    farmingData[item.Key].curDay++;
                }
            }
            else
            {
                Debug.Log("씨앗이 안 심어져있어용~");
            }
        }

        // 저장하기
        SaveFarmingData(); // 변경 사항 생겼으니까 저장해주기..
    }


    void Update()
    {
        //// 모바일용
        //if (Input.touchCount > 0)
        //FarmingSystemMobile();


        // 버튼 눌렀을 때 뒤에 있는 타일 못 누르도록 하기 위한 구문..
        // 이거 데스크탑용
        if (IsPointerOverUIObjectPC()) return;

        // 데스크탑용
        // 땅을 왼쪽 마우스키로 누르면..
        if (Input.GetMouseButtonDown(0))
        {
            // 땅을 왼쪽 마우스키로 눌렀을 때, 땅의 현재 상태를 파악한 후 버튼 등 전반적인 UI 조정하는 것과 관련된 로직 함수..
            FarmingSystemPC();
        }


        //GrowTimeUpdate(); // 과일이 다 자라기까지 남은 시간 업데이트 해주는 함수..

        CheckGrowedFruit(); // 과일이 다 자랐는지 확인하고, 다 자랐으면 그에 맞는 행동을 하도록 해주는 함수..


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


        // 확인용 로직..
        if (Input.GetKeyDown(KeyCode.W))
            SaveFarmingData();
    }


    private void FarmingSystemPC()
    {
        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].seedOnTile == false || farmingData[cellPosition].isGrown)
                    {
                        farmingData[cellPosition].stateButton.gameObject.SetActive(true);
                    }
                    // 씨앗이 자라는 중이면 남은 시간 나타내는 판넬 뜨도록
                    else if (!farmingData[cellPosition].isGrown)
                    {
                        // 판넬 위치를 현재 클릭한 타일 위치로..
                        growTimePanel.transform.position = mainCamera.WorldToScreenPoint(farmTilemap.CellToWorld(cellPosition)) + new Vector3(0, 100, 0);
                        growTimePanel.SetActive(true);

                        growTimeText.text = "남은일수\n" + (seedItems[farmingData[cellPosition].seedIdx].growDay - farmingData[cellPosition].curDay);
                    }
                }
            }
        }

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


    private void FarmingSystemMobile()
    {
        // 맨 처음 터치만 이용할 것
        Touch touch = Input.GetTouch(0);
        if (IsPointerOverUIObjectMobile(touch)) return; // 터치한 곳에 UI 있으면 그냥 빠져나오도록..


        // 맨 처음 터치 시작시
        if (touch.phase == TouchPhase.Began)
        {
            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(touch.position);
            // 게임 월드 위치를 타일 맵의 타일 셀 위치로 변환
            cellPosition = farmTilemap.WorldToCell(clickPosition);
            Debug.Log(cellPosition);


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

                            growTimeText.text = "남은일수\n" + (seedItems[farmingData[cellPosition].seedIdx].growDay - farmingData[cellPosition].curDay);
                        }
                    }
                }
            }

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

    private void GrowTimeUpdate()
    {
        // 이 함수는 씬이 전환될 때마다 호출되도록 하면 될 것 같다..

        // 농사 땅 위에 씨앗이 있으면
        if (farmEnableZoneTilemap.HasTile(cellPosition) && farmingData[cellPosition].seedOnTile)
        {
            if (!farmingData[cellPosition].isGrown)
                growTimeText.text = growTimeText.text = "남은일수\n" + (seedItems[farmingData[cellPosition].seedIdx].growDay - farmingData[cellPosition].curDay);
            else
                growTimePanel.SetActive(false); // 다 자라면 남은시간 나타내는 판넬 꺼지도록..
        }
    }

    private void CheckGrowedFruit()
    {
        foreach (Vector3Int pos in farmingData.Keys)
        {
            // 씨앗이 농사 땅 위에 있으면
            if (farmingData[pos].seedOnTile)
            {
                // 만약 다 자랐으면
                if (farmingData[pos].isGrown)
                {
                    farmTilemap.SetTile(pos, harvestTile); // 타일을 과일이 다 자란 상태로 변경
                    farmingData[pos].harvestEnableState = true; // 작물 수확할 수 있는 상태
                    farmingData[pos].stateButton.gameObject.SetActive(true); // 수확하기 버튼은 항상 떠있어야 함
                    farmingData[pos].currentState = "harvest";
                }
            }
        }
    }

    private bool IsPointerOverUIObjectPC()
    {
        // 컴퓨터용

        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current)
        {
            // 핸드폰 터치도 mousePosition 으로 이용할 수 있으므로 간단한 건 그냥 이것처럼 mousePosition 쓸 예정..
            position = new Vector2(Input.mousePosition.x, Input.mousePosition.y)
        };
        List<RaycastResult> results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventDataCurrentPosition, results);
        return results.Count > 0;
    }

    private bool IsPointerOverUIObjectMobile(Touch touch)
    {
        // 핸드폰용

        PointerEventData eventDataCurrentPosition = new PointerEventData(EventSystem.current)
        {
            position = new Vector2(touch.position.x, touch.position.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 버튼으로 변경..


        // 저장하기
        SaveFarmingData();
    }

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

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

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

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

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

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


        // 저장하기
        SaveFarmingData();
    }

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

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


        // 이건 이제 이 함수에서 관리 안 할 것...
        //fruitContainer.fruitCount[farmingData[pos].seed.seedData.seedIdx]++; // 씨앗의 인덱스와 같은 과일의 수 증가시키기


        // 수확하려는 과일의 개수만큼 InventoryItem 구조체의 인스턴스를 만들기..
        InventoryItem tempItem = new InventoryItem()
        {
            item = fruitItems[farmingData[pos].seedIdx],
            quantity = 1,
        };
        inventoryController.AddItem(tempItem); // 새로 생성한 인벤토리 아이템을 인벤토리 데이터에 추가해주기..


        farmingData[pos].stateButton.gameObject.SetActive(false); // 버튼 한 번 눌렀으니까 꺼지도록..
        farmingData[pos].stateButton = farmingData[pos].buttons[0]; // plow 버튼을 가지고 있도록..
        farmingData[pos].seedOnTile = false; // 수확했으니까 seedOnTile 변수의 값을 다시 false 로 설정해주기..
        farmingData[pos].seedIdx = -1; // 씨앗 인덱스 다시 -1로 바꿔주기..
        farmingData[pos].curDay = 0; // 씨앗 심은 후 지난 일자 다시 0으로 바꿔주기..
        farmingData[pos].isGrown = false; // 이제 땅에 아무것도 없으므로 isGrown 값도 false 로 바꿔주기..


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


        // 저장하기
        SaveFarmingData();
    }


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

        // 구매하려는 씨앗의 개수만큼 InventoryItem 구조체의 인스턴스를 만들기..
        InventoryItem tempItem = new InventoryItem()
        {
            item = seedItems[idx],
            quantity = count,
        };
        inventoryController.AddItem(tempItem); // 새로 생성한 인벤토리 아이템을 인벤토리 데이터에 추가해주기..


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

    public void SellFruit(int count, int idx)
    {
        // 만약 판매하려고 하는 과일의 개수가 현재 과일의 개수보다 적으면 그냥 빠져나가도록..
        if (fruitContainer.fruitCount[idx] < count)
        {
            Debug.Log("과일이 부족해!!!");
            return;
        }

        // 판매하려는 과일의 개수만큼 InventoryItem 구조체의 인스턴스를 만들기..
        InventoryItem tempItem = new InventoryItem()
        {
            item = fruitItems[idx],
            quantity = count,
        };

        inventoryController.MinusItem(tempItem); // 새로 생성한 인벤토리 아이템을 인벤토리 데이터에서 빼주기..

        // 이건 이제 이 함수에서 관리 안 할 것..
        //fruitContainer.fruitCount[idx] -= count; // 판매할 과일의 수만큼 과일 컨테이너에서 빼주기


        GameManager.instance.money += fruitItems[idx].fruitPrice * count; // 가진 돈에 더하기!

        PlayerPrefs.SetInt("money", GameManager.instance.money); // 현재 돈 저장
    }

    public void SetFarmingData(Vector3Int pos)
    {
        // 이 함수는 FarmingManager 클래스의 Start 함수와 UpgradeFarmSize 함수에서 사용할 것..

        // 딕셔너리에 이미 현재 등록하려는 타일이 존재하면 걍 빠져나가도록..
        if (farmingData.ContainsKey(pos)) return;


        // 아니면 딕셔너리에 등록
        // 유니티에서는 new 를 쓰려면 class 가 MonoBehaviour 를 상속 받으면 안 됨.
        farmingData[pos] = new FarmingData();
        farmingData[pos].SetData();
        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];
    }


    public void SetFarmSize()
    {
        // Awake 함수에서 호출할 함수
        // 불러온 농장 레벨에 따라 호출 횟수가 달라짐..

        // 땅의 크기를 업그레이드 하는 함수

        BoundsInt bounds = farmEnableZoneTilemap.cellBounds; // 농사 가능 구역 타일맵의 현재 크기 가져오기

        // 새로 확장할 영역 좌표 계산 로직..
        Debug.Log(bounds.xMin);

        int minX = bounds.xMin - expansionSize;
        int maxX = bounds.xMax + expansionSize;
        int minY = bounds.yMin - expansionSize;
        int maxY = bounds.yMax + expansionSize;

        for (int i = minX; i < maxX; i++)
        {
            for (int j = minY; j < maxY; j++)
            {
                // 테투리 부분만 경계타일 까는 로직
                // max 값은 1 이 더 더해져있기 때문에 이를 고려해서 조건식 짜야함.
                // 그래서 maxX, maxY 일 때는 i, j 에 1 을 더해줌..
                if (i == minX || i + 1 == maxX)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), grassTile);
                if (j == minY || j + 1 == maxY)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), grassTile);

                Vector3Int pos = new Vector3Int(i, j, 0);

                // 농사 가능 구역 타일맵에 타일이 없으면 진입
                if (!farmEnableZoneTilemap.HasTile(pos))
                {
                    farmEnableZoneTilemap.SetTile(pos, grassTile);
                }
            }
        }


        // 경계 타일맵 깔기 위한 로직
        bounds = farmEnableZoneTilemap.cellBounds; // 업데이트된 농사 가능 구역 타일맵의 현재 크기 가져오기
        minX = bounds.xMin - 1;
        maxX = bounds.xMax + 1;
        minY = bounds.yMin - 1;
        maxY = bounds.yMax + 1;

        Debug.Log("maxX: " + maxX + " maxY: " + maxY);
        Debug.Log("minX: " + minX + " minY: " + minY);

        for (int i = minX; i < maxX; i++)
        {
            for (int j = minY; j < maxY; j++)
            {
                // 테투리 부분만 경계타일 까는 로직
                // max 값은 1 이 더 더해져있기 때문에 이를 고려해서 조건식 짜야함.
                // 그래서 maxX, maxY 일 때는 i, j 에 1 을 더해줌..
                if (i == minX || i + 1 == maxX)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), borderTile);
                if (j == minY || j + 1 == maxY)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), borderTile);
            }
        }


        // 농사 가능 구역 타일맵의 타일들을 모두 돌면서..
        foreach (Vector3Int pos in farmEnableZoneTilemap.cellBounds.allPositionsWithin)
        {
            SetFarmingData(pos); // 새로운 농사 가능 구역의 타일 정보를 딕셔너리에 저장..
        }
    }

    public void UpgradeFarmSize()
    {
        // 일단 임시로 만원으로 해놨다..
        if (GameManager.instance.money < 10000)
        {
            Debug.Log("돈 없어!");
            return;
        }

        // 땅의 크기를 업그레이드 하는 함수

        BoundsInt bounds = farmEnableZoneTilemap.cellBounds; // 농사 가능 구역 타일맵의 현재 크기 가져오기

        // 새로 확장할 영역 좌표 계산 로직..
        Debug.Log(bounds.xMin);

        int minX = bounds.xMin - expansionSize;
        int maxX = bounds.xMax + expansionSize;
        int minY = bounds.yMin - expansionSize;
        int maxY = bounds.yMax + expansionSize;

        for (int i = minX; i < maxX; i++)
        {
            for (int j = minY; j < maxY; j++)
            {
                // 테투리 부분만 경계타일 까는 로직
                // max 값은 1 이 더 더해져있기 때문에 이를 고려해서 조건식 짜야함.
                // 그래서 maxX, maxY 일 때는 i, j 에 1 을 더해줌..
                if (i == minX || i + 1 == maxX)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), grassTile);
                if (j == minY || j + 1 == maxY)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), grassTile);

                Vector3Int pos = new Vector3Int(i, j, 0);

                // 농사 가능 구역 타일맵에 타일이 없으면 진입
                if (!farmEnableZoneTilemap.HasTile(pos))
                {
                    farmEnableZoneTilemap.SetTile(pos, grassTile);
                }
            }
        }


        // 경계 타일맵 깔기 위한 로직
        bounds = farmEnableZoneTilemap.cellBounds; // 업데이트된 농사 가능 구역 타일맵의 현재 크기 가져오기
        minX = bounds.xMin - 1;
        maxX = bounds.xMax + 1;
        minY = bounds.yMin - 1;
        maxY = bounds.yMax + 1;

        Debug.Log("maxX: " + maxX + " maxY: " + maxY);
        Debug.Log("minX: " + minX + " minY: " + minY);

        for (int i = minX; i < maxX; i++)
        {
            for (int j = minY; j < maxY; j++)
            {
                // 테투리 부분만 경계타일 까는 로직
                // max 값은 1 이 더 더해져있기 때문에 이를 고려해서 조건식 짜야함.
                // 그래서 maxX, maxY 일 때는 i, j 에 1 을 더해줌..
                if (i == minX || i + 1 == maxX)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), borderTile);
                if (j == minY || j + 1 == maxY)
                    farmTilemap.SetTile(new Vector3Int(i, j, 0), borderTile);
            }
        }


        // 농사 가능 구역 타일맵의 타일들을 모두 돌면서..
        foreach (Vector3Int pos in farmEnableZoneTilemap.cellBounds.allPositionsWithin)
        {
            SetFarmingData(pos); // 새로운 농사 가능 구역의 타일 정보를 딕셔너리에 저장..
        }


        farmLevel++; // 농장 레벨 증가
        PlayerPrefs.SetInt("FarmLevel", farmLevel); // 농장 레벨 저장..
        Debug.Log("농장을 업그레이드 했다!");
    }


}

 

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


[CreateAssetMenu(fileName = "Seed Data", menuName = "Scriptable Object/Seed Data", order = int.MaxValue)]
public class SeedItemSO : ItemSO
{
    // ItemSO 의 공통적인 속성을 상속받기..
    // 이 클래스는 Seed 클래스에서 seedData 라는 이름의 변수로 이용될 것..

    [SerializeField]
    public int seedPrice; // 씨앗 구매 가격

    [SerializeField]
    public int seedIdx; // 씨앗 인덱스


    // 이제 growTime 안 쓰고 growDay 쓸 것..
    [SerializeField]
    public int growDay; // 다 자라는데 걸리는 일수
}

 

 


 

[주요 기능 설명]

  • SeedItemSO 클래스를 보면 growTime 대신 growDay 가 생겼다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;


[CreateAssetMenu(fileName = "Seed Data", menuName = "Scriptable Object/Seed Data", order = int.MaxValue)]
public class SeedItemSO : ItemSO
{
    // ItemSO 의 공통적인 속성을 상속받기..
    // 이 클래스는 Seed 클래스에서 seedData 라는 이름의 변수로 이용될 것..

    [SerializeField]
    public int seedPrice; // 씨앗 구매 가격

    [SerializeField]
    public int seedIdx; // 씨앗 인덱스


    // 이제 growTime 안 쓰고 growDay 쓸 것..
    [SerializeField]
    public int growDay; // 다 자라는데 걸리는 일수
}

 

  • FarmingManager 클래스 속 FarmingData 클래스의 내용을 보면 seed 변수가 사라지고 seedIdx, seedOnTile, isGrown, curDay 변수가 생겼다(이제 seed 클래스는 안 쓸거라 숙청대상 근데 아직 씨앗 구매, 심기 창에서 쓰고 있어서 차차 없애야 할 것 같다..)
// 타일이 가지는 농사 데이터
[Serializable]
class FarmingData
{
    [SerializeField]
    public bool plowEnableState; // 밭을 갈 수 있는 상태인지 여부 확인용(밭이 안 갈린 상태)
    [SerializeField]
    public bool plantEnableState; // 씨앗을 심을 수 있는 상태인지 여부 확인용
    [SerializeField]
    public bool harvestEnableState; // 작물이 다 자란 상태인지 여부 확인용


    /*
     Button과 같은 Unity 엔진 내장 컴포넌트들은 게임 오브젝트나 컴포넌트로 참조되어 있기 때문에 JSON 직렬화에서 제대로 다룰 수 없다고함..
     Unity의 직렬화 시스템도 이러한 Unity 엔진 내장 객체들을 처리하는 방식과 JSON 직렬화는 다르기 때문에, 이런 컴포넌트들을 JSON으로 저장할 수는 없다고 함..
     => 일반적인 경우는 UI 요소들은 직렬화에서 제외한다고 함..
     */
    [NonSerialized] public Button stateButton; // 타일을 누르면 타일 위에 뜨도록 하는 버튼
    [NonSerialized] public Button[] buttons; // [0]: plow 버튼, [1]: plant 버튼, [2]: harvest 버튼

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



    [SerializeField]
    public int seedIdx = -1; // 그러면 이제 씨앗 인덱스도 저장해야 할듯(아무것도 안 심어져 있는 상태는 -1을 값으로 가지도록)..
    [SerializeField]
    public bool seedOnTile; // 그럼 이제 그냥 Seed 클래스 사용하지 말고 bool 값으로 씨앗이 있는지 여부 판단하는 게 좋을 듯..
    [SerializeField]
    public bool isGrown; // 다 자랐는지 여부도 이제 얘가 저장하도록 하는게..
    // 이제 시간이 아니라 일수로 관리하도록 수정할 것..
    // 근데 이러면 딱히 시간 안 더해줘도 되니까 씨앗도 그냥 ScriptableObject 클래스로만 관리해야 좋을 것 같은데
    // 원래는 시간 더해줘야 하니까 Monobehaviour 클래스를 상속받아야 했던 거고..
    [SerializeField]
    // 씨앗 마다 자라기까지 걸리는 일수가 있어서 다르게 자라도록?_?
    public int curDay; // 씨앗 심고 지난 일수(씬 전환 될 때마다 1씩 증가하는 식으로 하면 될 것 같다)


    public void SetData()
    {
        plowEnableState = true;
    }
}

 

  • 원래는 자라기까지 남은 시간을 보여주었는데, 이제는 남은 일수를 보여주도록 수정했다.
private void GrowTimeUpdate()
{
    // 이 함수는 씬이 전환될 때마다 호출되도록 하면 될 것 같다..

    // 농사 땅 위에 씨앗이 있으면
    if (farmEnableZoneTilemap.HasTile(cellPosition) && farmingData[cellPosition].seedOnTile)
    {
        if (!farmingData[cellPosition].isGrown)
            growTimeText.text = growTimeText.text = "남은일수\n" + (seedItems[farmingData[cellPosition].seedIdx].growDay - farmingData[cellPosition].curDay);
        else
            growTimePanel.SetActive(false); // 다 자라면 남은시간 나타내는 판넬 꺼지도록..
    }
}
'유니티 프로젝트/케이크게임' 카테고리의 다른 글
  • [개발일지] 11. 중간 정리
  • [개발일지] 10. scriptableobject 를 상속받는 클래스의 장점
  • [개발일지] 9. 농장 크기 업그레이드
  • [개발일지] 8. 씨앗 선택창 심기 버튼 연동 & 씨앗 구매하기 로직 추가
dubu0721
dubu0721
dubu0721 님의 블로그 입니다.
  • dubu0721
    dubu0721 님의 블로그
    dubu0721
  • 전체
    오늘
    어제
    • 분류 전체보기 (352)
      • 프로그래밍언어론 정리 (5)
      • 컴퓨터네트워크 정리 (5)
      • 알고리즘&자료구조 공부 (64)
        • it 취업을 위한 알고리즘 문제풀이 입문 강의 (60)
        • 학교 알고리즘 수업 (3)
        • 실전프로젝트I (0)
      • 백준 문제 (204)
        • 이분탐색 (7)
        • 투포인트 (10)
        • 그래프 (11)
        • 그리디 (24)
        • DP (25)
        • BFS (21)
        • MST (7)
        • KMP (4)
        • Dijkstra (3)
        • Disjoints Set (4)
        • Bellman-Ford (2)
        • 시뮬레이션 (3)
        • 백트래킹 (15)
        • 위상정렬 (5)
        • 자료구조 (25)
        • 기하학 (1)
        • 정렬 (11)
        • 구현 (8)
        • 재귀 (8)
        • 수학 (8)
        • 트리 (1)
      • 유니티 공부 (11)
        • 레트로의 유니티 게임 프로그래밍 에센스 (11)
        • 유니티 스터디 자료 (0)
        • C# 공부 (0)
      • 유니티 프로젝트 (48)
        • 케이크게임 (13)
        • 점토게임 (35)
      • 언리얼 공부 (10)
        • 이득우의 언리얼 프로그래밍 (10)
      • 진로 (1)
      • 논문 읽기 (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    그래프
    레트로의 유니티 프로그래밍
    dp
    우선순위큐
    유니티
    바킹독
    BFS
    골드메탈
    재귀
    시뮬레이션
    유니티 프로젝트
    이분탐색
    이벤트 트리거
    이득우
    오블완
    해시
    언리얼
    스택
    백준
    티스토리챌린지
    정렬
    투포인터
    C#
    큐
    자료구조
    수학
    맵
    유니티 공부 정리
    그리디
    백트래킹
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dubu0721
[개발일지] 12. 농작물 자라는 로직 변경
상단으로

티스토리툴바