unity楼层内摄像头模型设计碰撞点击、hover等功能及与web交互视频流显示全流程记录
一、Unity端开发
(1)楼层模型设计碰撞点击
在unity工程中依次点击左侧层级楼层内的摄像头模型----点击右侧检查器内的【添加组件】按钮----点击 Box Collider----在Box Collider组件内点击【编辑碰撞器】----调整碰撞器的四周位置,使得摄像头模型被碰撞器边界线完全包裹


以上方法依次处理各个摄像头模型。
(3)编写碰撞体碰撞处理脚本
(3.1)
在unity项目层级下的Scripts文件夹下新建脚本CameraColliderAndHover.cs;在脚本内部删除自动生成的void Start(),void Update()方法(用于该脚本用户处理hover事件和click事件,用不到帧相关操作);在脚本追加摄像头的基础属性配置信息


(3.2)依次将脚本绑定到摄像头中,并填写摄像头基础属性信息

(3.3)在unity项目层级下的Plugins下新建CameraHover&Click.jslib中间件文件,用于后续unity事件被监听后,调用中间件指定方法,再通过该方法内部调用web端暴露的js函数,实现unity--->web通信交互

(3.4)引入jslib中间件函数、编写C#脚本hover事件与点击事件的业务逻辑
CameraHover&Click.jslib中间件内部定义:
1 mergeInto(LibraryManager.library, { 2 //摄像头被hover时 3 CameraBeHovering: function (cameraname, cameraip,x, y) { 4 var cameraname = UTF8ToString(cameraname); 5 var cameraip = UTF8ToString(cameraip); 6 7 // 调用网页上暴露的全局方法 8 if (window.ShowCameraTooltip) { 9 window.ShowCameraTooltip(cameraname, cameraip,x, y); 10 } 11 }, 12 //摄像头被hover后离开时 13 CameraBeHovered: function () { 14 if (window.HideCameraTooltip) { 15 window.HideCameraTooltip(); 16 } 17 }, 18 //摄像头被点击时 19 CameraBeClicked: function (cameraname, cameraip) { 20 var cameraname = UTF8ToString(cameraname); 21 var cameraip = UTF8ToString(cameraip); 22 23 if (window.HandleCameraClick) { 24 window.HandleCameraClick(cameraname, cameraip); 25 } 26 } 27 });
CameraColliderAndHover.cs内部定义:
1 using System.Collections;
2 using System.Collections.Generic
3 using System.Runtime.InteropServices;
4 using UnityEngine; 5 using UnityEngine.EventSystems; 6 7 /// <summary> 8 /// 用于处理第二页楼层内摄像头模型的点击事件和hover事件的处理(也会涉及unity通知jslib进而调用webjs逻辑) 9 /// </summary> 10 public class CameraColliderAndHover : MonoBehaviour,IPointerEnterHandler,IPointerExitHandler,IPointerClickHandler 11 { 12 [Header("摄像头名称/位置")] 13 public string cameraName; 14 15 [Header("摄像头IP")] 16 public string cameraIP; 17 18 19 ////--------------- 引入中间件jslib定义的中间方法 (会自动搜索中间件方法)---------------------- 20 //注意: 21 //.jslib 定义的函数只会在 WebGL 构建产物里被 Unity 的 WebGL 桥接系统生成/绑定成可调用的符号;在 **Editor 里运行(UNITY_EDITOR)**时,这个符号根本不存在 22 //所以在函数调用处,加 #if UNITY_WEBGL && !UNITY_EDITOR #else #endif等判断,避免unity editor调试时报错!! 23 24 //摄像头被hover时 25 [DllImport("__Internal")] 26 private static extern void CameraBeHovering(string cameraname,string cameraip,float x,float y ); 27 28 //摄像头被hover后离开时 29 [DllImport("__Internal")] 30 private static extern void CameraBeHovered(); 31 32 //摄像头被点击时 33 [DllImport("__Internal")] 34 private static extern void CameraBeClicked(string cameraname, string cameraip); 35 36 37 ////--------------- hover、click业务逻辑方法 ---------------------- 38 39 40 /// <summary> 41 /// 1. 实现IPointerEnterHandler接口方法 42 /// 43 /// 摄像头被hover时 44 /// </summary> 45 /// <param name="eventData"></param> 46 public void OnPointerEnter(PointerEventData eventData) 47 { 48 //获取鼠标hover时的转换到屏幕平面的(X,Y)位置 49 float webX = Input.mousePosition.x; 50 //注意:Unity的Y轴是从下往上;而Web的Y轴是从上往下 51 float webY = Screen.height - Input.mousePosition.y; 52 #if UNITY_WEBGL && !UNITY_EDITOR 53 CameraBeHovering(cameraName, cameraIP, webX, webY);//调用中间件方法 54 #else 55 Debug.Log($"摄像头被hoverl了,webX,WebY:{webX},{webY}"); 56 #endif 57 } 58 59 60 /// <summary> 61 /// 2. 实现IPointerExitHandler接口方法 62 /// </summary> 63 /// <param name="eventData"></param> 64 65 public void OnPointerExit(PointerEventData eventData) 66 { 67 #if UNITY_WEBGL && !UNITY_EDITOR 68 CameraBeHovered();//调用中间件方法 69 #else 70 Debug.Log($"摄像头hover离开,触发隐藏!"); 71 #endif 72 } 73 74 75 /// <summary> 76 /// 3. 实现IPointerClickHandler接口方法
/// (这个点击不会被触发,因为该脚本挂在摄像头模型上,摄像头模型又在11层模型内部,而楼层模型都挂载了抽屉监控脚本,他也是监控左键点击事件。所以当摄像头模型被点击,他只会当成楼层被点击了,所以不起作用。)
/// 改用下面的OnPointerDown方法 77 /// </summary> 78 /// <param name="eventData"></param> 79
// public void OnPointerClick(PointerEventData eventData)
// {
//#if UNITY_WEBGL && !UNITY_EDITOR
// CameraBeClicked(cameraName, cameraIP);
//#else
// Debug.Log($"摄像头被点击,触发通知web端显示弹出框并显示视频流!");
//#endif
// }
public void OnPointerDown(PointerEventData eventData)
{
#if UNITY_WEBGL && !UNITY_EDITOR
CameraBeClicked(cameraName, cameraIP);
#else
Debug.Log($"摄像头被点击,触发通知web端显示弹出框并显示视频流!");
#endif
}
88 }
二、Web端开发
(1)构建浮动框div结构及CSS样式 (先占物理位置,未动态在js中生成div结构)、以及hover进/hover出的全局js脚本
(1.1)在第二页tab页内追加编写浮动框div结构:

(1.2)编写上述div结构配套的CSS样式:

可以看到效果已显示:

(1.3)编写全局js函数,处理用户hover进出unity摄像头模型动作 (中间件jslib调用的全局函数,就是此处web端定义的全局js函数)
1 <!--第二页场景交互 摄像头hover以及点击交互,对应unity端的CameraHover&Click.jslib 中间件和 CameraColliderAndHover.cs C#脚本 --> 2 <script> 3 4 //(1)hover摄像头进行信息提示框显示 5 window.ShowCameraTooltip = function (cameraName, cameraIP, screenX, screenY) { 6 const tooltip = document.getElementById('cameraHoverTooltip'); 7 //根据cameraId调用后端接口获取数据,此处先用假数据代替 8 //var data = GetCameraTooltipData; 9 10 document.getElementById('cameraHoverName').innerText = "位置:" + cameraName; 11 document.getElementById('cameraHoverIp').innerText = "IP:" + cameraIP; 12 13 //设置tooltip的偏移位置 14 tooltip.style.left = screenX + 'px'; 15 tooltip.style.top = screenY + 'px'; 16 //让tootip显示 17 tooltip.style.display = 'block'; 18 }; 19 20 //(2)hover离开摄像头取消信息提示框显示 21 window.HideCameraTooltip = function () { 22 const tooltip = document.getElementById("cameraHoverTooltip"); 23 tooltip.style.display = "none";
} 24 </script>
31
hover实际验证:

