Programming 

오브젝트 풀링 기법

 

작성 기준 버전 :: 2019.2

 

프로그래밍에서 오브젝트를 생성하거나 파괴하는 작업은 꽤나 무거운 작업으로 분류된다. 오브젝트 생성은 메모리를 새로 할당하고 리소스를 로드하는 등의 초기화하는 과정으로, 오브젝트 파괴는 파괴 이후에 발생하는 가비지 컬렉팅으로 인한 프레임 드랍이 발생할 수 있다.

 

이러한 문제를 해결하기 위해서 사용되는 기법이 바로 오브젝트 풀링(Object pooling)이다.

 

오브젝트 풀링의 개념

 

오브젝트 풀링의 개념은 아주 간단하다. 

 

 

먼저 풀링을 할 오브젝트를 담을 오브젝트 풀을 구성한다.

 

 

그 다음 풀링할 오브젝트를 생성해서 오브젝트 풀에서 그 오브젝트들을 관리하게 만든다.

 

 

외부에서 해당 오브젝트가 필요하면 오브젝트 풀에서 꺼내간다.

 

 

오브젝트 풀에서 꺼낸 오브젝트의 사용이 끝나면 오브젝트를 풀에 돌려준다.

 

 

오브젝트 풀에서 오브젝트를 가져오려고 할 때, 모든 오브젝트가 이미 사용중이라면 새로운 오브젝트를 생성해서 꺼내준다.

 

이것이 바로 오브젝트 풀의 개념이다. 게임에 필요한 오브젝트를 미리 생성해서 필요할 때마다 꺼내쓰고 사용이 끝나면 오브젝트 풀에 돌려주는 것이다. 이렇게 함으로써, 오브젝트가 필요할 때마다 생성하고 다 쓰면 파괴하는 것이 아니라, 게임이 시작될 때, 필요한 만큼의 오브젝트만 생성하고, 모자라면 추가로 생성하고, 게임이 끝나면 파괴하는 방식으로 오브젝트의 생성/파괴 횟수를 줄일 수 있게 된다.

 

오브젝트 풀링 기법은 특히 메모리가 부족하고 CPU의 성능이 낮은 디바이스의 사양이 낮았던 예전에 주로 사용되었던 기법이지만, 현재에 와서도 게임의 최적화를 위해서 많이 사용되며, 모바일 게임이 많이 제작되면서 PC에 비해서 모자란 디바이스 성능으로 인해서 재조명 받는 기법이다.

 

보통 자주 생성되었다가 파괴되어야 하는 총알이나, 캐릭터가 뛸 때 발생하는 먼지 이펙트 같은 곳에 많이 사용되는 기법이다.

 

 

 

유니티 엔진에서의 구현

 

기본 세팅

 

기본적인 세팅은 다음과 같다.

 

 

씬 한 가운데 캐릭터의 역할을 할 초록색 박스를 배치했다.

 

public class Shooter : MonoBehaviour
{
    [SerializeField]
    private GameObject bulletPrefab;

    private Camera mainCam;

    void Start()
    {
        mainCam = Camera.main;
    }

    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = Instantiate(bulletPrefab).GetComponent<Bullet>();
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

그리고 박스 게임 오브젝트에는 마우스 클릭 방향으로 총알 게임 오브젝트를 생성해서 발사하는 Shooter 컴포넌트가 부착되어 있다.

 

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Destroy(gameObject, 5f);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그리고 지정된 방향으로 5초 간 날아가다 자동으로 소멸하는 총알 프리팹 역시 만들어두었다.

 

 

이것을 테스트 해보면 마우스를 클릭하면 엄청난 양의 총알 오브젝트가 새로 생성되고 시간이 지나면 모두 파괴되는 것을 볼 수 있다.

 

 

이 상황을 프로파일러를 통해서 확인해보면 총알을 생성하는 Instantiate와 파괴하는 Destroy가 어느 정도 성능을 잡아먹고 있는 것을 확인할 수 있다.

 

오브젝트 풀 구현

 

public class ObjectPool : MonoBehaviour
{
    public static ObjectPool Instance;

    [SerializeField]
    private GameObject poolingObjectPrefab;

    Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

    private void Awake()
    {
        Instance = this;

        Initialize(10);
    }

    private void Initialize(int initCount)
    {
        for(int i = 0; i < initCount; i++)
        {
            poolingObjectQueue.Enqueue(CreateNewObject());
        }
    }

    private Bullet CreateNewObject()
    {
        var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
        newObj.gameObject.SetActive(false);
        newObj.transform.SetParent(transform);
        return newObj;
    }

    public static Bullet GetObject()
    {
        if(Instance.poolingObjectQueue.Count > 0)
        {
            var obj = Instance.poolingObjectQueue.Dequeue();
            obj.transform.SetParent(null);
            obj.gameObject.SetActive(true);
            return obj;
        }
        else
        {
            var newObj = Instance.CreateNewObject();
            newObj.gameObject.SetActive(true);
            newObj.transform.SetParent(null);
            return newObj;
        }
    }

    public static void ReturnObject(Bullet obj)
    {
        obj.gameObject.SetActive(false);
        obj.transform.SetParent(Instance.transform);
        Instance.poolingObjectQueue.Enqueue(obj);
    }
}

 

오브젝트 풀링 기법을 구현하는 기초적인 코드의 전체는 위의 코드와 같다. 

 

public static ObjectPool Instance;

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

이 코드를 하나씩 살펴보자. 우선 오브젝트 풀의 경우 오브젝트를 생성하는 어떤 코드에서든 접근이 가능해야하는 경우가 많기 때문에 싱글톤 패턴으로 구현되는 경우가 많다. 

 

[SerializeField]
private GameObject poolingObjectPrefab;

 

그리고 생성해야할 오브젝트의 프리팹을 가지고 있다. 프리팹을 가지고 있어야 하는 이유는 처음 생성해둔 오브젝트를 모두 꺼내주고 나서, 꺼내준 오브젝트를 미처 돌려받기 전에, 오브젝트를 요청받았을 때, 새로운 오브젝트를 생성해서 꺼내주기 위한 것이다.

 

Queue<Bullet> poolingObjectQueue = new Queue<Bullet>();

 

큐(Queue)를 이용해서 생성된 총알 오브젝트를 순서대로 관리한다. 만약 오브젝트 풀에서 오브젝트를 꺼내주는 순서와 돌려받는 순서를 제대로 관리하지 못하면, 하나의 오브젝트를 여러 곳에 빌려주는 등의 문제가 발생할 수 있다.

 

private void Awake()
{
    Instance = this;

    Initialize(10);
}

 

게임 오브젝트가 생성되면 제일 먼저 실행되는 Awake() 함수에서는 싱글톤 패턴에서 사용되는 오브젝트 풀 인스턴스에 자신을 할당하는 일과 오브젝트 풀을 초기화하는 Initialize() 함수를 호출한다. 

 

private void Initialize(int initCount)
{
    for(int i = 0; i < initCount; i++)
    {
        poolingObjectQueue.Enqueue(CreateNewObject());
    }
}

 

Initialze() 함수에서는 매개변수로 받은 초기화 갯수 값에 따라서 CreateNewObject() 함수에서 만들어진 새로운 총알 오브젝트를 pooling Object Queue에 넣어준다. 이렇게 게임이 시작하기 전에 사용될 게임 오브젝트를 미리 적절한 갯수를 만들어 줌으로써 게임 플레이 도중에 발생할 과부하를 게임 준비 과정인 로딩으로 돌릴 수 있다. 모두 잘 알겠지만, 플레이어는 아주 조금 더 긴 로딩은 당장 용납하지만, 플레이 도중에 발생하는 렉은 용납하지 못한다.

 

private Bullet CreateNewObject()
{
    var newObj = Instantiate(poolingObjectPrefab).GetComponent<Bullet>();
    newObj.gameObject.SetActive(false);
    newObj.transform.SetParent(transform);
    return newObj;
}

 

CreateNewObject() 함수는 poolingObjectPrefab으로부터 새 게임 오브젝트를 만든 뒤 비활성화해서 반환하는 역할을 한다. 이렇게 곧바로 비활성화하는 이유는 아직 오브젝트 풀이 꺼내주지 않은 오브젝트는 비활성화된 것으로써 플레이어의 눈에 띄지 않아야하기 때문이다.

 

public static Bullet GetObject()
{
    if(Instance.poolingObjectQueue.Count > 0)
    {
        var obj = Instance.poolingObjectQueue.Dequeue();
        obj.transform.SetParent(null);
        obj.gameObject.SetActive(true);
        return obj;
    }
    else
    {
        var newObj = Instance.CreateNewObject();
        newObj.gameObject.SetActive(true);
        newObj.transform.SetParent(null);
        return newObj;
    }
}

 

GetObject() 함수는 오브젝트 풀이 가지고 있는 게임 오브젝트를 요청한 자에게 꺼내주는 역할을 한다. 단, 모든 오브젝트를 꺼내서 빌려줘서 poolingObjectQueue에 빌려줄 오브젝트가 없는 상태라면 CreateNewObject() 함수를 호출해서 새로운 오브젝트를 생성해서 빌려준다.

 

public static void ReturnObject(Bullet obj)
{
    obj.gameObject.SetActive(false);
    obj.transform.SetParent(Instance.transform);
    Instance.poolingObjectQueue.Enqueue(obj);
}

 

ReturnObject() 함수는 빌려준 오브젝트를 돌려받는 함수이다. 돌려받은 오브젝트를 비활성화한 뒤 정리하는 일을 처리한다.

 

무엇이든지 빌려줬으면 돌려받아야 한다. 이 원칙은 프로그래밍에서도 매우 중요한 원칙으로 사용한 오브젝트를 제대로 돌려받지 않으면 GetObject() 함수를 호출할 때, 계속해서 새로운 오브젝트를 생성하고 사라지지 않은 오브젝트들이 씬에 쌓이게 된다. 이것이 바로 메모리 누수이다.

 

 

코드 작성이 끝나면 씬에 새 게임 오브젝트를 배치하고 오브젝트 풀 컴포넌트를 부착하고 pooling Object Prefab 프로퍼티에 풀링해야할 게임 오브젝트의 프리팹을 할당해주면 된다.

 

 

 

그 다음에는 총알 오브젝트를 생성하는 부분과 파괴하는 부분을 수정해야 한다.

 

public class Shooter : MonoBehaviour
{
    void Update()
    {
        if(Input.GetMouseButton(0))
        {
            RaycastHit hitResult;
            if(Physics.Raycast(mainCam.ScreenPointToRay(Input.mousePosition), out hitResult))
            {
                var bullet = ObjectPool.GetObject(); // 수정
                var direction = new Vector3(hitResult.point.x, transform.position.y, hitResult.point.z) - transform.position;
                bullet.transform.position = direction.normalized;
                bullet.Shoot(direction.normalized);
            }
        }
    }
}

 

우선 생성하는 부분은 Shooter 클래스의 Update() 함수에 Instanitate()로 총알을 생성하던 부분을 ObjectPool.GetObject()로 대체하면 된다.

 

public class Bullet : MonoBehaviour
{
    private Vector3 direction;
    public void Shoot(Vector3 direction)
    {
        this.direction = direction;
        Invoke("DestroyBullet", 5f);
    }

