Unity - Using components as interface - c#

The need :
To have the possibility to interact with Component instances of different type attached on GameObject instances through an interface.
For exemple, if I have a game with soldiers, and assuming that medics and snipers both are soldiers, I want to be able to get the Soldier component attached to a soldier GameObject, regardless of whether that soldier is actually a Sniper or a Medic. Then, I could do something as follows : soldier.GetComponent<Soldier>().Respawn(); which would end up calling either Medic.Respawn() or Sniper.Respawn(), depending on the actual type of the soldier.
Possible but dirty solution 1 :
A first naive approach would be to have the Sniper and Medic components implement a Soldier interface. However, this causes several problems.
For example, if you want to check whether a GameObject has a component implementing Soldier, you can't, because Soldier is only an interface, not an actual Unity Component. Thus, calling GetComponent<Soldier>() on a GameObject having, for exemple, a Medic component would not return that Medic component, even if Medic does implement Soldier.
(Actually you could check this by iterating over all the components and using the is operator, but that would be dirty and slow).
Possible but dirty solution 2 :
A second approach is to create a base Component class Soldier from which the Medic and Sniper classes would inherit.
But this also poses several problems.
First, the Unity events (Awake(), Start(), etc) are only going to be called on the leaf classes of the hierachy, forcing you to manually call the same functions on the parent class. Anyone who has tried that knows that it's easy to forget calling something, which results in improperly initialized objects, for example.
And second, the usual problems of inheritance are here too. For exemple, if I want my Medic and Sniper componenents to not only be Soldier, but also be Explodable or VehicleDriver or whatever, I can't, because C# does not support multiple inheritance.
The approach I'm thinking about :
I've thought about a way to design my code so that the issues listed above are solved.
The idea is to have a Component class that acts as the interface and have that interface component coexist with the acutal component on the same GameObject. In other words, let two game objects. One of them would have both a Soldier and a Medic component and the other one would have both a Soldier and a Sniper component. All three component classes, i.e Soldier, Medic and Sniper would be completely separate and all inherit from MonoBehaviour.
The other parts of the code would only interact with the Soldier component. In this case you would be able to do : soldier.GetComponent<Soldier>().Respawn();.
Then, it would be the resposibility of the "interface" component (i.e Soldier) to use the actual component (i.e Medic or Sniper) in order to perform the specific action.
However, since Soldier does not known anything about Medic, Sniper or whatever implementation might be added in the future, the Soldier component exposes an actual interface that the Medic and Soldier have to implement.
Since it is possible to implement multiple interfaces, using this solution, it would be possible to use more than one "interface" component. For exemple, a soldier game object could have the following "interface" components : Soldier and Explodable, and the following "actual" componenent : Medic which would implement both interfaces Soldier.ISolder and Explodable.IExplodable.
What do you think about this solution ? Thx !
EDIT :
I coded what I had in mind and it seems to work nicely. I've also created an editor script allowing to have the "interface" component reference the "actual" component without having public fields, but properties instead. I'll post the code, just in case someone wants it :
WaterComponent.cs - The "interface" component for water objects :
using System;
using UnityEngine;
public class WaterComponent : MonoBehaviour
{
#region Interface
public interface IWater
{
bool IsPointSubmerged(Vector3 worldPoint);
Vector3 GetNormalAtPoint(Vector3 worldPoint);
}
#endregion Interface
#region Properties
public IWater Water
{
get
{
return waterImplementation;
}
set
{
Component asComponent = value as Component;
if (null != value && null == waterComponent)
{
throw new ArgumentException($"The given {typeof(IWater).Name} is not a {typeof(Component).Name}.");
}
waterComponent = asComponent;
waterImplementation = value;
}
}
#endregion Properties
#region Fields
[SerializeField]
private Component waterComponent;
private IWater waterImplementation;
#endregion Fields
#region Public methods
public bool IsPointSubmerged(Vector3 worldPoint)
{
return waterImplementation.IsPointSubmerged(worldPoint);
}
public Vector3 GetNormalAtPoint(Vector3 worldPoint)
{
return waterImplementation.GetNormalAtPoint(worldPoint);
}
#endregion Public methods
#region Unity events
private void Awake()
{
waterImplementation = waterComponent as IWater;
}
#endregion Unity events
}
RealWater.cs - The "actual" component implementing the "interface" component :
using UnityEngine;
public class RealWater : MonoBehaviour, WaterComponent.IWater
{
#region WaterComponent.IWater implementation
public bool IsPointSubmerged(Vector3 worldPoint)
{
return SpecificIsPointSubmerged(worldPoint);
}
public Vector3 GetNormalAtPoint(Vector3 worldPoint)
{
return SpecificGetWaterAtPoint(worldPoint);
}
#endregion WaterComponent.IWater implementation
#region Non-public methods
private bool SpecificIsPointSubmerged(Vector3 worldPoint)
{
return true;
}
private Vector3 SpecificGetWaterAtPoint(Vector3 worldPoint)
{
return transform.up;
}
#endregion Non-public methods
}
WaterComponentEditor.cs - The custom editor allowing not to have naked fields exposed :
using UnityEditor;
[CustomEditor(typeof(WaterComponent))]
[CanEditMultipleObjects]
public class WaterComponentEditor : Editor
{
#region Serialized properties
private SerializedProperty waterProperty;
#endregion Serialized properties
#region Overridden methods
public override void OnInspectorGUI()
{
serializedObject.Update();
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(waterProperty);
if (EditorGUI.EndChangeCheck())
{
((WaterComponent) target).Water = waterProperty.exposedReferenceValue as WaterComponent.IWater;
}
serializedObject.ApplyModifiedProperties();
}
#endregion Overridden methods
#region Unity events
private void OnEnable()
{
waterProperty = serializedObject.FindProperty("waterComponent");
}
#endregion Unity events
}
Feel free to reuse, unless you see a flaw with this, in which case I'd would really like to know about it !!
EDIT : Well the problem with that custom editor is that you can have the "interface" component reference any Component even if the latter does not implement the real interface exposed by the "interface" component. It is still possible to do some run time checks in the custom editor script, but that's not so clean. However I think the advantages remain good enough in comparison to that issue.

