diff --git a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs
index 910f0a1..9f41b27 100644
--- a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs
+++ b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiSimpleUDPAudioSender.cs
@@ -88,6 +88,14 @@ namespace Convai.Scripts.Runtime.Multiplayer
{
targetIP = cfg.ipAddress;
targetPort = cfg.port;
+
+ // Subscribe to peer discovery if enabled
+ if (cfg.useAutoDiscovery && UDPPeerDiscovery.Instance != null)
+ {
+ UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered;
+ UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost;
+ ConvaiLogger.Info("Audio sender subscribed to peer discovery", ConvaiLogger.LogCategory.Character);
+ }
}
else
{
@@ -130,6 +138,13 @@ namespace Convai.Scripts.Runtime.Multiplayer
private void OnDestroy()
{
+ // Unsubscribe from peer discovery
+ if (UDPPeerDiscovery.Instance != null)
+ {
+ UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered;
+ UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost;
+ }
+
StopRecording();
_cancellationTokenSource?.Cancel();
_cancellationTokenSource?.Dispose();
@@ -143,6 +158,24 @@ namespace Convai.Scripts.Runtime.Multiplayer
_udpClient?.Close();
}
+ private void HandlePeerDiscovered(string peerIP)
+ {
+ targetIP = peerIP;
+ _targetEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), targetPort);
+ ConvaiLogger.Info($"🎤 Audio sender now targeting peer at {peerIP}:{targetPort}", ConvaiLogger.LogCategory.Character);
+ }
+
+ private void HandlePeerLost()
+ {
+ var cfg = NetworkConfig.Instance;
+ if (cfg != null)
+ {
+ targetIP = cfg.fallbackBroadcastIP;
+ _targetEndPoint = new IPEndPoint(IPAddress.Parse(targetIP), targetPort);
+ ConvaiLogger.Warn($"🎤 Audio sender falling back to broadcast: {targetIP}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
private void InitializeNetwork()
{
try
diff --git a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs
index 759077a..6496f74 100644
--- a/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs
+++ b/Unity-Master/Assets/Scripts/Multiplayer/ConvaiUDPSpeechSender.cs
@@ -59,6 +59,14 @@ namespace Convai.Scripts.Runtime.Multiplayer
{
targetIP = cfg.ipAddress;
targetPort = cfg.port;
+
+ // Subscribe to peer discovery if enabled
+ if (cfg.useAutoDiscovery && UDPPeerDiscovery.Instance != null)
+ {
+ UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered;
+ UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost;
+ ConvaiLogger.Info("Speech sender subscribed to peer discovery", ConvaiLogger.LogCategory.Character);
+ }
}
else
{
@@ -73,10 +81,35 @@ namespace Convai.Scripts.Runtime.Multiplayer
private void OnDestroy()
{
+ // Unsubscribe from peer discovery
+ if (UDPPeerDiscovery.Instance != null)
+ {
+ UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered;
+ UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost;
+ }
+
CleanupNPCSubscriptions();
CleanupNetwork();
}
+ private void HandlePeerDiscovered(string peerIP)
+ {
+ targetIP = peerIP;
+ _targetEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), targetPort);
+ ConvaiLogger.Info($"🔊 Speech sender now targeting peer at {peerIP}:{targetPort}", ConvaiLogger.LogCategory.Character);
+ }
+
+ private void HandlePeerLost()
+ {
+ var cfg = NetworkConfig.Instance;
+ if (cfg != null)
+ {
+ targetIP = cfg.fallbackBroadcastIP;
+ _targetEndPoint = new IPEndPoint(IPAddress.Parse(targetIP), targetPort);
+ ConvaiLogger.Warn($"🔊 Speech sender falling back to broadcast: {targetIP}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
private void InitializeNetwork()
{
try
diff --git a/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs
new file mode 100644
index 0000000..aa486f9
--- /dev/null
+++ b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs
@@ -0,0 +1,460 @@
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Threading;
+using System.Threading.Tasks;
+using Convai.Scripts.Runtime.LoggerSystem;
+using UnityEngine;
+
+namespace Convai.Scripts.Runtime.Multiplayer
+{
+ ///
+ /// UDP Peer Discovery - Finds other Quest Pro on the same hotspot network
+ /// Solves AP isolation problem by discovering peer IP and switching to direct unicast
+ ///
+ public class UDPPeerDiscovery : MonoBehaviour
+ {
+ [Header("Player Configuration")]
+ [SerializeField] private byte localPlayerID = 1; // 1 or 2 - must be unique per Quest Pro
+ [SerializeField] private bool enableDebugLogging = true;
+
+ [Header("Discovery Settings")]
+ [SerializeField] private float discoveryBroadcastInterval = 2.0f; // Broadcast every 2 seconds
+ [SerializeField] private float heartbeatInterval = 5.0f; // Heartbeat every 5 seconds
+ [SerializeField] private float peerTimeoutSeconds = 15.0f; // Consider peer lost after 15s
+
+ // Events
+ public event Action OnPeerDiscovered; // Fires when peer IP is found
+ public event Action OnPeerLost; // Fires when peer connection is lost
+ public event Action OnConnectionStateChanged;
+
+ // Network components
+ private UdpClient _udpClient;
+ private bool _isRunning = false;
+ private int _listenPort;
+ private CancellationTokenSource _cancellationTokenSource;
+
+ // Peer state
+ private string _peerIP = "";
+ private byte _peerPlayerID = 0;
+ private DateTime _lastPeerPacketTime;
+ private ConnectionState _connectionState = ConnectionState.Disconnected;
+
+ // Discovery protocol constants
+ private const uint DISCOVERY_MAGIC = 0x44495343; // "DISC" in hex
+ private const byte PACKET_TYPE_REQUEST = 0x01;
+ private const byte PACKET_TYPE_RESPONSE = 0x02;
+ private const byte PACKET_TYPE_HEARTBEAT = 0x03;
+
+ // Singleton for easy access
+ private static UDPPeerDiscovery _instance;
+ public static UDPPeerDiscovery Instance => _instance;
+
+ public enum ConnectionState
+ {
+ Disconnected,
+ Discovering,
+ Connected,
+ Lost
+ }
+
+ // Public properties
+ public string PeerIP => _peerIP;
+ public byte LocalPlayerID => localPlayerID;
+ public byte PeerPlayerID => _peerPlayerID;
+ public ConnectionState CurrentState => _connectionState;
+ public bool IsConnected => _connectionState == ConnectionState.Connected;
+
+ private void Awake()
+ {
+ // Singleton pattern
+ if (_instance != null && _instance != this)
+ {
+ Destroy(gameObject);
+ return;
+ }
+ _instance = this;
+ }
+
+ private void Start()
+ {
+ // Get network config
+ var cfg = NetworkConfig.Instance;
+ if (cfg != null)
+ {
+ _listenPort = cfg.port;
+ }
+ else
+ {
+ ConvaiLogger.Error("NetworkConfig not found! Using default port 1221", ConvaiLogger.LogCategory.Character);
+ _listenPort = 1221;
+ }
+
+ _cancellationTokenSource = new CancellationTokenSource();
+ StartDiscovery();
+ }
+
+ private void OnDestroy()
+ {
+ StopDiscovery();
+ _cancellationTokenSource?.Cancel();
+ _cancellationTokenSource?.Dispose();
+
+ if (_instance == this)
+ _instance = null;
+ }
+
+ private void Update()
+ {
+ // Check for peer timeout
+ if (_connectionState == ConnectionState.Connected)
+ {
+ TimeSpan timeSinceLastPacket = DateTime.UtcNow - _lastPeerPacketTime;
+ if (timeSinceLastPacket.TotalSeconds > peerTimeoutSeconds)
+ {
+ HandlePeerLost();
+ }
+ }
+ }
+
+ private void StartDiscovery()
+ {
+ if (_isRunning) return;
+
+ try
+ {
+ // Create UDP client with port reuse for shared port
+ _udpClient = new UdpClient();
+ _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
+ _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _listenPort));
+ _udpClient.EnableBroadcast = true;
+
+ _isRunning = true;
+ SetConnectionState(ConnectionState.Discovering);
+
+ ConvaiLogger.Info($"🔍 Peer Discovery started - Player {localPlayerID} on port {_listenPort}", ConvaiLogger.LogCategory.Character);
+
+ // Start listening for discovery packets
+ _ = ListenForDiscoveryPackets(_cancellationTokenSource.Token);
+
+ // Start broadcasting discovery requests
+ _ = BroadcastDiscoveryRequests(_cancellationTokenSource.Token);
+
+ // Start heartbeat when connected
+ _ = SendHeartbeats(_cancellationTokenSource.Token);
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Failed to start peer discovery: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
+ private void StopDiscovery()
+ {
+ if (!_isRunning) return;
+
+ _isRunning = false;
+ _udpClient?.Close();
+ _udpClient?.Dispose();
+ _udpClient = null;
+
+ SetConnectionState(ConnectionState.Disconnected);
+ ConvaiLogger.Info("Peer Discovery stopped", ConvaiLogger.LogCategory.Character);
+ }
+
+ private async Task ListenForDiscoveryPackets(CancellationToken cancellationToken)
+ {
+ while (_isRunning && !cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ var result = await _udpClient.ReceiveAsync();
+ await ProcessDiscoveryPacket(result.Buffer, result.RemoteEndPoint);
+ }
+ catch (ObjectDisposedException)
+ {
+ // Normal when stopping
+ break;
+ }
+ catch (Exception ex)
+ {
+ if (_isRunning)
+ {
+ ConvaiLogger.Error($"Error receiving discovery packet: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ }
+ }
+
+ private async Task BroadcastDiscoveryRequests(CancellationToken cancellationToken)
+ {
+ while (_isRunning && !cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ // Only broadcast if we're still discovering
+ if (_connectionState == ConnectionState.Discovering || _connectionState == ConnectionState.Lost)
+ {
+ await SendDiscoveryRequest();
+ }
+
+ await Task.Delay((int)(discoveryBroadcastInterval * 1000), cancellationToken);
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Error broadcasting discovery: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ }
+
+ private async Task SendHeartbeats(CancellationToken cancellationToken)
+ {
+ while (_isRunning && !cancellationToken.IsCancellationRequested)
+ {
+ try
+ {
+ await Task.Delay((int)(heartbeatInterval * 1000), cancellationToken);
+
+ // Only send heartbeat if connected
+ if (_connectionState == ConnectionState.Connected && !string.IsNullOrEmpty(_peerIP))
+ {
+ await SendHeartbeat();
+ }
+ }
+ catch (OperationCanceledException)
+ {
+ break;
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Error sending heartbeat: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ }
+
+ private async Task SendDiscoveryRequest()
+ {
+ try
+ {
+ byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_REQUEST);
+
+ // Broadcast to subnet (will be blocked by AP isolation but we try anyway)
+ var broadcastEndPoint = new IPEndPoint(IPAddress.Broadcast, _listenPort);
+ await _udpClient.SendAsync(packet, packet.Length, broadcastEndPoint);
+
+ if (enableDebugLogging && UnityEngine.Random.value < 0.1f) // Log 10% of broadcasts to reduce spam
+ {
+ ConvaiLogger.DebugLog($"🔍 Broadcasting discovery request (Player {localPlayerID})", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Failed to send discovery request: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
+ private async Task SendDiscoveryResponse(IPEndPoint targetEndPoint)
+ {
+ try
+ {
+ byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_RESPONSE);
+ await _udpClient.SendAsync(packet, packet.Length, targetEndPoint);
+
+ if (enableDebugLogging)
+ {
+ ConvaiLogger.DebugLog($"📤 Sent discovery response to {targetEndPoint.Address}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Failed to send discovery response: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
+ private async Task SendHeartbeat()
+ {
+ try
+ {
+ if (string.IsNullOrEmpty(_peerIP)) return;
+
+ byte[] packet = CreateDiscoveryPacket(PACKET_TYPE_HEARTBEAT);
+ var peerEndPoint = new IPEndPoint(IPAddress.Parse(_peerIP), _listenPort);
+ await _udpClient.SendAsync(packet, packet.Length, peerEndPoint);
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Warn($"Failed to send heartbeat: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
+ private byte[] CreateDiscoveryPacket(byte packetType)
+ {
+ byte[] packet = new byte[14];
+ int offset = 0;
+
+ // Magic number
+ BitConverter.GetBytes(DISCOVERY_MAGIC).CopyTo(packet, offset);
+ offset += 4;
+
+ // Packet type
+ packet[offset] = packetType;
+ offset += 1;
+
+ // Player ID
+ packet[offset] = localPlayerID;
+ offset += 1;
+
+ // Timestamp
+ long timestamp = DateTime.UtcNow.Ticks;
+ BitConverter.GetBytes(timestamp).CopyTo(packet, offset);
+
+ return packet;
+ }
+
+ private async Task ProcessDiscoveryPacket(byte[] data, IPEndPoint senderEndPoint)
+ {
+ try
+ {
+ // Check if this is a discovery packet
+ if (data.Length < 14) return;
+
+ // Verify magic number
+ uint magic = BitConverter.ToUInt32(data, 0);
+ if (magic != DISCOVERY_MAGIC) return;
+
+ // Parse packet
+ byte packetType = data[4];
+ byte senderPlayerID = data[5];
+ long timestamp = BitConverter.ToInt64(data, 6);
+
+ // Ignore packets from ourselves
+ if (senderPlayerID == localPlayerID) return;
+
+ string senderIP = senderEndPoint.Address.ToString();
+
+ if (enableDebugLogging)
+ {
+ string typeStr = packetType switch
+ {
+ PACKET_TYPE_REQUEST => "REQUEST",
+ PACKET_TYPE_RESPONSE => "RESPONSE",
+ PACKET_TYPE_HEARTBEAT => "HEARTBEAT",
+ _ => "UNKNOWN"
+ };
+ ConvaiLogger.DebugLog($"📥 Received {typeStr} from Player {senderPlayerID} at {senderIP}", ConvaiLogger.LogCategory.Character);
+ }
+
+ // Update peer info
+ _lastPeerPacketTime = DateTime.UtcNow;
+
+ switch (packetType)
+ {
+ case PACKET_TYPE_REQUEST:
+ // Respond to discovery request
+ await SendDiscoveryResponse(senderEndPoint);
+
+ // If we weren't connected before, this is a new peer
+ if (_connectionState != ConnectionState.Connected)
+ {
+ HandlePeerDiscovered(senderIP, senderPlayerID);
+ }
+ break;
+
+ case PACKET_TYPE_RESPONSE:
+ // Discovery response received - we found our peer!
+ if (_connectionState != ConnectionState.Connected)
+ {
+ HandlePeerDiscovered(senderIP, senderPlayerID);
+ }
+ break;
+
+ case PACKET_TYPE_HEARTBEAT:
+ // Heartbeat keeps connection alive
+ if (_connectionState == ConnectionState.Connected && _peerIP == senderIP)
+ {
+ // Connection still alive
+ }
+ else if (_connectionState != ConnectionState.Connected)
+ {
+ // Reconnected
+ HandlePeerDiscovered(senderIP, senderPlayerID);
+ }
+ break;
+ }
+ }
+ catch (Exception ex)
+ {
+ ConvaiLogger.Error($"Error processing discovery packet: {ex.Message}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+
+ private void HandlePeerDiscovered(string peerIP, byte peerPlayerID)
+ {
+ _peerIP = peerIP;
+ _peerPlayerID = peerPlayerID;
+ _lastPeerPacketTime = DateTime.UtcNow;
+
+ SetConnectionState(ConnectionState.Connected);
+
+ ConvaiLogger.Info($"✅ Peer discovered! Player {peerPlayerID} at {peerIP}", ConvaiLogger.LogCategory.Character);
+
+ // Notify listeners
+ OnPeerDiscovered?.Invoke(peerIP);
+ }
+
+ private void HandlePeerLost()
+ {
+ ConvaiLogger.Warn($"⚠️ Peer connection lost (Player {_peerPlayerID} at {_peerIP})", ConvaiLogger.LogCategory.Character);
+
+ string lostPeerIP = _peerIP;
+ _peerIP = "";
+ _peerPlayerID = 0;
+
+ SetConnectionState(ConnectionState.Lost);
+
+ // Notify listeners
+ OnPeerLost?.Invoke();
+
+ // Restart discovery
+ SetConnectionState(ConnectionState.Discovering);
+ }
+
+ private void SetConnectionState(ConnectionState newState)
+ {
+ if (_connectionState != newState)
+ {
+ _connectionState = newState;
+ OnConnectionStateChanged?.Invoke(newState);
+
+ if (enableDebugLogging)
+ {
+ ConvaiLogger.Info($"🔄 Connection state changed to: {newState}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+ }
+
+ // Public methods for manual control
+ public void RestartDiscovery()
+ {
+ ConvaiLogger.Info("Manually restarting peer discovery", ConvaiLogger.LogCategory.Character);
+ _peerIP = "";
+ _peerPlayerID = 0;
+ SetConnectionState(ConnectionState.Discovering);
+ }
+
+ public void ShowStatus()
+ {
+ ConvaiLogger.Info($"=== Peer Discovery Status ===", ConvaiLogger.LogCategory.Character);
+ ConvaiLogger.Info($"Local Player ID: {localPlayerID}", ConvaiLogger.LogCategory.Character);
+ ConvaiLogger.Info($"Connection State: {_connectionState}", ConvaiLogger.LogCategory.Character);
+ ConvaiLogger.Info($"Peer IP: {(_peerIP ?? "None")}", ConvaiLogger.LogCategory.Character);
+ ConvaiLogger.Info($"Peer Player ID: {_peerPlayerID}", ConvaiLogger.LogCategory.Character);
+ ConvaiLogger.Info($"Listen Port: {_listenPort}", ConvaiLogger.LogCategory.Character);
+ }
+ }
+}
+
diff --git a/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta
new file mode 100644
index 0000000..6a6ef96
--- /dev/null
+++ b/Unity-Master/Assets/Scripts/Multiplayer/UDPPeerDiscovery.cs.meta
@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: a8b9c1d2e3f4a5b6c7d8e9f0a1b2c3d4
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
+
diff --git a/Unity-Master/Assets/Scripts/NetworkConfig.asset b/Unity-Master/Assets/Scripts/NetworkConfig.asset
index 1e5c4a8..358fcda 100644
--- a/Unity-Master/Assets/Scripts/NetworkConfig.asset
+++ b/Unity-Master/Assets/Scripts/NetworkConfig.asset
@@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1
-oid sha256:17852fdd649ea85c2e4ac65f7d4426459d7466e27b5a807f5663811bee8b3c6b
-size 443
+oid sha256:54d451cef255fb6db3a3a443957065843bf5aa42025cebc34890aa3933c4e5d9
+size 559
diff --git a/Unity-Master/Assets/Scripts/NetworkConfig.cs b/Unity-Master/Assets/Scripts/NetworkConfig.cs
index 8b35c98..b9681ce 100644
--- a/Unity-Master/Assets/Scripts/NetworkConfig.cs
+++ b/Unity-Master/Assets/Scripts/NetworkConfig.cs
@@ -6,6 +6,19 @@ public class NetworkConfig : ScriptableObject
[Header("Global Network Settings")]
public string ipAddress = "127.0.0.1";
public int port = 1221; // Single port for all UDP communication
+
+ [Header("Peer Discovery Settings")]
+ [Tooltip("Enable auto-discovery to find peer Quest Pro on same network")]
+ public bool useAutoDiscovery = true;
+
+ [Tooltip("Fallback broadcast IP if discovery fails (default: 255.255.255.255)")]
+ public string fallbackBroadcastIP = "255.255.255.255";
+
+ [Tooltip("How often to broadcast discovery requests (seconds)")]
+ public float discoveryIntervalSeconds = 2.0f;
+
+ [Tooltip("Time before considering peer lost (seconds)")]
+ public float peerTimeoutSeconds = 15.0f;
private static NetworkConfig _instance;
public static NetworkConfig Instance
diff --git a/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs b/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs
index 7343f97..229ee79 100644
--- a/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs
+++ b/Unity-Master/Assets/Scripts/UDPAvatarBroadcaster.cs
@@ -92,6 +92,14 @@ public class UDPAvatarBroadcaster : MonoBehaviour
{
broadcastAddress = cfg.ipAddress;
broadcastPort = cfg.port;
+
+ // Subscribe to peer discovery if enabled
+ if (cfg.useAutoDiscovery && Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null)
+ {
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered;
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost;
+ Debug.Log("Avatar broadcaster subscribed to peer discovery");
+ }
}
else
{
@@ -444,6 +452,13 @@ public class UDPAvatarBroadcaster : MonoBehaviour
void OnDestroy()
{
+ // Unsubscribe from peer discovery
+ if (Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null)
+ {
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered;
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost;
+ }
+
if (udpClient != null)
{
udpClient.Close();
@@ -451,6 +466,24 @@ public class UDPAvatarBroadcaster : MonoBehaviour
}
}
+ private void HandlePeerDiscovered(string peerIP)
+ {
+ broadcastAddress = peerIP;
+ broadcastEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), broadcastPort);
+ Debug.Log($"👤 Avatar broadcaster now targeting peer at {peerIP}:{broadcastPort}");
+ }
+
+ private void HandlePeerLost()
+ {
+ var cfg = NetworkConfig.Instance;
+ if (cfg != null)
+ {
+ broadcastAddress = cfg.fallbackBroadcastIP;
+ broadcastEndPoint = new IPEndPoint(IPAddress.Parse(broadcastAddress), broadcastPort);
+ Debug.LogWarning($"👤 Avatar broadcaster falling back to broadcast: {broadcastAddress}");
+ }
+ }
+
void OnApplicationPause(bool pauseStatus)
{
enableBroadcast = !pauseStatus;
diff --git a/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs b/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs
index eef2dad..24de793 100644
--- a/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs
+++ b/Unity-Master/Assets/Scripts/UDPAvatarBroadcasterAgent.cs
@@ -95,6 +95,14 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour
{
broadcastAddress = cfg.ipAddress;
broadcastPort = cfg.port;
+
+ // Subscribe to peer discovery if enabled
+ if (cfg.useAutoDiscovery && Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null)
+ {
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered += HandlePeerDiscovered;
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost += HandlePeerLost;
+ Debug.Log("Avatar broadcaster agent subscribed to peer discovery");
+ }
}
else
{
@@ -447,6 +455,13 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour
void OnDestroy()
{
+ // Unsubscribe from peer discovery
+ if (Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance != null)
+ {
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerDiscovered -= HandlePeerDiscovered;
+ Convai.Scripts.Runtime.Multiplayer.UDPPeerDiscovery.Instance.OnPeerLost -= HandlePeerLost;
+ }
+
if (udpClient != null)
{
udpClient.Close();
@@ -454,6 +469,24 @@ public class UDPAvatarBroadcasterAgent : MonoBehaviour
}
}
+ private void HandlePeerDiscovered(string peerIP)
+ {
+ broadcastAddress = peerIP;
+ broadcastEndPoint = new IPEndPoint(IPAddress.Parse(peerIP), broadcastPort);
+ Debug.Log($"👤 Avatar broadcaster agent now targeting peer at {peerIP}:{broadcastPort}");
+ }
+
+ private void HandlePeerLost()
+ {
+ var cfg = NetworkConfig.Instance;
+ if (cfg != null)
+ {
+ broadcastAddress = cfg.fallbackBroadcastIP;
+ broadcastEndPoint = new IPEndPoint(IPAddress.Parse(broadcastAddress), broadcastPort);
+ Debug.LogWarning($"👤 Avatar broadcaster agent falling back to broadcast: {broadcastAddress}");
+ }
+ }
+
void OnApplicationPause(bool pauseStatus)
{
enableBroadcast = !pauseStatus;