From 8861888ade9cf97dc583c4a630410f8d1c3a85fd Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Thu, 6 Jan 2022 17:58:20 -0500 Subject: [PATCH] some more cleanup and documentation, split dissonance player into its own componenent for simple adding, added some binarywriter extensions --- Runtime/NetworkManager.cs | 34 +- Runtime/NetworkObject.cs | 10 +- Runtime/NetworkPlayer.cs | 23 +- Runtime/Util/BinaryWriterExtensions.cs | 38 +++ Runtime/Util/BinaryWriterExtensions.cs.meta | 3 + Runtime/Util/SyncTransform.cs | 33 +- .../VelAudioMixer.mixer | 0 .../VelAudioMixer.mixer.meta | 0 .../Dissonance Integration/VelCommsNetwork.cs | 104 ++++++ .../VelCommsNetwork.cs.meta | 0 .../VelNetDissonancePlayer.cs | 195 +++++++++++ .../VelNetDissonancePlayer.cs.meta | 3 + .../DissonanceIntegration/VelCommsNetwork.cs | 109 ------ Samples~/Example/NetworkGUI.cs | 124 ++++--- Samples~/Example/PlayerController.cs | 320 +++++------------- Samples~/Example/PlayerPrefab.prefab | 44 ++- Samples~/Example/test.unity | 6 +- package.json | 4 +- 18 files changed, 598 insertions(+), 452 deletions(-) create mode 100644 Runtime/Util/BinaryWriterExtensions.cs create mode 100644 Runtime/Util/BinaryWriterExtensions.cs.meta rename Samples~/{DissonanceIntegration => Dissonance Integration}/VelAudioMixer.mixer (100%) rename Samples~/{DissonanceIntegration => Dissonance Integration}/VelAudioMixer.mixer.meta (100%) create mode 100644 Samples~/Dissonance Integration/VelCommsNetwork.cs rename Samples~/{DissonanceIntegration => Dissonance Integration}/VelCommsNetwork.cs.meta (100%) create mode 100644 Samples~/Dissonance Integration/VelNetDissonancePlayer.cs create mode 100644 Samples~/Dissonance Integration/VelNetDissonancePlayer.cs.meta delete mode 100644 Samples~/DissonanceIntegration/VelCommsNetwork.cs diff --git a/Runtime/NetworkManager.cs b/Runtime/NetworkManager.cs index 1780255..65bb271 100644 --- a/Runtime/NetworkManager.cs +++ b/Runtime/NetworkManager.cs @@ -8,6 +8,7 @@ using System.Net; namespace VelNetUnity { + [AddComponentMenu("VelNetUnity/VelNet Network Manager")] public class NetworkManager : MonoBehaviour { public enum MessageType @@ -21,6 +22,8 @@ namespace VelNetUnity public string host; public int port; + public static NetworkManager instance; + #region private members private TcpClient socketConnection; @@ -36,14 +39,14 @@ namespace VelNetUnity public GameObject playerPrefab; public Dictionary players = new Dictionary(); - public Action onJoinedRoom = delegate { }; - public Action onPlayerJoined = delegate { }; - public Action onPlayerLeft = delegate { }; + public Action OnJoinedRoom; + public Action OnPlayerJoined; + public Action OnPlayerLeft; public List prefabs = new List(); public NetworkObject[] sceneObjects; public List deletedSceneObjects = new List(); - public Dictionary objects = new Dictionary(); //maintains a list of all known objects on the server (ones that have ids) + public readonly Dictionary objects = new Dictionary(); //maintains a list of all known objects on the server (ones that have ids) private NetworkPlayer masterPlayer; #endregion @@ -58,6 +61,15 @@ namespace VelNetUnity public readonly List receivedMessages = new List(); + private void Awake() + { + if (instance != null) + { + Debug.LogError("Multiple NetworkManagers detected! Bad!", this); + } + instance = this; + } + private void Start() { ConnectToTcpServer(); @@ -112,8 +124,7 @@ namespace VelNetUnity player.userid = m.sender; players.Add(userid, player); player.room = m.text; - player.manager = this; - onJoinedRoom(player); + OnJoinedRoom?.Invoke(player); } } else //not for me, a player is joining or leaving @@ -150,9 +161,8 @@ namespace VelNetUnity player.isLocal = false; player.room = m.text; player.userid = m.sender; - player.manager = this; players.Add(m.sender, player); - onPlayerJoined(player); + OnPlayerJoined?.Invoke(player); } } } @@ -498,10 +508,12 @@ namespace VelNetUnity } } - //changes the designated group that sendto(4) will go to - public void SetupMessageGroup(string groupname, int[] userids) + /// + /// changes the designated group that sendto(4) will go to + /// + public void SetupMessageGroup(string groupName, IEnumerable userIds) { - SendNetworkMessage("5:" + groupname + ":" + String.Join(":", userids)); + SendNetworkMessage($"5:{groupName}:{string.Join(":", userIds)}"); } public void DeleteNetworkObject(string networkId) diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index c106206..8742189 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -8,8 +8,14 @@ namespace VelNetUnity public abstract class NetworkObject : MonoBehaviour { public NetworkPlayer owner; - public string networkId; //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 prefabName; //this may be empty if it's not a prefab (scene object) + /// + /// 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); } diff --git a/Runtime/NetworkPlayer.cs b/Runtime/NetworkPlayer.cs index 1c1e46f..b59d089 100644 --- a/Runtime/NetworkPlayer.cs +++ b/Runtime/NetworkPlayer.cs @@ -5,28 +5,37 @@ using System; namespace VelNetUnity { + /// + /// One of these will be instantiated for each player that joins the "room". + /// [RequireComponent(typeof(NetworkObject))] + [AddComponentMenu("VelNetUnity/VelNet Network Player")] public class NetworkPlayer : MonoBehaviour { - public NetworkObject myObject; + private NetworkObject myObject; public int userid; public string username; public string room; - public NetworkManager manager; public bool isLocal; - public int lastObjectId; //for instantiation + private NetworkManager manager; + + /// + /// For instantiation + /// + public int lastObjectId; private bool isMaster; private void Start() { + myObject = GetComponent(); myObject.owner = this; - manager = FindObjectOfType(); - manager.onPlayerJoined += HandlePlayerJoined; + manager = NetworkManager.instance; + manager.OnPlayerJoined += HandlePlayerJoined; } public void HandlePlayerJoined(NetworkPlayer player) @@ -111,7 +120,7 @@ namespace VelNetUnity NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName); if (temp != null) { - NetworkObject instance = Instantiate(temp); + NetworkObject instance = Instantiate(temp); instance.networkId = networkId; instance.prefabName = prefabName; instance.owner = this; @@ -184,7 +193,7 @@ namespace VelNetUnity NetworkObject temp = manager.prefabs.Find((prefab) => prefab.name == prefabName); if (temp != null) { - NetworkObject instance = Instantiate(temp); + NetworkObject instance = Instantiate(temp); instance.networkId = networkId; instance.prefabName = prefabName; instance.owner = this; diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs new file mode 100644 index 0000000..a07fc60 --- /dev/null +++ b/Runtime/Util/BinaryWriterExtensions.cs @@ -0,0 +1,38 @@ +using System.IO; +using UnityEngine; + +namespace VelNetUnity +{ + public static class BinaryWriterExtensions + { + public static void Write(this BinaryWriter writer, Vector3 v) + { + writer.Write(v.x); + writer.Write(v.y); + writer.Write(v.z); + } + + public static void Write(this BinaryWriter writer, Quaternion q) + { + writer.Write(q.x); + writer.Write(q.y); + writer.Write(q.z); + writer.Write(q.w); + } + + public static Vector3 ReadVector3(this BinaryReader reader) + { + return new Vector3(reader.ReadSingle(), reader.ReadSingle(), reader.ReadSingle()); + } + + public static Quaternion ReadQuaternion(this BinaryReader reader) + { + return new Quaternion( + reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle(), + reader.ReadSingle() + ); + } + } +} \ No newline at end of file diff --git a/Runtime/Util/BinaryWriterExtensions.cs.meta b/Runtime/Util/BinaryWriterExtensions.cs.meta new file mode 100644 index 0000000..8b72fde --- /dev/null +++ b/Runtime/Util/BinaryWriterExtensions.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 239a48a05eea49c583384b4113cf4729 +timeCreated: 1641508695 \ No newline at end of file diff --git a/Runtime/Util/SyncTransform.cs b/Runtime/Util/SyncTransform.cs index f3e09e4..fae5396 100644 --- a/Runtime/Util/SyncTransform.cs +++ b/Runtime/Util/SyncTransform.cs @@ -1,5 +1,5 @@ -using System; using System.Collections; +using System.IO; using UnityEngine; @@ -8,6 +8,7 @@ namespace VelNetUnity /// /// A simple class that will sync the position and rotation of a network object /// + [AddComponentMenu("VelNetUnity/VelNet Sync Transform")] public class SyncTransform : NetworkObject { public Vector3 targetPosition; @@ -16,18 +17,13 @@ namespace VelNetUnity public byte[] GetSyncMessage() { - float[] data = new float[7]; - for (int i = 0; i < 3; i++) - { - data[i] = transform.position[i]; - data[i + 3] = transform.rotation[i]; - } + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); - data[6] = transform.rotation[3]; + writer.Write(transform.position); + writer.Write(transform.rotation); - byte[] toReturn = new byte[sizeof(float) * data.Length]; - Buffer.BlockCopy(data, 0, toReturn, 0, toReturn.Length); - return toReturn; + return mem.ToArray(); } public override void HandleMessage(string identifier, byte[] message) @@ -35,16 +31,15 @@ namespace VelNetUnity switch (identifier) { case "s": - float[] data = new float[7]; - Buffer.BlockCopy(message, 0, data, 0, message.Length); - for (int i = 0; i < 3; i++) - { - targetPosition[i] = data[i]; - targetRotation[i] = data[i + 3]; - } + { + using MemoryStream mem = new MemoryStream(message); + using BinaryReader reader = new BinaryReader(mem); + + targetPosition = reader.ReadVector3(); + targetRotation = reader.ReadQuaternion(); - targetRotation[3] = data[6]; break; + } } } diff --git a/Samples~/DissonanceIntegration/VelAudioMixer.mixer b/Samples~/Dissonance Integration/VelAudioMixer.mixer similarity index 100% rename from Samples~/DissonanceIntegration/VelAudioMixer.mixer rename to Samples~/Dissonance Integration/VelAudioMixer.mixer diff --git a/Samples~/DissonanceIntegration/VelAudioMixer.mixer.meta b/Samples~/Dissonance Integration/VelAudioMixer.mixer.meta similarity index 100% rename from Samples~/DissonanceIntegration/VelAudioMixer.mixer.meta rename to Samples~/Dissonance Integration/VelAudioMixer.mixer.meta diff --git a/Samples~/Dissonance Integration/VelCommsNetwork.cs b/Samples~/Dissonance Integration/VelCommsNetwork.cs new file mode 100644 index 0000000..e5af2f8 --- /dev/null +++ b/Samples~/Dissonance Integration/VelCommsNetwork.cs @@ -0,0 +1,104 @@ +using System; +using Dissonance; +using Dissonance.Networking; +using UnityEngine; + + +namespace VelNetUnity +{ + [AddComponentMenu("VelNetUnity/Dissonance/VelNet Comms Network")] + public class VelCommsNetwork : MonoBehaviour, ICommsNetwork + { + public ConnectionStatus Status => manager.connected ? ConnectionStatus.Connected : ConnectionStatus.Disconnected; + public NetworkMode Mode => NetworkMode.Client; + + public event Action ModeChanged; + public event Action PlayerJoined; + public event Action PlayerLeft; + public event Action VoicePacketReceived; + public event Action TextPacketReceived; + public event Action PlayerStartedSpeaking; + public event Action PlayerStoppedSpeaking; + public event Action PlayerEnteredRoom; + public event Action PlayerExitedRoom; + + private ConnectionStatus _status = ConnectionStatus.Disconnected; + private CodecSettings initSettings; + public string dissonanceId; + public DissonanceComms comms; + private NetworkManager manager; + + /// + /// listen to this if you want to send voice + /// + public Action> VoiceQueued; + + + // Start is called before the first frame update + private void Start() + { + _status = ConnectionStatus.Connected; + comms = GetComponent(); + manager = NetworkManager.instance; + } + + public void Initialize(string playerName, Rooms rooms, PlayerChannels playerChannels, RoomChannels roomChannels, CodecSettings codecSettings) + { + dissonanceId = playerName; + initSettings = codecSettings; + comms.ResetMicrophoneCapture(); + } + + public void VoiceReceived(string sender, byte[] data) + { + uint sequenceNumber = BitConverter.ToUInt32(data, 0); + VoicePacket vp = new VoicePacket(sender, ChannelPriority.Default, 1, true, new ArraySegment(data, 4, data.Length - 4), sequenceNumber); + VoicePacketReceived?.Invoke(vp); + } + + public void SendText(string data, ChannelType recipientType, string recipientId) + { + Debug.Log("sending text"); + } + + public void SendVoice(ArraySegment data) + { + VoiceQueued?.Invoke(data); + } + + public void SetPlayerJoined(string id) + { + Debug.Log("Dissonance player joined"); + PlayerJoined?.Invoke(id, initSettings); + RoomEvent re = new RoomEvent + { + Joined = true, + Room = "Global", + PlayerName = id + }; + PlayerEnteredRoom?.Invoke(re); + } + + public void SetPlayerLeft(string id) + { + RoomEvent re = new RoomEvent + { + Joined = false, + Room = "Global", + PlayerName = id + }; + PlayerExitedRoom?.Invoke(re); + PlayerLeft?.Invoke(id); + } + + public void SetPlayerStartedSpeaking(string id) + { + PlayerStartedSpeaking?.Invoke(id); + } + + public void SetPlayerStoppedSpeaking(string id) + { + PlayerStoppedSpeaking?.Invoke(id); + } + } +} \ No newline at end of file diff --git a/Samples~/DissonanceIntegration/VelCommsNetwork.cs.meta b/Samples~/Dissonance Integration/VelCommsNetwork.cs.meta similarity index 100% rename from Samples~/DissonanceIntegration/VelCommsNetwork.cs.meta rename to Samples~/Dissonance Integration/VelCommsNetwork.cs.meta diff --git a/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs b/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs new file mode 100644 index 0000000..9caf58a --- /dev/null +++ b/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Dissonance; +using UnityEngine; + +namespace VelNetUnity +{ + [RequireComponent(typeof(VelCommsNetwork))] + [AddComponentMenu("VelNetUnity/Dissonance/VelNet Dissonance Player")] + public class VelNetDissonancePlayer : NetworkObject, IDissonancePlayer + { + private VelCommsNetwork comms; + private bool isSpeaking; + private uint lastAudioId; + + public string dissonanceID = ""; + + //required by dissonance for spatial audio + public string PlayerId => dissonanceID; + public Vector3 Position => transform.position; + public Quaternion Rotation => transform.rotation; + public NetworkPlayerType Type => owner.isLocal ? NetworkPlayerType.Local : NetworkPlayerType.Remote; + public bool IsTracking => true; + + public Vector3 targetPosition; + public Quaternion targetRotation; + + public 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() + { + comms = FindObjectOfType(); + + if (!allPlayers.Contains(this)) + { + allPlayers.Add(this); + } + + if (owner.isLocal) + { + 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) => + { + byte[] b = Encoding.UTF8.GetBytes(dissonanceID); + owner.SendMessage(this, "d", b); + }; + 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.comms.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; + } + } + } + + private void SendVoiceData(ArraySegment data) + { + // need to send it + if (owner == null || !owner.isLocal) return; + + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + writer.Write(BitConverter.GetBytes(lastAudioId++)); + writer.Write(data.ToArray()); + // send voice data unreliably + owner.SendGroupMessage(this, "close", "a", mem.ToArray(), false); + } + + /// + /// This sort of all initializes dissonance + /// + /// Dissonance ID + public void SetDissonanceID(string id) + { + dissonanceID = id; + byte[] b = Encoding.UTF8.GetBytes(dissonanceID); + owner.SendMessage(this, "d", b); + comms.comms.TrackPlayerPosition(this); + } + + private void VoiceInitialized(string id) + { + dissonanceID = id; + } + + private void OnDestroy() + { + comms.SetPlayerLeft(dissonanceID); + } + + + // Update is called once per frame + private void Update() + { + if (owner == null) return; + if (!owner.isLocal) return; + + // handle nearness cutoff + if (maxDistance > 0) + { + bool closePlayerListChanged = false; + foreach (VelNetDissonancePlayer p in allPlayers) + { + if (p == this) + { + continue; + } + + float dist = Vector3.Distance(p.transform.position, transform.position); + if (dist < maxDistance && !closePlayers.Contains(p.owner.userid)) + { + closePlayers.Add(p.owner.userid); + closePlayerListChanged = true; + } + else if (dist >= maxDistance && closePlayers.Contains(p.owner.userid)) + { + closePlayers.Remove(p.owner.userid); + closePlayerListChanged = true; + } + } + + if (closePlayerListChanged) + { + NetworkManager.instance.SetupMessageGroup("close", closePlayers); + } + } + + + //handle dissonance comms + + //if we're not speaking, and the comms say we are, send a speaking event, which will be received on other network players and sent to their comms accordingly + if (comms.comms.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); + + if (!isSpeaking) + { + lastAudioId = 0; + } + } + } + } +} \ No newline at end of file diff --git a/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs.meta b/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs.meta new file mode 100644 index 0000000..a6bb634 --- /dev/null +++ b/Samples~/Dissonance Integration/VelNetDissonancePlayer.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c773e094326d413bb1bca7f91cbf7f8c +timeCreated: 1641506874 \ No newline at end of file diff --git a/Samples~/DissonanceIntegration/VelCommsNetwork.cs b/Samples~/DissonanceIntegration/VelCommsNetwork.cs deleted file mode 100644 index b6d7d8d..0000000 --- a/Samples~/DissonanceIntegration/VelCommsNetwork.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using Dissonance; -using Dissonance.Extensions; -using Dissonance.Networking; -using UnityEngine; - - -public class VelCommsNetwork : MonoBehaviour, ICommsNetwork -{ - public ConnectionStatus Status - { - get - { - return manager.connected?ConnectionStatus.Connected:ConnectionStatus.Disconnected; - } - } - - public NetworkMode Mode - { - get - { - return NetworkMode.Client; - } - } - - public event Action ModeChanged; - public event Action PlayerJoined; - public event Action PlayerLeft; - public event Action VoicePacketReceived; - public event Action TextPacketReceived; - public event Action PlayerStartedSpeaking; - public event Action PlayerStoppedSpeaking; - public event Action PlayerEnteredRoom; - public event Action PlayerExitedRoom; - - ConnectionStatus _status = ConnectionStatus.Disconnected; - CodecSettings initSettings; - public string dissonanceId; - public DissonanceComms comms; - public NetworkManager manager; - - public Action> voiceQueued = delegate { }; //listen to this if you want to send voice - - public void Initialize(string playerName, Rooms rooms, PlayerChannels playerChannels, RoomChannels roomChannels, CodecSettings codecSettings) - { - dissonanceId = playerName; - initSettings = codecSettings; - comms.ResetMicrophoneCapture(); - } - - public void voiceReceived(string sender,byte[] data) - { - uint sequenceNumber = BitConverter.ToUInt32(data, 0); - VoicePacket vp = new VoicePacket(sender, ChannelPriority.Default, 1, true, new ArraySegment(data,4,data.Length-4), sequenceNumber); - VoicePacketReceived(vp); - } - - public void SendText(string data, ChannelType recipientType, string recipientId) - { - Debug.Log("sending text"); - } - - public void SendVoice(ArraySegment data) - { - voiceQueued(data); - } - - // Start is called before the first frame update - void Start() - { - _status = ConnectionStatus.Connected; - comms = GetComponent(); - - } - - public void playerJoined(string id) - { - Debug.Log("dissonance player joined"); - PlayerJoined(id, initSettings); - RoomEvent re = new RoomEvent(); - re.Joined = true; - re.Room = "Global"; - re.PlayerName = id; - PlayerEnteredRoom(re); - } - - public void playerLeft(string id) - { - RoomEvent re = new RoomEvent(); - re.Joined = false; - re.Room = "Global"; - re.PlayerName = id; - PlayerExitedRoom(re); - PlayerLeft(id); - } - - public void playerStartedSpeaking(string id) - { - PlayerStartedSpeaking(id); - } - public void playerStoppedSpeaking(string id) - { - PlayerStoppedSpeaking(id); - } - -} - diff --git a/Samples~/Example/NetworkGUI.cs b/Samples~/Example/NetworkGUI.cs index 0ff25e8..b5ed7af 100644 --- a/Samples~/Example/NetworkGUI.cs +++ b/Samples~/Example/NetworkGUI.cs @@ -1,73 +1,71 @@ -using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; -public class NetworkGUI : MonoBehaviour + +namespace VelNetUnity { - public NetworkManager networkManager; - public InputField userInput; - public InputField sendInput; - public InputField roomInput; - public Text messages; - public List messageBuffer; - public Dropdown microphones; - Dissonance.DissonanceComms comms; - public void handleSend() - { - if(sendInput.text != "") - { - networkManager.sendTo(NetworkManager.MessageType.OTHERS,sendInput.text); - } - } - public void handleLogin() - { - if(userInput.text != "") - { - networkManager.login(userInput.text, "nopass"); - } - } - public void handleJoin() - { - if(roomInput.text != "") - { - - networkManager.join(roomInput.text); - } + public class NetworkGUI : MonoBehaviour + { + public NetworkManager networkManager; + public InputField userInput; + public InputField sendInput; + public InputField roomInput; + public Text messages; + public List messageBuffer; + public Dropdown microphones; + Dissonance.DissonanceComms comms; - } + public void HandleSend() + { + if (sendInput.text != "") + { + networkManager.SendTo(NetworkManager.MessageType.OTHERS, sendInput.text); + } + } - // Start is called before the first frame update - void Start() - { - comms = GameObject.FindObjectOfType(); - microphones.AddOptions(new List(Microphone.devices)); - networkManager.messageReceived += (m) => { - string s = m.type + ":" + m.sender +":" + m.text; - messageBuffer.Add(s); - messages.text = ""; + public void HandleLogin() + { + if (userInput.text != "") + { + networkManager.Login(userInput.text, "nopass"); + } + } + + public void HandleJoin() + { + if (roomInput.text != "") + { + networkManager.Join(roomInput.text); + } + } + + // Start is called before the first frame update + private void Start() + { + comms = FindObjectOfType(); + microphones.AddOptions(new List(Microphone.devices)); + networkManager.MessageReceived += (m) => + { + string s = m.type + ":" + m.sender + ":" + m.text; + messageBuffer.Add(s); + messages.text = ""; - + if (messageBuffer.Count > 10) + { + messageBuffer.RemoveAt(0); + } - if(messageBuffer.Count > 10) - { - messageBuffer.RemoveAt(0); - } - for(int i = 0; i < messageBuffer.Count; i++) - { - messages.text = messages.text + messageBuffer[i] + "\n"; - } - }; - } + foreach (string msg in messageBuffer) + { + messages.text = messages.text + msg + "\n"; + } + }; + } - public void handleMicrophoneSelection() - { - comms.MicrophoneName = microphones.options[microphones.value].text; - } - - // Update is called once per frame - void Update() - { - - } -} + public void handleMicrophoneSelection() + { + comms.MicrophoneName = microphones.options[microphones.value].text; + } + } +} \ No newline at end of file diff --git a/Samples~/Example/PlayerController.cs b/Samples~/Example/PlayerController.cs index 0cfce96..5f72100 100644 --- a/Samples~/Example/PlayerController.cs +++ b/Samples~/Example/PlayerController.cs @@ -1,244 +1,100 @@ -using System; using System.Collections; using System.Collections.Generic; +using System.IO; using UnityEngine; -using Dissonance; -using System.Text; -public class PlayerController : NetworkObject, Dissonance.IDissonancePlayer + +namespace VelNetUnity { - VelCommsNetwork comms; - bool isSpeaking = false; - uint lastAudioId = 0; - public string dissonanceID=""; - //required by dissonance for spatial audio - public string PlayerId => dissonanceID; - public Vector3 Position => transform.position; - public Quaternion Rotation => transform.rotation; - public NetworkPlayerType Type => this.owner.isLocal ? NetworkPlayerType.Local : NetworkPlayerType.Remote; - public bool IsTracking => true; + public class PlayerController : NetworkObject + { + public Vector3 targetPosition; + public Quaternion targetRotation; - public Vector3 targetPosition; - public Quaternion targetRotation; + public byte[] GetSyncMessage() + { + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); - public List closePlayers = new List(); + writer.Write(transform.position); + writer.Write(transform.rotation); + + return mem.ToArray(); + } + + public override void HandleMessage(string identifier, byte[] message) + { + switch (identifier) + { + case "s": // sync message + { + 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()); + } + } - public byte[] getSyncMessage() - { - float[] data = new float[7]; - for (int i = 0; i < 3; i++) - { - data[i] = transform.position[i]; - data[i + 3] = transform.rotation[i]; - } - data[6] = transform.rotation[3]; + private IEnumerator SyncBehavior() + { + while (true) + { + owner.SendMessage(this, "s", GetSyncMessage()); + yield return new WaitForSeconds(.1f); + } + } - byte[] toReturn = new byte[sizeof(float) * data.Length]; - Buffer.BlockCopy(data, 0, toReturn, 0, toReturn.Length); - return toReturn; - } + // 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); - public override void handleMessage(string identifier, byte[] message) - { - switch (identifier) - { - case "s": //sync message - float[] data = new float[7]; - Buffer.BlockCopy(message, 0, data, 0, message.Length); - for (int i = 0; i < 3; i++) - { - targetPosition[i] = data[i]; - targetRotation[i] = data[i + 3]; - } - targetRotation[3] = data[6]; - break; - 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.playerJoined(dissonanceID); //tell dissonance - comms.comms.TrackPlayerPosition(this); //tell dissonance to track the remote player - } - break; - } - case "x": //speaking state - { - if (message[0]==0) - { - comms.playerStoppedSpeaking(dissonanceID); - isSpeaking = false; - } - else - { - - comms.playerStartedSpeaking(dissonanceID); - isSpeaking = true; - } - break; - } - } - } + if (Input.GetKeyDown(KeyCode.Space)) + { + owner.NetworkInstantiate("TestNetworkedGameObject"); + } - // Start is called before the first frame update - void Start() - { + if (Input.GetKeyDown(KeyCode.BackQuote)) + { + foreach (KeyValuePair kvp in NetworkManager.instance.objects) + { + owner.TakeOwnership(kvp.Key); + } + } - //handle dissonance stuff - comms = GameObject.FindObjectOfType(); - if(comms != null) - { - if (owner.isLocal) - { - setDissonanceID(comms.dissonanceId); - comms.voiceQueued += sendVoiceData; - - //we also need to know when other players join, so we can send the dissonance ID again - - owner.manager.onPlayerJoined += (player) => - { - byte[] b = Encoding.UTF8.GetBytes(dissonanceID); - owner.sendMessage(this, "d", b); - }; - owner.manager.setupMessageGroup("close", closePlayers.ToArray()); - } - } - if (owner.isLocal) - { - StartCoroutine(syncBehavior()); - } - } - - void sendVoiceData(ArraySegment data) - { - //need to send it - if(owner != null && owner.isLocal) - { - byte[] toSend = new byte[data.Count+4]; - byte[] lastAudioIdBytes = BitConverter.GetBytes(lastAudioId++); - Buffer.BlockCopy(lastAudioIdBytes, 0, toSend, 0, 4); - Buffer.BlockCopy(data.Array, data.Offset, toSend, 4, data.Count); - owner.sendGroupMessage(this,"close", "a", toSend, false); //send voice data unreliably - } - } - - public void setDissonanceID(string id) //this sort of all initializes dissonance - { - dissonanceID = id; - byte[] b = Encoding.UTF8.GetBytes(dissonanceID); - owner.sendMessage(this, "d", b); - comms.comms.TrackPlayerPosition(this); - } - - void voiceInitialized(string id) - { - dissonanceID = id; - } - - void OnDestroy() - { - comms.playerLeft(dissonanceID); - } - - - IEnumerator syncBehavior() - { - while (true) - { - owner.sendMessage(this, "s", getSyncMessage()); - yield return new WaitForSeconds(.1f); - } - } - // Update is called once per frame - void Update() - { - if (owner != null && owner.isLocal) { - - PlayerController[] players = GameObject.FindObjectsOfType(); - bool shouldUpdate = false; - for (int i = 0; i < players.Length; i++) - { - if (players[i] == this) { continue; } - float dist = Vector3.Distance(players[i].transform.position, this.transform.position); - if (dist < 2 && !closePlayers.Contains(players[i].owner.userid)) - { - closePlayers.Add(players[i].owner.userid); - shouldUpdate = true; - } - else if(dist >=2 && closePlayers.Contains(players[i].owner.userid)) - { - closePlayers.Remove(players[i].owner.userid); - shouldUpdate = true; - } - } - if (shouldUpdate) - { - owner.manager.setupMessageGroup("close", closePlayers.ToArray()); - } - } - - - 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) - { - - //handle dissonance comms - - //if we're not speaking, and the comms say we are, send a speaking event, which will be received on other network players and sent to their comms accordingly - if (comms.comms.FindPlayer(dissonanceID).IsSpeaking != isSpeaking) //unfortunately, there does not seem to be an event for this - { - isSpeaking = !isSpeaking; - byte[] toSend = new byte[1]; - toSend[0] = isSpeaking ? (byte)1 : (byte)0; - owner.sendMessage(this, "x", toSend); - - if (!isSpeaking) - { - lastAudioId = 0; - } - - } - - - - 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 owner.manager.objects) - { - owner.takeOwnership(kvp.Key); - } - } - if (Input.GetKeyDown(KeyCode.Backspace)) - { - foreach (KeyValuePair kvp in owner.manager.objects) - { - owner.networkDestroy(kvp.Key); - } - } - } - } -} + if (Input.GetKeyDown(KeyCode.Backspace)) + { + foreach (KeyValuePair kvp in NetworkManager.instance.objects) + { + owner.NetworkDestroy(kvp.Key); + } + } + } + } + } +} \ No newline at end of file diff --git a/Samples~/Example/PlayerPrefab.prefab b/Samples~/Example/PlayerPrefab.prefab index 1c3683f..5bee352 100644 --- a/Samples~/Example/PlayerPrefab.prefab +++ b/Samples~/Example/PlayerPrefab.prefab @@ -14,6 +14,8 @@ GameObject: - component: {fileID: 5845716565458182149} - component: {fileID: 5713671764962751473} - component: {fileID: -4404668399269848200} + - component: {fileID: 7065383111165629622} + - component: {fileID: 1181612843795795320} m_Layer: 0 m_Name: PlayerPrefab m_TagString: Untagged @@ -109,15 +111,11 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d8d3b6de660834e3e898725928251405, type: 3} m_Name: m_EditorClassIdentifier: - myObject: {fileID: -4404668399269848200} userid: 0 username: room: - manager: {fileID: 0} isLocal: 0 lastObjectId: 0 - commsNetwork: {fileID: 0} - dissonanceID: --- !u!114 &-4404668399269848200 MonoBehaviour: m_ObjectHideFlags: 0 @@ -132,5 +130,43 @@ MonoBehaviour: m_EditorClassIdentifier: owner: {fileID: 5713671764962751473} networkId: + prefabName: + isSceneObject: 0 targetPosition: {x: 0, y: 0, z: 0} targetRotation: {x: 0, y: 0, z: 0, w: 0} +--- !u!114 &7065383111165629622 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6139051692386484099} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5d2009d8e264649749c0315d48765749, type: 3} + m_Name: + m_EditorClassIdentifier: + dissonanceId: + comms: {fileID: 0} +--- !u!114 &1181612843795795320 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6139051692386484099} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: c773e094326d413bb1bca7f91cbf7f8c, type: 3} + m_Name: + m_EditorClassIdentifier: + owner: {fileID: 5713671764962751473} + networkId: + prefabName: + isSceneObject: 0 + 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/test.unity b/Samples~/Example/test.unity index 7e44ef7..2fcffdb 100644 --- a/Samples~/Example/test.unity +++ b/Samples~/Example/test.unity @@ -2121,7 +2121,7 @@ MonoBehaviour: m_Calls: - m_Target: {fileID: 244561621} m_TargetAssemblyTypeName: NetworkGUI, Assembly-CSharp - m_MethodName: handleJoin + m_MethodName: HandleJoin m_Mode: 1 m_Arguments: m_ObjectArgument: {fileID: 0} @@ -2725,7 +2725,7 @@ MonoBehaviour: m_Calls: - m_Target: {fileID: 244561621} m_TargetAssemblyTypeName: NetworkGUI, Assembly-CSharp - m_MethodName: handleLogin + m_MethodName: HandleLogin m_Mode: 1 m_Arguments: m_ObjectArgument: {fileID: 0} @@ -2858,7 +2858,7 @@ MonoBehaviour: m_Calls: - m_Target: {fileID: 244561621} m_TargetAssemblyTypeName: NetworkGUI, Assembly-CSharp - m_MethodName: handleSend + m_MethodName: HandleSend m_Mode: 1 m_Arguments: m_ObjectArgument: {fileID: 0} diff --git a/package.json b/package.json index 20d2d37..57f4990 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,7 @@ "self-hosted" ], "author": { - "name": "Kyle Johnsen", + "name": "Virtual Experiences Laboratory", "email": "kjohnsen@uga.edu", "url": "https://vel.engr.uga.edu" }, @@ -17,7 +17,7 @@ { "displayName": "Dissonance Integration", "description": "Includes support for Dissonance Voice, available separately from the Unity Asset Store.", - "path": "Samples~/DissonanceIntegration" + "path": "Samples~/Dissonance Integration" }, { "displayName": "Example",