[개발일지] 7. 현재까지 구현한 기능 정리

2024. 7. 17. 15:27·유니티 프로젝트/케이크게임

전에 공부용으로 만들었던 코드들을 본격적으로 다시 만들었다. 그래서 현재까지 개발한 사항을 여기에 새로 작성하려고 한다. 전에 코드랑 달라진 부분이 많아서 이제 이 코드를 기준으로 앞으로의 게시물을 작성할 것이다. [주요 기능 설명] 파트는 [현재 프로젝트 클래스 종류] 파트 밑부분에 있다.


 

[구현한 기능]

  • 농사 가능 구역만 농사 가능하도록 하는 기능
  • 밭 갈기, 씨앗 심기, 과일 수확하기
  • 각 밭(타일) 마다 밭 갈기, 씨앗 심기, 과일 수확하기 버튼 동적 생성
  • 버튼 누를 때 뒤에 있는 타일을 안 눌리도록 하는 기능
  • 씨앗이 심어진 밭(타일) 클릭하면 다 자라기까지 남은 시간 뜨도록 하는 기능
  • 씨앗 구매창(이제 게임 매니저랑 연결해서 로직 더 작성해야함.)

 

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

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

Seed: 씨앗 클래스

Fruit: 과일 클래스

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

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

BuySeedUIManager: 씨앗 구매창 UI 를 관리하는 클래스

SlotManager: 씨앗 구매창의 슬롯을 관리하는 클래스

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 Canvas buttonParent; // 버튼 생성할 때 부모 지정하기 위한 변수

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

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


    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));
                } 
                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);
                        }
                    }

                    // 현재 선택한 타일의 버튼은 활성화 되도록..
                    //farmingData[cellPosition].stateButton.gameObject.SetActive(true);
                    // 아래 방법처럼 하면 오류남. 이유는 뭐지??
                    // --> GameObject 는 컴포넌트가 아니라서 오류가 나는 것이었다... 이번 기회에 알게 됐다.
                    // --> 그냥 gameObject 로 바로 게임 오브젝트에 접근할 수 있었다.
                    // --> 기본적인걸 까먹고 있었다.. 이번엔 잘 기억하자..
                    //farmingData[cellPosition].stateButton.GetComponent<GameObject>().SetActive(true);
                }
            }

            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";
                }
            }
        }
    }

    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 PlantTile(Vector3Int pos)
    {
        // 씨앗을 심는 함수
        // 다음에 이 함수의 매개변수로 씨앗 인덱스로 보내줘서 GetSeed 함수의 매개변수로 보낼 것.
        farmingData[pos].seed = seedContainer.GetSeed(0).GetComponent<Seed>();

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

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

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

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

        farmingData[pos].plowEnableState = true;
        farmingData[pos].currentState = "None"; // 과일을 수확한 상태니까 None 으로 바꿔주기
        
        fruitContainer.GetFruit(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); // 타일 모습을 초기 상태의로 바꿔주기
    }
}

 

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

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

    public string seedName;
    public float 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;

    private void Awake()
    {
        pools = new List<GameObject>[prefabs.Length];

        for (int idx = 0; idx < pools.Length; idx++)
        {
            pools[idx] = new List<GameObject>();
        }
    }

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

        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;

    private void Awake()
    {
        fruitCount = 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;

        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);
        }

        fruitCount++;

        return select;
    }
}

 

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

public class BuySeedUIManager : 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 slotContainer;
    public List<Button> BuySeedSlots;

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

    private void Awake()
    {
        // 현재 씨앗 구매 판넬에 존재하는 슬롯들을 가져와서 저장함.
        // 자식만 가져와야 하기 때문에 (자손은 가져오면 안 됨) GetComponentsInChildren 못 씀.
        for (int i=0; i< slotContainer.transform.childCount; i++)
        {
            Transform child = slotContainer.transform.GetChild(i);
            BuySeedSlots.Add(child.GetComponent<Button>());
        }
        
        // 현재 게임 상 존재하는 씨앗 구매 버튼 정보 설정
        for (int i=0; i<BuySeedSlots.Count; i++)
        {
            SlotManager slot = BuySeedSlots[i].GetComponent<SlotManager>();
            Seed slotSeedInfo = seedInfo.prefabs[i].GetComponent<Seed>();

            // 각 버튼의 초기값 설정
            slot.seedImage.sprite = seedImages[i];
            slot.seedName.text = slotSeedInfo.seedName;
            slot.totalPrice.text = "가격: " + slotSeedInfo.seedPrice;
            slot.seedCountText.text = "1";
        }
    }

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


        for (int i=0; i<BuySeedSlots.Count; i++)
        {
            SlotManager slot = BuySeedSlots[i].GetComponent<SlotManager>();
            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);
            }
        }
    }

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

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

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

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

 

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