    public void DestroyBullet()
    {
        ObjectPool.ReturnObject(this);
    }

    void Update()
    {
        transform.Translate(direction);   
    }
}

 

그 다음 파괴하는 부분은 Bullet 클래스에서 DestoryBullet() 함수에서 ObjectPool.ReturnObject()를 호출해서 오브젝트 풀에 돌려주도록 구현하고 Shoot() 함수에서 Destory() 함수를 호출하던 부분을 Invoke()로 5초 뒤에 DestoryBullet() 함수를 호출하도록 수정한다.

 

 

 

플레이를 해서 테스트 해보면 총알이 일정 갯수만큼 생성되고 나면 더 이상 생성되지 않으며, 시간이 지나면 소멸되어야 하는 총알 오브젝트들이 비활성화되서 오브젝트 풀로 반환되는 것을 확인할 수 있다.

 

 

프로파일러를 통해서 확인해보면 최대 생성량에 도달한 이후에는 Instatiate와 Destroy로 인한 작업이 발생하지 않는 것을 볼 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming 

로딩 씬(Loading Scene) 구현하기(커튼 방식)

 

작성 기준 버전 :: 2019.1.4f1

 

[이 포스트의 내용을 유튜브 영상으로도 시청하실 수 있습니다]

 

게임의 장르와 배경들의 종류는 많고도 많지만 그 어떤 종류의 게임이던간에 아주 가벼운 게임이 아닌 이상 반드시 등장하는 장면이 있다. 그 장면은 바로 로딩 씬이다. 다들 로딩 씬이 등장하면 언제쯤 지나가려나 하며 로딩 바에 마우스를 올리고 정말로 바가 채워지고 있는지 확인해본 경험이 있을 것이다. 게임을 처음으로 접했던 어린 시절에는 이런 로딩 씬이 왜 필요한지도 몰랐고 그냥 재미있는 게임을 할 시간을 잡아먹는 나쁜 녀석이라는 생각만 가득했다.

 

 

하지만 게임 개발을 시작한 이후로 이 로딩 씬만큼 중요한 씬이 또 없다는 것을 깨달을 수 있었다. 로딩 씬의 역할은 단지 시간만 잡아먹는 것이 아니라 게임의 씬이 전환될 때 다음 씬에서 사용될 리소스들을 물리적인 저장소에서 읽어와서 메모리에 올리는 등의 게임을 하기 위한 준비를 하는 작업이다.

 

만약에 게임에 로딩 장면이 존재하지 않는다면 어떻게 될까? 아마 플레이어는 다음 씬으로 넘어가는 동안 가만히 게임이 멈춘 화면을 보고 있거나 까만 화면을 보고 있어야 한다. 그런 일이 발생한다면 로딩이 얼마나 진행되었는지 알 수 없고 이 게임이 로딩 중인지 정지한 것인지 구분할 수도 없어서 너무 답답할 것이다. 그렇기 때문에 씬이 전환될 때에는 로딩 씬을 만들어서 플레이어에게 로딩이 얼마나 진행되었는지 알려주면서 플레이어가 지루하지 않게 게임 게임 팁이나 게임 스토리등을 보여주는 것이다.

 

 

개념

 

 

 

이전에는 로딩하는 씬을 전용으로 만들어서 로딩하는 방법을 소개했었다. 이번에는 그 방법과는 다르게 마치 무대에서 잠시 커튼을 내리고 무대를 교체하는 것과 비슷하게 로딩 바를 보여주는 UI를 전면에 씌운 뒤 로딩하는 방법을 구현해본다.

 

 

구현하기

 

앞에서는 로딩 씬의 필요성에 대해서 이야기했다면 이제는 실제로 로딩 씬을 유니티에서 구현하는 방법을 알아보자. 

 

 

코드 작성하기

 

씬 화면을 가리고 로딩작업을 진행하는 SceneLoader 클래스를 생성하고 다음의 순서로 코드를 작성한다.

 

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

 

SceneLoader는 기존 씬을 UI로 가리고 다른 씬을 불러오는 등의 작업을 해야하기 때문에 UnityEngine.SceneManagement와 UnityEngine.UI 네임스페이스를 using 선언해준다.

 

public class SceneLoader : MonoBehaviour
{
    protected static SceneLoader instance;
    public static SceneLoader Instance
    {
        get
        {
            if(instance == null)
            {
                var obj = FindObjectOfType<SceneLoader>();
                if(obj != null)
                {
                    instance = obj;
                }
                else
                {
                    instance = Create();
                }
            }
            return instance;
        }

        private set
        {
            instance = value;
        }
    }

    [SerializeField]
    private CanvasGroup sceneLoaderCanvasGroup;
    [SerializeField]
    private Image progressBar;

    private string loadSceneName;

    public static SceneLoader Create()
    {
        var SceneLoaderPrefab = Resources.Load<SceneLoader>("SceneLoader");
        return Instantiate(SceneLoaderPrefab);
    }

