# 참여 관리

> Manage participant events when users join or leave channels.

Vivox SDK는 채널에 있는 다른 모든 참가자가 볼 수 있는 개별 참가자에 관한 정보를 게시합니다. 게시되는 정보는 다음과 같습니다.

* 사용자가 채널에 참여할 때
* 사용자가 채널을 나갈 때
* 사용자가 말을 하고 있거나 텍스트를 입력 중인 경우와 같이 사용자의 상태에 중요한 변화가 있을 때

참가자 이벤트 처리는 선택사항입니다. 사용자 상태를 시각화(예: 음성이 사용 설정된 사용자 표시)하지 않은 경우 게임에서는 이러한 이벤트를 무시할 수 있습니다.

게임이 사용자 상태 정보를 시각화하길 원하는 경우 다음과 같은 메시지를 처리해야 합니다.

* `VivoxService.Instance.ParticipantAddedToChannel`
* `VivoxService.Instance.ParticipantRemovedFromChannel`
* `VivoxParticipant.ParticipantMuteStateChanged`
* `VivoxParticipant.ParticipantSpeechDetected`
* `VivoxParticipant.ParticipantAudioEnergyChanged`

## VivoxParticipant##vivoxparticipant

[`ParticipantAddedToChannel`](https://docs.unity3d.com/Packages/com.unity.services.vivox@latest/index.html?subfolder=/api/Unity.Services.Vivox.IVivoxService.html#Unity_Services_Vivox_IVivoxService_ParticipantAddedToChannel)과 [`ParticipantRemovedFromChannel`](https://docs.unity3d.com/Packages/com.unity.services.vivox@latest/index.html?subfolder=/api/Unity.Services.Vivox.IVivoxService.html#Unity_Services_Vivox_IVivoxService_ParticipantRemovedFromChannel)은 모두 VivoxParticipant와 함께 제공됩니다. VivoxParticipant에는 방금 추가된 참가자에 대한 다음과 같은 정보가 포함되어 있습니다.

* PlayerId
* DisplayName
* VivoxParticipant가 구성원으로 속한 채널의 ChannelName
* VivoxParticipant IsSelf 여부 - 채널 내에서 로컬 플레이어를 나타내는 참가자

VivoxParticipant에는 다음을 포함하여 해당 참가자의 현재 상태도 포함됩니다.

* IsMuted 상태
* AudioEnergy
* SpeechDetected(Vivox가 스피치로 간주할 정도로 플레이어의 AudioEnergy가 증가했는지 여부)

[`VivoxParticipant.ParticipantMuteStateChanged`](https://docs.unity3d.com/Packages/com.unity.services.vivox@latest/index.html?subfolder=/api/Unity.Services.Vivox.VivoxParticipant.html#Unity_Services_Vivox_VivoxParticipant_ParticipantMuteStateChanged)는 [`VivoxParticipant.ParticipantSpeechDetected`](https://docs.unity3d.com/Packages/com.unity.services.vivox@latest/index.html?subfolder=/api/Unity.Services.Vivox.VivoxParticipant.html#Unity_Services_Vivox_VivoxParticipant_ParticipantSpeechDetected)와 함께 참가자의 UI 표현과 긴밀하게 연결되어야 하며, VivoxParticipant는 로컬 플레이어에게 음소거된 참가자가 있는지 또는 참가자가 현재 채널에서 말하고 있는지 여부를 전달하는 데 사용됩니다.

[`VivoxParticipant.ParticipantAudioEnergyChanged`](https://docs.unity3d.com/Packages/com.unity.services.vivox@latest/index.html?subfolder=/api/Unity.Services.Vivox.VivoxParticipant.html#Unity_Services_Vivox_VivoxParticipant_ParticipantAudioEnergyChanged)를 사용하면 SpeechDetected에서 허용하는 것보다 더 정확한 VU(음량 단위) 미터를 생성할 수 있습니다.

다음 코드는 Vivox ChatChannelSample에서 간소화된 세그먼트로, 이러한 시스템의 예제입니다.

```cs
public class RosterManager : MonoBehaviour
{
    private const string LobbyChannelName = "lobbyChannel";
    private Dictionary<string, List<RosterItem>> rosterObjects = new Dictionary<string, List<RosterItem>>();
    public GameObject rosterItemPrefab;


    private void Start()
    {
        VivoxService.Instance.ParticipantAddedToChannel += OnParticipantAdded;
        VivoxService.Instance.ParticipantRemovedFromChannel += OnParticipantRemoved;
    }

    public void ClearAllRosters()
    {
        foreach(List<RosterItem> rosterList in rosterObjects.Values)
        {
            foreach(RosterItem item in rosterList)
            {
                Destroy(item.gameObject);
            }
            rosterList.Clear();
        }
        rosterObjects.Clear();
    }

    public void ClearChannelRoster(string channelName)
    {
        List<RosterItem> rosterList = rosterObjects[channelName];
        foreach(RosterItem item in rosterList)
        {
            Destroy(item.gameObject);
        }
        rosterList.Clear();
        rosterObjects.Remove(channelName);
    }

    private void CleanRoster(string channelName)
    {
        RectTransform rt = this.gameObject.GetComponent<RectTransform>();
        rt.sizeDelta = new Vector2(0, rosterObjects[channelName].Count * 50);
    }

    void UpdateParticipantRoster(VivoxParticipant participant, bool isAddParticipant)
    {
        if (isAddParticipant)
        {
            GameObject newRosterObject = GameObject.Instantiate(rosterItemPrefab, this.gameObject.transform);
            RosterItem newRosterItem = newRosterObject.GetComponent<RosterItem>();
            List<RosterItem> thisChannelList;

            if (rosterObjects.ContainsKey(participant.ChannelName))
            {
                //Add this object to an existing roster
                rosterObjects.TryGetValue(participant.ChannelName, out thisChannelList);
                newRosterItem.SetupRosterItem(participant);
                thisChannelList.Add(newRosterItem);
                rosterObjects[participant.ChannelName] = thisChannelList;
            }
            else
            {
                //Create a new roster to add this object to
                thisChannelList = new List<RosterItem>();
                thisChannelList.Add(newRosterItem);
                newRosterItem.SetupRosterItem(participant);
                rosterObjects.Add(participant.ChannelName, thisChannelList);
            }
            CleanRoster(participant.ChannelName);
        }
        else
        {
            if (rosterObjects.ContainsKey(participant.ChannelName))
            {
                RosterItem removedItem = rosterObjects[participant.ChannelName].FirstOrDefault(p => p.Participant.PlayerId == participant.PlayerId);
                if (removedItem != null)
                {
                    rosterObjects[participant.ChannelName].Remove(removedItem);
                    Destroy(removedItem.gameObject);
                    CleanRoster(participant.ChannelName);
                }
                else
                {
                    Debug.LogError("Trying to remove a participant that has no roster item.");
                }
            }
        }
    }

    void OnParticipantAdded(VivoxParticipant participant)
    {
        UpdateParticipantRoster(participant, true);
    }

    void OnParticipantRemoved(VivoxParticipant participant)
    {
        UpdateParticipantRoster(participant, false);
    }
}

public class RosterItem : MonoBehaviour
{
    // Player specific items.
    public VivoxParticipant Participant;
    public Text PlayerNameText;

    public Image ChatStateImage;
    public Sprite MutedImage;
    public Sprite SpeakingImage;
    public Sprite NotSpeakingImage;

    Button m_muteButton;

    private void UpdateChatStateImage()
    {
        if (Participant.IsMuted)
        {
            ChatStateImage.sprite = MutedImage;
        }
        else
        {
            if (Participant.SpeechDetected)
            {
                ChatStateImage.sprite = SpeakingImage;
            }
            else
            {
                ChatStateImage.sprite = NotSpeakingImage;
            }
        }
    }

    public void SetupRosterItem(VivoxParticipant participant)
    {
        //Set the Participant variable of this RosterItem to the VivoxParticipant added in the RosterManager
        Participant = participant;
        PlayerNameText.text = Participant.DisplayName;
        // Update the image to the active state of the user (either the SpeakingImage, the MutedImage, or the NotSpeakingImage) and then attach
        // the function to run if an event is fired denoting a change to that users state
        UpdateChatStateImage();
        Participant.ParticipantMuteStateChanged += UpdateChatStateImage;
        Participant.ParticipantSpeechDetected += UpdateChatStateImage;

        //A button on the UI element itself is implemented to handle muting on the participant represented by the UI element
        m_muteButton = gameObject.GetComponent<Button>();
        m_muteButton.onClick.AddListener(() =>
        {
            // If already muted, unmute, and vice versa.
            if (Participant.IsMuted)
            {
                Participant.UnmutePlayerLocally();
            }
            else
            {
                Participant.MutePlayerLocally();
            }
        });
    }

    void OnDestroy()
    {
        Participant.ParticipantMuteStateChanged -= UpdateChatStateImage;
        Participant.ParticipantSpeechDetected -= UpdateChatStateImage;

        m_muteButton.onClick.RemoveAllListeners();
    }
}
```
