From interface to gameobject

I’m still trying to get my mind around interfaces and their uses. I have an EventManager which handles events such as this, sent from a UFOController script when the UFO is hit by a player weapon:

EventManager.Instance.UFOKilledByPlayer(this);

This is picked up by EventManager, which has this among other things:

public Action<UFOController> OnUFOKilledByPlayer;
public void UFOKilledByPlayer(UFOController ufo) { if (OnUFOKilledByPlayer != null) OnUFOKilledByPlayer(ufo); }

I have a UIManager component which subscribes to that event and displays a small score value where the UFO was hit, like this:

public void ShowPointsAtScreenPosition(UFOController ufo)
{
	Text t = Instantiate(textRoaming);
	t.transform.SetParent(canvas.transform, false);
	t.transform.position = Camera.main.WorldToScreenPoint(ufo.gameObject.transform.position);
	t.text = ufo.pointValue.ToString();
	Destroy(t, 1.0f);
}

This works fine. However, it only works for the UFOController class. So I tried to generalize so I could apply to other objects which are not UFOs, by defining a basic “IKillable” interface and adding it to the UFOController class (in the class declaration line, next to MonoBehaviour):

public interface IKillable {

	void Kill();

}

If I now redefine the event in EventManager:

public Action<IKillable> OnSomethingKilledByPlayer;

And do the same everywhere along the chain of events, I get to this:

public void ShowPointsAtWorldPosition(IKillable k)
{
	// blah
}

But I can’t find any way of finding the object that the variable k came from. I read somewhere that I should cast it but it doesn’t seem to work, even if I cast it to MonoBehaviour.

As a side note, I can put the “ShowPoints” method as a separate, dedicated component on the gameobjects I’d like to use it, so that they display their point value themselves before being destroyed by Unity, or (maybe better) on the explosion gameobject itself that is instantiated when destroyed (a particle effect and an audio clip). This would seem to conform to the Unity principle of “one component per behaviour”. But then I’m totally coupling the UI to the game logic, am I not?

Thanks for your help.

But I can’t find any way of finding the object that the variable k came from.

That’s the whole point of an interface, you use them so you don’t have to worry about the actual object behind the interface, you should just care about what the interface provides (properties, methods, events and indexers). You could for instance include a method that returns a MonoBehaviour object and when you implement your interface on a script you just do return this; inside that method, however like I said, that would defeat the whole purpose of an interface since you would be tying it to a specific class and then bypassing it. Interfaces are supposed to be used with any object since they only add functionality.

Here’s how I would solve it. The ShowPointsAtScreenPosition uses the ufo variable for only 2 things. Obtaining a Vector3 and an integer value.

t.transform.position = Camera.main.WorldToScreenPoint(ufo.gameObject.transform.position);
t.text = ufo.pointValue.ToString();

So why don’t you include those in your interface like this:

public interface IKillable {
    public int pointValue;
    public Vector3 objPosition;
    private void Kill();
}

Then you can do this in your ShowPointsAtScreenPosition method:

public void ShowPointsAtScreenPosition(IKillable k){
    Text t = Instantiate(textRoaming);
    t.transform.SetParent(canvas.transform, false);
    t.transform.position = Camera.main.WorldToScreenPoint(k.objPosition);
    t.text = k.pointValue.ToString();
    Destroy(t, 1.0f);
}

You would just have to make sure to properly implement those properties in whatever class uses the IKillable interface.

Hope this helps, If you have any questions feel free to ask!

Option 1: Interface

public interface IKillable {
	GameObject gameObject { get; }
	int pointValue { get; }
	void Kill();
}

public class UFOController : MonoBehaviour, IKillable {
	// property `gameObject` inherited from MonoBehaviour.
	public int pointValue { get { return 42; } }
	public void Kill() {
		EventManager.Instance.SomethingKilledByPlayer(this);
	}
}

public class OtherController : MonoBehaviour, IKillable {
	public int pointValue { get { return 88; } }
	public void Kill() {
		EventManager.Instance.SomethingKilledByPlayer(this);
	}
}

Option 2: Inheritance

// base class
public class Killable : MonoBehaviour {
	public virtual int pointValue { get { return 88; } }
	public void Kill() {
		EventManager.Instance.SomethingKilledByPlayer(this);
	}
}

// daughter class
public class UFOController : Killable {
	public override int pointValue { get { return 42; } }
}

// daughter class
public class OtherController : Killable {
	// pointValue defaults to 88
}

Also, I would change your EventManager from using just a single Action to using an ‘event’ with Action listeners.

	// `event` keyword means multiple delegates can be added as listeners, not just one.
	public event Action<IKillable> OnSomethingKilledByPlayer;

	public void AddListener(Action<IKillable> handler) {
		OnSomethingKilledByPlayer += handler;
	}
	public void RemoveListener(Action<IKillable> handler) {
		OnSomethingKilledByPlayer -= handler;
	}

	// this function calls the event
	public void SomethingKilledByPlayer(IKillable killable) {
		if (OnSomethingKilledByPlayer != null) // if at least one listener
			OnSomethingKilledByPlayer(killable); // call event (let listeners handle the event)
	}

	// in your UIManager initialization
	EventManager.Instance.AddListener(ShowPointsAtScreenPosition);