    private void Awake()
    {
        if (Instance != this)
        {
            Destroy(gameObject);
            return;
        }

        DontDestroyOnLoad(gameObject);
    }
}

 

SceneLoader의 기본적인 구성은 위와 같다. 이 클래스는 싱글톤 패턴으로 작성되어서 어디서든지 호출할 수 있도록 만들어졌으며, 전체 패널의 투명도를 조절하는 방식으로 씬 로딩 UI를 페이드 인 아웃을 시켜서 자연스럽게 등장시키기 위해서 캔버스 그룹을 사용한다. 그리고 진행도를 표현하기 위해서 이미지를 멤버 변수로 가진다. 

 

public void LoadScene(string sceneName)
{
    gameObject.SetActive(true);
    SceneManager.sceneLoaded += LoadSceneEnd;
    loadSceneName = sceneName;
    StartCoroutine(Load(sceneName));
}

private IEnumerator Load(string sceneName)
{
    progressBar.fillAmount = 0f;
    yield return StartCoroutine(Fade(true));

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

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

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

            if (progressBar.fillAmount == 1.0f)
            {
                op.allowSceneActivation = true;
                yield break;
            }
        }
    }
}

private void LoadSceneEnd(Scene scene, LoadSceneMode loadSceneMode)
{
    if(scene.name == loadSceneName)
    {
        StartCoroutine(Fade(false));
        SceneManager.sceneLoaded -= LoadSceneEnd;
    }
}

private IEnumerator Fade(bool isFadeIn)
{
    float timer = 0f;

    while(timer <= 1f)
    {
        yield return null;
        timer += Time.unscaledDeltaTime * 2f;
        sceneLoaderCanvasGroup.alpha = Mathf.Lerp(isFadeIn ? 0 : 1, isFadeIn ? 1 : 0, timer);
    }

    if(!isFadeIn)
    {
        gameObject.SetActive(false);
    }
}

 

커튼 방식으로 로딩 UI를 구현하는 핵심 코드는 위와 같다. 씬을 로딩하는 로직 자체는 로딩 씬 교체 방식과 같으며 여기에 추가적으로 씬을 로딩하기 직전에 로딩 UI를 페이드 인하는 부분과 로딩이 완전히 끝난 지점에서 페이드 아웃되는 코드가 추가되었다. 그리고 씬 로딩 완료가 완전히 완료된 시점을 확인하기 위해서 SceneManager의 SceneLoaded 콜백에 함수를 추가해주는 코드 역시 추가되었다.

 

코드를 모두 작성했다면 저장하고 에디터로 돌아간다.

 

 

 

로딩 UI 구성

 

 

 

로딩 UI를 구성하기 위해서 SceneLoader라는 이름으로 캔버스(Canvas)를 하나 만들고 거기에 캔버스 그룹과 아까 만든 Scene Loader 컴포넌트를 부착한다. 그리고 Scene Loader Canvas Group 프로퍼티에 방금 추가한 캔버스 그룹 컴포넌트를 할당해준다.

 

 

그 다음엔 화면 전체를 덮는 이미지 하나를 Background라는 이름으로 추가한다. 이 이미지는 화면 전체를 가림으로써 씬이 교체되는 것을 플레이어의 시선에서 가려주고 마우스 입력을 방지하는 용도로 사용된다. 여기에는 검은 이미지 이외에도 플레이어의 시선을 끌만한 컨셉아트나 배경 이미지를 넣어줄 수 있다.

 

 

 

그리고 씬 로딩 진행도를 보여줄 이미지를 Progress Bar라는 이름으로 추가하고 위의 이미지와 같이 설정한다.

 

 

Progress Bar 이미지 설정이 끝났다면 SceneLoader 게임 오브젝트를 선택한 뒤, 위의 이미지와 같이 SceneLoader의 액티브를 끄고, Canvas Group의 Alpha 값을 0으로 설정한다. 그리고 Progress Bar 이미지를 Scene Loader 컴포넌트의 Progress Bar 프로퍼티에 할당해준다.

 

 

마지막으로 프로젝트 뷰에 Resources 폴더를 만들고 방금 만든 SceneLoader 게임 오브젝트를 드래그해서 프리팹으로 만들어주고 게임 오브젝트를 씬에서 삭제한다.

 

 

테스트 세팅하기

 

 

위의 과정을 모두 마쳤다면 두 개의 씬을 새로 만들고 씬이 전환되었음을 확인하기 위해서 각 씬에 다른 모양의 게임 오브젝트를 추가해준다.

 

using UnityEngine;

public class SceneLoadTester : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            SceneLoader.Instance.LoadScene("Scene2");
        }
    }
}

 

 

그리고 SceneLoader의 기능을 테스트하기 위해 스페이스바를 누르면 SceneLoader를 호출하는 코드를 작성하고 Scene1에 게임 오브젝트를 생성해서 스크립트를 넣어준다.

 

 

상단의 [File > Build Settings] 메뉴를 선택하거나 [Ctrl + Shift + B]를 눌러서 빌드 세팅 창을 연 다음 만든 씬들을 빌드될 씬 목록에 넣어준다.

 

 

 

테스트

 

 

그런 후에 첫 번째 씬에서 플레이 버튼을 눌러 게임을 실행하고 스페이스바를 누르면 첫 번째 씬에서 자연스럽게 로딩 UI가 나타난 후에 아래쪽 로딩 바가 자연스럽게 차오르고 두 번째 씬으로 넘어가는 것을 확인할 수 있다.

 

위의 예시에서는 씬의 로딩 진행도 만을 이용해서 진행 정도를 체크했지만, 유니티에서는 다음 씬에서 사용될 애셋 번들을 불러오는 것 또한 로딩에 포함될 수 있고, 만약 네트워크 게임을 제작한다면 네트워크 동기화 정도도 포함될 수 있다.

 

 

여담으로 일부 게임 제작자의 경우에는 로딩 시간이 너무 짧아서 로딩 시간동안 보여주고자 하는 팁이나 스토리 등이 너무 빠르게 스쳐지나간다고 생각하는 경우에는 일부러 로딩 속도를 늦추거나 페이크 로딩 시간을 넣어서 로딩 시간을 일부러 길게 만드는 경우도 있다.

 


 

이렇게 로딩 UI를 무대의 커튼처럼 전면에 깔아서 로딩하는 방법 이외에도 아예 로딩씬을 불러온 뒤에 다음 씬을 로딩하는 방법도 있으며 해당 방법은 링크에서 확인할 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming 

유니티에서의 싱글톤 패턴 활용

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

여러 종류의 프로그램을 만들다 보면 어떤 문제를 해결하기 위해서 비슷한 형태의 코드를 만들게 되는 경우가 자주 있다. 이런 비슷한 유형의 문제를 해결하는 방법을 묶어낸 것을 바로 프로그래밍 패턴이라고 한다. 이러한 프로그래밍 패턴은 유니티 엔진에서 스크립트를 작성하는데도 똑같이 유효하기 때문에 프로그래밍 패턴을 공부하는 것이 상당한 도움이 될 것이다.

 

 

일반적인 싱글톤 패턴(Singleton Pattern)

 

싱글톤 패턴은 프로그래밍 패턴 중에서 상당히 자주 쓰이는 패턴 중 하나로, 클래스의 오브젝트를 단 하나만 생성하고자 할 때 주로 사용된다.

 

public class SingletonClass

{

    private SingletonClass()

    { }

 

    public static SingletonClass Instance

    {

        get

        {

            if (Instance == null)

            {

                Instance = new SingletonClass();

            }

            return Instance;

        }

        private set

        {

            Instance = value;

        }

    }

}

 

일반적인 C# 프로그래밍에서는 싱글톤 패턴이 위의 코드와 같이 구현된다. 기본 생성자를 private으로 지정해서 클래스 외부에서 호출하지 못하게 하여서 클래스 외부에서는 오브젝트를 생성할 수 없게 한다. 그리고 Instance라는 프로퍼티를 호출했을 때, Instance 프로퍼티가 비어있다면 처음 한 번 생성하고 그 뒤로를 생성한 Instance를 호출하는 식으로 구현한다.

 

위와 같은 방법으로 오브젝트를 하나만 생성가능하게 만드는 것이 바로 싱글턴 패턴이다. 싱글톤 패턴은 예를들어 센서와 통신하는 클래스처럼 단 하나만 존재해야하는 경우에 사용된다.

 

 

유니티 엔진에서의 싱글톤 패턴

 

유니티 엔진에서도 싱글톤 패턴을 응용해서 사용할 수 있다. 일단 유니티 엔진에서 일반 C# 클래스로 싱글톤 패턴을 사용한다면 위의 코드 예시와 똑같이 구현하면 된다. 하지만 모노비헤이비어를 상속받는 컴포넌트 클래스에서의 싱글톤 패턴 구현은 조금 달라진다.

 

우선 모노비헤이비어를 상속받는 컴포넌트 클래스와 일반 C# 클래스의 차이점을 알아둬야 한다.

 

var newSingleton = new GameObject("Singleton Class");

newSingleton.AddComponent<SingletonClass>();

 

우선, new 연산자를 통해서 생성되는 일반 C# 클래스의 오브젝트와 달리 컴포넌트 클래스의 오브젝트를 생성할 때는 게임 오브젝트를 생성한다음에 AddComponent() 함수를 이용해서 게임 오브젝트에 컴포넌트를 붙여줘야 한다. 그렇기 때문에 일반 C# 클래스와 같이 생성자를 private으로 바꾸는 방법으로는 클래스 외부에서의 오브젝트의 생성을 막을 수 없다.

 

뿐만 아니라, 이미 컴포넌트가 부착된 오브젝트가 씬에 만들어져 있는 경우도 있을 수 있고, 컴포넌트가 부착된 프리팹이 생성되는 경우까지 있다. 그렇기 때문에 일반 C# 클래스와 같이 생성자로 외부 생성을 막는 방법은 전혀 유효하지 않다.

 

public class SingletonClass : MonoBehaviour

{

    private static SingletonClass instance;

    public static SingletonClass Instance

    {

        get

        {

            if (instance == null)

            {

                var obj = FindObjectOfType<SingletonClass>();

                if (obj != null)

                {

                    instance = obj;

                }

                else

                {

                    var newSingleton = new GameObject("Singleton Class").AddComponent<SingletonClass>();

                    instance = newSingleton;

                }

            }

            return instance;

        }

        private set

        {

            instance = value;

        }

    }

 

    private void Awake()

    {

        var objs = FindObjectsOfType<SingletonClass>();

        if (objs.Length != 1)

        {

            Destroy(gameObject);

            return;

        }

        DontDestroyOnLoad(gameObject);

    }

}

 

컴포넌트 클래스의 특성에 유의해서 싱글톤 패턴을 작성하면 위의 예시 코드와 같이 구현된다.

 

우선, 일반 C# 클래스에서와 같이 instance가 null인지 검사를 하되 곧바로 오브젝트를 생성하지 말고 씬에 이미 게임 오브젝트에 부착된 싱글톤 클래스가 있는지 검사하고 만약, 이미 있다면 instance에 넣어준 뒤, instance를 반환한다. 씬에 존재하지 않는 상태라면, 싱글톤 클래스의 오브젝트가 컴포넌트로 부착된 게임 오브젝트를 생성하고 이를 반환한다.

 

그리고 게임 오브젝트가 생성되거나 컴포넌트가 게임 오브젝트에 부착되었을 때 가장 먼저 실행되는 Awake() 함수에서 씬에 이미 존재하는 같은 싱글톤 오브젝트가 있는지 검사를 한 뒤, 이미 존재하는 오브젝트가 있다면 지금 생성된 오브젝트를 파괴하는 작업을 진행한다.

 

마지막으로 씬을 전환할때 싱글톤 패턴이 적용된 오브젝트가 파괴되는 것을 막기 위해 DontDestroyOnLoad()를 적용해준다. 보통 유니티에서 싱글톤 패턴을 적용하는 경우는 시스템 매니저와 같이 시스템 전체를 관리하는 오브젝트처럼 여러 씬에서 살아있어야 하는 오브젝트가 대부분이다.

 

 

싱글톤 패턴의 장점과 유용성

 

public class SingletonTester : MonoBehaviour

{

    void Start()

    {

        var singleton = SingletonClass.Instance;

        singleton.Function();

    }

}

 

싱글톤 패턴의 장점과 유용성은 게임이나 프로그램에 단 하나만 존재해야하는 클래스를 다룰 때 나타난다. 단 하나만 존재해야 하는 클래스의 생성을 적절하게 통제할 수 있으며, 전체에 단 하나이기 때문에 따로 매번 오브젝트를 생성하거나 탐색해서 찾을 필요없이 instance 프로퍼티를 통해서 빠르게 접근할 수 있다.

 

 

싱글톤 패턴의 단점과 해악

 

싱글톤 패턴의 장점은 프로그램에 단 하나여야 하는 클래스의 오브젝트를 다루는데 뛰어난 효율성을 보여준다는 것인데, 어떤 개발자들은 빠르고 편하게 사용할 수 있다는 점에 주목한다. 그리고 싱글톤 패턴의 모든 단점과 해악성은 여기에서 나온다.

 

오브젝트를 따로 탐색하거나 생성할 필요가 없이 바로 instance 호출로 바로 가져올 수 있다는 점에 취해서 쉽게 남용되는 패턴이 바로 싱글톤 패턴이다. 이 때문에 조금이라도 가져오기 어려운 오브젝트가 있으면 싱글톤 패턴으로 만들어버리거나, 기능을 싱글톤 패턴을 가진 오브젝트에 합쳐버리는 우를 범하게 된다.

 

일이 이렇게 진행되기 시작하면, 여기저기서 각기 다른 클래스의 instance를 호출하는 구조로 코드의 흐름이 알기 어려워지고 싱글톤 패턴을 가진 오브젝트의 코드 크기는 점점 거대해져서 기형적으로 비대한 구조가 만들어지며, 나중에는 따로 분리해내거나 정리하기 어려워진다.

 

때문에 싱글톤 패턴을 사용할 때는 편리함에 너무 취하지 않아야 하며, 해당 클래스가 너무 비대화되지 않는지, 처리하기로 설계한 기능 이외의 것을 처리하려고 하고 있지 않은지를 끊임없이 경계해야 한다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

DontDestroyOnLoad 

파괴하지 않을 게임 오브젝트 만들기

 

작성 기준 버전 :: 2019.1.4f1

 

[이 포스트의 내용을 유튜브 영상으로도 시청하실 수 있습니다]

 

유니티에서는 씬(Scene, 장면) 단위로 게임이 플레이될 공간이나 장소 등을 구현하며, 한 씬에서 다른 씬으로 넘어갈 때는, 기존 씬이 언로드되면서 기존 씬에 있던 게임 오브젝트(Game Object)는 모두 파괴된다. 아래의 예시를 보자.

 

 

위 이미지는 Destroy Object라는 이름의 게임 오브젝트 다섯 개를 배치한 Test Scene의 캡처 화면이다. 플레이가 시작되면 아무것도 없는 Other Scene으로 넘어가게 설계되어 있다.

 

 

설계대로 플레이 버튼을 누르면 Test Scene에서 Other Scene으로 이동하며 씬에 배치되어 있는 다섯 개의 Destroy Object가 사라지는 것을 볼 수 있다.

 

 

 

DontDestoryOnLoad 사용법

 

위의 예시에서 볼 수 있듯이 유니티에서는 새로운 씬을 불러오면 이전 씬에 남아있던 게임 오브젝트들은 모두 사라진다. 하지만 개발자의 의도나 설계에 따라서 몇몇 게임 오브젝트들은 다른 씬으로 넘어갈 때 파괴되지 않도록 할 필요성이 생길 수도 있다.

 

public class DontDestoryObject : MonoBehaviour
{
    private void Awake()
    {
        DontDestroyOnLoad(gameObject);
    }
}

 

씬 로드시 파괴하지 않을 오브젝트로 만들려면 바로 위의 코드처럼 DontDestroyOnLoad()함수를 호출해서 매개변수로 자신의 게임 오브젝트를 전달하면 된다.

 

   

이렇게 파괴하지 않을 오브젝트로 만들어진 게임 오브젝트는 게임 플레이가 시작되고 함수가 호출되면 위의 이미지처럼 Test Scene에서 DontDestroyOnLoad 영역으로 옮겨진다.

 

 

만들어진 스크립트를 Dont Destroy Game Object라는 이름의 구체 게임 오브젝트에 붙인 다음 플레이 버튼을 눌러보면 Test Scene에서 Other Scene으로 넘어가면서 Destroy Object는 전부 사라지지만 Dont Destory Game Object는 남아있는 것을 확인할 수 있다.

 

 

Don't Destroy On Load 게임 오브젝트 파괴하기

 

Don't Destory On Load로 설정된 게임 오브젝트는 그럼 파괴할 수 없는 것인가? 라고 생각할 수도 있다. 하지만 제일 앞의 단어가 Can't가 아니라 Don't 임을 명심하자. 파괴할 수 없는 것이 아니라 파괴하지 않는 것이다.

 

Destroy(gameObject);

 

