오늘은 배운 내용이 아주 많다.
- Lighting 심화
- Character Controller
- TimeLine
- CSV & JSON
- 스크립터블 오브젝트
- NavMesh
따라서 TIL 작성 시에 몇 가지 패스 예정.
Lighting 심화: 내가 당장 활용하기 어려울 정도의 내용이니 따로 정리해뒀고
Character Controller: 지금까지 배운 기능으로 구현이 가능하다.
TimeLine: 스크립팅보단 시각적인 기능이 많기에 글로 적기에는 부적합.
세부 기능이 기억나지 않으면 강의 내용을 통해 복습하겠다.
따라서 오늘 TIL은
Unity에서의 여러 데이터 저장 방식을 알아보고
각 기능의 특징과 활용, 마무리로 차이점을 알아본 뒤 NavMesh를 알아보겠다.
NavMesh는 팀 프로젝트 간 활용하느라 메서드를 어느 정도 알고 있으니
기능 설명 위주로 작성해보겠다.
시작하기 전에 자랑할 것이 있어 적는다.
드디어 코드 블록에 색을 입히는 법을 알아냈다.

백틱 뒤에 사용할 언어를 적어주면 된다!
지금까진 코드블록의 색이 통일되어 있어
가독성을 위해 코드를 IDE 스크린샷으로 첨부했으나
이젠 좀 더 깔끔하게 작성할 수 있을 듯.
지금까지 제작한 Unity 기반 프로젝트는 모두 단판 형식의 게임이었으며
또한 모든 유닛의 정보 초기값을 코드 혹은 인스펙터에서 저장하였다.
이번에 배워둔 데이터 저장 방식의 종류를 기억해두고
추후 진행할 개인 프로젝트에 활용할 기능을 미리 생각해두자.
오늘 알아볼 데이터 저장 방식은 총 4가지
PlayerPrefs
CVS
JSON
ScriptableObject
간단한 설정 값이나 작은 데이터를 저장하기 위해 Unity에서 제공하는 저장소
Int, Float, String 값을 레지스트리에 저장.
따라서 값 저장 시
PlayerPrefs.SetInt(key, value)
PlayerPrefs.GetInt(key)
이런 방식이지만 Value값으로 복잡한 데이터를 저장하는 것이 불가능하여
좌표 저장 시에 SetFloat을 3번 써야 된다던가 하는 불편함이 존재함
PlayerPrefs.SetFloat("PlayerX", pos.x);
PlayerPrefs.SetFloat("PlayerY", pos.y);
PlayerPrefs.SetFloat("PlayerZ", pos.z);
복잡한 데이터값을 요구하지 않는 부분에서 활용 가능.
인게임 볼륨, 언어 설정 등
다만 요즘 온라인 게임에선 개인 설정을 로컬이 아닌 원격으로 저장하는 모습이 종종 보인다.
점점 활용처가 줄어든다는 느낌을 받는 방식.
using UnityEngine;
using UnityEngine.InputSystem;
public class SaveTest : MonoBehaviour
{
public float playerHealth = 100;
void Start()
{
playerHealth = PlayerPrefs.GetFloat("Health");
Debug.Log(playerHealth);
}
void Update()
{
if (Keyboard.current.pKey.IsPressed())
{
playerHealth = 50;
PlayerPrefs.SetFloat("Health", playerHealth);
}
}
}
콤마로 구분한다는 뜻에서 CSV (Comma, Separated, Value)
행렬로 이루어진 테이블 형태의 데이터 관리에 용이한 방식.
쉼표 등을 이용해 데이터를 분리하여 텍스트 파일에 저장
표 형태의 데이터: 아이템 목록, 레벨 디자인 변수 등
대량의 정적 데이터 관리에 용이.
별도의 내장 메서드 등이 존재하지 않음.
따라서 파싱의 과정을 정리해 둔 예시 코드를 첨부해두겠다.
한번 따라 만들어보긴 했는데 필요할 때 다시 보자.
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public class CSVParser
{
public void Load(CSV Target)
{
// 파일 경로 지정
string filePath = Application.dataPath + "/Database/CSV/" + Target.Path + ".csv";
// 지정된 경로에 파일이 존재하는 경우에만 읽어야 한다.
if(File.Exists(filePath))
{
// 경로의 파일을 텍스트로 읽어들인다.
string csvFile = File.ReadAllText(filePath);
// 개행문자 단위로 줄을 나눈 뒤, 배열에 담는다.
string[] lines = csvFile.Split('\n');
// 모든 줄을 순회하면서
for(int l = 1; l < lines.Length; l++)
{
// 한 줄 안에서 쉼표(,)를 기준으로 각 데이터를 나누고 배열에 담는다
string[] fields = lines[l].Split(',');
// 리스트에 한 줄을 추가하고, 각 셀의 데이터가 들어갈 항목 단위의 리스트를 생성한다.
Target.Data.Add(new List<string>());
// 각 항목 단위로 순회하면서
for(int f = 1; f < fields.Length; f++)
{
// 항목(셀) 단위의 리스트에 데이터를 추가한다.
Target.Data[l - 1].Add(fields[f]);
}
}
}
}
}
public class CSV
{
// 파일 경로
public string Path { get; set; }
// CSV 파일의 데이터를 2차원 배열의 형태로 보관
public List<List<string>> Data { get; set; } = new List<List<string>>();
public CSV(string path)
{
Path = path;
}
public List<string> GetLine(int Number, ref bool isSuccessLoad)
{
// 입력받은 넘버가 CSV데이터의 줄 수보다 많다면
if(Number > Data.Count - 1)
{
return null;
}
// 아니라면
isSuccessLoad = true;
return Data[Number]; // 2차원 배열의 형태이므로, 한 줄을 통째로 반환한다.
}
}
JSON (JavaScript Object Notation)
계층적인 C# 객체 데이터를 저장하고 교환하기 위한 텍스트 기반의 데이터 형식
using UnityEngine;
using System.IO; //입출력을 가능케하는 도구의 집합.
//JSON변환을 위해선 직렬화 필요.
[System.Serializable]
public class PlayerData
{
public int hp;
public Vector3 position;
}
해당 직렬화된 클래스를 JSON으로 변환하고
변환된 JSON을 다시 객체에 전달하는 방식을 정리한 예시 코드.
private void Start()
{
//1. 유의미한 데이터 생성 및 기입
PlayerData pd = new PlayerData();
pd.hp = 100;
pd.position = playerToSave.transform.position;
//2. JSON으로 변환
string json = JsonUtility.ToJson(pd);
//3. JSON을 다시 객체로 변환
PlayerData loadedData = JsonUtility.FromJson<PlayerData>(json);
//새로 만든 필드에 JSON 문자열을 플레이어 데이터 형식으로 바꿔서 전달
}
또한 JSON을 이용해 게임 데이터를 저장하고 불러오는 예시 코드.
얘도 필요 시 다시 복습해보자.
using UnityEngine;
using System.IO; //입출력을 가능케하는 도구의 집합.
public class SaveTest : MonoBehaviour
{
public GameObject playerToSave;
private string filePath;//파일경로
private void Start()
{
//저장될 파일 경로 설정
filePath = Path.Combine(Application.persistentDataPath, "PlayerData.json");
//persistentDataPath => 데이터가 수시로 입출하는, 종료되어도 유지되는 공간
Debug.Log("파일경로:" + filePath);
}
void SaveData()
{
PlayerData pd = new PlayerData();
pd.hp = 100;
pd.position = playerToSave.transform.position;
string json = JsonUtility.ToJson(pd, true); //뒤의 true는 보기 좋게 포맷
File.WriteAllText(filePath, json); //원하는 경로에 해당 string을 파일로 저장
}
void LoadData()
{
if (File.Exists(filePath)) //원하는 파일이 존재하면?
{
string json = File.ReadAllText(filePath); //파일에서 문자열 읽어오기, 아직 통짜
PlayerData player = JsonUtility.FromJson<PlayerData>(json); //이젠 제이슨 클래스의 도움을 받아서 문자열을 클래스 형식에 맞게 파싱
playerToSave.transform.position = player.position;
Debug.Log("load complete");
}
else
{
Debug.LogWarning("파일 없음! 확인필수");
}
}
Unity 엔진의 에셋으로 데이터를 저장
게임 실행 중, 정적 데이터를 저장하도록 설계된 클래스

Scriptable Object의 구현
메서드는 추가할 수 없음
using UnityEngine;
[CreateAssetMenu(fileName = "CubeData", menuName = "Scriptable Objects/CubeData")]
public class CubeData : ScriptableObject
{
public string cubeName;
public Vector3 scale;
}

팀 프로젝트 떄 Enemy 유닛의 AI 추적 로직을 위해 미리 사용해봤었다.
모든 기능이 각 컴포넌트에 구현되어 있고, 당시에 메서드도 몇 가지 사용해봤으니
사용하지 않았거나, 알아두면 좋을 설정법 위주로 알아보자.
AI가 이동이 가능한 위치를 정의함
슬로프는 이동 가능한 경사를 나타내므로 활용처가 꽤 있어 보임

캐릭터가 NavMesh 위를 따라 이동할 수 있도록 해주는 핵심 컴포넌트
컴포넌트 기능 소개는 아래와 같으니
필요에 따라 찾아보자.


걸어다닐 바닥을 생성하는 데 사용되는 컴포넌트
씬에 배치된 지형이나 오브젝트를 분석하여
AI가 이동 가능한 영역을 나타내는 NavMesh를 Bake
몇 가지 기능이 추가로 존재하지만 저번 NavMesh 사용 시에 알아봤으므로 패스.

AI 캐릭터의 이동 경로를 방해하는 동적인 장애물을 정의
이걸 진작에 알았더라면
팀 프로젝트 간 좀 더 재밌는 로직을 많이 만들었을 것 같다.
얘는 중요한 기능만 정리해두고 넘어가자.


NavMeshAgent 유닛이 일반적인 방법으로는 이동할 수 없는
끊어진 두 지점을 연결하여 특수 이동(점프, 텔레포트) 경로를 제공
이런 기능도 있었는 지 알았다면...
역시 주요 기능을 알아두고 넘어가자.

특정 영역에 NavMesh 생성 규칙을 변경할 때 사용하는 컴포넌트
특정 구역을 AI 이동 불가 구역으로 설정하는 등 활용법이 다양하다.
또한 특정 영억의 cost를 높게 설정하면
AI가 해당 구역을 피하도록 구현 가능.
늪지대, 화산지대 등의 이동불가/이동속도 제한 맵 구현
4가지의 데이터 저장 방식을 알아봤는데
JSON 과 ScriptableObject 둘은 각자의 장점이 존재하여
프로젝트가 커져도 병행하여 사용할 듯 하다.
당장 소형 프로젝트를 진행할 때 나머지 두 방식도 사용해보면 좋을 듯.
NavMesh는 기능적으로만 알아두도록 하자.
내장 메서드의 정보가 필요할 경우, 저번 팀프로젝트 할 때처럼 검색해보자.