added mouse dragging script for testing, move networkdestroy and takeownership to static methods in the manager so not everybody needs references to the player, added a custom inspector to NetworkObject for showing owner and a button for automatically finding and assigning networkcomponents, better interpolation for synctransform based on serialization rate, synctransform can send local transforms, find new scene objects on scene load

upm
Anton Franzluebbers 2022-01-11 22:42:57 -05:00
parent 0c0f4af669
commit 7e2fbbe6c6
5 changed files with 172 additions and 37 deletions

View File

@ -1,5 +1,8 @@
using System.Collections.Generic;
using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;
namespace VelNet
@ -9,10 +12,11 @@ namespace VelNet
/// </summary>
public class NetworkObject : MonoBehaviour
{
[Header("NetworkObject properties")]
public VelNetPlayer owner;
[Header("NetworkObject properties")] public VelNetPlayer owner;
[Tooltip("Whether this object's ownership is transferrable. Should be true for player objects.")]
public bool ownershipLocked;
public bool IsMine => owner != null && owner.isLocal;
/// <summary>
@ -61,4 +65,44 @@ namespace VelNet
syncedComponents[int.Parse(identifier)].ReceiveBytes(message);
}
}
#if UNITY_EDITOR
/// <summary>
/// Sets up the interface for the CopyTransform script.
/// </summary>
[CustomEditor(typeof(NetworkObject))]
public class NetworkObjectEditor : Editor
{
public override void OnInspectorGUI()
{
NetworkObject t = target as NetworkObject;
EditorGUILayout.Space();
if (t == null) return;
EditorGUILayout.HelpBox("Network Object. One per prefab pls.\nAssign components to the list to be synced.", MessageType.Info);
EditorGUI.BeginDisabledGroup(true);
EditorGUILayout.Toggle("IsMine", t.IsMine);
EditorGUILayout.TextField("Owner ID", t.owner?.userid.ToString() ?? "No owner");
EditorGUI.EndDisabledGroup();
EditorGUILayout.Space();
if (GUILayout.Button("Find Network Components and add backreferences."))
{
NetworkComponent[] comps = t.GetComponents<NetworkComponent>();
t.syncedComponents = comps.ToList();
foreach (NetworkComponent c in comps)
{
c.networkObject = t;
}
}
EditorGUILayout.Space();
DrawDefaultInspector();
}
}
#endif
}

View File

@ -6,10 +6,10 @@ namespace VelNet
{
public abstract class NetworkSerializedObject : NetworkComponent
{
[FormerlySerializedAs("updateRateHz")] [Tooltip("Send rate of this object")]
[Tooltip("Send rate of this object. This caps out at the framerate of the game.")]
public float serializationRateHz = 30;
private void Start()
private void Awake()
{
StartCoroutine(SendMessageUpdate());
}
@ -23,11 +23,11 @@ namespace VelNet
SendBytes(SendState());
}
yield return new WaitForSeconds(1 / serializationRateHz);
yield return new WaitForSeconds(1f / serializationRateHz);
}
// ReSharper disable once IteratorNeverReturns
}
public override void ReceiveBytes(byte[] message)
{
ReceiveState(message);

View File

@ -10,21 +10,47 @@ namespace VelNet
[AddComponentMenu("VelNet/VelNet Sync Transform")]
public class SyncTransform : NetworkSerializedObject
{
public Vector3 targetPosition;
public Quaternion targetRotation;
public float smoothness = .1f;
public bool useLocalTransform;
private Vector3 targetPosition;
private Quaternion targetRotation;
private float distanceAtReceiveTime;
private float angleAtReceiveTime;
private void Start()
{
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>
/// <returns>The state of this object to send across the network</returns>
protected override byte[] SendState()
{
using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem);
writer.Write(transform.position);
writer.Write(transform.rotation);
writer.Write(transform.localPosition);
writer.Write(transform.localRotation);
return mem.ToArray();
}
/// <summary>
/// This gets called whenever a message about the state of this object is received.
/// Usually at serializationRateHz.
/// </summary>
/// <param name="message">The network state of this object</param>
protected override void ReceiveState(byte[] message)
{
using MemoryStream mem = new MemoryStream(message);
@ -32,13 +58,50 @@ namespace VelNet
targetPosition = reader.ReadVector3();
targetRotation = reader.ReadQuaternion();
// record the distance from the target for interpolation
if (useLocalTransform)
{
distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.localPosition);
angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.localRotation);
}
else
{
distanceAtReceiveTime = Vector3.Distance(targetPosition, transform.position);
angleAtReceiveTime = Quaternion.Angle(targetRotation, transform.rotation);
}
}
private void Update()
{
if (IsMine) return;
transform.position = Vector3.Lerp(transform.position, targetPosition, 1 / smoothness / serializationRateHz);
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 1 / smoothness / serializationRateHz);
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

