내일배움캠프/프로젝트

팀 프로젝트 - Vivox 조작 UI 제작

서보훈 2024. 12. 2. 20:25

좌측 하단에 다른 참가자가 입력을 할 경우 이를 시작적으로 표시해 줄 수 있는 HUD UI와, 보이스채팅 음량을 조절할 수 있는 UI를 제작하였습니다.

 


보이스채팅 채널에 입장할 때, HUD UI를 생성해주는 코드입니다.

public class VoiceChatSetter : MonoSingleton<VoiceChatSetter>
{
    public List<RosterItem> chatList = new List<RosterItem>();
    public RosterItem hudUiPrefab;
    public Transform hudParentPosition;

    protected override void Awake()
    {
        Create();

        if (Instance != this)
        {
            Destroy(gameObject);
        }

        VivoxService.Instance.ParticipantAddedToChannel += OnParticipantAddedToChannel;
        VivoxService.Instance.ParticipantRemovedFromChannel += OnParticipantRemovedFromChannel;
    }

    private void Start()
    {
        VivoxController.Instance.JoinVoiceChannel(NetworkSceneChanger.Instance.VoiceChannelName);
    }

    private void OnParticipantAddedToChannel(VivoxParticipant participant)
    {
        if (participant.PlayerId == AuthenticationService.Instance.PlayerId) return;

        RosterItem newPlayer = Instantiate(hudUiPrefab, hudParentPosition);
        newPlayer.SetupRosterItem(participant);
        chatList.Add(newPlayer);
    }

    private void OnParticipantRemovedFromChannel(VivoxParticipant participant)
    {
        RosterItem remove = chatList.Find(x => x.participant.PlayerId == participant.PlayerId);

        chatList.Remove(remove);
        remove.RemoveSelf();
    }
}

Awake 에서 Vivox 에서 지원하는 채널 참가, 채널 퇴장 이벤트에 메서드를 구독시켜서 UI를 생성하고 있습니다.

또한, 참가자의 ID가 자신일경우, 해당 UI는 생성하지 않고 넘어가게됩니다.

 

HUD UI 프리팹이 가지고있는 스크립트 입니다.

public class RosterItem : MonoBehaviour
{
    public VivoxParticipant participant;

    public TextMeshProUGUI nameText;
    public Image speekImage;

    public Sprite speekSprite;
    public Sprite nonSpeekSprite;
    public Sprite muteSprite;

    private Outline outline;
    private Color originColor;

    private void Awake()
    {
        outline = GetComponent<Outline>();
        originColor = outline.effectColor;
    }

    public void SetupRosterItem(VivoxParticipant newParticipant)
    {
        participant = newParticipant;

        nameText.text = participant.DisplayName;

        participant.ParticipantMuteStateChanged += UpdateChatState;
        participant.ParticipantSpeechDetected += UpdateChatState;
    }

    private void UpdateChatState()
    {
        if(participant.IsMuted)
        {
            //음소거시
            //speekImage.sprite = muteSprite;
        }
        else if (participant.SpeechDetected)
        {
            //입력 감지시
            //speekImage.sprite = speekSprite;
            outline.effectColor = Color.green;
        }
        else
        {
            //그 외(음소거가 아니지만 입력이 없을경우)
            //speekImage.sprite = nonSpeekSprite;
            outline.effectColor = originColor;
        }
    }

    public void RemoveSelf()
    {
        Destroy(gameObject);
    }
}

생성 후, SetupRosterItem 함수를 통해서 참가자의 정보를 가져오며, 참가자의 음성이 인식될 때, 음소거 상태가 인식될 때 발생하는 이벤트에 UpdateChatState 메서드를 구독하게 하였습니다.

 

사용자가 음소거를 할경우와 말하는것이 인식될경우 Vivox 에서 이벤트가 발생하여 시각적으로 음성채팅을 하고 있음을 보여줍니다.

 

참가자의 보이스채팅 음량을 조절하는 팝업형태의 UI입니다.

슬라이더를 조절하여 다른 사용자의 보이스채팅 출력 음량을 조절 할 수 있습니다.

 

Vivox 의 참여자 참가 이벤트에 음량조절 UI 생성 메서드를 구독하여 참여자가 참가할 때 UI 가 생성되도록 만들어주었습니다.

public class UIVoiceChatOption : MonoBehaviour
{
    public GameObject optionUI;

    public Transform volumOptionUIParent;

    public UIPlayerVoiceSoundController volumeControllerPrefab;

    private void Awake()
    {
        VivoxService.Instance.ParticipantAddedToChannel += InitPlayerUI;
    }

    public void InitPlayerUI(VivoxParticipant participant)
    {
        if (participant.PlayerId == AuthenticationService.Instance.PlayerId) return;

        UIPlayerVoiceSoundController controller = Instantiate(volumeControllerPrefab, volumOptionUIParent);
        controller.SetupUI(participant);
    }

    public void ActiveOptionUI(bool active)
    {
        optionUI.SetActive(active);
    }
}

마찬가지로 추가된 참가자의 ID가 자신이라면 UI는 생성되지 않습니다.

 

볼륨 조절 UI의 경우 별도의 프리팹으로 만들어주었습니다.

public class UIPlayerVoiceSoundController : MonoBehaviour
{
    private VivoxParticipant thisUIParticipant;

    public TextMeshProUGUI playerNameText;
    public Slider volumeControllSlider;

