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

handTracking
Anton Franzluebbers 2022-01-11 22:42:57 -05:00 committed by Brook Bowers
parent e315a7da78
commit 87f7a1a1a9
10 changed files with 259 additions and 50 deletions

View File

@ -0,0 +1,44 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using VelNet;
public class MouseDragger : MonoBehaviour
{
private Camera cam;
public string[] draggableTags = { "draggable" };
private NetworkObject draggingObject;
private void Start()
{
cam = Camera.main;
}
private void Update()
{
if (Input.GetMouseButtonDown(0))
{
if (Physics.Raycast(cam.ScreenPointToRay(Input.mousePosition), out RaycastHit hit))
{
foreach (string draggableTag in draggableTags)
{
if (!hit.transform.CompareTag(draggableTag) && !hit.transform.parent?.CompareTag(draggableTag) == true) continue;
NetworkObject netObj = hit.transform.GetComponent<NetworkObject>();
netObj ??= hit.transform.GetComponentInParent<NetworkObject>();
if (netObj == null) break;
VelNetManager.TakeOwnership(netObj.networkId);
draggingObject = netObj;
break;
}
}
}
else if (Input.GetMouseButtonUp(0))
{
draggingObject = null;
}
else if (Input.GetMouseButton(0) && draggingObject != null)
{
draggingObject.transform.position = cam.ScreenPointToRay(Input.mousePosition).direction * Vector3.Distance(draggingObject.transform.position, cam.transform.position) + cam.transform.position;
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c9d312e1088769143a72b0c13d5aee32
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -36,7 +36,7 @@ namespace VelNet
{ {
foreach (KeyValuePair<string, NetworkObject> kvp in VelNetManager.instance.objects) foreach (KeyValuePair<string, NetworkObject> kvp in VelNetManager.instance.objects)
{ {
Owner.TakeOwnership(kvp.Key); VelNetManager.TakeOwnership(kvp.Key);
} }
} }
@ -47,7 +47,7 @@ namespace VelNet
// don't destroy player objects // don't destroy player objects
if (!kvp.Value.ownershipLocked) if (!kvp.Value.ownershipLocked)
{ {
Owner.NetworkDestroy(kvp.Key); VelNetManager.NetworkDestroy(kvp.Key);
} }
} }
} }

View File

@ -32,7 +32,7 @@ Transform:
m_GameObject: {fileID: 6139051692386484099} m_GameObject: {fileID: 6139051692386484099}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalPosition: {x: 0, y: 0, z: 0}
m_LocalScale: {x: 0.1, y: 0.1, z: 0.1} m_LocalScale: {x: 0.5, y: 0.5, z: 0.5}
m_Children: [] m_Children: []
m_Father: {fileID: 0} m_Father: {fileID: 0}
m_RootOrder: 0 m_RootOrder: 0
@ -117,8 +117,8 @@ MonoBehaviour:
isSceneObject: 0 isSceneObject: 0
syncedComponents: syncedComponents:
- {fileID: -4404668399269848200} - {fileID: -4404668399269848200}
- {fileID: 1181612843795795320}
- {fileID: 7564913803199044469} - {fileID: 7564913803199044469}
- {fileID: 1181612843795795320}
--- !u!114 &-4404668399269848200 --- !u!114 &-4404668399269848200
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0

View File

