22 August 2025

C# Interfaces and the Unity Inspector

Interface contract
In C#, an interface is a contract that defines a set of methods, properties, events, or indexers that a class or structure must implement, without providing the actual implementation. It's like a blueprint that specifies "what" must be done, but not "how". When a class adheres to an interface, it publicly commits to providing at least the methods defined by that interface. Therefore, the class must implement the code for each of the interface's methods.

Interfaces in C# offer several advantages that make them fundamental in software design. The main ones are:

  • Abstraction: They define a contract without implementation details, allowing focus on "what" an object does, not "how" it does it.
  • Flexibility and extensibility: They make it easy to add new classes that implement the same interface without modifying existing code.
  • Multiple inheritance: They allow a class to implement multiple interfaces, overcoming C#'s single inheritance limitation. A class can only inherit from one base class but can implement several interfaces simultaneously.
  • Decoupling: They reduce dependency between components, promoting modular design and facilitating dependency injection.
  • Maintenance and testing: They simplify replacing implementations (e.g., in unit tests with mocks) by working with contracts instead of concrete classes.
  • Consistency: They ensure that classes implementing the interface adhere to a standard behavior, improving interoperability.
  • Support for design patterns: They are essential in patterns like Strategy, Factory, or Repository, which rely on abstractions.

All this is better understood with examples. In my case, I developed an abstract class called SteeringBehavior, which implements the basic functionality for the movement of an intelligent agent. Many child classes inherit from it and implement specialized movements.

The abstract class SteeringBehavior
The abstract class SteeringBehavior

Some (not all) of these child classes need a target to calculate their movement—whether to reach it, pursue it, look at it, etc. That target is defined with a Target field.

I could have included that field within the definition of the child classes, but that would have reduced flexibility. The problem arises when chaining SteeringBehavior classes. Some SteeringBehavior classes provide their functionality by composing the outputs of simpler SteeringBehavior classes. The flexibility of this approach comes from being able to build the dependency tree of the different SteeringBehavior classes in the inspector.

An agent built by composing several SteeringBehavior classes
An agent built by composing several SteeringBehavior classes


Inspector references to child SteeringBehavior classes
Inspector references to child SteeringBehavior classes


There are some SteeringBehavior classes for which we know exactly the type of child SteeringBehavior they will need. In the screenshot above, you can see that ActiveWallAvoiderSteeringBehavior specifically needs a child of type PassiveWallSteeringBehavior. But there are also cases where the parent SteeringBehavior doesn't care about the specific type of the child, as long as it inherits from SteeringBehavior and provides a Target field (to pass its own Target field to it). In the screenshot, I passed a SeekSteeringBehavior, but I could have passed an ArriveSteeringBehavior or a PursuitSteeringBehavior to get slightly different movement types. If I had implemented the Target field through inheritance, I would have lost the flexibility to pass any type of SteeringBehavior with a Target field, being forced to specify the concrete type, child of SteeringBehavior.

Instead, I created an interface.

The ITargeter Interface
The ITargeter Interface


As shown in the screenshot, the interface couldn't be simpler. It's declared with the interface keyword and, in this case, simply states that classes adhering to this interface must provide a Target property.

A class adhering to this interface only needs to implement that property.

A class implementing the ITargeter interface
A class implementing the ITargeter interface

As shown in the screenshot, the SeekSteeringBehavior class inherits from SteeringBehavior and also implements the ITargeter interface.

You may notice some overlap between abstract classes and interfaces. Although both are abstraction mechanisms, they have important differences in purpose and use. An interface defines a pure contract with method, property, event, or indexer signatures, without any implementation. It's ideal for specifying behaviors that different unrelated classes can share. An abstract class, on the other hand, is a class that cannot be instantiated directly and can contain both abstract (unimplemented) and concrete (implemented) members. It serves as a base for related classes that share common logic. In general, if you want to provide common behavior to related classes through a base class, you'd use abstract classes (possibly making the base class abstract); whereas if the common behavior occurs in heterogeneous classes, interfaces are typically used.

While an abstract class can include concrete methods, fields, constructors, and shared logic that derived classes can use directly, an interface cannot include method implementations or fields (only from C# 8.0 onward does it allow default methods, but with restrictions—generally everything must be implemented by the adopting class). Additionally, abstract classes allow access modifiers (public, private, protected, etc.) for their members, while in an interface all members are implicitly public and access modifiers cannot be specified. Also, abstract classes only allow single inheritance, while interfaces allow multiple inheritance.

Unity adds an additional difference, which is crucial for this article: it allows abstract classes to be shown in the inspector, but not interfaces. In one of the previous screenshots, you saw the inspector for the ActiveWallAvoiderSteeringBehavior class. Pay attention to its Steering Behavior field. The code that declares this field and shows it in the inspector is:

Declaration of the steeringBehavior field
Declaration of the steeringBehavior field


Note that steeringBehavior is of type SteeringBehavior (an abstract class), and the inspector has no problem displaying it. Therefore, we can use the inspector to assign any script that inherits from SteeringBehavior to this field.

If we wanted this field to accept any script that implements ITargeter, we should be able to write [SerializeField] private ITargeter steeringBehavior. The problem is that while C# allows this, Unity does not allow interface-type fields to be exposed in the inspector.

So, how can we ensure that the steeringBehavior field receives not only a class that inherits from SteeringBehavior, but also implements the ITargeter interface? The answer is that Unity doesn't offer any built-in safeguard, but we can develop our own, as I’ll explain.

It will be easier if I first show you what we want to achieve and then explain how to implement it. The solution I found was to design my own PropertyAttribute to decorate fields that I want to verify for compliance with one or more interfaces.

Custom attribute to confirm interface compliance
Custom attribute to confirm interface compliance


In the screenshot above, you can see that I named my attribute InterfaceCompliant. In the example, the attribute verifies that the SteeringBehavior passed to the steeringBehavior field complies with the ITargeter interface.

If the script passed to the field does not comply with the interface, the attribute will display a warning message in the inspector.

Warning message when a script does not comply with the interface
Warning message when a script does not comply with the interface


Now that we understand what we want to achieve, let’s see how to implement it. Like other attributes, we need to create a class that inherits from PropertyAttribute. This class is abstract and requires us to implement the InterfaceCompliantAttribute method, whose parameters are passed to the attribute when decorating a field.

Implementation of our custom attribute
Implementation of our custom attribute


Note that the name of this class must be the name of our attribute followed by the suffix "Attribute". The class should be placed inside the Scripts folder.

This class serves as the data model for the attribute. We’ll store in it all the information needed to perform our check and display the warning message if necessary. In this case, the attribute accepts a variable number of parameters (hence the params keyword). These parameters are the types of our interfaces and will be passed in an array stored in the InterfaceTypes field.

To represent the custom attribute in the inspector, as well as the field it decorates, we’ll use a PropertyDrawer. These are classes that inherit—surprise!—from PropertyDrawer and must be placed in an Editor folder, separate from the Scripts folder. We can name our class whatever we want, but we must decorate it with a CustomPropertyDrawer attribute to indicate which attribute this PropertyDrawer draws in the inspector.

PropertyDrawer to draw our attribute and the field it decorates
PropertyDrawer to draw our attribute and the field it decorates

In our case, I used the CustomPropertyDrawer attribute to indicate that this PropertyDrawer should be used whenever a field is decorated with an InterfaceCompliantAttribute.

PropertyDrawer is another abstract class that requires implementing a method called CreatePropertyGUI. This method is called by the inspector when it wants to draw a field decorated with this attribute.

Implementation of the CreatePropertyGUI method of InterfaceCompliantAttributeDrawer
Implementation of the CreatePropertyGUI method of InterfaceCompliantAttributeDrawer

A class inheriting from PropertyDrawer has a reference to the attribute it draws via the attribute field. That’s why in line 22 we cast it to the InterfaceCompliantAttribute type, which we know it actually is, to retrieve the list of interfaces to check in line 23.

Note that while the attribute can be retrieved using the attribute field of PropertyDrawer, the decorated field is passed as a parameter to the method, property, encapsulated in a SerializedProperty type (line 19).

Next, in line 26, we generate the visual container in which we’ll place the widgets to represent our field in the inspector, as well as the warning message if necessary. For now, that visual container is empty, but we’ll add content to it in the following lines. As we add elements to the container, they will be displayed top to bottom in the inspector.

If you look at the screenshot showing the warning message in the inspector, you’ll see that the message appears before the field, so the first element we’ll add to the container is the warning message, if necessary (line 28). This part is quite involved, so I moved it to its own method (AddErrorBoxIfNeeded) to make CreatePropertyGUI easier to read and to allow reuse of that method in line 45. Once we finish explaining the implementation of CreatePropertyGUI, we’ll look at AddErrorBoxIfNeeded.

After adding the warning message (if necessary) to the container, we need to display the decorated field. We want to show it with its default appearance in the inspector, so we simply create a PropertyField (line 31). PropertyField will detect the type of the field encapsulated in property and display it using Unity’s default widget for that type.

Now pay attention. We want the interface compliance to be re-evaluated every time a new script is assigned to the decorated field. To do this, in line 43 we instruct the container to monitor the value of property and re-evaluate whether to show the warning message (line 45), redrawing the field (line 46).

Once this is done, we add the decorated field to the container (line 50) and return this container for the inspector to draw.

As promised, let’s now analyze the implementation of AddErrorBoxIfNeeded.

Checking the decorated field for interface compliance
Checking the decorated field for interface compliance


To access the field encapsulated within a SerializedProperty, we use its objectReferenceValue field (line 70).

This field is of type Object, so we’ll need to cast it to the specific type we want to check. We do this in the GetNotComplyingInterfaces method on line 74. We’ll look at its implementation shortly, but at a high level, this method takes a list of interfaces (line 75) and an object (line 76), then casts that object against each interface. If any of those casts fail, the type of the failed interface is added to a list that is returned as the method’s output (line 74).

This list is passed to the GenerateBox method, which draws the warning message if the list contains any elements. If the list is empty, the method draws nothing. The drawn message is returned in an ErrorBox (line 79), which is added to the container (line 85), after clearing its previous content (line 82).

The interface check itself is very simple.

Interface check on the script in the field
Interface check on the script in the field


Starting at line 120, for each interface in the list (passed as parameters to the attribute), we check whether the script (checkedObject) implements it. To do this, we use C#’s IsInstanceOfType method (line 123). I’ve always found IsInstanceOfType misleading, because read left to right it seems to check whether "interfaceType is an instance of checkedObject", when in reality, it checks whether checkedObject is an instance of interfaceType.

If checkedObject does not implement the interface, IsInstanceOfType returns false, and the interface type is added to the list of non-compliant interfaces (line 125), which is returned as the method’s output.

Finally, for completeness, let’s look at how the error message is generated in the GenerateErrorBox method.

Generating the warning message
Generating the warning message


If the script being checked complies with all the interfaces, the list passed as a parameter to GenerateErrorBox would be empty, and the method would simply return null (line 90).

However, if there were any non-compliance, the names of the non-compliant interfaces would be chained together, concatenated with commas between them, as shown in lines 93 to 95.

That list of interface names, concatenated with commas, would be added to the end of the warning message (line 98), and with it, an error message would be created and returned to be added to the container (line 102).

And with that, you have everything. It's true that you won't be able to prevent scripts that don't comply with the interfaces from being assigned to the decorated field, but at least a message will appear warning of the issue. It will be very difficult not to notice the message and correct the oversight. On the other hand, this entire example has served to show you how to implement a custom attribute that leverages the flexibility of Unity's inspector. I hope you found it interesting.

11 August 2025

Lights and Shadows of the "Stop Killing Games" Initiative

The Stop Killing Games initiative is a consumer movement launched in 2024 by YouTuber Ross Scott, known for his channel Accursed Farms, in response to the shutdown of the servers for The Crew, a Ubisoft video game that became unplayable due to its constant internet connection requirement, even in single-player mode. This sparked outrage, as the game, despite having multiplayer components, also included a single-player mode that did not inherently need an online connection. The initiative raises a crucial debate about digital ownership in an era where games are increasingly expensive (with prices reaching 80-100 euros) and rely on servers that can be shut down.

The Stop Killing Games Logo
The Stop Killing Games Logo

Ross Scott released a video introducing the initiative and created a website (stopkillinggames.com) to collect signatures and promote government petitions in countries such as France, the UK, Canada, Australia, and the European Union.


In response to cases like this, the initiative demands the preservation of video games after their official support ends, protecting consumer rights and preventing purchased games from becoming inaccessible due to publishers' decisions. Its argument is rooted in opposing publishers selling games as temporary licenses rather than permanent products. It defends players' rights to enjoy what they paid for without time restrictions. Additionally, it claims video games as cultural heritage, comparing them to films or books that do not vanish after commercialization.

To this end, the initiative seeks legal protections for players against sudden server shutdowns that render games unplayable. It demands that games always include an offline mode, with single-player campaigns, so they do not rely on active servers, or that games allow users to create private servers to keep them alive when companies cease support.Since its launch, the initiative has garnered support from over 1.4 million signatories, media outlets, and content creators like PewDiePie, MoistCr1TiKaL, and Jacksepticeye. Even one of the European Parliament’s vice-presidents, Nicolae "Nicu" Ştefănuță, publicly endorsed the initiative and added his signature. While not decisive, given the size of the European Parliament, this support signals a growing trend to curb certain abusive practices.

Nicolae "Nicu" Ştefănuță, a vice-president of the European Parliament, publicly supported the initiative.

On the other side, major game developers, represented by the Video Games Europe lobby (including companies like EA, Ubisoft, and Activision), argue that the initiative’s proposals would make game development "prohibitively expensive" and maintain that players own revocable licenses, not the games themselves. Other voices in the gaming industry, such as streamer Pirate Software (Jason Thor Hall), have also criticized the initiative, claiming it is not viable for all games and could harm developers, leading to a public clash with Scott.

While publishers naturally defend their interests, some of their arguments have merit and deserve consideration to ensure well-intentioned initiatives do not harm indie studios or negatively impact the dynamic video game market.

For starters, the initiative oversimplifies by treating all video games the same, despite their diversity. A key distinction exists between games designed to run entirely on local resources (e.g., Half-Life: Alyx) and those reliant on online servers to manage game logic and player progress (e.g., Fortnite or World of Warcraft).

The initiative seems to overlook that redesigning a game built for online servers to work offline or on private servers is not always feasible. Online games often incorporate licensed elements on the server side (e.g., physics engines, matchmaking frameworks) that developers do not own and cannot redistribute without violating license terms. This is even clearer with audiovisual content, such as music licensed for use in Fortnite’s lobbies. If such content were redistributed to local game copies, the risk of uncontrolled distribution would skyrocket, prompting artists’ publishers to renegotiate rights. Alternatively, games could be distributed without licensed content, but this would contradict the initiative’s goal of preserving everything players paid for. Removing licensed functional elements could even render games inoperable. Additionally, computational power is a factor: maintaining the state and progression of an MMORPG requires significant processing, storage, and robust data networks, often relying on entire data centers. Replicating this in a user’s home is practically impossible.

Data Centers
Many online games run on complex data centers, impossible to replicate at home.

Then there’s the issue of time. Unlike films, which only require a compatible player, server-dependent games need ongoing, expert maintenance beyond the average person’s skills. Expecting users to maintain complex game servers at home is highly unrealistic.

Complex Infrastructure Maintenance
Maintaining such complex infrastructure is beyond the average citizen’s capabilities.

While the initiative’s motivation is understandable, it overlooks practical challenges widely discussed in game development forums. However, this debate is healthy and may lead to a balanced solution. Some propose labeling games to warn consumers about external server dependency, enabling informed purchasing decisions.

Labeling as a Possible Solution
Labeling as a Possible Solution

Others suggest a voluntary framework for developers, committing to design games with long-term preservation guarantees. For example, a first category could include games with limited offline modes using bots, specific maps, or local multiplayer; a second could involve partnerships with nonprofits to preserve games with security measures; a third could allow players to host local servers. Like labeling, these categories would be disclosed at purchase, letting buyers assess risks.

Voluntary Categorization as a Solution
Voluntary Categorization as a Solution

While the initiative’s proposed regulations aim to protect consumers, they must be carefully crafted to avoid stifling the creativity and economic vitality of the video game industry. Conditions feasible for large studios could be prohibitive for small or indie developers, increasing costs and legal risks. Protecting consumers without harming small studios or limiting new game production is key. Legislators tackling this issue will need to navigate a delicate balance, requiring deep understanding of the complex and ever-changing video game market.

Legislative Balance for the Initiative
This initiative demands highly delicate legislative work.