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;
}
};