@ -148,7 +148,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 2034436} m_GameObject: {fileID: 2034436}
m_Enabled: 1 m_Enabled: 0
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name: m_Name:
@ -469,7 +469,7 @@ MonoBehaviour:
m_PrefabInstance: {fileID: 0} m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 162005663} m_GameObject: {fileID: 162005663}
m_Enabled: 1 m_Enabled: 0
m_EditorHideFlags: 0 m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3}
m_Name: m_Name:
@ -851,7 +851,7 @@ MonoBehaviour:
m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_CustomCaretColor: 0 m_CustomCaretColor: 0
m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412}
m_Text: m_Text: TEST USER
m_CaretBlinkRate: 0.85 m_CaretBlinkRate: 0.85
m_CaretWidth: 1 m_CaretWidth: 1
m_ReadOnly: 0 m_ReadOnly: 0
@ -1108,7 +1108,7 @@ MonoBehaviour:
m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1}
m_CustomCaretColor: 0 m_CustomCaretColor: 0
m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412}
m_Text: m_Text: 0
m_CaretBlinkRate: 0.85 m_CaretBlinkRate: 0.85
m_CaretWidth: 1 m_CaretWidth: 1
m_ReadOnly: 0 m_ReadOnly: 0
@ -1241,6 +1241,7 @@ GameObject:
- component: {fileID: 903768657} - component: {fileID: 903768657}
- component: {fileID: 903768656} - component: {fileID: 903768656}
- component: {fileID: 903768655} - component: {fileID: 903768655}
- component: {fileID: 903768654}
m_Layer: 0 m_Layer: 0
m_Name: Main Camera m_Name: Main Camera
m_TagString: MainCamera m_TagString: MainCamera
@ -1248,6 +1249,20 @@ GameObject:
m_NavMeshLayer: 0 m_NavMeshLayer: 0
m_StaticEditorFlags: 0 m_StaticEditorFlags: 0
m_IsActive: 1 m_IsActive: 1
--- !u!114 &903768654
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 903768653}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c9d312e1088769143a72b0c13d5aee32, type: 3}
m_Name:
m_EditorClassIdentifier:
draggableTags:
- TestSphere
--- !u!81 &903768655 --- !u!81 &903768655
AudioListener: AudioListener:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -1834,7 +1849,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: 03a4d4e1a7fd74c7ab2eccca4ce168db, type: 3} m_Script: {fileID: 11500000, guid: 03a4d4e1a7fd74c7ab2eccca4ce168db, type: 3}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
host: 129.159.107.234 host: velnet.ugavel.com
port: 3290 port: 3290
udpConnected: 0 udpConnected: 0
userid: -1 userid: -1
@ -2323,8 +2338,8 @@ MonoBehaviour:
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
_lastPrefabError: _lastPrefabError:
_isMuted: 0 _isMuted: 1
_isDeafened: 0 _isDeafened: 1
_oneMinusBaseRemoteVoiceVolume: 0 _oneMinusBaseRemoteVoiceVolume: 0
_playbackPrefab: {fileID: 0} _playbackPrefab: {fileID: 0}
_playbackPrefab2: {fileID: 1041743830464418, guid: 7b8de751a39894b0c8e1cfc9ef961a5b, type: 3} _playbackPrefab2: {fileID: 1041743830464418, guid: 7b8de751a39894b0c8e1cfc9ef961a5b, type: 3}
@ -2454,7 +2469,7 @@ MonoBehaviour:
m_HorizontalOverflow: 1 m_HorizontalOverflow: 1
m_VerticalOverflow: 0 m_VerticalOverflow: 0
m_LineSpacing: 1 m_LineSpacing: 1
m_Text: m_Text: 0
--- !u!222 &1484033258 --- !u!222 &1484033258
CanvasRenderer: CanvasRenderer:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -2616,7 +2631,7 @@ MonoBehaviour:
m_HorizontalOverflow: 1 m_HorizontalOverflow: 1
m_VerticalOverflow: 0 m_VerticalOverflow: 0
m_LineSpacing: 1 m_LineSpacing: 1
m_Text: m_Text: TEST USER
--- !u!1 &1679565283 --- !u!1 &1679565283
GameObject: GameObject:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
@ -3392,6 +3407,10 @@ PrefabInstance:
m_Modification: m_Modification:
m_TransformParent: {fileID: 0} m_TransformParent: {fileID: 0}
m_Modifications: m_Modifications:
- target: {fileID: 3951900052977689805, guid: 6e4a023f70e01405e8b249a4488fe319, type: 3}
propertyPath: isSceneObject
value: 1
objectReference: {fileID: 0}
- target: {fileID: 8565720275311462453, guid: 6e4a023f70e01405e8b249a4488fe319, type: 3} - target: {fileID: 8565720275311462453, guid: 6e4a023f70e01405e8b249a4488fe319, type: 3}
propertyPath: m_Name propertyPath: m_Name
value: TestNetworkedGameObject value: TestNetworkedGameObject

View File

