Skip to content

feat: network physics #1175

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Sep 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions com.unity.netcode.gameobjects/Components/NetworkRigidbody.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using UnityEngine;

namespace Unity.Netcode.Components
{
/// <summary>
/// NetworkRigidbody allows for the use of <see cref="Rigidbody"/> on network objects. By controlling the kinematic
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
/// </summary>
[RequireComponent(typeof(Rigidbody))]
[RequireComponent(typeof(NetworkTransform))]
public class NetworkRigidbody : NetworkBehaviour
{
private Rigidbody m_Rigidbody;

private bool m_OriginalKinematic;

// Used to cache the authority state of this rigidbody during the last frame
private bool m_IsAuthority;

/// <summary>
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody"/> on this peer currently holds authority.
/// </summary>
internal bool HasAuthority => NetworkManager.IsServer; // TODO update this once we support owner authoritative NetworkTransform.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this change your plans now that we'll have ClientNetworkTransform?


private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody>();
}

// Currently commented out because it is not needed as authority currently can't change at runtime.
// private void FixedUpdate()
// {
// if (NetworkManager.IsListening)
// {
// if (HasAuthority != m_IsAuthority)
// {
// m_IsAuthority = HasAuthority;
// UpdateRigidbodyKinematicMode();
// }
// }
// }

// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
private void UpdateRigidbodyKinematicMode()
{
if (m_IsAuthority == false)
{
m_OriginalKinematic = m_Rigidbody.isKinematic;
m_Rigidbody.isKinematic = true;
}
else
{
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
m_Rigidbody.isKinematic = m_OriginalKinematic;
}
}

/// <inheritdoc />
public override void OnNetworkSpawn()
{
m_IsAuthority = HasAuthority;
m_OriginalKinematic = m_Rigidbody.isKinematic;
UpdateRigidbodyKinematicMode();
}

/// <inheritdoc />
public override void OnNetworkDespawn()
{
UpdateRigidbodyKinematicMode();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions com.unity.netcode.gameobjects/Components/NetworkRigidbody2D.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using UnityEngine;

namespace Unity.Netcode.Components
{
/// <summary>
/// NetworkRigidbody allows for the use of <see cref="Rigidbody2D"/> on network objects. By controlling the kinematic
/// mode of the rigidbody and disabling it on all peers but the authoritative one.
/// </summary>
[RequireComponent(typeof(Rigidbody2D))]
[RequireComponent(typeof(NetworkTransform))]
public class NetworkRigidbody2D : NetworkBehaviour
{
private Rigidbody2D m_Rigidbody;

private bool m_OriginalKinematic;

// Used to cache the authority state of this rigidbody during the last frame
private bool m_IsAuthority;

/// <summary>
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody2D"/> on this peer currently holds authority.
/// </summary>
internal bool HasAuthority => NetworkManager.IsServer; // TODO update this once we support owner authoritative NetworkTransform.

private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody2D>();
}

// Currently commented out because it is not needed as authority currently can't change at runtime.
// private void FixedUpdate()
// {
// if (NetworkManager.IsListening)
// {
// if (HasAuthority != m_IsAuthority)
// {
// m_IsAuthority = HasAuthority;
// UpdateRigidbodyKinematicMode();
// }
// }
// }

// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
private void UpdateRigidbodyKinematicMode()
{
if (m_IsAuthority == false)
{
m_OriginalKinematic = m_Rigidbody.isKinematic;
m_Rigidbody.isKinematic = true;
}
else
{
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
m_Rigidbody.isKinematic = m_OriginalKinematic;
}
}

/// <inheritdoc />
public override void OnNetworkSpawn()
{
m_IsAuthority = HasAuthority;
m_OriginalKinematic = m_Rigidbody.isKinematic;
UpdateRigidbodyKinematicMode();
}

/// <inheritdoc />
public override void OnNetworkDespawn()
{
m_IsAuthority = false;
UpdateRigidbodyKinematicMode();
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions com.unity.netcode.gameobjects/Tests/Runtime/Physics.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;

namespace Unity.Netcode.RuntimeTests.Physics
{
public class NetworkRigidbody2DDynamicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => false;
}

public class NetworkRigidbody2DKinematicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => true;
}

public abstract class NetworkRigidbody2DTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 1;

public abstract bool Kinematic { get; }

[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
playerPrefab.AddComponent<NetworkTransform>();
playerPrefab.AddComponent<Rigidbody2D>();
playerPrefab.AddComponent<NetworkRigidbody>();
playerPrefab.GetComponent<Rigidbody2D>().isKinematic = Kinematic;
});
}

/// <summary>
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestRigidbodyKinematicEnableDisable()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
var serverPlayer = serverClientPlayerResult.Result.gameObject;

// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
var clientPlayer = clientClientPlayerResult.Result.gameObject;

Assert.IsNotNull(serverPlayer);
Assert.IsNotNull(clientPlayer);

int waitFor = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= waitFor);

// server rigidbody has authority and should have a kinematic mode of false
Assert.True(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);

// client rigidbody has no authority and should have a kinematic mode of true
Assert.True(clientPlayer.GetComponent<Rigidbody2D>().isKinematic);

// despawn the server player
serverPlayer.GetComponent<NetworkObject>().Despawn(false);

yield return null;

Assert.IsTrue(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);

yield return null;
Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned.
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;

namespace Unity.Netcode.RuntimeTests.Physics
{
public class NetworkRigidbodyDynamicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => false;
}

public class NetworkRigidbodyKinematicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => true;
}

public abstract class NetworkRigidbodyTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 1;

public abstract bool Kinematic { get; }

[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
playerPrefab.AddComponent<NetworkTransform>();
playerPrefab.AddComponent<Rigidbody>();
playerPrefab.AddComponent<NetworkRigidbody>();
playerPrefab.GetComponent<Rigidbody>().isKinematic = Kinematic;
});
}

/// <summary>
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestRigidbodyKinematicEnableDisable()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
var serverPlayer = serverClientPlayerResult.Result.gameObject;

// This is the *CLIENT VERSION* of the *CLIENT PLAYER*
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
var clientPlayer = clientClientPlayerResult.Result.gameObject;

Assert.IsNotNull(serverPlayer);
Assert.IsNotNull(clientPlayer);

int waitFor = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= waitFor);

// server rigidbody has authority and should have a kinematic mode of false
Assert.True(serverPlayer.GetComponent<Rigidbody>().isKinematic == Kinematic);

// client rigidbody has no authority and should have a kinematic mode of true
Assert.True(clientPlayer.GetComponent<Rigidbody>().isKinematic);

// despawn the server player
serverPlayer.GetComponent<NetworkObject>().Despawn(false);

yield return null;

Assert.IsTrue(serverPlayer.GetComponent<Rigidbody>().isKinematic == Kinematic);

yield return null;
Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned.
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading