Search code examples
c#unity-game-enginedesign-patterns

Best way to extend Unity3d Rigidbody?


I am designing a physics class library for Unity3d and would love to be able to hook and inject functionality into Unity3d's Rigidbody class, but it's my understanding that there is no way to do this directly while still being able to target all platforms.

I thought the next best thing might be to create a new class that inherits directly from Rigidbody. However, it looks like unity has prevented this from occurring in anything but the UnityEngine namespace by requiring the internal sealed ExtensionOfNativeClassAttribute on anything that inherits native classes. Furthermore, Unity does not searialize inherited classes well.

So, in review, I'm not able to hook into Rigidbody directly (Please correct me if this is wrong!), and I'm not able to inhert from Rigidbody directly (Please correct me if this is wrong!). I think the next best option is to:

  1. Create a new Monobehavior, let's say it's called ModRigidbody, which makes a parellel implementation of all of Rigidbody's members. This class does not actually inherit from Rigidbody, but merely has members with all the same names / signatures as Rigidbody.
  2. On ModRigidbody add the attribute [RequireComponent(typeof(Rigidbody))]. Now I can add functionality to some ModRigidbody's members as I see fit, and have other members simply 'pass through' to transform.GetComponent<Rigidbody>().
  3. Have any end users (programmers) change all of their references to Rigidbody over to ModRigidbody.

To me this feels quite sloppy. Is there a way to mod the functionality of Rigidbody so that future users of this library will not have to change all of their Rigidbody over to ModRigidbody?


Solution

  • Your proposition seems reasonable given the constraints imposed by Unity, but why not create an extension method?

    Extension methods enable you to "add" methods to existing types without creating a new derived type, recompiling, or otherwise modifying the original type.


    I'll start by saying that even extending Rigidbody directly will cause your consumers to migrate to using a new type in their implementations so creating a proxy is certainly a good start. However, if you want to promote consumers using Rigidbody directly instead of a new type, then I recommend creating extension methods:

    public static class ForceApplicationRigidbodyExtensions {
        public static void ApplyDecayingForce(
            this Rigidbody rigidbody,
            Vector3 startingForce,
            float decayRate,
            TimeSpan lifetime) {
            ...
        }
    }
    

    This allows consumers to invoke your methods directly off of their Rigidbody instances:

    var rigidbody = transform.GetComponent<Rigidbody>();
    rigidbody.ApplyDecayingForce(Vector3.up * 10, 0.05f, TimeSpan.FromSeconds(5));
    

    If you're not a fan of extension methods, then you could create something focused on mutating a given Rigidbody instead:

    public class RigidbodyForceApplicator {
        private reaodnly Rigidbody _rigidbody;
        public RigidbodyForceApplicator(Rigidbody rigidbody) =>
            _rigidbody = rigidbody;
        public void ApplyDecayingForce(
            Rigidbody rigidbody,
            Vector3 startingForce,
            float decayRate,
            TimeSpan lifetime) {
            ...
        }
    
    }
    

    This allows consumers to be explicit while using only what they need:

    var rigidbody = transform.GetComponent<Rigidbody>();
    var forceApplicator = new RigidbodyForceApplicator(rigidbody);
    forceApplicator.ApplyDecayingForce(Vector3.up * 10, 0.05f, TimeSpan.FromSeconds(5));
    

    If you do create a proxy instead, then I recommend keeping the following points in mind:

    • Don't expect Rigidbody to fully support the mutations you have in mind as that almost never works out the way we intend.
    • Don't create pass-through implementations that hand work off directly to Rigidbody as this just adds an extra method invocation that isn't necessary.
    • Only expose functions that provide what you intend to complement Rigidbody with.