Skip to content

fix: provide non Rigidbody contact events and Rigidbody prioritization #3094

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
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
5 changes: 4 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ Additional documentation and release notes are available at [Multiplayer Documen
[Unreleased]

### Added


- Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094)
- `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094)
- `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094)
- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088)
- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,70 @@

namespace Unity.Netcode.Components
{
/// <summary>
/// Information a <see cref="Rigidbody"/> returns to <see cref="RigidbodyContactEventManager"/> via <see cref="IContactEventHandlerWithInfo.GetContactEventHandlerInfo"/> <br />
/// if the <see cref="Rigidbody"/> registers itself with <see cref="IContactEventHandlerWithInfo"/> as opposed to <see cref="IContactEventHandler"/>.
/// </summary>
public struct ContactEventHandlerInfo
{
/// <summary>
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will include non-Rigidbody based contact events.<br />
/// When the <see cref="RigidbodyContactEventManager"/> invokes the <see cref="IContactEventHandler.ContactEvent"/> it will return null in place <br />
/// of the collidingBody parameter if the contact event occurred with a collider that is not registered with the <see cref="RigidbodyContactEventManager"/>.
/// </summary>
public bool ProvideNonRigidBodyContactEvents;
/// <summary>
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br /></br>
/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a <see cref="NetworkObject"/> is owned by the local client <br />
/// will assure <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> is only invoked on the authoritative side.
/// </summary>
public bool HasContactEventPriority;
}

/// <summary>
/// Default implementation required to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance.
/// </summary>
/// <remarks>
/// Recommended to implement this method on a <see cref="NetworkBehaviour"/> component
/// </remarks>
public interface IContactEventHandler
{
/// <summary>
/// Should return a <see cref="Rigidbody"/>.
/// </summary>
Rigidbody GetRigidbody();

/// <summary>
/// Invoked by the <see cref="RigidbodyContactEventManager"/> instance.
/// </summary>
/// <param name="eventId">A unique contact event identifier.</param>
/// <param name="averagedCollisionNormal">The average normal of the collision between two colliders.</param>
/// <param name="collidingBody">If not null, this will be a registered <see cref="Rigidbody"/> that was part of the collision contact event.</param>
/// <param name="contactPoint">The world space location of the contact event.</param>
/// <param name="hasCollisionStay">Will be set if this is a collision stay contact event (i.e. it is not the first contact event and continually has contact)</param>
/// <param name="averagedCollisionStayNormal">The average normal of the collision stay contact over time.</param>
void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default);
}

/// <summary>
/// This is an extended version of <see cref="IContactEventHandler"/> and can be used to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance. <br />
/// This provides additional <see cref="ContactEventHandlerInfo"/> information to the <see cref="RigidbodyContactEventManager"/> for each set of contact events it is processing.
/// </summary>
public interface IContactEventHandlerWithInfo : IContactEventHandler
{
/// <summary>
/// Invoked by <see cref="RigidbodyContactEventManager"/> for each set of contact events it is processing (prior to processing).
/// </summary>
/// <returns><see cref="ContactEventHandlerInfo"/></returns>
ContactEventHandlerInfo GetContactEventHandlerInfo();
}

