我们邀请该团队分享他们的发展历史:
项目初始心脏
颈椎问题是困扰所有上班族的难题。大部分人工作的时候都没有机会起床活动。当他们回家时,他们会因为累而放弃做一些颈椎康复运动。所以我们要设计一个游戏,让每个人都能以游戏的形式移动颈椎来缓解疼痛。我们选择了职场中“抛锅”和“取锅”的场景作为游戏中的元素,希望增加玩家的代入感。此外,我们还增加了截图分享模块,方便游戏的传播。
强函数
经过五天的设计开发,我们终于完成了《把玩人头》的工作。下面分享一下它的主要功能和代码细节。
视频聊天模块的搭建视频聊天模块主要使用声音网络的音视频sdk,可以快速开发出一个基本的视频对话模块,核心代码如下:
//onCreateval RTC ENgine = RTC ENgine . create(this,AppConfig.appKey,
对象:IRtcEngineEventHandler{
override fun onfirstremotevidecoded(uid:Int,width: Int,height: Int,passed:Int){
setupRemoteVideo(uid)
}
}
//设置
私人娱乐setupRemoteVideo(uid: Int) {
val remoteView = RtcEngine。创建渲染视图(基本上下文)
remote view . setzordermediaoverlay(true)
container.addView(remoteView)
rtcengine . setupremote video(VideoCanvas(remote view,video canvas。RENDER_MODE_HIDDEN,uid))
}
视频帧数据的获取和处理对于下一步的人脸识别,我们需要获取视频帧数据,并对帧数据进行预处理。在阅读了声音网络提供的文档和演示后,我们构建了一个简单的apm插件,通过它我们可以获得视频聊天过程中的原始数据。首先我们创建APM-plugin-packet-processing . CPP文件,然后通过CMakeLists.txt配置编译参数:
cmake _ minimum _ required(VERSION 3 . 4 . 1)
add_library(
apm插件包处理
分享
APM-plugin-packet-processing . CPP)
包含_目录(../CPP/include)//在这里,您需要导入。sdk中的h文件
...
target_link_libraries(
apm插件包处理
${log-lib})
然后我们定义两个jni方法来注册和注销原始数据的回调:
JNIEXPORT void JNICALL Java _ com _ zero _ game _ utils _ frame _ video frame handler _ doRegisterProcessing
(JNIEnv *env,job object obj){
if(!rtcEngine) {
返回;
} else{
agora::util::AutoTr & lt;agora::media::IMediaEngine & gt;mediaEngine
MEDIA ENGINE . query interface(rtcEngine,AGORA::AGORA _ IID _ MEDIA _ ENGINE);
s _ packetObserver = * new agoravideoframobserver(JVM,env,env->;new global ref(obj));
media engine->;registerevideoframobserver(& amp;s _ packetObserver);
}
}
JNIEXPORT void JNICALL Java _ com _ zero _ game _ utils _ frame _ video frame handler _ dounrregister processing
(JNIEnv *env,job object obj){
if(!rtcEngine) {
返回;
} else{
agora::util::AutoTr & lt;agora::media::IMediaEngine & gt;mediaEngine
MEDIA ENGINE . query interface(rtcEngine,AGORA::AGORA _ IID _ MEDIA _ ENGINE);
s _ packetObserver.release
media engine->;registerevideoframobserver(nullptr);
}
}
agora::media::ivideoframebserver是声音网络sdk提供的视频帧回调,只要实现:
class agoravideoframobserver:public agora::media::ivideoframobserver {
公共:
AgoraVideoFrameObserver {
}
agoravideoframobserver(Javavm * VM,JNIEnv *env,job object jobj){
// ...
}
//获取本地摄像头拍摄的视频帧
虚拟画框(VideoFrame & ampvideoFrame)覆盖{
//processVideoFrame(videoFrame)
returntrue
}
//获取远程用户发送的视频帧
虚拟bool onRenderVideoFrame(无符号整数,视频帧& ampvideoFrame)覆盖{
returntrue
}
//获取本地视频编码前的视频帧
虚拟画框(VideoFrame & ampvideoFrame)覆盖{
returntrue
}
无效释放{
// ...
}
};
int width = videoFrame.width
int height = videoFrame.height
int index = 0;
char * rgba = new char[width * height * 4];
无符号char * ybase = static _ cast & lt无符号字符*>。(video frame . ybbuffer);
无符号char * ubase = static _ cast & lt无符号字符*>。(video frame . ubbuffer);;
无符号char * vbase = static _ cast & lt无符号字符*>。(video frame . vbuffer);;
for(int y = 0;y <。身高;y++) {
for(int x = 0;x <。宽度;x++) {
//yyyyyyyuvv
u _ char Y = ybase[x+Y * width];
U _ char U = ubase[y/2 * width/2+(x/2)];
u _ char V = vbase[y/2 * width/2+(x/2)];
int r = static _ cast & ltint>。(Y+1.402 *(V-128));
if(r >;255){ r = 255;} if(r & lt;0){ r = 0;}
int g = static _ cast & ltint>。(Y-0.34413 *(U-128)-0.71414 *(V-128));
if(g >;255){ g = 255;} if(g & lt;0){ g = 0;}
int b = static _ cast & ltint>。(Y+1.772 *(U-128));
if(b >;255){ b = 255;} if(b & lt;0){ b = 0;}
rgba[index++]= static _ cast & lt;char>。(r);//R
rgba[index++]= static _ cast & lt;char>。(g);//G
rgba[index++]= static _ cast & lt;char>。(b);//B
rgba[index++]= static _ cast & lt;char>。(255);
}
}
jbyte buf[width * height * 4];
int I = 0;
for(I = 0;i <。宽度*高度* 4;i++) {
buf[I]= rgba[I];
}
jbyteArray Jarrv = env->;NewByteArray(宽*高* 4);
env->;SetByteArrayRegion(jarrRV,0,width * height * 4,buf);
env->;CallVoidMethod(jobj,jSendMethodId,jarrRV,width,height,video frame . rotation);
env->;deleteLocalRef(JarrV);
人脸识别和方向检测 val bitmap = Bitmap.createBitmap(color,width,height,Bitmap.Config.ARGB_8888)//原始数据也需要水平旋转和翻转
值矩阵=矩阵
matrix . post rotate(rotation . tofloat)
matrix.postScale(-1.0f,1.0f)
值旋转位图=位图。创建位图(位图,0,0,宽度,高度,矩阵,真)
val image = FireBasevisionimage . FromBitmap(旋转位图)
val detect = Firebasevision . GetInstance . GetvisionFacedetector(高精度选项)
检测。检测最小尺寸(图像)
。addOnSuccessListener {
val Lefteye = face . GetLandmark(FirebasevisionFaceLandmark。左眼)
val Riverye = face . GetLandmark(FireBaSevisionFaceLandmark。右眼)
val nose = face . GetLandmark(FirebasevisionFaceLandmark。鼻基)
//获取左眼、右眼和鼻子的位置
Val lefteye = Euclidean(左眼,鼻子)//计算鼻子到左眼的距离
Val righteye节点=欧氏(右眼,鼻子)//计算鼻子到右眼的距离
值比率=最小值(左眼鼻,右眼鼻)/最大值(左眼鼻,右眼鼻)
if(比率> 1;0.7 &。& amp比率<。1) {
//左右眼与鼻子的比例在0.7-1.0之间。我们认为我们没有回头
FaceState。前面
} else{
if(右半边脸>;leftHalfFace) {
//右眼到鼻子的距离比左边大,所以我们认为我们已经转向左边了
FaceState。左边的
} else{
//恰恰相反,对吗
FaceState。正确的
}
}
}
游戏流程控制 由于游戏是在两端同时进行的,所以我们需要进行端对端的数据传递,我们采用的是声网提供的消息传输方案。通过实时传递游戏过程中的指令,对双方游戏画面进行控制,传递的指令包括:游戏开始,游戏结束,向左转头,向右转头,没有转头以及实时分数等。 //发送方streamId = RTcengine . CreateDataStream(true,true)
rtcengine . SendStreamMessage(StreamID,“左”)。toByteArray)
//接收器对象:IRtcEngineEventHandler
覆盖流消息(uid: Int,s: Int,data: ByteArray?) {
数据?。让{
值字符串=字符串(它)
when (string) {
"左"->。{
//处理游戏
}
“对”->对。{
//处理游戏
}
.....
}
}
“带头玩”这个项目是一个起点。基于它的框架,可以快速添加到各种应用中,形成一个额外的小游戏模块。用“收券”、“收料”等不同元素代替“收锅”和“抛锅”,可以拓展其使用场景。通过提供更多有趣的套餐,可以有效实现社会裂变引流。
Github项目地址:http://dwz.date/btxB
1.《1414小游戏 开发者实践:做一个双人视频社交小游戏,“甩头”才能玩》援引自互联网,旨在传递更多网络信息知识,仅代表作者本人观点,与本网站无关,侵删请联系页脚下方联系方式。
2.《1414小游戏 开发者实践:做一个双人视频社交小游戏,“甩头”才能玩》仅供读者参考,本网站未对该内容进行证实,对其原创性、真实性、完整性、及时性不作任何保证。
3.文章转载时请保留本站内容来源地址,https://www.lu-xu.com/tiyu/1114247.html