pulled changes from main branch

upm
Anton Franzluebbers 2022-01-24 19:18:38 -05:00
parent 9819ccfbf7
commit dc61c63563
9 changed files with 314 additions and 39 deletions

View File

@ -18,13 +18,19 @@ namespace VelNet
[Tooltip("Whether this object's ownership is transferrable. Should be true for player objects.")] [Tooltip("Whether this object's ownership is transferrable. Should be true for player objects.")]
public bool ownershipLocked; public bool ownershipLocked;
public bool IsMine => owner != null && owner.isLocal; public bool IsMine => owner?.isLocal ?? false;
/// <summary> /// <summary>
/// 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 /// 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
/// </summary> /// </summary>
public string networkId; public string networkId;
/// <summary>
/// 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.
/// </summary>
public int sceneNetworkId;
/// <summary> /// <summary>
/// This may be empty if it's not a prefab (scene object) /// This may be empty if it's not a prefab (scene object)
/// </summary> /// </summary>
@ -50,7 +56,14 @@ namespace VelNet
} }
int index = syncedComponents.IndexOf(component); 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) public void SendBytesToGroup(NetworkComponent component, string group, byte[] message, bool reliable = true)
@ -116,6 +129,26 @@ namespace VelNet
{ {
c.networkObject = t; 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<NetworkObject>().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(); EditorGUILayout.Space();

View File

@ -1,4 +1,7 @@
using System.IO; using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine; using UnityEngine;
namespace VelNet namespace VelNet
@ -34,5 +37,57 @@ namespace VelNet
reader.ReadSingle() reader.ReadSingle()
); );
} }
/// <summary>
/// Compresses the list of bools into bytes using a bitmask
/// </summary>
public static byte[] GetBitmasks(this IEnumerable<bool> bools)
{
List<bool> values = bools.ToList();
List<byte> bytes = new List<byte>();
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<bool> GetBitmaskValues(this IEnumerable<byte> bytes)
{
List<bool> l = new List<bool>();
foreach (byte b in bytes)
{
l.AddRange(b.GetBitmaskValues());
}
return l;
}
public static List<bool> GetBitmaskValues(this byte b)
{
List<bool> l = new List<bool>();
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;
}
} }
} }

View File

@ -1,4 +1,5 @@
using System.Collections; using System;
using System.Collections;
using System.IO; using System.IO;
using UnityEngine; using UnityEngine;
using UnityEngine.Serialization; using UnityEngine.Serialization;
@ -19,12 +20,19 @@ namespace VelNet
{ {
while (true) while (true)
{ {
if (IsMine) try
{ {
using MemoryStream mem = new MemoryStream(); if (IsMine)
using BinaryWriter writer = new BinaryWriter(mem); {
SendState(writer); using MemoryStream mem = new MemoryStream();
SendBytes(mem.ToArray()); using BinaryWriter writer = new BinaryWriter(mem);
SendState(writer);
SendBytes(mem.ToArray());
}
}
catch (Exception e)
{
Debug.LogError(e);
} }
yield return new WaitForSeconds(1f / serializationRateHz); yield return new WaitForSeconds(1f / serializationRateHz);

View File

@ -0,0 +1,143 @@
using System.IO;
using UnityEngine;
namespace VelNet
{
/// <summary>
/// A simple class that will sync the position and rotation of a network object with a rigidbody
/// </summary>
[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<Rigidbody>();
if (useLocalTransform)
{
targetPosition = transform.localPosition;
targetRotation = transform.localRotation;
}
else
{
targetPosition = transform.position;
targetRotation = transform.rotation;
}
}
/// <summary>
/// This gets called at serializationRateHz when the object is locally owned
/// </summary>
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);
}
/// <summary>
/// This gets called whenever a message about the state of this object is received.
/// Usually at serializationRateHz.
/// </summary>
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
);
}
}
}
}

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 951f5c5e2245481d8f969b94f998c78b
timeCreated: 1642738174

View File

