[Unity Multiplayer] Boss Room 게임 분석 - 1.2 Scene Script 분석

Danna 다나·2022년 8월 16일
0
post-thumbnail

전 편인 1.1 Scenes 디렉토리에서 플레이어가 게임 플레이 시 마주할 장면 순서대로 Scene의 구조를 살펴보았습니다.

이번에는 그 뒤에서 어떤 로직들이 일어나고 있는지 더 자세히 분석하려 합니다.

저번의 방식과 똑같이 시간 순서대로 나열하려면 필요없는 로직부를 너무 많이 설명하게 될 것 같아 이번에는 주요 아키텍쳐나 구현 방식 중심으로 적어보겠습니다.


Scene 관련 로직

Scene 이동

Startup -> MainMenu 이동

전 글에서도 잠깐 언급했듯, 모든 건 Startup Scene에서 시작합니다.

하지만 Startup 씬을 보면 이렇게 로딩 페이지만 있고,
실제로는 어떻게 메인 메뉴 씬으로 넘어가게 되는지 알지 못합니다.

그 해답은 script에 있는데요,
hierarchy를 보면 ApplicationController라는 게 가장 위에 보입니다.

스크립트로 들어가보니 상단부에 summary가 있고, ApplicationController가 어떤 역할을 하고 있는지에 대한 설명이 있습니다.

" An entry point to the application, where we bind all the common dependencies to the root DI scope. "

-> 애플리케이션의 엔트리포인트이며, root DI(Dependency injection; 의존성 주입) scope에 공통적으로 사용되는 모든 객체의 의존성을 정의하는 곳이다.

쉽게 말해, 애플리케이션 실행 주기 전반에 걸쳐 사용될 것들의 초기 세팅을 하는 곳이라 이해했습니다.

그래서 코드 구조를 보면 이벤트 함수인 Awake()에 의존성 주입과 관련된 세팅 코드가 있고, 모든 세팅이 끝난 이후에 불리는 Start()에 비로소 MainMenu를 부르는 코드가 적혀 있습니다.

namespace Unity.Multiplayer.Samples.BossRoom.Shared
{
    /// <summary>
    /// An entry point to the application, where we bind all the common dependencies to the root DI scope.
    /// </summary>
    public class ApplicationController : MonoBehaviour
    {
        private void Awake()
        {
            Application.wantsToQuit += OnWantToQuit;

            DontDestroyOnLoad(gameObject);
            DontDestroyOnLoad(m_UpdateRunner.gameObject);

            var scope = DIScope.RootScope;

            scope.BindInstanceAsSingle(this);
            scope.BindInstanceAsSingle(m_UpdateRunner);
            scope.BindInstanceAsSingle(m_GameNetPortal);
            scope.BindInstanceAsSingle(m_ClientNetPortal);
            scope.BindInstanceAsSingle(m_ServerGameNetPortal);

            // ...

            Application.targetFrameRate = 120;
        }

        private void Start()
        {
            SceneManager.LoadScene("MainMenu"); // MainMenu Scene으로 이동
        }
    }
}

여기서 잠깐 Awake와 Start 이벤트 함수가 불리는 정확한 시점에 관한 유니티 문서를 보고 가겠습니다.

Awake: This function is always called before any Start functions and also just after a prefab is instantiated. (If a GameObject is inactive during start up Awake is not called until it is made active.)

-> Start 함수 실행 전, 프리팹이 초기화 되고 난 즉시 불린다.

Start: Start is called before the first frame update only if the script instance is enabled.

-> 첫 프레임이 업데이트 되기 전에 불린다. 단, 스크립트 인스턴스가 활성화 되었을 때만 불린다.

따라서 의존성 주입 등의 세팅 작업이 Awake에서 끝나면, Start 함수에 정의된 MainMenu를 호출하는 코드가 실행되게 되고, 바로 메인메뉴로 이동하게 되는 것입니다.


팝업 표출

메인메뉴에서 두 번째 버튼(START WITH DIRECT IP)을 누르면 특정 IP를 지정해 게임을 호스트하거나 참여할 수 있는 팝업이 뜹니다.

이 팝업을 띄우는 로직을 살펴보려 합니다.

MainMenu Scene hierarchy를 보면 맨 위에 MainMenuState를 볼 수 있습니다. 상태값에 따라 MainMenu 각 버튼을 비롯한 UI를 변화시키는 로직이 담긴 스크립트입니다.
MainMenuState의 inspector를 보면 ClientMainMenuState.cs 스크립트와 연결이 되어 있는 것을 확인할 수 있습니다.
여기서 다시 'IP로 시작 팝업' 관련 로직은 serializedField로 관리되고 있는 IPUIMediator에 연결된 IPPopup에서 관장하고 있겠네요.
IPPopup은 IPUIMediator.cs라는 스크립트에 연결된 것까지 확인할 수 있습니다.