public class SlotManager : MonoBehaviour
{
    public Image seedImage;
    public Text seedName;
    public GameObject BuySlot;
    public Text totalPrice;
    public Text seedCountText;
    public Button leftButton;
    public Button rightButton;

    public int seedCount = 1;
    public int maxCount = 64;
    public int minCount = 1;

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

        seedCount--;
    }

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

        seedCount++;
    }

    private void Update()
    {
        // UI 가 아닌 부분을 클릭하면 그냥 꺼지도록..
        if (IsPointerOverUIObject()) return; 
        else
        {
            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 ResetData()
    {
        seedCount = 1;
    }
}

 

  • FarmingData(FarmingManager 클래스에 있음)
// 타일이 가지는 농사 데이터
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. 농사 가능 구역만 농사 할 수 있도록 하는 기능

  • 게임 하이어라키 창에 농사 가능 구역을 나타내는 타일맵과 진짜 농사가 이루어지는 타일맵 두 개를 만들어 놓았다
  • cellBounds.allPositionWithin 은 farmEnableZoneTilemap 타일맵의 모든 셀들의 위치를 가져오는 역할을 한다
  • 농사 가능 구역 타일맵의 모든 셀의 위치를 반복문을 통해 돌면서, 해당 위치에 타일이 있으면 farmingData 딕셔너리 변수에 FarmingData 클래스의 인스턴스를 만들어서 저장했다(Key: pos, Value: FarmingData).
  • 마우스 왼쪽키를 눌렀을 때, 누른 타일이 농사 가능 구열에 있는 타일이면 농사 관련 로직을 수행할 수 있도록, 딕셔너리에 저장되어 있는 모든 위치를 돌면서 현재 마우스 위치의 타일 위치랑 같은지 판단했다
[Header("Tilemap")]
public Tilemap farmEnableZoneTilemap; // 농사 가능 부지를 나타내는 타일맵
public Tilemap farmTilemap; // 진짜로 현재 타일의 상태에 따라 타일이 변경되는 타일맵
private void Start()
{
    // 농사 가능 구역만 farmingData 에 저장할 것임.
    foreach (Vector3Int pos in farmEnableZoneTilemap.cellBounds.allPositionsWithin)
    {
        if (!farmEnableZoneTilemap.HasTile(pos)) continue;

        // 유니티에서는 new 를 쓰려면 class 가 MonoBehaviour 를 상속 받으면 안 됨.
        farmingData[pos] = new FarmingData();
    }
}
void Update()
{
    // 땅을 왼쪽 마우스키로 누르면..
    if (Input.GetMouseButtonDown(0))
    {
        // 현재 마우스 위치를 게임 월드 위치로 바꿔서 저장
        clickPosition = mainCamera.ScreenToWorldPoint(Input.mousePosition);
        // 게임 월드 위치를 타일 맵의 타일 셀 위치로 변환
        cellPosition = farmTilemap.WorldToCell(clickPosition);
        

        foreach (Vector3Int pos in farmingData.Keys)
        {
        	if (pos == cellPosition) {
				// 농사 관련 로직..
            }
        }
    }

 

 

2. FarmingManager 클래스에서 각 타일마다 세 개의 버튼을 가지도록 하는 코드

    ---> 참고자료: Unity UI Button onClick.AddListener 활용하기 (tistory.com)

  • FarmingData 클래스는 멤버 변수 중 하나로 buttons 라는 배열을 가지는데, 이는 각 타일이 버튼을 3개(plow, plant, harvest) 가져야 하므로 선언한 변수이다.
  • buttonPrefabs(이 배열에는 미리 세 개의 버튼 프리팹을 넣어놨다) 의 길이만큼 for 문을 돌면서 버튼을 동적으로 생성해준다(클로저 문제를 피하기 위해 값을 변수에 저장해놓고 사용하는 식으로 했다).
  • 동적으로 생성된 버튼에 함수를 연결해주었다(동적으로 생성된 버튼에 함수를 연결해주려면 AddListener 라는 함수가 필요하다).
  • AddListener 에 버튼과 연결하려는 함수를 매개변수로 넘겨주어야 하는데 밭 갈기, 씨앗 심기, 수확하기 함수는 인자를 필요로 하는 함수이므로 람다식을 이용해서 넘겨주었다.
  • 버튼을 다 생성한 다음에, 각 타일이 가지는 현재 버튼(stateButton) 변수에 plow 버튼을 넣어주었다.
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));
                } 
                else if (index==2)
                {
                    farmingData[tilePos].buttons[index].onClick.AddListener(() => HarvestTile(tilePos));
                }
            }

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

            prevSelectTile = pos;
        }
    }
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;
}

 

 