@ -7,6 +7,7 @@ using System.Text;
using System.Threading;
using UnityEngine;
using System.Net;
using UnityEngine.SceneManagement;
namespace VelNet
{
@ -104,12 +105,17 @@ namespace VelNet
}
instance = this;
SceneManager.sceneLoaded += (scene, mode) =>
{
// add all local network objects
sceneObjects = FindObjectsOfType<NetworkObject>().Where(o=>o.isSceneObject).ToArray();
};
}
private IEnumerator Start()
{
ConnectToTcpServer();
sceneObjects = FindObjectsOfType<NetworkObject>(); //add all local network objects
yield return null;
try
@ -169,6 +175,7 @@ namespace VelNet
// we clear the list, but will recreate as we get messages from people in our room
players.Clear();
masterPlayer = null;
if (m.text != "")
{
@ -200,7 +207,7 @@ namespace VelNet
objects
.Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject)
.Select(o => o.Key)
.ToList().ForEach(DeleteNetworkObject);
.ToList().ForEach(NetworkDestroy);
// empty all the groups
foreach (string group in instance.groups.Keys)
@ -246,7 +253,7 @@ namespace VelNet
// I'm the local master player, so can take ownership immediately
else if (me.isLocal && me == masterPlayer)
{
me.TakeOwnership(kvp.Key);
TakeOwnership(kvp.Key);
}
// the master player left, so everyone should set the owner null (we should get a new master shortly)
else if (players[m.sender] == masterPlayer)
@ -257,7 +264,7 @@ namespace VelNet
}
// TODO this may check for ownership in the future. We don't need ownership here
deleteObjects.ForEach(DeleteNetworkObject);
deleteObjects.ForEach(NetworkDestroy);
players.Remove(m.sender);
}
@ -683,18 +690,43 @@ namespace VelNet
instance.objects.Add(newObject.networkId, newObject);
}
public void DeleteNetworkObject(string networkId)
public static void NetworkDestroy(string networkId)
{
if (!objects.ContainsKey(networkId)) return;
NetworkObject obj = objects[networkId];
if (!instance.objects.ContainsKey(networkId)) return;
NetworkObject obj = instance.objects[networkId];
if (obj == null) return;
if (obj.isSceneObject)
{
deletedSceneObjects.Add(networkId);
instance.deletedSceneObjects.Add(networkId);
}
Destroy(obj.gameObject);
objects.Remove(networkId);
instance.objects.Remove(networkId);
}
/// <summary>
/// Takes local ownership of an object by id.
/// </summary>
/// <param name="networkId">Network ID of the object to transfer</param>
/// <returns>True if successfully transferred, False if transfer message not sent</returns>
public static bool TakeOwnership(string networkId)
{
// local player must exist
if (LocalPlayer == null) return false;
// obj must exist
if (!instance.objects.ContainsKey(networkId)) return false;
// if the ownership is locked, fail
if (instance.objects[networkId].ownershipLocked) return false;
// immediately successful
instance.objects[networkId].owner = LocalPlayer;
// must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome.
SendTo(MessageType.ALL_ORDERED, "6," + networkId);
return true;
}
}
}

View File

@ -113,14 +113,14 @@ namespace VelNet
{
string networkId = sections[1];
manager.DeleteNetworkObject(networkId);
VelNetManager.NetworkDestroy(networkId);
break;
}
case "9": //deleted scene objects
{
for (int k = 1; k < sections.Length; k++)
{
manager.DeleteNetworkObject(sections[k]);
VelNetManager.NetworkDestroy(sections[k]);
}
break;
@ -146,9 +146,12 @@ namespace VelNet
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable);
}
/// <summary>
/// TODO could move this to a static method in VelNetManager
/// </summary>
public void SendSceneUpdate()
{
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects));
}
[Obsolete("Use VelNetManager.NetworkDestroy() instead.")]
public void NetworkDestroy(string networkId)
{
// must be the local owner of the object to destroy it
@ -158,10 +161,8 @@ namespace VelNet
VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "8," + networkId);
}
/// <summary>
/// TODO could move this to a static method in VelNetManager
/// </summary>
/// <returns>True if successful, False if failed to transfer ownership</returns>
[Obsolete("Use VelNetManager.TakeOwnership() instead.")]
public bool TakeOwnership(string networkId)
{
// must exist and be the the local player
@ -169,19 +170,14 @@ namespace VelNet
// if the ownership is locked, fail
if (manager.objects[networkId].ownershipLocked) return false;
// immediately successful
manager.objects[networkId].owner = this;
// must be ordered, so that ownership transfers are not confused. Also sent to all players, so that multiple simultaneous requests will result in the same outcome.
VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId);
return true;
}
public void SendSceneUpdate()
{
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects));
return true;
}
}
}