정체불명의 모모

[유니티C# 스크립팅 마스터하기] - 6챕터/모노를 이용한 개발 본문

프로그래밍(C#) /유니티공부

[유니티C# 스크립팅 마스터하기] - 6챕터/모노를 이용한 개발

정체불명의 모모 2022. 6. 7. 18:15

이번장에는 유니티가 제공하는 모노 프레임 워크 와

여러 플랫폼을 지원하는 마이크로소프트 닷넷의 오픈소스들로

어떻게 개발 하는지 알아 보는 챕터 입니다.

 

 ◻︎  목록

1. 리스트와 컬렉션(List , Dictionary,Stack, IEnumerable , IEnumerator)

2. 문자열과 정규식

3. LINQ와 정규식


리스트와 컬렉션

: 모노 프레임워크에서 데이터 목록을 유지할 수 있도록 여러 클래스들을 제공합니다.

  그 중 세 가지 메인 클래스로 List, Stack , Dictionary 가 있습니다.

 

 ◻︎  List 클래스 / List<T>

: 정렬되지 않은 상태로 하나의 데이터 형식을 가지는 연속적인 항목들의 목록이 필요할 때, 

  이 목록이 저장된 데이터의 크기에 맞게 늘어나거나 줄어드는 것을 원한다면 List 클래스가 적합합니다.

 

• 정리 : 데이터를 추가/ 삭제 하고 저장된 모든 항목들을 순차적으로 순회해야 할 필요가 있을때 적합 합니다.

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class Enemy
{
    public int Health = 100;
    public int Damage = 10;
    public int Defense = 5;
    public int Mana = 20;
    public int ID = 0;
};

public class UsingList : MonoBehaviour
{
    public List<Enemy> Enemies = new List<Enemy>();

    private void Start()
    {
        for (int i = 0; i < 5; i++)
            Enemies.Add(new Enemy());
        
        Enemies.RemoveRange(0,1);

        foreach (var enemy in Enemies)
        {
            Debug.Log(enemy.ID);
        }
    }
}

마소 링크 : https://docs.microsoft.com/ko-kr/dotnet/api/system.collections.generic.list-1?view=net-6.0


✖︎ 주의할 점으로는 반복문에서 순회하며 오브젝트를 지울때 문제가 발생 할 수도 있습니다. ✖︎

 

• 상황 : 씬에서 적과 같은 참조 형식을 가지는 모든 오브젝트들을 지워야 할 때 널(null) 참조를 가지지 않도록

   배열 내의 항목들 모두 지워하는 경우 입니다.

 

• 문제 이유 : 반복문 안에서의 항목 삭제는 반복자(iterator)를 하여금 현재 위치가 어디인지,

   반복문 중 항목의 총개수가 변경되었을 때 어디로 가야 하는지 놓치기 쉽게 만들었기 때문에 문제가 발생 할 수도 있습니다.

 

• 방법 : 배열의 끝에서 처음부터 거꾸로 순회해 삭제 하는 것 입니다.

// 각 오브젝트의 함수를 호출하는 반복문에서 모든 아이템을 삭제하는 함수
void RemoveAllItems()
{
    // 리스트를 거꾸로 순회합니다.
    for(int i = Enemies.Count -1 ; i >= 0 ; i--)
    {
         // 함수 실행
         Enemies[i].MyFunc();
         
         // 리스트에서 이 적 오브젝트를 제거 합니다.
         Enemies.RemoveAt(i);
     }
 }

 ◻︎  Dictionary 클래스. / Dictionary<TKey,TValue>

: Dictionary 는 key , value 에 따른 특정 원소를 검색해 즉시 접근해야 할 때 사용 합니다.

  특히, 데이터베이스에서 특정 값을 찾을때 유용하게 사용 됩니다.

  방대한 데이터를 매우 적은 비용만으로 저장할 수 있고, 키값을 통해 빠른 데이터 검색이 가능 합니다.

 

using System.Collections.Generic;
using UnityEngine;


public class UsingDictionary : MonoBehaviour
{
    public Dictionary<string, int> WordDatabase = new Dictionary<string, int>();

    private void Start()
    {
        string[] Words = new string[5];
        Words[0] = "hello";
        Words[1] = "todays";
        Words[2] = "car";
        Words[3] = "vehicle";
        Words[4] = "computers";

        foreach (var Word in Words)
        {
            WordDatabase.Add(Word, Word.Length);
        }
        
        Debug.Log("Score is: " + WordDatabase["computers"].ToString());
    }
}

마소 링크 : https://docs.microsoft.com/ko-kr/dotnet/api/system.collections.generic.dictionary-2?view=net-6.0


 ◻︎  Stack 클래스 / Stack<T>

: 스택은 후입선출(LIFO) 모델에 기반한 특별한 종류의 리스트 입니다.

  항목을 리스트에 집어넣고(push) 수직의 탑에서 다른 항목 위에 항목을 쌓아 올려 가장 최근에 집어넣은 항목이 

  항목이 항상 최상단에 위치하게 됩니다.

  그런 후, 스택 맨 위의 항목을 하나씩 꺼낼(pop) 수 있습니다.

  꺼내는 항목의 순서는 항상 밀어 넣은 아이템 순서의 역순입니다.

using System.Collections.Generic;
using UnityEngine;

[System.Serializable]
public class PlayingCard
{
    public string Name;
    public int Attack;
    public int Defense;
}

public class UsingStack : MonoBehaviour
{
    public Stack<PlayingCard> CardStack = new Stack<PlayingCard>();

    private void Start()
    {
        PlayingCard[] Cards = new PlayingCard[5];

        for (int i = 0; i < 5; i++)
        {
            Cards[i] = new PlayingCard();
            Cards[i].Name = "Card_0" + i.ToString();
            Cards[i].Attack = Cards[i].Defense = i * 3;
            
            CardStack.Push(Cards[i]);
        }

        while (CardStack.Count > 0)
        {
            PlayingCard PickedCard = CardStack.Pop();
            
            Debug.Log(PickedCard.Name);
        }
    }
}

 ◻︎  IEnumerable 과 IEnumerator

: 보통 List, Dictionary, Stack 등 데이터 컬렉션을 이용한 작업을 할 때, 리스트의 전체 혹은 일부 항목에 대한 일정

  범위에 대해 순회하며 반복 작업하기를 원할 것 입니다.

   그럴 때 IEnumerable 과 IEnumerator 인터페이스가 도움을 줄 수 있습니다.

int Toltal = 0;

for(int i = 0 ; i < MyList.Count; i++)
{
    int MyNumber = MyList[i];
    
    Total += MyNumber;
}

 

하지만, for 반복문을 사용하는 동안 두 가지 문제점이 있습니다.

 

• 첫 번째 : 왼쪽에서 오른쪽으로, 처음부터 끝까지 순회하는 이 문법은 반복문이 진행될 때 각각의 배열 항목에 접근하려면

   정수형 반복자Iterator 변수 i 를 항상 사용해야 하기 때문 입니다.

 

• 두 번째 : 반복자 스스로는 경계를 넘어가지 않는지 보장할 수 없다는 점입니다.

  배열 한계의 위로 증가하거나 아래로 감소하는 것이 실제로 가능하므로 경계를 벗어나는 오류를 유발할 수 있습니다.

  

foreach 반복문

: foreach 반복문은 IEnumerable 인터페이스를 구현하는 클래스만 함께 동작합니다.

  IEnumerable을 구현하는 오브젝트는 반드시 유효한 IEnumerable 인터페이스를 반환해야 합니다.

  실제로 배열 형식이 아닌 오브젝트의 그룹들을 순회할 수 있게 해주는데, 즉 배열이든 아니든 간에 여러 다른 형식의

  오브젝트를 배열처럼 순회할 수 있도록 해준다는 이야기 입니다.

 

⌲ IEnumerator를 이용해 적을 차례로 순회

: 어떤 타입의 모든 객체를 찾는 예제를 보도록 합시다.

using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

// IEnumerator에서 파생한 클래스
// 씬의 모든 마법사를 안전하게 순회하도록 처리 합니다.
public class WizardEnumerator : IEnumerator
{
    // 열거자가 가리키는 현재 마법사 오브젝트의 참조
    private Wizard CurrentObj = null;

    public bool MoveNext()
    {
        // 다음 마법사를 얻는다
        CurrentObj = (CurrentObj == null) ? Wizard.FirstCreated : CurrentObj.NextWizard;

        // 다음 마법사를 반환한다.
        return (CurrentObj != null);
    }

    // 첫 번째 마법사로 반복기를 재설정해 되돌린다.
    public void Reset()
    {
        CurrentObj = null;
    }

    // 현재 마법사를 얻기 위한 C# 프로퍼티
    public object Current
    {
        get { return CurrentObj; }
    }
}

// 마법사 오브젝트를 정의하는 예제 클래스
// IEnumerable 에서 파생되어 foreach로 순회하는 것이 가능하다.
[System.Serializable]
public class Wizard : MonoBehaviour, IEnumerable
{
    public static Wizard LastCreated = null;
    public static Wizard FirstCreated = null;

    public Wizard NextWizard = null;

    public Wizard PrevWizard = null;

    public string WizardName = "";

    void Awake()
    {
        if (FirstCreated == null)
            FirstCreated = this;

        if (Wizard.LastCreated != null)
        {
            Wizard.LastCreated.NextWizard = this;
            PrevWizard = Wizard.LastCreated;
        }

        Wizard.LastCreated = this;
    }

    private void OnDestroy()
    {
        if (PrevWizard != null)
            PrevWizard.NextWizard = NextWizard;

        if (NextWizard != null)
            NextWizard.PrevWizard = PrevWizard;
    }

    public IEnumerator GetEnumerator()
    {
        return new WizardEnumerator();
    }
}

Wizard와 WizardEnumerator 클래스는 빠르고 직접적이며 효율적으로 Wizard 오브젝트를 순회하게 해줍니다.

그리고 실제로 배열에 인스턴스가 존재하지 않아도 문제가 없습니다.

 

모든 마법사를 열거하는 실질적이 예제를 살펴 봅시다.

using UnityEngine;

public class Enums : MonoBehaviour
{
    // Update is called once per frame
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            // 정적 멤버를 통해 첫 번째 마법사를 얻는다.
            Wizard Wizards = Wizard.FirstCreated;
            
            // 하나 이상의 마법사가 있는 경우에, 모든 마법사를 순회 한다
            if (Wizard.FirstCreated != null)
            {
                // foreach를 이용해 모든 마법사를 순회해 반복한다
                foreach (Wizard W in Wizards)
                {
                    Debug.Log(W.WizardName);
                }
            }
        }
    }
}

위의 예제 코드처럼 foreach 반복문을 사용하지 않고 Enumerator 직접 접근해서 모든 마법사를 열거하는 것도 가능합니다.

void Update()
{
   if(Input.GetKeyDown(KeyCode.Space))
   {
      // Enumerator를 얻는다.
      IEnumerator WE = Wizard.FirstCreated.GetEnumerator();
      
      while(WE.MoveNext())
      {
         Debug.Log(((Wizard)WE.Current).WizardName);
      }
    }
}

문자열과 정규식

: 텍스트 데이터를 다루는 것은 여러 가지 이유에서 중요 합니다.

  자막을 표시해야 한다거나 게임 내 문자열을 보여줄 때, 현지화 기능(다중 언어를 지원하는)을 구현 할 때 텍스트 애셋을 이용해 문자열을 

  다루는 작업을 하게 될 것입니다.

  유니티에서 텍스트 애셋이란 유니티 프로젝트 안에 포함된 모든 텍스트 파일을 말하는 것으로서 여러 줄로 된 문자열의 경우에도 각각의

  애셋을 하나의 긴 문자열로 취급합니다.

 

  ⌲ 널, 빈문자열, 여백

   : 문자열을 처리할 때, 늘 유효성을 보장할 수 있는것은 아닙니다.

     따라서 문자열을 처리하기 전에 유효성을 자주 검사해야 합니다.

 

    1. 문자열이 null인지 확인 합니다.

    2. 문자열의 길이가 0인지 확인 합니다.

 

   또한 문자열 전체가 공배으로 구성될 가능성을 없애고 싶을 수 있습니다.

   null이 아닌 문자열이면서 여백으로만 채워진 경우에는 처리할 것이 아무것도 없는 문자열이지만

   실제로는 길이가 0이 아니기 때문 입니다.

   이럴때 'IsNullOrWhiteSpace'라는 메소드를 통해 검증을 할 수 있습니다.

using UnityEngine;

public static class StringExtensions
{
    public static bool IsNullOrWhitespace(this string s)
    {
        return s == null || s.Trim().Length == 0;
    }
}

public class StringOps : MonoBehaviour
{
    // 문자열의 유효성을 검사합니다.
    public bool IsValid(string MyString)
    {
        // null이나 여백이 존재하는지 검사한다.
        if (MyString.IsNullOrWhitespace()) return false;

        return true;
    }
}

  문자열 비교

: 보통 두 문자열이 동일한지를 판별하는 식으로 별개의 두 문자열을 비교해야 할 일이 빈번할 것이다.

  이럴 때 사용하는 메소드는 'String.Equals'을 이용하여 비교할 수 있습니다.

  이 메소드에는 여러 가지 버전이 있는데 모두 다른 연산 비용이 듭니다.

  일반적으로 StringComparison 파라미터를 포함하는 버전을 택합니다.

public bool IsSame(string Str1, string Str2)
{
   return string.Equals(Str1 , Str2 , 
                        System.StringComarison.CurrentCultureIgnoreCase);
}

 

◻︎ StringComparision 열거형

: StringComparison 열거형은 문자열 검색 및 비교하는 메소드에서 검색 규칙을 설정하는 기능입니다.

필드 설명
CurrentCulture 문화권 구분 정렬 규칙 및 현재 문화권을 사용하여 문자열을 비교합니다.
CurrentCultureIgnoreCase 문화권 구분 정렬 규칙 및 현재 문화권을 사용하고 비교되는
문자열의 대/소 문자를 무시하여 문자열을 비교합니다.
InvariantCulture 문화권 구분 정렬 규칙 및 고정 문화권을 사용하여 문자열을 비교합니다.
InvariantCultureIgnoreCase 문화권 구분 정렬 규칙 및 고정 문화권을 사용하고 비교되는
문자열의 대/소문자를 무시하여 문자열을 비교합니다.
Ordinal 서수(이진) 정렬 규칙을 사용하여 문자열을 비교합니다.
OrdinalIgnoreCase 서수(이진) 정렬 규칙을 사용하고 비교되는 문자열의 대/소문자를 무시하여 문자열을 비교합니다.

 

◻︎ 문자열 해시(Hash)를 이용하여 문자열 비교

public bool StringHashCompare(string Str1, string Str2)
{
    int Hash1 = Animator.StringToHash(Str1);
    int Hash2 = Animator.StringToHash(Str2);
    
    return Hash1 == Hash2;
}

 

◻︎ 문자열 우선순위 확인 후 정렬 하는 방법

: string.Compare 함수를 이용하면 예를 들어 두 문자열이 알파벳 순으로 나열되어 있을 때, 

  한 문자열이 다른 문자열 앞에 오게 할 수 있습니다.

  (StringComparsion 형식의 파라미터를 사용 합니다.)

 

// 비교 정렬
public int StringOrder(string Str1 , string Str2)
{
    // 대소문자를 무시합니다.
    return string.Compate(Str1, Str2 , 
              System.StringComparison.CurrentCultureIgnoreCase);
 }
두 문자열이 동일한 경우에 String.Compare 가 0을 반환하긴 하지만, 이 함수를 절대 동일성 검사용으로 사용 하면 안됩니다.
이 경우에 String.Equals 함수나 해시를 이용하는 것이 String.Compare를 이용하는 것보다 훨씬 빠르게 작동합니다.

◻︎ 문자열 서식 지정

: 문자로 된 텍스트만이 아니라 수치 값을 포함한 문자열을 보여주길 원할 것 입니다.

  그럴때 String.Format 메소드를 이용해 주면 됩니다.

public void BuildString(int Num1, int Num2, float Num3)
{
   string Output = string.Format("Number 1 is : {0}, Number 2 is : {1},
          Number 3 is : {2} " , Num1 , Num2 , Num3);
          
   Debug.Log(Output);
 }

 

◻︎ 문자열 순회

: foreach 과 IEnumerator를 이용하여 문자열의 모든 글자를 순회 할 수 있습니다.

// foreach를 이용해 순회
public void LoopLettersForEach(string Str)
{
   foreach(char C in Str)
   {
      // 글자를 콘솔에 출력한다.
      Debug.Log(C);
   }
}

// IEnumerator를 이용해 순회
pulbic void LoopLettersEnumerator(string Str)
{
    // Enumerator를 얻는다.
    IEnumerator StrEnum = Str.GetEnumerator();
    
    // 다음 글자로 이동한다.
    while(StrEnum.MoveNext())
    {
       Debug.Log((char)StrEnum.Current);
    }
 }

 

◻︎ 문자열 생성

: 가독성 높은 코드를 만들기 위해 깔끔한 방법으로 작성하는 것 뿐만 아니라, 

  닷넷을 이용해 좀 더 안정적인 코드를 만드는 방법을 권합니다.

  문자열 변수를 string MyString = "" ; 같은 방식으로 초기화하는 대신

  string.Empty를 이용해 다음 코드처럼 문자열을 선언하고 할당해보자.

string MyString = string.Empty ;

◻︎ 문자열 찾기

: 텍스트 에셋과 같이 파일에서 여러 줄의 텍스트를 읽어 처리할 때, 큰 문자열안에서 작은 문자열이 처음 발견되는

  지점을 찾아야 할 경우가 있습니다.

  위와 같은 경우  String.IndexOf 메소드를 이용하면 문자열을 찾을 수 있습니다.

// 지정한 단어로 문자열을 검색해 처음 발견된 위치의 인덱스를 반환한다.
public int SearchString(string LargerStr, string SearchStr)
{
    // 대소문자를 무시한다.
    return LargerStr.IndexOf(SearchStr, 
           System.StringComparison.CurrentCultureIgnoreCase);
}

◻︎ 정규식

: 엄청 큰 문자열에서 복잡한 검색이 필요한 경우가 있습니다.

  이럴때 정규식(Regex)을 이용하면 가능해집니다.

  정규식은 규격에 맞춘 특수한 문법을 통해 검색 패턴을 지정 합니다.

  Regex 클래스를 이용해 큰 문자열에 정규식을 적용할 수 있습니다.

  닷넷 프레임워크는 RegularExpressions 네임스페이스를 통해 정규식 검색 기능을 제공합니다.

using UnityEngine;
// 정규식 네임스페이스를 포함해야 한다.
using System.Text.RegularExpressions;


public class RGX : MonoBehaviour
{
    // 정규식 검색 패턴
    private string search = "[dw]ay";
    
    // 검색할 큰 문자열
    private string txt = "hello, today is a good day to do things my wat";
    
    // 여기에서 초기화한다.
    void Start()
    {
        // 검색을 수행하고 첫 번째 결과를 m에 담는다.
        Match m = Regex.Match(txt, search);
        
        // 검색 결과에 따라 계속 반복한다.
        while (m.Success)
        {
            //결과를 콘솔에 출력한다.
            Debug.Log(m.Value);
            
            // 다음 결과가 존재하는 경우 넘어간다.
            m = m.NextMatch();
        }
    }
}

 통합 언어 쿼리

: 게임은 무수한 데이터를 통해 작동합니다. 

  문자열뿐 아니라 오브젝트, 데이터베이스, 테이블 , 문서 등 여기서 모두 나열하기 어려울 만큼 수 없이 많은 데이터를

  통해 작동합니다.

  하지만, 이러한 광범위 하고 다양한 데이터를 필요에 따라 작은 주제로 살펴볼 수 있도록 걸러내야 할 필요가 생깁니다.

  이럴 때 통합 언어 쿼리(LINQ)라는 기술을 이용하여 구현 할 수 있습니다.

 

  LINQ은 같은 동작을 더 적은 코드와 일반적으로 더 나은 성능으로 수행 합니다.

  LINQ는 데이터베이스와 XML 문서에 쿼리하는 것처럼 배열과 오브젝트를 포함한 데이터 세트에 쿼리를 실행하기 위한 고수준의

  특화된 언어 입니다. 

  쿼리는 LINQ에 의해 데이터 세트에 이용할 수 있는 적합한 언어로 자동 변환 됩니다.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;

public class EnemyQuery : MonoBehaviour
{
    public void FindEnmiesLinqWay()
    {
        // 씬 안에 존재하는 모든 적을 얻어옵니다.
        Enemy[] Enemies = Object.FindObjectsOfType<Enemy>();
        
        // 검색을 수행한다.
        Enemy[] FilteredData = (from EnmeyChar in Enemies
            where EnmeyChar.Health <= 50 &&
                  EnmeyChar.Defense < 5
            select EnmeyChar).ToArray();
        
        
        // 이제 걸러낸 데이터에 접근해 처리할 수 있다.
        // FinteredData 안에 담긴 모든 항목은 검색 기준에 부합한다.
        foreach (Enemy E in FilteredData)
        {
            Debug.Log(E.name);
        }
    }
}

LINQ와 정규식

: LINQ를 분리해서 사용할 필요는 없습니다.

 예를 들어, 정규식과 함께 사용해 큰 문자열로 부터 특정 문자열 패턴을 뽑아내어 일치하는 결과를 탐색 가능한 

 배열로  변환할 수 있습니다.

  이 방법을 이용하면 쉼표로 구분된 값 형식의 파일(CSV 파일)을 처리할 때 특히 유용합니다.

 CSV 파일이란, 텍스트 파일 안에 각각의 항목이 쉼표로 구분되어 있는 파일을 말합니다.

 LINQ와 정규식을 함께 사용하면 이런 파일에서 각각의 값을 읽어 매우 쉽고 빠르게 고유한 배열 항목으로 집어넣을 수 있습니다.

 

using System.Collections;
using UnityEngine;
using System.Linq;
using System.Text.RegularExpressions;

public class LINQCSV : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        // 여자 이름을 생성한다
        // 정규식 검색 패턴
        // 'female : ' 을 접두어로 가지는 모든 이름을 찾되 접두어를 포함시키지 않는다.
        string search = @"(?<=\bfemale:)\w+\b";
        
        // CSV 데이터 - 캐릭터 이름들
        string CSVData = "male:john, male : tom, male : bob , female : betty, female : jessica, male : dirk";
        
        // 정규식을 수행해 여자 접두어를 가진 모든 이름을 접두어를 제외하고 얻는다.
        string[] FemaleNames = (from Match m in Regex.Matches(CSVData, search) select m.Groups[0].Value).ToArray();
        
        // 결과에 포함된 모든 여자 이름을 출력한다
        foreach (string S in FemaleNames)
        {
            Debug.Log(S);
        }
        
        // 컬렉션에서 임의의 여자 이름을 뽑는다.
        string RandomFemaleName = FemaleNames[Random.Range(0, FemaleNames.Length)];
    }
}

