参与者管理
Vivox SDK 会发布有关频道中各个参与者的信息,这些信息对所有其他参与者可见。其中包括以下信息:
- 用户加入频道的时间。
- 用户离开频道的时间。
- 用户状态发生重要变化的时间,如用户是否在说话或键入。
处理参与者事件是可选的。如果没有用户状态可视化(例如,显示启用了语音的用户),则游戏可以忽略这些事件。
若要提供用户状态信息的可视化,游戏必须处理以下消息:
VivoxService.Instance.ParticipantAddedToChannel
VivoxService.Instance.ParticipantRemovedFromChannel
VivoxParticipant.ParticipantMuteStateChanged
VivoxParticipant.ParticipantSpeechDetected
VivoxParticipant.ParticipantAudioEnergyChanged
VivoxParticipant
ParticipantAddedToChannel
和 ParticipantRemovedFromChannel
都带有 VivoxParticipant。VivoxParticipant 包含有关刚刚添加的参与者的信息,例如:
- PlayerId
- DisplayName
- VivoxParticipant 所属的频道的 ChannelName
- VivoxParticipant 是否为 IsSelf(表示频道内本地玩家的参与者)
VivoxParticipant 还包含该参与者的当前状态,包括:
- IsMuted 状态
- AudioEnergy
- SpeechDetected(玩家的 AudioEnergy 是否已增加到 Vivox 认为是语音的程度)
VivoxParticipants 应该与参与者的 UI 表示形式紧密耦合,使用 VivoxParticipant.ParticipantMuteStateChanged
和 VivoxParticipant.ParticipantSpeechDetected
来表达本地玩家是否已将参与者静音,或者参与者当前是否正在频道中讲话。
您可以使用 VivoxParticipant.ParticipantAudioEnergyChanged
创建比 SpeechDetected 更准确的音量单位 (VU) 计量表。
以下代码(这是 Vivox ChatChannelSample 的简化代码段)是这些系统的一个示例:
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();
}
}