본문 바로가기

포트폴리오/졸업작품

[MetaEdu] 포톤을 사용한 로딩 창 만들기!

서론

본 프로젝트에서는 "캠퍼스", "교실", "골든벨" 등 다른 씬을 로드할 때, 각 씬의 중복되는 기능을 모아둔 Petty란 이름의 씬을 Additive로 불러오도록 설계하였다.

필수적인 카메라나 캐릭터 생성 등을 수행하는데 시간이 필요하기 때문에 로딩창을 만들기로 했다.


본론

로딩창은 따로 씬으로 빼주지 않고, 싱글톤 패턴 및 DontDestroy를 Canvas(Loading)가 포함된 오브젝트에 적용하여 구현하였다.

public class RoomChangeManager : MonoBehaviourPunCallbacks
{
    /* Singleton */
    private static RoomChangeManager _instance;

    public static RoomChangeManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new RoomChangeManager();
            }
            return _instance;
        }
    }
    
    void Awake()
    {
        if (_instance == null)
        {
            _instance = this;
            DontDestroyOnLoad(this.gameObject);
        }
        else
        {
            Destroy(this);
        }
    }
}

부가적인 코드

더보기

다음으로는 방에서 방으로 이동하는 경우, 로비로 돌아가는 과정을 주었다.

public void RoomOut(string roomName, int maxPlayer, int type)
{
    PhotonNetwork.LeaveRoom();
    StartCoroutine(LoadRoom(roomName, true, maxPlayer, type));
}

public void RoomChange(string roomName) => StartCoroutine(LoadRoom(roomName, false, 20, 0));

이제 로딩창인 Canvas를 활성화 시켜주고, 종류(type)에 맞는 방에 접속한다.

이 때, 종류에 따른 이미지를 넣어주고, 간단한 팁들을 적은 문자열을 랜덤으로 배치함으로써 이쁘게 만들고자 하였다.

교실은 선택해서 들어가도록 구현하였고, 나머지 씬들은 방의 CustomRoomProperties를 통해 랜덤으로 방에 접속하도록 하였다. 로비에서 CustomRoomProperties를 사용하여 방에 들어가려면 CustomRoomPropertiesForLobby를 적용시켜 주어야 한다.

첫 사람과 두 번째 사람이 거의 동시에 접속했을 경우에도 방을 찾게 하기 위해 Invoke 함수를 사용하여 약간의 지연을 주었다. 만약 JoinRandomRoom에 실패하면, CustomRoomProperties 를 사용하여 방을 만들도록 하였다.

로딩창이 켜졌을 때, 방에 접속했을 때와 같이 커다란 흐름이 지날 때 마다 로딩바를 약간씩 이동시켜 주었다.

IEnumerator LoadRoom(string roomName, bool isJoin, int maxPlayer, int type)
    {
        transform.GetChild(0).gameObject.SetActive(true);
        loadingImage.sprite = type.Equals(0) ? loadingImages[0] : type.Equals(2) ? loadingImages[1] : type.Equals(3) ? loadingImages[2] : roomName.Split("#")[1].Equals("3_1.ClassRoom") ? loadingImages[3] : loadingImages[4];
        loadingText.text = loadingTexts[Random.Range(0, loadingTexts.Length)];
        loadingBar.fillAmount = 0.1f;
        while (true)
        {
            if (PhotonNetwork.InLobby)
            {                
                if (type.Equals(1)) // ClassRoom
                {
                    PhotonNetwork.JoinOrCreateRoom(roomName, new RoomOptions { MaxPlayers = (byte)maxPlayer }, null);
                }
                else // Others
                {
                    _type = type;
                    Invoke(nameof(RandomRoom), 2f * Time.deltaTime);
                }

                yield break;
            }
            yield return null;
        }
    }
    
    private void RandomRoom()
    {
        Hashtable expectedCustomRoomProperties = new Hashtable() { { "type", _type } };
        PhotonNetwork.JoinRandomRoom(expectedCustomRoomProperties, (byte)(_type.Equals(3) ? 4 : 20));
    } 

    public override void OnJoinedRoom()
    {
        loadingBar.fillAmount = 0.2f;
        StartCoroutine(LevelRoom());
    }

    public override void OnJoinRandomFailed(short returnCode, string message)
    {
        CreateRoomWithConditions();
    }

    private void CreateRoomWithConditions()
    {
        string roomName = (_type.Equals(0) ? "#2.Campus" : _type.Equals(2) ? "#4.Battle" : "#5.Goldenball") + "#" + UtilClass.GenerateToken(10);
        Hashtable customRoomProperties = new Hashtable() { { "type", _type } };

        RoomOptions roomOptions = new RoomOptions();
        roomOptions.CustomRoomProperties = customRoomProperties;
        roomOptions.MaxPlayers = (byte)(_type.Equals(3) ? 4 : 20);
        roomOptions.CustomRoomPropertiesForLobby = new string[] { "type" };
        PhotonNetwork.CreateRoom(roomName, roomOptions);
    }

 

