1

I'm trying to create an interaction system for a game I'm developing with Unity and C# and I've been struggling with it for a while now. It consists of interactors and interactables. The idea is that interactors are responsible for initiating and finishing the interaction, and the interactables handle the actual interaction logic once it began. Each interactor can interact with a specific interactable and all interactables deriving from it.

There is some logic that all interactors share. Because of this I made a generic BaseInteractor class that implements that logic (abstract class BaseInteractor<T> where T : BaseInteractable). So now I can make e.g. grab interactor class like this: GrabInteractor : BaseInteractor<GrabInteractable>. This works well but there is one issue - I would also like the BaseInteractable to have a generic parameter which describes which interactor can interact with it. This is necessary because different interactables need to access different methods of the interactor (grab interactable needs to know the position and rotation of the interactor, while some other interactable might need to know the scale of the interactor).

However when the BaseInteractable class has a generic parameter it creates a problem where I need to know the type of the interactable even in the BaseInteractor class (abstract class BaseInteractor<T> where T : BaseInteractable<_what_to_put_here_>). This is obviously a mess, but I can't figure out how can I design it better. I could obviously not inherit from the base classes at all, but then I would need to repeat the same code.

EDIT: One solution I thought of is to declare these classes like this: abstract class BaseInteractor<T> where T : BaseInteractable<T> and abstract class BaseInteractable<T> where T : BaseInteractor<T> which I thought makes sense, but the compiler wouldn't allow me to do it.

2
  • 1
    I think you should look into Composition over inheritance
    – Anders
    Jan 14, 2022 at 23:17
  • @Anders I understand the principles of composition over inheritance, but I'm not sure how o apply it in this case. Any tips? Jan 15, 2022 at 0:17

1 Answer 1

1

The solution below doesn't exactly answer what's you've asked, but at least, I hope, could lead you in right direction. So, the only option I've found to fit your criteria as close as possible is to refuse to use compile time verification for the BaseInteractor, but use runtime validation instead. Hence the IInteractable interface was introduced with bool Support(BaseInteracotr); method. The BaseInteractor class implements it but also is extended with generic methods variants.

/// <summary>
/// Represents basic operations for interacting with the BaseInteractor.
/// </summary>
public interface IInteractable
{
    bool Support(BaseInteractor actor);
    void BeginInteraction(BaseInteractor actor);
    void CompleteInteraction(BaseInteractor actor);
}

/// <summary>
/// Povides a base implementation for the IInteractable interface for a concrete Interactor type.
/// </summary>
public abstract class BaseInteractable<T> : IInteractable where T : BaseInteractor
{
    public virtual bool Support(BaseInteractor actor)
    {
        return actor is T;
    }

    public virtual void BeginInteraction(T actor)
    {
    }

    public virtual void CompleteInteraction(T actor)
    {
    }

    void IInteractable.BeginInteraction(BaseInteractor actor)
    {
        if (actor is T target)
        {
            BeginInteraction(target);
        }
    }

    void IInteractable.CompleteInteraction(BaseInteractor actor)
    {
        if (actor is T target)
        {
            CompleteInteraction(target);
        }
    }
}

/// <summary>
/// Implements interaction with the GrabInteractor.
/// </summary>
public class GrabInteractable : BaseInteractable<GrabInteractor>
{
    public override void BeginInteraction(GrabInteractor actor)
    {
        base.BeginInteraction(actor);
    }

    public override void CompleteInteraction(GrabInteractor actor)
    {
        base.CompleteInteraction(actor);
    }
}

/// <summary>
/// Represents a base interactor.
/// </summary>
public abstract class BaseInteractor
{
    public void Interact(IInteractable interaction)
    {
        // Communicate via the basic interface.
        if (interaction.Support(this))
        {
            interaction.BeginInteraction(this);
            interaction.CompleteInteraction(this);
        }
    }
}

/// <summary>
/// Represents a grab interactor.
/// </summary>
public class GrabInteractor : BaseInteractor
{
    public void Interact(BaseInteractable<GrabInteractor> interaction)
    {
        // Communicate via the generic interactable.
        interaction.BeginInteraction(this);
        interaction.CompleteInteraction(this);
    }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.