Well...
GetComponent family of functions now supports interfaces as generic argument.
Unity 5.0 release notes : https://unity3d.com/fr/unity/whats-new/unity-5.0
Whatever...

Related

How to create a MonoBehaviour fabric for non-MonoBehaviour classes?

I am developing a small nuclear reactor simulator game. I have a bunch of reactor component classes: HeatVent, HeatExchanger, UraniumCell etc. They are not deriving from MonoBehaviour since they don't have any Unity logic, but they do implement a shared interface IReactorComponent. What I want to do is to be able to create prefabs of such components (simple heat vent, advanced heat vent, doubled uranium cell etc.) The prefabs would have different sprites and something like that, but the main issue is to define what reactor component class the prefab is related to, because I can't just drag'n'drop a non-MonoBehaviour script on inspector. Also, I want to be able to set settings in the inspector (for example, HeatVent has CoolAmount and HeatCapacity properties, UraniumCell has FuelAmount, HeatProduce and PowerProduce properties).
I have read about factory method pattern and as I understood, I have to create a fabric class that derives from MonoBehaviour for each reactor component class like HeatVentBehaviour, HeatExchangerBehaviour etc. Yes, that completely solves my issue with prefabs but is there any way to not create an additional MonoBehaviour wrap for each class? If I had 15 IReactorComponent classes, I would need to create 15 fabrics which feels like not the greatest solution.
Sounds like what you are looking for is ScriptableObject!
Instances of those are assets so they don't live in a scene but in the Assets folder and basically behave a little bit like prefabs except: They already exist and do not need to be instantiated anymore.
Mostly they are used as just configurable data containers. They have an Inspector so you can easily fill them with your desired data and references to other assets (e.g. the related prefab in your case).
But in addition you can as well let them implement behavior like your interface and thereby change the behavior of your scene objects by using different implementations of a method from different ScriptableObjects!
For the factory you then only need to figure out for which method to use which ScriptableObject instance e.g. either by having different methods or by having a Dictionary where you fill in your SO references.
Just as an example how this might look like (make sure each MonoBehaviour and ScriptableObject has its individual script file with matching name)
SpawnManager.cs
public class SpawnManager : MonoBehaviour
{
[SerializeField] private ReactorComponentBehaviour _behaviourPrefab;
[SerializeField] private BaseReactorComponent[] _components;
public bool TrySpawn<T>(out T component, out ReactorComponentBehaviour componentBehaviour) where T : IReactorComponent
{
component = default(T);
componentBehaviour = default;
var foundComponent = components.FirstOrDefault(c => c.GetType() == typeof(T));
if(foundComponent == null)
{
Debug.LogError($"No component found of type {T.GetType().Name}!");
return false;
}
// Here Instantiate doesn't spawn anything into the scene but
// rather creates a copy of the ScriptableObject asset
// This is just to avoid that any changes in the fields during the game
// would change the original ScriptableObject asset and thereby ALL related behavior instances
component = Instantiate ( (T) foundComponent);
// This now indeed spawns the related MonoBehaviour + GameOver
componentBehaviour = Instantiate (behaviourPrefab);
componentBehaviour.Init(component);
return true;
}
}
BaseReactorComponent.cs
public abstract class BaseReactorComponent : ScriptableObject, IReactorComponent
{
public abstract void WhateverIReactorComponentNeeds();
// Common fields and methods e.g.
public Sprite Icon;
}
HeatVent.cs
[CreateAssetMenu]
public class HeatVent : BaseReactorComponent
{
public int CoolAmount;
public int HeatCapacity;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
UraniumCell.cs
[CreateAssetMenu]
public class UraniumCell : BaseReactorComponent
{
public int FuelAmount;
public int HeatProduce;
public int PowerProduce;
public override void WhateverIReactorComponentNeeds ()
{
// Do something
}
}
And finally you need only one base prefab with the
ReactorComponentBehavior.cs
public class ReactorComponentBehavior : MonoBehaviour
{
[SerializeField] private Image _image;
private IReactorComponent _component;
public void Init(IReactorComponent component)
{
_componemt = component;
// Do other stuff like e.g. adjust visuals according to the component etc
_image.sprite = component.Icon;
}
// And then use whatever this behavior should do with the assigned component
}
So in the end you would use that like e.g.
if(spawManagerReference.TrySpawn<HeatVent>(out var component, out var componentBehaviour)
{
// Do something with the behavior e.g. set its position, parent etc
}
else
{
Debug.LogError($"Failed to get a {nameof(HeatVent)}!");
}
If then at some point you still want different additional behaviours you could let them inherit from the common ReactorComponentBehavior and rather reference the prefabs inside the BaseReactorComponent itself .. then every component can bring its own prefab but still have a common core behaviour

How to set up Unity unit tests with *many* Script directories

I've been reading many articles online about how to set up a unit test, and most of all it seems pretty straight forward: you create a Test directory using Test Running in Unity. According to this post here, if you run into a namespace issue, then you create an assembly definition file in your scripts directly, reference it in your test.asmdef file, and boom you can start running tests successfully.
My problem is I've inherited a project with 34 of these Scripts directories, and the moment I add an assembly definition file to one, it creates a namespace issue with all other namespaces/objects. Logical conclusion is I create an .asmdef in each of these files and create references where they are needed. Unfortunately this program was designed in such a way that this creates a cyclical dependency among the assembly definition files. This circular dependency is not an issue in the general usage of the program. Without restructuring the code base, is there a way to make this code testable?
Simple solution would be to add the asmdef to the top folder of your 34 script folders.
If they are all across Assets folder then you can create that Script folder and move them all in there. That should not break your project as Unity will update all connections.
The long term solution you may have to go for is creating abstract/interface in assembly that current code would implement.
Say you have script Player in player.asmdef and you want to test it. But it has a dependency to Inventory which is not in any asmdef. You could move Inventory but it also has its set of dependencies and so on.
Instead of moving Inventory, you create a base inventory as abstract and interface in the manager.asmdef and add this one to player.asmdef. Assuming Player.cs uses
List<Item> Inventory.GetInventory();
void Inventory.SetItem(Item item);
Your IInventory.cs could look like so
public abstract class InventoryBase : MonoBehaviour, IInventory
{
// if those methods were self contained, meaning they don't use any outside code
// the implementation could be moved here
public abstract List<Item> GetInventory();
public abstract void SetItem(Item item);
}
public interface IInventory
{
List<Item> GetInventory();
void SetItem(Item item);
}
public class Item
{
public string id;
public int amount;
public string type;
}
Then the Inventory class
public class Inventory : InventoryBase
{
// Implementation is already there since it was used
// but requires the override on the methods
}
It may feel like adding extra useless layers but this adds a second advantage of major importance, you can mock the IInventory object in your player test:
[Test]
public void TestPlayer()
{
// Using Moq framework but NSubstitute does same with different syntax
Mock<IInventory> mockInventory = new Mock<IInventory>();
Mock<IPlayer> mockPlayer= new Mock<IPlayer>();
PlayerLogic player = new PlayerLogic(mockPlayer.Object, mockInventory.Object);
mock.Setup(m=> m.GetInventory).Returns(new List<Item>());
}
This assumes the Player class is decoupled between the MonoBehaviour and the logic:
public class Player : MonoBehaviour ,IPlayer
{
[SerializedField] private InventoryBase m_inventory;
PlayerLogic m_logic;
void Awake()
{
m_logic = new PlayerLogic(this, m_inventory);
}
}
public interface IPlayer{}
public class PlayerLogic
{
IPlayer m_player;
IInventory m_inventory
public PlayerLogic(IPlayer player, IInventory inventory)
{
m_player = player;
m_inventory = inventory;
}
// Do what you need with dependencies
// Test will use the mock objects as if they were real
}
Notice that Player uses InventoryBase since it cannot see Inventory not being in an assembly. But as you drop in the Inventory object, the compiler will use the code down there even if Player type is not aware of Inventory type.
If you were to use another method from Inventory into Player, then you'd need to add the abstract to the base class and the declaration in the interface for testing.
PlayerLogic uses the interface instead of the base type to make the testing possible.

How can I manage different weapons in manager script?

I'm making my rpg game in unity. As I need a lot of different weapons, I tried to make a script for each weapons. Then instead of enacting the Attack function in each weapon's object, I wanted to controll them in other class such as WeaponManager for some reason.
However, I got no idea how to manage variety of classes. It doesn't seem efficient to write all the codes for each classes, such as
if((gameObject).name=="Wand")
gameObject.Getcomponent<Wand>().Attack();
else if((gameObject).name=="Sword")
gameObject.Getcomponent<Sword>().Attack();
... and so on.
In other way, I also thought of SendMessage function, but it doesn't seem like efficient as well.
I'm wodering how can I solve this problem. Which method can I use?
Classical example use case for object oriented programming:
Inheritance!
Use a shared parent class both inherit from and either implement the method virtual with a shared default behavior the inheriting classes can overwrite/extend or make it abstract so inheriting classes have to implement it.
public abstract class Weapon : MonoBehaviour
{
public abstract void Attack();
// alternatively implement some default behavior
// in this case the child classes can but don't have to override this
//public virtual void Attack()
//{
// Debug.Log("Harr Harr .. but I'll do nothing else!", this);
//}
}
and then
public class Wand : Weapon
{
public override void Attack()
{
...
}
}
and
public class Sword : Weapon
{
public override void Attack()
{
...
}
}
then simply go
gameObject.GetComponent<Weapon>().Attack();

Can I use an abstract base class as a Unity Editor element?

I'm trying to create a component for a Unity GameObject, let's call it MediaController. I want it to be able to manage timing (play/pause/etc) for different media (audio/video). I created an abstract class PlayableMedia with basic properties/fields/methods and created 2 classes, PlayableVideo and PlayableAudio, that inherit and implement according to our needs.
The intent was to have a singular list of PlayableMedia that could be audio/video agnostic, allowing an easy (i.e.) media.Play() call regardless of type at specific app times... but my field public List<PlayableMedia> MediaList; is not appearing in the editor and there is no error.
So ultimately my question is as the title states: is it possible to use the PlayableMedia class as the type of a field?
I'm suspecting "no" based on my experiences with this, but I've found links that say "yes" or "yes, sort of" that seem to point to custom editors/inspectors/drawers, but I have 0 experience with those and haven't been able to get it implemented (see below).
[System.Serializable]
public class RegisteredMedia
{
public float StartTime;
public PlayableMedia Media;
}
[CustomPropertyDrawer(typeof(RegisteredMedia))]
class RegisteredMediaDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
EditorGUI.BeginProperty(position, label, property);
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), new GUIContent("Playable Media"));
var indent = EditorGUI.indentLevel;
EditorGUI.indentLevel = 0;
Rect rectStartTime = new Rect(position.x, position.y, 30, position.height);
Rect rectMedia = new Rect(position.x + 35, position.y, 50, position.height);
EditorGUI.PropertyField(rectStartTime, property.FindPropertyRelative("StartTime"), GUIContent.none);
EditorGUI.PropertyField(rectMedia, property.FindPropertyRelative("Media"), GUIContent.none);
EditorGUI.indentLevel = indent;
EditorGUI.EndProperty();
}
}
public class MediaController : MonoBehaviour
{
public List<RegisteredMedia> MediaList = new List<RegisteredMedia>();
\\[...] rest of implementation
}
Can anyone help me out? Either confirm that it isn't possible, or help me with an implementation if it is?
Also, if it can be done with custom editors/inspectors/drawers, can someone help me get a single item in the List<RegisteredMedia> to display as Start Time ____ Playable Media [=====] (where PlayableMedia will be a GameObject with the proper component attached)?
Be careful of your use of the word "property". In C# it means something very specific.
is it possible to use the PlayableMedia class as the type of a property?
I think you are asking the wrong question here. Rather than coming up with a new implementation, consider why your current implementation might not be working?
Firstly, I'll give you the following example:
public abstract class Car : MonoBehaviour { }
public class Jeep : Car { }
public class Ferrari : Car { }
public class CarHolder : MonoBehaviour
{
public List<Car> Cars;
}
In this example, I could create a GameObject with the CarHolder component, and was able to attach both Jeep and Ferrari Objects. It is important to note that each monoBehavior class I defined sits in its own file and the file name matches the class name. This is just how Unity works.
So to answer the question I think you are asking (assuming we replace "property" with "field"), it is indeed possible to use abstract class types and have them show up in the inspector. I suspect that you need to separate your classes into separate files.
It's possible natively since 2019.3 release via [SerializeReference] attribute https://docs.unity3d.com/ScriptReference/SerializeReference.html
e.g.
using System.Collections.Generic;
using UnityEngine;
using System;
[Serializable]
public abstract class AbstractExample {
public int foo;
}
// [Serializable] not needed here
public class ConcreteExample : AbstractExample {
}
public class Consumer : MonoBehaviour {
[SerializeReference]
public List<AbstractExample> examples = new() { new ConcreteExample() };
// both the list and the concrete instance visible in the editor
// and editable without any additional editor extensions
// note that you can't effectively add new items to the list via editor
// since that way you create a faulty abstract-ish instances instead
// (no actual way for the editor to know what subtype do you want)
// if you're OK with having the base class being not explicitly abstract
// and can provide a sensible constructor for it, just drop the abstract
// you'll still have full polymorphism support etc. with SerializeReference
}

