[SKKU DT] 28일차 -유니티 네트워크(System.IO, JSON, Xml, CSV), API, 에셋 번들

2023. 12. 7. 21:06SKKU DT

728x90
반응형

유니티 네트워크

  • 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를 하면 새로운 데이터가 저장된다.

728x90
반응형