3. 버튼 누를 때 뒤에 있는 타일은 안 눌리도록 하는 기능

[개발일지] 5. 버튼 눌렀을 때 뒤에 있는 타일(게임 오브젝트)은 안 눌리도록 하는 방법 (tistory.com)

 

 

4. 밭 갈기, 씨앗 심기, 과일 수확하기 기능

  • 밭 갈기, 씨앗 심기, 과일 수확하기 함수를 만들었다.
  • 세 함수 모두 Vector3Int 타입의 pos 변수를 매개변수로 받는다(farmingData 딕셔너리 변수의 키가 타일맵의 셀 위치 이므로, 타일맵 셀의 FarmingData 정보를 이용하기 위해서)
  • 밭을 갈고, 씨앗을 심고, 과일을 수확하면 타일의 모습이 변해야한다. 즉 타일의 모습을 변경하기 위해 SetTile 함수를 이용했다. SetTile 함수는 Vector3Int 타입의 변수와 TileBase 타입의 변수를 매개변수로 받는다.
  • TileBase 타입의 변수를 미리 선언해놓고(grassTile, farmTile, plantTile, harvestTile), 그 변수에 각각의 모습에 맞는 타일 프리팹을 넣어놨다. 
  • Update 함수에서 각 조건에 맞게 버튼을 띄우고 없애는 등의 전반적인 기능을 적었다(자세한 내용은 밑 코드 참조).
[Header("Tile")]
public TileBase grassTile; // 밭 갈기 전 상태
public TileBase farmTile; // 밭 간 후 상태
public TileBase plantTile; // 씨앗 심은 후 상태
public TileBase harvestTile; // 과일 다 자란 상태
public Vector3Int prevSelectTile; // 이전 클릭된 타일
// 타일이 가지는 농사 데이터
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 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 PlantTile(Vector3Int pos)
{
    // 씨앗을 심는 함수
    // 다음에 이 함수의 매개변수로 씨앗 인덱스로 보내줘서 GetSeed 함수의 매개변수로 보낼 것.
    farmingData[pos].seed = seedContainer.GetSeed(0).GetComponent<Seed>();

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

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

    farmingData[pos].stateButton = farmingData[pos].buttons[2]; // harvest 버튼을 가지고 있도록..
}
public void HarvestTile(Vector3Int pos)
{
    // 과일을 수확하는 함수
    // 생각해보니까 씨앗 인덱스 여기로 안 보내줘도 pos 보내줬으니까, pos 가 가지는 씨앗 인스턴스의 씨앗 인덱스 이용하면 될 듯.

    farmingData[pos].plowEnableState = true;
    farmingData[pos].currentState = "None"; // 과일을 수확한 상태니까 None 으로 바꿔주기
    
    fruitContainer.GetFruit(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); // 타일 모습을 초기 상태의로 바꿔주기
}
    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);
                        }
                    }
                }
            }

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


        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";
                }
            }
        }
    }

 

 

5. 씨앗이 심어진 밭(타일) 클릭하면 다 자라기까지 남은 시간 뜨도록 하는 기능

  • Update 문 안에있던 foreach 구문의 내용을 다음과 같이 수정했다.
  • else if (!farmingData[cellPosition].seed.isGrown) 로 진입하면 씨앗이 자라는 중임을 의미한다.
  • 미리 만들어놓았던 판넬의 위치를 현재 클릭한 타일의 위치로 바꿔주고, 판넬을 활성화 시켜서 화면에 나타나도록 했다.
  • 자라는데 남은 시간은 한 번 초기화 됐다고 끝이 아니라 계속해서 값을 업데이트 해줘야 한다.
  • 즉 foreach 구문 바로 밑에 두 번째 코드 블록을 추가했다.
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);
            }
        }
    }
}
// 자라는데 남은 시간이 계속 업데이트 되어야 하므로..
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); // 다 자라면 남은시간 나타내는 판넬 꺼지도록..
}

 

 

6. 씨앗 구매창(이제 게임 매니저랑 연결해서 로직 더 작성해야함.)

    [슬라이드 구매창 만드는데 참고한 영상]

    ---> 참고자료: 인벤토리 첫번째 영상! UI! (youtube.com)

  • 씨앗 구매창을 만들기 위해 필요한 클래스는 SlotManager 와, BuySeedUIManager 이다.
  • 슬롯 매니저를 만든 이유는 각 슬롯(씨앗 버튼) 의 정보를 코드상에서 조작할 수 있도록 하기 위함이다.
  • 각 슬롯(씨앗 버튼) 의 정보를 하이어라키 창에서 수정하도록 하면 슬롯의 개수가 늘어났을 때(한 100개 정도..) 를 상상해보면 너무 비효율적이기 때문에.. 코드상에서 조작할 수 있도록 하고 싶었다.
  • BuySeedUIManager 는 씨앗 구매창 UI 를 관리하는 역할이다.
  • BuySeedUIManager 에 각 슬롯(씨앗 버튼) 을 BuySeedSlots 배열에 저장해서 코드상에서 조작할 수 있도록 했다. 
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class SlotManager : MonoBehaviour
{
    public Image seedImage;
    public Text seedName;
    public GameObject BuySlot;
    public Text totalPrice;
    public Text seedCountText;
    public Button leftButton;
    public Button rightButton;

    public int seedCount = 1;
    public int maxCount = 64;
    public int minCount = 1;

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

        seedCount--;
    }

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

        seedCount++;
    }

    private void Update()
    {
        // UI 가 아닌 부분을 클릭하면 그냥 꺼지도록..
        if (IsPointerOverUIObject()) return; 
        else
        {
            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 ResetData()
    {
        seedCount = 1;
    }
}
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;

public class BuySeedUIManager : 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 slotContainer;
    public List<Button> BuySeedSlots;

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

    private void Awake()
    {
        // 현재 씨앗 구매 판넬에 존재하는 슬롯들을 가져와서 저장함.
        // 자식만 가져와야 하기 때문에 (자손은 가져오면 안 됨) GetComponentsInChildren 못 씀.
        for (int i=0; i< slotContainer.transform.childCount; i++)
        {
            Transform child = slotContainer.transform.GetChild(i);
            BuySeedSlots.Add(child.GetComponent<Button>());
        }
        
        // 현재 게임 상 존재하는 씨앗 구매 버튼 정보 설정
        for (int i=0; i<BuySeedSlots.Count; i++)
        {
            SlotManager slot = BuySeedSlots[i].GetComponent<SlotManager>();
            Seed slotSeedInfo = seedInfo.prefabs[i].GetComponent<Seed>();

            // 각 버튼의 초기값 설정
            slot.seedImage.sprite = seedImages[i];
            slot.seedName.text = slotSeedInfo.seedName;
            slot.totalPrice.text = "가격: " + slotSeedInfo.seedPrice;
            slot.seedCountText.text = "1";
        }
    }

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


        for (int i=0; i<BuySeedSlots.Count; i++)
        {
            SlotManager slot = BuySeedSlots[i].GetComponent<SlotManager>();
            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);
            }
        }
    }

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

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

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

        CloseBuySlot(); // 나가기 버튼 누르면 켜져있던 구매 슬롯 없어지도록..
    }
}
'유니티 프로젝트/케이크게임' 카테고리의 다른 글
  • [개발일지] 9. 농장 크기 업그레이드
  • [개발일지] 8. 씨앗 선택창 심기 버튼 연동 & 씨앗 구매하기 로직 추가
  • [개발일지] 6. 주석 깨짐 현상 해결하기
  • [개발일지] 5. 버튼 눌렀을 때 뒤에 있는 타일(게임 오브젝트)은 안 눌리도록 하는 방법
dubu0721
dubu0721
dubu0721 님의 블로그 입니다.
  • dubu0721
    dubu0721 님의 블로그
    dubu0721
  • 전체
    오늘
    어제
    • 분류 전체보기 (355) N
      • 프로그래밍언어론 정리 (5)
      • 컴퓨터네트워크 정리 (5)
      • 알고리즘&자료구조 공부 (64)
        • it 취업을 위한 알고리즘 문제풀이 입문 강의 (60)
        • 학교 알고리즘 수업 (3)
        • 실전프로젝트I (0)
      • 백준 문제 (207) N
        • 이분탐색 (7)
        • 투포인트 (10)
        • 그래프 (11)
        • 그리디 (24)
        • DP (25)
        • BFS (22) N
        • MST (7)
        • KMP (4)
        • Dijkstra (3)
        • Disjoints Set (4)
        • Bellman-Ford (2)
        • 시뮬레이션 (3)
        • 백트래킹 (15)
        • 위상정렬 (6) N
        • 자료구조 (25)
        • 기하학 (1)
        • 정렬 (11)
        • 구현 (8)
        • 재귀 (8)
        • 수학 (8)
        • 트리 (1)
      • 유니티 공부 (11)
        • 레트로의 유니티 게임 프로그래밍 에센스 (11)
        • 유니티 스터디 자료 (0)
        • C# 공부 (0)
      • 유니티 프로젝트 (48)
        • 케이크게임 (13)
        • 점토게임 (35)
      • 언리얼 공부 (10)
        • 이득우의 언리얼 프로그래밍 (10)
      • 진로 (1)
      • 논문 읽기 (2)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
dubu0721
[개발일지] 7. 현재까지 구현한 기능 정리
상단으로

티스토리툴바