教你用 C++ 在 Unreal 中为游戏增加实时音视频互动(上)

我们已经上线了 Agora Unreal SDK,提供了支持 Blueprint 和 C++ 的两个版本 SDK。我们分享了如何基于 Blueprint 在游戏中创建实时音视频功能。在本文中,我们来分享如何基于声网 Agora Unreal SDK C++版本,在游戏中实现实时音视频功能。

本篇教程较长,建议在 Web 浏览器端浏览,体验更好。

准备工作

需要的开发环境和需要准备的与 Blueprint 一样:

  • Unreal 4.34 以上版本
  • Visual Studio 或 Xcode(版本根据 Unreal 配置要求而定)
  • 运行 Windows 7 以上系统的 PC 或 一台 Mac
  • Agora 注册账号一枚(免费注册,见官网 Agora.io
  • 如果你的企业网络存在防火墙,请在声网文档中心搜索「应用企业防火墙限制」,进行配置。

新建项目

如果你已经有 Unreal 项目了,可以跳过这一步。在 Unreal 中创建一个 C++类型的项目。

image

确保在 [your_project]/Source/[project_name]/[project_name].Build.cs文件的 PrivateDependencyModuleNames一行,去掉注释。Unreal 默认是将它注释掉的,这会导致在编译的时候报错。

1.  // Uncomment if you are using Slate UI
2. PrivateDependencyModuleNames.AddRange(new string[] { "UMG", "Slate", "SlateCore" });

接下来我们在项目中集成 Agora SDK

1.将 SDK 复制到这个路径下 [your_project]/Plugins

2.把插件依赖添加到[your_project]/Source/[project_name]/[project_name].Build.cs文件的私有依赖(Private Dependencies)部分 PrivateDependencyModuleNames.AddRange(new string[] { “AgoraPlugin”, “AgoraBlueprintable” });

3.重启 Unreal

4.点击 Edit->Plugin,在分类中找到 Project->Other,确定插件已经生效

image

创建新的 Level

接下来我们将创建一个新的 Level,在那里建立我们的游戏环境。有几种不同的方法可以创建一个新的 Level,我们将使用文件菜单的方法,其中列出了关卡选择选项。

在虚幻编辑器里面,点击文件菜单选项,然后选择新建 Level…

image

然后会打开一个新的对话框。

image

选择Empty Level ,然后指定一个存储的路径。

创建核心类

在这里我们要创建两个类:VideoFrameObserver 和VideoCall C++ Class。他们会负责与 Agora SDK 进行通信。

首先是 VideoFrameObserver。VideoFrameObserver 执行的是 agora::media::IVideoFrameObserver。这个方法在 VideoFrameObserver 类中负责管理视频帧的回调。它是用 registerVideoFrameObserver 在 agora::media::IMediaEngine 中注册的。

在 Unreal 编辑器中,选择 File->Add New C++ Class。

image

父类谁定为 None,然后点击下一步。

image

为 VideoFrameObserver明明,然后选择 Create Class。
image

创建 VideoFrameObserver 类接口。

打开 VideoFrameObserver.h 文件然后添加如下代码:


1 //VideoFrameObserver.h
2 
3 #include "CoreMinimal.h"
4 
5 #include <functional>
6 
7 #include "AgoraMediaEngine.h"
8 
9 class AGORAVIDEOCALL_API VideoFrameObserver : public agora::media::IVideoFrameObserver
10 {
11 public:
12   virtual ~VideoFrameObserver() = default;
13 public:
14   bool onCaptureVideoFrame(VideoFrame& videoFrame) override;
15
16   bool onRenderVideoFrame(unsigned int uid, VideoFrame& videoFrame) override;
17
18  void setOnCaptureVideoFrameCallback(
19    std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> callback);
20
21   void setOnRenderVideoFrameCallback(
22    std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> callback);
23
24  virtual VIDEO_FRAME_TYPE getVideoFormatPreference() override { return FRAME_TYPE_RGBA; }
25 
26 private:
27
28  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnCaptureVideoFrame;
29  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnRenderVideoFrame;
30 };

AGORAVIDEOCALL_API 是项目依赖的定义,而不是由Unreal 生成的你自己的定义。

重写onCaptureVideoFrame/onRenderVideoFrame方法

onCaptureVideoFrame 会获取到摄像头捕获的画面,转换为 ARGB 格式并触发 OnCaptureVideoFrame 回调。

onRenderVideoFrame 讲收到的特定用户画面转换为 ARGB 格式,然后触发 onRenderVideoFrame 回调。


//VideoFrameObserver.cpp

bool VideoFrameObserver::onCaptureVideoFrame(VideoFrame& Frame)
{
   const auto BufferSize = Frame.yStride*Frame.height;

  if (OnCaptureVideoFrame)
  {
    OnCaptureVideoFrame( static_cast< uint8_t* >( Frame.yBuffer ), Frame.width, Frame.height, BufferSize );
  }

  return true;
}

bool VideoFrameObserver::onRenderVideoFrame(unsigned int uid, VideoFrame& Frame)
{
  const auto BufferSize = Frame.yStride*Frame.height;

  if (OnRenderVideoFrame)
  {
    OnRenderVideoFrame( static_cast<uint8_t*>(Frame.yBuffer), Frame.width, Frame.height, BufferSize );
  }

  return true;
}

增加setOnCaptureVideoFrameCallback/setOnRenderVideoFrameCallback方法。

设定回调,用来获取摄像头获取到的本地画面和远端的画面。

//VideoFrameObserver.cpp

void VideoFrameObserver::setOnCaptureVideoFrameCallback(
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> Callback)
{
  OnCaptureVideoFrame = Callback;
}

void VideoFrameObserver::setOnRenderVideoFrameCallback(
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> Callback)
{
  OnRenderVideoFrame = Callback;
}

创建视频通话C++类

VideoCall 类管理与 Agora SDK 的通信。需要创建多个方法和接口。

创建类接口

回到 Unreal 编辑器,再创建一个新的 C++类,命名为 VideoCall.h。然后进入VideoCall.h文件,添加一下接口:

//VideoCall.h

#pragma once

#include "CoreMinimal.h"

#include <functional>
#include <vector>

#include "AgoraRtcEngine.h"
#include "AgoraMediaEngine.h"

class VideoFrameObserver;

class AGORAVIDEOCALL_API VideoCall
{
public:
  VideoCall();
  ~VideoCall();

  FString GetVersion() const;

  void RegisterOnLocalFrameCallback(
    std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnLocalFrameCallback);
  void RegisterOnRemoteFrameCallback(
    std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnRemoteFrameCallback);

  void StartCall(
    const FString& ChannelName,
    const FString& EncryptionKey,
    const FString& EncryptionType);

  void StopCall();

  bool MuteLocalAudio(bool bMuted = true);
  bool IsLocalAudioMuted();

  bool MuteLocalVideo(bool bMuted = true);
  bool IsLocalVideoMuted();

  bool EnableVideo(bool bEnable = true);

private:
  void InitAgora();

private:
  TSharedPtr<agora::rtc::ue4::AgoraRtcEngine> RtcEnginePtr;
  TSharedPtr<agora::media::ue4::AgoraMediaEngine> MediaEnginePtr;

  TUniquePtr<VideoFrameObserver> VideoFrameObserverPtr;

  //callback
  //data, w, h, size
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnLocalFrameCallback;
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnRemoteFrameCallback;

  bool bLocalAudioMuted = false;
  bool bLocalVideoMuted = false;
};

创建初始化方法

进入 VideoCall.cpp 文件,添加以下代码:


//VideoCall.cpp

#include "AgoraVideoDeviceManager.h"
#include "AgoraAudioDeviceManager.h"

#include "MediaShaders.h"

#include "VideoFrameObserver.h"

用agora::rtc::ue4::AgoraRtcEngine::createAgoraRtcEngine()创建引擎,初始化 RtcEnginePtr 变量。创建一个RtcEngineContext对象,然后在ctx.eventHandler 和ctx.appId中设定 event handler 和 App ID 。初始化引擎,并创建AgoraMediaEngine对象,初始化 MediaEnginePtr。

//VideoCall.cpp

VideoCall::VideoCall()
{
  InitAgora();
}

VideoCall::~VideoCall()
{
  StopCall();
}

void VideoCall::InitAgora()
{
  RtcEnginePtr = TSharedPtr<agora::rtc::ue4::AgoraRtcEngine>(agora::rtc::ue4::AgoraRtcEngine::createAgoraRtcEngine());

  static agora::rtc::RtcEngineContext ctx;
  ctx.appId = "aab8b8f5a8cd4469a63042fcfafe7063";
  ctx.eventHandler = new agora::rtc::IRtcEngineEventHandler();

  int ret = RtcEnginePtr->initialize(ctx);
  if (ret < 0)
  {
    UE_LOG(LogTemp, Warning, TEXT("RtcEngine initialize ret: %d"), ret);
  }
  MediaEnginePtr = TSharedPtr<agora::media::ue4::AgoraMediaEngine>(agora::media::ue4::AgoraMediaEngine::Create(RtcEnginePtr.Get()));
}

FString VideoCall::GetVersion() const
{
  if (!RtcEnginePtr)
  {
    return "";
  }
  int build = 0;
  const char* version = RtcEnginePtr->getVersion(&build);
  return FString(ANSI_TO_TCHAR(version));
}

创建回调方法

接下来创建回调方法,返回本地和远端的视频帧


//VideoCall.cpp

void VideoCall::RegisterOnLocalFrameCallback(
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnFrameCallback)
{
  OnLocalFrameCallback = std::move(OnFrameCallback);
}

void VideoCall::RegisterOnRemoteFrameCallback(
  std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnFrameCallback)
{
  OnRemoteFrameCallback = std::move(OnFrameCallback);
}

创建呼叫方法

我们需要利用这个方法来实现“加入频道”和“离开频道”。

增加 StartCall

首先创建 VideoFrameObserver 对象,然后根据你的场景来设置以下回调。

  • OnLocalFrameCallback:用于 SDK 获取本地摄像头采集到的视频帧。
  • OnRemoteFrameCallback:用于 SDK 获取远端摄像头采集到的视频帧。

在 InitAgora 的 MediaEngine 对象中通过 registerVideoFrameObserver 方法注册 VideoFrameObserver。为了保证 EncryptionType 和 EncryptionKey 不为空,需要先设置 EncryptionMode 和 EncryptionSecret。然后按照你的需要来设置频道参数,并调用 joinChannel。

//VideoCall.cpp

void VideoCall::StartCall(
  const FString& ChannelName,
  const FString& EncryptionKey,
  const FString& EncryptionType)
{
  if (!RtcEnginePtr)
  {
    return;
  }
  if (MediaEnginePtr)
  {
    if (!VideoFrameObserverPtr)
    {
      VideoFrameObserverPtr = MakeUnique<VideoFrameObserver>();

      std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnCaptureVideoFrameCallback
        = [this](std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size)
      {
        if (OnLocalFrameCallback)
        {
          OnLocalFrameCallback(buffer, width, height, size);
        }
        else { UE_LOG(LogTemp, Warning, TEXT("VideoCall OnLocalFrameCallback isn't set")); }
      };
      VideoFrameObserverPtr->setOnCaptureVideoFrameCallback(std::move(OnCaptureVideoFrameCallback));

      std::function<void(std::uint8_t*, std::uint32_t, std::uint32_t, std::uint32_t)> OnRenderVideoFrameCallback
        = [this](std::uint8_t* buffer, std::uint32_t width, std::uint32_t height, std::uint32_t size)
      {
        if (OnRemoteFrameCallback)
        {
          OnRemoteFrameCallback(buffer, width, height, size);
        }
        else { UE_LOG(LogTemp, Warning, TEXT("VideoCall OnRemoteFrameCallback isn't set")); }
      };
      VideoFrameObserverPtr->setOnRenderVideoFrameCallback(std::move(OnRenderVideoFrameCallback));
    }

    MediaEnginePtr->registerVideoFrameObserver(VideoFrameObserverPtr.Get());
  }

    int nRet = RtcEnginePtr->enableVideo();
    if (nRet < 0)
    {
        UE_LOG(LogTemp, Warning, TEXT("enableVideo : %d"), nRet)
    }

  if (!EncryptionType.IsEmpty() && !EncryptionKey.IsEmpty())
  {
    if (EncryptionType == "aes-256")
    {
      RtcEnginePtr->setEncryptionMode("aes-256-xts");
    }
    else
    {
      RtcEnginePtr->setEncryptionMode("aes-128-xts");
    }

    nRet = RtcEnginePtr->setEncryptionSecret(TCHAR_TO_ANSI(*EncryptionKey));
    if (nRet < 0)
    {
      UE_LOG(LogTemp, Warning, TEXT("setEncryptionSecret : %d"), nRet)
    }
  }

  nRet = RtcEnginePtr->setChannelProfile(agora::rtc::CHANNEL_PROFILE_COMMUNICATION);
  if (nRet < 0)
  {
    UE_LOG(LogTemp, Warning, TEXT("setChannelProfile : %d"), nRet)
  }
  //"demoChannel1";
  std::uint32_t nUID = 0;
  nRet = RtcEnginePtr->joinChannel(NULL, TCHAR_TO_ANSI(*ChannelName), NULL, nUID);
  if (nRet < 0)
  {
    UE_LOG(LogTemp, Warning, TEXT("joinChannel ret: %d"), nRet);
  }
}

增加 StopCall 功能

根据你的场景需要,通过调用 leaveChannel 方法来结束通话,比如当要结束通话的时候,当你需要关闭应用的时候,或是当你的应用运行于后台的时候。调用 nullptr 作为实参的 registerVideoFrameObserver,用来取消 VideoFrameObserver的注册。


//VideoCall.cpp

void VideoCall::StopCall()
{
  if (!RtcEnginePtr)
  {
    return;
  }
  auto ConnectionState = RtcEnginePtr->getConnectionState();
  if (agora::rtc::CONNECTION_STATE_DISCONNECTED != ConnectionState)
  {
    int nRet = RtcEnginePtr->leaveChannel();
    if (nRet < 0)
    {
      UE_LOG(LogTemp, Warning, TEXT("leaveChannel ret: %d"), nRet);
    }
    if (MediaEnginePtr)
    {
      MediaEnginePtr->registerVideoFrameObserver(nullptr);
    }
  }
}

创建 Video 方法
这些方法是用来管理视频的。

加 EnableVideo() 方法

EnableVideo() 会启用本示例中的视频。初始化 nRet,值为 0。如果 bEnable 为 true,则通过 RtcEnginePtr->enableVideo() 启用视频。否则,通过 RtcEnginePtr->disableVideo() 关闭视频。


//VideoCall.cpp

bool VideoCall::EnableVideo(bool bEnable)
{
  if (!RtcEnginePtr)
  {
    return false;
  }
  int nRet = 0;
  if (bEnable)
    nRet = RtcEnginePtr->enableVideo();
  else
    nRet = RtcEnginePtr->disableVideo();
  return nRet == 0 ? true : false;
}

增加 MuteLocalVideo() 方法

MuteLocalVideo() 方法负责开启或关闭本地视频。在其余方法完成运行之前,需要保证 RtcEnginePtr 不为 nullptr。如果可以成功mute 或 unmute 本地视频,那么把 bLocalVideoMuted 设置为 bMuted。

//VideoCall.cpp

bool VideoCall::MuteLocalVideo(bool bMuted)
{
  if (!RtcEnginePtr)
  {
    return false;
  }
  int ret = RtcEnginePtr->muteLocalVideoStream(bMuted);
  if (ret == 0)
    bLocalVideoMuted = bMuted;

  return ret == 0 ? true : false;
}

增加 IsLocalVideoMuted() 方法

IsLocalVideoMuted() 方法的作用是,当本地视频开启或关闭的时候,返回 bLocalVideoMuted。


//VideoCall.cpp

bool VideoCall::IsLocalVideoMuted()
{
  return bLocalVideoMuted;
}

创建音频相关的方法

这些方法是用来管理音频的。

添加 MuteLocalAudio() 方法

MuteLocalAudio()用于 mute 或 unmute 本地音频:


//VideoCall.cpp

bool VideoCall::MuteLocalAudio(bool bMuted)
{
  if (!RtcEnginePtr)
  {
    return false;
  }
  int ret = RtcEnginePtr->muteLocalAudioStream(bMuted);
  if (ret == 0)
    bLocalAudioMuted = bMuted;

  return ret == 0 ? true : false;
}

增加 IsLocalAudioMuted() 方法

IsLocalAudioMuted()方法的作用是,当 mute 或 unmute 本地音频的时候,返回 bLocalAudioMuted。

//VideoCall.cpp

bool VideoCall::IsLocalAudioMuted()
{
  return bLocalAudioMuted;
}

创建 GUI

接下来就是要为一对一对话创建用户交互界面了,包括:

  • 创建 VideoCallPlayerController
  • 创建 EnterChannelWidget C++ Class
  • 创建 VideoViewWidget C++ Class
  • 创建 VideoCallViewWidget C++ Class
  • 创建 VideoCallWidget C++ Class
  • 创建 BP_EnterChannelWidget blueprint asset
  • 创建 BP_VideoViewWidget Asset
  • 创建 BP_VideoCallViewWidget Asset
  • 创建 BP_VideoCallWidget Asset
  • 创建 BP_VideoCallPlayerController blueprint asset
  • 创建 BP_AgoraVideoCallGameModeBase Asset
  • 修改 Game Mode

创建 VideoCallPlayerController

为了能够将我们的Widget Blueprints添加到Viewport中,我们创建我们的自定义播放器控制器类。

在 "内容浏览器 "中,按 "Add New "按钮,选择 “新建C++类”。在 "添加C++类 "窗口中,勾选 "显示所有类 "按钮,并输入PlayerController。按 "下一步 "按钮,给类命名为 VideoCallPlayerController。按Create Class按钮。

image

image


//VideoCallPlayerController.h

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "VideoCallPlayerController.generated.h"

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:
};

