음성채팅을 위해 Vivox를 사용하면서, Vivox를 관리하는 클래스와 음성채팅과 관련된 옵션을 만들어주었습니다.
채널 관리 클래스 - VivoxController
게임 시작시, Vivox 채널에 참여하고, 종료시 채널에서 나가는 기능 구현한 클래스 입니다.
기획당시 플레이어의 상태에 따라 음성채팅 연결을 조작할 가능성을 생각하였으며, 해당 내용을 구현했다면 이 클래스에서 조절하게 되었을 것입니다.
public class VivoxController : MonoSingleton<VivoxController>
{
private string nowChannelName;
public async void JoinVoiceChannel(string channelName)
{
try
{
nowChannelName = channelName;
await VivoxService.Instance.JoinGroupChannelAsync(channelName, ChatCapability.AudioOnly);
}
catch (Exception ex)
{
Debug.Log(ex);
}
}
public async void LeaveVoiceChannel()
{
await VivoxService.Instance.LeaveChannelAsync(nowChannelName);
nowChannelName = string.Empty;
}
}
UI 관리 클래스
음성채팅 참여시, 관련 UI들을 생성해주는 클래스입니다.
게임 종료시 음성채팅에서 나가면서, 관련 UI들을 전부 지우는 역할 또한 합니다.
public class VoiceChatSetter : MonoSingleton<VoiceChatSetter>
{
public List<RosterItem> chatList = new List<RosterItem>();
public RosterItem hudUiPrefab;
public Transform hudParentPosition;
public UIVoiceChatOption optionUI;
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);
}
//Vivox 채널에 참가했을때 HUD UI를 생성, 삭제하는 메서드들
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)
{
if (participant.PlayerId == VivoxService.Instance.SignedInPlayerId) return;
for(int i = 0; i < chatList.Count; i++)
{
if(chatList[i].participant.PlayerId == participant.PlayerId)
{
chatList[i].RemoveSelf();
chatList.Remove(chatList[i]);
return;
}
}
}
public void ClaerChatList()
{
VivoxService.Instance.ParticipantAddedToChannel -= OnParticipantAddedToChannel;
VivoxService.Instance.ParticipantRemovedFromChannel -= OnParticipantRemovedFromChannel;
for(int i = 0; i < chatList.Count; i++)
{
if (chatList[i] == null) continue;
chatList[i].RemoveSelf();
}
chatList.Clear();
if (optionUI != null)
{
Debug.Log("옵션UI 지우기 시작");
optionUI.ClearOptionUIs();
}
}
}
HUD UI 관리 클래스
채널에 참가하면, 게임 HUD UI 에 상대방의 이름을 표기하며, 음성채팅을 사용하고 있는지 확인할 수 있는 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()
{
participant.ParticipantMuteStateChanged -= UpdateChatState;
participant.ParticipantSpeechDetected -= UpdateChatState;
Destroy(gameObject);
}
}
음성채팅을 하는중에 좌측 상단의 UI가 녹색빛이 됩니다.
Vivox 채팅 음량조절 UI
음성채팅 소리가 너무 크거나 작을때, 음성채팅 사운드를 조절하기 위한 UI를 관리하는 클래스 입니다.
게임 시작시 채널 참가자의 UI를 생성하고, 해당 UI를 조작하여 음량을 조절합니다.
public class UIVoiceChatOption : MonoBehaviour
{
public GameObject optionUI;
public Transform volumOptionUIParent;
public UIPlayerVoiceSoundController volumeControllerPrefab;
private List<UIPlayerVoiceSoundController> controllerList = new List<UIPlayerVoiceSoundController>();
private void Awake()
{
VoiceChatSetter.Instance.optionUI = this;
VivoxService.Instance.ParticipantAddedToChannel += InitPlayerUI;
}
//옵션에 음량 조절 UI 생성
public void InitPlayerUI(VivoxParticipant participant)
{
if (participant.PlayerId == AuthenticationService.Instance.PlayerId) return;
UIPlayerVoiceSoundController controller = Instantiate(volumeControllerPrefab, volumOptionUIParent);
controller.SetupUI(participant);
controllerList.Add(controller);
}
public void ActiveOptionUI(bool active)
{
optionUI.SetActive(active);
if(active == false)
{
OptionUI ui = UIManager.Instance.CreateUI<OptionUI>();
ui.ClickSetting();
}
}
public void ClearOptionUIs()
{
VivoxService.Instance.ParticipantAddedToChannel -= InitPlayerUI;
for (int i = 0; i < controllerList.Count; i++)
{
if (controllerList[i] == null) continue;
controllerList[i].RemoveSelf();
}
controllerList.Clear();
}
}
public class UIPlayerVoiceSoundController : MonoBehaviour
{
private VivoxParticipant thisUIParticipant;
public TextMeshProUGUI playerNameText;
public Slider volumeControllSlider;
private string voiceChatVolumeKey = "VoiceChatVolume";
public void SetupUI(VivoxParticipant participant)
{
thisUIParticipant = participant;
playerNameText.text = thisUIParticipant.DisplayName;
int baseVolume = PlayerPrefs.GetInt(voiceChatVolumeKey, 0);
thisUIParticipant.SetLocalVolume(baseVolume);
volumeControllSlider.value = thisUIParticipant.LocalVolume;
//thisUIParticipant.SetLocalVolume(-50);
}
public void ChangeVolume()
{
int volume = (int)volumeControllSlider.value;
thisUIParticipant.SetLocalVolume(volume);
PlayerPrefs.SetInt(voiceChatVolumeKey, volume);
}
public void RemoveSelf()
{
Destroy(gameObject);
}
}
Vivox 는 -50 ~ 50 사이의 음량 조절을 지원합니다. 따라서 음량조절 기능은 기본을 0으로 두고 -50 ~ 50 사이로 정수값으로 조절 가능하게 만들어주었습니다.
다음으로 Popup 형태의 UI를 활성화/비활성화시 사용된 전략패턴과, 전략패턴을 적용해주는 팩토리 패턴 입니다.
전략 패턴
일단 즉시 활성화/비활성화 되는 None,
페이드인, 아웃 방식으로 활성화되는 Fade,
아래에서 위로 올라오는 SlideBottom 으로 3가지 패턴을 만들어서 사용하였습니다.
인스펙터창에서 Enum 값을 선택함으로써, 해당 전략을 사용하도록 팩토리 패턴을 사용하여 UI에 움직임을 주었습니다.
전략
public enum UIMovementType
{
None,
Fade,
SlideBottom
}
public interface IPopupStrategy
{
public void ShowUI();
public void HideUI();
}
//전략패턴 기반
public class UIStrategyBase
{
protected GameObject ui;
protected RectTransform transform;
public UIStrategyBase(GameObject thisUi)
{
ui = thisUi;
transform = ui.GetComponent<RectTransform>();
}
}
//전략 : 아래에서 위로 슬라이드 하며 생성
public class UISlideBottomStrategy : UIStrategyBase, IPopupStrategy
{
private Vector3 hideVector;
private Vector3 basePosition;
public UISlideBottomStrategy(GameObject ui) : base(ui)
{
hideVector = new Vector3(Screen.width/2, -Screen.height, 0);
basePosition = new Vector3(Screen.width/2, Screen.height/2, 0);
}
public void HideUI()
{
transform.position = basePosition;
CoroutineManager.Instance.StartMyCoroutine(ActiveUI(false));
}
public void ShowUI()
{
transform.position = hideVector;
CoroutineManager.Instance.StartMyCoroutine(ActiveUI(true));
}
private IEnumerator ActiveUI(bool isActive)
{
if(isActive)
{
ui.SetActive(true);
Tween tween = transform.DOAnchorPosY(0, 1f);
yield return tween.WaitForCompletion();
}
else
{
Tween tween = transform.DOAnchorPosY(-1080, 1);
yield return tween.WaitForCompletion();
ui.SetActive(false);
}
}
}
//전략 : 페이드 인, 아웃
public class UIFadeStrategy : UIStrategyBase, IPopupStrategy
{
private CanvasGroup canvasGroup;
public UIFadeStrategy(GameObject ui) : base(ui)
{
if (!ui.TryGetComponent(out canvasGroup))
{
canvasGroup = ui.AddComponent<CanvasGroup>();
}
}
public void HideUI()
{
CoroutineManager.Instance.StartMyCoroutine(Fade(true));
}
public void ShowUI()
{
CoroutineManager.Instance.StartMyCoroutine(Fade(false));
}
IEnumerator Fade(bool isFadeIn)
{
if(isFadeIn)
{
canvasGroup.alpha = 1f;
Tween tween = canvasGroup.DOFade(0, 1);
yield return tween.WaitForCompletion();
ui.SetActive(false);
}
else
{
canvasGroup.alpha = 0f;
ui.SetActive(true);
Tween tween = canvasGroup.DOFade(1, 1);
yield return tween.WaitForCompletion();
}
}
}
//전략 : 아무런 효과가 없음
public class UINoneStrategy : UIStrategyBase, IPopupStrategy
{
public UINoneStrategy(GameObject ui) : base(ui)
{
}
public void HideUI()
{
ui.SetActive(false);
}
public void ShowUI()
{
ui.SetActive(true);
}
}
팩토리
public static class UIPopupFactory
{
// UI에 전략패턴을 만들어주는 팩토리 메서드
public static IPopupStrategy SetPopupStrategy(GameObject ui, UIMovementType move)
{
IPopupStrategy strategy;
switch (move)
{
case UIMovementType.None:
strategy = new UINoneStrategy(ui);
break;
case UIMovementType.Fade:
strategy = new UIFadeStrategy(ui);
break;
case UIMovementType.SlideBottom:
strategy = new UISlideBottomStrategy(ui);
break;
default:
strategy = new UINoneStrategy(ui);
break;
}
return strategy;
}
}
Popup 형식의 UI의 기반이 되는 클래스에 해당 내용을 적용하였습니다.
public class UIPopup : UIBase
{
//팝업 UI 상속 클래스 (예: 인벤토리창 설정창)
public UIMovementType movement;
//UI 활성화, 비활성화시 전략
private IPopupStrategy strategy;
//전략패턴 지정
protected virtual void Awake()
{
strategy = UIPopupFactory.SetPopupStrategy(gameObject, movement);
}
public override void Show()
{
base.Show();
strategy.ShowUI();
}
public override void Hide()
{
base.Hide();
strategy.HideUI();
}
}
로비 UI의 인스펙터 모습 입니다.
Movement에 Slide Bottom 을 선택하여 UI가 아래에서 위로 슬라이드 하듯이 올라오도록 구현하였습니다.
'내일배움캠프 > 프로젝트' 카테고리의 다른 글
팀 프로젝트 마무리 정리 - 멀티플레이 구현 (0) | 2025.01.15 |
---|---|
팀 프로젝트 마무리 정리 - 로비 (0) | 2025.01.14 |
팀 프로젝트 - 퍼즐 힌트 시작 수정 (0) | 2025.01.13 |
팀 프로젝트 - 아이템 무작위 배치 (0) | 2025.01.08 |
팀 프로젝트 - 프리팹 용량문제 (0) | 2025.01.07 |