텍스트 데이터 애셋 다루기

: 지금까지 다룬 예제들을 통해 문자열 오브젝트에 직접 담긴 텍스트를 살펴봤는데,

  유니티에서 텍스트 파일을 다루는 것도 가능합니다.

  다시 말해, 외부에서 텍스트를 불러오는것도 가능합니다.

 

◻︎ 텍스트 애셋 : 정적 로딩

:  첫 번째 방법은 텍스트 파일을 유니티 프로젝트로 드래그앤드롭해 임포트하는 것 입니다.

 

◻︎ TextAsset 멤버를 노출시켜 텍스트 데이터 접근 하는 예제 

using System.Collections;
using UnityEngine;

public class TextFileAccess : MonoBehaviour
{
    public TextAsset TextData = null;
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log(TextData.text);   
    }
}

 

◻︎ 텍스트 애셋 : 로컬 파일 로딩

: 텍스트 데이터를 로컬 하드 드라이브와 같이 프로젝트 바깥에서 로딩하기 위한 다른 방법을 살펴 봅시다.

  스크립트에서 동적으로 텍스트 데이터를 불러오는 방법으로는 씬 시작 시점에 불러올 필요가 없고 필요한 시점에 코드를 

  실행하면 됩니다.

  텍스트 파일을 불러올 때는 무거운 처리가 필요하므로 지연시간을 중요하게 고려 해야 합니다.

  따라서 일반적으로는 동적으로 텍스트 애셋을 로딩하는 방법보다 정적인 로딩 방법이 선호되는 편 입니다.

 

using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using UnityEngine;

public class TextFileAccess1 : MonoBehaviour
{
    // 외부 파일에서 텍스트 데이터를 불러오는 함수
    public static string LoadTextFromFile(string Filename)
    {
        // 파일이 시스템에 존재하지 않는다면, 빈 문자열을 반환한다.
        if (!File.Exists(Filename))
            return string.Empty;
        
        // 파일이 존재하는 경우, 파일에서 텍스트를 읽어 들인다.
        return File.ReadAllText(Filename);
    }
}

위 예제 코드는 텍스트 파일의 내용 전체를 하나의 문자열 오브젝트에 불러옵니다.

하지만 값들이 줄 단위로 구분해 기입된 설정 파일 처럼 텍스트 파일의 내용을 줄 단위로 대신 처리하는것을 선호 할 수 있다.

아래의 예제를 봅시다.

// 줄 단위로 텍스트 데이터를 문자열 배열 불러오는 함수
public static string[] LoadTextAsLines(string Filename)
{
   // 파일이 시스템에 존재하지 않는다면, 빈 문자열을 반환한다.
   if(!File.Exists(Filename)) return null;
   
   // 줄 단위로 읽어들인다.
   return File.ReadAllLines(Filename);
}

텍스트 애셋 : INI 파일 로딩

