Programming 

static 키워드를 파일 경로와 URL 표현에 사용하기


작성 기준 버전 :: 2019.1.4f1


유니티 엔진으로 게임을 만들 때, 스크립트 작업은 대부분 C# 스크립트로 이루어진다. 한 때 유니티 초기에는 자바 스크립트(Java Script)나 부(Boo) 같은 언어도 지원을 했었지만, 최신 버전의 유니티 엔진은 C#만을 지원한다. 그렇기 때문에 C#에서 지원하는 기본적인 문법을 충분히 배우고 활용하는 법을 공부해야한다.


이번에는 유니티에서 static 키워드를 활용하여 파일 경로와 URL 표현에 사용하는 방법에 대해서 알아볼 것이다. C#의 static 키워드에 대한 기본적인 내용은 링크를 통해서 확인할 수 있다.



파일 경로와 URL 표현


public class FileLoader : MonoBehaviour

{

    void Start()

    {

        LoadSomeFile(Application.dataPath + "/Save/" + "fileName.txt");

    }


    public void LoadSomeFile(string filePath)

    {

        // 파일을 로드하는 작업...

        Debug.Log(filePath);

    }

}


public class UrlDownloader : MonoBehaviour

{

    void Start()

    {

        StartCoroutine(DownloadSomeFile("https://SomeUrl/GameData/" + "fileName.png"));

    }


    public IEnumerator DownloadSomeFile(string filePath)

    {

        UnityWebRequest request = new UnityWebRequest(filePath);


        yield return request.SendWebRequest();


        var data = request.downloadHandler.data;

        // URL에서 받아온 데이터로 작업...

    }

}


모든 프로그래밍도 마찬가지겠지만 게임 프로그래밍 역시 게임 저장/불러오기나 네트워크 게임이라면 게임 데이터 받아오기 등의 파일 경로와 URL을 다루어야 할 일이 발생한다. 하지만 위의 코드처럼 경로와 URL을 코드에 하드코딩을 해버리면 나중에 파일 경로나 URL이 바뀌는 경우가 발생했을 때, 변경된 경로를 모두 찾아서 바꾸어야 하는 번거로움이 발생한다. 그리고 그 중에 하나라도 놓치는 경우가 발생한다면, 그것은 곧바로 게임이 제대로 동작하지 않은 버그로 직행한다.


이러한 문제를 막기 위해서 게임에서 사용되는 모든 경로는 하나의 클래스로 묶어두고 그 클래스에서 경로를 가져오도록 만드는게 좋다. 다만 클래스에서 경로를 불러올 때는 객체를 생성하지 않고 곧바로 불러올 수 있게 하는 것이 좋다. 바로 그런 점에서 static 키워드를 적용하면 매우 좋다.


public static class GamePath

{

    public static string savePath = Application.dataPath + "/Save/";

}


public static class GameURL

{

    public static string GameDataURL = "https://SomeUrl/GameData/";

}


위 예시 코드처럼 정적 클래스와 정적 변수를 만들어서 경로를 표현한다.


public class FileLoader MonoBehaviour

{

    void Start()

    {

        LoadSomeFile(GamePath.savePath + "fileName.txt");

    }


    public void LoadSomeFile(string filePath)

    {

        // 파일을 로드하는 작업...

        Debug.Log(filePath);

    }

}


public class UrlDownloader MonoBehaviour

{

    void Start()

    {

        StartCoroutine(DownloadSomeFile(GameURL.GameDataURL + "fileName.png"));

    }


    public IEnumerator DownloadSomeFile(string filePath)

    {

        UnityWebRequest request = new UnityWebRequest(filePath);


        yield return request.SendWebRequest();


        var data = request.downloadHandler.data;

        // URL에서 받아온 데이터로 작업...

    }

}

 

경로를 사용할 때는 바로 위 예시 코드처럼 사용하면 된다. 그러면 만약에 경로가 변경되었을 때, 모든 코드에서 수정된 경로를 일일이 찾아서 바꿀 필요없이 GamePath 클래스와 GameURL 클래스의 경로만 수정하면 모든 코드에 적용이 된다.


이런 식으로 코드 내에 상수로 들어가지만, 추후에 변경이 발생할 수 있는 부분을 정적 클래스로 묶어서 관리하면 좋다.

반응형

static 

정적 변수와 정적 함수 그리고 정적 클래스


static 키워드는 변수나 함수, 클래스에 정적 속성을 부여하는 것으로 클래스로부터 객체를 생성하지 않고 변수나 함수를 호출할 수 있도록 해주는 것이다.



정적 변수


public class StaticTestClass

{

    public static int score;

}


