[StarChaser] 로그라이크 게임 프로젝트

[유니티/C#] Action을 이용한 로비 시스템 만들기(feat. 건파이어리본)

진서박 2023. 8. 18. 17:31
반응형

대략적으로 그려본 UML

 

 

건파이어 리본과 비슷한 로비 시스템을 만들게 되었다.

대충 이런느낌의

 

 

버튼 스크립트 설명

더보기

버튼은 재사용성을 위하여 Base 클래스를 abstract로 작성했다.

마우스 커서에 대한 동작을 처리하기 위해 인터페이스를 상속받았다.

1. 올렸을 때(IPointerEnterHandler)

2. 내렸을 때(IPointerExitHandler)

3. 눌렀을 때(IPointerDownHandler)

 

- 구현은 자식 클래스에서 하도록 abstract 함수로 만들었다.

- 커서에 대한 반응을 크기를 변경하는 방식으로 하고싶어서 DoScale~() 함수를 만들었다. 이 함수는 공통으로 사용될 것이며, 사용은 상속받은 클래스에서 자유롭게 할 수 있다.

- 버튼에 사용할것이기 때문에 onClickEvent 함수를 마찬가지로 abstract로 만들었고, AddListener로 클릭 시 작동하도록 버튼 클릭 이벤트에 추가해주었다.

public abstract class UIButtonBase : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerDownHandler
{
    protected Button _button;
    protected RectTransform _rTr;
    protected Vector3 _originScale;

    protected virtual void Start()
    {
        _rTr = GetComponent<RectTransform>();
        _button = gameObject.GetOrAddComponent<Button>();

        _button.onClick.AddListener(OnClickEvent);
        _originScale = _rTr.localScale;
    }

    public abstract void OnPointerEnter(PointerEventData eventData);

    public abstract void OnPointerExit(PointerEventData eventData);

    public abstract void OnPointerDown(PointerEventData eventData);

    protected abstract void OnClickEvent();

    protected void DoScaleOrigin()
    {
        _rTr.DOScale(_originScale, 0.05f);
    }

    protected void DoScaleBig(float multiplier = 1.2f)
    {
        _rTr.DOScale(_originScale * multiplier, 0.05f);
    }

 

다른 곳에서도 사용할 일이 있을 수 있어서, 상속으로 로비에서 사용할 버튼의 스크립트를 따로 만들었다.

- LobbyClickAction()은 미리 만들어둔 열거형 LobbyMenuButton 타입으로 등록된 Action을 실행한다. 

- 후술할 MainLobbyManager에 등록해 둔 액션을 타입 호출로만 간편히 실행할 수 있도록 구성했다

public class UIButtonLobby : UIButtonBase
{
    protected MainLobbyManager _lobbyManager;

    protected override void Start()
    {
        base.Start();
        _lobbyManager = FindObjectOfType<MainLobbyManager>();
    }

    protected virtual void LobbyClickAction(Define.LobbyMenuButton buttonType)
    {
        if (_lobbyManager != null)
            _lobbyManager?.ButtonClickAction.Invoke(buttonType);
    }

    protected override void OnClickEvent() { }

    public override void OnPointerEnter(PointerEventData eventData)
    {
        DoScaleBig();
    }

    public override void OnPointerExit(PointerEventData eventData)
    {
        DoScaleOrigin();
    }

    public override void OnPointerDown(PointerEventData eventData)
    {
        DoScaleOrigin();
    }
}

 

LobbyClickAction()은 상속받은 클래스에서 사용되는데, 다 비슷비슷해서 하나만 기록해두려고 한다.

public class UIButtonLobby_Option : UIButtonLobby
{
    protected override void OnClickEvent()
    {
        LobbyClickAction(Define.LobbyMenuButton.Option);
    }
}

 

버튼이 눌렸을 때 LobbyClickAction(버튼의 타입) 을 실행하여, 로직이 뭔지는 관심없고 액션만 실행해주면 된다

 

 

더보기

로비 매니저 스크립트 설명

 

요약

1. 카메라와 UI를 각각 찾아서 타입에 맞게 Dictionary에 저장한다.

2-1. ButtonClickAction : 버튼이 눌렸을 때 동작할 로직을 등록할 Action

2-2. ButtonClickHandler() : 타입에 맞는 카메라로 우선순위를 바꾸고, 이전 UI를 끄고 타입에 맞는 UI를 켠다. ButtonClickAction에 등록한다

3-1. ExitAction : 메뉴에서 나갈 때 동작할 로직을 등록할 Action

3-2. 람다식으로 나갈 때 ESC를 누르라는 문구를 끄는 로직을 등록

3-3. 버튼들의 로직에서 메뉴에서 나갈 때 동작할 로직을 ExitAction에 등록

 

public class MainLobbyManager : MonoBehaviour
{
    private CinemachineVirtualCamera _mainCam;
    private CinemachineVirtualCamera _enforceCam;
    private CinemachineVirtualCamera _optionCam;
    private CinemachineVirtualCamera _exitCam;

    private GameObject _mainUI;
    private GameObject _enforceUI;
    private GameObject _optionUI;
    private GameObject _exitUI;
    private GameObject _escGuideText;

    private Dictionary<Define.LobbyMenuButton, CinemachineVirtualCamera> _camDic 
            = new Dictionary<Define.LobbyMenuButton, CinemachineVirtualCamera>();

    private Dictionary<Define.LobbyMenuButton, GameObject> _uiDic
            = new Dictionary<Define.LobbyMenuButton, GameObject>();

    private CinemachineBrain _cinemachineBrain;

    public Action<Define.LobbyMenuButton> ButtonClickAction;
    public Action ExitAction;

    private Define.LobbyMenuButton _currentLobby;

    void Start()
    {
        Init();
        _cinemachineBrain = FindObjectOfType<CinemachineBrain>();

        ButtonClickAction += ButtonClickHandler;
        ExitAction += () => _escGuideText?.SetActive(false);

        StartCoroutine(CamChangeCorouine(Define.LobbyMenuButton.Main));
    }

    private void Init()
    {
        _camDic.Add(Define.LobbyMenuButton.Main, GameObject.Find("UICam_Main").GetComponent<CinemachineVirtualCamera>());
        _camDic.Add(Define.LobbyMenuButton.Enforce, GameObject.Find("UICam_Enforce").GetComponent<CinemachineVirtualCamera>());
        _camDic.Add(Define.LobbyMenuButton.Option, GameObject.Find("UICam_Option").GetComponent<CinemachineVirtualCamera>());
        _camDic.Add(Define.LobbyMenuButton.Exit, GameObject.Find("UICam_Exit").GetComponent<CinemachineVirtualCamera>());

        _uiDic.Add(Define.LobbyMenuButton.Main, FindChildRecursively(transform, "UIMain").gameObject);
        _uiDic.Add(Define.LobbyMenuButton.Enforce, FindChildRecursively(transform, "UIEnforce").gameObject);
        _uiDic.Add(Define.LobbyMenuButton.Option, FindChildRecursively(transform, "UIOption").gameObject);
        _uiDic.Add(Define.LobbyMenuButton.Exit, FindChildRecursively(transform, "UIExit").gameObject);

        _escGuideText = FindChildRecursively(transform, "UIESCGuide").gameObject;
    }

    private Transform FindChildRecursively(Transform parent, string name)
    {
    	// 재귀하여 자식 오브젝트 찾는 함수
    }

    private void Update()
    {
        if(Input.GetKeyDown(KeyCode.Escape)
        && !_cinemachineBrain.IsBlending
        && _currentLobby!= Define.LobbyMenuButton.Main)
        {
            if(ExitAction != null)
                ExitAction.Invoke();
            ButtonClickAction.Invoke(Define.LobbyMenuButton.Main);
        }
    }

    private void ButtonClickHandler(Define.LobbyMenuButton button)
    {
        StartCoroutine(CamChangeCorouine(button));
    }

    private IEnumerator CamChangeCorouine(Define.LobbyMenuButton buttonType)
    {
        SetCam(buttonType);
        SetUI(buttonType);
        
        // 카메라 블렌딩이 바로 이루어지지 않아서 한프레임 기다려준다
        yield return null;

        while (_cinemachineBrain.IsBlending)
        {
            yield return null;
        }

        _uiDic[buttonType].SetActive(true);
        _escGuideText.SetActive(buttonType == Define.LobbyMenuButton.Main ? false : true);

        _currentLobby = buttonType;
    }

    private void SetCam(Define.LobbyMenuButton camType)
    {
        foreach (var cam in _camDic)
        {
            if (cam.Key == camType)
                cam.Value.Priority = 50;
            else
                cam.Value.Priority = 0;
        }
    }

    private void SetUI(Define.LobbyMenuButton uiType)
    {
        foreach (var ui in _uiDic)
        {
        	// 타입에 맞는 ui.SetActive(true); 해준다
        }
    }
}

 

결과물

얼추 비슷하다

 

카메라가 블렌딩 될 동안에 화면이 이상하거나 어색한 부분은 검은색으로 페이드아웃 시키면 되겠다

반응형