Don't Destory On Load로 설정된 게임 오브젝트는 Destroy() 함수를 이용하면 손쉽게 다시 파괴할 수 있다.

 

 

설계시 주의 사항

 

Don't Destory On Load 게임 오브젝트를 사용한 설계를 할 때는 주의할 점이 있다.

 

 

위의 이미지와 같이 Don't Destory On Load가 적용된 게임 오브젝트가 들어있는 씬과 다른 씬을 여러 번 왔다 갔다 하는 방식의 구조를 생각해보자. 

 

Test Scene에 진입하면 파괴하지 않는 게임 오브젝트가 Don't Destroy On Load 영역으로 이동되면서 씬을 이동해도 파괴되지 않게 될 것이다. 그 다음 Other Scene으로 이동했다가 다시 Test Scene으로 돌아오면 어떻게 될까? 파괴하지 않는 게임 오브젝트는 Don't Destroy On Load 영역으로 이동했으니 더 이상 Test Scene에 남아있지 않을까?

 

아니다. 씬을 새로 불러올 때는 해당 씬의 초기 상태로 불러오기 때문에 파괴하지 않는 게임 오브젝트는 새 것이 생성되어 있으며 이것은 그대로 다시 Don't Destroy On Load 영역으로 옮겨진다. 즉, 똑같은 파괴하지 않는 게임 오브젝트가 2개가 되어버리는 것이다.

 

 

그대로 두 씬을 왕복하면 같은 게임 오브젝트가 계속해서 누적되는 문제가 발생한다. 이 문제는 그대로 불필요한 메모리 사용을 증가시킬 것이고, 뿐만 아니라 예측할 수 없는 작동 문제 역시 일으킬 수 있다. 

 

 

해결 방법 1 : 중복 검사 후 파괴하기

 

public class DontDestoryObject : MonoBehaviour
{
    private void Awake()
    {
        var obj = FindObjectsOfType<DontDestoryObject>();
        if (obj.Length == 1)
        {
            DontDestroyOnLoad(gameObject);
        }
        else
        {
            Destroy(gameObject);
        }
    }
}

 

해당 게임 오브젝트를 파괴하지 않는 게임 오브젝트로 설정하는 이유가 게임 내에서 단 하나만 존재하는 오브젝트를 만들고자 하는 경우라면 가장 간단하고 손쉬운 해결 방법으로는 게임 오브젝트가 생성된 직후에 실행되는 Awake() 함수에서 현재 씬에 있는 같은 오브젝트가 몇 개인지 검사를 한 뒤 2개 이상이라면 생성된 오브젝트를 파괴하는 방법이다.

 

 

 

해결 방법 2 : 초기화 씬 구현하기

 

 

두 번째 방법으로는 파괴하지 않는 게임 오브젝트를 생성하는 전용 씬을 만들고 그 씬에서 파괴하지 않는 게임 오브젝트를 생성한 뒤 다른 씬만 오가는 방식이다. 이 방법 게임 내에서 단 하나만 존재하는 오브젝트를 만들고자 하는 경우에 유용한 방법이다.

 

 

해결 방법 3 : 라이프 사이클 관리하기

 

파괴하지 않는 게임 오브젝트는 위의 예시에서 볼 수 있듯이 씬을 이동한다고 해서 파괴되지 않는다. 그렇기 때문에 제대로 관리하지 않는다면 생성되는 족족 Don't Destroy On Load 영역에 게임 오브젝트가 쌓일 것이다. 

 

위의 두 가지 방법은 게임 내에서 해당 오브젝트를 단 하나만 만들고자 하는 경우에 유용한 방법이다. 만약 해당 오브젝트가 파괴되지 않아야 하지만 여러 개의 오브젝트를 만드는 것을 허용하는 구조라면 위의 방법들은 적절하지 않다.

 

또한 앞선 예시들은 모두 파괴하지 않을 게임 오브젝트를 정적으로 씬에 배치를 해서 씬이 불러와질 때마다 생성되게 만드는 구조를 채용했다. 하지만 이와 반대로 같은 종류의 파괴되지 않을 게임 오브젝트를 여러 개 생성하는 것을 허용하려고 한다면 정적인 씬 배치 방식보다는 프리팹으로 관리하며 원하는 시점에 생성하는 것이 좋다.

 

 

이러한 경우에 더욱 주의해야할 점은 파괴하지 않는 게임 오브젝트들이 동적으로 여러 개 생성되어 작동하고 있으며, 이후에도 계속해서 생성된다는 점이다. 때문에 파괴하지 않은 게임 오브젝트들에 대해서 제대로 추적/관리해야 하며 해당 오브젝트의 사용이 끝나면 파괴하는 식으로 라이프 사이클(Life Cycle) 관리가 필요하다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Scriptable Object 

스크립터블 오브젝트(Scriptable Object) 기본 사용법

 

작성 기준 버전 :: 2019.1 ~ 2019.2

 

[이 포스트의 내용은 유튜브 영상으로도 시청하실 수 있습니다]

 

스크립터블 오브젝트(Scriptable Object)는 유니티에서 제공하는 대량의 데이터를 저장하는 데 사용할 수 있는 데이터 컨테이너이다. 스크립터블 오브젝트를 사용하면 값의 사본이 생성되는 것을 방지하여 프로젝트의 메모리 사용을 줄일 수 있으며 이것은 모노비헤이비어(MonoBehaviour) 스크립트에 변경되지 않는 데이터를 저장하는 프리팹을 사용하는 프로젝트에서 유용하다고 한다. 변경되지 않는 데이터를 사용하는 프리팹의 데이터를 일반 변수로 구현할 경우 인스턴스화 할때마다 프리펩에 이 데이터에 대한 자체 사본이 생성되는데, 스크립터블 오브젝트를 사용하면 메모리에 스크립터블 오브젝트의 데이터 사본만을 저장하고 이를 참조하는 방식으로 작동한다고 한다.

 

스크립터블 오브젝트 클래스는 유니티에서 기본적으로 제공하는 것으로 모노비헤이비어 클래스와 마찬가지로 기본 유니티 오브젝트(Unity Object)에서 파생되지만, 모노비헤이비어와 달리, 게임 오브젝트에 컴포넌트로 부착할 수 없고, 프로젝트에 에셋으로 저장된다.

 

 

스크립터블 오브젝트 만들기

 

스크립터블 오브젝트를 만들기 위해서는 ScriptableObject 클래스를 상속받아서 아래의 코드와 같이 구현하면 된다.

 

using UnityEngine;

[CreateAssetMenu(fileName = "Zombie Data", menuName = "Scriptable Object/Zombie Data", order = int.MaxValue)]
public class ZombieData : ScriptableObject
{
    [SerializeField]
    private string zombieName;
    public string ZombieName { get { return zombieName; } }
    [SerializeField]
    private int hp;
    public int Hp { get { return hp; } }
    [SerializeField]
    private int damage;
    public int Damage { get { return damage; } }
    [SerializeField]
    private float sightRange;
    public float SightRange { get { return sightRange; } }
    [SerializeField]
    private float moveSpeed;
    public float MoveSpeed { get { return moveSpeed; } }
}

 

CreateAssetMenu 속성은 스크립터블 오브젝트 스크립트를 이용해서 빠르고 쉽게 에셋을 생성할 수 있게 만들어주는 속성이다.

 

 

 

코드를 빌드하고 에디터로 돌아가서 Assets 메뉴를 보면 추가한 menuName 대로 Create>Scriptable Object>Zombie Data 항목이 새로 생긴 것을 볼 수 있다.

 

 

그리고 그 항목을 선택하면 Zombie Data의 스크립터블 오브젝트가 생성된다.

 

 

생성된 Zombie Data 스크립터블 오브젝트를 선택해보면 위의 이미지와 같이 좀비의 정보에 대한 프로퍼티들이 보인다.

 

 

 

 

 

스크립터블 오브젝트 사용하기

 

앞에서 스크립터블 오브젝트를 생성하는 방법을 배웠으니 이번에는 스크립터블 오브젝트를 사용하는 방법에 대해서 알아보자.

 

 이름 체력 데미지 시야  이동속도 
일반 좀비(Normal Zombie) 10  10 
스피드 좀비(Speed Zombie) 10 
파워 좀비(Power Zombie)  10  10 
탱커 좀비(Tank Zombie)  20  10  1.5 
센서 좀비(Sensor Zombie) 20 

 

위의 표와 같이 다섯 종류의 좀비 데이터를 담을 스크립터블 오브젝트를 만들어보자.

 

 