: 많은 텍스트 파일 형식 중 INI 파일도 불러올 수 있습니다.

  유니티 게임에서는 많은 개발자들이 애플리케이션을 저장하는 용도로 PlayerPreferences 클래스를 

  대신 사용하기 때문에 일반적이지 않을 수도 있습니다.

  그렇지만 INI 파일은 서로 다른 많은 플랫폼 사이에서 같은 형식으로 한 위치에만 애플리케이션 설정 데이터를

  저장하도록 하는 장점을 가지고 있습니다.

INI 파일을 불러오기 위한 이상적인 데이터 구조는 키-값 쌍 구조를 반영하는 딕셔너리 입니다.

따라서 INI 파일을 딕셔너리에 불러오면 좋습니다.

 // 기본적인 INI 파일을 딕셔너리로 읽어오는 함수
    public static Dictionary<string, string> ReadINITFile(string Filename)
    {
        // 파일이 시스템에 존재하지 않는다면, null 을 반환한다.
        if (!File.Exists(Filename)) return null;
        
        // 새 딕셔너리 생성
        Dictionary<string, string> INIFile = new Dictionary<string, string>();
        
        // 새 스트림 리더 생성
        using (StreamReader SR = new StreamReader(Filename))
        {
            // 현재 줄을 담을 문자열
            string Line =string.Empty;
            
            // 유효한 줄을 계속 읽어 나간다.
            while(!string.IsNullOrEmpty(Line = SR.ReadLine()))
            {
                // 줄 앞뒤의 여백을 제거 한다.
                Line.Trim();
                
                // 현재 줄의 키=값 사이를 분리 한다.
                string[] Parts = Line.Split();
            }
        }

        return INIFile;
    }