정적 변수를 선언하기 위해서는 위의 예시 코드와 같이 static 키워드를 붙여서 변수를 정의하면 된다. 이렇게 선언한 정적 변수는 클래스로부터 객체를 생성하지 않아도 [클래스명.변수이름]의 형식으로 곧바로 사용할 수 있게 된다. 


public class MainClass

{

    public void Main()

    {

        StaticTestClass.score = 10;

    }

}


클래스의 일반 멤버 변수는 클래스의 객체가 생성될 때, 각 객체마다 따로 생기지만, 정적 변수는 해당 클래스가 처음으로 사용되는 때에 한 번만 초기화되어 계속 동일한 메모리를 사용하게 된다.

 

 

도식으로 보면 위의 그림과 같다. 정적 변수를 포함한 클래스 A의 객체를 두 개를 생성하여 각 이름을 object1, object2라고 했을 때, 각 인스턴스에는 정적 변수가 포함되지 않으며, 일반 멤버 변수만 포함된다. 클래스 A의 정적 변수는 클래스 A가 처음 사용되는 시점에 별도의 메모리 공간에 할당된다.


 

생성된 객체에 정적 변수가 포함되지 않는 것은 실제로 객체를 생성해서 멤버 변수를 찾았을 때, 목록에 나오지 않는 것을 보면 확인할 수 있다.



정적 함수


public class StaticTestClass

{

    public static int score;


    public int memberInt;


    public static void StaticFunction()

    {

        score = 10 // static 변수는 호출할 수 있다.

        memberInt = 10// static 함수 내에서 멤버변수는 호출할 수 없다.

    }

}


public class MainClass

{

    public void Main()

    {

        StaticTestClass.score = 10;

        StaticTestClass.StaticFunction();

    }

}


함수를 선언할 때, static 키워드를 붙여서 함수를 정의하면 정적 함수를 만들 수 있다. 이 정적 함수 역시 [클래스명.함수이름]의 형식으로 객체를 생성하지 않고 곧바로 호출할 수 있다.


단, 정적 함수는 객체가 생성되기 전에 호출이 가능하기 때문에, 정적 함수 내에서는 정적 변수가 아닌 일반 멤버 변수를 호출할 수 없다.



정적 클래스


public static class StaticTestClass

{

    public static int score;


    static StaticTestClass()

    {

        score = 10;

    }


    public static void StaticFunction()

    {

        score = 20;

    }

}


정적 클래스는 모든 멤버가 정적 변수 혹은 정적 함수로 이루어진 것으로 객체를 생성할 수 없는 클래스이다. 모든 정적 멤버 변수 및 정적 멤버 함수는 [클래스명.변수이름] 혹은 [클래스명.함수이름]으로 호출된다.


정적 클래스는 정적 생성자를 가질 수 있는데 이 정적 생성자는 public, protected, private 등의 액세스 한정자를 사용할 수 없으며, 매개변수 역시 가질 수 없다.

반응형

다른 cs 파일의 클래스 호출과 static 로직에 대하여


로딩 씬(Loading Scene) 구현하기 글에 달아주신 ㅇㅇ님의 질문 댓글에 대한 답변입니다. 질문의 내용은 아래와 같습니다.


 

로딩 씬 구현하기 글을 보면 LoadingSceneManager 클래스는 아래와 같이 구현되어 있습니다.


LoadingSceneManager.cs

using System.Collections;

using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.SceneManagement;

public class LoadingSceneManager : MonoBehaviour
{
    public static string nextScene;

    [SerializeField]
    Image progressBar;

    private void Start()
    {
        StartCoroutine(LoadScene());
    }

    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("LoadingScene");
    }

    IEnumerator LoadScene()
    {
        yield return null;

        AsyncOperation op = SceneManager.LoadSceneAsync(nextScene);
        op.allowSceneActivation = false;

        float timer = 0.0f;
        while (!op.isDone)
        {
            yield return null;

            timer += Time.deltaTime;

            if (op.progress >= 0.9f)
            {
                progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer);

                if (progressBar.fillAmount == 1.0f)
                    op.allowSceneActivation = true;
            }
            else
            {
                progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, op.progress, timer);
                if (progressBar.fillAmount >= op.progress)
                {
                    timer = 0f;
                }
            }
        }
    }
}


첫 번째 질문에서의 LoadingSceneManager.LoadScene("Scene2");를 호출하는 클래스는 아래와 같이 구현되어 있었습니다.


TestCode.cs

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

public class TestCode : MonoBehaviour
{

    // Use this for initialization
    void Start ()
    {
        LoadingSceneManager.LoadScene("Scene2");
    }
}



다른 cs 파일의 클래스 호출


