在Unity中使用Agora的音频社交聊天直播
你好,无畏的开发者! 在本教程中,你将在Unity 2019.4.1(LTS)的3D Unity环境中设置实时音视频直播。这本质上是一个视频游戏环境,在这主播可以与同一频道中的任何人交流,并且任何观众都可以在没有实时音视频的情况下收听游戏。在加入场景时,每个玩家可以通过用户界面选择成为主播或观众,以及他们想要加入的频道。每个主播将拥有金色材料,而观众成员将有标准的维京人审美。当每个主播说话时,你将使用Agora回调来提供视觉反馈。
开始使用Agora
开始你需要一个Agora账户。如果你还没有,这里有一份建立账户的 指南。
这个项目是建立在agora-unity-audio-broadcasting 演示上的。
使用agora-unity-audio-broadcasting作为一个起始模板。或者从头开始创建一个项目,并import Agora Video SDK和Photon Viking Demo。
开发
创建Agora引擎
在Assets > DemoVikings > Scenes > VikingScene中,创建一个空的GameObject并命名为AgoraEngine。如果你没有位置,创建一个Scripts文件夹,并创建一个名为AgoraEngine.cs的脚本。
using UnityEngine;
using agora_gaming_rtc;
public class AgoraEngine : MonoBehaviour
{
[Header("Agora Properties")]
[SerializeField]
private string appID = "";
public static IRtcEngine mRtcEngine;
void Start()
{
if(mRtcEngine != null)
{
IRtcEngine.Destroy();
}
// Initialize Agora engine
mRtcEngine = IRtcEngine.GetEngine(appID);
}
// Cleaning up the Agora engine during OnApplicationQuit() is an essential part of the Agora process with Unity.
private void OnApplicationQuit()
{
TerminateAgoraEngine();
}
public static void TerminateAgoraEngine()
{
if (mRtcEngine != null)
{
mRtcEngine.LeaveChannel();
mRtcEngine = null;
IRtcEngine.Destroy();
}
}
}
你现在有一个可以运行的Agora引擎,你可以从你的Viking玩家对象中访问它。
但在继续之前,请在viking scene中通过右键单击Hierarchy > UI > Event System向场景添加一个EventSystem对象。
主播用户界面
帆布面板
接下来,你将创建设置频道名称和主播/观众状态所需的用户界面。在Assets > DemoVikings > Resources > Charprefab中,双击Charprefab以打开prefab视图。在层次结构中,右键单击 Charprefab parent object > UI > Panel,并命名为BroadcasterSelectionPanel。在你的Canvas object > Canvas Scalar component > UI Scale Mode > select Scale With Screen Size。这样可以在不同的设备和分辨率下保持统一的画布。在直播选择面板中,将矩形变换的属性设置为左: 250, 顶部: 200, 右: 250, 底部: 50.
按钮
接下来,你将创建按钮和文本输入栏。右键单击BroadcasterSelectionPanel > UI > Button > UI > InputField。这样做两次,以制作两个按钮。我的按钮看起来像这样:
通过在检查器中移除其名称旁边的复选标记,将BroadcasterSelectionPanel切换到关闭状态。
注意:在一个场景中有多个联网的玩家,他们所有的用户界面面板都会显示,除非切换为关闭。如果我们想只显示本地用户的用户界面,我们可以通过代码切换它。
配置文件选择脚本
现在,你将创建一个脚本,它将接收来自你刚刚创建的用户界面的输入并相应地处理这些情况。创建一个名为ProfileSelection.cs的脚本。
在ProfileSelection中,确保包括agora_gaming_rtc;,并替换为Public class ProfileSelection : MonoBehaviour改为Public class ProfileSelection : Photon.PunBehaviour。这使你能够访问PUN服务器,而且PunBehaviour使你能够访问你在其他地方看不到的PUN回调。
首先,你要为脚本创建你的属性:
using System.Collections;
using UnityEngine;
using agora_gaming_rtc;
public class ProfileSelection : Photon.PunBehaviour
{
private bool isBroadcaster;
private AgoraProfile agoraScript;
private IRtcEngine agoraEngine;
[Header("UI Elements")]
[SerializeField]
private GameObject BroadCastSelectionPanel;
[Header("Broadcaster")]
[SerializeField]
private Material broadcasterMaterial;
[SerializeField]
private SkinnedMeshRenderer vikingMesh;
void Start()
{
if(photonView.isMine)
{
agoraEngine = null;
isBroadcaster = false;
BroadCastSelectionPanel.SetActive(false);
agoraScript = GetComponent<AgoraProfile>();
StartCoroutine(AgoraEngineSetup());
}
}
接下来,你将初始化Agora引擎。引擎本身是静态的,并且在场景中有一个单独的实例。从每个玩家那里,我们寻找这个引擎并访问它。因为你是在一个联网的演示中,从玩家到引擎的同步可能会有一点延迟,所以我们创建了一个超时函数,要么在3秒内检索引擎,要么出现错误:
IEnumerator AgoraEngineSetup()
{
if (photonView.isMine)
{
float engineTimer = 0f;
float engineTimeout = 3f;
while (agoraEngine == null)
{
agoraEngine = AgoraEngine.mRtcEngine;
engineTimer += Time.deltaTime;
if (engineTimer >= engineTimeout)
{
Debug.LogError("InCallStats AgoraEngineSetup() Failure - No Agora Engine Found.");
yield break;
}
yield return null;
}
agoraEngine.SetChannelProfile(CHANNEL_PROFILE.CHANNEL_PROFILE_LIVE_BROADCASTING);
BroadCastSelectionPanel.SetActive(true);
}
}
成功设置引擎后,将显示broadcastselectionpanel,其中包含按钮和一个单击时没有功能的输入字段。创建一个名为ButtonSetBroadcastState(布尔是NewStateBroadcaster)的函数。
public void ButtonSetBroadCastState(bool isNewStateBroadcaster)
{
if (photonView.isMine)
{
if (isNewStateBroadcaster)
{
agoraEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_BROADCASTER);
isBroadcaster = true;
TurnVikingGold();
}
else
{
agoraEngine.SetClientRole(CLIENT_ROLE_TYPE.CLIENT_ROLE_AUDIENCE);
isBroadcaster = false;
}
BroadCastSelectionPanel.SetActive(false);
agoraScript.JoinChannel();
}
}
注意:当我创建专门用于分配给按钮的功能时,我喜欢在它们前面加上 “Button”,这样你就能在检查器中轻松找到它们。
此项目是开发环境,仅供参考,请勿直接用于生产环境 。对于在生产环境中运行的所有RTE应用程序,推荐使用Token鉴权。关于Agora平台内基于Token鉴权的更多信息,请参考 本指南。
接下来,创建一个名为TurnVikingGold的函数。如果用户是主播,这个函数将通过网络发送一个“远程过程调用”(RPC)来更改必要的viking gold:
public void TurnVikingGold()
{
if (isBroadcaster)
{
photonView.RPC("UpdateBroadcasterMaterial", PhotonTargets.All);
}
}
[PunRPC]
public void UpdateBroadcasterMaterial()
{
vikingMesh.material = broadcasterMaterial;
}
注意[PunRPC]属性,没有它,该函数将只在本地调用。也就是说,它将在你的机器上把维京人变为金色,但是当你看另一个和你一起在游戏中的客户端时,你在他们的屏幕上就不会是金色了!
最后,我们将增加一个功能,在人们开始加入实时音视频时同步主播/观众的状态。如果某人是主播,但另一个人在他们激活该状态后加入,新加入的人就不会看到主播的黄金材料。
public override void OnPhotonPlayerConnected(PhotonPlayer newPlayer)
{
if(photonView.isMine)
{
base.OnPhotonPlayerConnected(newPlayer);
TurnVikingGold();
}
}
这就是继承Photon.PunBehaviour的关键所在,因为没有它,这个Photon事件就无法访问。
随着轮廓选择脚本的完成,现在是在检查器中分配适当元素的时候了。确保ProfileSelection被连接到你的Charprefab。把BroadcasterSelectionPanel拖到自命名的变量槽中。通过在资产窗格中右键点击Assets pane > Create > Material,创建一个名为Gold的金色材料,并将其拖入Broadcaster材质槽中。对于维京人的网格,选择Charprefab > Viking > BaseHuman。
在你的BroadcasterSelectionPanel按钮中,一个分配主播状态,另一个分配观众状态。对于每个按钮,如果OnClick()列表是空的,点击加号(+)来创建一个功能槽。把Charprefab对象拖到槽中。点击标题为 "No Function > ProfileSelection > ButtonSetBroadcastState "的下拉菜单。对于Broadcaster按钮,确保复选框被选中,Audience复选框未被选中。
注意:这个切换复选框的出现是因为我们的按钮函数有一个布尔参数,允许你从 Hierarchy 用户界面分配布尔状态。
Agora简介脚本
作为演示的最后一部分,你将创建AgoraProfile脚本,显示关于播放器的有用属性,容纳回调,并在主播说话时显示一个用户界面指示器。
创建一个名为AgoraProfile.cs的脚本,并将其附加到Charprefab上,就像你对ProfileSelection.cs所做的那样。
首先,设置驱动该脚本所需的变量,并继承Photon.PunBehaviour。
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using agora_gaming_rtc;
public class AgoraProfile : Photon.PunBehaviour
{
[Header("Agora Properties")]
[SerializeField]
private string channel;
[SerializeField]
private uint myUID = 0;
[SerializeField]
CLIENT_ROLE_TYPE myClientRole;
[Header("UI Elements")]
[SerializeField]
private Text text_ChannelName;
[SerializeField]
private GameObject chatBubble;
private bool isLocalPlayerTalking = false;
[SerializeField]
private float talkBubbleBuffer = .75f;
private bool isSmoothingTalkBubble = false;
在启动方法中,你初始化回调和必要的变量。
void Start()
{
if (photonView.isMine)
{
AgoraEngine.mRtcEngine.OnJoinChannelSuccess = OnJoinChannelSuccessHandler;
AgoraEngine.mRtcEngine.OnUserJoined = OnUserJoinedHandler;
AgoraEngine.mRtcEngine.OnLeaveChannel = OnLeaveChannelHandler;
AgoraEngine.mRtcEngine.OnUserOffline = OnUserOfflineHandler;
AgoraEngine.mRtcEngine.OnClientRoleChanged = OnClientRoleChangedHandler;
AgoraEngine.mRtcEngine.OnVolumeIndication = OnVolumeChangedHandler;
myClientRole = CLIENT_ROLE_TYPE.CLIENT_ROLE_AUDIENCE;
isSmoothingTalkBubble = false;
}
}
接下来,创建连接频道的方法:
public void JoinChannel()
{
AgoraEngine.mRtcEngine.EnableAudioVolumeIndication(250, 3, true);
channel = text_ChannelName.text;
AgoraEngine.mRtcEngine.JoinChannel(text_ChannelName.text, null, 0);
}
接下来,你将创建标准的Agora回调。在这个例子中,它们是用来调试的。但是看看它们是如何工作的,这一点很重要。
注意:我把它们藏在#region里,这样你就可以把回调折叠起来。我觉得region非常有帮助。你可以自由地使用它们或不使用!
#region Agora Callbacks
// Local Client Joins Channel.
private void OnJoinChannelSuccessHandler(string channelName, uint uid, int elapsed)
{
if (!photonView.isMine)
return;
Debug.Log("Local user joined - uid: " + uid);
myUID = uid;
}
// Remote Client Joins Channel.
private void OnUserJoinedHandler(uint uid, int elapsed)
{
if (!photonView.isMine)
return;
Debug.Log("Remote user joined - uid: " + uid);
}
// Local user leaves channel.
private void OnLeaveChannelHandler(RtcStats stats)
{
if (!photonView.isMine)
return;
Debug.Log("Local user left channel");
}
// Remote User Leaves the Channel.
private void OnUserOfflineHandler(uint uid, USER_OFFLINE_REASON reason)
{
if (!photonView.isMine)
return;
Debug.Log("Remote user left - uid: " + uid);
}
private void OnClientRoleChangedHandler(CLIENT_ROLE_TYPE oldRole, CLIENT_ROLE_TYPE newRole)
{
myClientRole = newRole;
}
#endregion
联网的语音气泡
作为点睛之笔,你将添加一个说话的气泡,它会出现在所有主播的头上,当他们说完后就会消失。回调对说话开始和停止的时间是非常精确的,如果没有一些额外的爱护,看起来会有点不流畅。我将向你展示如何使说话的气泡平滑,使它在有人说话时看起来更自然。如果你在跟随的话,从回购中抓取语音泡泡资产,或者使用你自己的。
首先,创建两个函数,用于切换语音气泡的开启和关闭。
[PunRPC]
public void DisableSpeechBubble()
{
chatBubble.SetActive(false);
}
[PunRPC]
public void ActivateSpeechBubble()
{
print("my uid: " + myUID);
chatBubble.SetActive(true);
}
接下来,创建在音量变化时启动的回调。
private void OnVolumeChangedHandler(AudioVolumeInfo[] speakers, int speakerNumber, int totalVolume)
{
// if there is anyone speaking
if (speakers != null)
{
for (int i = 0; i < speakers.Length; i++)
{
//Debug.Log("speaker: " + i);
// If speaker uid == 0, that signifies you have the local player
if (speakers[i].uid == 0)
{
if (speakers[i].vad == 0)
{
isLocalPlayerTalking = false;
}
else
{
isLocalPlayerTalking = true;
StartCoroutine(SpeechBubbleSmoothing());
photonView.RPC("ActivateSpeechBubble", PhotonTargets.All);
}
}
}
}
}
这将检查本地玩家是否在说话。如果他们在说话,就在他们的头顶上放置一个联网的语音气泡。你想在玩家开始说话的时候就出现说话的气泡,所以不需要进行平滑处理。
下一个函数的目的是在隐藏语音气泡之前提供一个轻微的延迟,以防止主播暂停、呼吸等。
代码的逻辑看起来像这样:
- 当玩家停止说话时,激活talkBubbleDisableTimer。
- 如果玩家再次开始说话,将talkBubbleDisableTimer重置为其最大值。
- 如果玩家停止说话直到定时器达到0,则禁用说话的气泡,并退出协同程序。
注意:通过检查isSmoothingTalkBubble是否为真,该协同程序被多次调用 “屏蔽”。如果一个协同程序已经在运行,所有试图禁用通话气泡的尝试都将被否定,直到原始协同程序被停止。
private IEnumerator SpeechBubbleSmoothing()
{
if(isSmoothingTalkBubble == true)
{
yield break;
}
isSmoothingTalkBubble = true;
float talkBubbleDisableTimer = talkBubbleBuffer;
while(talkBubbleDisableTimer > 0f)
{
if(isLocalPlayerTalking)
{
talkBubbleDisableTimer = talkBubbleBuffer;
}
talkBubbleDisableTimer -= Time.deltaTime;
yield return null;
}
photonView.RPC("DisableSpeechBubble", PhotonTargets.All);
isSmoothingTalkBubble = false;
}
注意:我把talkBubbleBuffer(起始定时器量)设置为0.75f,这是人的平均反应时间(0.75秒)。这是一个很好的默认值,对于像这样的生活质量设计技巧来说,要牢记在心,因为任何明显低于这个值的东西都可能使说话的气泡过于抖动,而任何明显高于这个值的东西都可能使说话的气泡停留时间过长。玩一玩你认为最合适的东西吧!
最后一件事。在你结束之前,你需要适当地清理一下引擎。
包括回调:
private IEnumerator OnLeftRoom()
{
//Wait untill Photon is properly disconnected (empty room, and connected back to main server)
while (PhotonNetwork.room != null || PhotonNetwork.connected == false)
yield return 0;
IRtcEngine.Destroy();
}
在Photon的演示中,有一个按钮可以让你离开大厅。这对于调试检查不同的频道或玩家状态确实很有帮助,但如果Agora引擎正在运行,它可能会导致错误。
结论
现在是测试的时候了! 如果你学到了什么,请在#unity-help-me slack频道发布,并直接给我留言,同时你也可以教教其他人
如果你想为这个项目和更广泛的Agora社区做出贡献,请随时在GitHub 上提交拉动请求,并将你的修改加入到项目中来。
获取更多文档、Demo、技术帮助
- 获取 SDK 开发文档,可访问声网文档中心。
- 如需参考各类场景 Demo,可访问下载中心获取。
- 如遇开发疑难,可访问论坛发帖提问。
- 了解更多教程、RTE 技术干货与技术活动,可访问声网开发者社区。
- 欢迎扫码关注我们。