/// <summary>
/// Add this component to an in-scene placed GameObject to provide faster collision event processing between <see cref="Rigidbody"/> instances and optionally static colliders.
/// <see cref="IContactEventHandler"/> <br />
/// <see cref="IContactEventHandlerWithInfo"/> <br />
/// <see cref="ContactEventHandlerInfo"/> <br />
/// </summary>
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
public class RigidbodyContactEventManager : MonoBehaviour
{
Expand All @@ -34,6 +91,7 @@ private struct JobResultStruct

private readonly Dictionary<int, Rigidbody> m_RigidbodyMapping = new Dictionary<int, Rigidbody>();
private readonly Dictionary<int, IContactEventHandler> m_HandlerMapping = new Dictionary<int, IContactEventHandler>();
private readonly Dictionary<int, ContactEventHandlerInfo> m_HandlerInfo = new Dictionary<int, ContactEventHandlerInfo>();

private void OnEnable()
{
Expand All @@ -49,6 +107,15 @@ private void OnEnable()
Instance = this;
}

/// <summary>
/// Any <see cref="IContactEventHandler"/> implementation can register a <see cref="Rigidbody"/> to be handled by this <see cref="RigidbodyContactEventManager"/> instance.
/// </summary>
/// <remarks>
/// You should enable <see cref="Collider.providesContacts"/> for each <see cref="Collider"/> associated with the <see cref="Rigidbody"/> being registered.<br/>
/// You can enable this during run time or within the editor's inspector view.
/// </remarks>
/// <param name="contactEventHandler"><see cref="IContactEventHandler"/> or <see cref="IContactEventHandlerWithInfo"/></param>
/// <param name="register">true to register and false to remove from being registered</param>
public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true)
{
var rigidbody = contactEventHandler.GetRigidbody();
Expand All @@ -64,6 +131,22 @@ public void RegisterHandler(IContactEventHandler contactEventHandler, bool regis
{
m_HandlerMapping.Add(instanceId, contactEventHandler);
}

if (!m_HandlerInfo.ContainsKey(instanceId))
{
var handlerInfo = new ContactEventHandlerInfo()
{
HasContactEventPriority = true,
ProvideNonRigidBodyContactEvents = false,
};
var handlerWithInfo = contactEventHandler as IContactEventHandlerWithInfo;

if (handlerWithInfo != null)
{
handlerInfo = handlerWithInfo.GetContactEventHandlerInfo();
}
m_HandlerInfo.Add(instanceId, handlerInfo);
}
}
else
{
Expand All @@ -88,25 +171,98 @@ private void OnDisable()

private void ProcessCollisions()
{
foreach (var contactEventHandler in m_HandlerMapping)
{
var handlerWithInfo = contactEventHandler.Value as IContactEventHandlerWithInfo;

if (handlerWithInfo != null)
{
m_HandlerInfo[contactEventHandler.Key] = handlerWithInfo.GetContactEventHandlerInfo();
}
else
{
var info = m_HandlerInfo[contactEventHandler.Key];
info.HasContactEventPriority = !m_RigidbodyMapping[contactEventHandler.Key].isKinematic;
m_HandlerInfo[contactEventHandler.Key] = info;
}
}

ContactEventHandlerInfo contactEventHandlerInfo0;
ContactEventHandlerInfo contactEventHandlerInfo1;

// Process all collisions
for (int i = 0; i < m_Count; i++)
{
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID);
var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID);
// Only notify registered rigid bodies.
if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID))
var contactHandler0 = (IContactEventHandler)null;
var contactHandler1 = (IContactEventHandler)null;
var preferredContactHandler = (IContactEventHandler)null;
var preferredContactHandlerNonRigidbody = false;
var preferredRigidbody = (Rigidbody)null;
var otherContactHandler = (IContactEventHandler)null;
var otherRigidbody = (Rigidbody)null;

var otherContactHandlerNonRigidbody = false;

if (m_RigidbodyMapping.ContainsKey(thisInstanceID))
{
contactHandler0 = m_HandlerMapping[thisInstanceID];
contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID];
if (contactEventHandlerInfo0.HasContactEventPriority)
{
preferredContactHandler = contactHandler0;
preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
preferredRigidbody = m_RigidbodyMapping[thisInstanceID];
}
else
{
otherContactHandler = contactHandler0;
otherContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
otherRigidbody = m_RigidbodyMapping[thisInstanceID];
}
}

if (m_RigidbodyMapping.ContainsKey(otherInstanceID))
{
contactHandler1 = m_HandlerMapping[otherInstanceID];
contactEventHandlerInfo1 = m_HandlerInfo[otherInstanceID];
if (contactEventHandlerInfo1.HasContactEventPriority && preferredContactHandler == null)
{
preferredContactHandler = contactHandler1;
preferredContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
preferredRigidbody = m_RigidbodyMapping[otherInstanceID];
}
else
{
otherContactHandler = contactHandler1;
otherContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
otherRigidbody = m_RigidbodyMapping[otherInstanceID];
}
}

if (preferredContactHandler == null && otherContactHandler != null)
{
preferredContactHandler = otherContactHandler;
preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody;
preferredRigidbody = otherRigidbody;
otherContactHandler = null;
otherContactHandlerNonRigidbody = false;
otherRigidbody = null;
}

if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody))
{
continue;
}

if (m_ResultsArray[i].HasCollisionStay)
{
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
}
else
{
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint);
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint);
}
}
}
Expand Down
Loading
Loading