핵심코드

더보기

이제 방에 접속하였으니 씬을 불러오고, 씬이 어느정도 로딩되었는지 알 수 있는 LevelLoadingProgress를 사용하여 로딩창을 만들어주었다.

IEnumerator LevelRoom()
{
    PhotonNetwork.LoadLevel(PhotonNetwork.CurrentRoom.Name.Split("#")[1]);
    while (PhotonNetwork.LevelLoadingProgress < 1)
    {
        loadingBar.fillAmount = 0.2f + PhotonNetwork.LevelLoadingProgress * 0.7f;
        if (PhotonNetwork.LevelLoadingProgress == 0.9f)
        {
            StartCoroutine(LoadPetty());
        }
        yield return null;
    }
}

로딩이 완료되면 Petty 씬을 불러오고, Petty씬이 불려오는 약간의 텀 후에 로딩 Canvas를 꺼주었다.

IEnumerator LoadPetty()
{
    if (!Singleton.Inst.isPatty)
    {
        Singleton.Inst.isPatty = true;
        SceneManager.LoadScene("Petty", LoadSceneMode.Additive);
        float timer = 0f;
        while (true)
        {
            yield return null;
            timer += Time.unscaledDeltaTime * 0.3f;
            loadingBar.fillAmount = Mathf.Lerp(0.9f, 1f, timer);
            if (loadingBar.fillAmount == 1f)
            {
                transform.GetChild(0).gameObject.SetActive(false);
                yield break;
            }
        }
    }
}

 

실패한 코드

더보기

원래는 이 Petty 씬을 불러오는 것도 LoadSceneAsync 및 allowSceneActivation 을 사용하여 구현하였었지만, 패티씬이 두 번 생성되는 등의 오류가 있어 제외하고 개발하였다..

IEnumerator LoadPetty()
{
    if (!Singleton.Inst.isPatty)
    {
        Singleton.Inst.isPatty = true;
        SceneManager.LoadScene("Petty", LoadSceneMode.Additive);
        transform.GetChild(0).gameObject.SetActive(false);
        yield return null;
        AsyncOperation op = SceneManager.LoadSceneAsync("Petty", LoadSceneMode.Additive);
        op.allowSceneActivation = true;

        float timer = 0f;
        while (true)
        {
            yield return null;
            if (op.progress < 0.85f)
            {
                loadingBar.fillAmount = 0.6f + (op.progress * 0.3f);
            }
            else
            {
                op.allowSceneActivation = true;
                listener.enabled = false;
                timer += Time.unscaledDeltaTime;
                loadingBar.fillAmount = Mathf.Lerp(0.9f, 1f, timer);
                if (loadingBar.fillAmount >= 1f)
                {
                    yield return new WaitForSeconds(1f);
                    transform.GetChild(0).gameObject.SetActive(false);
                    yield break;
                }
            }
        }
    }
}

결과물