우선 첫 번째로 LoadingSceneManager.cs 파일에 구현된 LoadingSceneManager 클래스를 어떻게 TestCode.cs 파일로 다른 선언 없이 바로 호출할 수 있었느냐라는 의도로 질문하신 것 같습니다. 어떻게 다른 cs 파일에 정의된 클래스를 바로 호출할 수 있었냐는 질문을 하신 걸로 보아, ㅇㅇ님께서는 아마 유니티를 배우시기 이전에 C/C++과 같이 다른 소스 파일의 코드를 불러오기 위해서는 별도의 include 등의 전처리가 필요한 언어를 배우신게 아닌가 싶습니다.


그런 C/C++에서는 다른 소스 파일의 클래스를 불러와서 사용하기 위해서는 아래의 예시와 같이 전처리기에서 사용하고자 하는 클래스가 담긴 헤더를 포함시켜주어야 합니다.


LoadingSceneManager.h

#include <string>


#pragma once


class LoadingSceneManager

{

public:

static void LoadingScene(std::string sceneName)

{

// 씬 로딩 처리 ...

}

};


TestCode.h

#include "LoadingSceneManager.h" // C/C++에서는 이렇게 호출하고자 하는 클래스가 담긴 헤더를 호출해주어야 한다.


#pragma once


class TestCode

{

public:

void Start()

{

LoadingSceneManager::LoadingScene("Scene2");

}

};


하지만 C#에서는 헤더 파일이 따로 존재하지 않고 cs파일만 존재하며, 같은 프로젝트 안에 있는 cs파일이라면, 다른 cs 파일 안에 정의된 클래스를 가져와서 사용할 수 있다는 뜻입니다.



정적 함수(Static Function)


두 번째 질문으로는 static으로 선언된 LoadScene() 함수가 LoadSceneManager 오브젝트가 없는 다른 씬에서 어떻게 호출될 수 있는가 입니다.



 

로딩 씬 구현하기 글에서 구현한 방식을 그림으로 표현하면 위의 이미지와 같습니다. 씬은 Scene1, LoadingScene, Scene2가 존재하고 TestCode 클래스의 인스턴스는 Scene1에서만 존재하고 LoadSceneManager 클래스의 인스턴스는 LoadingScene에서만 존재합니다. 그런데 Scene1에서 LoadScene에만 존재하는 LoadSceneManager의 함수를 호출할 수 있느냐가 메인인데, 이 부분은 정적 함수(static function)에 대한 기본적인 이해가 필요합니다.


    public static void LoadScene(string sceneName)
    {
        nextScene = sceneName;
        SceneManager.LoadScene("LoadingScene");

    }


코드를 다시 보면 아시겠지만, LoadSceneManager 클래스의 LoadScene(string) 함수는 static으로 선언되어 있습니다. 이렇게 static으로 선언된 함수를 정적 함수라고 하며, 이러한 정적 함수는 클래스의 인스턴스가 생성되지 않았더라도 컴파일 시점에 전역에 할당되어 있습니다. 그렇기 때문에 TestCode.cs에서와 같이 해당 클래스의 이름을 통해서 곧바로 접근이 가능해집니다.


다시 한번 풀어서 이야기하자면, TestCode.cs가 실행되는 때에는 LoadSceneManager 클래스의 인스턴스가 생성되어 있지 않지만, 전역에 할당되어 있는 LoadSceneManager 클래스의 전역 함수에 접근하여 정적 변수(이것 역시 전역에 할당되어 모든 LoadSceneManager 클래스의 인스턴스들이 공유한다.)인 nextSceneName에 다음으로 넘어갈 씬 이름을 전달하고 LoadingScene을 불러옵니다. 


LoadingScene이 전부 불러와지면 LoadSceneManager 클래스의 인스턴스가 생성되고, LoadSceneManager의 Start() 함수가 실행되면서 LoadScene 코루틴을 호출하는 순서로 동작합니다.




PS.

로딩 씬 구현하기 글은 2017년에 작성한 오래 전 글이라 내용이 많이 불친절한 편입니다. 기본적인 내용은 많이 바뀌지 않겠지만, 유니티 버전이 많이 바뀌면서 좀 더 알아보기 쉽게 새로 작성할 계획은 오래 전부터 세워둔 상태였는데, 이래저래 시간을 보내면서 아직까지 작성하지 못했습니다. 최대한 이른 시일 내로 새로운 버전으로 작성해보도록 하겠습니다.

반응형
  1. 료용 2019.12.26 02:02

    닉네임도 ㅇㅇ이라는 싸가지없는 닉으로 해놨는데 답변해주셔서감사합니다.

+ Recent posts