이 함수에서 반환된 딕셔너리는 INI 파일의 구조와 일치하게 됩니다.

따라서 INI 파일의 키에 따른 값에 접근할 때 Value = MyDictionary["Key"]; 와 같은 방식으로

접근할 수 있게 됩니다.


텍스트 애셋 : CSV

: 쉼표로 각각 구분된 문자열이 담겨 있는 CSV 파일을 디스크에서 문자열 배열로 불러와 봅시다.

// CSV 파일을 문자열 배열로 불러오는 함수
public static string[] LoadFromCSV(string Filename)
{
   // 파일이 시스템에 존재하지 않는다면, null을 반환한다.
   if(!File.Exists(Filename) return null;
   
   // 모든 텍스트를 읽어 온다.
   string AllText = File.ReadAllText(Filename);
   
   // 문자열 배열을 반환한다.
   return AllText.Split(new char[] {','});
}

텍스트 애셋 : 웹 로딩

: 멀티플레이어 게임을 만든다면 웹과 공유하는 플레이어나 게임 데이터에 접근해야 할 때가 있습니다.

 패스워드의 해시를 온라인으로 검증한다거나 웹페이지의 구성요소를 처리하기 위해 접근하려 할 때는 

 온라인으로 텍스트 데이터를 받기 위해 WWW 클래스가 필요 합니다.

public IEnumerator GetTextFromURL(string URL)
{
	// 새 WWW 오브젝트를 생성 한다.
    WWW TXTSource = new WWW(URL);
    
    // 데이터를 불러오길 기다린다.
    yield return TXTSource;
    
    // 텍스트 데이터를 얻는다.
    string ReturnedText = TXTSource.text;
}

이것으로 6장의 정리를 마치도록 하겠습니다.

Comments