这个类是 BP_VideoCallPlayerController 的 Blueprint Asset 的基类,我们将在最后创建。

增加需要的 Include

在 VideoCallPlayerController.h 文件的头部包括了所需的头文件。

//VideoCallPlayerController.h

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "Templates/UniquePtr.h"

#include "VideoCall.h"

#include "VideoCallPlayerController.generated.h"
//VideoCallPlayerController.cpp

#include "Blueprint/UserWidget.h"

#include "EnterChannelWidget.h"
#include "VideoCallWidget.h"

类声明

为下一个类添加转发声明:

//VideoCallPlayerController.h
class UEnterChannelWidget;
class UVideoCallWidget;

稍后我们将跟进其中的两个创建,即 UEnterChannelWidget 和 UVideoCallWidget。

添加成员变量

现在,在编辑器中添加成员引用到 UMG Asset 中。

//VideoCallPlayerController.h

...

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:

  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets")
    TSubclassOf<class UUserWidget> wEnterChannelWidget;

  UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Widgets")
    TSubclassOf<class UUserWidget> wVideoCallWidget;

  ...
};

变量来保持创建后的小部件,以及一个指向VideoCall的指针。


//VideoCallPlayerController.h

...

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:

  ...

  UEnterChannelWidget* EnterChannelWidget = nullptr;

  UVideoCallWidget* VideoCallWidget = nullptr;

  TUniquePtr<VideoCall> VideoCallPtr;

  ...
};