@ -1,16 +1,19 @@
using System.IO; using System.IO;
using UnityEngine; using UnityEngine;
namespace VelNet namespace VelNet
{ {
/// <summary> /// <summary>
/// A simple class that will sync the position and rotation of a network object /// A simple class that will sync the position and rotation of a network object
/// </summary> /// </summary>
[AddComponentMenu("VelNet/VelNet Sync Transform")] [AddComponentMenu("VelNet/VelNet Sync Transform")]
public class SyncTransform : NetworkSerializedObject public class SyncTransform : NetworkSerializedObjectStream
{ {
public bool useLocalTransform; public bool useLocalTransform;
[Tooltip("0 to disable.")]
public float teleportDistance;
[Tooltip("0 to disable.")]
public float teleportAngle;
private Vector3 targetPosition; private Vector3 targetPosition;
private Quaternion targetRotation; private Quaternion targetRotation;
@ -34,28 +37,18 @@ namespace VelNet
/// <summary> /// <summary>
/// This gets called at serializationRateHz when the object is locally owned /// This gets called at serializationRateHz when the object is locally owned
/// </summary> /// </summary>
/// <returns>The state of this object to send across the network</returns> protected override void SendState(BinaryWriter writer)
protected override byte[] SendState()
{ {
using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write(transform.localPosition); writer.Write(transform.localPosition);
writer.Write(transform.localRotation); writer.Write(transform.localRotation);
return mem.ToArray();
} }
/// <summary> /// <summary>
/// This gets called whenever a message about the state of this object is received. /// This gets called whenever a message about the state of this object is received.
/// Usually at serializationRateHz. /// Usually at serializationRateHz.
/// </summary> /// </summary>
/// <param name="message">The network state of this object</param> protected override void ReceiveState(BinaryReader reader)
protected override void ReceiveState(byte[] message)
{ {
using MemoryStream mem = new MemoryStream(message);
using BinaryReader reader = new BinaryReader(mem);
targetPosition = reader.ReadVector3(); targetPosition = reader.ReadVector3();
targetRotation = reader.ReadQuaternion(); targetRotation = reader.ReadQuaternion();
@ -64,11 +57,27 @@ namespace VelNet
{ {
distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition); distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition);
angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation);
if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime)
{
transform.localPosition = targetPosition;
}
if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime)
{
transform.localRotation = targetRotation;
}
} }
else else
{ {
distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position); distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position);
angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation); angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation);
if (teleportDistance != 0 && teleportDistance < distanceAtReceiveTime)
{
transform.position = targetPosition;
}
if (teleportAngle != 0 && teleportAngle < angleAtReceiveTime)
{
transform.rotation = targetRotation;
}
} }
} }

View File

@ -42,7 +42,6 @@ namespace VelNet
private Thread clientReceiveThread; private Thread clientReceiveThread;
private Thread clientReceiveThreadUDP; private Thread clientReceiveThreadUDP;
public int userid = -1; public int userid = -1;
public string room;
private int messagesReceived = 0; private int messagesReceived = 0;
public readonly Dictionary<int, VelNetPlayer> players = new Dictionary<int, VelNetPlayer>(); public readonly Dictionary<int, VelNetPlayer> players = new Dictionary<int, VelNetPlayer>();
@ -90,8 +89,23 @@ namespace VelNet
public readonly Dictionary<string, List<int>> groups = new Dictionary<string, List<int>>(); public readonly Dictionary<string, List<int>> groups = new Dictionary<string, List<int>>();
private VelNetPlayer masterPlayer; 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 bool InRoom => LocalPlayer != null && LocalPlayer.room != "-1" && LocalPlayer.room != "";
public static string Room => LocalPlayer?.room;
/// <summary>
/// The player count in this room.
/// -1 if not in a room.
/// </summary>
public static int PlayerCount => instance.players.Count;
/// <summary>
/// The player count in all rooms.
/// Will include players connected to the server but not in a room?
/// </summary>
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 //this is for sending udp packets
@ -396,7 +410,7 @@ namespace VelNet
private void OnApplicationQuit() private void OnApplicationQuit()
{ {
socketConnection.Close(); socketConnection?.Close();
} }
/// <summary> /// <summary>
@ -845,14 +859,26 @@ namespace VelNet
public static bool TakeOwnership(string networkId) public static bool TakeOwnership(string networkId)
{ {
// local player must exist // 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 // 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 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 // immediately successful
instance.objects[networkId].owner = LocalPlayer; instance.objects[networkId].owner = LocalPlayer;
@ -862,4 +888,4 @@ namespace VelNet
return true; return true;
} }
} }
} }

View File

@ -10,8 +10,6 @@ namespace VelNet
public class VelNetPlayer public class VelNetPlayer
{ {
public int userid; public int userid;
public string username;
public string room; public string room;
public bool isLocal; public bool isLocal;
@ -72,7 +70,7 @@ namespace VelNet
switch (sections[0]) 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 objectKey = sections[1];
string identifier = sections[2]; string identifier = sections[2];
@ -88,7 +86,7 @@ namespace VelNet
break; 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]; string networkId = sections[1];

View File

@ -1,7 +1,7 @@
{ {
"name": "edu.uga.engr.vel.velnet", "name": "edu.uga.engr.vel.velnet",
"displayName": "VelNet", "displayName": "VelNet",
"version": "1.0.6", "version": "1.0.7",
"unity": "2019.1", "unity": "2019.1",
"description": "A custom networking library for Unity.", "description": "A custom networking library for Unity.",
"keywords": [ "keywords": [
@ -23,4 +23,4 @@
"dependencies": { "dependencies": {
} }
} }