开源项目|使用声网&环信 SDK 构建元宇宙应用 MetaTown 最佳实践
大家好!我们是美特兄弟三人组!前阵参加了【声网&环信 RTE2022 创新编程挑战赛】,整个大赛历时47天,除了对声网和环信的 SDK 有了很多的体验,还做了很多奇思妙想的结合。在此次大赛中我们团队基于声网&环信 SDK 构建了一个元宇宙应用 MetaTown,获得了环信专项奖,开心之余把这个项目介绍给大家,抛砖引玉,感兴趣的兄弟可以加入进来一起在元宇宙中闯荡江湖~
一、关于MetaTown
金钱是被铸造出来的自由——陀思妥耶夫斯基
在三次元的现实世界,你是否为了搞钱而终日奔波?忍受996甚至007的非人待遇?是否正经历着创业人的凛冬?疫情等因素带来的本轮经济下行落实在每个人身上都是真真切切的,对于经历了40年经济暴增的国人来讲更是史无前例的。
在荷包日瘪萎靡不振的日子里,精神慰藉尤为重要,元宇宙正是当下最时髦的,何不创造一个可以躺着赚钱的元宇宙小镇?不为别的,在有虚拟工作的前提下每天我的虚拟角色的金币都会涨,想一想岂不是有一点小欢愉?还能在这个小镇结交一些志同道合沉迷于搞钱的友友们!
自然这些虚拟财富目前还是无法转化为真正的成就感的(变为现实财富),但现在市面上开始有人吹web3.0了!坐等币圈大佬入局,我们有信心打造一款能让一部分人先富起来的元宇宙小镇!
---------------------------------------
MetaTown 是基于声网 RTC 和环信 IM 打造的模拟城市生活的元宇宙社交类 App。
初来乍到的玩家首次进入 MetaTown 先选择不同的职业,在这座城市首先要考虑的是如何赚钱,或做一名程序员上下班打卡,或自己创业开个酒吧/书店或去银行投资理财。除此以外,还要注意身体健康,可能哪天会随机生病需要去医院,支付挂号费咨询不同科室的医生。注意,没有核酸证明有可能看不了病嗷~不打工没钱也看不了病嗷~
这个小镇的所有公共场所,均可以随时发起与陌生人私聊,因共同兴趣结缘,充分体验在MetaTown小镇 搞钱 闯荡的日子!
项目 GitHub 地址:
https://github.com/AgoraIO-Community/RTE-2022-Innovation-Challenge/tree/main/Application-Challenge/%E9%A1%B9%E7%9B%AE243-metatown-metatown
二、MetaTown 核心技术
MetaTown 使用当下最流行的声网实时音视频以及环信即时通讯 SDK,具体场景如:医院场景中一对一咨询医生,进行远程实时问诊。社交场景中与好友实时音视频沟通,聊天。
1、环信即时通讯
MetaTown 在6大交互场景中运用了环信的即时通讯 IM(Instant Messaging),给 IM 赋予了新的场景活力,支持陌生人私聊,群聊及超大型聊天室。
2-1)会话列表 项目中 IM 会话列表如下图:
会话列表关键代码:
publicvoidshow(BaseActivity activity){
NiceDialog.init().setLayoutId(R.layout.dialog_message)
.setConvertListener(new ViewConvertListener() {
@Override
protectedvoidconvertView(ViewHolder holder, BaseNiceDialog dialog){
RecyclerView rv = holder.getView(R.id.rv);
List easeConversationInfos = initData();
rv.setLayoutManager(new LinearLayoutManager(dialog.getContext()));
DialogMsgAdapter dialogMsgAdapter = new DialogMsgAdapter(easeConversationInfos);
rv.setAdapter(dialogMsgAdapter);
dialogMsgAdapter.setOnItemClickListener(new DialogMsgAdapter.OnItemClickListener() {
@Override
publicvoidonItemClick(int pos,String name){
SoundUtil.getInstance().playBtnSound();
dialog.dismissAllowingStateLoss();
EMConversation item = (EMConversation) easeConversationInfos.get(pos).getInfo();
ChatDialog.getInstance().show(activity,item.conversationId(), name);
}
});
}
})
.setAnimStyle(R.style.EndAnimation)
.setOutCancel(true)
.setShowEnd(true)
.show(activity.getSupportFragmentManager());
}
2-2)IM 聊天
项目中 IM 聊天如下图:
发送消息关键代码:
@Override
public void sendMessage(EMMessage message) {
if(message == null) {
if(isActive()) {
runOnUI(() -> mView.sendMessageFail("message is null!"));
}
return;
}
addMessageAttributes(message);
if (chatType == EaseConstant.CHATTYPE_GROUP){
message.setChatType(EMMessage.ChatType.GroupChat);
}else if(chatType == EaseConstant.CHATTYPE_CHATROOM){
message.setChatType(EMMessage.ChatType.ChatRoom);
}
...
EMClient.getInstance().chatManager().sendMessage(message);
if(isActive()) {
runOnUI(()-> mView.sendMessageFinish(message));
}
}
接受消息关键代码:
public void onMessageReceived(List messages) {
super.onMessageReceived(messages);
LiveDataBus.get().with(Constants.RECEIVE_MSG, LiveEvent.class).postValue(new LiveEvent());
for (EMMessage message : messages) {
// in background, do not refresh UI, notify it in notification bar
if(!MetaTownApp.getInstance().getLifecycleCallbacks().isFront()){
getNotifier().notify(message);
}
//notify new message
getNotifier().vibrateAndPlayTone(message);
}
}
2、声网音视频
MetaTown 运用了声网的实时音视频功能。
1)集成声网 SDK
1-1)添加声网音视频依赖在 app module 的 build.gradle 文件的 dependencies 代码块中添加如下代码:
implementation 'io.agora.rtc:full-rtc-basic:3.6.2'
然后在app module的build.gradle文件的android->defaultConfig代码块中添加如下代码
ndk {
abiFilters "arm64-v8a"
}
// 设置支持的SO库架构(开发者可以根据需要,选择一个或多个平台的so)
1-2)添加必要权限 为了保证 SDK 能正常运行,我们需要在 AndroidManisfest.xml 文件中声明以下权限:
1-3)APP 在签名打包时防止出现混淆的问题需要在 proguard-rules.pro 文件里添加以下代码:
-keep classio.agora.**{*;}
2)创建并初始化 RtcEngine
创建并初始化 RtcEngine
private void initializeEngine() {
try {
EaseCallKitConfig config = EaseCallKit.getInstance().getCallKitConfig();
if(config != null){
agoraAppId = config.getAgoraAppId();
}
mRtcEngine = RtcEngine.create(getBaseContext(), agoraAppId, mRtcEventHandler);
//因为有小程序 设置为直播模式 角色设置为主播
mRtcEngine.setChannelProfile(CHANNEL_PROFILE_LIVE_BROADCASTING);
mRtcEngine.setClientRole(CLIENT_ROLE_BROADCASTER);
EaseCallFloatWindow.getInstance().setRtcEngine(getApplicationContext(), mRtcEngine);
//设置小窗口悬浮类型
EaseCallFloatWindow.getInstance().setCallType(EaseCallType.CONFERENCE_CALL);
} catch (Exception e) {
EMLog.e(TAG, Log.getStackTraceString(e));
throw new RuntimeException("NEED TO check rtc sdk init fatal error\n" + Log.getStackTraceString(e));
}
3)设置视频模式
privatevoidsetupVideoConfig(){
mRtcEngine.enableVideo();
mRtcEngine.muteLocalVideoStream(true);
mRtcEngine.setVideoEncoderConfiguration(new VideoEncoderConfiguration(
VideoEncoderConfiguration.VD_1280x720,
VideoEncoderConfiguration.FRAME_RATE.FRAME_RATE_FPS_15,
VideoEncoderConfiguration.STANDARD_BITRATE,
VideoEncoderConfiguration.ORIENTATION_MODE.ORIENTATION_MODE_FIXED_PORTRAIT));
//启动谁在说话检测
int res = mRtcEngine.enableAudioVolumeIndication(500,3,false);
}
4)设置本地视频显示属性
4-1)setupLocalVideo( VideoCanvas local ) 方法用于设置本地视频显示信息。应用程序通过调用此接口绑定本地视频流的显示视窗(view),并设置视频显示模式。在应用程序开发中,通常在初始化后调用该方法进行本地视频设置,然后再加入频道。
privatevoidsetupLocalVideo(){
if(isFloatWindowShowing()) {
return;
}
localMemberView = createCallMemberView();
UserInfo info = new UserInfo();
info.userAccount = EMClient.getInstance().getCurrentUser();
info.uid = 0;
localMemberView.setUserInfo(info);
localMemberView.setVideoOff(true);
localMemberView.setCameraDirectionFront(isCameraFront);
callConferenceViewGroup.addView(localMemberView);
setUserJoinChannelInfo(EMClient.getInstance().getCurrentUser(),0);
mUidsList.put(0, localMemberView);
mRtcEngine.setupLocalVideo(new VideoCanvas(localMemberView.getSurfaceView(), VideoCanvas.RENDER_MODE_HIDDEN, 0));
}
4-2)joinChannel(String token,String channelName,String optionalInfo,int optionalUid ) 方法让用户加入通话频道,在同一个频道内的用户可以互相通话,多个用户加入同一个频道,可以群聊。使用不同 App ID 的应用程序是不能互通的。如果已在通话中,用户必须调用 leaveChannel() 退出当前通话,才能进入下一个频道。
privatevoidjoinChannel(){
EaseCallKitConfig callKitConfig = EaseCallKit.getInstance().getCallKitConfig();
if(listener != null && callKitConfig != null && callKitConfig.isEnableRTCToken()){
listener.onGenerateToken(EMClient.getInstance().getCurrentUser(),channelName, EMClient.getInstance().getOptions().getAppKey(), new EaseCallKitTokenCallback(){
@Override
publicvoidonSetToken(String token,int uId){
EMLog.d(TAG,"onSetToken token:" + token + " uid: " +uId);
//获取到Token uid加入频道
mRtcEngine.joinChannel(token, channelName,null,uId);
//自己信息加入uIdMap
uIdMap.put(uId,new EaseUserAccount(uId,EMClient.getInstance().getCurrentUser()));
}
@Override
public void onGetTokenError(int error, String errorMsg) {
EMLog.e(TAG,"onGenerateToken error :" + error + " errorMsg:" + errorMsg);
//获取Token失败,退出呼叫
exitChannel();
}
});
}
}
完成以上配置后就可以发起呼叫了,其它一些摄像头控制,声音控制可以参考声网官网的API,这里不再赘述。
3、场景原画
1)人物行走可分为踏步、水平移动两种动作,分别通过踏步动画和控制人物及背景 scrollview 移动实现。
2)关键点在于人物向左走过半屏继续向左行走,或向右走过半屏继续向右走的情况,以向右走为例,如果人物未超过屏幕中线,则控制人物向右移动;如果超出屏幕中线继续向右移动,则将人物固定在中线位置,背景向左滑动;如果背景向左已滑动至尽头,则保持背景不动,人物继续向右移动;如果人物移动至右边缘,则只控制人物原地踏步,背景和人物均不水平移动。
动画关键代码:
if (isToRight) {
ivPerson.setRotationY(180f);
}
isToRight = false;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivPerson.getLayoutParams();
if (sv != null) {
if (layoutParams.leftMargin > DisplayUtil.getHeight(MetaTownApp.getApplication()) || !sv.canScrollHorizontally(-1)) {
layoutParams.leftMargin -= STEP;
layoutParams.leftMargin = Math.max(layoutParams.leftMargin, 50);
ivPerson.setLayoutParams(layoutParams);
} else {
sv.smoothScrollBy(-STEP, 0);
}
} else {
layoutParams.leftMargin -= STEP;
layoutParams.leftMargin = Math.max(layoutParams.leftMargin, 50);
ivPerson.setLayoutParams(layoutParams);
}
mAnimationDrawable.start();
if (!isToRight) {
ivPerson.setRotationY(0f);
}
isToRight = true;
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) ivPerson.getLayoutParams();
if (sv != null) {
if (layoutParams.leftMargin < DisplayUtil.getHeight(MetaTownApp.getApplication()) || !sv.canScrollHorizontally(1)) {
layoutParams.leftMargin += STEP;
layoutParams.leftMargin = Math.min(layoutParams.leftMargin, DisplayUtil.getWidth(MetaTownApp.getApplication()) - 100);
ivPerson.setLayoutParams(layoutParams);
} else {
sv.smoothScrollBy(STEP, 0);
}
} else {
layoutParams.leftMargin += STEP;
layoutParams.leftMargin = Math.min(layoutParams.leftMargin, DisplayUtil.getWidth(MetaTownApp.getApplication()) - 100);
ivPerson.setLayoutParams(layoutParams);
}
mAnimationDrawable.start();
写字楼打工
银行投资理财
再说说2.0版本后续计划,有时间有兴趣的小伙伴欢迎留言加入我们兴趣小组一起搞事情~
1)把现有的几个场景补齐(II期)
2)开发新场景,丰富搞钱路数
3)等一位币圈大佬掉到碗里。
以上是 MetaTown 作品在 RTE2022 编程挑战赛期间的实践分享,更多开源项目可以访问环信开源项目频道:https://www.imgeek.org/code/
备注“开源项目”加入环信开发者开源项目交流群