当前提交
506f0246
共有 485 个文件被更改,包括 5093 次插入 和 21 次删除
-
101Assets/Scenes/mainScene.unity
-
3Assets/Scripts/LobbyRelaySample.asmdef
-
37Packages/packages-lock.json
-
6ProjectSettings/ProjectSettings.asset
-
44Assets/Scripts/Relay/RelayNetcodeHost.cs
-
11Assets/Scripts/Relay/RelayNetcodeHost.cs.meta
-
5Packages/com.unity.multiplayer.transport.utp/CHANGELOG.md
-
7Packages/com.unity.multiplayer.transport.utp/CHANGELOG.md.meta
-
5Packages/com.unity.multiplayer.transport.utp/Documentation~/Manual.md
-
9Packages/com.unity.multiplayer.transport.utp/LICENSE.md
-
7Packages/com.unity.multiplayer.transport.utp/LICENSE.md.meta
-
3Packages/com.unity.multiplayer.transport.utp/README.md
-
7Packages/com.unity.multiplayer.transport.utp/README.md.meta
-
8Packages/com.unity.multiplayer.transport.utp/Runtime.meta
-
467Packages/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs
-
11Packages/com.unity.multiplayer.transport.utp/Runtime/UTPTransport.cs.meta
-
64Packages/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs
-
11Packages/com.unity.multiplayer.transport.utp/Runtime/Utilities.cs.meta
-
11Packages/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef
-
7Packages/com.unity.multiplayer.transport.utp/Runtime/com.unity.multiplayer.transport.utp.asmdef.meta
-
8Packages/com.unity.multiplayer.transport.utp/Tests.meta
-
8Packages/com.unity.multiplayer.transport.utp/Tests/Editor.meta
-
20Packages/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs
-
11Packages/com.unity.multiplayer.transport.utp/Tests/Editor/BasicUTPTest.cs.meta
-
15Packages/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef
-
7Packages/com.unity.multiplayer.transport.utp/Tests/Editor/com.unity.multiplayer.transport.utp.editortests.asmdef.meta
-
8Packages/com.unity.multiplayer.transport.utp/Tests/Runtime.meta
-
26Packages/com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs
-
11Packages/com.unity.multiplayer.transport.utp/Tests/Runtime/DummyTestScript.cs.meta
-
14Packages/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef
-
7Packages/com.unity.multiplayer.transport.utp/Tests/Runtime/com.unity.multiplayer.transport.utp.runtimetests.asmdef.meta
-
12Packages/com.unity.multiplayer.transport.utp/package.json
-
7Packages/com.unity.multiplayer.transport.utp/package.json.meta
-
120Packages/com.unity.netcode.gameobjects/CHANGELOG.md
-
7Packages/com.unity.netcode.gameobjects/CHANGELOG.md.meta
-
30Packages/com.unity.netcode.gameobjects/Documentation~/Manual.md
-
8Packages/com.unity.netcode.gameobjects/Editor.meta
-
8Packages/com.unity.netcode.gameobjects/Editor/CodeGen.meta
-
290Packages/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs.meta
-
1001Packages/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs.meta
-
137Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorAssemblyResolver.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorAssemblyResolver.cs.meta
-
22Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorReflectionImporter.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorReflectionImporter.cs.meta
-
12Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorReflectionImporterProvider.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/PostProcessorReflectionImporterProvider.cs.meta
-
130Packages/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs.meta
-
19Packages/com.unity.netcode.gameobjects/Editor/CodeGen/com.unity.netcode.editor.codegen.asmdef
-
7Packages/com.unity.netcode.gameobjects/Editor/CodeGen/com.unity.netcode.editor.codegen.asmdef.meta
-
8Packages/com.unity.netcode.gameobjects/Editor/DontShowInTransportDropdownAttribute.cs
-
3Packages/com.unity.netcode.gameobjects/Editor/DontShowInTransportDropdownAttribute.cs.meta
-
211Packages/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs.meta
-
371Packages/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/NetworkManagerEditor.cs.meta
-
104Packages/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs.meta
-
118Packages/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs
-
11Packages/com.unity.netcode.gameobjects/Editor/NetworkTransformEditor.cs.meta
-
11Packages/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef
-
7Packages/com.unity.netcode.gameobjects/Editor/com.unity.netcode.editor.asmdef.meta
-
9Packages/com.unity.netcode.gameobjects/LICENSE.md
-
7Packages/com.unity.netcode.gameobjects/LICENSE.md.meta
-
8Packages/com.unity.netcode.gameobjects/Prototyping.meta
-
10Packages/com.unity.netcode.gameobjects/Prototyping/AssemblyInfo.cs
-
11Packages/com.unity.netcode.gameobjects/Prototyping/AssemblyInfo.cs.meta
-
560Packages/com.unity.netcode.gameobjects/Prototyping/NetworkAnimator.cs
-
11Packages/com.unity.netcode.gameobjects/Prototyping/NetworkAnimator.cs.meta
-
119Packages/com.unity.netcode.gameobjects/Prototyping/NetworkNavMeshAgent.cs
-
11Packages/com.unity.netcode.gameobjects/Prototyping/NetworkNavMeshAgent.cs.meta
-
407Packages/com.unity.netcode.gameobjects/Prototyping/NetworkTransform.cs
-
11Packages/com.unity.netcode.gameobjects/Prototyping/NetworkTransform.cs.meta
-
7Packages/com.unity.netcode.gameobjects/Prototyping/com.unity.netcode.prototyping.asmdef
-
7Packages/com.unity.netcode.gameobjects/Prototyping/com.unity.netcode.prototyping.asmdef.meta
-
84Packages/com.unity.netcode.gameobjects/README.md
-
7Packages/com.unity.netcode.gameobjects/README.md.meta
-
8Packages/com.unity.netcode.gameobjects/Runtime.meta
-
10Packages/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs
-
11Packages/com.unity.netcode.gameobjects/Runtime/AssemblyInfo.cs.meta
-
8Packages/com.unity.netcode.gameobjects/Runtime/Collections.meta
-
77Packages/com.unity.netcode.gameobjects/Runtime/Collections/FixedQueue.cs
-
11Packages/com.unity.netcode.gameobjects/Runtime/Collections/FixedQueue.cs.meta
-
8Packages/com.unity.netcode.gameobjects/Runtime/Configuration.meta
|
|||
using System; |
|||
using System.Collections; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using LobbyRelaySample; |
|||
using Unity.Netcode; |
|||
using UnityEngine; |
|||
|
|||
public class AsyncNetworkRequest : AsyncRequest |
|||
{ |
|||
private static AsyncNetworkRequest s_instance; |
|||
|
|||
public static AsyncNetworkRequest Instance |
|||
{ |
|||
get |
|||
{ |
|||
if (s_instance == null) |
|||
s_instance = new AsyncNetworkRequest(); |
|||
return s_instance; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The Relay service will wrap HTTP errors in RelayServiceExceptions. We can filter on RelayServiceException.Reason for custom behavior.
|
|||
/// </summary>
|
|||
protected override void ParseServiceException(Exception e) |
|||
{ |
|||
throw e; |
|||
} |
|||
} |
|||
|
|||
public class RelayNetcodeHost : NetworkBehaviour |
|||
{ |
|||
async void Start() |
|||
{ |
|||
var hostSocket = NetworkManager.Singleton.StartHost(); |
|||
while (!hostSocket.IsDone) |
|||
{ |
|||
await Task.Yield(); |
|||
} |
|||
} |
|||
|
|||
|
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: cc7678c1f9b7b9c4fac8bac6324007de |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# Changelog |
|||
All notable changes to this package will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) |
|||
|
|||
## [0.0.1-preview.1] - 2020-12-20 |
|||
This is the first release of Unity MLAPI Package |
|
|||
fileFormatVersion: 2 |
|||
guid: c482292884eb2a14f8d59b4a5c55c62e |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# **Unity Transport for Netcode for GameObjects Manual** |
|||
|
|||
# Getting Started |
|||
|
|||
todo @andrews-unity - fill this in |
|
|||
MIT License |
|||
|
|||
Copyright (c) 2021 Unity Technologies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|||
fileFormatVersion: 2 |
|||
guid: 5501977e9ab93ad469220f23990177cc |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
com.unity.transport transport for Netcode for GameObjects |
|||
|
|||
WIP: This is not a functional transport just a work in progress and should not be used as any measure of current state a very early proof of concept and place holder package. |
|
|||
fileFormatVersion: 2 |
|||
guid: 8dcab9a16b9388445a77d3b927b242cf |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: d6198c048d7fde84e837ad7b5ae231d9 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Runtime.InteropServices; |
|||
|
|||
using Unity.Netcode; |
|||
|
|||
using Unity.Burst; |
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
using Unity.Jobs; |
|||
using Unity.Networking.Transport; |
|||
|
|||
using UnityEngine; |
|||
using UnityEngine.Assertions; |
|||
|
|||
using NetworkEvent = Unity.Networking.Transport.NetworkEvent; |
|||
using NetcodeEvent = Unity.Netcode.NetworkEvent; |
|||
|
|||
[StructLayout(LayoutKind.Explicit)] |
|||
public unsafe struct RawNetworkMessage |
|||
{ |
|||
[FieldOffset(0)] public int Length; |
|||
[FieldOffset(4)] public uint Type; |
|||
[FieldOffset(8)] public int Id; |
|||
[FieldOffset(12)] public byte Padding; |
|||
[FieldOffset(13)] public byte ChannelId; |
|||
[FieldOffset(14)] public fixed byte Data[NetworkParameterConstants.MTU]; |
|||
} |
|||
|
|||
[BurstCompile] |
|||
internal struct ClientUpdateJob : IJob |
|||
{ |
|||
public NetworkDriver Driver; |
|||
public NativeArray<NetworkConnection> Connection; |
|||
public NativeQueue<RawNetworkMessage> PacketData; |
|||
|
|||
unsafe public void Execute() |
|||
{ |
|||
if (!Connection[0].IsCreated) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
DataStreamReader streamReader; |
|||
NetworkEvent.Type cmd; |
|||
|
|||
while ((cmd = Connection[0].PopEvent(Driver, out streamReader)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
if (cmd == NetworkEvent.Type.Connect) |
|||
{ |
|||
var d = new RawNetworkMessage() { Length = 0, Type = (uint)NetcodeEvent.Connect, Id = Connection[0].InternalId }; |
|||
PacketData.Enqueue(d); |
|||
} |
|||
else if (cmd == NetworkEvent.Type.Data) |
|||
{ |
|||
byte channelId = streamReader.ReadByte(); |
|||
int messageSize = streamReader.ReadInt(); |
|||
|
|||
var temp = new NativeArray<byte>(messageSize, Allocator.Temp); |
|||
streamReader.ReadBytes(temp); |
|||
|
|||
var d = new RawNetworkMessage() |
|||
{ |
|||
Length = messageSize, |
|||
Type = (uint)NetcodeEvent.Data, |
|||
Id = Connection[0].InternalId, |
|||
ChannelId = channelId |
|||
}; |
|||
|
|||
UnsafeUtility.MemCpy(d.Data, temp.GetUnsafePtr(), d.Length); |
|||
|
|||
PacketData.Enqueue(d); |
|||
} |
|||
else if (cmd == NetworkEvent.Type.Disconnect) |
|||
{ |
|||
Connection[0] = default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[BurstCompile] |
|||
internal struct ServerUpdateJob : IJobParallelForDefer |
|||
{ |
|||
public NetworkDriver.Concurrent Driver; |
|||
public NativeArray<NetworkConnection> Connections; |
|||
public NativeQueue<RawNetworkMessage>.ParallelWriter PacketData; |
|||
|
|||
private unsafe void QueueMessage(ref DataStreamReader streamReader, int index) |
|||
{ |
|||
byte channelId = streamReader.ReadByte(); |
|||
int messageSize = streamReader.ReadInt(); |
|||
|
|||
var temp = new NativeArray<byte>(messageSize, Allocator.Temp); |
|||
streamReader.ReadBytes(temp); |
|||
|
|||
// Debug.Log($"Server: Got a message {channelId} {messageSize} ");
|
|||
|
|||
var d = new RawNetworkMessage() |
|||
{ |
|||
Length = messageSize, |
|||
Type = (uint)NetcodeEvent.Data, |
|||
Id = index, |
|||
ChannelId = channelId |
|||
}; |
|||
|
|||
UnsafeUtility.MemCpy(d.Data, temp.GetUnsafePtr(), d.Length); |
|||
PacketData.Enqueue(d); |
|||
} |
|||
|
|||
public unsafe void Execute(int index) |
|||
{ |
|||
DataStreamReader streamReader; |
|||
Assert.IsTrue(Connections[index].IsCreated); |
|||
|
|||
NetworkEvent.Type command; |
|||
while ((command = Driver.PopEventForConnection(Connections[index], out streamReader)) != NetworkEvent.Type.Empty) |
|||
{ |
|||
if (command == NetworkEvent.Type.Data) |
|||
{ |
|||
QueueMessage(ref streamReader, index); |
|||
} |
|||
else if (command == NetworkEvent.Type.Connect) |
|||
{ |
|||
var d = new RawNetworkMessage() { Length = 0, Type = (uint)NetcodeEvent.Connect, Id = index }; |
|||
PacketData.Enqueue(d); |
|||
} |
|||
else if (command == NetworkEvent.Type.Disconnect) |
|||
{ |
|||
var d = new RawNetworkMessage() { Length = 0, Type = (uint)NetcodeEvent.Disconnect, Id = index }; |
|||
PacketData.Enqueue(d); |
|||
Connections[index] = default; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
[BurstCompile] |
|||
internal struct ServerUpdateConnectionsJob : IJob |
|||
{ |
|||
public NetworkDriver Driver; |
|||
public NativeList<NetworkConnection> Connections; |
|||
public NativeQueue<RawNetworkMessage>.ParallelWriter PacketData; |
|||
|
|||
public void Execute() |
|||
{ |
|||
// Clean up connections
|
|||
for (int i = 0; i < Connections.Length; i++) |
|||
{ |
|||
if (!Connections[i].IsCreated) |
|||
{ |
|||
Connections.RemoveAtSwapBack(i); |
|||
--i; |
|||
} |
|||
} |
|||
// Accept new connections
|
|||
NetworkConnection c; |
|||
while ((c = Driver.Accept()) != default(NetworkConnection)) |
|||
{ |
|||
Connections.Add(c); |
|||
var d = new RawNetworkMessage() { Length = 0, Type = (uint)NetcodeEvent.Connect, Id = c.InternalId }; |
|||
PacketData.Enqueue(d); |
|||
Debug.Log("Accepted a connection"); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class UTPTransport : NetworkTransport |
|||
{ |
|||
public ushort Port = 7777; |
|||
public string Address = "127.0.0.1"; |
|||
|
|||
[Serializable] |
|||
public struct UTPChannel |
|||
{ |
|||
[HideInInspector] |
|||
public byte Id; |
|||
public string Name; |
|||
public UTPDelivery Flags; |
|||
} |
|||
|
|||
public enum UTPDelivery |
|||
{ |
|||
UnreliableSequenced, |
|||
ReliableSequenced, |
|||
Unreliable |
|||
} |
|||
|
|||
public NetworkDriver Driver; |
|||
public NativeList<NetworkConnection> Connections; |
|||
public NativeQueue<RawNetworkMessage> PacketData; |
|||
private NativeArray<byte> m_PacketProcessBuffer; |
|||
|
|||
private JobHandle m_JobHandle; |
|||
|
|||
private bool m_IsClient = false; |
|||
private bool m_IsServer = false; |
|||
|
|||
|
|||
public override ulong ServerClientId => 0; |
|||
|
|||
public override void DisconnectLocalClient() { _ = Driver.Disconnect(Connections[0]); } |
|||
public override void DisconnectRemoteClient(ulong clientId) |
|||
{ |
|||
GetUTPConnectionDetails(clientId, out uint peerId); |
|||
var con = GetConnection(peerId); |
|||
if (con != default) |
|||
{ |
|||
Driver.Disconnect(con); |
|||
} |
|||
} |
|||
|
|||
private NetworkConnection GetConnection(uint id) |
|||
{ |
|||
foreach (var item in Connections) |
|||
{ |
|||
if (item.InternalId == id) |
|||
{ |
|||
return item; |
|||
} |
|||
} |
|||
|
|||
return default; |
|||
} |
|||
|
|||
private NetworkPipeline[] m_NetworkPipelines = new NetworkPipeline[3]; |
|||
|
|||
public override void Init() |
|||
{ |
|||
Driver = NetworkDriver.Create(); |
|||
|
|||
// So we have a bunch of different pipelines we can send :D
|
|||
m_NetworkPipelines[0] = Driver.CreatePipeline(typeof(NullPipelineStage)); |
|||
m_NetworkPipelines[1] = Driver.CreatePipeline(typeof(ReliableSequencedPipelineStage)); |
|||
m_NetworkPipelines[2] = Driver.CreatePipeline(typeof(UnreliableSequencedPipelineStage)); |
|||
|
|||
PacketData = new NativeQueue<RawNetworkMessage>(Allocator.Persistent); |
|||
m_PacketProcessBuffer = new NativeArray<byte>(1000, Allocator.Persistent); |
|||
} |
|||
|
|||
[BurstCompile] |
|||
public void SendToClient(NativeArray<byte> packet, ulong clientId, int index) |
|||
{ |
|||
for (int i = 0; i < Connections.Length; i++) |
|||
{ |
|||
if (Connections[i].InternalId != (int)clientId) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
var writer = Driver.BeginSend(m_NetworkPipelines[index], Connections[i]); |
|||
|
|||
if (!writer.IsCreated) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
writer.WriteBytes(packet); |
|||
|
|||
Driver.EndSend(writer); |
|||
} |
|||
} |
|||
|
|||
public override unsafe void Send(ulong clientId, ArraySegment<byte> data, NetworkChannel networkChannel) |
|||
{ |
|||
var pipelineIndex = 0; |
|||
|
|||
GetUTPConnectionDetails(clientId, out uint peerId); |
|||
|
|||
var writer = new DataStreamWriter(data.Count + 1 + 4, Allocator.Temp); |
|||
writer.WriteByte((byte)networkChannel); |
|||
writer.WriteInt(data.Count); |
|||
|
|||
fixed (byte* dataArrayPtr = data.Array) |
|||
{ |
|||
writer.WriteBytes(dataArrayPtr, data.Count); |
|||
} |
|||
|
|||
SendToClient(writer.AsNativeArray(), peerId, pipelineIndex); |
|||
} |
|||
|
|||
public override NetcodeEvent PollEvent(out ulong clientId, out NetworkChannel networkChannel, out ArraySegment<byte> payload, out float receiveTime) |
|||
{ |
|||
clientId = 0; |
|||
networkChannel = NetworkChannel.ChannelUnused; |
|||
|
|||
payload = new ArraySegment<byte>(Array.Empty<byte>()); |
|||
receiveTime = 0; |
|||
|
|||
return NetcodeEvent.Nothing; |
|||
} |
|||
|
|||
public override ulong GetCurrentRtt(ulong clientId) => 0; |
|||
|
|||
private void Update() |
|||
{ |
|||
if (m_IsServer || m_IsClient) |
|||
{ |
|||
RawNetworkMessage message; |
|||
while (PacketData.TryDequeue(out message)) |
|||
{ |
|||
var data = m_PacketProcessBuffer.Slice(0, message.Length); |
|||
unsafe |
|||
{ |
|||
UnsafeUtility.MemClear(data.GetUnsafePtr(), message.Length); |
|||
UnsafeUtility.MemCpy(data.GetUnsafePtr(), message.Data, message.Length); |
|||
} |
|||
var clientId = GetNetcodeClientId((uint)message.Id, false); |
|||
|
|||
switch ((NetcodeEvent)message.Type) |
|||
{ |
|||
case NetcodeEvent.Data: |
|||
int size = message.Length; |
|||
byte[] arr = new byte[size]; |
|||
unsafe |
|||
{ |
|||
Marshal.Copy((IntPtr)message.Data, arr, 0, size); |
|||
var payload = new ArraySegment<byte>(arr); |
|||
InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, (NetworkChannel)message.ChannelId, payload, Time.realtimeSinceStartup); |
|||
} |
|||
|
|||
break; |
|||
case NetcodeEvent.Connect: |
|||
{ |
|||
InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, NetworkChannel.ChannelUnused, new ArraySegment<byte>(), Time.realtimeSinceStartup); |
|||
} |
|||
break; |
|||
case NetcodeEvent.Disconnect: |
|||
InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, NetworkChannel.ChannelUnused, new ArraySegment<byte>(), Time.realtimeSinceStartup); |
|||
break; |
|||
case NetcodeEvent.Nothing: |
|||
InvokeOnTransportEvent((NetcodeEvent)message.Type, clientId, NetworkChannel.ChannelUnused, new ArraySegment<byte>(), Time.realtimeSinceStartup); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
|
|||
if (m_JobHandle.IsCompleted) |
|||
{ |
|||
|
|||
if (m_IsServer) |
|||
{ |
|||
var connectionJob = new ServerUpdateConnectionsJob |
|||
{ |
|||
Driver = Driver, |
|||
Connections = Connections, |
|||
PacketData = PacketData.AsParallelWriter() |
|||
|
|||
}; |
|||
|
|||
var serverUpdateJob = new ServerUpdateJob |
|||
{ |
|||
Driver = Driver.ToConcurrent(), |
|||
Connections = Connections.AsDeferredJobArray(), |
|||
PacketData = PacketData.AsParallelWriter() |
|||
}; |
|||
|
|||
m_JobHandle = Driver.ScheduleUpdate(); |
|||
m_JobHandle = connectionJob.Schedule(m_JobHandle); |
|||
m_JobHandle = serverUpdateJob.Schedule(Connections, 1, m_JobHandle); |
|||
} |
|||
|
|||
if (m_IsClient) |
|||
{ |
|||
var job = new ClientUpdateJob |
|||
{ |
|||
Driver = Driver, |
|||
Connection = Connections, |
|||
PacketData = PacketData |
|||
}; |
|||
m_JobHandle = Driver.ScheduleUpdate(); |
|||
m_JobHandle = job.Schedule(m_JobHandle); |
|||
} |
|||
} |
|||
|
|||
m_JobHandle.Complete(); |
|||
} |
|||
} |
|||
|
|||
public override void Shutdown() |
|||
{ |
|||
m_JobHandle.Complete(); |
|||
|
|||
if (PacketData.IsCreated) |
|||
{ |
|||
PacketData.Dispose(); |
|||
} |
|||
|
|||
if (Connections.IsCreated) |
|||
{ |
|||
Connections.Dispose(); |
|||
} |
|||
|
|||
Driver.Dispose(); |
|||
m_PacketProcessBuffer.Dispose(); |
|||
} |
|||
|
|||
// This is kind of a mess!
|
|||
public override SocketTasks StartClient() |
|||
{ |
|||
Connections = new NativeList<NetworkConnection>(1, Allocator.Persistent); |
|||
var endpoint = NetworkEndPoint.Parse(Address, Port); |
|||
Connections.Add(Driver.Connect(endpoint)); |
|||
m_IsClient = true; |
|||
|
|||
Debug.Log("StartClient"); |
|||
return SocketTask.Working.AsTasks(); |
|||
} |
|||
|
|||
public int NetcodeChannelToPipeline(UTPDelivery type) |
|||
{ |
|||
switch (type) |
|||
{ |
|||
case UTPDelivery.UnreliableSequenced: |
|||
return 2; |
|||
case UTPDelivery.ReliableSequenced: |
|||
return 1; |
|||
case UTPDelivery.Unreliable: |
|||
return 0; |
|||
} |
|||
|
|||
return 0; |
|||
} |
|||
|
|||
public ulong GetNetcodeClientId(uint peerId, bool isServer) |
|||
{ |
|||
if (isServer) |
|||
{ |
|||
return 0; |
|||
} |
|||
else |
|||
{ |
|||
return peerId + 1; |
|||
} |
|||
} |
|||
|
|||
public void GetUTPConnectionDetails(ulong clientId, out uint peerId) |
|||
{ |
|||
if (clientId == 0) |
|||
{ |
|||
peerId = (uint)ServerClientId; |
|||
} |
|||
else |
|||
{ |
|||
peerId = (uint)clientId - 1; |
|||
} |
|||
} |
|||
|
|||
public override SocketTasks StartServer() |
|||
{ |
|||
Connections = new NativeList<NetworkConnection>(300, Allocator.Persistent); |
|||
var endpoint = NetworkEndPoint.Parse(Address, Port); |
|||
m_IsServer = true; |
|||
|
|||
Debug.Log("StartServer"); |
|||
|
|||
if (Driver.Bind(endpoint) != 0) |
|||
{ |
|||
Debug.LogError("Failed to bind to port " + Port); |
|||
} |
|||
else |
|||
{ |
|||
Driver.Listen(); |
|||
} |
|||
|
|||
return SocketTask.Working.AsTasks(); |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: fc5ef7b69296d69458910681f29471e6 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
|
|||
using Unity.Collections; |
|||
using Unity.Collections.LowLevel.Unsafe; |
|||
|
|||
namespace Assets.Scripts.Transport |
|||
{ |
|||
public static class Utilities |
|||
{ |
|||
private static unsafe byte[] SerializeUnmanagedArray<T>(NativeArray<T> value) where T : unmanaged |
|||
{ |
|||
var bytes = new byte[UnsafeUtility.SizeOf<T>() * value.Length + sizeof(int)]; |
|||
fixed (byte* ptr = bytes) |
|||
{ |
|||
var buf = new UnsafeAppendBuffer(ptr, bytes.Length); |
|||
buf.Add(value); |
|||
} |
|||
|
|||
return bytes; |
|||
} |
|||
|
|||
private static unsafe NativeArray<T> DeserializeUnmanagedArray<T>(byte[] buffer, Allocator allocator = Allocator.Temp) where T : unmanaged |
|||
{ |
|||
fixed (byte* ptr = buffer) |
|||
{ |
|||
var buf = new UnsafeAppendBuffer.Reader(ptr, buffer.Length); |
|||
buf.ReadNext<T>(out var array, allocator); |
|||
return array; |
|||
} |
|||
} |
|||
|
|||
public unsafe static byte[] SerializeUnmanaged<T>(ref T value) where T : unmanaged |
|||
{ |
|||
var bytes = new byte[UnsafeUtility.SizeOf<T>()]; |
|||
fixed (byte* ptr = bytes) |
|||
{ |
|||
UnsafeUtility.CopyStructureToPtr(ref value, ptr); |
|||
} |
|||
|
|||
return bytes; |
|||
} |
|||
|
|||
public unsafe static T DeserializeUnmanaged<T>(byte[] buffer) where T : unmanaged |
|||
{ |
|||
fixed (byte* ptr = buffer) |
|||
{ |
|||
UnsafeUtility.CopyPtrToStructure<T>(ptr, out var value); |
|||
return value; |
|||
} |
|||
} |
|||
|
|||
public unsafe static T DeserializeUnmanaged<T>(ref NativeSlice<byte> buffer) where T : unmanaged |
|||
{ |
|||
int structSize = UnsafeUtility.SizeOf<T>(); |
|||
long ptr = (long)buffer.GetUnsafePtr(); |
|||
long size = buffer.Length; |
|||
|
|||
long addr = ptr + size - structSize; |
|||
|
|||
var data = UnsafeUtility.ReadArrayElement<T>((void*)addr, 0); |
|||
|
|||
return data; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4613e18f31d730a4c908e335ae40f832 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Multiplayer.Transport.UTP", |
|||
"references": [ |
|||
"Unity.Collections", |
|||
"Unity.Networking.Transport", |
|||
"Unity.Jobs", |
|||
"Unity.Burst", |
|||
"Unity.Netcode.Runtime" |
|||
], |
|||
"allowUnsafeCode": true |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 3ca8b3b66202abc418c13ff7812fb7ef |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 6ffe51a727e9e5541a9bcef793f920f2 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: ff6b9fdc7bae5bf46aad42bf806d50ab |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using UnityEngine; |
|||
using NUnit.Framework; |
|||
|
|||
namespace Unity.Netcode.UTP.EditorTests |
|||
{ |
|||
public class BasicUTPTest : MonoBehaviour |
|||
{ |
|||
[Test] |
|||
public void BasicUTPInitializationTest() |
|||
{ |
|||
var o = new GameObject(); |
|||
var utpTransport = (UTPTransport)o.AddComponent(typeof(UTPTransport)); |
|||
utpTransport.Init(); |
|||
|
|||
Assert.True(utpTransport.ServerClientId == 0); |
|||
|
|||
utpTransport.Shutdown(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 4d51a9ee9a7131d47acc9ecb8c99dd65 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Multiplayer.Transport.UTP.EditorTests", |
|||
"rootNamespace": "Unity.Netcode.UTP.EditorTests", |
|||
"references": [ |
|||
"Unity.Netcode.Runtime", |
|||
"Unity.Networking.Transport", |
|||
"Unity.Multiplayer.Transport.UTP" |
|||
], |
|||
"optionalUnityReferences": [ |
|||
"TestAssemblies" |
|||
], |
|||
"includePlatforms": [ |
|||
"Editor" |
|||
] |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e7d1cf2f203c677459c30bada5c15a9a |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 8a093c5e4dedd224891d2e8a197de43a |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections; |
|||
using NUnit.Framework; |
|||
using UnityEngine.TestTools; |
|||
|
|||
namespace Unity.Netcode.UTP.RuntimeTests |
|||
{ |
|||
public class DummyTestScript |
|||
{ |
|||
// A Test behaves as an ordinary method
|
|||
[Test] |
|||
public void DummyTestScriptSimplePasses() |
|||
{ |
|||
// Use the Assert class to test conditions
|
|||
} |
|||
|
|||
// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
|
|||
// `yield return null;` to skip a frame.
|
|||
[UnityTest] |
|||
public IEnumerator DummyTestScriptWithEnumeratorPasses() |
|||
{ |
|||
// Use the Assert class to test conditions.
|
|||
// Use yield to skip a frame.
|
|||
yield return null; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a4625f3349130440d9879c25654c9862 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Multiplayer.Transport.UTP.RuntimeTests", |
|||
"rootNamespace": "Unity.Netcode.UTP.RuntimeTests", |
|||
"references": [ |
|||
"Unity.Netcode.Runtime", |
|||
"Unity.Networking.Transport" |
|||
], |
|||
"optionalUnityReferences": [ |
|||
"TestAssemblies" |
|||
], |
|||
"defineConstraints": [ |
|||
"UNITY_INCLUDE_TESTS" |
|||
] |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 05c2d009acf3b36488f0e38dc7ee0e0f |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "com.unity.multiplayer.transport.utp", |
|||
"displayName": "Unity Transport for Netcode for GameObjects", |
|||
"description": "This package is plugging Unity Transport into Netcode for GameObjects, which is a network transport layer - the low-level interface for sending UDP data", |
|||
"version": "0.0.1-preview.1", |
|||
"unity": "2020.3", |
|||
"dependencies": { |
|||
"com.unity.netcode.gameobjects": "0.0.1-preview.1", |
|||
"com.unity.transport": "0.4.1-preview.1", |
|||
"com.unity.jobs":"0.2.10-preview.13" |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 67a2dba60abbf414584aab568fb56103 |
|||
PackageManifestImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# Changelog |
|||
This file documents all notable changes to this package. Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). |
|||
|
|||
## [0.2.0] - 2021-06-03 |
|||
|
|||
WIP version increment to pass package validation checks. Changelog & final version number TBD. |
|||
|
|||
## [0.1.1] - 2021-06-01 |
|||
|
|||
This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package. |
|||
|
|||
### Changes |
|||
|
|||
* Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release. |
|||
|
|||
## [0.1.0] - 2021-03-23 |
|||
|
|||
This is the initial experimental Unity MLAPI Package, v0.1.0. |
|||
|
|||
### New Features |
|||
|
|||
- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)). |
|||
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details. |
|||
- `INetworkSerializable` interface replaces `IBitWritable`. |
|||
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally. |
|||
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details: |
|||
|
|||
- It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution. |
|||
- You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation. |
|||
- `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs. |
|||
|
|||
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately. |
|||
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute. |
|||
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later: |
|||
|
|||
- Added new profiler modules for MLAPI that report important network data. |
|||
- Attached the profiler to a remote player to view network data over the wire. |
|||
|
|||
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject). |
|||
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`. |
|||
|
|||
### Changes |
|||
|
|||
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management. |
|||
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s. |
|||
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects. |
|||
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings. |
|||
|
|||
For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514). |
|||
|
|||
| Previous MLAPI Versions | V 0.1.0 Name | |
|||
| -- | -- | |
|||
| `NetworkingManager` | `NetworkManager` | |
|||
| `NetworkedObject` | `NetworkObject` | |
|||
| `NetworkedBehaviour` | `NetworkBehaviour` | |
|||
| `NetworkedClient` | `NetworkClient` | |
|||
| `NetworkedPrefab` | `NetworkPrefab` | |
|||
| `NetworkedVar` | `NetworkVariable` | |
|||
| `NetworkedTransform` | `NetworkTransform` | |
|||
| `NetworkedAnimator` | `NetworkAnimator` | |
|||
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` | |
|||
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` | |
|||
| `SpawnManager` | `NetworkSpawnManager` | |
|||
| `BitStream` | `NetworkBuffer` | |
|||
| `BitReader` | `NetworkReader` | |
|||
| `BitWriter` | `NetworkWriter` | |
|||
| `NetEventType` | `NetworkEventType` | |
|||
| `ChannelType` | `NetworkDelivery` | |
|||
| `Channel` | `NetworkChannel` | |
|||
| `Transport` | `NetworkTransport` | |
|||
| `NetworkedDictionary` | `NetworkDictionary` | |
|||
| `NetworkedList` | `NetworkList` | |
|||
| `NetworkedSet` | `NetworkSet` | |
|||
| `MLAPIConstants` | `NetworkConstants` | |
|||
| `UnetTransport` | `UNetTransport` | |
|||
|
|||
### Fixes |
|||
|
|||
- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`. |
|||
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame. |
|||
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method. |
|||
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode. |
|||
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions. |
|||
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits. |
|||
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`. |
|||
|
|||
### Removed features |
|||
|
|||
With a new release of MLAPI in Unity, some features have been removed: |
|||
|
|||
* SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 --> |
|||
* [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo. |
|||
* [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes: |
|||
|
|||
* Removed `SecuritySendFlags` from all APIs. |
|||
* Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`. |
|||
* Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries. |
|||
* Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing. |
|||
|
|||
* Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later. |
|||
* Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details. |
|||
* [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer. |
|||
|
|||
## Known issues |
|||
|
|||
* `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination. |
|||
* For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error. |
|||
* For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work. |
|||
* For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel. |
|||
* `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them |
|||
* `NetworkTransform` have the following issues: |
|||
* Replicated objects may have jitter. |
|||
* The owner is always authoritative about the object's position. |
|||
* Scale is not synchronized. |
|||
* Connection Approval is not called on the host client. |
|||
* For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages. |
|||
* For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly. |
|||
|
|||
## [0.0.1-preview.1] - 2020-12-20 |
|||
This was an internally-only-used version of the Unity MLAPI Package |
|
|||
fileFormatVersion: 2 |
|||
guid: d8f80d04925759d41a7bbee0259b3c39 |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
# About Netcode for GameObjects |
|||
|
|||
Unity Netcode for GameObjects is a high-level networking library built to abstract networking. This allows developers to focus on the game rather than low level protocols and networking frameworks. |
|||
|
|||
## Guides |
|||
|
|||
See guides below to install Unity Netcode for GameObjects, set up your project, and get started with your first networked game: |
|||
|
|||
* [Documentation](https://docs-multiplayer.unity3d.com/docs/getting-started/about-mlapi) |
|||
* [Installation](https://docs-multiplayer.unity3d.com/docs/migration/install) |
|||
* [First Steps](https://docs-multiplayer.unity3d.com/docs/tutorials/helloworldintro) |
|||
* [API Reference](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction) |
|||
|
|||
# Technical details |
|||
|
|||
## Requirements |
|||
|
|||
This version of Netcode for GameObjects is compatible with the following Unity versions and platforms: |
|||
|
|||
* 2020.3 and later |
|||
* Windows, Mac, Linux platforms |
|||
|
|||
## Document revision history |
|||
|
|||
|Date|Reason| |
|||
|---|---| |
|||
|March 10, 2021|Document created. Matches package version 0.1.0| |
|||
|June 1, 2021|Update and add links for additional content. Matches patch version 0.1.0 and hotfixes.| |
|||
|June 3, 2021|Update document to acknowledge Unity min version change. Matches package version 0.2.0| |
|||
|August 5, 2021|Update product/package name| |
|
|||
fileFormatVersion: 2 |
|||
guid: 0388707d01c6e18409986ad2fadf6faa |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: bbb4974b4302f435b9f4663c64d8f803 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using Mono.Cecil; |
|||
using Mono.Cecil.Cil; |
|||
using Mono.Cecil.Rocks; |
|||
using Unity.CompilationPipeline.Common.Diagnostics; |
|||
using Unity.CompilationPipeline.Common.ILPostProcessing; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Netcode.Editor.CodeGen |
|||
{ |
|||
internal static class CodeGenHelpers |
|||
{ |
|||
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime"; |
|||
|
|||
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName; |
|||
public static readonly string ServerRpcAttribute_FullName = typeof(ServerRpcAttribute).FullName; |
|||
public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName; |
|||
public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName; |
|||
public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName; |
|||
public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName; |
|||
public static readonly string INetworkSerializable_NetworkSerialize_Name = nameof(INetworkSerializable.NetworkSerialize); |
|||
public static readonly string UnityColor_FullName = typeof(Color).FullName; |
|||
public static readonly string UnityColor32_FullName = typeof(Color32).FullName; |
|||
public static readonly string UnityVector2_FullName = typeof(Vector2).FullName; |
|||
public static readonly string UnityVector3_FullName = typeof(Vector3).FullName; |
|||
public static readonly string UnityVector4_FullName = typeof(Vector4).FullName; |
|||
public static readonly string UnityQuaternion_FullName = typeof(Quaternion).FullName; |
|||
public static readonly string UnityRay_FullName = typeof(Ray).FullName; |
|||
public static readonly string UnityRay2D_FullName = typeof(Ray2D).FullName; |
|||
|
|||
public static uint Hash(this MethodDefinition methodDefinition) |
|||
{ |
|||
var sigArr = Encoding.UTF8.GetBytes($"{methodDefinition.Module.Name} / {methodDefinition.FullName}"); |
|||
var sigLen = sigArr.Length; |
|||
unsafe |
|||
{ |
|||
fixed (byte* sigPtr = sigArr) |
|||
{ |
|||
return XXHash.Hash32(sigPtr, sigLen); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public static bool IsSubclassOf(this TypeDefinition typeDefinition, string classTypeFullName) |
|||
{ |
|||
if (!typeDefinition.IsClass) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
var baseTypeRef = typeDefinition.BaseType; |
|||
while (baseTypeRef != null) |
|||
{ |
|||
if (baseTypeRef.FullName == classTypeFullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
baseTypeRef = baseTypeRef.Resolve().BaseType; |
|||
} |
|||
catch |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName) |
|||
{ |
|||
if (typeReference.IsArray) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var typeDef = typeReference.Resolve(); |
|||
var typeFaces = typeDef.Interfaces; |
|||
return typeFaces.Any(iface => iface.InterfaceType.FullName == interfaceTypeFullName); |
|||
} |
|||
catch |
|||
{ |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
public static bool IsSerializable(this TypeReference typeReference) |
|||
{ |
|||
var typeSystem = typeReference.Module.TypeSystem; |
|||
|
|||
// C# primitives
|
|||
if (typeReference == typeSystem.Boolean) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Char) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.SByte) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Byte) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Int16) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.UInt16) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Int32) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.UInt32) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Int64) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.UInt64) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Single) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.Double) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference == typeSystem.String) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// Unity primitives
|
|||
if (typeReference.FullName == UnityColor_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityColor32_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityVector2_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityVector3_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityVector4_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityQuaternion_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityRay_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
if (typeReference.FullName == UnityRay2D_FullName) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// Enum
|
|||
if (typeReference.GetEnumAsInt() != null) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// INetworkSerializable
|
|||
if (typeReference.HasInterface(INetworkSerializable_FullName)) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
// Static array
|
|||
if (typeReference.IsArray) |
|||
{ |
|||
return typeReference.GetElementType().IsSerializable(); |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
public static TypeReference GetEnumAsInt(this TypeReference typeReference) |
|||
{ |
|||
if (typeReference.IsArray) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
try |
|||
{ |
|||
var typeDef = typeReference.Resolve(); |
|||
return typeDef.IsEnum ? typeDef.GetEnumUnderlyingType() : null; |
|||
} |
|||
catch |
|||
{ |
|||
return null; |
|||
} |
|||
} |
|||
|
|||
public static void AddError(this List<DiagnosticMessage> diagnostics, string message) |
|||
{ |
|||
diagnostics.AddError((SequencePoint)null, message); |
|||
} |
|||
|
|||
public static void AddError(this List<DiagnosticMessage> diagnostics, MethodDefinition methodDefinition, string message) |
|||
{ |
|||
diagnostics.AddError(methodDefinition.DebugInformation.SequencePoints.FirstOrDefault(), message); |
|||
} |
|||
|
|||
public static void AddError(this List<DiagnosticMessage> diagnostics, SequencePoint sequencePoint, string message) |
|||
{ |
|||
diagnostics.Add(new DiagnosticMessage |
|||
{ |
|||
DiagnosticType = DiagnosticType.Error, |
|||
File = sequencePoint?.Document.Url.Replace($"{Environment.CurrentDirectory}{Path.DirectorySeparatorChar}", ""), |
|||
Line = sequencePoint?.StartLine ?? 0, |
|||
Column = sequencePoint?.StartColumn ?? 0, |
|||
MessageData = $" - {message}" |
|||
}); |
|||
} |
|||
|
|||
public static AssemblyDefinition AssemblyDefinitionFor(ICompiledAssembly compiledAssembly) |
|||
{ |
|||
var assemblyResolver = new PostProcessorAssemblyResolver(compiledAssembly); |
|||
var readerParameters = new ReaderParameters |
|||
{ |
|||
SymbolStream = new MemoryStream(compiledAssembly.InMemoryAssembly.PdbData), |
|||
SymbolReaderProvider = new PortablePdbReaderProvider(), |
|||
AssemblyResolver = assemblyResolver, |
|||
ReflectionImporterProvider = new PostProcessorReflectionImporterProvider(), |
|||
ReadingMode = ReadingMode.Immediate |
|||
}; |
|||
|
|||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(new MemoryStream(compiledAssembly.InMemoryAssembly.PeData), readerParameters); |
|||
|
|||
//apparently, it will happen that when we ask to resolve a type that lives inside Unity.Netcode.Runtime, and we
|
|||
//are also postprocessing Unity.Netcode.Runtime, type resolving will fail, because we do not actually try to resolve
|
|||
//inside the assembly we are processing. Let's make sure we do that, so that we can use postprocessor features inside
|
|||
//Unity.Netcode.Runtime itself as well.
|
|||
assemblyResolver.AddAssemblyDefinitionBeingOperatedOn(assemblyDefinition); |
|||
|
|||
return assemblyDefinition; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 0e5541b3bca0e43b48c2e694fffef5b3 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
1001
Packages/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
文件差异内容过多而无法显示
查看文件
文件差异内容过多而无法显示
查看文件
|
|||
fileFormatVersion: 2 |
|||
guid: cf1c8b78182704372820a586c1c91d97 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Threading; |
|||
using Mono.Cecil; |
|||
using Unity.CompilationPipeline.Common.ILPostProcessing; |
|||
|
|||
namespace Unity.Netcode.Editor.CodeGen |
|||
{ |
|||
internal class PostProcessorAssemblyResolver : IAssemblyResolver |
|||
{ |
|||
private readonly string[] m_AssemblyReferences; |
|||
private readonly Dictionary<string, AssemblyDefinition> m_AssemblyCache = new Dictionary<string, AssemblyDefinition>(); |
|||
private readonly ICompiledAssembly m_CompiledAssembly; |
|||
private AssemblyDefinition m_SelfAssembly; |
|||
|
|||
public PostProcessorAssemblyResolver(ICompiledAssembly compiledAssembly) |
|||
{ |
|||
m_CompiledAssembly = compiledAssembly; |
|||
m_AssemblyReferences = compiledAssembly.References; |
|||
} |
|||
|
|||
public void Dispose() { } |
|||
|
|||
public AssemblyDefinition Resolve(AssemblyNameReference name) => Resolve(name, new ReaderParameters(ReadingMode.Deferred)); |
|||
|
|||
public AssemblyDefinition Resolve(AssemblyNameReference name, ReaderParameters parameters) |
|||
{ |
|||
lock (m_AssemblyCache) |
|||
{ |
|||
if (name.Name == m_CompiledAssembly.Name) |
|||
{ |
|||
return m_SelfAssembly; |
|||
} |
|||
|
|||
var fileName = FindFile(name); |
|||
if (fileName == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var lastWriteTime = File.GetLastWriteTime(fileName); |
|||
var cacheKey = $"{fileName}{lastWriteTime}"; |
|||
if (m_AssemblyCache.TryGetValue(cacheKey, out var result)) |
|||
{ |
|||
return result; |
|||
} |
|||
|
|||
parameters.AssemblyResolver = this; |
|||
|
|||
var ms = MemoryStreamFor(fileName); |
|||
var pdb = $"{fileName}.pdb"; |
|||
if (File.Exists(pdb)) |
|||
{ |
|||
parameters.SymbolStream = MemoryStreamFor(pdb); |
|||
} |
|||
|
|||
var assemblyDefinition = AssemblyDefinition.ReadAssembly(ms, parameters); |
|||
m_AssemblyCache.Add(cacheKey, assemblyDefinition); |
|||
|
|||
return assemblyDefinition; |
|||
} |
|||
} |
|||
|
|||
private string FindFile(AssemblyNameReference name) |
|||
{ |
|||
var fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.dll"); |
|||
if (fileName != null) |
|||
{ |
|||
return fileName; |
|||
} |
|||
|
|||
// perhaps the type comes from an exe instead
|
|||
fileName = m_AssemblyReferences.FirstOrDefault(r => Path.GetFileName(r) == $"{name.Name}.exe"); |
|||
if (fileName != null) |
|||
{ |
|||
return fileName; |
|||
} |
|||
|
|||
//Unfortunately the current ICompiledAssembly API only provides direct references.
|
|||
//It is very much possible that a postprocessor ends up investigating a type in a directly
|
|||
//referenced assembly, that contains a field that is not in a directly referenced assembly.
|
|||
//if we don't do anything special for that situation, it will fail to resolve. We should fix this
|
|||
//in the ILPostProcessing API. As a workaround, we rely on the fact here that the indirect references
|
|||
//are always located next to direct references, so we search in all directories of direct references we
|
|||
//got passed, and if we find the file in there, we resolve to it.
|
|||
return m_AssemblyReferences |
|||
.Select(Path.GetDirectoryName) |
|||
.Distinct() |
|||
.Select(parentDir => Path.Combine(parentDir, $"{name.Name}.dll")) |
|||
.FirstOrDefault(File.Exists); |
|||
} |
|||
|
|||
private static MemoryStream MemoryStreamFor(string fileName) |
|||
{ |
|||
return Retry(10, TimeSpan.FromSeconds(1), () => |
|||
{ |
|||
byte[] byteArray; |
|||
using var fileStream = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); |
|||
byteArray = new byte[fileStream.Length]; |
|||
var readLength = fileStream.Read(byteArray, 0, (int)fileStream.Length); |
|||
if (readLength != fileStream.Length) |
|||
{ |
|||
throw new InvalidOperationException("File read length is not full length of file."); |
|||
} |
|||
|
|||
return new MemoryStream(byteArray); |
|||
}); |
|||
} |
|||
|
|||
private static MemoryStream Retry(int retryCount, TimeSpan waitTime, Func<MemoryStream> func) |
|||
{ |
|||
try |
|||
{ |
|||
return func(); |
|||
} |
|||
catch (IOException) |
|||
{ |
|||
if (retryCount == 0) |
|||
{ |
|||
throw; |
|||
} |
|||
|
|||
Console.WriteLine($"Caught IO Exception, trying {retryCount} more times"); |
|||
Thread.Sleep(waitTime); |
|||
|
|||
return Retry(retryCount - 1, waitTime, func); |
|||
} |
|||
} |
|||
|
|||
public void AddAssemblyDefinitionBeingOperatedOn(AssemblyDefinition assemblyDefinition) |
|||
{ |
|||
m_SelfAssembly = assemblyDefinition; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2c247f4266b2864eb96e6a9ae6557d31 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Linq; |
|||
using System.Reflection; |
|||
using Mono.Cecil; |
|||
|
|||
namespace Unity.Netcode.Editor.CodeGen |
|||
{ |
|||
internal class PostProcessorReflectionImporter : DefaultReflectionImporter |
|||
{ |
|||
private const string k_SystemPrivateCoreLib = "System.Private.CoreLib"; |
|||
private readonly AssemblyNameReference m_CorrectCorlib; |
|||
|
|||
public PostProcessorReflectionImporter(ModuleDefinition module) : base(module) |
|||
{ |
|||
m_CorrectCorlib = module.AssemblyReferences.FirstOrDefault(a => a.Name == "mscorlib" || a.Name == "netstandard" || a.Name == k_SystemPrivateCoreLib); |
|||
} |
|||
|
|||
public override AssemblyNameReference ImportReference(AssemblyName reference) |
|||
{ |
|||
return m_CorrectCorlib != null && reference.Name == k_SystemPrivateCoreLib ? m_CorrectCorlib : base.ImportReference(reference); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 484e8ad8c4dde382ea67036b32935ef1 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using Mono.Cecil; |
|||
|
|||
namespace Unity.Netcode.Editor.CodeGen |
|||
{ |
|||
internal class PostProcessorReflectionImporterProvider : IReflectionImporterProvider |
|||
{ |
|||
public IReflectionImporter GetReflectionImporter(ModuleDefinition moduleDefinition) |
|||
{ |
|||
return new PostProcessorReflectionImporter(moduleDefinition); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: f9273a5dad109ab0783891e36c983080 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using System.IO; |
|||
using Mono.Cecil; |
|||
using Mono.Cecil.Cil; |
|||
using Unity.CompilationPipeline.Common.Diagnostics; |
|||
using Unity.CompilationPipeline.Common.ILPostProcessing; |
|||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor; |
|||
|
|||
namespace Unity.Netcode.Editor.CodeGen |
|||
{ |
|||
internal sealed class RuntimeAccessModifiersILPP : ILPPInterface |
|||
{ |
|||
public override ILPPInterface GetInstance() => this; |
|||
|
|||
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName; |
|||
|
|||
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>(); |
|||
|
|||
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) |
|||
{ |
|||
if (!WillProcess(compiledAssembly)) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
m_Diagnostics.Clear(); |
|||
|
|||
// read
|
|||
var assemblyDefinition = CodeGenHelpers.AssemblyDefinitionFor(compiledAssembly); |
|||
if (assemblyDefinition == null) |
|||
{ |
|||
m_Diagnostics.AddError($"Cannot read Netcode Runtime assembly definition: {compiledAssembly.Name}"); |
|||
return null; |
|||
} |
|||
|
|||
// process
|
|||
var mainModule = assemblyDefinition.MainModule; |
|||
if (mainModule != null) |
|||
{ |
|||
foreach (var typeDefinition in mainModule.Types) |
|||
{ |
|||
if (!typeDefinition.IsClass) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
switch (typeDefinition.Name) |
|||
{ |
|||
case nameof(NetworkManager): |
|||
ProcessNetworkManager(typeDefinition, compiledAssembly.Defines); |
|||
break; |
|||
case nameof(NetworkBehaviour): |
|||
ProcessNetworkBehaviour(typeDefinition); |
|||
break; |
|||
case nameof(__RpcParams): |
|||
typeDefinition.IsPublic = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
m_Diagnostics.AddError($"Cannot get main module from Netcode Runtime assembly definition: {compiledAssembly.Name}"); |
|||
} |
|||
|
|||
// write
|
|||
var pe = new MemoryStream(); |
|||
var pdb = new MemoryStream(); |
|||
|
|||
var writerParameters = new WriterParameters |
|||
{ |
|||
SymbolWriterProvider = new PortablePdbWriterProvider(), |
|||
SymbolStream = pdb, |
|||
WriteSymbols = true |
|||
}; |
|||
|
|||
assemblyDefinition.Write(pe, writerParameters); |
|||
|
|||
return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); |
|||
} |
|||
|
|||
private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines) |
|||
{ |
|||
foreach (var fieldDefinition in typeDefinition.Fields) |
|||
{ |
|||
if (fieldDefinition.Name == nameof(NetworkManager.__rpc_func_table)) |
|||
{ |
|||
fieldDefinition.IsPublic = true; |
|||
} |
|||
|
|||
if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table)) |
|||
{ |
|||
fieldDefinition.IsPublic = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) |
|||
{ |
|||
foreach (var nestedType in typeDefinition.NestedTypes) |
|||
{ |
|||
if (nestedType.Name == nameof(NetworkBehaviour.__RpcExecStage)) |
|||
{ |
|||
nestedType.IsNestedFamily = true; |
|||
} |
|||
} |
|||
|
|||
foreach (var fieldDefinition in typeDefinition.Fields) |
|||
{ |
|||
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage)) |
|||
{ |
|||
fieldDefinition.IsFamily = true; |
|||
} |
|||
} |
|||
|
|||
foreach (var methodDefinition in typeDefinition.Methods) |
|||
{ |
|||
switch (methodDefinition.Name) |
|||
{ |
|||
case nameof(NetworkBehaviour.__beginSendServerRpc): |
|||
case nameof(NetworkBehaviour.__endSendServerRpc): |
|||
case nameof(NetworkBehaviour.__beginSendClientRpc): |
|||
case nameof(NetworkBehaviour.__endSendClientRpc): |
|||
methodDefinition.IsFamily = true; |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 2c9f2f4b03d774432be69d4c2f53bd2d |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Netcode.Editor.CodeGen", |
|||
"rootNamespace": "Unity.Netcode.Editor.CodeGen", |
|||
"references": [ |
|||
"Unity.Netcode.Runtime" |
|||
], |
|||
"includePlatforms": [ |
|||
"Editor" |
|||
], |
|||
"allowUnsafeCode": true, |
|||
"overrideReferences": true, |
|||
"precompiledReferences": [ |
|||
"Mono.Cecil.dll", |
|||
"Mono.Cecil.Mdb.dll", |
|||
"Mono.Cecil.Pdb.dll", |
|||
"Mono.Cecil.Rocks.dll" |
|||
], |
|||
"autoReferenced": false |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: fe4fa159f4a96442ba22af67ddf20c65 |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace Unity.Netcode.Editor |
|||
{ |
|||
public class DontShowInTransportDropdownAttribute : Attribute |
|||
{ |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 5f097067d4254dc7ad018d7ad90df7c3 |
|||
timeCreated: 1620386886 |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Reflection; |
|||
using UnityEngine; |
|||
using UnityEditor; |
|||
|
|||
namespace Unity.Netcode.Editor |
|||
{ |
|||
[CustomEditor(typeof(NetworkBehaviour), true)] |
|||
[CanEditMultipleObjects] |
|||
public class NetworkBehaviourEditor : UnityEditor.Editor |
|||
{ |
|||
private bool m_Initialized; |
|||
private readonly List<string> m_NetworkVariableNames = new List<string>(); |
|||
private readonly Dictionary<string, FieldInfo> m_NetworkVariableFields = new Dictionary<string, FieldInfo>(); |
|||
private readonly Dictionary<string, object> m_NetworkVariableObjects = new Dictionary<string, object>(); |
|||
|
|||
private GUIContent m_NetworkVariableLabelGuiContent; |
|||
|
|||
private void Init(MonoScript script) |
|||
{ |
|||
m_Initialized = true; |
|||
|
|||
m_NetworkVariableNames.Clear(); |
|||
m_NetworkVariableFields.Clear(); |
|||
m_NetworkVariableObjects.Clear(); |
|||
|
|||
m_NetworkVariableLabelGuiContent = new GUIContent("NetworkVariable", "This variable is a NetworkVariable. It can not be serialized and can only be changed during runtime."); |
|||
|
|||
var fields = script.GetClass().GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); |
|||
for (int i = 0; i < fields.Length; i++) |
|||
{ |
|||
var ft = fields[i].FieldType; |
|||
if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true)) |
|||
{ |
|||
m_NetworkVariableNames.Add(fields[i].Name); |
|||
m_NetworkVariableFields.Add(fields[i].Name, fields[i]); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void RenderNetworkVariable(int index) |
|||
{ |
|||
if (!m_NetworkVariableFields.ContainsKey(m_NetworkVariableNames[index])) |
|||
{ |
|||
serializedObject.Update(); |
|||
var scriptProperty = serializedObject.FindProperty("m_Script"); |
|||
if (scriptProperty == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var targetScript = scriptProperty.objectReferenceValue as MonoScript; |
|||
Init(targetScript); |
|||
} |
|||
|
|||
object value = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target); |
|||
if (value == null) |
|||
{ |
|||
var fieldType = m_NetworkVariableFields[m_NetworkVariableNames[index]].FieldType; |
|||
var networkVariable = (NetworkVariableBase)Activator.CreateInstance(fieldType, true); |
|||
m_NetworkVariableFields[m_NetworkVariableNames[index]].SetValue(target, networkVariable); |
|||
} |
|||
|
|||
var type = m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target).GetType(); |
|||
var genericType = type.GetGenericArguments()[0]; |
|||
|
|||
EditorGUILayout.BeginHorizontal(); |
|||
if (genericType.IsValueType) |
|||
{ |
|||
var method = typeof(NetworkBehaviourEditor).GetMethod("RenderNetworkVariableValueType", BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy | BindingFlags.NonPublic); |
|||
var genericMethod = method.MakeGenericMethod(genericType); |
|||
genericMethod.Invoke(this, new[] { (object)index }); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.LabelField("Type not renderable"); |
|||
} |
|||
|
|||
GUILayout.Label(m_NetworkVariableLabelGuiContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(m_NetworkVariableLabelGuiContent).x)); |
|||
EditorGUILayout.EndHorizontal(); |
|||
} |
|||
|
|||
private void RenderNetworkVariableValueType<T>(int index) where T : unmanaged |
|||
{ |
|||
var networkVariable = (NetworkVariable<T>)m_NetworkVariableFields[m_NetworkVariableNames[index]].GetValue(target); |
|||
var type = typeof(T); |
|||
object val = networkVariable.Value; |
|||
string name = m_NetworkVariableNames[index]; |
|||
|
|||
var behaviour = (NetworkBehaviour)target; |
|||
|
|||
if (behaviour.NetworkManager != null && behaviour.NetworkManager.IsListening) |
|||
{ |
|||
if (type == typeof(int)) |
|||
{ |
|||
val = EditorGUILayout.IntField(name, (int)val); |
|||
} |
|||
else if (type == typeof(uint)) |
|||
{ |
|||
val = (uint)EditorGUILayout.LongField(name, (long)((uint)val)); |
|||
} |
|||
else if (type == typeof(short)) |
|||
{ |
|||
val = (short)EditorGUILayout.IntField(name, (int)((short)val)); |
|||
} |
|||
else if (type == typeof(ushort)) |
|||
{ |
|||
val = (ushort)EditorGUILayout.IntField(name, (int)((ushort)val)); |
|||
} |
|||
else if (type == typeof(sbyte)) |
|||
{ |
|||
val = (sbyte)EditorGUILayout.IntField(name, (int)((sbyte)val)); |
|||
} |
|||
else if (type == typeof(byte)) |
|||
{ |
|||
val = (byte)EditorGUILayout.IntField(name, (int)((byte)val)); |
|||
} |
|||
else if (type == typeof(long)) |
|||
{ |
|||
val = EditorGUILayout.LongField(name, (long)val); |
|||
} |
|||
else if (type == typeof(ulong)) |
|||
{ |
|||
val = (ulong)EditorGUILayout.LongField(name, (long)((ulong)val)); |
|||
} |
|||
else if (type == typeof(bool)) |
|||
{ |
|||
val = EditorGUILayout.Toggle(name, (bool)val); |
|||
} |
|||
else if (type == typeof(string)) |
|||
{ |
|||
val = EditorGUILayout.TextField(name, (string)val); |
|||
} |
|||
else if (type.IsEnum) |
|||
{ |
|||
val = EditorGUILayout.EnumPopup(name, (Enum)val); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.LabelField("Type not renderable"); |
|||
} |
|||
|
|||
networkVariable.Value = (T)val; |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.LabelField(name, EditorStyles.wordWrappedLabel); |
|||
EditorGUILayout.SelectableLabel(val.ToString(), EditorStyles.wordWrappedLabel); |
|||
} |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
if (!m_Initialized) |
|||
{ |
|||
serializedObject.Update(); |
|||
var scriptProperty = serializedObject.FindProperty("m_Script"); |
|||
if (scriptProperty == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var targetScript = scriptProperty.objectReferenceValue as MonoScript; |
|||
Init(targetScript); |
|||
} |
|||
|
|||
EditorGUI.BeginChangeCheck(); |
|||
serializedObject.Update(); |
|||
|
|||
if (EditorApplication.isPlaying) |
|||
{ |
|||
for (int i = 0; i < m_NetworkVariableNames.Count; i++) |
|||
{ |
|||
RenderNetworkVariable(i); |
|||
} |
|||
} |
|||
|
|||
var property = serializedObject.GetIterator(); |
|||
bool expanded = true; |
|||
while (property.NextVisible(expanded)) |
|||
{ |
|||
if (property.propertyType == SerializedPropertyType.ObjectReference) |
|||
{ |
|||
if (property.name == "m_Script") |
|||
{ |
|||
EditorGUI.BeginDisabledGroup(true); |
|||
} |
|||
|
|||
EditorGUILayout.PropertyField(property, true); |
|||
|
|||
if (property.name == "m_Script") |
|||
{ |
|||
EditorGUI.EndDisabledGroup(); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.BeginHorizontal(); |
|||
EditorGUILayout.PropertyField(property, true); |
|||
EditorGUILayout.EndHorizontal(); |
|||
} |
|||
|
|||
expanded = false; |
|||
} |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
EditorGUI.EndChangeCheck(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a4a5c9c08a4038e449fd259764bc663f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using UnityEditor; |
|||
using UnityEngine; |
|||
using UnityEditorInternal; |
|||
|
|||
namespace Unity.Netcode.Editor |
|||
{ |
|||
[CustomEditor(typeof(NetworkManager), true)] |
|||
[CanEditMultipleObjects] |
|||
public class NetworkManagerEditor : UnityEditor.Editor |
|||
{ |
|||
// Properties
|
|||
private SerializedProperty m_DontDestroyOnLoadProperty; |
|||
private SerializedProperty m_RunInBackgroundProperty; |
|||
private SerializedProperty m_LogLevelProperty; |
|||
|
|||
// NetworkConfig
|
|||
private SerializedProperty m_NetworkConfigProperty; |
|||
|
|||
// NetworkConfig fields
|
|||
private SerializedProperty m_PlayerPrefabProperty; |
|||
private SerializedProperty m_ProtocolVersionProperty; |
|||
private SerializedProperty m_NetworkTransportProperty; |
|||
private SerializedProperty m_TickRateProperty; |
|||
private SerializedProperty m_MaxObjectUpdatesPerTickProperty; |
|||
private SerializedProperty m_ClientConnectionBufferTimeoutProperty; |
|||
private SerializedProperty m_ConnectionApprovalProperty; |
|||
private SerializedProperty m_EnableNetworkVariableProperty; |
|||
private SerializedProperty m_EnsureNetworkVariableLengthSafetyProperty; |
|||
private SerializedProperty m_ForceSamePrefabsProperty; |
|||
private SerializedProperty m_EnableSceneManagementProperty; |
|||
private SerializedProperty m_RecycleNetworkIdsProperty; |
|||
private SerializedProperty m_NetworkIdRecycleDelayProperty; |
|||
private SerializedProperty m_RpcHashSizeProperty; |
|||
private SerializedProperty m_LoadSceneTimeOutProperty; |
|||
|
|||
private ReorderableList m_NetworkPrefabsList; |
|||
|
|||
private NetworkManager m_NetworkManager; |
|||
private bool m_Initialized; |
|||
|
|||
private readonly List<Type> m_TransportTypes = new List<Type>(); |
|||
private string[] m_TransportNames = { "Select transport..." }; |
|||
|
|||
private void ReloadTransports() |
|||
{ |
|||
m_TransportTypes.Clear(); |
|||
|
|||
var assemblies = AppDomain.CurrentDomain.GetAssemblies(); |
|||
|
|||
foreach (var assembly in assemblies) |
|||
{ |
|||
var types = assembly.GetTypes(); |
|||
|
|||
foreach (var type in types) |
|||
{ |
|||
if (type.IsSubclassOf(typeof(NetworkTransport)) && type.GetCustomAttributes(typeof(DontShowInTransportDropdownAttribute), true).Length == 0) |
|||
{ |
|||
m_TransportTypes.Add(type); |
|||
} |
|||
} |
|||
} |
|||
|
|||
m_TransportNames = new string[m_TransportTypes.Count + 1]; |
|||
m_TransportNames[0] = "Select transport..."; |
|||
|
|||
for (int i = 0; i < m_TransportTypes.Count; i++) |
|||
{ |
|||
m_TransportNames[i + 1] = m_TransportTypes[i].Name; |
|||
} |
|||
} |
|||
|
|||
private void Initialize() |
|||
{ |
|||
if (m_Initialized) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
m_Initialized = true; |
|||
m_NetworkManager = (NetworkManager)target; |
|||
|
|||
// Base properties
|
|||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy)); |
|||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground)); |
|||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel)); |
|||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)); |
|||
|
|||
// NetworkConfig properties
|
|||
m_PlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative(nameof(NetworkConfig.PlayerPrefab)); |
|||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); |
|||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); |
|||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); |
|||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); |
|||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); |
|||
m_EnableNetworkVariableProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableNetworkVariable"); |
|||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); |
|||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs"); |
|||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); |
|||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds"); |
|||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); |
|||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); |
|||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); |
|||
|
|||
|
|||
ReloadTransports(); |
|||
} |
|||
|
|||
private void CheckNullProperties() |
|||
{ |
|||
// Base properties
|
|||
m_DontDestroyOnLoadProperty = serializedObject.FindProperty(nameof(NetworkManager.DontDestroy)); |
|||
m_RunInBackgroundProperty = serializedObject.FindProperty(nameof(NetworkManager.RunInBackground)); |
|||
m_LogLevelProperty = serializedObject.FindProperty(nameof(NetworkManager.LogLevel)); |
|||
m_NetworkConfigProperty = serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)); |
|||
|
|||
// NetworkConfig properties
|
|||
m_PlayerPrefabProperty = m_NetworkConfigProperty.FindPropertyRelative(nameof(NetworkConfig.PlayerPrefab)); |
|||
m_ProtocolVersionProperty = m_NetworkConfigProperty.FindPropertyRelative("ProtocolVersion"); |
|||
m_NetworkTransportProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTransport"); |
|||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate"); |
|||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout"); |
|||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval"); |
|||
m_EnableNetworkVariableProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableNetworkVariable"); |
|||
m_EnsureNetworkVariableLengthSafetyProperty = m_NetworkConfigProperty.FindPropertyRelative("EnsureNetworkVariableLengthSafety"); |
|||
m_ForceSamePrefabsProperty = m_NetworkConfigProperty.FindPropertyRelative("ForceSamePrefabs"); |
|||
m_EnableSceneManagementProperty = m_NetworkConfigProperty.FindPropertyRelative("EnableSceneManagement"); |
|||
m_RecycleNetworkIdsProperty = m_NetworkConfigProperty.FindPropertyRelative("RecycleNetworkIds"); |
|||
m_NetworkIdRecycleDelayProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkIdRecycleDelay"); |
|||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize"); |
|||
m_LoadSceneTimeOutProperty = m_NetworkConfigProperty.FindPropertyRelative("LoadSceneTimeOut"); |
|||
} |
|||
|
|||
private void OnEnable() |
|||
{ |
|||
m_NetworkPrefabsList = new ReorderableList(serializedObject, serializedObject.FindProperty(nameof(NetworkManager.NetworkConfig)).FindPropertyRelative(nameof(NetworkConfig.NetworkPrefabs)), true, true, true, true); |
|||
m_NetworkPrefabsList.elementHeightCallback = index => |
|||
{ |
|||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index); |
|||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override)); |
|||
var networkOverrideInt = networkOverrideProp.enumValueIndex; |
|||
|
|||
return 8 + (networkOverrideInt == 0 ? EditorGUIUtility.singleLineHeight : (EditorGUIUtility.singleLineHeight * 2) + 5); |
|||
}; |
|||
m_NetworkPrefabsList.drawElementCallback = (rect, index, isActive, isFocused) => |
|||
{ |
|||
rect.y += 5; |
|||
|
|||
var networkPrefab = m_NetworkPrefabsList.serializedProperty.GetArrayElementAtIndex(index); |
|||
var networkPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Prefab)); |
|||
var networkSourceHashProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourceHashToOverride)); |
|||
var networkSourcePrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.SourcePrefabToOverride)); |
|||
var networkTargetPrefabProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.OverridingTargetPrefab)); |
|||
var networkOverrideProp = networkPrefab.FindPropertyRelative(nameof(NetworkPrefab.Override)); |
|||
var networkOverrideInt = networkOverrideProp.enumValueIndex; |
|||
var networkOverrideEnum = (NetworkPrefabOverride)networkOverrideInt; |
|||
EditorGUI.LabelField(new Rect(rect.x + rect.width - 70, rect.y, 60, EditorGUIUtility.singleLineHeight), "Override"); |
|||
if (networkOverrideEnum == NetworkPrefabOverride.None) |
|||
{ |
|||
if (EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), false)) |
|||
{ |
|||
networkOverrideProp.enumValueIndex = (int)NetworkPrefabOverride.Prefab; |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
if (!EditorGUI.Toggle(new Rect(rect.x + rect.width - 15, rect.y, 10, EditorGUIUtility.singleLineHeight), true)) |
|||
{ |
|||
networkOverrideProp.enumValueIndex = 0; |
|||
networkOverrideEnum = NetworkPrefabOverride.None; |
|||
} |
|||
} |
|||
|
|||
if (networkOverrideEnum == NetworkPrefabOverride.None) |
|||
{ |
|||
EditorGUI.PropertyField(new Rect(rect.x, rect.y, rect.width - 80, EditorGUIUtility.singleLineHeight), networkPrefabProp, GUIContent.none); |
|||
} |
|||
else |
|||
{ |
|||
networkOverrideProp.enumValueIndex = GUI.Toolbar(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), networkOverrideInt - 1, new[] { "Prefab", "Hash" }) + 1; |
|||
|
|||
if (networkOverrideEnum == NetworkPrefabOverride.Prefab) |
|||
{ |
|||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourcePrefabProp, GUIContent.none); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 190, EditorGUIUtility.singleLineHeight), networkSourceHashProp, GUIContent.none); |
|||
} |
|||
|
|||
rect.y += EditorGUIUtility.singleLineHeight + 5; |
|||
|
|||
EditorGUI.LabelField(new Rect(rect.x, rect.y, 100, EditorGUIUtility.singleLineHeight), "Overriding Prefab"); |
|||
EditorGUI.PropertyField(new Rect(rect.x + 110, rect.y, rect.width - 110, EditorGUIUtility.singleLineHeight), networkTargetPrefabProp, GUIContent.none); |
|||
} |
|||
}; |
|||
m_NetworkPrefabsList.drawHeaderCallback = rect => EditorGUI.LabelField(rect, "NetworkPrefabs"); |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
Initialize(); |
|||
CheckNullProperties(); |
|||
|
|||
{ |
|||
var iterator = serializedObject.GetIterator(); |
|||
|
|||
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false) |
|||
{ |
|||
using (new EditorGUI.DisabledScope("m_Script" == iterator.propertyPath)) |
|||
{ |
|||
EditorGUILayout.PropertyField(iterator, false); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient) |
|||
{ |
|||
serializedObject.Update(); |
|||
EditorGUILayout.PropertyField(m_DontDestroyOnLoadProperty); |
|||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty); |
|||
EditorGUILayout.PropertyField(m_LogLevelProperty); |
|||
EditorGUILayout.Space(); |
|||
|
|||
EditorGUILayout.PropertyField(m_PlayerPrefabProperty); |
|||
EditorGUILayout.Space(); |
|||
|
|||
m_NetworkPrefabsList.DoLayoutList(); |
|||
EditorGUILayout.Space(); |
|||
|
|||
EditorGUILayout.LabelField("General", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_ProtocolVersionProperty); |
|||
|
|||
EditorGUILayout.PropertyField(m_NetworkTransportProperty); |
|||
|
|||
if (m_NetworkTransportProperty.objectReferenceValue == null) |
|||
{ |
|||
EditorGUILayout.HelpBox("You have no transport selected. A transport is required for netcode to work. Which one do you want?", MessageType.Warning); |
|||
|
|||
int selection = EditorGUILayout.Popup(0, m_TransportNames); |
|||
|
|||
if (selection > 0) |
|||
{ |
|||
ReloadTransports(); |
|||
|
|||
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]); |
|||
|
|||
if (transportComponent == null) |
|||
{ |
|||
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]); |
|||
} |
|||
|
|||
m_NetworkTransportProperty.objectReferenceValue = transportComponent; |
|||
|
|||
Repaint(); |
|||
} |
|||
} |
|||
|
|||
EditorGUILayout.PropertyField(m_TickRateProperty); |
|||
|
|||
EditorGUILayout.LabelField("Performance", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_EnableNetworkVariableProperty); |
|||
|
|||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableNetworkVariable)) |
|||
{ |
|||
EditorGUILayout.PropertyField(m_EnsureNetworkVariableLengthSafetyProperty); |
|||
} |
|||
|
|||
EditorGUILayout.LabelField("Connection", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_ConnectionApprovalProperty); |
|||
|
|||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.ConnectionApproval)) |
|||
{ |
|||
EditorGUILayout.PropertyField(m_ClientConnectionBufferTimeoutProperty); |
|||
} |
|||
|
|||
EditorGUILayout.LabelField("Spawning", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty); |
|||
|
|||
|
|||
EditorGUILayout.PropertyField(m_RecycleNetworkIdsProperty); |
|||
|
|||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.RecycleNetworkIds)) |
|||
{ |
|||
EditorGUILayout.PropertyField(m_NetworkIdRecycleDelayProperty); |
|||
} |
|||
|
|||
EditorGUILayout.LabelField("Bandwidth", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_RpcHashSizeProperty); |
|||
|
|||
EditorGUILayout.LabelField("Scene Management", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_EnableSceneManagementProperty); |
|||
|
|||
using (new EditorGUI.DisabledScope(!m_NetworkManager.NetworkConfig.EnableSceneManagement)) |
|||
{ |
|||
EditorGUILayout.PropertyField(m_LoadSceneTimeOutProperty); |
|||
} |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
|
|||
|
|||
// Start buttons below
|
|||
{ |
|||
string buttonDisabledReasonSuffix = ""; |
|||
|
|||
if (!EditorApplication.isPlaying) |
|||
{ |
|||
buttonDisabledReasonSuffix = ". This can only be done in play mode"; |
|||
GUI.enabled = false; |
|||
} |
|||
|
|||
if (GUILayout.Button(new GUIContent("Start Host", "Starts a host instance" + buttonDisabledReasonSuffix))) |
|||
{ |
|||
m_NetworkManager.StartHost(); |
|||
} |
|||
|
|||
if (GUILayout.Button(new GUIContent("Start Server", "Starts a server instance" + buttonDisabledReasonSuffix))) |
|||
{ |
|||
m_NetworkManager.StartServer(); |
|||
} |
|||
|
|||
if (GUILayout.Button(new GUIContent("Start Client", "Starts a client instance" + buttonDisabledReasonSuffix))) |
|||
{ |
|||
m_NetworkManager.StartClient(); |
|||
} |
|||
|
|||
if (!EditorApplication.isPlaying) |
|||
{ |
|||
GUI.enabled = true; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
string instanceType = string.Empty; |
|||
|
|||
if (m_NetworkManager.IsHost) |
|||
{ |
|||
instanceType = "Host"; |
|||
} |
|||
else if (m_NetworkManager.IsServer) |
|||
{ |
|||
instanceType = "Server"; |
|||
} |
|||
else if (m_NetworkManager.IsClient) |
|||
{ |
|||
instanceType = "Client"; |
|||
} |
|||
|
|||
EditorGUILayout.HelpBox("You cannot edit the NetworkConfig when a " + instanceType + " is running.", MessageType.Info); |
|||
|
|||
if (GUILayout.Button(new GUIContent("Stop " + instanceType, "Stops the " + instanceType + " instance."))) |
|||
{ |
|||
if (m_NetworkManager.IsHost) |
|||
{ |
|||
m_NetworkManager.StopHost(); |
|||
} |
|||
else if (m_NetworkManager.IsServer) |
|||
{ |
|||
m_NetworkManager.StopServer(); |
|||
} |
|||
else if (m_NetworkManager.IsClient) |
|||
{ |
|||
m_NetworkManager.StopClient(); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 74a8f011a324b7642b69098fe57bf635 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEditor; |
|||
|
|||
namespace Unity.Netcode.Editor |
|||
{ |
|||
[CustomEditor(typeof(NetworkObject), true)] |
|||
[CanEditMultipleObjects] |
|||
public class NetworkObjectEditor : UnityEditor.Editor |
|||
{ |
|||
private bool m_Initialized; |
|||
private NetworkObject m_NetworkObject; |
|||
private bool m_ShowObservers; |
|||
|
|||
private void Initialize() |
|||
{ |
|||
if (m_Initialized) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
m_Initialized = true; |
|||
m_NetworkObject = (NetworkObject)target; |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
Initialize(); |
|||
|
|||
if (EditorApplication.isPlaying && !m_NetworkObject.IsSpawned && m_NetworkObject.NetworkManager != null && m_NetworkObject.NetworkManager.IsServer) |
|||
{ |
|||
EditorGUILayout.BeginHorizontal(); |
|||
EditorGUILayout.LabelField(new GUIContent("Spawn", "Spawns the object across the network")); |
|||
if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft)) |
|||
{ |
|||
m_NetworkObject.Spawn(); |
|||
EditorUtility.SetDirty(target); |
|||
} |
|||
|
|||
EditorGUILayout.EndHorizontal(); |
|||
} |
|||
else if (EditorApplication.isPlaying && m_NetworkObject.IsSpawned) |
|||
{ |
|||
var guiEnabled = GUI.enabled; |
|||
GUI.enabled = false; |
|||
EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString()); |
|||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkObjectId), m_NetworkObject.NetworkObjectId.ToString()); |
|||
EditorGUILayout.TextField(nameof(NetworkObject.OwnerClientId), m_NetworkObject.OwnerClientId.ToString()); |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsSpawned), m_NetworkObject.IsSpawned); |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsLocalPlayer), m_NetworkObject.IsLocalPlayer); |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsOwner), m_NetworkObject.IsOwner); |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsOwnedByServer), m_NetworkObject.IsOwnedByServer); |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsPlayerObject), m_NetworkObject.IsPlayerObject); |
|||
if (m_NetworkObject.IsSceneObject.HasValue) |
|||
{ |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.IsSceneObject), m_NetworkObject.IsSceneObject.Value); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.TextField(nameof(NetworkObject.IsSceneObject), "null"); |
|||
} |
|||
EditorGUILayout.Toggle(nameof(NetworkObject.DestroyWithScene), m_NetworkObject.DestroyWithScene); |
|||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkManager), m_NetworkObject.NetworkManager == null ? "null" : m_NetworkObject.NetworkManager.gameObject.name); |
|||
GUI.enabled = guiEnabled; |
|||
|
|||
if (m_NetworkObject.NetworkManager != null && m_NetworkObject.NetworkManager.IsServer) |
|||
{ |
|||
m_ShowObservers = EditorGUILayout.Foldout(m_ShowObservers, "Observers"); |
|||
|
|||
if (m_ShowObservers) |
|||
{ |
|||
HashSet<ulong>.Enumerator observerClientIds = m_NetworkObject.GetObservers(); |
|||
|
|||
EditorGUI.indentLevel += 1; |
|||
|
|||
while (observerClientIds.MoveNext()) |
|||
{ |
|||
if (m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject != null) |
|||
{ |
|||
EditorGUILayout.ObjectField($"ClientId: {observerClientIds.Current}", m_NetworkObject.NetworkManager.ConnectedClients[observerClientIds.Current].PlayerObject, typeof(GameObject), false); |
|||
} |
|||
else |
|||
{ |
|||
EditorGUILayout.TextField($"ClientId: {observerClientIds.Current}", EditorStyles.label); |
|||
} |
|||
} |
|||
|
|||
EditorGUI.indentLevel -= 1; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
base.OnInspectorGUI(); |
|||
|
|||
var guiEnabled = GUI.enabled; |
|||
GUI.enabled = false; |
|||
EditorGUILayout.TextField(nameof(NetworkObject.GlobalObjectIdHash), m_NetworkObject.GlobalObjectIdHash.ToString()); |
|||
EditorGUILayout.TextField(nameof(NetworkObject.NetworkManager), m_NetworkObject.NetworkManager == null ? "null" : m_NetworkObject.NetworkManager.gameObject.name); |
|||
GUI.enabled = guiEnabled; |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 36e4b519d287d0f4e8bfb7d088a9275f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using Unity.Netcode.Prototyping; |
|||
using UnityEditor; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Netcode.Editor |
|||
{ |
|||
[CustomEditor(typeof(NetworkTransform))] |
|||
public class NetworkTransformEditor : UnityEditor.Editor |
|||
{ |
|||
private SerializedProperty m_SyncPositionXProperty; |
|||
private SerializedProperty m_SyncPositionYProperty; |
|||
private SerializedProperty m_SyncPositionZProperty; |
|||
private SerializedProperty m_SyncRotationXProperty; |
|||
private SerializedProperty m_SyncRotationYProperty; |
|||
private SerializedProperty m_SyncRotationZProperty; |
|||
private SerializedProperty m_SyncScaleXProperty; |
|||
private SerializedProperty m_SyncScaleYProperty; |
|||
private SerializedProperty m_SyncScaleZProperty; |
|||
private SerializedProperty m_PositionThresholdProperty; |
|||
private SerializedProperty m_RotAngleThresholdProperty; |
|||
private SerializedProperty m_ScaleThresholdProperty; |
|||
private SerializedProperty m_InLocalSpaceProperty; |
|||
private SerializedProperty m_InterpolateProperty; |
|||
|
|||
private static int s_ToggleOffset = 45; |
|||
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5; |
|||
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position"); |
|||
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation"); |
|||
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale"); |
|||
|
|||
public void OnEnable() |
|||
{ |
|||
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX)); |
|||
m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY)); |
|||
m_SyncPositionZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionZ)); |
|||
m_SyncRotationXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleX)); |
|||
m_SyncRotationYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleY)); |
|||
m_SyncRotationZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncRotAngleZ)); |
|||
m_SyncScaleXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleX)); |
|||
m_SyncScaleYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleY)); |
|||
m_SyncScaleZProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncScaleZ)); |
|||
m_PositionThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.PositionThreshold)); |
|||
m_RotAngleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.RotAngleThreshold)); |
|||
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold)); |
|||
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace)); |
|||
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate)); |
|||
} |
|||
|
|||
public override void OnInspectorGUI() |
|||
{ |
|||
EditorGUILayout.LabelField("Syncing", EditorStyles.boldLabel); |
|||
{ |
|||
GUILayout.BeginHorizontal(); |
|||
|
|||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField); |
|||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect); |
|||
|
|||
rect = EditorGUI.PrefixLabel(rect, ctid, s_PositionLabel); |
|||
rect.width = s_ToggleOffset; |
|||
|
|||
m_SyncPositionXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncPositionXProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncPositionYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncPositionYProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncPositionZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncPositionZProperty.boolValue); |
|||
|
|||
GUILayout.EndHorizontal(); |
|||
} |
|||
{ |
|||
GUILayout.BeginHorizontal(); |
|||
|
|||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField); |
|||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect); |
|||
|
|||
rect = EditorGUI.PrefixLabel(rect, ctid, s_RotationLabel); |
|||
rect.width = s_ToggleOffset; |
|||
|
|||
m_SyncRotationXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncRotationXProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncRotationYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncRotationYProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncRotationZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncRotationZProperty.boolValue); |
|||
|
|||
GUILayout.EndHorizontal(); |
|||
} |
|||
{ |
|||
GUILayout.BeginHorizontal(); |
|||
|
|||
var rect = GUILayoutUtility.GetRect(EditorGUIUtility.fieldWidth, s_MaxRowWidth, EditorGUIUtility.singleLineHeight, EditorGUIUtility.singleLineHeight, EditorStyles.numberField); |
|||
var ctid = GUIUtility.GetControlID(7231, FocusType.Keyboard, rect); |
|||
|
|||
rect = EditorGUI.PrefixLabel(rect, ctid, s_ScaleLabel); |
|||
rect.width = s_ToggleOffset; |
|||
|
|||
m_SyncScaleXProperty.boolValue = EditorGUI.ToggleLeft(rect, "X", m_SyncScaleXProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncScaleYProperty.boolValue = EditorGUI.ToggleLeft(rect, "Y", m_SyncScaleYProperty.boolValue); |
|||
rect.x += s_ToggleOffset; |
|||
m_SyncScaleZProperty.boolValue = EditorGUI.ToggleLeft(rect, "Z", m_SyncScaleZProperty.boolValue); |
|||
|
|||
GUILayout.EndHorizontal(); |
|||
} |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField("Thresholds", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_PositionThresholdProperty); |
|||
EditorGUILayout.PropertyField(m_RotAngleThresholdProperty); |
|||
EditorGUILayout.PropertyField(m_ScaleThresholdProperty); |
|||
|
|||
EditorGUILayout.Space(); |
|||
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel); |
|||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty); |
|||
EditorGUILayout.PropertyField(m_InterpolateProperty); |
|||
|
|||
serializedObject.ApplyModifiedProperties(); |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 17891488cb32d4243b0710884463d70f |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Netcode.Editor", |
|||
"rootNamespace": "Unity.Netcode.Editor", |
|||
"references": [ |
|||
"Unity.Netcode.Runtime", |
|||
"Unity.Netcode.Prototyping" |
|||
], |
|||
"includePlatforms": [ |
|||
"Editor" |
|||
] |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 9f4f5bf029cebb64f983b7bdc29f62a1 |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
MIT License |
|||
|
|||
Copyright (c) 2021 Unity Technologies |
|||
|
|||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
|||
|
|||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
|||
|
|||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
|||
fileFormatVersion: 2 |
|||
guid: a850895ba88aa114f804fefa31faf4d2 |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: 4f9f645c043894b81ab326fe4703f4c8 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#if UNITY_EDITOR
|
|||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] |
|||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")] |
|||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] |
|||
[assembly: InternalsVisibleTo("TestProject.EditorTests")] |
|||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] |
|||
#endif
|
|||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] |
|
|||
fileFormatVersion: 2 |
|||
guid: 5b8086dc75d86473f9e3c928dd773733 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Linq; |
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Netcode.Prototyping |
|||
{ |
|||
/// <summary>
|
|||
/// A prototype component for syncing animations
|
|||
/// </summary>
|
|||
[AddComponentMenu("Netcode/" + nameof(NetworkAnimator))] |
|||
public class NetworkAnimator : NetworkBehaviour |
|||
{ |
|||
|
|||
private class AnimatorSnapshot : INetworkSerializable |
|||
{ |
|||
public Dictionary<int, bool> BoolParameters; |
|||
public Dictionary<int, float> FloatParameters; |
|||
public Dictionary<int, int> IntParameters; |
|||
public HashSet<int> TriggerParameters; |
|||
public LayerState[] LayerStates; |
|||
|
|||
public AnimatorSnapshot(Dictionary<int, bool> boolParameters, Dictionary<int, float> floatParameters, Dictionary<int, int> intParameters, HashSet<int> triggerParameters, LayerState[] layerStates) |
|||
{ |
|||
BoolParameters = boolParameters; |
|||
FloatParameters = floatParameters; |
|||
IntParameters = intParameters; |
|||
TriggerParameters = triggerParameters; |
|||
LayerStates = layerStates; |
|||
} |
|||
|
|||
public AnimatorSnapshot() |
|||
{ |
|||
BoolParameters = new Dictionary<int, bool>(0); |
|||
FloatParameters = new Dictionary<int, float>(0); |
|||
IntParameters = new Dictionary<int, int>(0); |
|||
TriggerParameters = new HashSet<int>(); |
|||
LayerStates = new LayerState[0]; |
|||
} |
|||
|
|||
public bool SetInt(int key, int value) |
|||
{ |
|||
if (IntParameters.TryGetValue(key, out var existingValue) && existingValue == value) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
IntParameters[key] = value; |
|||
return true; |
|||
} |
|||
|
|||
public bool SetBool(int key, bool value) |
|||
{ |
|||
if (BoolParameters.TryGetValue(key, out var existingValue) && existingValue == value) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
BoolParameters[key] = value; |
|||
return true; |
|||
} |
|||
|
|||
public bool SetFloat(int key, float value) |
|||
{ |
|||
if (FloatParameters.TryGetValue(key, out var existingValue) && |
|||
Mathf.Abs(existingValue - value) < Mathf.Epsilon) |
|||
{ |
|||
return false; |
|||
} |
|||
|
|||
FloatParameters[key] = value; |
|||
return true; |
|||
} |
|||
|
|||
public bool SetTrigger(int key) |
|||
{ |
|||
return TriggerParameters.Add(key); |
|||
} |
|||
|
|||
public void NetworkSerialize(NetworkSerializer serializer) |
|||
{ |
|||
SerializeIntParameters(serializer); |
|||
SerializeFloatParameters(serializer); |
|||
SerializeBoolParameters(serializer); |
|||
SerializeTriggerParameters(serializer); |
|||
SerializeAnimatorLayerStates(serializer); |
|||
} |
|||
|
|||
private void SerializeAnimatorLayerStates(NetworkSerializer serializer) |
|||
{ |
|||
int layerCount = serializer.IsReading ? 0 : LayerStates.Length; |
|||
serializer.Serialize(ref layerCount); |
|||
|
|||
if (serializer.IsReading && LayerStates.Length != layerCount) |
|||
{ |
|||
LayerStates = new LayerState[layerCount]; |
|||
} |
|||
|
|||
for (int paramIndex = 0; paramIndex < layerCount; paramIndex++) |
|||
{ |
|||
var stateHash = serializer.IsReading ? 0 : LayerStates[paramIndex].StateHash; |
|||
serializer.Serialize(ref stateHash); |
|||
|
|||
var layerWeight = serializer.IsReading ? 0 : LayerStates[paramIndex].LayerWeight; |
|||
serializer.Serialize(ref layerWeight); |
|||
|
|||
var normalizedStateTime = serializer.IsReading ? 0 : LayerStates[paramIndex].NormalizedStateTime; |
|||
serializer.Serialize(ref normalizedStateTime); |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
LayerStates[paramIndex] = new LayerState() |
|||
{ |
|||
LayerWeight = layerWeight, |
|||
StateHash = stateHash, |
|||
NormalizedStateTime = normalizedStateTime |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void SerializeTriggerParameters(NetworkSerializer serializer) |
|||
{ |
|||
int paramCount = serializer.IsReading ? 0 : TriggerParameters.Count; |
|||
serializer.Serialize(ref paramCount); |
|||
|
|||
var paramArray = serializer.IsReading ? new int[paramCount] : TriggerParameters.ToArray(); |
|||
for (int i = 0; i < paramCount; i++) |
|||
{ |
|||
var paramId = serializer.IsReading ? 0 : paramArray[i]; |
|||
serializer.Serialize(ref paramId); |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
paramArray[i] = paramId; |
|||
} |
|||
} |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
TriggerParameters = new HashSet<int>(paramArray); |
|||
} |
|||
} |
|||
|
|||
private void SerializeBoolParameters(NetworkSerializer serializer) |
|||
{ |
|||
int paramCount = serializer.IsReading ? 0 : BoolParameters.Count; |
|||
serializer.Serialize(ref paramCount); |
|||
|
|||
var paramArray = serializer.IsReading ? new KeyValuePair<int, bool>[paramCount] : BoolParameters.ToArray(); |
|||
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) |
|||
{ |
|||
var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; |
|||
serializer.Serialize(ref paramId); |
|||
|
|||
var paramBool = serializer.IsReading ? false : paramArray[paramIndex].Value; |
|||
serializer.Serialize(ref paramBool); |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
paramArray[paramIndex] = new KeyValuePair<int, bool>(paramId, paramBool); |
|||
} |
|||
} |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
BoolParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); |
|||
} |
|||
} |
|||
|
|||
private void SerializeFloatParameters(NetworkSerializer serializer) |
|||
{ |
|||
int paramCount = serializer.IsReading ? 0 : FloatParameters.Count; |
|||
serializer.Serialize(ref paramCount); |
|||
|
|||
var paramArray = serializer.IsReading ? new KeyValuePair<int, float>[paramCount] : FloatParameters.ToArray(); |
|||
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) |
|||
{ |
|||
var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; |
|||
serializer.Serialize(ref paramId); |
|||
|
|||
var paramFloat = serializer.IsReading ? 0 : paramArray[paramIndex].Value; |
|||
serializer.Serialize(ref paramFloat); |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
paramArray[paramIndex] = new KeyValuePair<int, float>(paramId, paramFloat); |
|||
} |
|||
} |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
FloatParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); |
|||
} |
|||
} |
|||
|
|||
private void SerializeIntParameters(NetworkSerializer serializer) |
|||
{ |
|||
int paramCount = serializer.IsReading ? 0 : IntParameters.Count; |
|||
serializer.Serialize(ref paramCount); |
|||
|
|||
var paramArray = serializer.IsReading ? new KeyValuePair<int, int>[paramCount] : IntParameters.ToArray(); |
|||
|
|||
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) |
|||
{ |
|||
var paramId = serializer.IsReading ? 0 : paramArray[paramIndex].Key; |
|||
serializer.Serialize(ref paramId); |
|||
|
|||
var paramInt = serializer.IsReading ? 0 : paramArray[paramIndex].Value; |
|||
serializer.Serialize(ref paramInt); |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
paramArray[paramIndex] = new KeyValuePair<int, int>(paramId, paramInt); |
|||
} |
|||
} |
|||
|
|||
if (serializer.IsReading) |
|||
{ |
|||
IntParameters = paramArray.ToDictionary(pair => pair.Key, pair => pair.Value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private struct LayerState |
|||
{ |
|||
public int StateHash; |
|||
public float NormalizedStateTime; |
|||
public float LayerWeight; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Server authority only allows the server to update this animator
|
|||
/// Client authority only allows the client owner to update this animator
|
|||
/// </summary>
|
|||
public enum Authority |
|||
{ |
|||
Server = 0, |
|||
Owner |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This constant is used to force the resync if the delta between current
|
|||
/// and last synced normalized state time goes above it
|
|||
/// </summary>
|
|||
private const float k_NormalizedTimeResyncThreshold = 0.15f; |
|||
|
|||
/// <summary>
|
|||
/// Specifies who can update this animator
|
|||
/// </summary>
|
|||
[Tooltip("Defines who can update this animator.")] |
|||
public Authority AnimatorAuthority = Authority.Owner; |
|||
|
|||
[SerializeField] |
|||
private float m_SendRate = 0.1f; |
|||
private double m_NextSendTime = 0.0f; |
|||
private bool m_ServerRequestsAnimationResync = false; |
|||
[SerializeField] |
|||
private Animator m_Animator; |
|||
|
|||
private AnimatorSnapshot m_AnimatorSnapshot; |
|||
private List<(int, AnimatorControllerParameterType)> m_CachedAnimatorParameters; |
|||
|
|||
public bool IsAuthorityOverAnimator => (IsClient && AnimatorAuthority == Authority.Owner && IsOwner) || (IsServer && AnimatorAuthority == Authority.Server); |
|||
|
|||
public override void OnNetworkSpawn() |
|||
{ |
|||
var parameters = m_Animator.parameters; |
|||
m_CachedAnimatorParameters = new List<(int, AnimatorControllerParameterType)>(parameters.Length); |
|||
|
|||
int intCount = 0; |
|||
int floatCount = 0; |
|||
int boolCount = 0; |
|||
|
|||
for (var i = 0; i < parameters.Length; i++) |
|||
{ |
|||
var parameter = parameters[i]; |
|||
|
|||
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash)) |
|||
{ |
|||
//we are ignoring parameters that are controlled by animation curves - syncing the layer states indirectly syncs the values that are driven by the animation curves
|
|||
continue; |
|||
} |
|||
|
|||
m_CachedAnimatorParameters.Add((parameter.nameHash, parameter.type)); |
|||
|
|||
switch (parameter.type) |
|||
{ |
|||
case AnimatorControllerParameterType.Float: |
|||
++floatCount; |
|||
break; |
|||
case AnimatorControllerParameterType.Int: |
|||
++intCount; |
|||
break; |
|||
case AnimatorControllerParameterType.Bool: |
|||
++boolCount; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
var intParameters = new Dictionary<int, int>(intCount); |
|||
var floatParameters = new Dictionary<int, float>(floatCount); |
|||
var boolParameters = new Dictionary<int, bool>(boolCount); |
|||
var triggerParameters = new HashSet<int>(); |
|||
var states = new LayerState[m_Animator.layerCount]; |
|||
|
|||
m_AnimatorSnapshot = new AnimatorSnapshot(boolParameters, floatParameters, intParameters, triggerParameters, states); |
|||
|
|||
if (!IsAuthorityOverAnimator) |
|||
{ |
|||
m_Animator.StopPlayback(); |
|||
} |
|||
} |
|||
|
|||
private void OnEnable() |
|||
{ |
|||
if (IsServer) |
|||
{ |
|||
NetworkManager.OnClientConnectedCallback += ServerOnClientConnectedCallback; |
|||
} |
|||
} |
|||
|
|||
private void OnDisable() |
|||
{ |
|||
if (IsServer) |
|||
{ |
|||
NetworkManager.OnClientConnectedCallback -= ServerOnClientConnectedCallback; |
|||
} |
|||
} |
|||
|
|||
private void ServerOnClientConnectedCallback(ulong clientId) |
|||
{ |
|||
if (IsAuthorityOverAnimator) |
|||
{ |
|||
m_ServerRequestsAnimationResync = true; |
|||
} |
|||
|
|||
var clientRpcParams = new ClientRpcParams |
|||
{ |
|||
Send = new ClientRpcSendParams |
|||
{ |
|||
TargetClientIds = NetworkManager.ConnectedClientsList |
|||
.Where(c => c.ClientId != NetworkManager.ServerClientId) |
|||
.Select(c => c.ClientId) |
|||
.ToArray() |
|||
} |
|||
}; |
|||
|
|||
RequestResyncClientRpc(clientRpcParams); |
|||
} |
|||
|
|||
|
|||
[ClientRpc] |
|||
private void RequestResyncClientRpc(ClientRpcParams clientRpcParams = default) |
|||
{ |
|||
if (!IsAuthorityOverAnimator) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
m_ServerRequestsAnimationResync = true; |
|||
} |
|||
|
|||
private void FixedUpdate() |
|||
{ |
|||
if (!NetworkObject.IsSpawned) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (IsAuthorityOverAnimator) |
|||
{ |
|||
bool shouldSendBasedOnTime = CheckSendRate(); |
|||
bool shouldSendBasedOnChanges = StoreState(); |
|||
if (m_ServerRequestsAnimationResync || shouldSendBasedOnTime || shouldSendBasedOnChanges) |
|||
{ |
|||
SendAllParamsAndState(); |
|||
m_AnimatorSnapshot.TriggerParameters.Clear(); |
|||
m_ServerRequestsAnimationResync = false; |
|||
} |
|||
} |
|||
} |
|||
|
|||
private bool CheckSendRate() |
|||
{ |
|||
var networkTime = NetworkManager.LocalTime.FixedTime; |
|||
if (m_SendRate != 0 && m_NextSendTime < networkTime) |
|||
{ |
|||
m_NextSendTime = networkTime + m_SendRate; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
private bool StoreState() |
|||
{ |
|||
bool layerStateChanged = StoreLayerState(); |
|||
bool animatorParametersChanged = StoreParameters(); |
|||
|
|||
return layerStateChanged || animatorParametersChanged; |
|||
} |
|||
|
|||
private bool StoreLayerState() |
|||
{ |
|||
bool changed = false; |
|||
|
|||
for (int i = 0; i < m_AnimatorSnapshot.LayerStates.Length; i++) |
|||
{ |
|||
var animStateInfo = m_Animator.GetCurrentAnimatorStateInfo(i); |
|||
|
|||
var snapshotAnimStateInfo = m_AnimatorSnapshot.LayerStates[i]; |
|||
bool didStateChange = snapshotAnimStateInfo.StateHash != animStateInfo.fullPathHash; |
|||
bool enoughDelta = !didStateChange && |
|||
Mathf.Abs(animStateInfo.normalizedTime - snapshotAnimStateInfo.NormalizedStateTime) >= k_NormalizedTimeResyncThreshold; |
|||
|
|||
float newLayerWeight = m_Animator.GetLayerWeight(i); |
|||
bool layerWeightChanged = Mathf.Abs(snapshotAnimStateInfo.LayerWeight - newLayerWeight) > Mathf.Epsilon; |
|||
|
|||
if (didStateChange || enoughDelta || layerWeightChanged || m_ServerRequestsAnimationResync) |
|||
{ |
|||
m_AnimatorSnapshot.LayerStates[i] = new LayerState |
|||
{ |
|||
StateHash = animStateInfo.fullPathHash, |
|||
NormalizedStateTime = animStateInfo.normalizedTime, |
|||
LayerWeight = newLayerWeight |
|||
}; |
|||
changed = true; |
|||
} |
|||
} |
|||
|
|||
return changed; |
|||
} |
|||
|
|||
private bool StoreParameters() |
|||
{ |
|||
bool changed = false; |
|||
foreach (var animParam in m_CachedAnimatorParameters) |
|||
{ |
|||
var animParamHash = animParam.Item1; |
|||
var animParamType = animParam.Item2; |
|||
|
|||
switch (animParamType) |
|||
{ |
|||
case AnimatorControllerParameterType.Float: |
|||
changed = changed || m_AnimatorSnapshot.SetFloat(animParamHash, m_Animator.GetFloat(animParamHash)); |
|||
break; |
|||
case AnimatorControllerParameterType.Int: |
|||
changed = changed || m_AnimatorSnapshot.SetInt(animParamHash, m_Animator.GetInteger(animParamHash)); |
|||
break; |
|||
case AnimatorControllerParameterType.Bool: |
|||
changed = changed || m_AnimatorSnapshot.SetBool(animParamHash, m_Animator.GetBool(animParamHash)); |
|||
break; |
|||
case AnimatorControllerParameterType.Trigger: |
|||
if (m_Animator.GetBool(animParamHash)) |
|||
{ |
|||
changed = changed || m_AnimatorSnapshot.SetTrigger(animParamHash); |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return changed; |
|||
} |
|||
|
|||
private void SendAllParamsAndState() |
|||
{ |
|||
if (IsServer) |
|||
{ |
|||
var clientRpcParams = new ClientRpcParams |
|||
{ |
|||
Send = new ClientRpcSendParams |
|||
{ |
|||
TargetClientIds = NetworkManager.ConnectedClientsList |
|||
.Where(c => c.ClientId != NetworkManager.ServerClientId) |
|||
.Select(c => c.ClientId) |
|||
.ToArray() |
|||
} |
|||
}; |
|||
|
|||
SendParamsAndLayerStatesClientRpc(m_AnimatorSnapshot, clientRpcParams); |
|||
} |
|||
else |
|||
{ |
|||
SendParamsAndLayerStatesServerRpc(m_AnimatorSnapshot); |
|||
} |
|||
} |
|||
|
|||
[ServerRpc] |
|||
private void SendParamsAndLayerStatesServerRpc(AnimatorSnapshot animSnapshot, ServerRpcParams serverRpcParams = default) |
|||
{ |
|||
if (!IsAuthorityOverAnimator) |
|||
{ |
|||
ApplyAnimatorSnapshot(animSnapshot); |
|||
} |
|||
|
|||
var clientRpcParams = new ClientRpcParams |
|||
{ |
|||
Send = new ClientRpcSendParams |
|||
{ |
|||
TargetClientIds = NetworkManager.ConnectedClientsList |
|||
.Where(c => c.ClientId != serverRpcParams.Receive.SenderClientId) |
|||
.Select(c => c.ClientId) |
|||
.ToArray() |
|||
} |
|||
}; |
|||
|
|||
SendParamsAndLayerStatesClientRpc(animSnapshot, clientRpcParams); |
|||
} |
|||
|
|||
[ClientRpc] |
|||
private void SendParamsAndLayerStatesClientRpc(AnimatorSnapshot animSnapshot, ClientRpcParams clientRpcParams = default) |
|||
{ |
|||
if (!IsAuthorityOverAnimator) |
|||
{ |
|||
ApplyAnimatorSnapshot(animSnapshot); |
|||
} |
|||
} |
|||
|
|||
private void ApplyAnimatorSnapshot(AnimatorSnapshot animatorSnapshot) |
|||
{ |
|||
foreach (var intParameter in animatorSnapshot.IntParameters) |
|||
{ |
|||
m_Animator.SetInteger(intParameter.Key, intParameter.Value); |
|||
} |
|||
|
|||
foreach (var floatParameter in animatorSnapshot.FloatParameters) |
|||
{ |
|||
m_Animator.SetFloat(floatParameter.Key, floatParameter.Value); |
|||
} |
|||
|
|||
foreach (var boolParameter in animatorSnapshot.BoolParameters) |
|||
{ |
|||
m_Animator.SetBool(boolParameter.Key, boolParameter.Value); |
|||
} |
|||
|
|||
foreach (var triggerParameter in animatorSnapshot.TriggerParameters) |
|||
{ |
|||
m_Animator.SetTrigger(triggerParameter); |
|||
} |
|||
|
|||
for (var layerIndex = 0; layerIndex < animatorSnapshot.LayerStates.Length; layerIndex++) |
|||
{ |
|||
var layerState = animatorSnapshot.LayerStates[layerIndex]; |
|||
|
|||
m_Animator.SetLayerWeight(layerIndex, layerState.LayerWeight); |
|||
|
|||
var currentAnimatorState = m_Animator.GetCurrentAnimatorStateInfo(layerIndex); |
|||
|
|||
bool stateChanged = currentAnimatorState.fullPathHash != layerState.StateHash; |
|||
bool forceAnimationCatchup = !stateChanged && |
|||
Mathf.Abs(layerState.NormalizedStateTime - currentAnimatorState.normalizedTime) >= k_NormalizedTimeResyncThreshold; |
|||
|
|||
if (stateChanged || forceAnimationCatchup) |
|||
{ |
|||
m_Animator.Play(layerState.StateHash, layerIndex, layerState.NormalizedStateTime); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e8d0727d5ae3244e3b569694d3912374 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Collections.Generic; |
|||
using UnityEngine; |
|||
using UnityEngine.AI; |
|||
|
|||
namespace Unity.Netcode.Prototyping |
|||
{ |
|||
/// <summary>
|
|||
/// A prototype component for syncing NavMeshAgents
|
|||
/// </summary>
|
|||
[AddComponentMenu("Netcode/" + nameof(NetworkNavMeshAgent))] |
|||
[RequireComponent(typeof(NavMeshAgent))] |
|||
public class NetworkNavMeshAgent : NetworkBehaviour |
|||
{ |
|||
private NavMeshAgent m_Agent; |
|||
|
|||
/// <summary>
|
|||
/// Is proximity enabled
|
|||
/// </summary>
|
|||
public bool EnableProximity = false; |
|||
|
|||
/// <summary>
|
|||
/// The proximity range
|
|||
/// </summary>
|
|||
public float ProximityRange = 50f; |
|||
|
|||
/// <summary>
|
|||
/// The delay in seconds between corrections
|
|||
/// </summary>
|
|||
public float CorrectionDelay = 3f; |
|||
|
|||
//TODO rephrase.
|
|||
/// <summary>
|
|||
/// The percentage to lerp on corrections
|
|||
/// </summary>
|
|||
[Tooltip("Everytime a correction packet is received. This is the percentage (between 0 & 1) that we will move towards the goal.")] |
|||
public float DriftCorrectionPercentage = 0.1f; |
|||
|
|||
/// <summary>
|
|||
/// Should we warp on destination change
|
|||
/// </summary>
|
|||
public bool WarpOnDestinationChange = false; |
|||
|
|||
private void Awake() |
|||
{ |
|||
m_Agent = GetComponent<NavMeshAgent>(); |
|||
} |
|||
|
|||
private Vector3 m_LastDestination = Vector3.zero; |
|||
private double m_LastCorrectionTime = 0d; |
|||
|
|||
private void Update() |
|||
{ |
|||
if (!IsOwner) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (m_Agent.destination != m_LastDestination) |
|||
{ |
|||
m_LastDestination = m_Agent.destination; |
|||
if (!EnableProximity) |
|||
{ |
|||
OnNavMeshStateUpdateClientRpc(m_Agent.destination, m_Agent.velocity, transform.position); |
|||
} |
|||
else |
|||
{ |
|||
var proximityClients = new List<ulong>(); |
|||
foreach (KeyValuePair<ulong, NetworkClient> client in NetworkManager.ConnectedClients) |
|||
{ |
|||
if (client.Value.PlayerObject == null || Vector3.Distance(client.Value.PlayerObject.transform.position, transform.position) <= ProximityRange) |
|||
{ |
|||
proximityClients.Add(client.Key); |
|||
} |
|||
} |
|||
|
|||
OnNavMeshStateUpdateClientRpc(m_Agent.destination, m_Agent.velocity, transform.position, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = proximityClients.ToArray() } }); |
|||
} |
|||
} |
|||
|
|||
if (NetworkManager.LocalTime.Time - m_LastCorrectionTime >= CorrectionDelay) // TODO this is not aliased correctly, is this an issue?
|
|||
{ |
|||
if (!EnableProximity) |
|||
{ |
|||
OnNavMeshCorrectionUpdateClientRpc(m_Agent.velocity, transform.position); |
|||
} |
|||
else |
|||
{ |
|||
var proximityClients = new List<ulong>(); |
|||
foreach (KeyValuePair<ulong, NetworkClient> client in NetworkManager.ConnectedClients) |
|||
{ |
|||
if (client.Value.PlayerObject == null || Vector3.Distance(client.Value.PlayerObject.transform.position, transform.position) <= ProximityRange) |
|||
{ |
|||
proximityClients.Add(client.Key); |
|||
} |
|||
} |
|||
|
|||
OnNavMeshCorrectionUpdateClientRpc(m_Agent.velocity, transform.position, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = proximityClients.ToArray() } }); |
|||
} |
|||
|
|||
m_LastCorrectionTime = NetworkManager.LocalTime.Time; |
|||
} |
|||
} |
|||
|
|||
[ClientRpc] |
|||
private void OnNavMeshStateUpdateClientRpc(Vector3 destination, Vector3 velocity, Vector3 position, ClientRpcParams rpcParams = default) |
|||
{ |
|||
m_Agent.Warp(WarpOnDestinationChange ? position : Vector3.Lerp(transform.position, position, DriftCorrectionPercentage)); |
|||
m_Agent.SetDestination(destination); |
|||
m_Agent.velocity = velocity; |
|||
} |
|||
|
|||
[ClientRpc] |
|||
private void OnNavMeshCorrectionUpdateClientRpc(Vector3 velocity, Vector3 position, ClientRpcParams rpcParams = default) |
|||
{ |
|||
m_Agent.Warp(Vector3.Lerp(transform.position, position, DriftCorrectionPercentage)); |
|||
m_Agent.velocity = velocity; |
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 43b86c65774f4494ba4e5878d5df9bcd |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
using UnityEngine; |
|||
|
|||
namespace Unity.Netcode.Prototyping |
|||
{ |
|||
/// <summary>
|
|||
/// A prototype component for syncing transforms
|
|||
/// </summary>
|
|||
[AddComponentMenu("Netcode/" + nameof(NetworkTransform))] |
|||
public class NetworkTransform : NetworkBehaviour |
|||
{ |
|||
internal struct NetworkState : INetworkSerializable |
|||
{ |
|||
internal const int InLocalSpaceBit = 0; |
|||
internal const int PositionXBit = 1; |
|||
internal const int PositionYBit = 2; |
|||
internal const int PositionZBit = 3; |
|||
internal const int RotAngleXBit = 4; |
|||
internal const int RotAngleYBit = 5; |
|||
internal const int RotAngleZBit = 6; |
|||
internal const int ScaleXBit = 7; |
|||
internal const int ScaleYBit = 8; |
|||
internal const int ScaleZBit = 9; |
|||
// 10-15: <unused>
|
|||
public ushort Bitset; |
|||
|
|||
public bool InLocalSpace |
|||
{ |
|||
get => (Bitset & (1 << InLocalSpaceBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << InLocalSpaceBit); |
|||
} |
|||
// Position
|
|||
public bool HasPositionX |
|||
{ |
|||
get => (Bitset & (1 << PositionXBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << PositionXBit); |
|||
} |
|||
public bool HasPositionY |
|||
{ |
|||
get => (Bitset & (1 << PositionYBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << PositionYBit); |
|||
} |
|||
public bool HasPositionZ |
|||
{ |
|||
get => (Bitset & (1 << PositionZBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << PositionZBit); |
|||
} |
|||
// RotAngles
|
|||
public bool HasRotAngleX |
|||
{ |
|||
get => (Bitset & (1 << RotAngleXBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << RotAngleXBit); |
|||
} |
|||
public bool HasRotAngleY |
|||
{ |
|||
get => (Bitset & (1 << RotAngleYBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << RotAngleYBit); |
|||
} |
|||
public bool HasRotAngleZ |
|||
{ |
|||
get => (Bitset & (1 << RotAngleZBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << RotAngleZBit); |
|||
} |
|||
// Scale
|
|||
public bool HasScaleX |
|||
{ |
|||
get => (Bitset & (1 << ScaleXBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << ScaleXBit); |
|||
} |
|||
public bool HasScaleY |
|||
{ |
|||
get => (Bitset & (1 << ScaleYBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << ScaleYBit); |
|||
} |
|||
public bool HasScaleZ |
|||
{ |
|||
get => (Bitset & (1 << ScaleZBit)) != 0; |
|||
set => Bitset |= (ushort)((value ? 1 : 0) << ScaleZBit); |
|||
} |
|||
|
|||
public float PositionX, PositionY, PositionZ; |
|||
public float RotAngleX, RotAngleY, RotAngleZ; |
|||
public float ScaleX, ScaleY, ScaleZ; |
|||
|
|||
public void NetworkSerialize(NetworkSerializer serializer) |
|||
{ |
|||
// InLocalSpace + HasXXX Bits
|
|||
serializer.Serialize(ref Bitset); |
|||
// Position Values
|
|||
if (HasPositionX) |
|||
{ |
|||
serializer.Serialize(ref PositionX); |
|||
} |
|||
if (HasPositionY) |
|||
{ |
|||
serializer.Serialize(ref PositionY); |
|||
} |
|||
if (HasPositionZ) |
|||
{ |
|||
serializer.Serialize(ref PositionZ); |
|||
} |
|||
// RotAngle Values
|
|||
if (HasRotAngleX) |
|||
{ |
|||
serializer.Serialize(ref RotAngleX); |
|||
} |
|||
if (HasRotAngleY) |
|||
{ |
|||
serializer.Serialize(ref RotAngleY); |
|||
} |
|||
if (HasRotAngleZ) |
|||
{ |
|||
serializer.Serialize(ref RotAngleZ); |
|||
} |
|||
// Scale Values
|
|||
if (HasScaleX) |
|||
{ |
|||
serializer.Serialize(ref ScaleX); |
|||
} |
|||
if (HasScaleY) |
|||
{ |
|||
serializer.Serialize(ref ScaleY); |
|||
} |
|||
if (HasScaleZ) |
|||
{ |
|||
serializer.Serialize(ref ScaleZ); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool SyncPositionX = true, SyncPositionY = true, SyncPositionZ = true; |
|||
public bool SyncRotAngleX = true, SyncRotAngleY = true, SyncRotAngleZ = true; |
|||
public bool SyncScaleX = true, SyncScaleY = true, SyncScaleZ = true; |
|||
|
|||
public float PositionThreshold, RotAngleThreshold, ScaleThreshold; |
|||
|
|||
/// <summary>
|
|||
/// Sets whether this transform should sync in local space or in world space.
|
|||
/// This is important to set since reparenting this transform could have issues,
|
|||
/// if using world position (depending on who gets synced first: the parent or the child)
|
|||
/// Having a child always at position 0,0,0 for example will have less possibilities of desync than when using world positions
|
|||
/// </summary>
|
|||
[Tooltip("Sets whether this transform should sync in local space or in world space")] |
|||
public bool InLocalSpace = false; |
|||
|
|||
// todo: revisit after MTT-876
|
|||
public bool Interpolate = true; |
|||
|
|||
/// <summary>
|
|||
/// The base amount of sends per seconds to use when range is disabled
|
|||
/// </summary>
|
|||
[SerializeField, Range(0, 120), Tooltip("The base amount of sends per seconds to use when range is disabled")] |
|||
public float FixedSendsPerSecond = 30f; |
|||
|
|||
private Transform m_Transform; // cache the transform component to reduce unnecessary bounce between managed and native
|
|||
internal NetworkState LocalNetworkState; |
|||
internal readonly NetworkVariable<NetworkState> ReplNetworkState = new NetworkVariable<NetworkState>(new NetworkState()); |
|||
internal NetworkState PrevNetworkState; |
|||
|
|||
// updates `NetworkState` properties if they need to and returns a `bool` indicating whether or not there was any changes made
|
|||
// returned boolean would be useful to change encapsulating `NetworkVariable<NetworkState>`'s dirty state, e.g. ReplNetworkState.SetDirty(isDirty);
|
|||
internal bool UpdateNetworkState(ref NetworkState networkState) |
|||
{ |
|||
var position = InLocalSpace ? m_Transform.localPosition : m_Transform.position; |
|||
var rotAngles = InLocalSpace ? m_Transform.localEulerAngles : m_Transform.eulerAngles; |
|||
var scale = InLocalSpace ? m_Transform.localScale : m_Transform.lossyScale; |
|||
|
|||
bool isDirty = false; |
|||
|
|||
if (InLocalSpace != networkState.InLocalSpace) |
|||
{ |
|||
networkState.InLocalSpace = InLocalSpace; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncPositionX && |
|||
Mathf.Abs(networkState.PositionX - position.x) >= PositionThreshold && |
|||
!Mathf.Approximately(networkState.PositionX, position.x)) |
|||
{ |
|||
networkState.PositionX = position.x; |
|||
networkState.HasPositionX = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncPositionY && |
|||
Mathf.Abs(networkState.PositionY - position.y) >= PositionThreshold && |
|||
!Mathf.Approximately(networkState.PositionY, position.y)) |
|||
{ |
|||
networkState.PositionY = position.y; |
|||
networkState.HasPositionY = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncPositionZ && |
|||
Mathf.Abs(networkState.PositionZ - position.z) >= PositionThreshold && |
|||
!Mathf.Approximately(networkState.PositionZ, position.z)) |
|||
{ |
|||
networkState.PositionZ = position.z; |
|||
networkState.HasPositionZ = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncRotAngleX && |
|||
Mathf.Abs(networkState.RotAngleX - rotAngles.x) >= RotAngleThreshold && |
|||
!Mathf.Approximately(networkState.RotAngleX, rotAngles.x)) |
|||
{ |
|||
networkState.RotAngleX = rotAngles.x; |
|||
networkState.HasRotAngleX = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncRotAngleY && |
|||
Mathf.Abs(networkState.RotAngleY - rotAngles.y) >= RotAngleThreshold && |
|||
!Mathf.Approximately(networkState.RotAngleY, rotAngles.y)) |
|||
{ |
|||
networkState.RotAngleY = rotAngles.y; |
|||
networkState.HasRotAngleY = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncRotAngleZ && |
|||
Mathf.Abs(networkState.RotAngleZ - rotAngles.z) >= RotAngleThreshold && |
|||
!Mathf.Approximately(networkState.RotAngleZ, rotAngles.z)) |
|||
{ |
|||
networkState.RotAngleZ = rotAngles.z; |
|||
networkState.HasRotAngleZ = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncScaleX && |
|||
Mathf.Abs(networkState.ScaleX - scale.x) >= ScaleThreshold && |
|||
!Mathf.Approximately(networkState.ScaleX, scale.x)) |
|||
{ |
|||
networkState.ScaleX = scale.x; |
|||
networkState.HasScaleX = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncScaleY && |
|||
Mathf.Abs(networkState.ScaleY - scale.y) >= ScaleThreshold && |
|||
!Mathf.Approximately(networkState.ScaleY, scale.y)) |
|||
{ |
|||
networkState.ScaleY = scale.y; |
|||
networkState.HasScaleY = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
if (SyncScaleZ && |
|||
Mathf.Abs(networkState.ScaleZ - scale.z) >= ScaleThreshold && |
|||
!Mathf.Approximately(networkState.ScaleZ, scale.z)) |
|||
{ |
|||
networkState.ScaleZ = scale.z; |
|||
networkState.HasScaleZ = true; |
|||
isDirty |= true; |
|||
} |
|||
|
|||
return isDirty; |
|||
} |
|||
|
|||
// TODO: temporary! the function body below probably needs to be rewritten later with interpolation in mind
|
|||
internal void ApplyNetworkState(NetworkState networkState) |
|||
{ |
|||
PrevNetworkState = networkState; |
|||
|
|||
var position = InLocalSpace ? m_Transform.localPosition : m_Transform.position; |
|||
var rotAngles = InLocalSpace ? m_Transform.localEulerAngles : m_Transform.eulerAngles; |
|||
var scale = InLocalSpace ? m_Transform.localScale : m_Transform.lossyScale; |
|||
|
|||
// InLocalSpace Read
|
|||
InLocalSpace = networkState.InLocalSpace; |
|||
// Position Read
|
|||
if (networkState.HasPositionX) |
|||
{ |
|||
position.x = networkState.PositionX; |
|||
} |
|||
if (networkState.HasPositionY) |
|||
{ |
|||
position.y = networkState.PositionY; |
|||
} |
|||
if (networkState.HasPositionZ) |
|||
{ |
|||
position.z = networkState.PositionZ; |
|||
} |
|||
// RotAngles Read
|
|||
if (networkState.HasRotAngleX) |
|||
{ |
|||
rotAngles.x = networkState.RotAngleX; |
|||
} |
|||
if (networkState.HasRotAngleY) |
|||
{ |
|||
rotAngles.y = networkState.RotAngleY; |
|||
} |
|||
if (networkState.HasRotAngleZ) |
|||
{ |
|||
rotAngles.z = networkState.RotAngleZ; |
|||
} |
|||
// Scale Read
|
|||
if (networkState.HasScaleX) |
|||
{ |
|||
scale.x = networkState.ScaleX; |
|||
} |
|||
if (networkState.HasScaleY) |
|||
{ |
|||
scale.y = networkState.ScaleY; |
|||
} |
|||
if (networkState.HasScaleZ) |
|||
{ |
|||
scale.z = networkState.ScaleZ; |
|||
} |
|||
|
|||
// Position Apply
|
|||
if (networkState.HasPositionX || networkState.HasPositionY || networkState.HasPositionZ) |
|||
{ |
|||
if (InLocalSpace) |
|||
{ |
|||
m_Transform.localPosition = position; |
|||
} |
|||
else |
|||
{ |
|||
m_Transform.position = position; |
|||
} |
|||
} |
|||
// RotAngles Apply
|
|||
if (networkState.HasRotAngleX || networkState.HasRotAngleY || networkState.HasRotAngleZ) |
|||
{ |
|||
if (InLocalSpace) |
|||
{ |
|||
m_Transform.localEulerAngles = rotAngles; |
|||
} |
|||
else |
|||
{ |
|||
m_Transform.eulerAngles = rotAngles; |
|||
} |
|||
} |
|||
// Scale Apply
|
|||
if (networkState.HasScaleX || networkState.HasScaleY || networkState.HasScaleZ) |
|||
{ |
|||
if (InLocalSpace) |
|||
{ |
|||
m_Transform.localScale = scale; |
|||
} |
|||
else |
|||
{ |
|||
m_Transform.localScale = Vector3.one; |
|||
var lossyScale = m_Transform.lossyScale; |
|||
m_Transform.localScale = new Vector3(networkState.ScaleX / lossyScale.x, networkState.ScaleY / lossyScale.y, networkState.ScaleZ / lossyScale.z); |
|||
} |
|||
} |
|||
} |
|||
|
|||
private void OnNetworkStateChanged(NetworkState oldState, NetworkState newState) |
|||
{ |
|||
if (!NetworkObject.IsSpawned) |
|||
{ |
|||
// todo MTT-849 should never happen but yet it does! maybe revisit/dig after NetVar updates and snapshot system lands?
|
|||
return; |
|||
} |
|||
|
|||
ApplyNetworkState(newState); |
|||
} |
|||
|
|||
private void Awake() |
|||
{ |
|||
m_Transform = transform; |
|||
ReplNetworkState.OnValueChanged += OnNetworkStateChanged; |
|||
} |
|||
|
|||
private void OnDestroy() |
|||
{ |
|||
ReplNetworkState.OnValueChanged -= OnNetworkStateChanged; |
|||
} |
|||
|
|||
private void FixedUpdate() |
|||
{ |
|||
if (!NetworkObject.IsSpawned) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
if (IsServer) |
|||
{ |
|||
// try to update local NetworkState
|
|||
if (UpdateNetworkState(ref LocalNetworkState)) |
|||
{ |
|||
// if updated (dirty), change NetVar, mark it dirty
|
|||
ReplNetworkState.Value = LocalNetworkState; |
|||
ReplNetworkState.SetDirty(true); |
|||
} |
|||
} |
|||
// try to update previously consumed NetworkState
|
|||
// if we have any changes, that means made some updates locally
|
|||
// we apply the latest ReplNetworkState again to revert our changes
|
|||
else if (UpdateNetworkState(ref PrevNetworkState)) |
|||
{ |
|||
ApplyNetworkState(ReplNetworkState.Value); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Teleports the transform to the given values without interpolating
|
|||
/// </summary>
|
|||
public void Teleport(Vector3 newPosition, Quaternion newRotation, Vector3 newScale) |
|||
{ |
|||
throw new NotImplementedException(); // TODO MTT-769
|
|||
} |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: e96cb6065543e43c4a752faaa1468eb1 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
{ |
|||
"name": "Unity.Netcode.Prototyping", |
|||
"rootNamespace": "Unity.Netcode.Prototyping", |
|||
"references": [ |
|||
"Unity.Netcode.Runtime" |
|||
] |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: 3b8ed52f1b5c64994af4c4e0aa4b6c4b |
|||
AssemblyDefinitionImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
[![](https://i.imgur.com/d0amtqs.png)](https://mlapi.network/) |
|||
|
|||
[![GitHub Release](https://img.shields.io/github/release/MidLevel/MLAPI.svg?logo=github)](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/releases/latest) |
|||
[![Github All Releases](https://img.shields.io/github/downloads/MidLevel/MLAPI/total.svg?logo=github&color=informational)](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/releases) |
|||
|
|||
[![Forums](https://img.shields.io/badge/unity--forums-multiplayer-blue)](https://forum.unity.com/forums/multiplayer.26/) |
|||
[![Discord](https://img.shields.io/discord/449263083769036810.svg?label=discord&logo=discord&color=informational)](https://discord.gg/FM8SE9E) |
|||
|
|||
|
|||
[![Licence](https://img.shields.io/github/license/midlevel/mlapi.svg?color=informational)](https://github.com/MidLevel/MLAPI/blob/master/LICENSE) |
|||
[![Website](https://img.shields.io/badge/docs-website-informational.svg)](https://docs-multiplayer.unity3d.com/) |
|||
[![Api](https://img.shields.io/badge/docs-api-informational.svg)](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction) |
|||
|
|||
|
|||
The Unity MLAPI (Mid level API) is a framework that simplifies building networked games in Unity. It offers **low level** access to core networking while at the same time providing **high level** abstractions. The MLAPI aims to remove the repetitive tasks and reduces the network code dramatically, no matter how many of the **modular** features you use. |
|||
|
|||
### Getting Started |
|||
To get started, check the [Multiplayer Docs Site](https://docs-multiplayer.unity3d.com/). |
|||
|
|||
### Community and Feedback |
|||
For general questions, networking advice or discussions about MLAPI, please join our [Discord Community](https://discord.gg/FM8SE9E) or create a post in the [Unity Multiplayer Forum](https://forum.unity.com/forums/multiplayer.26/). |
|||
|
|||
### Compatibility |
|||
The MLAPI supports all major Unity platforms. To use the WebGL platform a custom WebGL transport based on web sockets is needed. |
|||
|
|||
MLAPI is compatible with Unity 2019 and newer versions. |
|||
|
|||
### Development |
|||
We follow the [Gitflow Workflow](https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow). The master branch contains our latest stable release version while the develop branch tracks our current work. |
|||
|
|||
This repository is broken into multiple components, each one implemented as a Unity Package. |
|||
``` |
|||
. |
|||
├── com.unity.multiplayer.mlapi # The core netcode SDK unity package (source + tests) |
|||
├── com.unity.multiplayer.transport.utp # Transport wrapper for com.unity.transport experimental package (not currently supported) |
|||
└── testproject # A Unity project with various test implementations & scenes which exercise the features in the above package(s). |
|||
``` |
|||
|
|||
### Contributing |
|||
The MLAPI is an open-source project and we encourage and welcome |
|||
contributions. If you wish to contribute, be sure to review our |
|||
[contribution guidelines](CONTRIBUTING.md) |
|||
|
|||
### Issues and missing features |
|||
If you have an issue, bug or feature request, please follow the information in our [contribution guidelines](CONTRIBUTING.md) to submit an issue. |
|||
|
|||
### Example |
|||
Here is a sample MonoBehaviour showing a chat script where everyone can write and read from. This shows the basis of the MLAPI and the abstractions it adds. |
|||
|
|||
```csharp |
|||
public class Chat : NetworkBehaviour |
|||
{ |
|||
private NetworkList<string> ChatMessages = new NetworkList<string>(new MLAPI.NetworkVariable.NetworkVariableSettings() |
|||
{ |
|||
ReadPermission = MLAPI.NetworkVariable.NetworkVariablePermission.Everyone, |
|||
WritePermission = MLAPI.NetworkVariable.NetworkVariablePermission.Everyone, |
|||
SendTickrate = 5 |
|||
}, new List<string>()); |
|||
|
|||
private string textField = ""; |
|||
|
|||
private void OnGUI() |
|||
{ |
|||
if (IsClient) |
|||
{ |
|||
textField = GUILayout.TextField(textField, GUILayout.Width(200)); |
|||
|
|||
if (GUILayout.Button("Send") && !string.IsNullOrWhiteSpace(textField)) |
|||
{ |
|||
ChatMessages.Add(textField); |
|||
textField = ""; |
|||
} |
|||
|
|||
for (int i = ChatMessages.Count - 1; i >= 0; i--) |
|||
{ |
|||
GUILayout.Label(ChatMessages[i]); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
### License |
|||
[MIT License](LICENSE) |
|
|||
fileFormatVersion: 2 |
|||
guid: 7a8193086d1cd7d4dadc6a324430350b |
|||
TextScriptImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: c83905999e4e71246b5a3ebf2565faf7 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System.Runtime.CompilerServices; |
|||
|
|||
#if UNITY_EDITOR
|
|||
[assembly: InternalsVisibleTo("Unity.Netcode.EditorTests")] |
|||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")] |
|||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")] |
|||
[assembly: InternalsVisibleTo("TestProject.EditorTests")] |
|||
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] |
|||
#endif
|
|||
[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] |
|
|||
fileFormatVersion: 2 |
|||
guid: c9fd74adf4a0f6e479be3978543dc6a0 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: f2ef964afcae91248b2298b479ed1b53 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
using System; |
|||
|
|||
namespace Unity.Netcode |
|||
{ |
|||
/// <summary>
|
|||
/// Queue with a fixed size
|
|||
/// </summary>
|
|||
/// <typeparam name="T">The type of the queue</typeparam>
|
|||
public sealed class FixedQueue<T> |
|||
{ |
|||
private readonly T[] m_Queue; |
|||
private int m_QueueCount = 0; |
|||
private int m_QueueStart; |
|||
|
|||
/// <summary>
|
|||
/// The amount of enqueued objects
|
|||
/// </summary>
|
|||
public int Count => m_QueueCount; |
|||
|
|||
/// <summary>
|
|||
/// Gets the element at a given virtual index
|
|||
/// </summary>
|
|||
/// <param name="index">The virtual index to get the item from</param>
|
|||
/// <returns>The element at the virtual index</returns>
|
|||
public T this[int index] => m_Queue[(m_QueueStart + index) % m_Queue.Length]; |
|||
|
|||
/// <summary>
|
|||
/// Creates a new FixedQueue with a given size
|
|||
/// </summary>
|
|||
/// <param name="maxSize">The size of the queue</param>
|
|||
public FixedQueue(int maxSize) |
|||
{ |
|||
m_Queue = new T[maxSize]; |
|||
m_QueueStart = 0; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Enqueues an object
|
|||
/// </summary>
|
|||
/// <param name="t"></param>
|
|||
/// <returns></returns>
|
|||
public bool Enqueue(T t) |
|||
{ |
|||
m_Queue[(m_QueueStart + m_QueueCount) % m_Queue.Length] = t; |
|||
if (++m_QueueCount > m_Queue.Length) |
|||
{ |
|||
--m_QueueCount; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Dequeues an object
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public T Dequeue() |
|||
{ |
|||
if (--m_QueueCount == -1) |
|||
{ |
|||
throw new IndexOutOfRangeException("Cannot dequeue empty queue!"); |
|||
} |
|||
|
|||
T res = m_Queue[m_QueueStart]; |
|||
m_QueueStart = (m_QueueStart + 1) % m_Queue.Length; |
|||
return res; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the element at a given virtual index
|
|||
/// </summary>
|
|||
/// <param name="index">The virtual index to get the item from</param>
|
|||
/// <returns>The element at the virtual index</returns>
|
|||
public T ElementAt(int index) => m_Queue[(m_QueueStart + index) % m_Queue.Length]; |
|||
} |
|||
} |
|
|||
fileFormatVersion: 2 |
|||
guid: a8514b4eca0c7044d9b92faf9407ec93 |
|||
MonoImporter: |
|||
externalObjects: {} |
|||
serializedVersion: 2 |
|||
defaultReferences: [] |
|||
executionOrder: 0 |
|||
icon: {instanceID: 0} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
|
|||
fileFormatVersion: 2 |
|||
guid: d3cc7700dfd03ee4397858710461d179 |
|||
folderAsset: yes |
|||
DefaultImporter: |
|||
externalObjects: {} |
|||
userData: |
|||
assetBundleName: |
|||
assetBundleVariant: |
部分文件因为文件数量过多而无法显示
撰写
预览
正在加载...
取消
保存
Reference in new issue