From 796037e74facc3757cd33d32ca97945914bb7cb3 Mon Sep 17 00:00:00 2001 From: Anton Franzluebbers Date: Thu, 20 Jan 2022 23:35:28 -0500 Subject: [PATCH] added syncrigidbody, added more binary extensions, fiexed some errors with scene changes, added a scene network id that is automatically generated and tested for uniqueness in the editor --- Runtime/NetworkObject.cs | 25 +++++ Runtime/Util/BinaryWriterExtensions.cs | 29 ++++- Runtime/Util/SyncRigidbody.cs | 143 +++++++++++++++++++++++++ Runtime/Util/SyncRigidbody.cs.meta | 3 + Runtime/Util/SyncTransform.cs | 37 ++++--- Runtime/VelNetManager.cs | 24 ++++- package.json | 4 +- 7 files changed, 244 insertions(+), 21 deletions(-) create mode 100644 Runtime/Util/SyncRigidbody.cs create mode 100644 Runtime/Util/SyncRigidbody.cs.meta diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index b08e041..94bf3a7 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -25,6 +25,12 @@ namespace VelNet /// 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) /// @@ -118,6 +124,25 @@ namespace VelNet } } + // 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(); DrawDefaultInspector(); diff --git a/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs index f122745..ed7aba5 100644 --- a/Runtime/Util/BinaryWriterExtensions.cs +++ b/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,29 @@ 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(); + } } } \ No newline at end of file diff --git a/Runtime/Util/SyncRigidbody.cs b/Runtime/Util/SyncRigidbody.cs new file mode 100644 index 0000000..3f65511 --- /dev/null +++ b/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; + public bool syncGravity; + public bool syncVelocity; + public bool syncAngularVelocity; + + 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/Runtime/Util/SyncRigidbody.cs.meta b/Runtime/Util/SyncRigidbody.cs.meta new file mode 100644 index 0000000..e516561 --- /dev/null +++ b/Runtime/Util/SyncRigidbody.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 951f5c5e2245481d8f969b94f998c78b +timeCreated: 1642738174 \ No newline at end of file diff --git a/Runtime/Util/SyncTransform.cs b/Runtime/Util/SyncTransform.cs index 162a9c9..dddd19f 100644 --- a/Runtime/Util/SyncTransform.cs +++ b/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/Runtime/VelNetManager.cs b/Runtime/VelNetManager.cs index 71ea086..295df6e 100644 --- a/Runtime/VelNetManager.cs +++ b/Runtime/VelNetManager.cs @@ -316,7 +316,11 @@ namespace VelNet for (int i = 0; i < sceneObjects.Length; i++) { - sceneObjects[i].networkId = -1 + "-" + i; + if (sceneObjects[i].sceneNetworkId == 0) + { + Debug.LogError("Scene Network ID is 0. Make sure to assign one first.", sceneObjects[i]); + } + sceneObjects[i].networkId = -1 + "-" + sceneObjects[i].sceneNetworkId; sceneObjects[i].owner = masterPlayer; sceneObjects[i].isSceneObject = true; // needed for special handling when deleted objects.Add(sceneObjects[i].networkId, sceneObjects[i]); @@ -727,13 +731,25 @@ 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; diff --git a/package.json b/package.json index b846be2..956001f 100644 --- a/package.json +++ b/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 +