Interfaces
If you've followed my previous blog posts, you'll know that I prefer practical explanations over diving into technical jargon. While there's certainly a place for technical terms, I believe in presenting concepts in a relatable manner, providing a more approachable first experience with the subject matter.
In this article, I'll discuss a real-world implementation of interfaces in my project and share my thoughts on using them. This approach should provide a more realistic perspective than the typical online C# examples that often involve finance or accounting scenarios. Seriously, why are the examples always about finance? But I digress, let's jump in!
What is an Interface?
In the context of Unity and C#, an interface is a programming construct that establishes a contract for classes to follow. It outlines a specific set of methods, properties, and events that implementing classes must supply. Interfaces enable the creation of standardized behaviors that unrelated classes can share, promoting code consistency and modularity.
Let's simplify this: Picture two classes that need to communicate without direct awareness of each other. They can do so through an interface. In fact, more than two classes can interact by agreeing to implement the same interface or "contract," but we'll just focus on the two for now.
This concept is less complicated than it may sound. Consider this example: Imagine a player character in a game. Pressing the A button should allow the character to interact with various objects in the game world, like chests, NPCs, doors, or save points. You've probably even seen games with a dedicated "Interact" button. Think of Kingdom Hearts 2 and its use of the triangle button (yes, I was an adolescent in the 2000s). When near "interactable" objects, like a chest, pressing this button lets the player interact with or open the chest. We can achieve something similar using interfaces in Unity.
Creating and Implementing Interfaces
How is this accomplished? Start by defining an interface. Continuing with the "interactable" example, create a new C# script (or add it within your chosen namespace). The script should resemble this:
public interface IInteractable
{
// Declare functions, variables, etc. here //for implementing classes to use.
// This is the 'contract' we discussed //earlier.
public void Interact();
}
Remember to follow SOLID principles when implementing interfaces. Specifically, adhere to the 'I' in SOLID, which stands for the Interface Segregation Principle. This principle advises against overcrowding interfaces with functions that aren't required by implementing classes. Smaller, more focused interfaces are generally more effective. In the case of IInteractable, include only what's necessary for interaction with any object in your project.
Implementing the Interface
Now let's return to our example. We have an interface but can't use it yet. Let's say we want the player to be able to click on a chest and interact with it when the player character is within the chest's collider.
Ensure that your player's setup allows it to look for the implementation of IInteractable" when a collider is detected. In my projects, I incorporate an "interact state" where the player enters upon pressing the interact button while detecting a collider with the "Interactable" layer tag. Within this state, a function runs to check if the object implements IInteractable. The function might look like this:
if (HitsToInteract) // Collider found by the //raycast
{
// Obtain the implementation of the //interface and trigger the interaction.
HitsToInteract.collider.GetComponent().Interact();
}
An alternative approach that handles null references better could look like this:
if (HitsToInteract.TryGetComponent(out IInteractable interactable))
{
interactable.Interact();
}
Now that we have the "invoker's" perspective, let's explore the "client's" side.
Assuming we have a class named Chest, it should implement IInteractable and fulfill the contract like this:
public class Chest : MonoBehaviour, IInteractable
{
public void Interact()//The function that
//fulfills the "contract"
{
// Implement logic for interacting //with the chest, e.g., opening it.
OpenChest();
}
}
That's pretty much it! Ensure that:
The interface is defined.
The "invoker" (in this case, the player) effectively calls it.
The "client" (e.g., the chest) implements the interface and fulfills its contract, providing the logic for the desired behavior (like opening the chest).
During runtime, when the player detects an interactable object, the game checks for the interface implementation, triggering the associated functionality. This process effectively opens the chest or performs the intended action. Now, with this same process, you can simply create more clients for the player to interact with.
*Going Deeper: Abstracting Interfaces
*
While interfaces provide a level of abstraction, you can take it further by creating an abstract interface. This allows you to create concrete interfaces derived from the abstract one. This approach enables similar interface contracts with more specific details. For instance, in my item spawn system, I have an abstract interface named ISpawnable. Derived from it are IBasicSpawn, IRareSpawn, and IExtraRareSpawn. This segmentation facilitates clearer and more consistent functionality.
Thanks for reading! I hope this basic implementation provides a clear example of using interfaces in Unity.
Top comments (0)