diff --git a/Runtime/NetworkObject.cs b/Runtime/NetworkObject.cs index 6bcb3aa..46d1c5b 100644 --- a/Runtime/NetworkObject.cs +++ b/Runtime/NetworkObject.cs @@ -31,6 +31,7 @@ namespace VelNet /// This way objects can be spawned in a static location /// internal bool instantiatedWithTransform = false; + internal Vector3 initialPosition; internal Quaternion initialRotation; diff --git a/Runtime/Util/NetworkSerializedObject.cs b/Runtime/Util/NetworkSerializedObject.cs index 2614c5e..5bb2183 100644 --- a/Runtime/Util/NetworkSerializedObject.cs +++ b/Runtime/Util/NetworkSerializedObject.cs @@ -4,6 +4,7 @@ using UnityEngine; namespace VelNet { + [Obsolete("Use SyncState instead.")] public abstract class NetworkSerializedObject : NetworkComponent, IPackState { [Tooltip("Send rate of this object. This caps out at the framerate of the game.")] diff --git a/Runtime/Util/NetworkSerializedObjectStream.cs b/Runtime/Util/NetworkSerializedObjectStream.cs index f87a7f9..f372f6d 100644 --- a/Runtime/Util/NetworkSerializedObjectStream.cs +++ b/Runtime/Util/NetworkSerializedObjectStream.cs @@ -1,17 +1,20 @@ using System; using System.Collections; +using System.Collections.Generic; using System.IO; +using System.Linq; using UnityEngine; namespace VelNet { + [Obsolete("Use SyncState instead")] public abstract class NetworkSerializedObjectStream : NetworkComponent, IPackState { [Tooltip("Send rate of this object. This caps out at the framerate of the game.")] public float serializationRateHz = 30; /// - /// If the data hasn't changed, only sends updates across the network at 1Hz + /// If the data hasn't changed, only sends updates across the network at 0.5Hz /// public bool hybridOnChangeCompression = true; @@ -19,7 +22,6 @@ namespace VelNet private double lastSendTime; private const double slowSendInterval = 2; - protected virtual void Awake() { StartCoroutine(SendMessageUpdate()); diff --git a/Runtime/Util/SyncRigidbody.cs b/Runtime/Util/SyncRigidbody.cs index 083ea1b..a7b4e26 100644 --- a/Runtime/Util/SyncRigidbody.cs +++ b/Runtime/Util/SyncRigidbody.cs @@ -10,7 +10,7 @@ namespace VelNet /// [AddComponentMenu("VelNet/VelNet Sync Rigidbody")] [RequireComponent(typeof(Rigidbody))] - public class SyncRigidbody : NetworkSerializedObjectStream + public class SyncRigidbody : SyncState { public bool useLocalTransform; public float minPosDelta = .01f; diff --git a/Runtime/Util/SyncState.cs b/Runtime/Util/SyncState.cs new file mode 100644 index 0000000..3a494c8 --- /dev/null +++ b/Runtime/Util/SyncState.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections; +using System.IO; +using UnityEngine; + +namespace VelNet +{ + public abstract class SyncState : NetworkComponent, IPackState + { + [Tooltip("Send rate of this object. This caps out at the framerate of the game.")] + public float serializationRateHz = 30; + + /// + /// If the data hasn't changed, only sends updates across the network at 0.5Hz + /// + public bool hybridOnChangeCompression = true; + + private byte[] lastSentBytes; + private double lastSendTime; + private const double slowSendInterval = 2; + + private MemoryStream writerMemory; + private BinaryWriter writer; + private MemoryStream readerMemory; + private BinaryReader reader; + + protected virtual void Awake() + { + writerMemory = new MemoryStream(); + writer = new BinaryWriter(writerMemory); + readerMemory = new MemoryStream(); + reader = new BinaryReader(readerMemory); + + StartCoroutine(SendMessageUpdate()); + } + + private IEnumerator SendMessageUpdate() + { + while (true) + { + try + { + if (IsMine && enabled) + { + byte[] newBytes = PackState(); + + if (hybridOnChangeCompression) + { + if (Time.timeAsDouble - lastSendTime > slowSendInterval || + !BinaryWriterExtensions.BytesSame(lastSentBytes, newBytes)) + { + SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; + } + } + else + { + SendBytes(newBytes); + lastSendTime = Time.timeAsDouble; + } + + lastSentBytes = newBytes; + } + } + catch (Exception e) + { + Debug.LogError(e); + } + + yield return new WaitForSeconds(1f / serializationRateHz); + } + // ReSharper disable once IteratorNeverReturns + } + + public override void ReceiveBytes(byte[] message) + { + UnpackState(message); + } + + protected abstract void SendState(BinaryWriter binaryWriter); + + protected abstract void ReceiveState(BinaryReader binaryReader); + + public byte[] PackState() + { + writerMemory.Position = 0; + writerMemory.SetLength(0); + SendState(writer); + return writerMemory.ToArray(); + } + + public void UnpackState(byte[] state) + { + readerMemory.Position = 0; + readerMemory.SetLength(0); + readerMemory.Write(state, 0, state.Length); + readerMemory.Position = 0; + ReceiveState(reader); + } + } +} \ No newline at end of file diff --git a/Runtime/Util/SyncState.cs.meta b/Runtime/Util/SyncState.cs.meta new file mode 100644 index 0000000..1762749 --- /dev/null +++ b/Runtime/Util/SyncState.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91bac26baa2fadb4d86aa2ff05c94a2a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Util/SyncTransform.cs b/Runtime/Util/SyncTransform.cs index 29be722..984a233 100644 --- a/Runtime/Util/SyncTransform.cs +++ b/Runtime/Util/SyncTransform.cs @@ -7,7 +7,7 @@ namespace VelNet /// A simple class that will sync the position and rotation of a network object /// [AddComponentMenu("VelNet/VelNet Sync Transform")] - public class SyncTransform : NetworkSerializedObjectStream + public class SyncTransform : SyncState { [Space] public bool position = true; public bool rotation = true; diff --git a/Runtime/Util/UndoGroup.cs b/Runtime/Util/UndoGroup.cs new file mode 100644 index 0000000..a59d0ba --- /dev/null +++ b/Runtime/Util/UndoGroup.cs @@ -0,0 +1,105 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +#if UNITY_EDITOR +using UnityEditor; +#endif + +namespace VelNet +{ + public class UndoGroup : MonoBehaviour + { + /// + /// This cannot be changed at runtime. + /// + public List objects; + + private readonly List undoBuffer = new List(); + public int maxUndoSteps = 50; + + + /// + /// Reset to the last UndoState. This only takes ownership if the IPackState component is also a NetworkComponent + /// + public void Undo() + { + byte[][] lastStates = undoBuffer.LastOrDefault(); + if (lastStates != null) + { + for (int i = 0; i < objects.Count; i++) + { + objects[i].networkObject.TakeOwnership(); + objects[i].UnpackState(lastStates[i]); + } + } + } + + public void SaveUndoState() + { + byte[][] states = new byte[objects.Count][]; + for (int i = 0; i < objects.Count; i++) + { + states[i] = objects[i].PackState(); + } + + undoBuffer.Add(states); + + while (undoBuffer.Count > maxUndoSteps) + { + undoBuffer.RemoveAt(0); + } + } + + public int UndoHistoryLength() + { + return undoBuffer.Count; + } + } + +#if UNITY_EDITOR + [CustomEditor(typeof(UndoGroup))] + public class UndoGroupEditor : Editor + { + public override void OnInspectorGUI() + { + UndoGroup t = target as UndoGroup; + + EditorGUILayout.Space(); + + if (t == null) return; + + EditorGUILayout.HelpBox("Undo Group. Use SaveUndoState() to make checkpoints.", MessageType.Info); + + EditorGUI.BeginDisabledGroup(true); + EditorGUILayout.TextField("Undo Length: ", t.UndoHistoryLength().ToString("N0")); + EditorGUI.EndDisabledGroup(); + EditorGUILayout.Space(); + + if (EditorApplication.isPlaying && GUILayout.Button("Save undo checkpoint now.")) + { + t.SaveUndoState(); + } + + if (GUILayout.Button("Find all undoable components in children.")) + { + SyncState[] components = t.GetComponentsInChildren(); + SerializedObject so = new SerializedObject(t); + SerializedProperty prop = so.FindProperty("objects"); + prop.ClearArray(); + foreach (SyncState comp in components) + { + prop.InsertArrayElementAtIndex(0); + prop.GetArrayElementAtIndex(0).objectReferenceValue = comp; + } + + so.ApplyModifiedProperties(); + } + + EditorGUILayout.Space(); + + DrawDefaultInspector(); + } + } +#endif +} \ No newline at end of file diff --git a/Runtime/Util/UndoGroup.cs.meta b/Runtime/Util/UndoGroup.cs.meta new file mode 100644 index 0000000..d91ff0b --- /dev/null +++ b/Runtime/Util/UndoGroup.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3ab6cea605a37144a87766745c28fcc4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Samples/DissonanceExample/Scripts/PlayerController.cs b/Samples/DissonanceExample/Scripts/PlayerController.cs index 50d7b24..94516a1 100644 --- a/Samples/DissonanceExample/Scripts/PlayerController.cs +++ b/Samples/DissonanceExample/Scripts/PlayerController.cs @@ -6,7 +6,7 @@ using Random = UnityEngine.Random; namespace VelNet { - public class PlayerController : NetworkSerializedObjectStream + public class PlayerController : SyncState { private Renderer rend; public Color color; diff --git a/Samples/DissonanceExample/Scripts/SyncedTextbox.cs b/Samples/DissonanceExample/Scripts/SyncedTextbox.cs index af83c6d..c8b264b 100644 --- a/Samples/DissonanceExample/Scripts/SyncedTextbox.cs +++ b/Samples/DissonanceExample/Scripts/SyncedTextbox.cs @@ -2,7 +2,7 @@ using System.IO; using UnityEngine.UI; using VelNet; -public class SyncedTextbox : NetworkSerializedObjectStream +public class SyncedTextbox : SyncState { public InputField text; diff --git a/Samples/FullExample/Prefabs/PlayerPrefab.prefab b/Samples/FullExample/Prefabs/PlayerPrefab.prefab index fbc90dc..914aad7 100644 --- a/Samples/FullExample/Prefabs/PlayerPrefab.prefab +++ b/Samples/FullExample/Prefabs/PlayerPrefab.prefab @@ -92,9 +92,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 857495161650682534} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &2597866068570990601 @@ -189,9 +197,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2597866068570990601} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!1 &6139051692386484099 @@ -294,9 +310,17 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6139051692386484099} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} --- !u!114 &9102273340480352682 @@ -357,6 +381,7 @@ MonoBehaviour: serializationRateHz: 30 hybridOnChangeCompression: 1 color: {r: 0, g: 0, b: 0, a: 0} + spawnableObj: {fileID: 419466048389313174, guid: c0c7ee35b5be203468523c819c9da422, type: 3} --- !u!114 &1674689795532972108 MonoBehaviour: m_ObjectHideFlags: 0 @@ -562,8 +587,16 @@ BoxCollider: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 7360746642267561319} m_Material: {fileID: 0} + m_IncludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_ExcludeLayers: + serializedVersion: 2 + m_Bits: 0 + m_LayerOverridePriority: 0 m_IsTrigger: 0 + m_ProvidesContacts: 0 m_Enabled: 1 - serializedVersion: 2 + serializedVersion: 3 m_Size: {x: 1, y: 1, z: 1} m_Center: {x: 0, y: 0, z: 0} diff --git a/Samples/FullExample/Scenes/FullExample.unity b/Samples/FullExample/Scenes/FullExample.unity index 296fc31..25ba695 100644 --- a/Samples/FullExample/Scenes/FullExample.unity +++ b/Samples/FullExample/Scenes/FullExample.unity @@ -104,7 +104,7 @@ NavMeshSettings: serializedVersion: 2 m_ObjectHideFlags: 0 m_BuildSettings: - serializedVersion: 2 + serializedVersion: 3 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 @@ -117,7 +117,7 @@ NavMeshSettings: cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 - accuratePlacement: 0 + buildHeightMesh: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: @@ -585,7 +585,9 @@ Canvas: m_OverrideSorting: 0 m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 m_SortingOrder: 0 m_TargetDisplay: 0 @@ -1727,9 +1729,17 @@ Camera: m_projectionMatrixMode: 1 m_GateFitMode: 2 m_FOVAxisMode: 0 + m_Iso: 200 + m_ShutterSpeed: 0.005 + m_Aperture: 16 + m_FocusDistance: 10 + m_FocalLength: 50 + m_BladeCount: 5 + m_Curvature: {x: 2, y: 11} + m_BarrelClipping: 0.25 + m_Anamorphism: 0 m_SensorSize: {x: 36, y: 24} m_LensShift: {x: 0, y: 0} - m_FocalLength: 50 m_NormalizedViewPortRect: serializedVersion: 2 x: 0 @@ -2589,7 +2599,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 233344de094f11341bdb834d564708dc, type: 3} m_Name: m_EditorClassIdentifier: - host: vn.ugavel.com + host: velnet-demo.ugavel.com port: 5000 udpConnected: 0 userid: -1 diff --git a/Samples/FullExample/Scripts/PlayerController.cs b/Samples/FullExample/Scripts/PlayerController.cs index 530065d..6be3180 100644 --- a/Samples/FullExample/Scripts/PlayerController.cs +++ b/Samples/FullExample/Scripts/PlayerController.cs @@ -6,7 +6,7 @@ using Random = UnityEngine.Random; namespace VelNet { - public class PlayerController : NetworkSerializedObjectStream + public class PlayerController : SyncState { private Renderer rend; public Color color; diff --git a/Samples/FullExample/Scripts/SyncedCustomObj.cs b/Samples/FullExample/Scripts/SyncedCustomObj.cs index c9c43bd..2fb5052 100644 --- a/Samples/FullExample/Scripts/SyncedCustomObj.cs +++ b/Samples/FullExample/Scripts/SyncedCustomObj.cs @@ -4,7 +4,7 @@ using System.IO; using UnityEngine; using VelNet; -public class SyncedCustomObj : NetworkSerializedObjectStream +public class SyncedCustomObj : SyncState { private Renderer rend; private Rigidbody rb; diff --git a/Samples/FullExample/Scripts/SyncedTextbox.cs b/Samples/FullExample/Scripts/SyncedTextbox.cs index af83c6d..c8b264b 100644 --- a/Samples/FullExample/Scripts/SyncedTextbox.cs +++ b/Samples/FullExample/Scripts/SyncedTextbox.cs @@ -2,7 +2,7 @@ using System.IO; using UnityEngine.UI; using VelNet; -public class SyncedTextbox : NetworkSerializedObjectStream +public class SyncedTextbox : SyncState { public InputField text; diff --git a/package.json b/package.json index 9df5034..4923be5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "edu.uga.engr.vel.velnet", "displayName": "VelNet", - "version": "1.1.9", + "version": "1.2.0", "unity": "2019.1", "description": "A custom networking library for Unity.", "keywords": [