diff --git a/Assets/PongStreamSender.cs b/Assets/PongStreamSender.cs new file mode 100644 index 0000000..2e75404 --- /dev/null +++ b/Assets/PongStreamSender.cs @@ -0,0 +1,119 @@ +using System; +using Unity.RenderStreaming.Signaling; +using UnityEngine; +using Unity.WebRTC; + +namespace Unity.RenderStreaming.Samples +{ + /// + /// 心跳接收通道 - 继承 DataChannelBase(对应博客中的 ReceiveChannel) + /// 接收服务端发送的 Ping,回复 Pong + /// + class HeartbeatReceiveChannel : DataChannelBase + { + // 消息类型(必须与服务端保持一致) + private const int MSG_TYPE_PING = 1; + private const int MSG_TYPE_PONG = 2; + + // 统计信息 + public long LastRtt { get; private set; } + public float AverageRtt { get; private set; } + public int ReceivedPingCount { get; private set; } + + // 事件 + public event Action OnPingReceived; // 参数:RTT + public event Action OnTimeoutWarning; // 超时警告 + + private System.Collections.Generic.Queue _latencyHistory = new System.Collections.Generic.Queue(10); + + [Serializable] + private class HeartbeatMessage + { + public int type; // 1=ping, 2=pong + public long timestamp; // 服务端发送时间戳 + public int seq; // 序列号 + } + + + /// + /// 重写 OnMessage - 当收到数据时自动调用(对应博客中的 ReceiveChannel 实现) + /// + protected override void OnMessage(byte[] bytes) + { + try + { + string json = System.Text.Encoding.UTF8.GetString(bytes); + var msg = JsonUtility.FromJson(json); + + if (msg.type == MSG_TYPE_PING) + { + HandlePing(msg); + } + } + catch (Exception e) + { + Debug.LogError($"[心跳接收通道] 消息解析失败: {e.Message}"); + } + } + + /// + /// 处理 Ping 消息并回复 Pong + /// + private void HandlePing(HeartbeatMessage ping) + { + long now = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); + long latency = now - ping.timestamp; + + ReceivedPingCount++; + LastRtt = latency; + + // 更新平均延迟 + _latencyHistory.Enqueue(latency); + if (_latencyHistory.Count > 10) + _latencyHistory.Dequeue(); + + long sum = 0; + foreach (var lat in _latencyHistory) + sum += lat; + AverageRtt = sum / (float)_latencyHistory.Count; + + Debug.Log($"[心跳接收通道] 收到 Ping #{ping.seq}, 延迟: {latency}ms, 平均: {AverageRtt:F1}ms"); + + // 立即回复 Pong(关键:回传原始时间戳,方便服务端计算 RTT) + SendPong(ping.timestamp, ping.seq); + + // 触发事件 + OnPingReceived?.Invoke(latency); + } + + /// + /// 发送 Pong 响应 + /// + private void SendPong(long originalTimestamp, int seq) + { + if (Channel == null || Channel.ReadyState != RTCDataChannelState.Open) + { + Debug.LogWarning("[心跳接收通道] 通道未打开,无法发送 Pong"); + return; + } + + var pong = new HeartbeatMessage + { + type = MSG_TYPE_PONG, + timestamp = originalTimestamp, // 回传服务端的时间戳 + seq = seq + }; + + string json = JsonUtility.ToJson(pong); + byte[] bytes = System.Text.Encoding.UTF8.GetBytes(json); + + Channel.Send(bytes); + Debug.Log($"[心跳接收通道] 发送 Pong #{seq}"); + } + + /// + /// 获取当前连接状态 + /// + public bool IsConnected => Channel != null && Channel.ReadyState == RTCDataChannelState.Open; + } +} \ No newline at end of file diff --git a/Assets/PongStreamSender.cs.meta b/Assets/PongStreamSender.cs.meta new file mode 100644 index 0000000..75be6d9 --- /dev/null +++ b/Assets/PongStreamSender.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 873305531e6c3bd4a8bdb1ef21aaf367 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/Bidirectional.unity b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/Bidirectional.unity index 01d5629..352cc2a 100644 --- a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/Bidirectional.unity +++ b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/Bidirectional.unity @@ -38,7 +38,6 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -104,7 +103,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +116,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -150,11 +149,11 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1180904220} - {fileID: 1423621364} m_Father: {fileID: 1031505000} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} @@ -215,9 +214,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1013695939} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -295,10 +294,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1046847830} m_Father: {fileID: 447881815} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -349,7 +348,7 @@ MonoBehaviour: m_HandleRect: {fileID: 375413528} m_Direction: 2 m_Value: 1 - m_Size: 0.99999994 + m_Size: 0.4401976 m_NumberOfSteps: 0 m_OnValueChanged: m_PersistentCalls: @@ -420,9 +419,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2128695119} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -500,16 +499,16 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2143861285} - {fileID: 678698532} m_Father: {fileID: 932364532} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 188.24998, y: -25} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &185332756 MonoBehaviour: @@ -562,7 +561,10 @@ MonoBehaviour: m_HideMobileInput: 0 m_CharacterValidation: 0 m_CharacterLimit: 0 - m_OnEndEdit: + m_OnSubmit: + m_PersistentCalls: + m_Calls: [] + m_OnDidEndEdit: m_PersistentCalls: m_Calls: [] m_OnValueChanged: @@ -640,10 +642,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2063941925} m_Father: {fileID: 2101892767} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -655,6 +657,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 528948626} m_Modifications: - target: {fileID: 508261314311064870, guid: 562feaa3a43a01841a00c6ac89b133fb, @@ -918,6 +921,9 @@ PrefabInstance: value: 0 objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 562feaa3a43a01841a00c6ac89b133fb, type: 3} --- !u!224 &310412160 stripped RectTransform: @@ -953,9 +959,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 474040021} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -1033,10 +1039,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1261207007} m_Father: {fileID: 1268027397} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -1159,15 +1165,15 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1676245737} m_Father: {fileID: 932364532} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 376.49997, y: -60} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 1, y: 1} --- !u!114 &346362251 MonoBehaviour: @@ -1279,9 +1285,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1046847830} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -1355,10 +1361,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1485084886} m_Father: {fileID: 1664650282} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -1445,12 +1451,12 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1031505000} - {fileID: 2101892767} - {fileID: 174861474} m_Father: {fileID: 528948626} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -1552,12 +1558,12 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1136971054} - {fileID: 966029635} - {fileID: 313258692} m_Father: {fileID: 1464711774} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.5} m_AnchorMax: {x: 1, y: 0.5} @@ -1617,6 +1623,7 @@ PrefabInstance: m_ObjectHideFlags: 0 serializedVersion: 2 m_Modification: + serializedVersion: 3 m_TransformParent: {fileID: 528948626} m_Modifications: - target: {fileID: 4037113455314838168, guid: 7aa5bec5b1e406445af144843fe4d62c, @@ -1730,6 +1737,9 @@ PrefabInstance: value: BackButtonManager objectReference: {fileID: 0} m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] m_SourcePrefab: {fileID: 100100000, guid: 7aa5bec5b1e406445af144843fe4d62c, type: 3} --- !u!224 &509297428 stripped RectTransform: @@ -1813,7 +1823,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -1827,12 +1839,12 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 447881815} - {fileID: 310412160} - {fileID: 509297428} m_Father: {fileID: 0} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -1867,9 +1879,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2028247180} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -1942,9 +1954,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 185332755} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2021,9 +2033,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1446828911} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2083,6 +2095,7 @@ GameObject: - component: {fileID: 710419883} - component: {fileID: 710419882} - component: {fileID: 710419881} + - component: {fileID: 710419884} m_Layer: 0 m_Name: UICamera m_TagString: MainCamera @@ -2112,9 +2125,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -2148,13 +2169,58 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 710419879} + serializedVersion: 2 m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 1, z: -10} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &710419884 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 710419879} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: a79441f348de89743a2939f4d699eac1, type: 3} + m_Name: + m_EditorClassIdentifier: + m_RenderShadows: 1 + m_RequiresDepthTextureOption: 2 + m_RequiresOpaqueTextureOption: 2 + m_CameraType: 0 + m_Cameras: [] + m_RendererIndex: -1 + m_VolumeLayerMask: + serializedVersion: 2 + m_Bits: 1 + m_VolumeTrigger: {fileID: 0} + m_VolumeFrameworkUpdateModeOption: 2 + m_RenderPostProcessing: 0 + m_Antialiasing: 0 + m_AntialiasingQuality: 2 + m_StopNaN: 0 + m_Dithering: 0 + m_ClearDepth: 1 + m_AllowXRRendering: 1 + m_AllowHDROutput: 1 + m_UseScreenCoordOverride: 0 + m_ScreenSizeOverride: {x: 0, y: 0, z: 0, w: 0} + m_ScreenCoordScaleBias: {x: 0, y: 0, z: 0, w: 0} + m_RequiresDepthTexture: 0 + m_RequiresColorTexture: 0 + m_Version: 2 + m_TaaSettings: + m_Quality: 3 + m_FrameInfluence: 0.1 + m_JitterScale: 1 + m_MipBias: 0 + m_VarianceClampScale: 0.9 + m_ContrastAdaptiveSharpening: 0 --- !u!1 &848285179 GameObject: m_ObjectHideFlags: 0 @@ -2183,9 +2249,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1134039333} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2261,17 +2327,17 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 185332755} - {fileID: 346362250} - {fileID: 2128695119} m_Father: {fileID: 1423621364} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 396.49997, y: -180} - m_SizeDelta: {x: 376.49997, y: 170} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} --- !u!114 &932364533 MonoBehaviour: @@ -2299,6 +2365,53 @@ MonoBehaviour: m_ChildScaleWidth: 0 m_ChildScaleHeight: 0 m_ReverseArrangement: 0 +--- !u!1 &945565819 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 945565821} + - component: {fileID: 945565820} + m_Layer: 0 + m_Name: PongStreamSender + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &945565820 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945565819} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 873305531e6c3bd4a8bdb1ef21aaf367, type: 3} + m_Name: + m_EditorClassIdentifier: + channelName: heartbeat + logVerbose: 1 + latencyHistorySize: 10 +--- !u!4 &945565821 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 945565819} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -145.7894, y: -25.414478, z: 163.57951} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &966029634 GameObject: m_ObjectHideFlags: 0 @@ -2327,9 +2440,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 474040021} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.5} m_AnchorMax: {x: 0, y: 0.5} @@ -2403,15 +2516,15 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 172843649} m_Father: {fileID: 2134522315} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 376.49997, y: -160} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 1, y: 1} --- !u!114 &1013695940 MonoBehaviour: @@ -2524,10 +2637,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 131784669} m_Father: {fileID: 447881815} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2611,10 +2724,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 375413528} m_Father: {fileID: 174861474} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2650,10 +2763,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1258234880} m_Father: {fileID: 1664650282} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2775,9 +2888,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1446828911} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2851,16 +2964,16 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2028247180} - {fileID: 848285180} m_Father: {fileID: 2134522315} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 188.24998, y: -135} - m_SizeDelta: {x: 376.49997, y: 30} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 30} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1134039334 MonoBehaviour: @@ -2976,9 +3089,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 474040021} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3051,9 +3164,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2125586391} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3129,16 +3242,16 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1363584100} - {fileID: 1221238572} m_Father: {fileID: 131784669} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 0, y: -250} - m_SizeDelta: {x: 782.99994, y: 250} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 250} m_Pivot: {x: 0, y: 0} --- !u!114 &1180904221 MonoBehaviour: @@ -3195,14 +3308,14 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1180904220} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 587.24994, y: -130} - m_SizeDelta: {x: 371.49997, y: 220} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1221238573 MonoBehaviour: @@ -3361,10 +3474,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1563904669} m_Father: {fileID: 1054277507} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3397,10 +3510,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1708597410} m_Father: {fileID: 314272781} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -3436,11 +3549,11 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1846535030} - {fileID: 314272781} m_Father: {fileID: 2125586391} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -3545,6 +3658,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 01614664b831546d2ae94a42149d80ac, type: 3} m_Name: m_EditorClassIdentifier: + m_SendPointerHoverToParent: 1 m_MoveRepeatDelay: 0.5 m_MoveRepeatRate: 0.1 m_XRTrackingOrigin: {fileID: 0} @@ -3573,6 +3687,7 @@ MonoBehaviour: m_DeselectOnBackgroundClick: 1 m_PointerBehavior: 0 m_CursorLockBehavior: 0 + m_ScrollDeltaPerTick: 6 --- !u!114 &1334313019 MonoBehaviour: m_ObjectHideFlags: 0 @@ -3595,12 +3710,13 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1334313017} + serializedVersion: 2 m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1360947116 GameObject: @@ -3630,9 +3746,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 2125586391} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0.5} m_AnchorMax: {x: 1, y: 0.5} @@ -3706,14 +3822,14 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1180904220} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 195.74998, y: -130} - m_SizeDelta: {x: 371.49997, y: 220} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1363584101 MonoBehaviour: @@ -3803,16 +3919,16 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 2134522315} - {fileID: 932364532} m_Father: {fileID: 131784669} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 391.49997, y: -350} - m_SizeDelta: {x: 782.99994, y: 200} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 200} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1423621365 MonoBehaviour: @@ -3867,12 +3983,12 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1094835990} - {fileID: 1811427819} - {fileID: 707600075} m_Father: {fileID: 1485084886} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.5} m_AnchorMax: {x: 1, y: 0.5} @@ -3953,10 +4069,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 474040021} m_Father: {fileID: 1846535030} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} @@ -3989,10 +4105,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1446828911} m_Father: {fileID: 441688740} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 1, y: 1} @@ -4027,9 +4143,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1258234880} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0.2} @@ -4103,11 +4219,11 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 441688740} - {fileID: 1054277507} m_Father: {fileID: 1859585769} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -4210,9 +4326,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 346362250} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -4289,9 +4405,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1261207007} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0.2} @@ -4364,9 +4480,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1859585769} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -4443,9 +4559,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1859585769} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0.5} m_AnchorMax: {x: 1, y: 0.5} @@ -4518,9 +4634,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 1446828911} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0.5} m_AnchorMax: {x: 0, y: 0.5} @@ -4594,10 +4710,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1464711774} m_Father: {fileID: 1268027397} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -4684,17 +4800,17 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1720753588} - {fileID: 1772713976} - {fileID: 1664650282} m_Father: {fileID: 2134522315} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 188.24998, y: -25} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &1859585770 MonoBehaviour: @@ -4858,14 +4974,15 @@ MonoBehaviour: m_useDefault: 1 signalingSettingsObject: {fileID: 0} signalingSettings: - id: 0 + rid: 0 handlers: - {fileID: 1915034406} runOnAwake: 0 evaluateCommandlineArguments: 1 references: - version: 1 - 00000000: + version: 2 + RefIds: + - rid: 0 type: {class: WebSocketSignalingSettings, ns: Unity.RenderStreaming, asm: Unity.RenderStreaming} data: m_url: ws://127.0.0.1 @@ -4885,9 +5002,9 @@ RectTransform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0.5, y: 0.5} m_AnchorMax: {x: 0.5, y: 0.5} @@ -5002,10 +5119,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 640709221} m_Father: {fileID: 1134039333} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 1} m_AnchorMax: {x: 0, y: 1} @@ -5078,9 +5195,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 220075808} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 0, y: 0} @@ -5154,10 +5271,10 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 220075808} m_Father: {fileID: 447881815} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -5280,17 +5397,17 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1161220085} - {fileID: 1360947117} - {fileID: 1268027397} m_Father: {fileID: 2134522315} - m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 188.24998, y: -85} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &2125586392 MonoBehaviour: @@ -5418,15 +5535,15 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 174875021} m_Father: {fileID: 932364532} - m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 376.49997, y: -120} - m_SizeDelta: {x: 376.49997, y: 50} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 50} m_Pivot: {x: 1, y: 1} --- !u!114 &2128695120 MonoBehaviour: @@ -5537,18 +5654,18 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: - {fileID: 1859585769} - {fileID: 2125586391} - {fileID: 1134039333} - {fileID: 1013695939} m_Father: {fileID: 1423621364} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 10, y: -220} - m_SizeDelta: {x: 376.49997, y: 210} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 0} --- !u!114 &2134522316 MonoBehaviour: @@ -5604,9 +5721,9 @@ RectTransform: m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 185332755} - m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -5655,3 +5772,12 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2143861284} m_CullTransparentMesh: 0 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 710419883} + - {fileID: 1915034403} + - {fileID: 528948626} + - {fileID: 1334313020} + - {fileID: 945565821} diff --git a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/BidirectionalSample.cs b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/BidirectionalSample.cs index 404c1ac..febe314 100644 --- a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/BidirectionalSample.cs +++ b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/Bidirectional/BidirectionalSample.cs @@ -30,6 +30,7 @@ namespace Unity.RenderStreaming.Samples void Awake() { + startButton.interactable = true; webcamSelectDropdown.interactable = true; setUpButton.interactable = false; diff --git a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset index 028c88e..de16384 100644 --- a/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset +++ b/Assets/Samples/Unity Render Streaming/3.1.0-exp.9/Example/RenderStreamingSample.asset @@ -14,14 +14,14 @@ MonoBehaviour: m_EditorClassIdentifier: automaticStreaming: 0 signalingSettings: - rid: 0 + rid: 9043491472605708289 references: version: 2 RefIds: - - rid: 0 + - rid: 9043491472605708289 type: {class: WebSocketSignalingSettings, ns: Unity.RenderStreaming, asm: Unity.RenderStreaming} data: - m_url: ws://192.168.31.67:8080 + m_url: wss://192.168.31.67:8080 m_iceServers: - m_urls: - stun:stun.l.google.com:19302 diff --git a/WebApp/src/class/websockethandler.ts b/WebApp/src/class/websockethandler.ts index c7ada78..f054bda 100644 --- a/WebApp/src/class/websockethandler.ts +++ b/WebApp/src/class/websockethandler.ts @@ -1,104 +1,191 @@ +/** + * WebSocket处理器 + * 负责管理WebSocket连接和信令消息处理 + */ import Offer from './offer'; import Answer from './answer'; import Candidate from './candidate'; +/** + * 是否为私有模式 + */ let isPrivate: boolean; -// [{sessonId:[connectionId,...]}] +/** + * 客户端连接映射 + * 键: WebSocket实例 + * 值: 该WebSocket的连接ID集合 + */ const clients: Map> = new Map>(); -// [{connectionId:[sessionId1, sessionId2]}] +/** + * 连接对映射 + * 键: connectionId + * 值: [WebSocket实例1, WebSocket实例2] + */ const connectionPair: Map = new Map(); +/** + * 获取或创建WebSocket会话的连接ID集合 + * @param session WebSocket会话实例 + * @returns 连接ID的Set集合 + */ function getOrCreateConnectionIds(session: WebSocket): Set { let connectionIds = null; + // 检查客户端是否已存在 if (!clients.has(session)) { + // 如果不存在,创建新的连接ID集合 connectionIds = new Set(); + // 将新的连接ID集合与客户端关联 clients.set(session, connectionIds); } + // 获取客户端的连接ID集合 connectionIds = clients.get(session); + // 返回连接ID集合 return connectionIds; } +/** + * 重置处理器状态 + * @param mode 通信模式(public或private) + */ function reset(mode: string): void { + // 设置是否为私有模式 isPrivate = mode == "private"; } +/** + * 添加新的WebSocket连接 + * @param ws WebSocket连接实例 + */ function add(ws: WebSocket): void { + // 为新连接创建空的连接ID集合 clients.set(ws, new Set()); + // 记录添加WebSocket连接的日志 + console.log(`Add WebSocket: ${ws}`); } +/** + * 移除WebSocket连接 + * @param ws WebSocket连接实例 + */ function remove(ws: WebSocket): void { + // 获取连接的所有连接ID const connectionIds = clients.get(ws); + // 遍历所有连接ID connectionIds.forEach(connectionId => { + // 获取连接对 const pair = connectionPair.get(connectionId); if (pair) { + // 找到另一个WebSocket实例 const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; if (otherSessionWs) { + // 向另一个连接发送断开连接消息 otherSessionWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); } } + // 从连接对映射中删除 connectionPair.delete(connectionId); + // 记录删除连接ID的日志 + console.log(`Remove connectionId: ${connectionId}`); }); + // 从客户端映射中删除 clients.delete(ws); } +/** + * 处理连接请求 + * @param ws WebSocket连接实例 + * @param connectionId 连接ID + */ function onConnect(ws: WebSocket, connectionId: string): void { let polite = true; + // 处理私有模式 if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); if (pair[0] != null && pair[1] != null) { + // 连接ID已被使用 ws.send(JSON.stringify({ type: "error", message: `${connectionId}: This connection id is already used.` })); return; } else if (pair[0] != null) { + // 找到配对连接 connectionPair.set(connectionId, [pair[0], ws]); } } else { + // 创建新的连接对 connectionPair.set(connectionId, [ws, null]); polite = false; } } + // 获取或创建连接ID集合 const connectionIds = getOrCreateConnectionIds(ws); + // 添加连接ID connectionIds.add(connectionId); + // 发送连接成功消息 ws.send(JSON.stringify({ type: "connect", connectionId: connectionId, polite: polite })); } +/** + * 处理断开连接请求 + * @param ws WebSocket连接实例 + * @param connectionId 连接ID + */ function onDisconnect(ws: WebSocket, connectionId: string): void { + // 获取连接的连接ID集合 const connectionIds = clients.get(ws); + // 从集合中删除连接ID connectionIds.delete(connectionId); + // 处理连接对 if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); + // 找到另一个WebSocket实例 const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; if (otherSessionWs) { + // 向另一个连接发送断开连接消息 otherSessionWs.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); } } + // 从连接对映射中删除 connectionPair.delete(connectionId); + // 向当前连接发送断开连接消息 ws.send(JSON.stringify({ type: "disconnect", connectionId: connectionId })); } +/** + * 处理offer信令 + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ function onOffer(ws: WebSocket, message: any): void { + // 获取连接ID const connectionId = message.connectionId as string; + // 创建新的offer const newOffer = new Offer(message.sdp, Date.now(), false); + // 处理私有模式 if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); + // 找到另一个WebSocket实例 const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; if (otherSessionWs) { + // 设置为polite模式 newOffer.polite = true; + // 发送offer消息 otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "offer", data: newOffer })); } } return; } + // 公共模式:创建新的连接对 connectionPair.set(connectionId, [ws, null]); + // 向所有其他客户端广播offer clients.forEach((_v, k) => { if (k == ws) { return; @@ -107,41 +194,66 @@ function onOffer(ws: WebSocket, message: any): void { }); } +/** + * 处理answer信令 + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ function onAnswer(ws: WebSocket, message: any): void { + // 获取连接ID const connectionId = message.connectionId as string; + // 获取或创建连接ID集合 const connectionIds = getOrCreateConnectionIds(ws); + // 添加连接ID connectionIds.add(connectionId); + // 创建新的answer const newAnswer = new Answer(message.sdp, Date.now()); + // 检查连接对是否存在 if (!connectionPair.has(connectionId)) { return; } + // 获取连接对 const pair = connectionPair.get(connectionId); + // 找到另一个WebSocket实例 const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; + // 公共模式:更新连接对 if (!isPrivate) { connectionPair.set(connectionId, [otherSessionWs, ws]); } + // 发送answer消息 otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "answer", data: newAnswer })); } +/** + * 处理candidate信令 + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ function onCandidate(ws: WebSocket, message: any): void { + // 获取连接ID const connectionId = message.connectionId; + // 创建新的candidate const candidate = new Candidate(message.candidate, message.sdpMLineIndex, message.sdpMid, Date.now()); + // 处理私有模式 if (isPrivate) { if (connectionPair.has(connectionId)) { const pair = connectionPair.get(connectionId); + // 找到另一个WebSocket实例 const otherSessionWs = pair[0] == ws ? pair[1] : pair[0]; if (otherSessionWs) { + // 发送candidate消息 otherSessionWs.send(JSON.stringify({ from: connectionId, to: "", type: "candidate", data: candidate })); } } return; } + // 公共模式:向所有其他客户端广播candidate clients.forEach((_v, k) => { if (k === ws) { return; @@ -150,4 +262,102 @@ function onCandidate(ws: WebSocket, message: any): void { }); } -export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate }; +/** + * 处理获取连接信息请求 + * @param ws WebSocket连接实例 + */ +function onGetConnections(ws: WebSocket): void { + // 收集所有connectionId + const allConnectionIds = Array.from(connectionPair.keys()); + + // 收集所有WebSocket连接信息 + const allWebSockets = Array.from(clients.entries()).map(([ws, connectionIds]) => { + return { + connectionIds: Array.from(connectionIds), + // 注意:这里不能直接序列化WebSocket对象,只能返回连接数量或其他信息 + connected: true + }; + }); + + // 发送连接信息给请求的客户端 + ws.send(JSON.stringify({ + type: "connections", + connectionIds: allConnectionIds, + websocketCount: clients.size + })); +} + +/** + * 处理广播消息请求 + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +/** + * 处理广播消息请求 + * @param ws WebSocket连接实例 + * @param message 消息数据 + */ +function onBroadcast(ws: WebSocket, message: any): void { + const broadcastMessage = message.message; + const targetConnectionId = message.targetConnectionId; + + if (targetConnectionId) { + // 向指定连接广播 + if (connectionPair.has(targetConnectionId)) { + const pair = connectionPair.get(targetConnectionId); + // 向连接对中的两个WebSocket实例发送消息 + if (pair[0]) { + pair[0].send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + } + if (pair[1]) { + pair[1].send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + } + } + } else { + // 全局广播:向所有客户端发送消息 + clients.forEach((_v, k) => { + k.send(JSON.stringify({ + type: "broadcast", + message: broadcastMessage, + from: "server" + })); + }); + } +} +function AddHeartbeat(ws: WebSocket){ + // 初始化心跳检测 + (ws as any).lastActivity = Date.now(); + + // 设置心跳检测定时器,每30秒发送一次ping + (ws as any).heartbeatTimer = setInterval(() => { + const now = Date.now(); + // 检查上次活动时间,如果超过60秒没有活动,关闭连接 + if (now - (ws as any).lastActivity > 10000) { + console.log('WebSocket connection timeout, closing...'); + clearInterval((ws as any).heartbeatTimer); + //ws.close(); + } else { + // 发送ping消息 + ws.send(JSON.stringify({ type: "ping" })); + console.log('WebSocket connection heartbeat, lastActivity: ', (ws as any).lastActivity); + } + }, 3000); +} +function RemoveHeartbeat(ws: WebSocket){ + // 清除心跳检测定时器 + if ((ws as any).heartbeatTimer) { + clearInterval((ws as any).heartbeatTimer); + } +} +/** + * 导出WebSocket处理器函数 + */ +export { reset, add, remove, onConnect, onDisconnect, onOffer, onAnswer, onCandidate, onGetConnections, onBroadcast, AddHeartbeat, RemoveHeartbeat }; diff --git a/WebApp/src/websocket.ts b/WebApp/src/websocket.ts index 6f4dd7d..d208c3b 100644 --- a/WebApp/src/websocket.ts +++ b/WebApp/src/websocket.ts @@ -6,53 +6,109 @@ export default class WSSignaling { server: Server; wss: websocket.Server; + /** + * 构造函数,初始化WebSocket信令服务器 + * @param server HTTP服务器实例 + * @param mode 通信模式(public或private) + */ constructor(server: Server, mode: string) { + // 保存服务器实例 this.server = server; + // 创建WebSocket服务器 this.wss = new websocket.Server({ server }); + // 重置处理器,设置通信模式 handler.reset(mode); + /** + * 监听WebSocket连接事件 + * @param ws WebSocket连接实例 + */ this.wss.on('connection', (ws: WebSocket) => { - + // 添加新的WebSocket连接到处理器 handler.add(ws); - + handler.AddHeartbeat(ws); + /** + * 监听连接关闭事件 + */ ws.onclose = (): void => { + // 从处理器中移除关闭的连接 handler.remove(ws); + handler.RemoveHeartbeat(ws); }; + /** + * 监听消息事件 + * @param event 消息事件对象 + */ ws.onmessage = (event: MessageEvent): void => { + // 消息类型说明: + // 1. connect, disconnect 消息格式: + // { type: "connect", connectionId: "连接ID" } + // { type: "disconnect", connectionId: "连接ID" } + // 2. offer, answer, candidate 消息格式: + // { + // type: "offer", + // data: { + // from: "发送方连接ID", + // to: "接收方连接ID", + // data: "信令数据" + // } + // } + // 3. broadcast 消息格式: + // { + // type: "broadcast", + // message: "广播消息内容", + // targetConnectionId: "目标连接ID(可选)" + // } - // type: connect, disconnect JSON Schema - // connectionId: connect or disconnect connectionId - - // type: offer, answer, candidate JSON Schema - // from: from connection id - // to: to connection id - // data: any message data structure - + // 解析消息数据 const msg = JSON.parse(event.data); + // 检查消息是否有效 if (!msg || !this) { return; } + // 打印接收到的消息 console.log(msg); + // 根据消息类型处理 switch (msg.type) { case "connect": + // 处理连接请求 handler.onConnect(ws, msg.connectionId); break; case "disconnect": + // 处理断开连接请求 handler.onDisconnect(ws, msg.connectionId); break; case "offer": + // 处理offer信令 handler.onOffer(ws, msg.data); break; case "answer": + // 处理answer信令 handler.onAnswer(ws, msg.data); break; case "candidate": + // 处理candidate信令 handler.onCandidate(ws, msg.data); break; + case "ping": + // 处理心跳请求,回复pong + ws.send(JSON.stringify({ type: "pong" })); + break; + case "pong": + // 处理心跳响应,更新最后活动时间 + (ws as any).lastActivity = Date.now(); + break; + case "broadcast": + handler.onBroadcast(ws, msg.data); + break; + case "onGetConnections": + handler.onGetConnections(ws); + break; default: + // 忽略未知消息类型 break; } };