IPUIMediator.cs에 들어가보면 긴 코드가 보이지만,
우리가 집중해야 할 건 몇 줄 안 됩니다.
중요한 코드만 모아 보겠습니다.

namespace Unity.Multiplayer.Samples.BossRoom.Visual
{
    public class IPUIMediator : MonoBehaviour
    {
        [SerializeField] CanvasGroup m_CanvasGroup; // 팝업 캔버스
        
        public void Show()
        {
            m_CanvasGroup.alpha = 1f;
            m_CanvasGroup.interactable = true;
            m_CanvasGroup.blocksRaycasts = true;

            DisableSignInSpinner();
        }

        public void Hide()
        {
            m_CanvasGroup.alpha = 0f;
            m_CanvasGroup.interactable = false;
            m_CanvasGroup.blocksRaycasts = false;
        }
    }
}

팝업을 어떻게 띄우는지 보려고 했던 것이므로,
팝업을 띄우고 없애는 로직 이외에는 다 지워봤습니다.

상당히 직관적인 코드입니다.
없애고 나타내야 할 때 알파값을 조정하고, 사용자 인터랙션을 끄고 켜고, raycast를 막고 푸는 작업을 합니다.

팝업은 기본적으로 보이는 상태고,
IPUIMediator.cs 스크립트의 Awake()에서 Hide() 되고 있습니다.

namespace Unity.Multiplayer.Samples.BossRoom.Visual
{
    public class IPUIMediator : MonoBehaviour
    {
        void Awake()
        {
            Hide();
        }
    }
}

기본적으로는 Hide 되어 있는 팝업이지만, START WITH DIRECT IP 버튼을 누르면 팝업이 나와야 합니다.

이 로직은 Hierarchy의 IP Start Button 프리팹의 버튼 컴포넌트 > On Click에서 시작점을 찾아볼 수 있습니다.

버튼이 클릭되면 ClientMainMenuState.cs에 정의되어 있는 OnDirectIPClicked라는 메서드를 부르라고 되어 있네요.

따라가보면 이런 코드를 볼 수 있습니다.

namespace Unity.Multiplayer.Samples.BossRoom.Client
{
    public class ClientMainMenuState : GameStateBehaviour
    {
        [SerializeField] LobbyUIMediator m_LobbyUIMediator;
        [SerializeField] IPUIMediator m_IPUIMediator;
            
        public void OnDirectIPClicked()
        {
            m_LobbyUIMediator.Hide();
            m_IPUIMediator.Show();
        }
    }
}

로비 팝업 (맨 위 버튼 누르면 나오는 팝업)이 혹시 있다면 숨기고,
IP 팝업을 나타나게 하라는 간단한 코드입니다.

각각 Hide와 Show를 따라 들어가보면 또 알파값, 유저인터랙션, 래이케스트 값을 조정하는 코드가 있겠죠.

이렇게 팝업을 나타나게 하는 로직을 살펴봤습니다.

이제는 팝업이 사라지는 로직을 살펴보겠습니다. 간단합니다.

만약 우상단의 X 버튼이 클릭된다면 Cancel Button 오브젝트의 Button > On Click에서 Hide() 메서드를 불러줍니다.

그런데 보니 Hide만 불리고 있는 게 아니라
CancelConnectionWindow라는 메서드도 함께 불리고 있습니다.

얘는 뭘까요?

namespace Unity.Multiplayer.Samples.BossRoom.Visual
{
    public class IPUIMediator : MonoBehaviour
    {
        // To be called from the Cancel (X) UI button
        public void CancelConnectingWindow()
        {
            RequestShutdown();
            m_IPConnectionWindow.CancelConnectionWindow();
        }
        
        void RequestShutdown()
        {
            if (m_ConnectionManager && m_ConnectionManager.NetworkManager)
            {
                m_ConnectionManager.RequestShutdown();
            }
        }
    }
}

간단히 말해 네트워크 통신 중이었다면 연결을 끊고,
로딩창도 숨기라는 뜻입니다.

자세한 건 직접 코드를 살펴보시면 쉽게 이해하실 수 있을 것 같습니다. (코드를 타고타고 들어가다 보면 관련된 코드가 너무 많아 상세히 적지 않겠습니다)

이렇게 팝업 띄우고 없애기를 모두 살펴보았습니다.

profile
요즘은 https://welcometodannas.tistory.com/에 더 많은 글을 씁니다.

0개의 댓글