diff --git a/Runtime/NetworkComponent.cs b/Runtime/NetworkComponent.cs new file mode 100644 index 0000000..0dead22 --- /dev/null +++ b/Runtime/NetworkComponent.cs @@ -0,0 +1,33 @@ +using UnityEngine; + +namespace VelNetUnity +{ + public abstract class NetworkComponent : MonoBehaviour + { + public NetworkObject networkObject; + protected bool IsMine => networkObject != null && networkObject.owner != null && networkObject.owner.isLocal; + protected NetworkPlayer Owner => networkObject != null ? networkObject.owner : null; + + /// + /// call this in child classes to send a message to other people + /// + protected void SendBytes(byte[] message, bool reliable = true) + { + networkObject.SendBytes(this, message, reliable); + } + + /// + /// call this in child classes to send a message to other people + /// + protected void SendBytesToGroup(string group, byte[] message, bool reliable = true) + { + networkObject.SendBytesToGroup(this, group, message, reliable); + } + + // + /// + /// This is called by when messages are received for this component + /// + public abstract void ReceiveBytes(byte[] message); + } +} \ No newline at end of file diff --git a/Runtime/NetworkComponent.cs.meta b/Runtime/NetworkComponent.cs.meta new file mode 100644 index 0000000..ba86217 --- /dev/null +++ b/Runtime/NetworkComponent.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 52c3bc18f2284ca28a9d2095e77330d2 +timeCreated: 1641527089 \ No newline at end of file diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index ce0520a..e4139db 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; +using System.Linq; using UnityEngine; namespace VelNetUnity @@ -5,20 +7,57 @@ namespace VelNetUnity /// /// This is a base class for all objects that a player can instantiated/owned /// - public abstract class NetworkObject : MonoBehaviour + public class NetworkObject : MonoBehaviour { - [Header("NetworkObject properties")] + [Header("NetworkObject properties")] public NetworkPlayer owner; - public bool IsMine => owner !=null && owner.isLocal; + public bool ownershipLocked; + public bool IsMine => owner != null && owner.isLocal; + /// /// This is forged from the combination of the creator's id (-1 in the case of a scene object) and an object id, so it's always unique for a room /// public string networkId; + /// /// This may be empty if it's not a prefab (scene object) /// public string prefabName; + public bool isSceneObject; - public abstract void HandleMessage(string identifier, byte[] message); + + public List syncedComponents; + + public void SendBytes(NetworkComponent component, byte[] message, bool reliable = true) + { + if (!IsMine) + { + Debug.LogError("Can't send message if owner is null or not local", this); + return; + } + + // send the message and an identifier for which component it belongs to + int index = syncedComponents.IndexOf(component); + owner.SendMessage(this, index.ToString(), message, reliable); + } + + public void SendBytesToGroup(NetworkComponent component, string group, byte[] message, bool reliable = true) + { + if (owner == null || !owner.isLocal) + { + Debug.LogError("Can't send message if owner is null or not local", this); + return; + } + + // send the message and an identifier for which component it belongs to + int index = syncedComponents.IndexOf(component); + owner.SendGroupMessage(this, group, index.ToString(), message, reliable); + } + + public void ReceiveBytes(string identifier, byte[] message) + { + // send the message to the right component + syncedComponents[int.Parse(identifier)].ReceiveBytes(message); + } } } \ No newline at end of file diff --git a/Runtime/NetworkObject.cs.meta b/Runtime/NetworkObject.cs.meta index 6a1a6e9..29df2e6 100644 --- a/Runtime/NetworkObject.cs.meta +++ b/Runtime/NetworkObject.cs.meta @@ -1,11 +1,3 @@ -fileFormatVersion: 2 -guid: dd77ffdf919cc444f863d7bf0cda29ea -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: +fileFormatVersion: 2 +guid: 5515094c5c544b6b8ed7fd51a86548d4 +timeCreated: 1641527700 \ No newline at end of file diff --git a/Runtime/NetworkPlayer.cs b/Runtime/NetworkPlayer.cs index b59d089..448c6b2 100644 --- a/Runtime/NetworkPlayer.cs +++ b/Runtime/NetworkPlayer.cs @@ -1,18 +1,13 @@ using System.Collections.Generic; -using UnityEngine; using System; - namespace VelNetUnity { /// - /// One of these will be instantiated for each player that joins the "room". + /// Represents a network player /// - [RequireComponent(typeof(NetworkObject))] - [AddComponentMenu("VelNetUnity/VelNet Network Player")] - public class NetworkPlayer : MonoBehaviour + public class NetworkPlayer { - private NetworkObject myObject; public int userid; public string username; @@ -20,7 +15,7 @@ namespace VelNetUnity public bool isLocal; - private NetworkManager manager; + private VelNetManager manager; /// /// For instantiation @@ -30,11 +25,10 @@ namespace VelNetUnity private bool isMaster; - private void Start() + + public NetworkPlayer() { - myObject = GetComponent(); - myObject.owner = this; - manager = NetworkManager.instance; + manager = VelNetManager.instance; manager.OnPlayerJoined += HandlePlayerJoined; } @@ -47,7 +41,7 @@ namespace VelNetUnity { if (kvp.Value.owner == this && kvp.Value.prefabName != "") { - manager.SendTo(NetworkManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName); + manager.SendTo(VelNetManager.MessageType.OTHERS, "7," + kvp.Value.networkId + "," + kvp.Value.prefabName); } } @@ -60,27 +54,20 @@ namespace VelNetUnity } - public void HandleMessage(NetworkManager.Message m) + public void HandleMessage(VelNetManager.Message m) { //these are generally things that come from the "owner" and should be enacted locally, where appropriate //we need to parse the message //types of messages string[] messages = m.text.Split(';'); //messages are split by ; - for (int i = 0; i < messages.Length; i++) + foreach (string s in messages) { //individual message parameters separated by comma - string[] sections = messages[i].Split(','); + string[] sections = s.Split(','); switch (sections[0]) { - case "1": //update my object's data - { - string identifier = sections[1]; - byte[] message = Convert.FromBase64String(sections[2]); - myObject.HandleMessage(identifier, message); - break; - } case "5": //sync update for an object I may own { string objectKey = sections[1]; @@ -91,7 +78,7 @@ namespace VelNetUnity { if (manager.objects[objectKey].owner == this) { - manager.objects[objectKey].HandleMessage(identifier, messageBytes); + manager.objects[objectKey].ReceiveBytes(identifier, messageBytes); } } @@ -108,7 +95,7 @@ namespace VelNetUnity break; } - case "7": //I'm trying to instantiate an object + case "7": // I'm trying to instantiate an object { string networkId = sections[1]; string prefabName = sections[2]; @@ -117,19 +104,11 @@ namespace VelNetUnity break; //we already have this one, ignore } - NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName); - if (temp != null) - { - NetworkObject instance = Instantiate(temp); - instance.networkId = networkId; - instance.prefabName = prefabName; - instance.owner = this; - manager.objects.Add(instance.networkId, instance); - } + VelNetManager.InstantiateNetworkObject(networkId, prefabName, this); break; } - case "8": //I'm trying to destroy a gameobject I own + case "8": // I'm trying to destroy a gameobject I own { string networkId = sections[1]; @@ -158,71 +137,50 @@ namespace VelNetUnity public void SendGroupMessage(NetworkObject obj, string group, string identifier, byte[] data, bool reliable = true) { - if (obj == myObject) - { - manager.SendToGroup(group, "1," + identifier + "," + Convert.ToBase64String(data), reliable); - } - else - { - manager.SendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); - } + manager.SendToGroup(group, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); } public void SendMessage(NetworkObject obj, string identifier, byte[] data, bool reliable = true) { - if (obj == myObject) - { - manager.SendTo(NetworkManager.MessageType.OTHERS, "1," + identifier + "," + Convert.ToBase64String(data), reliable); - } - else - { - manager.SendTo(NetworkManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); - } - } - - public NetworkObject NetworkInstantiate(string prefabName) - { - if (!isLocal) - { - return null; //must be the local player to call instantiate - } - - string networkId = userid + "-" + lastObjectId++; - - - NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName); - if (temp != null) - { - NetworkObject instance = Instantiate(temp); - instance.networkId = networkId; - instance.prefabName = prefabName; - instance.owner = this; - manager.objects.Add(instance.networkId, instance); - - manager.SendTo(NetworkManager.MessageType.OTHERS, "7," + networkId + "," + prefabName); //only sent to others, as I already instantiated this. Nice that it happens immediately. - return instance; - } - - return null; + manager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); } + /// + /// TODO could move this to a static method in VelNetManager + /// public void NetworkDestroy(string networkId) { - if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; //must be the local owner of the object to destroy it - manager.SendTo(NetworkManager.MessageType.ALL_ORDERED, "8," + networkId); //send to all, which will make me delete as well + // must be the local owner of the object to destroy it + if (!manager.objects.ContainsKey(networkId) || manager.objects[networkId].owner != this || !isLocal) return; + + // send to all, which will make me delete as well + manager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "8," + networkId); } - public void TakeOwnership(string networkId) + /// + /// TODO could move this to a static method in VelNetManager + /// + /// True if successful, False if failed to transfer ownership + public bool TakeOwnership(string networkId) { - if (!manager.objects.ContainsKey(networkId) || !isLocal) return; //must exist and be the the local player + // must exist and be the the local player + if (!manager.objects.ContainsKey(networkId) || !isLocal) return false; - manager.objects[networkId].owner = this; //immediately successful - manager.SendTo(NetworkManager.MessageType.ALL_ORDERED, "6," + networkId); //must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. + // if the ownership is locked, fail + if (manager.objects[networkId].ownershipLocked) return false; + + // immediately successful + manager.objects[networkId].owner = this; + + // must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome. + manager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId); + + return true; } public void SendSceneUpdate() { - manager.SendTo(NetworkManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects)); + manager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects)); } } } \ No newline at end of file diff --git a/Runtime/NetworkSerializedObject.cs b/Runtime/NetworkSerializedObject.cs index 18ac263..162ced2 100644 --- a/Runtime/NetworkSerializedObject.cs +++ b/Runtime/NetworkSerializedObject.cs @@ -1,39 +1,40 @@ -using System; -using System.Timers; +using System.Collections; using UnityEngine; using UnityEngine.Serialization; namespace VelNetUnity { - public abstract class NetworkSerializedObject : NetworkObject + public abstract class NetworkSerializedObject : NetworkComponent { - [FormerlySerializedAs("updateRateHz")] [Tooltip("Send rate of this object")] public float serializationRateHz = 30; + [FormerlySerializedAs("updateRateHz")] [Tooltip("Send rate of this object")] + public float serializationRateHz = 30; private void Start() { - Timer timer = new Timer(); - timer.Interval = serializationRateHz; - timer.Elapsed += SendMessageUpdate; + StartCoroutine(SendMessageUpdate()); } - private void SendMessageUpdate(object sender, ElapsedEventArgs e) + private IEnumerator SendMessageUpdate() { - if (owner != null && owner.isLocal) + while (true) { - owner.SendMessage(this, "s", SendState()); + if (IsMine) + { + SendBytes(SendState()); + } + + yield return new WaitForSeconds(1 / serializationRateHz); } + // ReSharper disable once IteratorNeverReturns + } + + public override void ReceiveBytes(byte[] message) + { + ReceiveState(message); } protected abstract byte[] SendState(); - public override void HandleMessage(string identifier, byte[] message) - { - if (identifier == "s") - { - ReceiveState(message); - } - } - protected abstract void ReceiveState(byte[] message); } } \ No newline at end of file diff --git a/Runtime/Util/SyncTransform.cs b/Runtime/Util/SyncTransform.cs index fe8cbc4..5cb2b50 100644 --- a/Runtime/Util/SyncTransform.cs +++ b/Runtime/Util/SyncTransform.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.IO; using UnityEngine; @@ -13,6 +12,7 @@ namespace VelNetUnity { public Vector3 targetPosition; public Quaternion targetRotation; + public float smoothness = .1f; protected override byte[] SendState() { @@ -36,9 +36,9 @@ namespace VelNetUnity private void Update() { - if (owner == null || owner.isLocal) return; - transform.position = Vector3.Lerp(transform.position, targetPosition, .1f); - transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f); + if (IsMine) return; + transform.position = Vector3.Lerp(transform.position, targetPosition, 1 / smoothness / serializationRateHz); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 1 / smoothness / serializationRateHz); } } } \ No newline at end of file diff --git a/Runtime/NetworkManager.cs b/Runtime/VelNetManager.cs similarity index 78% rename from Runtime/NetworkManager.cs rename to Runtime/VelNetManager.cs index 65bb271..140d231 100644 --- a/Runtime/NetworkManager.cs +++ b/Runtime/VelNetManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net.Sockets; using System.Text; using System.Threading; @@ -8,8 +9,8 @@ using System.Net; namespace VelNetUnity { - [AddComponentMenu("VelNetUnity/VelNet Network Manager")] - public class NetworkManager : MonoBehaviour + [AddComponentMenu("VelNetUnity/VelNet Manager")] + public class VelNetManager : MonoBehaviour { public enum MessageType { @@ -22,9 +23,7 @@ namespace VelNetUnity public string host; public int port; - public static NetworkManager instance; - - #region private members + public static VelNetManager instance; private TcpClient socketConnection; private Socket udpSocket; @@ -36,8 +35,7 @@ namespace VelNetUnity public string room; private int messagesReceived = 0; - public GameObject playerPrefab; - public Dictionary players = new Dictionary(); + public readonly Dictionary players = new Dictionary(); public Action OnJoinedRoom; public Action OnPlayerJoined; @@ -48,8 +46,9 @@ namespace VelNetUnity public List deletedSceneObjects = new List(); public readonly Dictionary objects = new Dictionary(); //maintains a list of all known objects on the server (ones that have ids) private NetworkPlayer masterPlayer; + public static NetworkPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p=>p.Value).FirstOrDefault(); + - #endregion // Use this for initialization public class Message @@ -67,6 +66,7 @@ namespace VelNetUnity { Debug.LogError("Multiple NetworkManagers detected! Bad!", this); } + instance = this; } @@ -106,85 +106,92 @@ namespace VelNetUnity if (m.type == 2) { - //if this message is for me, that means I joined a new room... + // if this message is for me, that means I joined a new room... if (userid == m.sender) { - foreach (KeyValuePair kvp in players) - { - Destroy(kvp.Value.gameObject); - } + // TODO delete all old objects when joining a new room players.Clear(); //we clear the list, but will recreate as we get messages from people in our room if (m.text != "") { - NetworkPlayer player = Instantiate(playerPrefab).GetComponent(); + NetworkPlayer player = new NetworkPlayer + { + isLocal = true, + userid = m.sender, + room = m.text + }; - player.isLocal = true; - player.userid = m.sender; players.Add(userid, player); - player.room = m.text; OnJoinedRoom?.Invoke(player); } } - else //not for me, a player is joining or leaving + else // not for me, a player is joining or leaving { NetworkPlayer me = players[userid]; if (me.room != m.text) { - //we got a left message, kill it - //change ownership of all objects to master - + // we got a left message, kill it + // change ownership of all objects to master foreach (KeyValuePair kvp in objects) { - if (kvp.Value.owner == players[m.sender]) //the owner is the player that left + if (kvp.Value.owner == players[m.sender]) // the owner is the player that left { - if (me.isLocal && me == masterPlayer) //I'm the local master player, so can take ownership immediately + // if this object has locked ownership, delete it + if (kvp.Value.ownershipLocked) + { + // TODO this may check for ownership in the future. We don't need ownership here + DeleteNetworkObject(kvp.Value.networkId); + } + // I'm the local master player, so can take ownership immediately + else if (me.isLocal && me == masterPlayer) { me.TakeOwnership(kvp.Key); } - else if (players[m.sender] == masterPlayer) //the master player left, so everyone should set the owner null (we should get a new master shortly) + // the master player left, so everyone should set the owner null (we should get a new master shortly) + else if (players[m.sender] == masterPlayer) { kvp.Value.owner = null; } } } - Destroy(players[m.sender].gameObject); players.Remove(m.sender); } else { - //we got a join mesage, create it - NetworkPlayer player = Instantiate(playerPrefab).GetComponent(); - player.isLocal = false; - player.room = m.text; - player.userid = m.sender; + // we got a join message, create it + NetworkPlayer player = new NetworkPlayer + { + isLocal = false, + room = m.text, + userid = m.sender + }; players.Add(m.sender, player); OnPlayerJoined?.Invoke(player); } } } - if (m.type == 3) //generic message + if (m.type == 3) // generic message { players[m.sender]?.HandleMessage(m); } - if (m.type == 4) //change master player (this should only happen when the first player joins or if the master player leaves) + if (m.type == 4) // change master player (this should only happen when the first player joins or if the master player leaves) { if (masterPlayer == null) { masterPlayer = players[m.sender]; - //no master player yet, add the scene objects + // no master player yet, add the scene objects for (int i = 0; i < sceneObjects.Length; i++) { sceneObjects[i].networkId = -1 + "-" + i; sceneObjects[i].owner = masterPlayer; - sceneObjects[i].isSceneObject = true; //needed for special handling when deleted + sceneObjects[i].isSceneObject = true; // needed for special handling when deleted objects.Add(sceneObjects[i].networkId, sceneObjects[i]); } } @@ -195,7 +202,7 @@ namespace VelNetUnity masterPlayer.SetAsMasterPlayer(); - //master player should take over any objects that do not have an owner + // master player should take over any objects that do not have an owner foreach (KeyValuePair kvp in objects) { @@ -515,6 +522,34 @@ namespace VelNetUnity { SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); } + + + public static void InstantiateNetworkObject(string prefabName) + { + NetworkPlayer localPlayer = LocalPlayer; + NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); + if (prefab == null) + { + Debug.LogError("Couldn't find a prefab with that name: " + prefabName); + return; + } + NetworkObject newObject = Instantiate(prefab); + newObject.networkId = localPlayer.userid + "-" + localPlayer.lastObjectId++;; + newObject.prefabName = prefabName; + newObject.owner = localPlayer; + instance.objects.Add(newObject.networkId, newObject); + } + + public static void InstantiateNetworkObject(string networkId, string prefabName, NetworkPlayer owner) + { + NetworkObject prefab = instance.prefabs.Find(p => p.name == prefabName); + if (prefab == null) return; + NetworkObject newObject = Instantiate(prefab); + newObject.networkId = networkId; + newObject.prefabName = prefabName; + newObject.owner = owner; + instance.objects.Add(newObject.networkId, newObject); + } public void DeleteNetworkObject(string networkId) { diff --git a/Runtime/NetworkManager.cs.meta b/Runtime/VelNetManager.cs.meta similarity index 100% rename from Runtime/NetworkManager.cs.meta rename to Runtime/VelNetManager.cs.meta diff --git a/Samples~/DissonanceIntegration/VelCommsNetwork.cs b/Samples~/DissonanceIntegration/VelCommsNetwork.cs index b1defa8..5633ee4 100644 --- a/Samples~/DissonanceIntegration/VelCommsNetwork.cs +++ b/Samples~/DissonanceIntegration/VelCommsNetwork.cs @@ -7,6 +7,10 @@ using UnityEngine.Serialization; namespace VelNetUnity { + /// + /// Added to the same object as DissonanceComms component. Only one in the scene. + /// + [RequireComponent(typeof(DissonanceComms))] [AddComponentMenu("VelNetUnity/Dissonance/VelNet Comms Network")] public class VelCommsNetwork : MonoBehaviour, ICommsNetwork { @@ -26,8 +30,8 @@ namespace VelNetUnity private ConnectionStatus _status = ConnectionStatus.Disconnected; private CodecSettings initSettings; public string dissonanceId; - [FormerlySerializedAs("comms")] public DissonanceComms dissonanceComms; - private NetworkManager manager; + [HideInInspector] public DissonanceComms dissonanceComms; + private VelNetManager manager; /// /// listen to this if you want to send voice @@ -40,7 +44,7 @@ namespace VelNetUnity { _status = ConnectionStatus.Connected; dissonanceComms = GetComponent(); - manager = NetworkManager.instance; + manager = VelNetManager.instance; } public void Initialize(string playerName, Rooms rooms, PlayerChannels playerChannels, RoomChannels roomChannels, CodecSettings codecSettings) diff --git a/Samples~/DissonanceIntegration/VelNetDissonancePlayer.cs b/Samples~/DissonanceIntegration/VelNetDissonancePlayer.cs index 9bf72c0..0ea2dba 100644 --- a/Samples~/DissonanceIntegration/VelNetDissonancePlayer.cs +++ b/Samples~/DissonanceIntegration/VelNetDissonancePlayer.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using Dissonance; using UnityEngine; @@ -12,7 +11,7 @@ namespace VelNetUnity /// This should be added to your player object /// [AddComponentMenu("VelNetUnity/Dissonance/VelNet Dissonance Player")] - public class VelNetDissonancePlayer : NetworkObject, IDissonancePlayer + public class VelNetDissonancePlayer : NetworkComponent, IDissonancePlayer { private VelCommsNetwork comms; private bool isSpeaking; @@ -25,20 +24,24 @@ namespace VelNetUnity public string PlayerId => dissonanceID; public Vector3 Position => transform.position; public Quaternion Rotation => transform.rotation; - public NetworkPlayerType Type => owner.isLocal ? NetworkPlayerType.Local : NetworkPlayerType.Remote; + public NetworkPlayerType Type => IsMine ? NetworkPlayerType.Local : NetworkPlayerType.Remote; public bool IsTracking => true; - public Vector3 targetPosition; - public Quaternion targetRotation; - private static readonly List allPlayers = new List(); public List closePlayers = new List(); [Tooltip("Maximum distance to transmit voice data. 0 to always send voice to all players.")] public float maxDistance; - // Start is called before the first frame update - private void Start() + private enum MessageType : byte + { + AudioData, + DissonanceId, + SpeakingState + } + + // This object should not be in the scene at the start. + private void Awake() { comms = FindObjectOfType(); if (comms == null) @@ -53,76 +56,37 @@ namespace VelNetUnity allPlayers.Add(this); } - if (owner.isLocal) + if (IsMine) { SetDissonanceID(comms.dissonanceId); comms.VoiceQueued += SendVoiceData; //we also need to know when other players join, so we can send the dissonance ID again - NetworkManager.instance.OnPlayerJoined += (player) => + VelNetManager.instance.OnPlayerJoined += (player) => { - byte[] b = Encoding.UTF8.GetBytes(dissonanceID); - owner.SendMessage(this, "d", b); + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.DissonanceId); + writer.Write(dissonanceID); + SendBytes(mem.ToArray()); }; - NetworkManager.instance.SetupMessageGroup("close", closePlayers.ToArray()); - } - } - - public override void HandleMessage(string identifier, byte[] message) - { - switch (identifier) - { - case "a": //audio data - { - if (isSpeaking) - { - comms.VoiceReceived(dissonanceID, message); - } - - break; - } - case "d": //dissonance id (player joined) - { - if (dissonanceID == "") // I don't have this yet - { - dissonanceID = Encoding.UTF8.GetString(message); - // tell the comms network that this player joined the channel - comms.SetPlayerJoined(dissonanceID); // tell dissonance - comms.dissonanceComms.TrackPlayerPosition(this); // tell dissonance to track the remote player - } - - break; - } - case "x": // speaking state - { - if (message[0] == 0) - { - comms.SetPlayerStoppedSpeaking(dissonanceID); - isSpeaking = false; - } - else - { - comms.SetPlayerStartedSpeaking(dissonanceID); - isSpeaking = true; - } - - break; - } + VelNetManager.instance.SetupMessageGroup("close", closePlayers.ToArray()); } } private void SendVoiceData(ArraySegment data) { // need to send it - if (owner == null || !owner.isLocal) return; + if (!IsMine) return; using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.AudioData); writer.Write(BitConverter.GetBytes(lastAudioId++)); writer.Write(data.ToArray()); // send voice data unreliably - owner.SendGroupMessage(this, "close", "a", mem.ToArray(), false); + SendBytesToGroup("close", mem.ToArray(), false); } /// @@ -132,8 +96,12 @@ namespace VelNetUnity public void SetDissonanceID(string id) { dissonanceID = id; - byte[] b = Encoding.UTF8.GetBytes(dissonanceID); - owner.SendMessage(this, "d", b); + + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.DissonanceId); + writer.Write(dissonanceID); + SendBytes(mem.ToArray()); comms.dissonanceComms.TrackPlayerPosition(this); } @@ -151,8 +119,7 @@ namespace VelNetUnity // Update is called once per frame private void Update() { - if (owner == null) return; - if (!owner.isLocal) return; + if (!IsMine) return; // handle nearness cutoff if (maxDistance > 0) @@ -166,21 +133,21 @@ namespace VelNetUnity } float dist = Vector3.Distance(p.transform.position, transform.position); - if (dist < maxDistance && !closePlayers.Contains(p.owner.userid)) + if (dist < maxDistance && !closePlayers.Contains(p.Owner.userid)) { - closePlayers.Add(p.owner.userid); + closePlayers.Add(p.Owner.userid); closePlayerListChanged = true; } - else if (dist >= maxDistance && closePlayers.Contains(p.owner.userid)) + else if (dist >= maxDistance && closePlayers.Contains(p.Owner.userid)) { - closePlayers.Remove(p.owner.userid); + closePlayers.Remove(p.Owner.userid); closePlayerListChanged = true; } } if (closePlayerListChanged) { - NetworkManager.instance.SetupMessageGroup("close", closePlayers); + VelNetManager.instance.SetupMessageGroup("close", closePlayers); } } @@ -191,8 +158,12 @@ namespace VelNetUnity if (comms.dissonanceComms.FindPlayer(dissonanceID)?.IsSpeaking != isSpeaking) //unfortunately, there does not seem to be an event for this { isSpeaking = !isSpeaking; - byte[] toSend = { isSpeaking ? (byte)1 : (byte)0 }; - owner.SendMessage(this, "x", toSend); + + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write((byte)MessageType.SpeakingState); + writer.Write(isSpeaking ? (byte)1 : (byte)0); + SendBytes(mem.ToArray()); if (!isSpeaking) { @@ -200,5 +171,52 @@ namespace VelNetUnity } } } + + public override void ReceiveBytes(byte[] message) + { + using MemoryStream mem = new MemoryStream(message); + using BinaryReader reader = new BinaryReader(mem); + + byte identifier = reader.ReadByte(); + switch (identifier) + { + case 0: //audio data + { + if (isSpeaking) + { + comms.VoiceReceived(dissonanceID, message.Skip(1).ToArray()); + } + + break; + } + case 1: //dissonance id (player joined) + { + if (dissonanceID == "") // I don't have this yet + { + dissonanceID = reader.ReadString(); + // tell the comms network that this player joined the channel + comms.SetPlayerJoined(dissonanceID); // tell dissonance + comms.dissonanceComms.TrackPlayerPosition(this); // tell dissonance to track the remote player + } + + break; + } + case 2: // speaking state + { + if (message[0] == 0) + { + comms.SetPlayerStoppedSpeaking(dissonanceID); + isSpeaking = false; + } + else + { + comms.SetPlayerStartedSpeaking(dissonanceID); + isSpeaking = true; + } + + break; + } + } + } } } \ No newline at end of file diff --git a/Samples~/Example/NetworkGUI.cs b/Samples~/Example/NetworkGUI.cs index b5ed7af..034466f 100644 --- a/Samples~/Example/NetworkGUI.cs +++ b/Samples~/Example/NetworkGUI.cs @@ -1,12 +1,13 @@ using System.Collections.Generic; using UnityEngine; +using UnityEngine.Serialization; using UnityEngine.UI; namespace VelNetUnity { public class NetworkGUI : MonoBehaviour { - public NetworkManager networkManager; + [FormerlySerializedAs("networkManager")] public VelNetManager velNetManager; public InputField userInput; public InputField sendInput; public InputField roomInput; @@ -19,7 +20,7 @@ namespace VelNetUnity { if (sendInput.text != "") { - networkManager.SendTo(NetworkManager.MessageType.OTHERS, sendInput.text); + velNetManager.SendTo(VelNetManager.MessageType.OTHERS, sendInput.text); } } @@ -27,7 +28,7 @@ namespace VelNetUnity { if (userInput.text != "") { - networkManager.Login(userInput.text, "nopass"); + velNetManager.Login(userInput.text, "nopass"); } } @@ -35,7 +36,7 @@ namespace VelNetUnity { if (roomInput.text != "") { - networkManager.Join(roomInput.text); + velNetManager.Join(roomInput.text); } } @@ -44,7 +45,7 @@ namespace VelNetUnity { comms = FindObjectOfType(); microphones.AddOptions(new List(Microphone.devices)); - networkManager.MessageReceived += (m) => + velNetManager.MessageReceived += (m) => { string s = m.type + ":" + m.sender + ":" + m.text; messageBuffer.Add(s); diff --git a/Samples~/Example/PlayerController.cs b/Samples~/Example/PlayerController.cs index 5f72100..aebafb5 100644 --- a/Samples~/Example/PlayerController.cs +++ b/Samples~/Example/PlayerController.cs @@ -1,16 +1,60 @@ -using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; namespace VelNetUnity { - public class PlayerController : NetworkObject + public class PlayerController : NetworkSerializedObject { public Vector3 targetPosition; public Quaternion targetRotation; - public byte[] GetSyncMessage() + + // Update is called once per frame + private void Update() + { + if (!IsMine) + { + transform.position = Vector3.Lerp(transform.position, targetPosition, .1f); + transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f); + } + else + { + Vector3 movement = new Vector3(); + movement.x += Input.GetAxis("Horizontal"); + movement.y += Input.GetAxis("Vertical"); + movement.z = 0; + transform.Translate(movement * Time.deltaTime); + + if (Input.GetKeyDown(KeyCode.Space)) + { + VelNetManager.InstantiateNetworkObject("TestNetworkedGameObject"); + } + + if (Input.GetKeyDown(KeyCode.BackQuote)) + { + foreach (KeyValuePair kvp in VelNetManager.instance.objects) + { + Owner.TakeOwnership(kvp.Key); + } + } + + if (Input.GetKeyDown(KeyCode.Backspace)) + { + foreach (KeyValuePair kvp in VelNetManager.instance.objects) + { + // don't destroy player objects + if (!kvp.Value.ownershipLocked) + { + Owner.NetworkDestroy(kvp.Key); + } + } + } + } + } + + + protected override byte[] SendState() { using MemoryStream mem = new MemoryStream(); using BinaryWriter writer = new BinaryWriter(mem); @@ -21,80 +65,13 @@ namespace VelNetUnity return mem.ToArray(); } - public override void HandleMessage(string identifier, byte[] message) + protected override void ReceiveState(byte[] message) { - switch (identifier) - { - case "s": // sync message - { - using MemoryStream mem = new MemoryStream(message); - using BinaryReader reader = new BinaryReader(mem); + using MemoryStream mem = new MemoryStream(message); + using BinaryReader reader = new BinaryReader(mem); - targetPosition = reader.ReadVector3(); - targetRotation = reader.ReadQuaternion(); - - break; - } - } - } - - // Start is called before the first frame update - private void Start() - { - // player controller shouldn't change ownership, so we can check here once - if (owner.isLocal) - { - StartCoroutine(SyncBehavior()); - } - } - - - private IEnumerator SyncBehavior() - { - while (true) - { - owner.SendMessage(this, "s", GetSyncMessage()); - yield return new WaitForSeconds(.1f); - } - } - - // Update is called once per frame - private void Update() - { - if (owner != null && !owner.isLocal) - { - transform.position = Vector3.Lerp(transform.position, targetPosition, .1f); - transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, .1f); - } - else if (owner != null && owner.isLocal) - { - Vector3 movement = new Vector3(); - movement.x += Input.GetAxis("Horizontal"); - movement.y += Input.GetAxis("Vertical"); - movement.z = 0; - transform.Translate(movement * Time.deltaTime); - - if (Input.GetKeyDown(KeyCode.Space)) - { - owner.NetworkInstantiate("TestNetworkedGameObject"); - } - - if (Input.GetKeyDown(KeyCode.BackQuote)) - { - foreach (KeyValuePair kvp in NetworkManager.instance.objects) - { - owner.TakeOwnership(kvp.Key); - } - } - - if (Input.GetKeyDown(KeyCode.Backspace)) - { - foreach (KeyValuePair kvp in NetworkManager.instance.objects) - { - owner.NetworkDestroy(kvp.Key); - } - } - } + targetPosition = reader.ReadVector3(); + targetRotation = reader.ReadQuaternion(); } } } \ No newline at end of file diff --git a/Samples~/Example/PlayerPrefab.prefab b/Samples~/Example/PlayerPrefab.prefab index d590dc3..e4b77af 100644 --- a/Samples~/Example/PlayerPrefab.prefab +++ b/Samples~/Example/PlayerPrefab.prefab @@ -12,7 +12,7 @@ GameObject: - component: {fileID: 8527011532923434593} - component: {fileID: 6854617867369839} - component: {fileID: 5845716565458182149} - - component: {fileID: 5713671764962751473} + - component: {fileID: 9102273340480352682} - component: {fileID: -4404668399269848200} - component: {fileID: 1181612843795795320} m_Layer: 0 @@ -98,7 +98,7 @@ BoxCollider: serializedVersion: 2 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} ---- !u!114 &5713671764962751473 +--- !u!114 &9102273340480352682 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -107,14 +107,16 @@ MonoBehaviour: m_GameObject: {fileID: 6139051692386484099} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d8d3b6de660834e3e898725928251405, type: 3} + m_Script: {fileID: 11500000, guid: 5515094c5c544b6b8ed7fd51a86548d4, type: 3} m_Name: m_EditorClassIdentifier: - userid: 0 - username: - room: - isLocal: 0 - lastObjectId: 0 + ownershipLocked: 1 + networkId: + prefabName: + isSceneObject: 0 + syncedComponents: + - {fileID: -4404668399269848200} + - {fileID: 1181612843795795320} --- !u!114 &-4404668399269848200 MonoBehaviour: m_ObjectHideFlags: 0 @@ -127,10 +129,8 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 89e3af759df774692a566a166b4bf69b, type: 3} m_Name: m_EditorClassIdentifier: - owner: {fileID: 5713671764962751473} - networkId: - prefabName: - isSceneObject: 0 + networkObject: {fileID: 9102273340480352682} + serializationRateHz: 30 targetPosition: {x: 0, y: 0, z: 0} targetRotation: {x: 0, y: 0, z: 0, w: 0} --- !u!114 &1181612843795795320 @@ -145,13 +145,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c773e094326d413bb1bca7f91cbf7f8c, type: 3} m_Name: m_EditorClassIdentifier: - owner: {fileID: 5713671764962751473} - networkId: - prefabName: - isSceneObject: 0 + networkObject: {fileID: 9102273340480352682} dissonanceID: targetPosition: {x: 0, y: 0, z: 0} targetRotation: {x: 0, y: 0, z: 0, w: 0} - allPlayers: [] closePlayers: maxDistance: 0 diff --git a/Samples~/Example/TestNetworkedGameObject.prefab b/Samples~/Example/TestNetworkedGameObject.prefab index bac19d3..e08d947 100644 --- a/Samples~/Example/TestNetworkedGameObject.prefab +++ b/Samples~/Example/TestNetworkedGameObject.prefab @@ -9,10 +9,11 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 8565720275311462455} + - component: {fileID: 3951900052977689805} - component: {fileID: 8565720275311462452} m_Layer: 0 m_Name: TestNetworkedGameObject - m_TagString: Untagged + m_TagString: TestSphere m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 @@ -32,6 +33,24 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &3951900052977689805 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8565720275311462453} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5515094c5c544b6b8ed7fd51a86548d4, type: 3} + m_Name: + m_EditorClassIdentifier: + ownershipLocked: 0 + networkId: + prefabName: + isSceneObject: 0 + syncedComponents: + - {fileID: 8565720275311462452} --- !u!114 &8565720275311462452 MonoBehaviour: m_ObjectHideFlags: 0 @@ -44,10 +63,11 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 3f1f9b0bbd93a484a987c51f1107ebe5, type: 3} m_Name: m_EditorClassIdentifier: - owner: {fileID: 0} - networkId: + networkObject: {fileID: 3951900052977689805} + serializationRateHz: 30 targetPosition: {x: 0, y: 0, z: 0} targetRotation: {x: 0, y: 0, z: 0, w: 0} + smoothness: 0.1 --- !u!1 &8565720276181857625 GameObject: m_ObjectHideFlags: 0 diff --git a/Samples~/Example/VelNetMan.cs b/Samples~/Example/VelNetMan.cs new file mode 100644 index 0000000..00bf9e7 --- /dev/null +++ b/Samples~/Example/VelNetMan.cs @@ -0,0 +1,18 @@ +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using VelNetUnity; + +public class VelNetMan : MonoBehaviour +{ + public GameObject playerPrefab; + + // Start is called before the first frame update + private void Start() + { + VelNetManager.instance.OnJoinedRoom += player => + { + VelNetManager.InstantiateNetworkObject(playerPrefab.name); + }; + } +} \ No newline at end of file diff --git a/Samples~/Example/VelNetMan.cs.meta b/Samples~/Example/VelNetMan.cs.meta new file mode 100644 index 0000000..225d370 --- /dev/null +++ b/Samples~/Example/VelNetMan.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2fcf036844b060b47b23ad9a1e49eec2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples~/Example/test.unity b/Samples~/Example/test.unity index 2fcffdb..bda34bd 100644 --- a/Samples~/Example/test.unity +++ b/Samples~/Example/test.unity @@ -38,7 +38,7 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1} + m_IndirectSpecularColor: {r: 0.44657898, g: 0.4964133, b: 0.5748178, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -624,7 +624,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 7a7db5bc792cd471dbd8039664359eee, type: 3} m_Name: m_EditorClassIdentifier: - networkManager: {fileID: 1099803616} + velNetManager: {fileID: 1099803616} userInput: {fileID: 626742069} sendInput: {fileID: 945446555} roomInput: {fileID: 711524768} @@ -1787,6 +1787,7 @@ GameObject: m_Component: - component: {fileID: 1099803615} - component: {fileID: 1099803616} + - component: {fileID: 1099803613} m_Layer: 0 m_Name: NetworkManager m_TagString: Untagged @@ -1794,6 +1795,19 @@ GameObject: m_NavMeshLayer: 0 m_StaticEditorFlags: 0 m_IsActive: 1 +--- !u!114 &1099803613 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1099803612} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2fcf036844b060b47b23ad9a1e49eec2, type: 3} + m_Name: + m_EditorClassIdentifier: + playerPrefab: {fileID: 6139051692386484099, guid: d4158ab9c4a204cdbba28d3273fc1fb3, type: 3} --- !u!4 &1099803615 Transform: m_ObjectHideFlags: 0 @@ -1825,9 +1839,9 @@ MonoBehaviour: udpConnected: 0 userid: -1 room: - playerPrefab: {fileID: 6139051692386484099, guid: d4158ab9c4a204cdbba28d3273fc1fb3, type: 3} prefabs: - - {fileID: 8565720275311462452, guid: 6e4a023f70e01405e8b249a4488fe319, type: 3} + - {fileID: 3951900052977689805, guid: 6e4a023f70e01405e8b249a4488fe319, type: 3} + - {fileID: 9102273340480352682, guid: d4158ab9c4a204cdbba28d3273fc1fb3, type: 3} sceneObjects: [] deletedSceneObjects: [] connected: 0 @@ -2281,8 +2295,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: dissonanceId: - comms: {fileID: 1434745021} - manager: {fileID: 1099803616} + dissonanceComms: {fileID: 1434745021} --- !u!4 &1434745020 Transform: m_ObjectHideFlags: 0 diff --git a/package.json b/package.json index 50f6464..fcdbec8 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "edu.uga.engr.vel.velnetunity", "displayName": "VelNetUnity", - "version": "1.0.2", + "version": "1.0.3", "unity": "2019.1", "description": "A custom networking library for Unity.", "keywords": [