    public void SetupUI(VivoxParticipant participant)
    {
        thisUIParticipant = participant;

        playerNameText.text = thisUIParticipant.DisplayName;
        volumeControllSlider.value = thisUIParticipant.LocalVolume;
    }

    public void ChangeVolume()
    {
        int volume = (int)volumeControllSlider.value;
        thisUIParticipant.SetLocalVolume(volume);
    }
}

UI를 생성할때 SetupUI 메서드를 호출하여 참여자 정보를 넘기고, LocalVolume 값을 수정하여 자신이 들리는 소리만을 조절합니다.

 

 


트러블슈팅

  • DisplayName을 보여주도록 설정한 결과, 이름이 이상한 값이 나오는 오류

SetProfile() 을 통해 생성되는 프로필의 경우, 프로필의 이름이 변경되고 그 내부 값은 자동 생성되어 이러한 문제가 발생하였습니다.

private async Task LoginVivoxAsync()
{
    LoginOptions options = new LoginOptions();

    options.DisplayName = AuthenticationService.Instance.Profile;

    await VivoxService.Instance.LoginAsync(options);
}

DisplayName 을 Profile 으로 받게하여 임시로 문제를 해결하였습니다.

※익명 로그인 후, UpdatePlayerNameAsync() 메서드를 사용하여 플레이어 이름을 바꿔서 해결할 수도 있습니다

(이 경우, 이름 옆에 #숫자 가 붙게됩니다.)

public async void Authenticate()
{
    //플레이어 프로필 설정
    playerName = nameInput.text;
    //기본 프로필에서 입력한 프로필 이름으로 변경
    AuthenticationService.Instance.SwitchProfile(playerName);

    //인증 시도
    await AuthenticationService.Instance.SignInAnonymouslyAsync();

    await AuthenticationService.Instance.UpdatePlayerNameAsync(playerName);

    OnAuthenticateSuccessEvent?.Invoke();
}

 

  • 자신이 UI에 보이는 문제

위의 코드와 같이, 참여자의 아이디가 자신일경우 UI를 생성하지 않도록 하여 막아줄 수 있습니다.

 

  • 이름이 Default 로 나오는 문제

※이미지를 찍어두지 않았습니다;;

 

게임 종료후, 로비로 나가게될 경우 UI가 인증부터 시작될 가능성이 있어서 이를 막기위해 로그인되어있을경우 방 찾기 부터 하도록 설정해주었습니다.

private void Start()
{
    LobbyManager.Instance.OnAuthenticateSuccessEvent += SuccessLogin;
    LobbyManager.Instance.OnJoinLobbyEvent += JoinLobby;
    LobbyManager.Instance.OnLeaveLobbyEvent += LeaveLobby;
    LobbyManager.Instance.OnKickedFromLobbyEvent += LeaveLobby;
    LobbyManager.Instance.OnPlayerReadyEvent += GameStartButtonControll;

    if (AuthenticationService.Instance.IsSignedIn == false)
    {
        loginUI.SetActive(true);
        searchLobbyUI.SetActive(false);
        lobbyUI.SetActive(false);
    }
    else
    {
        loginUI.SetActive(false);
        searchLobbyUI.SetActive(true);
        lobbyUI.SetActive(false);
    }
}

이 경우, 사전에 UnityServices 클래스를 초기화 해주어야하여 이 내용을 Awake로 옮겨준 결과, 이러한 오류가 발생하였습니다.

 

초기화를 하면서 프로필을 설정하지 않은 결과 이러한 오류가 발생하였고, 이를 해결하기위해 인증 메서드에서 초기화 코드 대신, SwitchProfile 을 사용하여 프로필을 변경하여 문제를 해결해주었습니다.

//인증 메서드 - UGS에 연결
public async void Authenticate()
{
    //플레이어 프로필 설정
    playerName = nameInput.text;
    //기본 프로필에서 입력한 프로필 이름으로 변경
    AuthenticationService.Instance.SwitchProfile(playerName);

    //인증 시도
    await AuthenticationService.Instance.SignInAnonymouslyAsync();

    await AuthenticationService.Instance.UpdatePlayerNameAsync(playerName);

    OnAuthenticateSuccessEvent?.Invoke();
}

 

진행중

  • 게임씬을 들어간뒤, 로비 검색씬으로 나왔을때 다시 로비에 들어가서 게임 시작을 시도할경우 진행되지 않는 오류
    • → 연결 끊겼을경우와 동시에 해결방안을 찾는중입니다.

현재 진행중인 내용

  • 연결이 끊겼을경우 이를 감지하여 연결을 끊고 로비로 나가는 기능
  • 게임 시작시, 플레이어의 역할을 배정해줄 방법 생각중

해야할 일 목록

  • Netcode 로 플레이어 연동 시키기
  • 게임이 정상적으로 종료되었을경우, 로비에 다시 들어가게 할지 로비를 다시 찾게할지 선택하기 (방법도 같이 생각하기)

 

로비 시스템 기반과 음성채팅 시스템 기반을 완성하여 이제 본격적으로 Netcode를 활용한 멀티플레이 요소 구현을 하면 될 것 같습니다.

 

이후, 로비나 음성채팅 개편은 사실상 알파버전(게임이 진행 가능한 버전) 이 나오면 그 후로 세부 내용을 다듬는쪽으로 길을 잡고 갈 듯 합니다.