diff --git a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/MouseDragger.cs b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/MouseDragger.cs index 6802b65..55cf69a 100644 --- a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/MouseDragger.cs +++ b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/MouseDragger.cs @@ -1,5 +1,3 @@ -using System.Collections; -using System.Collections.Generic; using UnityEngine; using VelNet; @@ -22,13 +20,15 @@ public class MouseDragger : MonoBehaviour { foreach (string draggableTag in draggableTags) { - if (!hit.transform.CompareTag(draggableTag) && !hit.transform.parent?.CompareTag(draggableTag) == true) continue; - NetworkObject netObj = hit.transform.GetComponent(); - netObj ??= hit.transform.GetComponentInParent(); - if (netObj == null) break; - VelNetManager.TakeOwnership(netObj.networkId); - draggingObject = netObj; - break; + if (hit.transform.CompareTag(draggableTag) || hit.transform.parent == null || hit.transform.parent.CompareTag(draggableTag)) + { + NetworkObject netObj = hit.transform.GetComponent(); + netObj ??= hit.transform.GetComponentInParent(); + if (netObj == null) break; + netObj.TakeOwnership(); + draggingObject = netObj; + break; + } } } } diff --git a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/PlayerController.cs b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/PlayerController.cs index 3420680..e0fda99 100644 --- a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/PlayerController.cs +++ b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/PlayerController.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; using UnityEngine; namespace VelNet @@ -36,19 +37,15 @@ namespace VelNet { foreach (KeyValuePair kvp in VelNetManager.instance.objects) { - VelNetManager.TakeOwnership(kvp.Key); + kvp.Value.TakeOwnership(); } } if (Input.GetKeyDown(KeyCode.Backspace)) { - foreach (KeyValuePair kvp in VelNetManager.instance.objects) + foreach (KeyValuePair kvp in VelNetManager.instance.objects.Where(kvp => !kvp.Value.ownershipLocked)) { - // don't destroy player objects - if (!kvp.Value.ownershipLocked) - { - VelNetManager.NetworkDestroy(kvp.Key); - } + VelNetManager.NetworkDestroy(kvp.Key); } } } diff --git a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/TestNetworkedGameObject.prefab b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/TestNetworkedGameObject.prefab index e08d947..f94ea79 100644 --- a/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/TestNetworkedGameObject.prefab +++ b/TestVelGameServer/Assets/Samples/VelNet/1.0.4/Example/TestNetworkedGameObject.prefab @@ -47,6 +47,7 @@ MonoBehaviour: m_EditorClassIdentifier: ownershipLocked: 0 networkId: + sceneNetworkId: 0 prefabName: isSceneObject: 0 syncedComponents: @@ -64,10 +65,8 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: networkObject: {fileID: 3951900052977689805} - serializationRateHz: 30 - targetPosition: {x: 0, y: 0, z: 0} - targetRotation: {x: 0, y: 0, z: 0, w: 0} - smoothness: 0.1 + serializationRateHz: 60 + useLocalTransform: 0 --- !u!1 &8565720276181857625 GameObject: m_ObjectHideFlags: 0 diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/NetworkObject.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/NetworkObject.cs index b08e041..5c605ac 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/NetworkObject.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/NetworkObject.cs @@ -18,13 +18,19 @@ namespace VelNet [Tooltip("Whether this object's ownership is transferrable. Should be true for player objects.")] public bool ownershipLocked; - public bool IsMine => owner != null && owner.isLocal; - + public bool IsMine => owner?.isLocal ?? false; + /// /// 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 is generated at editor time and used to generated the network id at runtime. + /// This is needed because finding all objects of type at runtime doesn't have a guaranteed order. + /// + public int sceneNetworkId; + /// /// This may be empty if it's not a prefab (scene object) /// @@ -50,7 +56,14 @@ namespace VelNet } int index = syncedComponents.IndexOf(component); - owner.SendMessage(this, index.ToString(), message, reliable); + if (index < 0) + { + Debug.LogError("WAAAAAAAH. NetworkObject doesn't have a reference to this component.", component); + } + else + { + owner.SendMessage(this, index.ToString(), message, reliable); + } } public void SendBytesToGroup(NetworkComponent component, string group, byte[] message, bool reliable = true) @@ -116,6 +129,26 @@ namespace VelNet { c.networkObject = t; } + PrefabUtility.RecordPrefabInstancePropertyModifications(t); + } + + // make the sceneNetworkId a new unique value + if (Application.isEditor && !Application.isPlaying && t.isSceneObject && t.sceneNetworkId == 0) + { + // find the first unused value + int[] used = FindObjectsOfType().Select(o => o.sceneNetworkId).ToArray(); + int available = -1; + for (int i = 1; i <= used.Max()+1; i++) + { + if (!used.Contains(i)) + { + available = i; + break; + } + } + + t.sceneNetworkId = available; + PrefabUtility.RecordPrefabInstancePropertyModifications(t); } EditorGUILayout.Space(); diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/BinaryWriterExtensions.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/BinaryWriterExtensions.cs index f122745..f43524d 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/BinaryWriterExtensions.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/BinaryWriterExtensions.cs @@ -1,4 +1,7 @@ -using System.IO; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; using UnityEngine; namespace VelNet @@ -34,5 +37,57 @@ namespace VelNet reader.ReadSingle() ); } + + /// + /// Compresses the list of bools into bytes using a bitmask + /// + public static byte[] GetBitmasks(this IEnumerable bools) + { + List values = bools.ToList(); + List bytes = new List(); + for (int b = 0; b < Mathf.Ceil(values.Count / 8f); b++) + { + byte currentByte = 0; + for (int bit = 0; bit < 8; bit++) + { + if (values.Count > b * 8 + bit) + { + currentByte |= (byte)((values[b * 8 + bit] ? 1 : 0) << bit); + } + } + + bytes.Add(currentByte); + } + + return bytes.ToArray(); + } + + public static List GetBitmaskValues(this IEnumerable bytes) + { + List l = new List(); + foreach (byte b in bytes) + { + l.AddRange(b.GetBitmaskValues()); + } + + return l; + } + + public static List GetBitmaskValues(this byte b) + { + List l = new List(); + for (int i = 0; i < 8; i++) + { + l.Add(b.GetBitmaskValue(i)); + } + + return l; + } + + public static bool GetBitmaskValue(this byte b, int index) + { + return (b & (1 << index)) != 0; + } + } } \ No newline at end of file diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/NetworkSerializedObjectStream.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/NetworkSerializedObjectStream.cs index a6929f8..161791f 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/NetworkSerializedObjectStream.cs @@ -1,4 +1,5 @@ -using System.Collections; +using System; +using System.Collections; using System.IO; using UnityEngine; using UnityEngine.Serialization; @@ -19,12 +20,19 @@ namespace VelNet { while (true) { - if (IsMine) + try { - using MemoryStream mem = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(mem); - SendState(writer); - SendBytes(mem.ToArray()); + if (IsMine) + { + using MemoryStream mem = new MemoryStream(); + using BinaryWriter writer = new BinaryWriter(mem); + SendState(writer); + SendBytes(mem.ToArray()); + } + } + catch (Exception e) + { + Debug.LogError(e); } yield return new WaitForSeconds(1f / serializationRateHz); diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs new file mode 100644 index 0000000..5937ff4 --- /dev/null +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs @@ -0,0 +1,143 @@ +using System.IO; +using UnityEngine; + +namespace VelNet +{ + /// + /// A simple class that will sync the position and rotation of a network object with a rigidbody + /// + [AddComponentMenu("VelNet/VelNet Sync Rigidbody")] + [RequireComponent(typeof(Rigidbody))] + public class SyncRigidbody : NetworkSerializedObjectStream + { + public bool useLocalTransform; + [Tooltip("0 to disable.")] + public float teleportDistance; + [Tooltip("0 to disable.")] + public float teleportAngle; + + public bool syncKinematic = true; + public bool syncGravity = true; + public bool syncVelocity = true; + public bool syncAngularVelocity = true; + + private Vector3 targetPosition; + private Quaternion targetRotation; + private float distanceAtReceiveTime; + private float angleAtReceiveTime; + private Rigidbody rb; + + private void Start() + { + rb = GetComponent(); + if (useLocalTransform) + { + targetPosition = transform.localPosition; + targetRotation = transform.localRotation; + } + else + { + targetPosition = transform.position; + targetRotation = transform.rotation; + } + } + + /// + /// This gets called at serializationRateHz when the object is locally owned + /// + protected override void SendState(BinaryWriter writer) + { + if (useLocalTransform) + { + writer.Write(transform.localPosition); + writer.Write(transform.localRotation); + } + else + { + writer.Write(transform.position); + writer.Write(transform.rotation); + } + + // writer.Write((new bool[] {rb.isKinematic, rb.useGravity}).GetBitmasks()); + if (syncKinematic) writer.Write(rb.isKinematic); + if (syncGravity) writer.Write(rb.useGravity); + if (syncVelocity) writer.Write(rb.velocity); + if (syncAngularVelocity) writer.Write(rb.angularVelocity); + } + + /// + /// This gets called whenever a message about the state of this object is received. + /// Usually at serializationRateHz. + /// + protected override void ReceiveState(BinaryReader reader) + { + targetPosition = reader.ReadVector3(); + targetRotation = reader.ReadQuaternion(); + + if (syncKinematic) rb.isKinematic = reader.ReadBoolean(); + if (syncGravity) rb.useGravity = reader.ReadBoolean(); + if (syncVelocity) rb.velocity = reader.ReadVector3(); + if (syncAngularVelocity) rb.angularVelocity = reader.ReadVector3(); + + // record the distance from the target for interpolation + if (useLocalTransform) + { + distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition); + angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.localPosition = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.localRotation = targetRotation; + } + } + else + { + distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position); + angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.position = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.rotation = targetRotation; + } + } + } + + private void Update() + { + if (IsMine) return; + + if (useLocalTransform) + { + transform.localPosition = Vector3.MoveTowards( + transform.localPosition, + targetPosition, + Time.deltaTime * distanceAtReceiveTime * serializationRateHz + ); + transform.localRotation = Quaternion.RotateTowards( + transform.localRotation, + targetRotation, + Time.deltaTime * angleAtReceiveTime * serializationRateHz + ); + } + else + { + transform.position = Vector3.MoveTowards( + transform.position, + targetPosition, + Time.deltaTime * distanceAtReceiveTime * serializationRateHz + ); + transform.rotation = Quaternion.RotateTowards( + transform.rotation, + targetRotation, + Time.deltaTime * angleAtReceiveTime * serializationRateHz + ); + } + } + } +} \ No newline at end of file diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs.meta b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs.meta new file mode 100644 index 0000000..e516561 --- /dev/null +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncRigidbody.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 951f5c5e2245481d8f969b94f998c78b +timeCreated: 1642738174 \ No newline at end of file diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncTransform.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncTransform.cs index 162a9c9..dddd19f 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncTransform.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/Util/SyncTransform.cs @@ -1,16 +1,19 @@ using System.IO; using UnityEngine; - namespace VelNet { /// /// A simple class that will sync the position and rotation of a network object /// [AddComponentMenu("VelNet/VelNet Sync Transform")] - public class SyncTransform : NetworkSerializedObject + public class SyncTransform : NetworkSerializedObjectStream { public bool useLocalTransform; + [Tooltip("0 to disable.")] + public float teleportDistance; + [Tooltip("0 to disable.")] + public float teleportAngle; private Vector3 targetPosition; private Quaternion targetRotation; @@ -34,28 +37,18 @@ namespace VelNet /// /// This gets called at serializationRateHz when the object is locally owned /// - /// The state of this object to send across the network - protected override byte[] SendState() + protected override void SendState(BinaryWriter writer) { - using MemoryStream mem = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(mem); - writer.Write(transform.localPosition); writer.Write(transform.localRotation); - - return mem.ToArray(); } /// /// This gets called whenever a message about the state of this object is received. /// Usually at serializationRateHz. /// - /// The network state of this object - protected override void ReceiveState(byte[] message) + protected override void ReceiveState(BinaryReader reader) { - using MemoryStream mem = new MemoryStream(message); - using BinaryReader reader = new BinaryReader(mem); - targetPosition = reader.ReadVector3(); targetRotation = reader.ReadQuaternion(); @@ -64,11 +57,27 @@ namespace VelNet { distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.localPosition = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.localRotation = targetRotation; + } } else { distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation); + if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime) + { + transform.position = targetPosition; + } + if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime) + { + transform.rotation = targetRotation; + } } } diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetManager.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetManager.cs index 0e9ae32..31714f7 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetManager.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetManager.cs @@ -42,7 +42,6 @@ namespace VelNet private Thread clientReceiveThread; private Thread clientReceiveThreadUDP; public int userid = -1; - public string room; private int messagesReceived = 0; public readonly Dictionary players = new Dictionary(); @@ -90,8 +89,23 @@ namespace VelNet public readonly Dictionary> groups = new Dictionary>(); private VelNetPlayer masterPlayer; - public static VelNetPlayer LocalPlayer => instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault(); + public static VelNetPlayer LocalPlayer => instance != null ? instance.players.Where(p => p.Value.isLocal).Select(p => p.Value).FirstOrDefault() : null; public static bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != ""; + public static string Room => LocalPlayer?.room; + + /// + /// The player count in this room. + /// -1 if not in a room. + /// + public static int PlayerCount => instance.players.Count; + + /// + /// The player count in all rooms. + /// Will include players connected to the server but not in a room? + /// + public static int PlayerCountInAllRooms => PlayerCount; // TODO hook up to actual player count + + public static bool IsConnected => instance != null && instance.connected && instance.udpConnected; //this is for sending udp packets @@ -396,7 +410,7 @@ namespace VelNet private void OnApplicationQuit() { - socketConnection.Close(); + socketConnection?.Close(); } /// @@ -845,14 +859,26 @@ namespace VelNet public static bool TakeOwnership(string networkId) { // local player must exist - if (LocalPlayer == null) return false; - + if (LocalPlayer == null) + { + Debug.LogError("Can't take ownership. No local player."); + return false; + } + // obj must exist - if (!instance.objects.ContainsKey(networkId)) return false; + if (!instance.objects.ContainsKey(networkId)) + { + Debug.LogError("Can't take ownership. Object with that network id doesn't exist."); + return false; + } // if the ownership is locked, fail - if (instance.objects[networkId].ownershipLocked) return false; - + if (instance.objects[networkId].ownershipLocked) + { + Debug.LogError("Can't take ownership. Ownership for this object is locked."); + return false; + } + // immediately successful instance.objects[networkId].owner = LocalPlayer; @@ -862,4 +888,4 @@ namespace VelNet return true; } } -} \ No newline at end of file +} diff --git a/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetPlayer.cs b/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetPlayer.cs index c31ab89..56bb8ff 100644 --- a/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetPlayer.cs +++ b/TestVelGameServer/Packages/VelNetUnity/Runtime/VelNetPlayer.cs @@ -10,8 +10,6 @@ namespace VelNet public class VelNetPlayer { public int userid; - public string username; - public string room; public bool isLocal; @@ -72,7 +70,7 @@ namespace VelNet switch (sections[0]) { - case "5": //sync update for an object I may own + case "5": // sync update for an object I may own { string objectKey = sections[1]; string identifier = sections[2]; @@ -88,7 +86,7 @@ namespace VelNet break; } - case "6": //I'm trying to take ownership of an object + case "6": // I'm trying to take ownership of an object { string networkId = sections[1]; diff --git a/TestVelGameServer/Packages/VelNetUnity/package.json b/TestVelGameServer/Packages/VelNetUnity/package.json index b846be2..956001f 100644 --- a/TestVelGameServer/Packages/VelNetUnity/package.json +++ b/TestVelGameServer/Packages/VelNetUnity/package.json @@ -1,7 +1,7 @@ { "name": "edu.uga.engr.vel.velnet", "displayName": "VelNet", - "version": "1.0.6", + "version": "1.0.7", "unity": "2019.1", "description": "A custom networking library for Unity.", "keywords": [ @@ -23,4 +23,4 @@ "dependencies": { } } - \ No newline at end of file +