우선 제일 처음 만든 스크립터블 오브젝트를 복사해서 다섯 개로 만든다.

 

 

각 파일의 이름을 좀비 종류에 맞게 바꿔준다.

 

 

 

그리고 표의 내용에 맞게 각 스크립터블 오브젝트에 데이터를 입력해준다.

 

public class Zombie : MonoBehaviour
{
    [SerializeField]
    private ZombieData zombieData;
    public ZombieData ZombieData { set { zombieData = value; } }

    public void WatchZombieInfo()
    {
        Debug.Log("좀비 이름 :: " + zombieData.ZombieName);
        Debug.Log("좀비 체력 :: " + zombieData.Hp);
        Debug.Log("좀비 공격력 :: " + zombieData.Damage);
        Debug.Log("좀비 시야 :: " + zombieData.SightRange);
        Debug.Log("좀비 이동속도 :: " + zombieData.MoveSpeed);
    }
}

 

그 다음에는 좀비 데이터를 사용할 좀비 클래스를 작성하고

 

 

좀비 클래스를 사용하는 프리팹을 만들어준다.

 

public enum ZombieType 
{
    Normal, Power, Sensor, Speed, Tank 
}

public class ZombieSpawner : MonoBehaviour
{
    [SerializeField]
    private List<ZombieData> zombieDatas;
    [SerializeField]
    private GameObject zombiePrefab;
    
    void Start()
    {
        for (int i = 0; i < zombieDatas.Count; i++)
        {
            var zombie = SpawnZombie((ZombieType)i);
            zombie.WatchZombieInfo();
        }
    }
    
    public Zombie SpawnZombie(ZombieType type)
    {
        var newZombie = Instantiate(zombiePrefab).GetComponent<Zombie>();
        newZombie.ZombieData = zombieDatas[(int)type];
        return newZombie;
    }
}

 

좀비를 소환하는 좀비 스포너 클래스를 만들고 Start() 함수에는 테스트용 코드를 작성한다.

 

 

코드를 작성한 뒤에는 씬에 좀비 스포너 게임 오브젝트를 만들고 거기에 Zombie Spawner 컴포넌트를 붙인 뒤에, 앞에서 만든 좀비 데이터 스크립터블 오브젝트와 좀비 프리팹을 프로퍼티에 넣어준다.

 

 

모든 세팅을 마친 다음에 플레이 버튼을 눌러서 실행해보면 좀비 게임 오브젝트가 생성되고, 콘솔 창에서는 각 좀비의 정보가 출력되는 것을 볼 수 있다.

 

 

그리고 하이어라키 뷰에서 각 좀비의 게임 오브젝트를 살펴보면 각 Zombie 컴포넌트의 Zombie Data 프로퍼티에는 서로 다른 좀비 데이터 스크립터블 오브젝트들이 참조되고 있는 것을 확인할 수 있다.

 

 

 

 

 

스크립터블 오브젝트의 특징 및 응용

 

에디터에서는 스크립터블 오브젝트에 데이터를 저장하는 작업이 언제나 가능하지만, 배포된 빌드에서는 데이터를 저장할 수 없고 개발시 설정한 스크립터블 오브젝트 에셋에 저장된 데이터만을 사용할 수 있다.

 

 

그리고 스크립터블 오브젝트는 에셋 파일 형태로 관리되기 때문에 에셋번들 태그를 이용해서 에셋 번들로 빌드하고 배포하는 방식으로 게임 데이터를 업데이트시키는데 사용할 수도 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming

일반 C# 클래스와 게임 오브젝트의 컴포넌트로써의 클래스

 

유니티로 게임을 개발할 때, 게임 씬에 배치되며 하이어라키 뷰에 존재하는 객체를 게임 오브젝트(Game Object)라고 하는데, 이 게임 오브젝트에 부착되는 컴포넌트를 컴포넌트 클래스라고 하고, 게임 오브젝트에 컴포넌트로 부착되지 않고 메모리 상에만 있는, 코드 상에서만 다루어질 클래스를 일반 C# 클래스라고 하자.

 

왜 이런 복잡한 분류가 있어야 되느냐 싶겠지만, 게임을 개발하다고 보면 유니티에서 기본적으로 제공하는 모노비헤이비어를 상속받는 게임 오브젝트에 컴포넌트로 부착될 클래스 이외의 일반적인 C# 클래스 역시 필요한 시점이 반드시 온다.

 

 

컴포넌트 클래스(Component Class)

 

public class ComponentClass : MonoBehaviour

{

    // Start is called before the first frame update

    void Start()

    {

        

    }

 

    // Update is called once per frame

    void Update()

    {

        

    }

}

 

유니티 엔진에서 C# 스크립트를 생성하면 생성된 클래스를 기본적으로 모노비헤이비어(MonoBehaviour) 클래스를 상속받으며 위의 예시 코드와 같이 기본적으로 Start() 함수와 Update() 함수가 만들어진 채로 스크립트가 생성된다.

 

 

이렇게 모노비헤이비어 클래스를 상속받는 클래스는 위의 이미지처럼 인스펙터 뷰에서 Add Component 버튼을 통해서 게임 오브젝트에 부착될 수 있으며, 모노비헤이비어 클래스에서 상속받는 다양한 프로퍼티와 함수를 활용할 수 있다. 그리고 게임 오브젝트가 생성될 때는 Start() 함수, 게임 오브젝트가 업데이트되는 동안에는 Update() 함수, 소멸될 때는 OnDestroy() 함수 등 다양한 상황에서 호출되는 콜백 함수 역시 제공받는다.

 

 

일반 C# 클래스

 

public class CSharpClass

{

 

}

 

일반 C# 클래스는 모노비헤이비어 클래스를 상속받지 않으며, 게임 오브젝트에 컴포넌트로 부착되지 않는 코드 내에서만 동작하는 클래스를 만들고자 할 때 사용된다. 모노비헤이비어 클래스로부터 상속받는 프로퍼티와 함수들을 사용하지는 못하지만, 컴포넌트로 부착될 필요가 없거나 씬에 배치될 필요가 없는 오브젝트 일 때 사용된다.

 

 

일반 C# 클래스는 인스펙터 창의 Add Component 버튼에서 검색해도 게임 오브젝트에 부착할 수 없게 표시되지 않는다.

 

 

일반 C# 클래스를 다룰 때 실수할 수 있는 부분

 

public class CSharpClass : MonoBehaviour

{

    // Start is called before the first frame update

    void Start()

    {

        

    }

 

    // Update is called once per frame

    void Update()

    {

        

    }

}

 

그런데 유니티 에디터에서 .cs파일을 처음 생성하면 위와 같이 코드가 생성된다. 일반적으로 유니티에 입문한지 얼마 되지 않은 개발자들은 이때 생성한 클래스의 모노비헤이비어(MonoBehaviour) 클래스 상속을 그대로 두고 사용한다.

 

이 클래스가 컴포넌트 클래스라면 상관없는 문제지만, 일반 C# 클래스라면 문제가 발생할 수 있다. 우선은 모노비헤이비어 클래스를 상속받음으로써 불필요한 프로퍼티가 생성되는 점이 첫 번째 문제이고, 두 번째 문제는 일반 C# 클래스로써 설계해놓고 게임 오브젝트와 혼용해서 사용하려는 시도가 발생할 수 있다는 점이다.

 

예시로 코드 내에서 CSharpClass에 모노비헤이비어 클래스를 상속시키고 일반 C# 클래스에서도 실행가능한 기능과 컴포넌트 클래스로서 게임 오브젝트에 부착되었을 때만 가능한 기능을 섞어둔 코드를 아래와 같이 작성해보겠다.

 

public class CSharpClass 

MonoBehaviour

{

    public int i = 10;
    void Start()
    {
        Debug.Log("CSharpClass :: Start()");
      }

 

    void Update()

    {

       Debug.Log("CSharpClass :: Update()");
    }

    public void SomeFunction1()
    {
        Debug.Log(string.Format("CSharpClass :: Function1({0})", i));

    }


    public void SomeFunction2()
    {
        Debug.Log(string.Format("CSharpClass :: Function2()"));
        StartCoroutine(SomeCoroutine());
    }

    public IEnumerator SomeCoroutine()
    {
      yield return null;
        Debug.Log("CSharpClass :: SomeCoroutine()");
    }

}

 