覆盖 BeginPlay/EndPlay

//VideoCallPlayerController.h

...

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:

  ...

  void BeginPlay() override;

  void EndPlay(const EEndPlayReason::Type EndPlayReason) override;

  ...
};
//VideoCallPlayerController.cpp

void AVideoCallPlayerController::BeginPlay()
{
  Super::BeginPlay();

  //initialize wigets
  if (wEnterChannelWidget) // Check if the Asset is assigned in the blueprint.
  {
    // Create the widget and store it.
    if (!EnterChannelWidget)
    {
      EnterChannelWidget = CreateWidget<UEnterChannelWidget>(this, wEnterChannelWidget);
      EnterChannelWidget->SetVideoCallPlayerController(this);
    }
    // now you can use the widget directly since you have a referance for it.
    // Extra check to  make sure the pointer holds the widget.
    if (EnterChannelWidget)
    {
      //let add it to the view port
      EnterChannelWidget->AddToViewport();
    }
    //Show the Cursor.
    bShowMouseCursor = true;
  }
  if (wVideoCallWidget)
  {
    if (!VideoCallWidget)
    {
      VideoCallWidget = CreateWidget<UVideoCallWidget>(this, wVideoCallWidget);
      VideoCallWidget->SetVideoCallPlayerController(this);
    }
    if (VideoCallWidget)
    {
      VideoCallWidget->AddToViewport();
    }
    VideoCallWidget->SetVisibility(ESlateVisibility::Collapsed);
  }

  //create video call and switch on the EnterChannelWidget
  VideoCallPtr = MakeUnique<VideoCall>();


  FString Version = VideoCallPtr->GetVersion();
  Version = "Agora version: " + Version;
  EnterChannelWidget->UpdateVersionText(Version);

  SwitchOnEnterChannelWidget(std::move(VideoCallPtr));
}

