[SKKU DT] 28일차 -유니티 네트워크(System.IO, JSON, Xml, CSV), API, 에셋 번들
유니티 네트워크
- System.IO 입출력 namespace 사용하기
- JSON 파일 사용하기
- XML 파일 사용하기
- CSV 파일 사용하기
- 다른 형식의 파일 읽기
- ScriptableObject 파일 읽기
- REST API -사이트 접근
- 에셋 관리 시스템
System.IO 네임스페이스 -텍스트 파일
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public class Datapath : MonoBehaviour
{
string path = "c:/tmp/example.txt";
//string filePath = Application.dataPath + "/MyFolder/MyFile.txt"; //상대 경로
void Start()
{
string readText = File.ReadAllText(path);
Debug.Log(readText);
File.WriteAllText(path, "Hello.unity!");
}
}
파일 경로에 txt 파일이 있어야 한다. 스크립트를 유니티 안에서 실행하면 파일에 "Hello.unity!"가 적혀있는 것을 볼 수 있다.
상대경로 사용
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public class Datapath : MonoBehaviour
{
//string path = "c:/tmp/example.txt";
string filePath = Application.dataPath + "/MyFolder/MyFile.txt"; //상대 경로
void Start()
{
string readText = File.ReadAllText(filePath);
Debug.Log(readText);
File.WriteAllText(filePath, "Hello.unity!");
}
}
상대 경로를 사용하면 내가 작업하고 있는 프로젝트의 "Assets/MyFolder/MyFile.txt"에 텍스트 파일을 만들어 놓으면 텍스트 파일에 글이 작성된다. 만약 "/MyFolder/MyFile.txt" 경로 자체가 없다면 "MyFolder" 폴더가 생성되기는 한다.
텍스트 내용을 바꾸면 텍스트 전체가 바뀐다.
System.IO 네임스페이스 -이미지 파일 불러오기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Networking;
public class LoadImageFromFile : MonoBehaviour
{
public RawImage displayImage;
void Start()
{
StartCoroutine(LoadImage("c:/tmp/low_city.png"));
}
IEnumerator LoadImage(string filePath)
{
UnityWebRequest request = UnityWebRequestTexture.GetTexture(filePath);
yield return request.SendWebRequest(); //언제까지 기다릴 것인가 -답을 받았으면 return
if(request.result == UnityWebRequest.Result.ConnectionError || request.result == UnityWebRequest.Result.ProtocolError)
{
Debug.LogError("Error: " + request.error);
}
else
{
Texture2D texture = DownloadHandlerTexture.GetContent(request);
displayImage.texture = texture;
}
}
}
위 스크립트를 GameObject에 넣고 해당 GameObject의 컴포넌트 중 비어있는 Display Image란에 [UI] - [RawImage]를 만들어서 끌어다 놓으면 유니티를 실행하면 이미지가 경로에 있는 이미지로 바뀌게 된다.
프로젝트 실행을 중지하면 이미지도 원래대로 돌아간다.
JSON 파일에 접근하기
객체형과 배열형이 있다.
메모장을 열어서 다음과 같이 쓸 수 있다. 콜론 앞은 자료형의 느낌, 콜론 뒤는 이름.
txt 유형을 json으로 바꾼 후 스크립트에서 명시해 놓은 경로에 저장한다.
{ "name": "john", "age": 30, "gender": "male", "height": 180}
스크립트를 작성해서 GameObject에 넣고 실행하면 Console창에 표시가 된다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
public class JsonLoader : MonoBehaviour
{
//JSON 데이터에 해당하는 C# 클래스, 파싱 부분. 많은 데이터 중에서 필요한 데이터만 가져와야 한다.
[System.Serializable]
public class Person
{
public string name;
public int age;
public string gender;
public int height;
}
void Start()
{
//JSON 파일의 경로
string path = "c:/tmp/object.json";
//JSON 파일을 읽고 문자열로 변환
string jsonString = File.ReadAllText(path);
//JSON 문자열을 Person 객체로 변환
Person person = JsonUtility.FromJson<Person>(jsonString);
//데이터 출력 (예시)
Debug.Log("Name: " + person.name);
Debug.Log("Age: " + person.age);
Debug.Log("Gender: " + person.gender);
Debug.Log("Height: " + person.height);
}
}
JSON 파일 읽기 -Array
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class JsonArrayLoader : MonoBehaviour
{
//JSON 배열을 위한 레퍼 클래스
[System.Serializable]
public class StringArray
{
public string[] array;
}
void Start()
{
string path = "c:/tmp/array.json";
string jsonString = File.ReadAllText(path);
//JSON 문자열은 StringArray 객체로 변환
//JSONUtility는 배열을 직접 파싱하지 않으므로 레퍼 클래스를 사용한다.
StringArray fruits = JsonUtility.FromJson<StringArray>("{\"array\":" + jsonString + "}");
//데이터 출력 (예시)
foreach (string fruit in fruits.array)
{
Debug.Log(fruit);
}
}
JSON에는 대괄호를 이용해서 배열을 입력해 놓으면 스크립트가 실행될 때 Console창에 배열이 나타난다.
["apple", "banana", "cherry", "orange"]
**JSON 검사기
JSON 검사기
JSON 형식의 검증 및 검사 JSON 형식은 널리 웹 개발에 적용된다. JSON 문자열은 항상 네트워크 대역폭 및 문서 크기,하지만 읽기는 너무 열심히하고 디버깅을 저장 빈 공간, 들여 쓰기와 줄 바꿈을
kr.piliapp.com
문법을 틀리면 오류를 찾기 힘들기 때문에 JSON 검사기를 사용할 수도 있다.
XML 파일 읽기
XML을 파싱하기 위해서는 System.Xml을 사용한다.
메모장으로 Xml 파일을 하나 만들고,
<Person>
<Name>John Doe</Name>
<Age>30</Age>
<Gender>Male</Gender>
</Person>
스크립트를 만들어서 GameObject에 넣으면, 결과 값이 출력된다. Person 클래스를 따로 C# 스크립로 만들어도 작동한다.
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
public class XmlLoader : MonoBehaviour
{
[Serializable]
public class Person
{
public string Name;
public int Age;
public string Gender;
}
void Start()
{
//XML 파일의 경로
string path = "c:/tmp/JohnDoe.xml";
//XML 파일을 읽고 문자열로 변환
string xmlString = File.ReadAllText(path);
//XmlSerializer 객체를 생성하고, Person 클래스 타입을 지정
XmlSerializer serializer = new XmlSerializer(typeof(Person));
//문자열을 StringReader 객체로 변환
using (StringReader reader = new StringReader(xmlString))
{
//XML 데이터를 Person 객체로 변환
Person person = (Person)serializer.Deserialize(reader);
//데이터 출력 (예시)
Debug.Log("Name: " + person.Name);
Debug.Log("Age: " + person.Age);
Debug.Log("Gender: " + person.Gender);
}
}
}
CSV 파일 읽기
첫 행은 데이터가 들어가지 않는다. csv 파일도 메모장으로 작성할 수 있다.
메모장에 다음과 같이 입력한 후 csv 파일 형식으로 저장한다.
ID, Name, Age
1, Jone, 30
2, Jane, 25
3, Bob, 22
아래와 같은 코드를 작성한 후 유니티를 보면, CSV 파일을 컴포넌트에 넣을 수 있게 되어있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CSVLoader : MonoBehaviour
{
public TextAsset csvFile; //Unity Editor에서 할당
void Start()
{
ReadCSV(csvFile);
}
void ReadCSV(TextAsset csv)
{
string[] rows = csv.text.Split(new char[] { '\n' }, System.StringSplitOptions.RemoveEmptyEntries);
for (int i = 1; i < rows.Length; i++)
{
string[] rowValues = rows[i].Split(',');
if (rowValues.Length != 3) continue;
int id = int.Parse(rowValues[0]);
string name = rowValues[1];
int age = int.Parse(rowValues[2]);
Debug.Log($"ID: {id}, Name: {name}, Age: {age}");
}
}
}
CSV 파일의 기준 행 다음부터 읽어진다. 즉, for문에서 i = 1 부터 시작된다는 말이다.
다른 파일 불러오기 (YAML, INI, Binary File)
바이너리 스크립트 생성,
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System;
[Serializable]
public class PlayerData
{
public string name;
public int health;
public float[] position;
public PlayerData(string name, int health, float[] poisition)
{
this.name = name;
this.health = health;
this.position = position;
}
}
DisplayPlayerData 스크립트 생성
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
public class DisplayPlayerData : MonoBehaviour
{
public TMP_InputField playerDataText;
private PlayerData playerData;
void Start()
{
//플레이어 데이터 예시 생성
float[] position = { 1.0f, 2.0f, 3.0f };
playerData = new PlayerData("PlayerName", 100, position);
//플레이어 데이터 표시
DisplayData();
}
void DisplayData()
{
if (playerDataText != null && playerData != null)
{
playerDataText.text = $"Name: {playerData.name}\n" + $"Health: {playerData.health}\n" + $"Position: ({ playerData.position[0]}, { playerData.position[1]}, { playerData.position[2]})";
}
}
}
PlayerData 스크립트는 게임 오브젝트에 넣고, 빈 컴포넌트에는 InputField UI를 만들어서 넣는다. 그러면 InputField에 정보가 나온다.
**한글 폰트 적용2
.ttf 파일 형식의 폰트를 다운받은 후, [Window] - [TextMeshPro] - [Font Asset Creator]에서 원하는 폰트를 넣고 설정을 맞춘 후 [Generate Font Atlas]를 누르면 만들어진다.
다른 형식의 파일 읽기 ScriptableObject
상태나 데이터를 저장하는 데에 사용된다. MonoBehaviour와는 달리, ScriptableObject는 게임 오브젝트에 붙지 않고 대신 에셋으로 저장된다. 메모리 효율성, 재사용성, 모듈화의 장점이 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[CreateAssetMenu(fileName = "NewGameData", menuName = "Game Data", order = 51)]
public class GameData : ScriptableObject
{
public int level;
public string playerName;
public float health;
}
위의 스크립트를 프로젝트에 저장하면, 프로젝트에서 생성할 때 [Creat] - [Game Data] 라는 메뉴를 선택할 수 있다.
GameControl이라는 스크립트도 새로 생성한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GameControl : MonoBehaviour
{
public GameData gameData;
void Start()
{
if(gameData != null)
{
Debug.Log("Player Name: " + gameData.playerName);
Debug.Log("Level: " + gameData.level);
Debug.Log("Health: " + gameData.health);
}
}
}
게임 오브젝트에 GameControl 스크립트를 넣고, 빈 컴포넌트에 새로 만들었던 Game Data를 넣으면 Console 창에 내용이 출력된다.
API(Application Programming Interface)
Rest API
요청이 들어올 때만 비동기적으로 응답한다.
응답 형식(Response Formats): 클라이언트에게 데이터를 전송할 때 특정 형식을 사용한다. 일반적으로 JSON, XML.
던전앤파이터 API 이용하기
Neople Developers
## 참고 사항 >- [타임라인 코드](/contents/guide/pages/all#던파-타임라인-코드) 다중 입력 시 콤마(,)를 이용해서 구분 처리 ex) /timeline?code=101,102,103 - startDate, endDate 요청 변수 사용 예시 ex) /timeline?
developers.neople.co.kr
위의 사이트에 들어가서 로그인한다.
API Key를 받는다.
스크립트를 생성한다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;
[System.Serializable]
public class ServerResponse
{
public ServerInfo[] rows;
}
[System.Serializable]
public class ServerInfo
{
public string serverName;
}
public class DnFNetworkTest : MonoBehaviour
{
public TMP_Text[] serverTexts = new TMP_Text[8];
void Start()
{
StartCoroutine(UnityWebRequestGet());
}
IEnumerator UnityWebRequestGet()
{
string url = "https://api.neople.co.kr/df/servers?apikey=본인의 apikey";
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
if(www.error == null)
{
string jsonResponse = www.downloadHandler.text;
ServerResponse serverResponse = JsonUtility.FromJson<ServerResponse>(jsonResponse);
for (int i = 0; i < serverTexts.Length && i < serverResponse.rows.Length; i++)
{
serverTexts[i].text = serverResponse.rows[i].serverName;
}
}
else
{
Debug.Log("Error: " + www.error);
}
}
}
8개의 텍스트를 만든 후 스크립트 컴포넌트와 연결하고 실행하면 던전앤파이터의 8개의 서버 이름이 출력되는 것을 볼 수 있다.
https://api.neople.co.kr/df/servers/<serverId>/characters?characterName=<characterName>&jobId=<jobId>&jobGrowId=<jobGrowId>&isAllJobGrow=<isAllJobGrow>&limit=<limit>&wordType=<wordType>&apikey=본인 API 주소
위의 API 주소에서 <serverId>에는 영문 서버 이름을, <charaterName>에는 인코딩한 캐릭터 이름을 넣고 필요 없는 부분은 지운다.
Debug.Log(jsonResponse); 코드를 추가하면 어떤 정보들이 불러와지는지 볼 수 있다.
기존 스크립트에서 serverName을 characterName으로 바꾸면 유니티에 캐릭터 이름이 불러와진다.
URL 인코딩 - 온라인 URL 인코더
www.convertstring.com
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;
[System.Serializable]
public class ServerResponse
{
public ServerInfo[] rows;
}
[System.Serializable]
public class ServerInfo
{
public string characterName;
}
public class DnFNetworkTest : MonoBehaviour
{
public TMP_Text[] serverTexts = new TMP_Text[8];
void Start()
{
StartCoroutine(UnityWebRequestGet());
}
IEnumerator UnityWebRequestGet()
{
string url = "https://api.neople.co.kr/df/servers/anton/characters?characterName=%ea%b3%a0%ec%9a%b4%eb%a7%90_25696&apikey=본인 API키";
UnityWebRequest www = UnityWebRequest.Get(url);
yield return www.SendWebRequest();
if (www.error == null)
{
string jsonResponse = www.downloadHandler.text;
ServerResponse serverResponse = JsonUtility.FromJson<ServerResponse>(jsonResponse);
Debug.Log(jsonResponse);
for (int i = 0; i < serverTexts.Length && i < serverResponse.rows.Length; i++)
{
serverTexts[i].text = serverResponse.rows[i].characterName;
}
}
else
{
Debug.Log("Error: " + www.error);
}
}
}
날씨 데이터 받아오기
https://openweathermap.org/current
Current weather data - OpenWeatherMap
openweathermap.org
도시 이름을 불러오는 코드는 다음과 같다.
https://api.openweathermap.org/data/2.5/weather?q={CITY_NAME}&appid={api_key}
using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using TMPro;
using System;
public class WeatherFetcher : MonoBehaviour
{
public TMP_Text[] weatherText;
private string apiKey = "본인의 api key";
private string city = "Seoul";
void Start()
{
StartCoroutine(GetWeatherData());
}
IEnumerator GetWeatherData()
{
string url = $"https://api.openweathermap.org/data/2.5/weather?q={city}&appid={apiKey}&units=metric";
using (UnityWebRequest webRequest = UnityWebRequest.Get(url))
{
yield return webRequest.SendWebRequest();
if (webRequest.result != UnityWebRequest.Result.Success)
{
Debug.LogError("Error: " + webRequest.error);
weatherText[0].text = "Error fetching weather data";
}
else
{
OpenWeatherResponse weatherData = JsonUtility.FromJson<OpenWeatherResponse>(webRequest.downloadHandler.text);
DisplayWeatherInfo(weatherData);
}
}
}
void DisplayWeatherInfo(OpenWeatherResponse weatherData)
{
if (weatherData != null)
{
weatherText[0].text = $"City: {weatherData.name}\n";
weatherText[1].text = $"Coordinates: {weatherData.coord.lat}, {weatherData.coord.lon}\n";
weatherText[2].text = $"Temperature: {weatherData.main.temp}°C\n";
weatherText[3].text = $"Feels Like: {weatherData.main.feels_like}°C\n";
weatherText[4].text = $"Min Temp: {weatherData.main.temp_min}°C, Max Temp: {weatherData.main.temp_max}°C\n";
weatherText[5].text = $"Pressure: {weatherData.main.pressure} hPa\n";
weatherText[6].text = $"Humidity: {weatherData.main.humidity}%\n";
weatherText[7].text = $"Visibility: {weatherData.visibility} meters\n" +
$"Wind: {weatherData.wind.speed} m/s, {weatherData.wind.deg} degrees\n" +
$"Cloudiness: {weatherData.clouds.all}%\n" +
$"Sunrise: {UnixTimeStampToDateTime(weatherData.sys.sunrise)}\n" +
$"Sunset: {UnixTimeStampToDateTime(weatherData.sys.sunset)}";
}
}
DateTime UnixTimeStampToDateTime(long unixTimeStamp)
{
// Unix timestamp is seconds past epoch
System.DateTime dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, System.DateTimeKind.Utc);
dtDateTime = dtDateTime.AddSeconds(unixTimeStamp).ToLocalTime();
return dtDateTime;
}
}
[System.Serializable]
public class OpenWeatherResponse
{
public Coordinate coord;
public WeatherInfo[] weather;
public string baseStation;
public MainWeather main;
public int visibility;
public Wind wind;
public Clouds clouds;
public int dt;
public Sys sys;
public int timezone;
public int id;
public string name;
public int cod;
}
[System.Serializable]
public class Coordinate
{
public float lon;
public float lat;
}
[System.Serializable]
public class WeatherInfo
{
public int id;
public string main;
public string description;
public string icon;
}
[System.Serializable]
public class MainWeather
{
public float temp;
public float feels_like;
public float temp_min;
public float temp_max;
public int pressure;
public int humidity;
}
[System.Serializable]
public class Wind
{
public float speed;
public int deg;
}
[System.Serializable]
public class Clouds
{
public int all;
}
[System.Serializable]
public class Sys
{
public int type;
public int id;
public string country;
public long sunrise;
public long sunset;
}
날씨 데이터가 원하는 text 자리에 들어가서 표시되었다.
공공정보 포탈 이용하기
https://www.data.go.kr/index.do
위의 홈페이지에서 공공 API를 사용할 수 있다.
에셋 번들
Resources.Load 함수를 이용하면 에셋을 불러올 수 있다.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class AssetBundle : MonoBehaviour
{
void Start()
{
GameObject obj = Resources.Load<GameObject>("Prefabs/Cube");
Instantiate(obj);
}
}
에셋 번들 이름을 같게 하면 나중에 같은 이름을 가진 에셋들을 따로 압축할 수 있다.
[Package Manager]에서 Addressables를 다운 받으면 Addressable Asset 시스템의 주요 이점을 사용할 수 있다.
- 성능 최적화 -메모리 관리 용이, 필요할 때만 에셋 로드
- 지연 로딩 -객체의 초기화를 그 객체가 실제로 필요할 때까지 지연시킨다. 어플 시작 시간을 단축 시킴. 큰 파일을 다룰 때 사용
- 동적 로딩 -게임이 실행 중일 때 필요한 에셋을 로드/언로드하여 전체 게임 패키지의 크기를 줄이고 메모리 사용을 최적화 한다.
- 컨텐츠 업데이트 -게임을 다시 배포하지 않아도 새로운 컨텐츠 추가나 기존 컨텐츠 업데이트가 가능하다.
- 클라우드 통합 -에셋을 클라우드에 저장하고 게임에서 직접 다운로드하여 사용할 수 있다.
- 패치 시스템 -소프트웨어 패치를 더 작은 사이즈로 분할하여 사용자의 데이터 사용량을 줄이고 다운로드 시간을 줄인다.
Addressables를 다운로드하여 Inspector 창에서 해당 기능을 체크할 수 있게 되었다.
체크하면 그룹으로 나눌 수 있다.
유니티에서 JSON 파일을 이용하여 Save/Load 시스템 만들기
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.UI;
using System.IO;
[System.Serializable]
public class WeaponData
{
public string Id;
public string Name;
public string Information;
}
public class JsonReadWriteSystem : MonoBehaviour
{
public TMP_InputField idInputField;
public TMP_InputField nameInputField;
public TMP_InputField infoInputField;
public void SaveToJason()
{
WeaponData data = new WeaponData();
data.Id = idInputField.text;
data.Name = nameInputField.text;
data.Information = infoInputField.text;
string json = JsonUtility.ToJson(data, true);
File.WriteAllText(Application.dataPath + "/WeaponDataFile.json", json);
}
public void LoadFromJson()
{
string json = File.ReadAllText(Application.dataPath + "/WeaponDataFile.json");
WeaponData data = JsonUtility.FromJson<WeaponData>(json);
idInputField.text = data.Id;
nameInputField.text = data.Name;
infoInputField.text = data.Information;
}
}
위의 스크립트를 생성해서 게임 오브젝트에 넣는다.
InputField 3개를 만들고 프로젝트를 실행한 후 InputField에 문자열 넣고 Save 버튼 누르면 파일이 생성되어 내용이 저장된다. Load 버튼 누르면 InputField에 저장되어 있던 값이 불러와진다.
원래 저장되어 있던 값에 다시 Save를 하면 새로운 데이터가 저장된다.