이런 CSharpClass를 컴포넌트가 아닌 일반 C# 오브젝트처럼 사용하려고 하면 생성해서 사용하려고 시도할 것이고 아직 유니티에서의 스크립팅 작업에 익숙하지 않은 개발자라면 일반 C#과 모노비헤이비어에서 상속받는 기능을 혼용해서 사용하려고 시도할 수 있다. 마치 아래의 코드 예시와 같이 :

 

public class ComponentClass : MonoBehaviour

{

    void Start()

    {

        StartCoroutine(CreateCSharpClassObject());

    }

 

   private IEnumerator CreateCSharpClassObject()

    {

       var some = new CSharpClass();

        Debug.Log(some);

        some.SomeFunction1();

        yield return StartCoroutine(some.SomeCoroutine());

        some.SomeFunction2();

    }

}

 

ComponentClass는 게임 오브젝트에 부착될 컴포넌트 클래스이며, 일반적인 C#의 오브젝트 생성 방식을 통해서 CSharpClass를 생성하고 멤버 함수들을 호출하는 역할을 한다. SomeCoroutine()의 호출순서를 보장하기 위해서 코루틴 함수를 통해서 호출했다.

 

 

 

모노비헤이비어를 상속받은 CSharpClass를 기존 C# 방식으로 생성한 뒤 호출 테스트를 하기위해서 씬에 빈 게임 오브젝트를 ComponentClass 컴포넌트를 부착하고  플레이 버튼을 눌러보자.

 

 

  

그러면 위와 같은 로그를 얻을 수 있는데, 위 로그를 통해서 확인할 수 있는 사실은 다음과 같다.

 

1. 게임 오브젝트가 시작될 때, 실행되어야 하는 Start() 함수와 게임 오브젝트가 존재하는 동안 호출되어야할 Update() 함수가 호출되지 않는다.

2. Debug.Log(some)은 null이라고 표시된다. 즉, 오브젝트가 null reference 상태이다.

3. 하지만 SomeClass의 멤버함수인 SomeFunction1() 함수는 정상적으로 호출되었고 멤버변수 i의 값도 정상적으로 출력되었다. 즉, 오브젝트 자체는 생성되었다.

4. ComponentClass의 게임 오브젝트가 매개체가 되어 호출한 코루틴은 정상으로 동작했다.

5. CSharpClass의 게임 오브젝트가 매개체가 되어 호출한 코루틴은 null reference가 발생하며 동작에 실패했다.

 

이를 통해서 알 수 있는 사실은 C# 방식으로 모노비헤이비어를 상속받은 클래스를 생성하면 오브젝트는 생성되지만, 게임 오브젝트는 생성되지 않는다는 것이다. 그렇기 때문에 모노비헤이비어에서 상속받아오는 Start() 함수, Update() 함수, StartCoroutine() 함수의 호출에 실패하는 것이다. 이런 문제가 발생하는 것을 막기 위해서 일반 C# 클래스로 설계된 클래스의 .cs 파일을 유니티 엔진에서 생성하면 반드시 모노비헤이비어 클래스 상속을 제거해주어야만 한다.

 

 

 

 

컴포넌트 클래스와 일반 C# 클래스의 생성

 

위의 예시를 통해서 알 수 있는 점은 일반 C# 클래스에서는 모노비헤이비어를 상속받지 말아야 한다는 점과 컴포넌트 클래스와 일반 C# 클래스의 생성방법은 다르다는 것이다. 그렇다면 컴포넌트 클래스와 일반 C# 클래스는 각각 어떻게 생성해주어야 하는가를 알아보자.

 

우선 CSharpClass와 ComponentClass의 코드를 다음과 같이 수정하자.

 

CSharpClass.cs

public class CSharpClass

{

    public int i = 10;

 

    public void SomeFunction1()
    {
        Debug.Log(string.Format("CSharpClass :: Function1({0})", i));

    }

 

    public IEnumerator SomeCoroutine()
    {
      yield return null;
        Debug.Log("CSharpClass :: SomeCoroutine()");
    }

}

ComponentClass.cs

public class ComponentClass : MonoBehaviour

{

    void Start()

    {

        Debug.Log("ComponentClass :: Start");

    }

 

    void Update()

    {

        Debug.Log("ComponentClass :: Update");

    }

}

 

그 다음에는 ObjectGenerator라는 이름으로 클래스를 만들고 다음처럼 코드를 작성한다.

 

public class ObjectGenerator : MonoBehaviour

{

    void Start()

    {

        var gameObj = new GameObject();

        gameObj.AddComponent<ComponentClass>();

 

        var obj = new CSharpClass();

        obj.SomeFunction1();

        StartCoroutine(obj.SomeCoroutine());

    }

}

 

간단한 코드 해설을 덧붙이자면, 게임 오브젝트의 경우 new GameObject()를 호출하면 자동으로 씬에 빈 게임 오브젝트 하나가 배치된다. 그리고 컴포넌트 클래스는 게임 오브젝트의 AddComponent<>() 함수를 호출해서 해당 게임 오브젝트에 컴포넌트로 부착할 수 있다.

 

일반 C# 클래스는 C#에서와 같이 new 연산자를 통해서 오브젝트를 생성할 수 있다. 그리고 일반 C# 클래스의 멤버 함수로 들어가 있는 코루틴 함수의 경우에는 일반 C# 클래스가 스스로 Start Coroutine을 할 수는 없지만, 다른 게임 오브젝트의 Start Coroutine을 통해서는 코루틴을 시작할 수 있다.

 

 

이를 테스트하기 위해서 씬에 게임 오브젝트 하나를 배치하고 Object Generator 컴포넌트를 붙여준다.

 

 

그리고 에디터에서 플레이 버튼을 눌러 실행해보면 New Game Object라는 이름의 게임 오브젝트가 하나 새로 생성되고 ComponenetClass가 컴포넌트로 부착되는 것을 볼 수 있으며

 

 

로그를 통해서는 컴포넌트 클래스의 함수와 일반 C# 클래스의 함수가 정상적으로 동작하는 것을 확인할 수 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

Programming

-

로딩 씬(Loading Scene) 구현하기

(씬 교체 방식)

작성 기준 버전 :: 2019.2.9f1

 

게임의 장르와 배경들의 종류는 많고도 많지만 그 어떤 종류의 게임이던간에 아주 가벼운 게임이 아닌 이상 반드시 등장하는 장면이 있다. 그 장면은 바로 로딩 씬이다. 다들 로딩 씬이 등장하면 언제쯤 지나가려나 하며 로딩 바에 마우스를 올리고 정말로 바가 채워지고 있는지 확인해본 경험이 있을 것이다. 게임을 처음으로 접했던 어린 시절에는 이런 로딩 씬이 왜 필요한지도 몰랐고 그냥 재미있는 게임을 할 시간을 잡아먹는 나쁜 녀석이라는 생각만 가득했다.

 

 

하지만 게임 개발을 시작한 이후로 이 로딩 씬만큼 중요한 씬이 또 없다는 것을 깨달을 수 있었다. 로딩 씬의 역할은 단지 시간만 잡아먹는 것이 아니라 게임의 씬이 전환될 때 다음 씬에서 사용될 리소스들을 물리적인 저장소에서 읽어와서 메모리에 올리는 등의 게임을 하기 위한 준비를 하는 작업이다.

 

만약에 게임에 로딩 장면이 존재하지 않는다면 어떻게 될까? 아마 플레이어는 다음 씬으로 넘어가는 동안 가만히 게임이 멈춘 화면을 보고 있거나 까만 화면을 보고 있어야 한다. 그런 일이 발생한다면 로딩이 얼마나 진행되었는지 알 수 없고 이 게임이 로딩 중인지 정지한 것인지 구분할 수도 없어서 너무 답답할 것이다. 그렇기 때문에 씬이 전환될 때에는 로딩 씬을 만들어서 플레이어에게 로딩이 얼마나 진행되었는지 알려주면서 플레이어가 지루하지 않게 게임 팁이나 게임 스토리등을 보여주는 것이다.

 

 

개념

 

 

로딩 씬을 구현하는 방법에는 여러가지 방법이 있을 수 있지만 이번 섹션에서는 로딩씬을 불러들인 다음에 호출하는 씬을 비동기로 호출하는 방법을 사용한다.

 

 

구현하기

 

앞에서는 로딩 씬의 필요성에 대해서 이야기했다면 이제는 실제로 로딩씬을 유니티에서 구현하는 방법을 알아보자. 

 

 