void AVideoCallPlayerController::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
  Super::EndPlay(EndPlayReason);
}

这时你可能注意到EnterChannelWidget和VideoCallWidget方法被标记为错误,那是因为它们还没有实现。我们将在接下来的步骤中实现它们。

增加 StartCall/EndCall


//VideoCallPlayerController.h

...

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:

  ...

  void StartCall(
    TUniquePtr<VideoCall> PassedVideoCallPtr,
    const FString& ChannelName,
    const FString& EncryptionKey,
    const FString& EncryptionType
    );

  void EndCall(TUniquePtr<VideoCall> PassedVideoCallPtr);

  ...
};
//VideoCallPlayerController.cpp

void AVideoCallPlayerController::StartCall(
  TUniquePtr<VideoCall> PassedVideoCallPtr,
  const FString& ChannelName,
  const FString& EncryptionKey,
  const FString& EncryptionType)
{
  SwitchOnVideoCallWidget(std::move(PassedVideoCallPtr));

  VideoCallWidget->OnStartCall(
    ChannelName,
    EncryptionKey,
    EncryptionType);
}

void AVideoCallPlayerController::EndCall(TUniquePtr<VideoCall> PassedVideoCallPtr)
{
  SwitchOnEnterChannelWidget(std::move(PassedVideoCallPtr));
}

