I am trying to add LAN server discovery to my Unity game. I am using UDP to broadcast requests. If anyone on the network is a server, they will respond with some server data. I have this part figured out, but I need to use some Unity functions(which include other custom monobehaviour scripts) to generate the packet data.
Unity will not allow access to its APIs from any thread other than the main one.
I am using the UdpClient.BeginReceive, UdpClient.EndReceive, AsyncCallback flow. So the receive AsyncCallback function(AsyncRequestReceiveData
in the example script below) is run in another thread which doesn't allow me to use any Unity functions.
I used this answer by flamy as a basis for my script.
I have tried making a delegate and event to fire in AsyncRequestReceiveData
when the script hears a request, but it seems to fire in the same separate thread.
An event fired in the main thread by the separate thread would work great, but I am not sure on how this could be done.
Although the script inherits from MonoBehaviour
(base object in Unity 3D) it should run just fine standalone.
The output of the script should look like:
Sendering Request: requestingServers
Request Received: requestingServers
Here is the barebones script:
using UnityEngine;
using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
public class TestUDP : MonoBehaviour {
public delegate void RequestReceivedEventHandler(string message);
public event RequestReceivedEventHandler OnRequestReceived;
// Use this to trigger the event
protected virtual void ThisRequestReceived(string message)
{
RequestReceivedEventHandler handler = OnRequestReceived;
if(handler != null)
{
handler(message);
}
}
public int requestPort = 55795;
UdpClient udpRequestSender;
UdpClient udpRequestReceiver;
// Use this for initialization
void Start () {
// Set up the sender for requests
this.udpRequestSender = new UdpClient();
IPEndPoint requestGroupEP = new IPEndPoint(IPAddress.Broadcast, this.requestPort);
this.udpRequestSender.Connect(requestGroupEP);
// Set up the receiver for the requests
// Listen for anyone looking for us
this.udpRequestReceiver = new UdpClient(this.requestPort);
this.udpRequestReceiver.BeginReceive(new AsyncCallback(AsyncRequestReceiveData), null);
// Listen for the request
this.OnRequestReceived += (message) => {
Debug.Log("Request Received: " + message);
// Do some more stuff when we get a request
// Use `Network.maxConnections` for example
};
// Send out the request
this.SendRequest();
}
void Update () {
}
void SendRequest()
{
try
{
string message = "requestingServers";
if (message != "")
{
Debug.Log("Sendering Request: " + message);
this.udpRequestSender.Send(System.Text.Encoding.ASCII.GetBytes(message), message.Length);
}
}
catch (ObjectDisposedException e)
{
Debug.LogWarning("Trying to send data on already disposed UdpCleint: " + e);
return;
}
}
void AsyncRequestReceiveData(IAsyncResult result)
{
IPEndPoint receiveIPGroup = new IPEndPoint(IPAddress.Any, this.requestPort);
byte[] received;
if (this.udpRequestReceiver != null) {
received = this.udpRequestReceiver.EndReceive(result, ref receiveIPGroup);
} else {
return;
}
this.udpRequestReceiver.BeginReceive (new AsyncCallback(AsyncRequestReceiveData), null);
string receivedString = System.Text.Encoding.ASCII.GetString(received);
// Fire the event
this.ThisRequestReceived(receivedString);
}
}
I have seen this question (Cleanest Way to Invoke Cross-Thread Events) but I can't seem to figure out how to it works and how to incorporate in.