코드 작성하기

 

로딩 씬을 불러오고 로딩작업을 진행하는 LoadingSceneManager 클래스를 생성하고 다음의 코드를 작성한다.

 

using System.Collections;

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, op.progress, timer);                if (progressBar.fillAmount >= op.progress)                {                    timer = 0f;                }            }            else            {                progressBar.fillAmount = Mathf.Lerp(progressBar.fillAmount, 1f, timer);
                if (progressBar.fillAmount == 1.0f)                {                    op.allowSceneActivation = true;                    yield break;                }            }

        }

    }

}

 

처음 씬을 불러오는 방법을 배우는 유니티 게임 제작자의 경우에는 대부분 SceneManager.LoadScene()을 사용하지만, 로딩 씬을 만들기 위해서는 SceneManager.LoadSceneAsync()를 사용해야 한다. LoadScene()의 경우에는 동기 방식으로 불러올 씬을 한꺼번에 불러오고 다른 모든 것이 불러오는 동안 기다리는 방식으로 불러오는 도중에는 다른 작업을 할 수 없게 되지만, LoadSceneAsync()의 방식은 비동기 방식으로 씬을 불러오는 도중에도 다른 작업이 가능한 방식이다. 로딩의 진행 정도는 LoadSceneAsync() 함수가 AsyncOperation 클래스 형식으로 반환한다.

 

위의 코드에서는 AsyncOperation 클래스의 객체인 op를 통해서 allowSceneActivation을 false로 설정하는데, 이 옵션은 씬을 비동기로 불러들일 때, 씬의 로딩이 끝나면 자동으로 불러온 씬으로 이동할 것인가를 의미한다. 로딩씬에서 이 값을 false로 설정하는 것은 로딩이 완료되었을 때, 자동으로 다음 씬으로 넘어가지 않고 기다린다는 의미이다. allowSceneActivation이 false일 때는 씬이 로드될 때, 95%까지만 로딩되고 더 이상 불러들이지 않는다. 다시 allowSceneActivation을 true로 변경하면 그 때 마무리 로딩을 완료하고 씬을 불러오게 된다.

 

코드 작성을 완료했다면 코드를 저장하고 에디터로 넘어간다.

 

 

씬 구성

 

그 다음에는 로딩 씬을 구성한다.

 

 

로딩 씬을 위의 이미지와 같이 구성하자. 초록색 막대는 다음 씬이 얼마나 로딩되었는지 알려주는 진행막대(Progress Bar)이다. 배경은 아무런 이미지 없이 카메라가 찍고 있는 텅 빈 씬을 보여주고 있지만, 실제의 게임에서는 그 게임의 일러스트나 게임 장면 등을 넣을 수 있고, 덤으로 게임 팁(Tip)이나 게임의 배경이 되는 스토리를 보여줄 수도 있다. 그렇게 하면 엘리베이터에 거울을 달아두면 엘리베이터 탑승자들이 거울을 보느라 엘리베이터가 조금 느려도 속도에 신경을 덜 쓰게 되는 것처럼 로딩을 기다리는 유저들 또한 배경 이미지나 팁, 배경 스토리를 읽으면서 로딩의 지루함을 덜어낼 수 있게 되는 것이다.

 

 

진행막대로 사용되는 이미지의 경우에는 스케일을 조정해서 로딩의 진행도를 표시할 수도 있지만, 만약 위의 이미지처럼 단색의 이미지를 사용하는 것이 아닌 경우에는 이미지가 찌그러져 출력될 것이기 때문에 가능하다면 Image Type을 Filled를 사용할 것을 권장한다. Fill Method를 Horizontal로 설정하면 수평으로 채워지고 Fill Origin를 Left로 하면 이미지가 왼쪽부터 채워진다. 그리고 Fill Amount를 이용해서 로딩이 얼마나 진행되었는지 표시할 수 있다.

 

 

그 다음에는 로딩 씬에 게임 오브젝트를 하나 생성하고 Loading Scene Manager로 이름을 지은다음에 LoadingSceneManager 스크립트를 추가한다. 그리고 하이어라키 뷰에서 Progress Bar 이미지를 Progress Bar 프로퍼티에 끌어다 넣는다.

 

 

 

 

 

테스트 세팅하기

 

 

  

위의 과정을 모두 마쳤다면 두 개의 씬을 새로 만들고 씬이 전환되었음을 확인하기 위해서 각 씬에 다른 모양의 게임 오브젝트를 추가해준다.

 

using UnityEngine;
public class SceneLoadTester : MonoBehaviour
{
    private void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            LoadingSceneManager.LoadScene("Scene2");
        }
    }
}

 

 

그리고 LoadingSceneManager의 기능을 테스트하기 위해 스페이스바를 누르면 LoadingSceneManager를 호출하는 코드를 작성하고 Scene1에 게임 오브젝트를 생성해서 스크립트를 넣어준다.

 

 

상단의 [File > Build Settings] 메뉴를 선택하거나 [Ctrl + Shift + B]를 눌러서 빌드 세팅 창을 연 다음 만든 씬들을 빌드될 씬 목록에 넣어준다.

 

 

테스트

 

 

그런 후에 첫 번째 씬에서 플레이 버튼을 눌러 게임을 실행하고 스페이스바를 누르면 첫 번째 씬에서 로딩 씬으로 넘어간 후에 아래쪽 로딩 바가 자연스럽게 차오른뒤에 두 번째 씬으로 넘어가는 것을 확인할 수 있다.

 

위의 예시에서는 씬의 로딩 진행도 만을 이용해서 진행 정도를 체크했지만, 유니티에서는 다음 씬에서 사용될 애셋 번들을 불러오는 것 또한 로딩에 포함될 수 있고, 만약 네트워크 게임을 제작한다면 네트워크 동기화 정도도 포함될 수 있다.

 

 

여담으로 일부 게임 제작자의 경우에는 로딩 시간이 너무 짧아서 로딩 시간동안 보여주고자 하는 팁이나 스토리 등이 너무 빠르게 스쳐지나간다고 생각하는 경우에는 일부러 로딩 속도를 늦추거나 페이크 로딩 시간을 넣어서 로딩 시간을 일부러 길게 만드는 경우도 있다.

 


 

이 글은 이전에 작성된 로딩 씬 구현하기 글의 새로운 버전으로 좀 더 따라하기 쉽고 이전에는 생략되었던 중간과정이 추가되었으며 코드가 약간 수정되었다.

 

로딩 씬을 불러온 뒤에 다음 씬을 로딩하는 이 방법 이외에도 로딩 UI 만을 무대 커튼처럼 전면에 깔아두고 다음 씬을 로딩하는 방법도 있다.

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

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 클래스의 경로만 수정하면 모든 코드에 적용이 된다.

 

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

 

[유니티 어필리에이트 프로그램]

아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.

 

에셋스토어

여러분의 작업에 필요한 베스트 에셋을 찾아보세요. 유니티 에셋스토어가 2D, 3D 모델, SDK, 템플릿, 툴 등 여러분의 콘텐츠 제작에 날개를 달아줄 다양한 에셋을 제공합니다.

assetstore.unity.com

 

Easy 2D, 3D, VR, & AR software for cross-platform development of games and mobile apps. - Unity Store

Have a 2D, 3D, VR, or AR project that needs cross-platform functionality? We can help. Take a look at the easy-to-use Unity Plus real-time dev platform!

store.unity.com

 

Create 2D & 3D Experiences With Unity's Game Engine | Unity Pro - Unity Store

Unity Pro software is a real-time 3D platform for teams who want to design cross-platform, 2D, 3D, VR, AR & mobile experiences with a full suite of advanced tools.

store.unity.com

[투네이션]

 

-

 

toon.at

[Patreon]

 

WER's GAME DEVELOP CHANNEL님이 Game making class videos 창작 중 | Patreon

WER's GAME DEVELOP CHANNEL의 후원자가 되어보세요. 아티스트와 크리에이터를 위한 세계 최대의 멤버십 플랫폼에서 멤버십 전용 콘텐츠와 체험을 즐길 수 있습니다.

www.patreon.com

[디스코드 채널]

 

Join the 베르의 게임 개발 채널 Discord Server!

Check out the 베르의 게임 개발 채널 community on Discord - hang out with 399 other members and enjoy free voice and text chat.

discord.com

 

반응형

+ Recent posts