@ -1,5 +1,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine; using UnityEngine;
namespace VelNet namespace VelNet
@ -9,10 +12,11 @@ namespace VelNet
/// </summary> /// </summary>
public class NetworkObject : MonoBehaviour public class NetworkObject : MonoBehaviour
{ {
[Header("NetworkObject properties")] [Header("NetworkObject properties")] public VelNetPlayer owner;
public VelNetPlayer owner;
[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 != null && owner.isLocal;
/// <summary> /// <summary>
@ -68,4 +72,44 @@ namespace VelNet
syncedComponents[int.Parse(identifier)].ReceiveBytes(message); 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 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; public float serializationRateHz = 30;
private void Start() private void Awake()
{ {
StartCoroutine(SendMessageUpdate()); StartCoroutine(SendMessageUpdate());
} }
@ -23,11 +23,11 @@ namespace VelNet
SendBytes(SendState()); SendBytes(SendState());
} }
yield return new WaitForSeconds(1 / serializationRateHz); yield return new WaitForSeconds(1f / serializationRateHz);
} }
// ReSharper disable once IteratorNeverReturns // ReSharper disable once IteratorNeverReturns
} }
public override void ReceiveBytes(byte[] message) public override void ReceiveBytes(byte[] message)
{ {
ReceiveState(message); ReceiveState(message);

View File

@ -10,21 +10,47 @@ namespace VelNet
[AddComponentMenu("VelNet/VelNet Sync Transform")] [AddComponentMenu("VelNet/VelNet Sync Transform")]
public class SyncTransform : NetworkSerializedObject public class SyncTransform : NetworkSerializedObject
{ {
public Vector3 targetPosition; public bool useLocalTransform;
public Quaternion targetRotation;
public float smoothness = .1f;
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() protected override byte[] SendState()
{ {
using MemoryStream mem = new MemoryStream(); using MemoryStream mem = new MemoryStream();
using BinaryWriter writer = new BinaryWriter(mem); using BinaryWriter writer = new BinaryWriter(mem);
writer.Write(transform.position); writer.Write(transform.localPosition);
writer.Write(transform.rotation); writer.Write(transform.localRotation);
return mem.ToArray(); 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) protected override void ReceiveState(byte[] message)
{ {
using MemoryStream mem = new MemoryStream(message); using MemoryStream mem = new MemoryStream(message);
@ -32,13 +58,50 @@ namespace VelNet
targetPosition = reader.ReadVector3(); targetPosition = reader.ReadVector3();
targetRotation = reader.ReadQuaternion(); 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() private void Update()
{ {
if (IsMine) return; 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 System.Threading;
using UnityEngine; using UnityEngine;
using System.Net; using System.Net;
using UnityEngine.SceneManagement;
namespace VelNet namespace VelNet
{ {
@ -104,12 +105,17 @@ namespace VelNet
} }
instance = this; instance = this;
SceneManager.sceneLoaded += (scene, mode) =>
{
// add all local network objects
sceneObjects = FindObjectsOfType<NetworkObject>().Where(o=>o.isSceneObject).ToArray();
};
} }
private IEnumerator Start() private IEnumerator Start()
{ {
ConnectToTcpServer(); ConnectToTcpServer();
sceneObjects = FindObjectsOfType<NetworkObject>(); //add all local network objects
yield return null; yield return null;
try try
@ -169,6 +175,7 @@ namespace VelNet
// we clear the list, but will recreate as we get messages from people in our room // we clear the list, but will recreate as we get messages from people in our room
players.Clear(); players.Clear();
masterPlayer = null;
if (m.text != "") if (m.text != "")
{ {
@ -200,7 +207,7 @@ namespace VelNet
objects objects
.Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject) .Where(kvp => kvp.Value == null || !kvp.Value.isSceneObject)
.Select(o => o.Key) .Select(o => o.Key)
.ToList().ForEach(DeleteNetworkObject); .ToList().ForEach(NetworkDestroy);
// empty all the groups // empty all the groups
foreach (string group in instance.groups.Keys) foreach (string group in instance.groups.Keys)
@ -246,7 +253,7 @@ namespace VelNet
// I'm the local master player, so can take ownership immediately // I'm the local master player, so can take ownership immediately
else if (me.isLocal && me == masterPlayer) 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) // the master player left, so everyone should set the owner null (we should get a new master shortly)
else if (players[m.sender] == masterPlayer) 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 // 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); players.Remove(m.sender);
} }
@ -684,18 +691,43 @@ namespace VelNet
instance.objects.Add(newObject.networkId, newObject); instance.objects.Add(newObject.networkId, newObject);
} }
public void DeleteNetworkObject(string networkId) public static void NetworkDestroy(string networkId)
{ {
if (!objects.ContainsKey(networkId)) return; if (!instance.objects.ContainsKey(networkId)) return;
NetworkObject obj = objects[networkId]; NetworkObject obj = instance.objects[networkId];
if (obj == null) return; if (obj == null) return;
if (obj.isSceneObject) if (obj.isSceneObject)
{ {
deletedSceneObjects.Add(networkId); instance.deletedSceneObjects.Add(networkId);
} }
Destroy(obj.gameObject); 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]; string networkId = sections[1];
manager.DeleteNetworkObject(networkId); VelNetManager.NetworkDestroy(networkId);
break; break;
} }
case "9": //deleted scene objects case "9": //deleted scene objects
{ {
for (int k = 1; k < sections.Length; k++) for (int k = 1; k < sections.Length; k++)
{ {
manager.DeleteNetworkObject(sections[k]); VelNetManager.NetworkDestroy(sections[k]);
} }
break; break;
@ -146,9 +146,12 @@ namespace VelNet
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable); VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "5," + obj.networkId + "," + identifier + "," + Convert.ToBase64String(data), reliable);
} }
/// <summary> public void SendSceneUpdate()
/// TODO could move this to a static method in VelNetManager {
/// </summary> VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects));
}
[Obsolete("Use VelNetManager.NetworkDestroy() instead.")]
public void NetworkDestroy(string networkId) public void NetworkDestroy(string networkId)
{ {
// must be the local owner of the object to destroy it // 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); 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> /// <returns>True if successful, False if failed to transfer ownership</returns>
[Obsolete("Use VelNetManager.TakeOwnership() instead.")]
public bool TakeOwnership(string networkId) public bool TakeOwnership(string networkId)
{ {
// must exist and be the the local player // must exist and be the the local player
@ -169,19 +170,14 @@ namespace VelNet
// if the ownership is locked, fail // if the ownership is locked, fail
if (manager.objects[networkId].ownershipLocked) return false; if (manager.objects[networkId].ownershipLocked) return false;
// immediately successful // immediately successful
manager.objects[networkId].owner = this; 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. // 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); VelNetManager.SendTo(VelNetManager.MessageType.ALL_ORDERED, "6," + networkId);
return true;
}
public void SendSceneUpdate() return true;
{
VelNetManager.SendTo(VelNetManager.MessageType.OTHERS, "9," + string.Join(",", manager.deletedSceneObjects));
} }
} }
} }