# 实现元语聊 (/zh-CN/solutions/meta-world/build/metachat)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
MetaWorld 解决方案支持元语聊、元直播场景。本文介绍如何实现元语聊。

## 示例项目 [#示例项目]

声网在 GitHub 上提供开源 [Agora-MetaWorld](https://github.com/AgoraIO-Community/Agora-MetaWorld/tree/dev_metasdk1.0) 示例项目供你参考。

如果你还需了解 Unity 部分的工程文件和功能指南，请联系 sales@shengwang.cn 获取。

## 实现元语聊 [#实现元语聊]

完成[集成声网 Meta SDK](/zh-CN/solutions/meta-world/get-started/integrate-sdk) 后，你可以参考本节实现元语聊。

下图展示实现元语聊的 API 调用时序：

<DocsImage alt="元语聊 API 时序图" src="https://web-cdn.agora.io/docs-portal/img/meta-world/meta-chat.svg" displayWidth="70%" />

实现步骤需用到如下类：

- `RtcEngine` 类：提供实时音视频功能的核心类。
- `IMetaService` 类：提供 Meta 服务的核心类。可用于获取场景资源列表、下载场景资源、删除本地场景资源等场景资源管理，还可用于创建 `IMetaScene`。
- `IMetaScene` 类：场景资源相关操作。
- `ILocalUserAvatar` 类：包含在 `IMetaScene` 中，生命周期和 `IMetaScene` 相同，用于设置虚拟形象 (Avatar)。
- `IMetaServiceEventHandler` 类：`IMetaService` 的异步方法的事件回调类。
- `IMetaSceneEventHandler` 类：`IMetaScene` 的异步方法的事件回调类。

### 1 创建并初始化 IMetaService [#1-创建并初始化-imetaservice]

调用 [`create`](/zh-CN/api-reference/rtc/android/initialize#api_irtcengine_initialize) 创建 `RtcEngine`。调用 `IMetaService` 类的 [`create`](/zh-CN/solutions/meta-world/reference/meta-api#create) 和 [`initialize`](/zh-CN/solutions/meta-world/reference/meta-api#initialize) 创建并初始化 `IMetaService`。

初始化 `IMetaService` 时，需要在 `MetaServiceConfig` 里设置如下重要的字段：

- `mRtcEngine`：通过 `create` 方法创建的 `RtcEngine` 实例。
- `mAppId`：在声网控制台获取的 App ID。详见[开通元语聊服务](/zh-CN/solutions/meta-world/build/enable-service)。
- `mUserId`：登录声网 RTM 系统的用户 ID。推荐取值详见 [`MetaServiceConfig`](/zh-CN/solutions/meta-world/reference/meta-api#metaserviceconfig)。
- `mRtmToken`：用于登录声网 RTM 系统的动态密钥。开启动态鉴权后可用。详见[生成 Token](/zh-CN/realtime-media/rtm/build/authentication-and-connection/token-generation)。
- `mLocalDownloadPath`：场景资源下载到本地的保存路径。
- `mEventHandler`：`IMetaService` 的回调事件句柄。

声网项目有两种 Token 和 UID，请不要搞混淆：

- RTC UID：用于在实时音视频通讯中标志用户身份的用户 ID。推荐取值详见 [joinChannel 的参数解释](/zh-CN/api-reference/rtc/android/channel#api_irtcengine_joinchannel2)。
- RTM UID：用于在云信令系统中标志用户身份的用户 ID。推荐取值详见 [MetaServiceConfig 的字段解释](/zh-CN/solutions/meta-world/reference/meta-api#metaserviceconfig)。
- RTC Token：用于保障实时音视频通讯安全的动态密钥。详见[如何生成 RTC Token 进行鉴权](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)。
- RTM Token：用于保障云信令系统安全的动态密钥。详见[如何生成 RTM Token 进行鉴权](/zh-CN/realtime-media/rtm/build/authentication-and-connection/user-authentication)。

```java
// 设置 RtcEngine 配置
RtcEngineConfig rtcConfig = new RtcEngineConfig();
rtcConfig.mContext = context;
// 声网项目的 App ID，从控制台获取
rtcConfig.mAppId = KeyCenter.APP_ID;
rtcConfig.mChannelProfile = Constants.CHANNEL_PROFILE_LIVE_BROADCASTING;
// 监听 RTC 引擎回调事件
rtcConfig.mEventHandler = new IRtcEngineEventHandler() {
    @Override
    public void onJoinChannelSuccess(String channel, int uid, int elapsed) {
        Log.d(TAG, String.format("onJoinChannelSuccess %s %d", channel, uid));
    }

    @Override
    public void onUserOffline(int uid, int reason) {
        Log.d(TAG, String.format("onUserOffline %d %d ", uid, reason));
    }

    @Override
    public void onAudioRouteChanged(int routing) {
        Log.d(TAG, String.format("onAudioRouteChanged %d", routing));
    }

    @Override
    public void onUserJoined(int uid, int elapsed) {
        Log.d(TAG, "onUserJoined uid=" + uid);
    }

    @Override
    public void onFirstRemoteVideoDecoded(int uid, int width, int height, int elapsed) {
        Log.d(TAG, "onFirstRemoteVideoDecoded uid=" + uid + ",width=" + width + ",heigh=" + height + ",elapsed=" + elapsed);
    }
};
rtcConfig.mAudioScenario = Constants.AudioScenario.getValue(Constants.AudioScenario.DEFAULT);

// 创建 RtcEngine
rtcEngine = RtcEngine.create(rtcConfig);
rtcEngine.setParameters("{\"rtc.enable_debug_log\":true}");

// 开启音视频模块并设置音频属性和路由
rtcEngine.enableAudio();
rtcEngine.enableVideo();
rtcEngine.setAudioProfile(
        Constants.AUDIO_PROFILE_DEFAULT, Constants.AUDIO_SCENARIO_GAME_STREAMING
);
rtcEngine.setDefaultAudioRoutetoSpeakerphone(true);

// 设置场景资源的下载路径
scenePath = context.getExternalFilesDir("").getPath();
{
   // 创建 IMetaService
    metaService = IMetaService.create();

    MetaServiceConfig config = new MetaServiceConfig() {{
        // RtcEngine 实例
        mRtcEngine = rtcEngine;
        // 声网项目的 App ID，从控制台获取
        mAppId = KeyCenter.APP_ID;
        // 声网 RTM（云信令）Token，保障安全
        // 声网项目有 RTC Token 和 RTM Token，不要搞混淆
        mRtmToken = KeyCenter.RTM_TOKEN;
        mLocalDownloadPath = scenePath;
        // 声网 RTM（云信令）UID，用户 ID，标志用户身份
        // 声网项目有 RTC UID 和 RTM UID，不要搞混淆
        mUserId = KeyCenter.RTM_UID;
        mEventHandler = MetaContext.this;
    }};
    // 初始化 IMetaService
    ret += metaService.initialize(config);
    Log.i(TAG, "launcher version=" + metaService.getLauncherVersion(context));
}
```

### 2 下载场景资源 [#2-下载场景资源]

调用 `IMetaService` 类的 [`getSceneAssetsInfo`](/zh-CN/solutions/meta-world/reference/meta-api#getsceneassetsinfo) 获取场景资源，并通过 `IMetaServiceEventHandler` 类的 [`onGetSceneAssetsInfoResult`](/zh-CN/solutions/meta-world/reference/meta-api#ongetsceneassetsinforesult) 回调监听获取场景资源时的事件。

调用 `IMetaService` 类的 [`downloadScene`](/zh-CN/solutions/meta-world/reference/meta-api#downloadscene) 获取场景资源，并通过 `IMetaServiceEventHandler` 类的 [`onDownloadSceneAssetsProgress`](/zh-CN/solutions/meta-world/reference/meta-api#ondownloadsceneassetsprogress) 回调监听获取场景资源时的事件。

```java
// 获取场景资源
public boolean getSceneInfos() {
    return metaService.getSceneAssetsInfo() == Constants.ERR_OK;
}

// 监听获取场景资源的回调事件
@Override
public void onGetSceneAssetsInfoResult(MetaSceneAssetsInfo[] metaSceneAssetsInfos, int errorCode) {
}

// 下载场景资源
public boolean downloadScene(MetaSceneAssetsInfo sceneInfo) {
    return metaService.downloadSceneAssets(sceneInfo.mSceneId) == Constants.ERR_OK;
}

// 监听下载场景资源的回调事件
@Override
public void onDownloadSceneAssetsProgress(long sceneId, int progress, int state) {
}
```

### 3 创建 IMetaScene [#3-创建-imetascene]

调用 [`createScene`](/zh-CN/solutions/meta-world/reference/meta-api#createscene) 创建 `IMetaScene`，并在 `sceneConfig` 中设置场景配置信息。通过 `IMetaServiceEventHandler` 类的 [`onCreateSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#oncreatesceneresult) 和 [`onConnectionStateChanged`](/zh-CN/solutions/meta-world/reference/meta-api#onconnectionstatechanged) 回调监听创建场景和连接状态的事件。

```java
// 设置场景配置信息
MetaSceneConfig sceneConfig = new MetaSceneConfig();
sceneConfig.mActivityContext = activityContext;
// 设置是否开启面部捕捉
// 元语聊场景无需开启面部捕捉
sceneConfig.mEnableFaceCapture = false;
// 传入面部捕捉插件的 App ID 和 Certificate
// 仅开启面部捕捉时才需要传值
sceneConfig.mFaceCaptureAppId = KeyCenter.FACE_CAP_APP_ID;
sceneConfig.mFaceCaptureCertificate = KeyCenter.FACE_CAP_APP_KEY;
int ret = -1;
if (metaScene == null) {
    // 创建场景
    ret = metaService.createScene(sceneConfig);
}

// 监听创建场景的回调事件
@Override
public void onCreateSceneResult(IMetaScene scene, int errorCode) {
    Log.i(TAG, "onCreateSceneResult errorCode: " + errorCode);
    metaScene = scene;
    // 获取 `ILocalUserAvatar` 对象
    localUserAvatar = metaScene.getLocalUserAvatar();
}

// 监听连接状态
@Override
public void onConnectionStateChanged(int state, int reason) {
    Log.d(TAG, "onConnectionStateChanged state=" + state + ",reason=" + reason);
    if (state == ConnectionState.META_CONNECTION_STATE_ABORTED) {
        setCurrentScene(MetaConstants.SCENE_NONE);
        resetRoleInfo();
        leaveScene();
    }
}
```

### 4 设置用户信息并进入场景 [#4-设置用户信息并进入场景]

要完成进入场景的操作，参考如下步骤：

1. 调用 [`setUserInfo`](/zh-CN/solutions/meta-world/reference/meta-api#setuserinfo) 和 [`setModelInfo`](/zh-CN/solutions/meta-world/reference/meta-api#setmodelinfo) 设置用户的基本信息和虚拟形象（Avatar）的模型信息。
2. 调用 `IMetaScene` 类的 [`addEventHandler`](/zh-CN/solutions/meta-world/reference/meta-api#addeventhandler) 添加事件句柄，监听 `IMetaScene` 的事件回调。
3. 调用 [`enterScene`](/zh-CN/solutions/meta-world/reference/meta-api#enterscene) 进入场景，并通过 `config` 设置配置信息。
4. 通过 `IMetaSceneEventHandler` 类的 [`onEnterSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#onentersceneresult) 回调监听进入场景的结果。

```java
public void enterScene() {
    if (null != localUserAvatar) {
        // 设置用户的基本信息
        localUserAvatar.setUserInfo(userInfo);
        // 设置用户的虚拟形象模型信息
        // 模型信息的 mBundleType 需设为 2(BUNDLE_TYPE_AVATAR)
        localUserAvatar.setModelInfo(modelInfo);
    }
    if (null != metaScene) {
        // 监听 IMetaScene 的事件回调
        metaScene.addEventHandler(MetaContext.getInstance());
        // 设置进入场景时的配置信息
        EnterSceneConfig config = new EnterSceneConfig();
        // 场景资源渲染时所需要的视图，Android 上使用原生 TextureView
        config.mSceneView = this.sceneView;
        // 进入场景的房间名称
        config.mRoomName = KeyCenter.CHANNEL_ID;
        // 内容中心对应的 ID
        if (null != sceneInfo) {
            config.mSceneId = this.sceneInfo.mSceneId;
        }
        // 设置进入场景的 ID 和加载场景资源的路径
        if (isEnableLocalSceneRes) {
            config.mSceneId = 0;
            config.mScenePath = scenePath + "/" + getSceneId();
        }
        /*
         * 仅为示例格式，具体格式以项目实际为准
         *   "extraInfo":{
         *     "sceneIndex":0  // 0 为默认场景，在这里指咖啡厅
         *   }
         */
        EnterSceneExtraInfo extraInfo = new EnterSceneExtraInfo();
        extraInfo.setSceneIndex(MetaConstants.SCENE_GAME);

        // 设置加载场景资源时需要的额外自定义信息，只支持字符串
        // 在这里指设置 sceneIndex
        // 在业务逻辑中包含多个场景的情况下，你可以用 sceneIndex 来区分不同的场景，Unity 场景脚本可以根据 sceneIndex 来确定进入哪个场景，并执行相应的逻辑
        config.mExtraInfo = JSONObject.toJSONString(extraInfo).getBytes();
        // 进入场景
        metaScene.enterScene(config);
    }
}

// 监听进入场景的回调事件
@Override
public void onEnterSceneResult(int errorCode) {
    Log.d(TAG, String.format("onEnterSceneResult %d", errorCode));
}
```

### 5 离开场景并释放资源 [#5-离开场景并释放资源]

离开场景时，参考如下步骤：

1. 调用 [`leaveScene`](/zh-CN/solutions/meta-world/reference/meta-api#leavescene) 离开场景。
2. 通过 `IMetaSceneEventHandler` 类的 [`onLeaveSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#onleavesceneresult) 回调得知成功离开场景后，调用 [`release`](/zh-CN/solutions/meta-world/reference/meta-api#release) 释放 `IMetaScene`。
3. 通过 `IMetaSceneEventHandler` 类的 [`onReleasedScene`](/zh-CN/solutions/meta-world/reference/meta-api#onreleasedscene) 回调监听 `IMetaScene` 是否释放成功。
4. 依次调用 `IMetaService` 和 `RtcEngine` 类的 `destroy` 方法销毁 `IMetaService` 和 `RtcEngine`。

```java
// 离开场景
private void leaveScene() {
    if (metaScene != null) {
        metaScene.leaveScene();
    }
}

// 监听离开场景的回调事件
@Override
public void onLeaveSceneResult(int errorCode) {
    Log.d(TAG, String.format("onLeaveSceneResult %d", errorCode));
    if (errorCode == 0) {
        // 释放 IMetaScene
        metaScene.release();
        metaScene = null;
    }
}

// 监听释放 IMetaScene 的回调事件
@Override
public void onReleasedScene(int status) {
    Log.d(TAG, String.format("[meta] onReleasedScene %d", status));
    destroy();
}

// 销毁 IMetaService 和 RtcEngine
public void destroy() {
    IMetaService.destroy();
    metaService = null;
    RtcEngine.destroy();
    rtcEngine = null;
}
```

## 开发注意事项 [#开发注意事项]

在使用 3D 场景的过程中，需要注意以下几点：

- 为了保持场景的连续性和流畅性，通常不能销毁 Activity。为了避免销毁场景所在的 Activity，可以使用 `Intent.FLAG_ACTIVITY_REORDER_TO_FRONT` 标志将 Activity 置于后台。
- 由于 Texture 的数据是在 GPU 中处理的，因此不能在运行时被销毁或重新创建，否则会影响应用程序的性能和稳定性。如果 Texture 尺寸大小发生变化，例如切换场景时需要切换横竖屏，你需要在 `setSurfaceTextureListener` 的回调方法 `onSurfaceTextureSizeChanged` 中再次调用 `createScene` 和 `enterScene` 等方法，重新创建和进入场景。
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
MetaWorld 解决方案支持元语聊、元直播场景。本文介绍如何实现元语聊。

## 示例项目 [#示例项目-1]

声网在 GitHub 上提供开源 [Agora-MetaWorld](https://github.com/AgoraIO-Community/Agora-MetaWorld/tree/dev_metasdk1.0) 示例项目供你参考。

如果你还需了解 Unity 部分的工程文件和功能指南，请联系 sales@shengwang.cn 获取。

## 实现元语聊 [#实现元语聊-1]

完成[集成声网 Meta SDK](/zh-CN/solutions/meta-world/get-started/integrate-sdk) 后，你可以参考本节实现元语聊。

下图展示实现元语聊的 API 调用时序：

<DocsImage alt="元语聊 API 时序图" src="https://web-cdn.agora.io/docs-portal/img/meta-world/meta-chat.svg" displayWidth="70%" />

实现步骤需用到如下类：

- `AgoraRtcEngineKit` 类：提供实时音视频功能的核心类。
- `AgoraMetaServiceKit` 类：提供 Meta 服务的核心类。可用于获取场景资源列表、下载场景资源、删除本地场景资源等场景资源管理，还可用于创建 `AgoraMetaScene`。
- `AgoraMetaScene` 类：场景资源相关操作。
- `AgoraMetaLocalUserAvatar` 类：包含在 `AgoraMetaScene` 中，生命周期和 `AgoraMetaScene` 相同，用于设置虚拟形象 (Avatar)。
- `AgoraMetaEventDelegate` 类：`AgoraMetaServiceKit` 的异步方法的事件回调类。
- `AgoraMetaSceneEventDelegate` 类：`AgoraMetaScene` 的异步方法的事件回调类。

### 1 创建并初始化 AgoraMetaServiceKit [#1-创建并初始化-agorametaservicekit]

调用 [`sharedEngineWithConfig`](/zh-CN/api-reference/rtc/ios/initialize#api_irtcengine_initialize) 创建 `AgoraRtcEngineKit`。调用 [`sharedMetaServiceWithConfig`](/zh-CN/solutions/meta-world/reference/meta-api#sharedmetaservicewithconfig) 创建并初始化 `AgoraMetaServiceKit`。

初始化 `AgoraMetaServiceKit` 时，需要在 `AgoraMetaServiceConfig` 里设置如下重要的字段：

- `rtcEngine`：通过 `sharedEngineWithConfig` 方法创建的 `AgoraRtcEngineKit` 实例。
- `appId`：在声网控制台获取的 App ID。详见[开通元语聊服务](/zh-CN/solutions/meta-world/build/enable-service)。
- `userId`：登录声网 RTM 系统的用户 ID。推荐取值详见 [AgoraMetaServiceConfig](/zh-CN/solutions/meta-world/reference/meta-api#agorametaserviceconfig)。
- `rtmToken`：用于登录声网 RTM 系统的动态密钥。开启动态鉴权后可用。详见[生成 Token](/zh-CN/realtime-media/rtm/build/authentication-and-connection/token-generation)。
- `localDownloadPath`：场景资源下载到本地的保存路径。
- `delegate``：AgoraMetaServiceKit` 的回调事件。

声网项目有两种 Token 和 UID，请不要搞混淆：

- RTC UID：用于在实时音视频通讯中标志用户身份的用户 ID。推荐取值详见 [joinChannelByToken 的参数解释](/zh-CN/api-reference/rtc/ios/channel#api_irtcengine_joinchannel2)。
- RTM UID：用于在云信令系统中标志用户身份的用户 ID。推荐取值详见 [AgoraMetaServiceConfig 的字段解释](/zh-CN/solutions/meta-world/reference/meta-api#agorametaserviceconfig)。
- RTC Token：用于保障实时音视频通讯安全的动态密钥。详见[如何生成 RTC Token 进行鉴权](/zh-CN/realtime-media/rtc/build/setup-and-access/token-authentication)。
- RTM Token：用于保障云信令系统安全的动态密钥。详见[如何生成 RTM Token 进行鉴权](/zh-CN/realtime-media/rtm/build/authentication-and-connection/user-authentication)。

```swift
func createRtcEngine() {
    guard let token = KeyCenter.RTM_TOKEN else { return }
    // 设置 AgoraRtcEngineKit 配置信息
    let rtcEngineConfig = AgoraRtcEngineConfig()
    // 声网项目的 App ID，从控制台获取
    rtcEngineConfig.appId = KeyCenter.APP_ID
    rtcEngineConfig.areaCode = .global
    // 创建 AgoraRtcEngineKit
    rtcEngine = AgoraRtcEngineKit.sharedEngine(with: rtcEngineConfig, delegate: self)

    rtcEngine?.setVideoFrameDelegate(self)
    rtcEngine?.setParameters("{\"rtc.audio.force_bluetooth_a2dp\": true}")
    rtcEngine?.setChannelProfile(.liveBroadcasting)
    rtcEngine?.setClientRole(.broadcaster)
    // 设置视频编码参数
    let vec = AgoraVideoEncoderConfiguration(size: resolution!, frameRate: .fps30, bitrate: AgoraVideoBitrateStandard, orientationMode: .adaptative, mirrorMode: .enabled)
    rtcEngine?.setVideoEncoderConfiguration(vec)
```

```swift
func createMetaService(userName: String, avatarUrl: String, delegate: AgoraMetaEventDelegate?) {
    currentUserInfo = AgoraMetaUserInfo.init()
    currentUserInfo?.userId = KeyCenter.RTM_UID
    currentUserInfo?.userName = userName
    currentUserInfo?.userIconUrl = avatarUrl
    // 设置 AgoraMetaServiceKit 配置信息
    let metaServiceConfig = AgoraMetaServiceConfig()
    // 声网项目的 App ID，从控制台获取
    metaServiceConfig.appId = KeyCenter.APP_ID
    // 声网 RTM（云信令）Token，保障安全
    // 声网项目有 RTC Token 和 RTM Token，不要搞混淆
    metaServiceConfig.rtmToken = KeyCenter.RTM_TOKEN ?? ""
    // 声网 RTM（云信令）UID，用户 ID，标志用户身份
    // 声网项目有 RTC UID 和 RTM UID，不要搞混淆
    metaServiceConfig.userId = KeyCenter.RTM_UID
    metaServiceConfig.delegate = delegate

    let paths = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true)
    metaServiceConfig.localDownloadPath = paths.first!
    metaServiceConfig.rtcEngine = rtcEngine
    // 创建 AgoraMetaServiceKit
    metaService = AgoraMetaServiceKit.sharedMetaServiceWithConfig(_: metaServiceConfig)
}
```

### 2 下载场景资源 [#2-下载场景资源-1]

调用 `AgoraMetaServiceKit` 类的 [`getSceneAssetsInfo`](/zh-CN/solutions/meta-world/reference/meta-api#getsceneassetsinfo) 获取场景资源，并通过 `AgoraMetaEventDelegate` 类的 [`onGetSceneAssetsInfoResult`](/zh-CN/solutions/meta-world/reference/meta-api#ongetsceneassetsinforesult) 回调监听获取场景资源时的事件。

调用 `AgoraMetaServiceKit` 类的 [`downloadSceneAssets`](/zh-CN/solutions/meta-world/reference/meta-api#downloadsceneassets) 获取场景资源，并通过 `AgoraMetaEventDelegate` 类的 [`onDownloadSceneAssetsProgress`](/zh-CN/solutions/meta-world/reference/meta-api#ondownloadsceneassetsprogress) 回调监听获取场景资源时的事件。

```swift tab="判断是否下载场景资源"
// 判断是否下载场景资源
metaService?.isSceneAssetsDownloaded(_ sceneId: Int)

// 获取场景资源
metaService?.getSceneAssetsInfo()

// 监听获取场景资源的回调事件
func onGetSceneAssetsInfoResult(_ scenes: NSMutableArray, errorCode: Int)

// 下载场景资源
metaService?.downloadSceneAssets(_ sceneId: Int)

// 监听下载场景资源的回调事件
func onDownloadSceneAssetsProgress(_ sceneId: Int, progress: Int, state: AgoraMetaDownloadStateType)
```

```swift tab="可选调用"
// 可选调用
// 取消下载场景资源
metaService?.cancelDownloadSceneAssets(_ sceneId: Int)

// 可选调用
// 清空场景资源
metaService?.cleanSceneAssets(_ sceneId: Int)
```

### 3 创建 AgoraMetaScene [#3-创建-agorametascene]

调用 [`createScene`](/zh-CN/solutions/meta-world/reference/meta-api#createscene) 创建 `AgoraMetaScene`，并在 `AgoraMetaSceneConfig` 中设置场景配置信息。通过 `AgoraMetaEventDelegate` 类的 [`onCreateSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#oncreatesceneresult) 和 [`onConnectionStateChanged`](/zh-CN/solutions/meta-world/reference/meta-api#onconnectionstatechanged) 回调监听创建场景和连接状态的事件。

```swift tab="设置场景配置信息"
func createScene(_ delegate: MetaChatSceneViewController) {
    // 设置场景配置信息
    let config = AgoraMetaSceneConfig()
    config.delegate = delegate
    // 设置是否开启面部捕捉
    // 元语聊场景无需开启面部捕捉
    config.enableFaceCapture = false
    // 传入面部捕捉插件的 App ID 和 Certificate
    // 仅开启面部捕捉时才需要传值
    config.faceCaptureAppId = KeyCenter.FACE_CAPTURE_APP_ID
    config.faceCaptureCertificate = KeyCenter.FACE_CAPTURE_CERTIFICATE
    // 创建场景
    metaService?.createScene(config)
}
```

```swift tab="监听创建场景的回调事件"
// 监听创建场景的回调事件
func onCreateSceneResult(_ scene: AgoraMetaScene?, errorCode: Int) {
    if errorCode != 0 {
        print("create scene error: \(errorCode)")
        return
    }

    metachatScene = scene
    DispatchQueue.main.async {
        // 创建场景渲染所需的视图
        guard let view = scene?.createRenderView(CGRect(x: 0, y: 0, width: width, height: height)) else { return }
        // 开启视频模块
        rtcEngine?.enableVideo()
    }
}

// 监听连接状态
func onConnectionStateChanged(_ state: AgoraMetaConnectionStateType, reason: AgoraMetaConnectionChangedReasonType) {
    if state == .disconnected {
        // 一些处理操作，详见源代码
        // ...
    } else if state == .aborted {
        // 一些处理操作，详见源代码
        // ...
    }
}
```

### 4 设置用户信息并进入场景 [#4-设置用户信息并进入场景-1]

要完成进入场景的操作，参考如下步骤：

1. 调用 [`setUserInfo`](/zh-CN/solutions/meta-world/reference/meta-api#setuserinfo) 和 [`setModelInfo`](/zh-CN/solutions/meta-world/reference/meta-api#getmodelinfo) 设置用户的基本信息和虚拟形象（Avatar）的模型信息。
2. 调用 [`enterScene`](/zh-CN/solutions/meta-world/reference/meta-api#enterscene) 进入场景，并通过 `config` 设置配置信息。
3. 通过 `AgoraMetaSceneEventDelegate` 类的 [`onEnterSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#onentersceneresult) 回调监听进入场景的结果。

```swift
func enterScene(view: UIView) {
    guard let sceneInfo = currentSceneInfo else {
        return
    }
    // 用户的虚拟形象模型信息
    let avatarInfo = AgoraMetaAvatarModelInfo.init()
    for info in sceneInfo.bundles {
        if info.bundleType == .avatar {
            avatarInfo.bundleCode = info.bundleCode;
            break
        }
    }
    avatarInfo.localVisible = true
    avatarInfo.remoteVisible = true
    avatarInfo.syncPosition = true

    // 设置进入场景时的配置信息
    let enterSceneConfig = AgoraMetaEnterSceneConfig()
    enterSceneConfig.roomName = KeyCenter.CHANNEL_ID
    //  场景资源渲染时所需要的视图，从 createRenderView 创建获得
    enterSceneConfig.sceneView = view
    // 内容中心对应的 ID
    enterSceneConfig.sceneId = sceneInfo.sceneId
    // 设置加载场景资源时需要的额外自定义信息，只支持字符串
    // 在这里指设置 sceneIndex，取值 0 代表元直播场景，取值 1 代表元语聊场景
    // 在业务逻辑中包含多个场景的情况下，你可以用 sceneIndex 来区分不同的场景，Unity 场景脚本可以根据 sceneIndex 来确定进入哪个场景，并执行相应的逻辑
    let dict = ["sceneIndex": kSceneIndex.rawValue]
    let data = try? JSONSerialization.data(withJSONObject: dict, options: [])
    let extraInfo = String(data: data!, encoding: String.Encoding.utf8)
    enterSceneConfig.extraInfo = extraInfo!.data(using: String.Encoding.utf8)
    // 获得 localUserAvatar 对象
    localUserAvatar = metachatScene?.getLocalUserAvatar()
    // 设置用户的基本信息
    localUserAvatar?.setUserInfo(currentUserInfo)
    // 设置用户的虚拟形象模型信息
    localUserAvatar?.setModelInfo(avatarInfo)

    // 进入场景
    metachatScene?.enter(enterSceneConfig)
}

// 监听进入场景的回调事件
func metaScene(_ scene: AgoraMetaScene, onEnterSceneResult errorCode: Int) {}
```

### 5 离开场景并释放资源 [#5-离开场景并释放资源-1]

离开场景时，参考如下步骤：

1. 调用 [`leaveScene`](/zh-CN/solutions/meta-world/reference/meta-api#leavescene) 离开场景。
2. 通过 `AgoraMetaSceneEventDelegate` 类的 [`onLeaveSceneResult`](/zh-CN/solutions/meta-world/reference/meta-api#onleavesceneresult) 回调得知成功离开场景后，调用 [`destroy`](/zh-CN/solutions/meta-world/reference/meta-api#destroy) 释放 `AgoraMetaScene`。
3. 通过 `AgoraMetaSceneEventDelegate` 类的 [`onReleasedScene`](/zh-CN/solutions/meta-world/reference/meta-api#onreleasedscene) 回调监听 `AgoraMetaScene` 是否释放成功。
4. 依次调用 `AgoraMetaServiceKit` 和 `AgoraRtcEngineKit` 类的 `destroy` 方法销毁 `AgoraMetaServiceKit` 和 `AgoraRtcEngineKit`。

```swift
private func leaveScene() {
    // 离开场景
    metaScene?.leave()
}

// 监听离开场景的回调事件
func metaScene(_ scene: AgoraMetaScene, onLeaveSceneResult errorCode: Int) {
    // 销毁场景
    metaScene?.destroy()
    metaScene = nil
}

// 监听释放 AgoraMetaScene 的回调事件
func metaScene(_ scene: AgoraMetaScene, onReleasedScene errorCode: Int) {
    // 销毁 AgoraMetaServiceKit
    AgoraMetaServiceKit.destroy()
    metaService = nil
    // 销毁 AgoraRtcEngineKit
    AgoraRtcEngineKit.destroy()
    rtcEngine = nil
}
```
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