Using constructors of monobehaviors

OK, I know why we don't use constructors on monobehaviors in Unity. For nearly all of the use cases, Start and Awake fit perfectly. Usually.
However, there's a great C# feature that only can be used with constructors — readonly fields. In my particular situation, I work on a project with a lot of developers and write an abstract MonoBehavior that will be subclassed and rewritten a lot of times by a lot of different people. And I want a field to act like constant throughout object's lifetime (or it WILL introduce strange, hard-detectable bugs) but with different values in different subclasses — in other words, a classic use-case for a readonly field. (I don't want to use properties because they have no language-enforce obligation of staying the same.)
So — can I safely use MonoBehavior's constructors? Won't some strange dragon come out of the lair somewhere down the road? What should I know if I choose to use them?
I think the main reasons Unity wants you to stay away from using the constructor is that the constructor isn't called on the main thread, and the constructor is called before serialized data is restored to the object.
So if the readonly fields you're setting in the constructor depend on data from serialized fields, then they won't work right. And if they don't then you can just assign them at initialization.
You could also use a container object to keep your readonly values, but there's nothing stopping someone else from re-assigning that container later.
using UnityEngine;
using System.Collections;
public class ReadOnlyTest : MonoBehaviour {
public string part1 = "alpha"; // change these values in the editor and
public string part2 = "beta"; // see the output of the readonly variable "combined"
public readonly string combined;
// just assign to readonly vars.
public readonly string guid = System.Guid.NewGuid().ToString();
public readonly float readOnlyFloat = 2.0f;
public class ReadOnlyContainer {
public readonly int readOnlyInt;
public readonly float readOnlyFloat;
public readonly string readOnlyString;
public ReadOnlyContainer(int _int, float _flt, string _str) {
readOnlyInt = _int;
readOnlyFloat = _flt;
readOnlyString = _str;
}
public override string ToString() {
return string.Format("int:{0} float:{1} string:{2}", readOnlyInt, readOnlyFloat, readOnlyString);
}
}
public ReadOnlyTest() {
combined = part1 + part2;
}
public ReadOnlyContainer container;
void Awake() {
if (container == null) {
container = new ReadOnlyContainer(Random.Range(-100,100), Time.realtimeSinceStartup, System.Guid.NewGuid().ToString());
}
}
void Start () {
Debug.Log(container.ToString());
Debug.Log("combine1: " + combined);
Debug.Log("guid: " + guid);
}
}
Many unity classes are created by reflection, and there's no way for unity to non-default constructors properly; hence the limitation.
#Calvin's answer points out one very good option: create classes that are not derived from MonoBehaviour; these can have constructors like any other C#. You can put those classes into fields in MonoBehaviours as long as your code can tolerate missing instances. If you use the typical quasi-singleton pattern from #Calvin's answer you'll always get an instance when you need one, and you can push the 'give me an instance the first time' logic into a method that can be overridden in derived classes to customize behavior.
If you want constant-like behavior, with the option of different values in derived classes it may be easier to define a method rather than a field. The method is effectively read-only, and it has more predictable mutations as per #Jerdak's answer.
If you must have constructors, the last option is to use the monobehavior as a minimal placeholder and write all of the interesting stuff in a class of your own, then delegate all of the work in the Monobehavior to your class.
using UnityEngine;
using System.Collections;
public class OuterPlaceholder: MonoBehaviour {
public InnerBehavior _Inner;
public void Awake() {
if (_Inner == null) {
_Inner= new InnerBehavior(4);
}
}
public void Update()
{
_Inner.DoUpdate(this);
}
}
public class InnerBehavior
{
public readonly int UpConstant;
public InnerBehavior (int up)
{
UpConstant = up;
}
public void DoUpdate(MonoBehaviour owner)
{
owner.transform.Translate(Vector3.up * UpConstant * Time.deltaTime);
}
}
This option may work best if you are sure you're going to get a lot of complex inheritance as the project evolves.
Finally: It's perfectly OK to name the field _ReadOnlyField or _DoNotWrite or whatever to tell users not to muck with it. All Python programmers live with the possibility of somebody doing far worse things and it seems to work out fine most of the time :)
From the script refs:
If you attempt to define a constructor for a script component, it will
interfere with the normal operation of Unity and can cause major
problems with the project.
MonoBehaviours are constructed many times during serialization, something Unity does quite frequently in the editor and I suspect there is a lot more going on the hood to hook the C layer to C#. Ultimately the behavior is undefined so it's best not to try.
Regarding "but with different values in different subclasses", from MSDN:
assignments to the fields introduced by the declaration [readonly] can only occur as part of the declaration or in a constructor in the same class.
So no modification in derived classes.

Categories