(2)构建视频流显示div结构及CSS样式
(2.1)在原有第二页tab页内部的html结构中的顶部追加视频弹出框的结构div
1 <!--第二页的视频弹出框(参考之前项目)--> 2 <div id="cameraModal" class="modal"> 3 <div class="modal-content"> 4 <span id="camera-close-btn" class="close-btn">×</span> 5 <video id="modalCameraVideo" autoplay controls></video> 6 <div id="modalCameraLabel" class="modal-camera-label">摄像头1(监控视角)</div> 7 </div> 8 </div>
(2.2)配套的CSS
1 /* 弹窗样式 */ 2 .modal { 3 display: none; 4 position: fixed; 5 z-index: 10000; 6 left: 0; 7 top: 0; 8 width: 100%; 9 height: 100%; 10 background-color: rgba(0, 0, 0, 0.8); 11 justify-content: center; 12 align-items: center; 13 14 pointer-events: auto; 15 } 16 17 .modal-content { 18 position: relative; 19 width: 50vw; 20 height: 65vh; 21 background-color: #000; 22 border-radius: 5px; 23 overflow: hidden; 24 } 25 26 .close-btn { 27 position: absolute; 28 top: 10px; 29 right: 15px; 30 color: rgb(246, 6, 6); 31 font-size: 28px; 32 font-weight: bold; 33 cursor: pointer; 34 z-index: 10000; 35 } 36 37 .close-btn:hover { 38 color: #f96d10; 39 } 40 41 #modalCameraVideo{ 42 width: 100%; 43 height: 100%; 44 object-fit: contain; 45 } 46 47 .modal-camera-label { 48 position: absolute; 49 bottom: 10px; 50 left: 10px; 51 color: white; 52 background-color: rgba(0, 0, 0, 0.5); 53 padding: 5px 10px; 54 border-radius: 3px; 55 font-size: 14px; 56 }
(3)webrtc-streamer工具进行视频流转换,js使用逻辑编写
(3.1) webrtc-streamer rtsp视频流转换工具搭建
rtsp视频流是无法直接在浏览器中显示的(rtsp流可以直接在客户端编写的程序中显示),要想解决这个问题,可以使用轻量化的webrtc工具:webrtc-streamer.
下载地址:Release v0.8.15 · mpromonet/webrtc-streamer · GitHub
解压到本地,找到内部的bin文件夹,双击.exe文件即启动了该工具:

注意:也可以通过cmd命令框参数命令来启动webrtc-streamer工具,自定义指定服务端口;也可以通过nssm工具,设置启动命令参数,配置成开机自启动服务。(此处不展示操作)
(3.2)编写unity摄像头物体被点击时,调用摄像头视频显示定义的方法
(unity监听到鼠标被按下---->调用jslib中间件函数CameraBeClicked(cameraName, cameraIP)--->进而调用js全局函数 window.HandleCameraClick ---->在该内部编写摄像头模型被点击时,web端要执行的逻辑)
1 //(3)点击摄像头进行视频弹窗显示 2 window.HandleCameraClick = function (cameraName, cameraIp) { 3 window.FloorCameraVideo(cameraName, cameraIp); 4 }
(3.3)上述摄像头弹出显示方法的具体业务逻辑
为了方便全局调用,定义了全局方法:window.FloorCameraVideo(cameraName, cameraIp)
1 //全局公共变量 2 var WEBRTC_SERVER = "http://localhost:8000";//webrtc-streamer工具部署的服务器地址 3 window.FloorCameraVideo = function (cameraName, cameraIp) { 4 //alert(`全局方法有效!点击了${cameraName},摄像头IP地址是${cameraIp},后续可在此处添加打开视频弹窗的功能!`); 5 //把第一人称按钮、关闭楼层按钮等都先隐藏,等视频弹窗关闭后再恢复显示 6 document.getElementById("fpsToggleBtn").style.display = "none"; 7 document.getElementById("closefloorBtn").style.display = "none"; 8 9 //获取元素 10 var modal = document.getElementById('cameraModal');//获取弹出框顶级div对象 11 modal.style.display = 'flex';//显示弹出框 12 13 var modalVideo = document.getElementById('modalCameraVideo');//获取弹出框内video对象 14 var closeBtn = document.getElementById("camera-close-btn");//获取弹出框内关闭按钮对象 15 var videoLabel = document.getElementById("modalCameraLabel");//获取设置摄像头名称的元素对象 16 17 //设置摄像头名称显示 18 videoLabel.textContent = cameraName; 19 20 //核心的2步代码:实例化webrtc-streamer对象并进行连接 21 var webrtc = new WebRtcStreamer(modalVideo, WEBRTC_SERVER); 22 //webrtc.connect(cameraIp,null,"rtptransport=tcp&timeout=60",null);//执行连接 23 webrtc.connect("rtsp://stream.strba.sk:1935/strba/VYHLAD_JAZERO.stream", null, "rtptransport=tcp&timeout=60", null);//临时测试 24 25 //页面卸载时清理资源 26 window.addEventListener('beforeunload', function () { 27 webrtc.disconnect(); 28 }); 29 30 // 关闭弹窗 31 closeBtn.onclick = function () { 32 33 //再重新恢复button按钮的显示 34 document.getElementById("fpsToggleBtn").style.display = "block"; 35 document.getElementById("closefloorBtn").style.display = "block"; 36 37 modal.style.display = 'none'; 38 webrtc.disconnect(); 39 40 }; 41 }
最终点击摄像头模型,显示了视频:


浙公网安备 33010602011771号