diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs
index b08e041..5c605ac 100644
--- a/Runtime/NetworkObject.cs
+++ b/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/Runtime/Util/BinaryWriterExtensions.cs b/Runtime/Util/BinaryWriterExtensions.cs
index f122745..f43524d 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,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/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs
index a6929f8..161791f 100644
--- a/Runtime/Util/NetworkSerializedObjectStream.cs
+++ b/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/Runtime/Util/SyncRigidbody.cs b/Runtime/Util/SyncRigidbody.cs
new file mode 100644
index 0000000..5937ff4
--- /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 = 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/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 0e9ae32..31714f7 100644
--- a/Runtime/VelNetManager.cs
+++ b/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/Runtime/VelNetPlayer.cs b/Runtime/VelNetPlayer.cs
index c31ab89..56bb8ff 100644
--- a/Runtime/VelNetPlayer.cs
+++ b/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/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
+