增加打开另一个小工具的方法

//VideoCallPlayerController.h

...

UCLASS()
class AGORAVIDEOCALL_API AVideoCallPlayerController : public APlayerController
{
  GENERATED_BODY()

public:

  ...

  void SwitchOnEnterChannelWidget(TUniquePtr<VideoCall> PassedVideoCallPtr);
  void SwitchOnVideoCallWidget(TUniquePtr<VideoCall> PassedVideoCallPtr);

  ...
};
//VideoCallPlayerController.cpp

void AVideoCallPlayerController::SwitchOnEnterChannelWidget(TUniquePtr<VideoCall> PassedVideoCallPtr)
{
  if (!EnterChannelWidget)
  {
    return;
  }

  EnterChannelWidget->SetVideoCall(std::move(PassedVideoCallPtr));
  EnterChannelWidget->SetVisibility(ESlateVisibility::Visible);
}

void AVideoCallPlayerController::SwitchOnVideoCallWidget(TUniquePtr<VideoCall> PassedVideoCallPtr)
{
  if (!VideoCallWidget)
  {
    return;
  }
  VideoCallWidget->SetVideoCall(std::move(PassedVideoCallPtr));
  VideoCallWidget->SetVisibility(ESlateVisibility::Visible);
}

创建 EnterChannelWidget C++类

