Skip to content

Instantly share code, notes, and snippets.

@robert-wallis
Last active December 29, 2017 04:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robert-wallis/515efd60a5d17b8c02304a91d2ab04b4 to your computer and use it in GitHub Desktop.
Save robert-wallis/515efd60a5d17b8c02304a91d2ab04b4 to your computer and use it in GitHub Desktop.
"Lower-Level" UNet example refactored into UI and Networking classes. https://docs.unity3d.com/Manual/UnityMultiplayerIntegratingLowLevel.html
// Copyright (C) 2017 Robert A. Wallis, All Rights Reserved
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking.Match;
using UnityEngine.UI;
namespace Network {
public class NetUi : MonoBehaviour {
[SerializeField] public Button ButtonCreateRoom;
[SerializeField] public Button ButtonJoinFirstRoom;
[SerializeField] public Button ButtonListRooms;
[SerializeField] public InputField InputFieldMessage;
[SerializeField] public Button ButtonSendMessage;
[SerializeField] public Button ButtonDisconnect;
[SerializeField] public GameObject ScrollViewContent;
[SerializeField] public GameObject PrefabMatchButton;
[SerializeField] public GameObject PrefabNetworkText;
string _matchName;
UnityRelayNetwork _unityRelayNetwork;
void Awake() {
var networkMatch = gameObject.AddComponent<NetworkMatch>();
_unityRelayNetwork = new UnityRelayNetwork(networkMatch, Debug.Log, Debug.LogError);
ButtonCreateRoom.onClick.AddListener(OnCreateRoomClicked);
ButtonJoinFirstRoom.onClick.AddListener(OnJoinFirstFoundMatchClicked);
ButtonListRooms.onClick.AddListener(OnListRoomsClicked);
InputFieldMessage.onEndEdit.AddListener(OnMessageEndEdit);
ButtonSendMessage.onClick.AddListener(OnMessageSendClicked);
ButtonDisconnect.onClick.AddListener(OnDisconnectClicked);
_unityRelayNetwork.OnMatchHosted += EventMatchHosted;
_unityRelayNetwork.OnConnectToMatch += EventConnectToMatch;
_unityRelayNetwork.OnMatchesListed += EventListMatches;
_unityRelayNetwork.OnMessage += EventMessage;
_unityRelayNetwork.OnDisconnected += EventDisconnected;
}
void Start() {
// While testing with multiple standalone players on one machine this will need to be enabled
Application.runInBackground = true;
_matchName = $"v{Application.version} Match";
CleanList();
CreateLogEntry("Cloud Project Id: " + Application.cloudProjectId);
}
void CleanList() {
var childCount = ScrollViewContent.transform.childCount;
var objs = new GameObject[childCount];
for (var i = 0; i < childCount; i++) {
objs[i] = ScrollViewContent.transform.GetChild(i).gameObject;
}
foreach (var o in objs) {
Destroy(o);
}
}
void OnApplicationQuit() {
_unityRelayNetwork.ShutDown();
}
void Update() {
_unityRelayNetwork.ProcessNetworkEvents();
}
void OnCreateRoomClicked() {
_unityRelayNetwork.CreateMatch(_matchName);
}
void OnJoinFirstFoundMatchClicked() {
_unityRelayNetwork.JoinAnyMatch();
}
void OnListRoomsClicked() {
_unityRelayNetwork.ListMatches();
}
void OnDisconnectClicked() {
_unityRelayNetwork.Disconnect();
}
void OnMessageSendClicked() {
_unityRelayNetwork.Send(InputFieldMessage.text);
}
void OnMessageEndEdit(string message) {
_unityRelayNetwork.Send(message);
}
void EventMatchHosted(MatchInfo matchInfo) {
CleanList();
CreateLogEntry($"Created server: {matchInfo}");
CreateLogEntry($"Relay Server: {matchInfo.address} :{matchInfo.port}");
CreateLogEntry($"NetworkID: {matchInfo.networkId} NodeID: {matchInfo.nodeId}");
}
void EventConnectToMatch(MatchInfo matchInfo) {
CleanList();
CreateLogEntry($"Joined game: {matchInfo}");
CreateLogEntry($"Relay Server: {matchInfo.address} :{matchInfo.port}");
CreateLogEntry($"NetworkID: {matchInfo.networkId} NodeID: {matchInfo.nodeId}");
}
void EventListMatches(List<MatchInfoSnapshot> matches) {
if (matches.Count == 0)
return;
CleanList();
foreach (var match in matches) {
CreateMatchButton(match);
}
}
void EventMessage(string message) {
CreateLogEntry(message);
}
void EventDisconnected() {
CleanList();
}
void CreateMatchButton(MatchInfoSnapshot match) {
var buttonGo = Instantiate(PrefabMatchButton, ScrollViewContent.transform);
var text = buttonGo.GetComponentInChildren<Text>();
text.text = match.name;
var button = buttonGo.GetComponentInChildren<Button>();
var netId = match.networkId;
button.onClick.AddListener(() => {
_unityRelayNetwork.JoinMatchById(netId);
});
}
void CreateLogEntry(string message) {
var logGo = Instantiate(PrefabNetworkText, ScrollViewContent.transform);
var text = logGo.GetComponent<Text>();
text.text = message;
}
}
}
// Copyright (C) 2017 Robert A. Wallis, All Rights Reserved
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.Networking.Match;
using UnityEngine.Networking.Types;
namespace Network {
public class UnityRelayNetwork {
const int MAX_MESSAGE_SIZE = 65535;
const int MAX_DEFAULT_CONNECTIONS = 4;
const int SERVER_PORT = 25000;
readonly List<int> _connectionIds = new List<int>();
readonly Action<string> _errorLogger;
readonly Action<string> _logger;
readonly NetworkMatch _networkMatch;
readonly NetworkWriter _networkWriter;
readonly byte[] _receiveBuffer;
int _hostId = -1;
MatchInfo _matchInfo;
List<MatchInfoSnapshot> _matchList = new List<MatchInfoSnapshot>();
public event Action<List<MatchInfoSnapshot>> OnMatchesListed;
public event Action<MatchInfo> OnMatchHosted;
public event Action<MatchInfo> OnConnectToMatch;
public event Action OnDisconnected;
public event Action<string> OnMessage;
public UnityRelayNetwork(NetworkMatch networkMatch, Action<string> logger, Action<string> errorLogger) {
UnityBugFixBaseUriIos(networkMatch);
_networkMatch = networkMatch;
_logger = logger;
_errorLogger = errorLogger;
_receiveBuffer = new byte[MAX_MESSAGE_SIZE];
_networkWriter = new NetworkWriter();
}
public void CreateMatch(string roomName) {
_networkMatch.CreateMatch(roomName, 4, true, "", "", "", 0, 0, OnMatchCreate);
}
public void ListMatches() {
_networkMatch.ListMatches(0, 20, "", true, 0, 0, OnMatchList);
}
public void JoinAnyMatch() {
if (_matchList.Count > 0) {
JoinMatchById(_matchList[0].networkId);
return;
}
_networkMatch.ListMatches(0, 1, "", true, 0, 0, (success, info, matches) => {
if (success && matches.Count > 0)
JoinMatchById(matches[0].networkId);
});
}
public void JoinMatchById(NetworkID networkId) {
_networkMatch.JoinMatch(networkId, "", "", "", 0, 0, OnMatchJoined);
}
public void Send(string message) {
_networkWriter.SeekZero();
_networkWriter.Write(message);
foreach (var connectionId in _connectionIds) {
byte error;
NetworkTransport.Send(_hostId,
connectionId, 0, _networkWriter.AsArray(), _networkWriter.Position, out error);
if ((NetworkError) error != NetworkError.Ok)
_errorLogger("Failed to send message: " + (NetworkError) error);
}
}
public void ProcessNetworkEvents() {
if (_hostId == -1)
return;
ProcessRelayEvents();
ProcessHostEvents();
}
void ProcessHostEvents() {
NetworkEventType networkEvent;
do {
int connectionId;
int channelId;
int receivedSize;
byte error;
networkEvent = NetworkTransport.ReceiveFromHost(
_hostId,
out connectionId, out channelId,
_receiveBuffer, _receiveBuffer.Length,
out receivedSize, out error);
if ((NetworkError) error != NetworkError.Ok)
_errorLogger("Error while receiveing network message: " + (NetworkError) error);
switch (networkEvent) {
case NetworkEventType.ConnectEvent: {
EventConnected(connectionId, channelId);
break;
}
case NetworkEventType.DataEvent: {
EventData(connectionId, channelId, receivedSize);
break;
}
case NetworkEventType.DisconnectEvent: {
EventDisconnected(connectionId);
break;
}
case NetworkEventType.Nothing:
break;
case NetworkEventType.BroadcastEvent:
break;
}
} while (networkEvent != NetworkEventType.Nothing);
}
void ProcessRelayEvents() {
byte error;
var networkEvent = NetworkTransport.ReceiveRelayEventFromHost(_hostId, out error);
switch (networkEvent) {
case NetworkEventType.ConnectEvent:
_logger("Relay Event: Connected");
break;
case NetworkEventType.DisconnectEvent:
_logger("Relay Event: Disconnected");
break;
case NetworkEventType.DataEvent:
break;
case NetworkEventType.Nothing:
break;
case NetworkEventType.BroadcastEvent:
break;
}
}
public void Disconnect() {
if (_matchInfo == null)
return;
_networkMatch.DropConnection(_matchInfo.networkId, _matchInfo.nodeId, 0, OnConnectionDropped);
}
public void ShutDown() {
NetworkTransport.Shutdown();
_hostId = -1;
_connectionIds.Clear();
_matchInfo = null;
}
void StartServer(string relayIp, int relayPort, NetworkID networkId, NodeID nodeId) {
_logger("NetworkTransport.Init");
NetworkTransport.Init();
var topology = SetupTopology();
_hostId = NetworkTransport.AddHost(topology, SERVER_PORT);
byte error;
_logger("StartServer ConnectAsNetworkHost");
var sourceId = Utility.GetSourceID();
NetworkTransport.ConnectAsNetworkHost(_hostId, relayIp, relayPort, networkId, sourceId, nodeId, out error);
}
void StartClient(string relayIp, int relayPort, NetworkID networkId, NodeID nodeId) {
_logger("NetworkTransport.Init");
NetworkTransport.Init();
var topology = SetupTopology();
_hostId = NetworkTransport.AddHost(topology);
byte error;
_logger("StartClient ConnectToNetworkPeer");
var sourceId = Utility.GetSourceID();
NetworkTransport.ConnectToNetworkPeer(_hostId, relayIp, relayPort, 0, 0, networkId, sourceId, nodeId,
out error);
}
HostTopology SetupTopology() {
var config = new ConnectionConfig();
config.AddChannel(QosType.Reliable);
config.AddChannel(QosType.Unreliable);
return new HostTopology(config, MAX_DEFAULT_CONNECTIONS);
}
void EventDisconnected(int connectionId) {
_logger("Event Disconnected on #" + connectionId);
}
void EventData(int connectionId, int channelId, int receivedSize) {
_logger("Event Data on #" + connectionId + " channel:" + channelId + " size:" + receivedSize);
var networkReader = new NetworkReader(_receiveBuffer);
var message = networkReader.ReadString();
OnMessage?.Invoke(message);
}
void EventConnected(int connectionId, int channelId) {
_logger("Event Connect #:" + connectionId + " channel:" + channelId);
_connectionIds.Add(connectionId);
}
void OnConnectionDropped(bool success, string extendedInfo) {
_logger("Connection Dropped");
ShutDown();
OnDisconnected?.Invoke();
}
void OnMatchCreate(bool success, string extendedInfo, MatchInfo matchInfo) {
if (!success) {
_errorLogger("Match Failed to Create: " + extendedInfo);
return;
}
_logger("Match Created");
Utility.SetAccessTokenForNetwork(matchInfo.networkId, matchInfo.accessToken);
_matchInfo = matchInfo;
StartServer(matchInfo.address, matchInfo.port, matchInfo.networkId, matchInfo.nodeId);
OnMatchHosted?.Invoke(matchInfo);
}
void OnMatchList(bool success, string extendedInfo, List<MatchInfoSnapshot> matches) {
if (!success) {
_errorLogger("List match failed: " + extendedInfo);
return;
}
if (matches == null)
return;
_matchList = matches;
OnMatchesListed?.Invoke(matches);
}
void OnMatchJoined(bool success, string extendedInfo, MatchInfo matchInfo) {
if (!success) {
_errorLogger("Join match failed: " + extendedInfo);
return;
}
_logger("Join match succeeded");
Utility.SetAccessTokenForNetwork(matchInfo.networkId, matchInfo.accessToken);
_matchInfo = matchInfo;
_logger("Connecting to Address:" + matchInfo.address + " Port:" + matchInfo.port + " NetworKID: " +
matchInfo.networkId + " NodeID: " + matchInfo.nodeId);
StartClient(matchInfo.address, matchInfo.port, matchInfo.networkId, matchInfo.nodeId);
OnConnectToMatch?.Invoke(matchInfo);
}
static void UnityBugFixBaseUriIos(NetworkMatch networkMatch) {
if (networkMatch.baseUri != null)
return;
var uri = new Uri("https://mm.unet.unity3d.com/");
Debug.Log($"Unity Bug: baseUri was null, setting to {uri} still a bug in " + Application.unityVersion);
networkMatch.baseUri = uri;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment