![[1인개발 프로젝트] 하늘소 프로젝트 4주차](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsJ7EG%2FbtsED8Z6D7s%2FImGQxHORmKtEKcu1sMnXBk%2Fimg.png)
3주차에 셀 필드 파밍 기능을 위해 알고리즘 공부를 하고, 이를 적용시키니 뭔가 큰 것을 한 것 같았다. 그래서 조금 쉬어버렸다. (이번엔 나피리...ㅎ)
이번에도 어떻게든 부작용을 기획 작업으로 희석하고 다시 프로젝트로 들어갔다.
위 사진은 동아리에서 쿡앱스라는 기업으로 기업 방문 체험을 갔던 장면이다. 기업 체험이 끝나고 취업에 성공하신 멋진 선배님들도 만나고 오느라 12시간이 훅 지나가버렸다. (개발을 안 했다는 뜻)
아무튼, 이번 주차는 클래스 구조를 짜고 그걸 기반으로 DB를 구현했다. 또, 셀 필드 파밍 기능에 쓰일 타일을 제작해보며 전체적인 개발 설계도를 구상했다. 그런데 이제 기획도 곁들인. 그리고 삽질 어게인
데이터베이스 구현
유니티 프로젝트에서 데이터를 저장하는 데에는 크게 세가지 방법이 있다.
- PlayerPrefs로 저장하는 방법
- Json파일로 기기에 저장하는 방법
- Scriptable Object로 저장하는 방법
PlayerPrefs로 저장하는 방법은 세 가지 방법 중 가장 간단하고 쉬운 방법이지만, 보안이 취약하다는 큰 단점이 있다. 그래서 재화 정보나 유저 정보를 담고 있기에는 적절하지 않은 방법이다.
Scriptable Object로 저장하는 방법은 오브젝트의 형태를 저장하는 방법인데, 이 역시 대량의 정보 저장에는 적합하지 않다고 생각했다. 그래서 Json파일로 기기에 저장하는 방법을 선택했다.
Json 이란?
Json(Java Script Object Notation)은, 일반적으로 서버에서 클라이언트로 데이터를 보낼 때 사용하는 양식이다. 데이터를 저장하는 데 매우 유용하다.
{
"items": [
{
"name": "사과",
"description": "정말 단 사과입니다!"
},
{
"name": "바나나",
"description": "정말 길쭉한 바나나입니다!"
}
]
}
이런 문법을 가지고 있으며, 기본적으로 중괄호 안에 데이터를 작성한다. 중괄호로 구분되어있는 것들이 모두 하나의 객체이며, 이 파일을 읽어내 객체의 정보를 저장하고 불러오는 것이다.
그렇다면 이것을 유니티에서는 어떻게 사용할까?
유니티에서 Json 사용하기
유니티에서 Json을 사용하는 데에는 여러가지 방법이 있지만, 나는 JsonUtility라는 것을 사용하기로 했다.
Newtonsoft.Json이나 LitJson같은 외부 라이브러리도 있기는 한데, 외부 플러그인을 설치해야하고 네임스페이스를 사용 선언 해줘야하는 번거로움이 있어서 공부만 하고 선택은 하지 않았다.
JsonUtility 알아보기
JsonUtility는 유니티가 기본 제공하는 내장 API로, 간단하고 쓰기 쉽다.가장 많이 쓰는 문법이 클래스를 Json으로 바꾸기 / Json을 클래스로 바꾸기 정도이다.
- 클래스를 Json으로 바꾸기
string jsonStr = JsonUtility.ToJson(클래스, true);
File.WriteAllText(파일 경로, jsonStr);
클래스를 Json으로 바꾸는 예제 코드이다. 우선 클래스 객체를 하나 생성한 후, JsonUtility.ToJson을 통해 이를 Json 문법을 가진 문자열로 변환해준다. 그 후, File.WriteAllText으로 이 문자열을 지정된 파일 경로에 Json파일로 저장한다.
이 때, 파일 경로에는 디렉토리만 있는 것이 아닌 파일 명과 확장자까지 있어야 한다. 예를 들면, ~~~/~~~/info.json 이런 식이다.
이걸 실행하면 지정된 파일 경로에 있는 Json 파일이 저장된 정보로 덮어씌워지며, 만약 파일이 없다면 새로 생성된다.
JsonUtility.ToJson의 두 번째 인자로 있는 true는 Json 문자열을 예쁘게 만들어주는 것이며, 이것을 적용하지 않으면 위에서 봤던 Json파일이 한 줄로 생겨버린다. 저장하고 불러오는데에는 상관이 없지만, 개발하는 동안 이 파일을 보면서 작업해야할 것들이 좀 있을 것 같아 적용해줬다.
- Json을 클래스로 바꾸기
if (!File.Exists(파일 경로))
{
Debug.Log("Load Failed");
}
else
{
string jsonStr = File.ReadAllText(파일 경로);
클래스로 선언한 변수 = JsonUtility.FromJson<클래스>(jsonStr);
}
Json을 클래스로 바꾸는 예제 코드이다. 일단 파일 경로에 파일이 있는지 확인한 후, 없다면 예외처리를 해주고, 있다면 변환 과정을 실행한다. File.ReadAllText를 통해 Json문자열을 파일에서 읽어오고, 이 문자열을 JsonUtility.FromJson<클래스>로 클래스로 선언한 변수에 담아준다. 이 때, 클래스로 선언한 변수의 클래스와 변환하는 클래스가 같아야 하며, 변환할 클래스가 변환하기 전에 먼저 선언되어있어야 한다.
이러면 Json에 담겨있던 정보가 클래스로 선언한 변수로 들어가 유니티 안에서 클래스 형태로 접근이 가능해진다.
클래스 구성하기
프로젝트에서 사용할 큰 객체들은 유저, 아이템, 퀘스트 등이 있다.
[Serializable]
public class User
{
public string userName;
public int birthDay;
public int workDays;
public string currentCity;
public Money money;
[Serializable]
public class Money
{
public int ironIngot;
public int gear;
public int blueIce;
public int star;
public int shell;
public int dotori;
public int cloud;
public int coal;
public int ticket;
public Money()
{
ironIngot = 500;
gear = 0;
blueIce = 0;
star = 0;
shell = 0;
dotori = 0;
cloud = 0;
ticket = 0;
}
}
public Inventory inventory;
[Serializable]
public class Inventory
{
public Item[] myItems;
public int[] itemAmounts;
public Furniture[] myFurnitures;
public int[] furnitureAmounts;
public Inventory()
{
myItems = new Item[0];
itemAmounts = new int[0];
myFurnitures = new Furniture[0];
furnitureAmounts = new int[0];
}
}
public Quests myQuest;
public int clearedQuest;
}
위 코드는 유저 클래스이다. 이름, 생일, 현재 도시와 자신만의 돈, 인벤토리 클래스를 가지고 있다. 이 때, 클래스 상단에 [Serializable]을 붙여주어야 JsonUtility로 Json 변환이 가능해진다. 유저 클래스 처럼 나머지 클래스들도 구성해주었다. 특히, 퀘스트와 업적 같은 것들은 각자 타입 (어떤 퀘스트는 일퀘, 어떤 퀘스트는 주간퀘 이기도 하고, 어떤 업적은 단계별 업적같은 변수로 구분하기에는 미묘한 차이들이 있다.) 이 다르기 때문에 타입별로 클래스를 생성하고, 이를 관리하는 마스터 클래스를 만들어 이를 Json으로 저장하는 방식을 채택했다.
그 후, 저장될 파일 경로를 설정해주었다. 사실, 여기서 삽질을 가장 많이 했다.
유니티에서 파일 경로를 설정하는 방법이 세 가지 있다.
- Application.dataPath : 읽기 전용 경로로, 런타임중에 파일을 수정하거나 작성할 수 없다.
- Application.persistentDataPath : 읽기/쓰기가 모두 가능한 경로이다.
- Application.streamingAssetsPath : 읽기 전용 경로로, 서버에서 다운받는 데이터를 아직 서버가 마련되지 않아 파일 형식으로 유니티에 포함시킬 때 사용한다.
그래서 나는 읽기 쓰기가 모두 필요하기에 Application.persistentDataPath를 사용했다.
저장되는 경로는 C:/Users/사용자 이름/AppData/LocalLow/회사 이름/프로젝트 이름 의 경로에 저장된다고 한다. 이걸 찾는데 정말 많은 시간이 걸렸다. 블로그에서 말하는 경로가 다 달라서 하나하나 들어가보는 삽질 끝에 얻은 경로이다.
그리고, 회사 이름과 프로젝트 이름을 따로 설정한 것이 아니라면 DefaultCompany 이런 식으로 되어있을텐데, 이것은 Project Settings > Player로 가면 바로 바꿀 수 있다.
최종적으로, 경로 변수를 string으로 선언하고 Awake 함수에서 경로를 지정해주었다.
void Awake() //set file pathes.
{
userFilePath = Application.persistentDataPath + "/user.json";
NPCFilePath = Application.persistentDataPath + "/npc.json";
itemsFilePath = Application.persistentDataPath + "/items.json";
furnituresFilePath = Application.persistentDataPath + "/furnitures.json";
questsFilePath = Application.persistentDataPath + "/quests.json";
achievementsFilePath = Application.persistentDataPath + "/achievements.json";
}
Awake에서 지정한 이유는, Application.persistentDataPath를 변수 선언과 동시에 초기화해주니 오류가 나서, 우선 선언만 하고 데이터베이스 스크립트가 불러질 때 실행되도록 Awake 안에 넣어주고 다시 실행하니 정상 작동하여 이렇게 쓴 것이다.
DB 구현
처음에는 Json파일을 프로젝트 경로 안에 미리 생성한 후 이를 불러오고, 다시 지정 경로(persistentDataPath) 안에 저장하는 방법으로 하려고 했는데, 에디터에서는 잘 되었지만 빌드 시에는 이 Json파일이 읽히지 않아 잘 되지 않았다.
그래서, Init 함수를 만들어 여기서 직접 객체들의 인스턴스를 지정해주어 초기에 이 함수를 불러와 저장하는 방식을 사용하기로 했다. 결과만 먼저 말하자면, 대성공이었다. 모두 잘 변환되었고, Json파일도 잘 생성되고 불러와졌다. 물론 에디터와 모바일 빌드시에도 이상없이 작동했다.
public void SaveData(string type)
{
switch (type)
{
case "user":
SaveUserData(); break;
case "npc":
SaveNPCData(); break;
case "items":
SaveItemsData(); break;
case "furnitures":
SaveFurnituresData(); break;
case "quests":
SaveQuestsData(); break;
case "achivements":
SaveAchivementsData(); break;
case "all":
SaveUserData();
SaveItemsData();
SaveFurnituresData();
SaveQuestsData();
SaveAchivementsData();
break;
}
Debug.Log(type + " Saved.");
text.text = "Success!";
}
저장 함수는 다른 스크립트에서 쓰기 쉽도록 한 함수 안에 경우를 나눠 여러가지 함수에 접근 가능하도록 했다. 불러오기 함수도 같은 방식으로 작성했다.
이제 자유롭게 객체를 건드려서 저장 / 불러오기를 할 수 있게 되었다!
Isometric 타일 제작
저번 학기에 했던 프로젝트에서 Aseprite를 이용해 Isometric 타일을 제작했었는데, 이번 프로젝트는 도트 그래픽이 아니기 때문에 Isometric 타일을 직접 그려야 한다. 그래서 일단 임시로 전에 봤던 레퍼런스를 참고해 타일을 만들었다.
이게 레퍼런스이고, 아래는 내가 만든 타일이다.
땅 타일 (윗부분) | 땅 타일 (아랫부분) | 나무 타일 1 | 나무 타일 2 | 나무 타일 3 | 투명 타일 |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
원래 땅 타일이 합쳐져있었는데, Isometric 타일을 다루면서 다른 타일끼리 만났을 때 뒤에 있는 타일이 앞에 오는 버그가 있던 것 같아 그것을 해결하기 위해 따로 제작했다.
그리고 나무 타일과 땅 타일의 윗부분을 같이 두었을 때 나무 타일의 끝자락이 땅 타일의 앞으로 와 부자연스러움을 연출하길래 나무 타일을 뒤로 보냈다. 그 후 전에 보냈던 길 찾기 알고리즘에 벽인지 판단하는 로직을 타일맵 두개로 판단하게 했더니 로직 문제인지 잘 되지 않았다.
그래서 투명 타일을 만들어 땅 타일이 있는 타일맵에서 나무 타일 위치에 투명 타일을 깔아 원래 길찾기 로직대로 하게끔 시도했고, 잘 되었다.
그런데 Isometric 타일을 직접 그림으로 제작하니 생각보다 생각할 것도 많고 시간도 조금 걸렸다. 더군다나 기차 소품과 인테리어용 가구도 Isometric으로 작업해야하니 좀 막막하다. 그래서 배경만 직접 그리고 Isometric 요소들은 도트로 찍는 방법을 고려해볼 예정이다.
이번 주차는 DB 구현에 모든 것을 투자한 한 주였다. Json을 보기만 했지 다뤘던 것은 이번이 처음이었고, 그동안 데이터 저장 소리만 들으면 손사래를 치던 나에게는 별게 아닌 것 같아도 좀 큰 도전이었다. 그리고, 무릎이 돌아왔다! 스트레칭을 꾸준히 해주니 좋아졌다.
이제 슬슬 다른 팀 프로젝트가 시작되고있으니, 그것과 병행하며 기획, 아트, 작곡, 개발을 틈틈이 할 예정이다. 이제 다음으로 해야 할 것은 NPC 생성과 상호작용이다. 아트를 먼저 하는 것이 원래 계획이었는데, 아트를 하더라도 개발이 진행이 안되면 기껏 그린 것이 쓸모가 없어지기도 하고, 개발이 먼저 되면 그림을 어떤 것을 그릴 지, 어떻게 그릴 지 방향이 대충 잡히기 때문에 개발을 우선으로 두기로 했다.
그리고, 오늘 골드메탈님의 생방송을 봤는데 생각보다 얻어가는 것이 많아서 이것도 가끔씩 보면 좋을 것 같다.
프로젝트를 시작한지 이제 딱 한 달차가 되었다. 아예 기획조차 백지에서 시작했던 것은 아니지만, 개발은 백지부터 시작했기에 여태까지 한 결과물을 보니 뿌듯하기도 하고 이대로 가면 어떤 결과물이 나올지 기대되기도 하고 걱정되기도 한다.
힘들든 어쨌든 재밌으니 된거 아닐까?

'1인 개발 > 하늘소 프로젝트' 카테고리의 다른 글
[1인개발 프로젝트] 하늘소 프로젝트 6주차 (0) | 2024.02.25 |
---|---|
[1인개발 프로젝트] 하늘소 프로젝트 5주차 (1) | 2024.02.15 |
[1인개발 프로젝트] 하늘소 프로젝트 3주차 (2) | 2024.02.02 |
[1인개발 프로젝트] 하늘소 프로젝트 2주차 (1) | 2024.01.19 |
[1인개발 프로젝트] 하늘소 프로젝트 1주차 (4) | 2024.01.12 |
안녕하세요! 코드 짜는 농부입니다! 경희대학교 소프트웨어융합학과 23학번 재학중입니다. 문의 : dsblue_jun@khu.ac.kr
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!