- Home /
Call method from unknown class
Dear programming experts,
I've been coding in Unity for a year and have only just discovered the Component-based system's potential. However, I've run into trouble trying to use it fully.
The concept: I have a class called "InteractableObject.cs" which holds the basics for the player to interact with certain game objects. When added to a game object, the player can press E while inside its collider, and InteractableObject.cs will call its method "ActivateInteractable()". What exactly this method does, however, depends on which other Component(s) the game object holds. For instance, if the game object is a bed, it also contains a Bed.cs component. I want to set it up so that, when ActivateInteractable() in InteractableObject.cs is called, it calls ActivateInteractable() in the other class, e.g. in Bed.cs.
The problem: This is easy to achieve as long as InteractableObject.cs knows what the other script is. I would: 1. assign it in InteractableObject.cs by declaring it as a variable (e.g. "Bed bed;"), 2. call GetComponent() (e.g. GetComponent();), 3. then call bed.ActivateInteractable() from the ActivateInteractable() function inside InteractableObject.cs. Unfortunately, this isn't feasable because InteractableObject.cs doesn't know what the other script is. It could be Bed.cs, but also Chair.cs, Elevator.cs, NPC.cs, or some other class. In ActivateInteractable() inside InteractableObject.cs, I want to essentially call WhichEverClassTheInteractableIsInThisCase.ActivateInteractable().
That way, in my opinion, I'm using the Component system the way it is intended to be used: I'm adding a Component with a certain functionality (here: being activated by pressing E) to a game object which holds several components that make up its unique behavior. For these components to interact, however, they need to know what the other components are, which seems like a huge disadvantage to me. One I'm sure can be dealt with easily, but I don't know how - do you guys?
Thanks a lot in advance!
Answer by MaxGuernseyIII · Nov 04, 2017 at 09:52 AM
Something needs to do a similar kind of activity regardless of context but the context causes part of that activity to vary. You have a strategy pattern problem.
Generally, the way people solve this problem is to create an abstraction (e.g., an abstract base class or an interface) and make all of the variants implement that abstraction.
In your case, you need a way to publish the correct implementation of an abstraction. So I would make something like a container behavior.
public interface InteractionTarget {
void Interact();
}
public class InteractionContainer : MonoBehaviour {
public InteractionTarget Target;
}
public class Bed : MonoBehaviour, InteractionTarget {
public void Interact() {
// whatever a bed does
}
}
public class InteractionConsumer : MonoBehaviour {
// I'm assuming this is called from some collision
void InteractWith(GameObject go) {
var interactionTarget = go.GetComponent<InteractionContainer>().Target;
interactionTarget.Interact();
}
}
Why the InteractionContainer ? You don't use this class anywhere. You use GetComponent on InteractionTarget which doesn't have a "Target" field.
Hey, thanks a lot for taking the time! I failed to mention that I originally worked with inheritance but wanted to move away from it. However, your solution seems a little different than when I used inheritance, and although it's intruiging, I can't say I completely understand. Especially because, like Bunny83 pointed out, I don't know what "Target" is. Would you $$anonymous$$d elaborating a little further? (:
You have to use some kind of inheritance or you're stuck with something like a switch statement. The important thing to remember is how to use inheritance: use it to categorize rather than to specialize. In other words, create base classes or interfaces that represent groups of interchangeable variants and make each inheritor an alternative to it's siblings rather than taking something that works and trying to make a new "flavor" of it with inheritance.
There was a typo in my example, which may have been focusing you. I've fixed it.
The point of this design is to have everything do one job. The container is the thing that you search for with something like GetComponent. The interface represents the commonality in the interchangeable group of things with which you can interact. Bed represents one of those interactable variants. The consumer represents the part of the rule that does not change.
Both my explanation and @Bunny83's OOP explanation hinge on our understanding what your problem really is. If, for instance, the thing that is driving interaction absolutely needs to travel differently to different kinds of interactable objects, that's probably a visitor pattern and will demand a different design.
Hey, thanks again for your effort and sorry for the long delay on my end. Based on the answers I got to my question, I tried out different things and ended up using an Interface the way you suggested. The different classes implementing ActivateInteractable() all inherit IInteractable() now and their instances are called from the outside using IInteractable.ActivateInteractable().
Answer by Bunny83 · Nov 04, 2017 at 10:25 AM
There are several approaches to this. One is to use Unity's SendMessage system. It's the most flexible as you only need to know the method name. However it has some downsides. It's rather slow compared to a direct call. Also since it's based on a string you don't get a compiler error if you mistype it.
However in most cases since you only call the method on certain events the speed is quite irrelevant.
Another way is to use normal OOP. So you could use a base class with a virtual method which you can override in the derived classes. Instead of a base class you can also use interfaces. Personally i prefer interfaces over base classes. A base class is useful if you have a certain type of component which all share the same fields / functionality with only slight variations. The base class allows you to define the common things there so all components derived from the base class have those as well.
interfaces are more flexible than baseclasses. Unlike base classes a class can implement several interfaces. Imagine you have an interface like this:
public interface IInteractableObject
{
void Activate();
}
Now you can create several components which implement this interface:
public class Bed : MonoBehaviour, IInteractableObject
{
public void Activate()
{
Debug.Log("Activate Bed");
}
}
// or
public class Chair : MonoBehaviour, IInteractableObject
{
public void Activate()
{
Debug.Log("Activate Chair");
}
}
Now to actually use the interface you can simply do;
someGameObject.GetComponent<IInteractableObject>().Activate();
This will work for any component that implements this interface. Unfortunately Unity can't serialize interface references in the inspector. This would work with base classes. So if you have a base class like this:
public abstract class InteractableObject : MonoBehaviour
{
public abstract void Activate();
}
public class Bed : InteractableObject
{
public override void Activate()
{
Debug.Log("Activate Bed");
}
}
In another class you can now define a public variable of type "InteractableObject" where you can assign any class that is derived from InteractableObject:
public InteractableObject someObj;
// ...
someObj.Activate();
Awesome! Thank you very much for taking the time. I'm actually inclined to use the Send$$anonymous$$essage system since the method is never called in Update(), but only every few seconds or, more likely, $$anonymous$$utes when the player interacts with an object. Thus, I think, its high cost isn't an important factor.
I did use inheritance before but felt like it wasn't exactly what I wanted. The system has been working fine using inheritance for a couple of months now, but the ability to just add and remove features (like "interactable") to and from objects sounds like exactly what I'd like to be able to do in the future. However, I like your idea with Interfaces a lot and I'm going to give it a spin as well to see which system works best. Thanks again!
Your answer
Follow this Question
Related Questions
HoloLens: detect Airtap regardless of Raycast hit 0 Answers
How to achieve cross platform deterministic physics(Android , IOS) ? 1 Answer
Is there a thing like a type of a script? 3 Answers
I cannot get script component. 3 Answers
Can't disable component, little checkbox is missing from inspector 1 Answer