EnterChannelWidget是负责管理 UI 元素交互的。我们要创建一个新的 UserWidget 类型的类。在内容浏览器中,按Add New按钮,选择New C++类,然后勾选Show All Classes按钮,输入UserWidget。按下 "下一步 "按钮,为类设置一个名称,EnterChannelWidget。
image
image
我们会得到如下代码:

//EnterChannelWidget.h

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "EnterChannelWidget.generated.h"

UCLASS()
class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget
{
  GENERATED_BODY()

};

在EnterChannelWidget.h文件中增加一些必要的 include:

//EnterCahnnelWidget.h

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/TextBlock.h"
#include "Components/RichTextBlock.h"
#include "Components/EditableTextBox.h"
#include "Components/ComboBoxString.h"
#include "Components/Button.h"
#include "Components/Image.h"

#include "VideoCall.h"

#include "EnterChannelWidget.generated.h"

class AVideoCallPlayerController;
//EnterCahnnelWidget.cpp

#include "Blueprint/WidgetTree.h"

#include "VideoCallPlayerController.h"

然后我们需要增加如下变量:

//EnterChannelWidget.h

...

UCLASS()
class AGORAVIDEOCALL_API UEnterChannelWidget : public UUserWidget
{
  GENERATED_BODY()

public:

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UTextBlock* HeaderTextBlock = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UTextBlock* DescriptionTextBlock = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UEditableTextBox* ChannelNameTextBox = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UEditableTextBox* EncriptionKeyTextBox = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UTextBlock* EncriptionTypeTextBlock = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UComboBoxString* EncriptionTypeComboBox = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UButton* JoinButton = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UButton* TestButton = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UButton* VideoSettingsButton = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UTextBlock* ContactsTextBlock = nullptr;

  UPROPERTY(VisibleAnywhere, BlueprintReadOnly, meta = (BindWidget))
    UTextBlock* BuildInfoTextBlock = nullptr;

  ...
};
注册登录 后评论
    // 作者
    声网技术社区 发布于 声网开发者社区
    • 0
    // 本帖子
    关键词
    // 相关帖子
    Coming soon...
    • 0
    教你用 C++ 在 Unreal 中为游戏增加实时音视频互动(上)声网技术社区 发布于 声网开发者社区