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.
Answer by ElijahShadbolt · Feb 04, 2018 at 04:00 AM
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);
Option 3: Type Casting
// in your UI$$anonymous$$anager
public void ShowPointsAtScreenPosition(I$$anonymous$$illable killable)
{
Text t = Instantiate(textRoa$$anonymous$$g);
t.transform.SetParent(canvas.transform, false);
UFOController ufo = killable as UFOController;
if (ufo != null) {
t.transform.position = Camera.main.WorldToScreenPoint(ufo.gameObject.transform.position);
t.text = ufo.pointValue.ToString();
}
else {
OtherController other = killable as OtherController;
if (other != null) {
t.transform.position = Camera.main.WorldToScreenPoint(other.gameObject.transform.position);
t.text = "OtherController (may not have pointValue property)";
}
else {
$$anonymous$$onoBehaviour behaviour = killable as $$anonymous$$onoBehaviour;
if (behaviour != null) {
t.transform.position = Camera.main.WorldToScreenPoint(behaviour.gameObject.transform.position);
t.text = behaviour.name;
}
else {
t.transform.position = Vector3.zero;
t.text = "Cannot recognize $$anonymous$$illable type.";
}
}
}
Destroy(t, 1.0f);
}
Thank you for clearing this up with example code. I obviously have a design issue here because my I$$anonymous$$illable objects already inherit from an Entity class which has a pointValue variable. I should shuffle stuff around and probably have two interfaces ins$$anonymous$$d of one, because some objects can be "killed" but not all of them are worth points. Something like "IScorePoints". But I get the idea. Thanks again! Like I wrote, I also thought about making the "ShowPointsAtWorldPosition" a component ins$$anonymous$$d, which I could then drag onto any prefab that needs it. But this has proved frustrating because of canvas referencing. I posted a question about it if anyone's interested. As to the Event$$anonymous$$anager, I don't understand. The way I did it does allow multiple subscribers. For example, the UI$$anonymous$$anager simply does this inside OnEnable:
Event$$anonymous$$anager.Instance.OnUFO$$anonymous$$illedByPlayer += ShowPointsAtScreenPosition;
BTW, subscribing (either with your method or $$anonymous$$e) has swelled into another issue because I can't find a way of subscribing only after the Event$$anonymous$$anager has been instantiated (again, in case you're interested). Anyway, thanks a lot for your help. I dig interfaces a bit more each day but the road is long :)
Well I learned something new. Actions and delegates can be multidelegate (have multiple functions assigned to them), just like events. One major difference between delegates and events is that a normal delegate/Action/Func field can be assigned a new value, which replaces all the previous delegates, whereas an event cannot be assigned a new value and must be used with the += operator. It's just syntactical sugar.
public event Action myEvent;
myEvent += () => Console.WriteLine("my delegate");
myEvent = () => Console.WriteLine("second delegate"); // this throws an error
Yes, I know. Since I learned that last week I've replaced all my delegates+events declaration with Action, all is one line and functionality is exactly the same. No idea about performance difference, though, not an issue for me right now (as you've probably figured out :)
Answer by MacDx · Feb 03, 2018 at 11:06 PM
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!
"you should just care about what the interface provides (properties, methods, events and indexers)"
This is what I dont get about interfaces. All I see them providing is placeholders that get repeated anyway. I havent hit upon the actual case in which it'll all make sense, I suppose. So I do other things ins$$anonymous$$d.
I havent hit upon the actual case in which it'll all make sense
Neither have I, but I trust someday I will. $$anonymous$$aybe I haven't worked on a project that's big enough to make interfaces worthwhile.
You get compiler errors if you don't fill in ALL the placeholders, which can be useful. OOP wise: I look at interfaces as a way to allow derived classes to "inherit" from more "types" than just their base class. Sure, the interfaces are completely and purely abstract, but being derived from them (and thus implicitly CASTable into them), can still be useful.
Edit/addition - Unity Wise: GetComponents only takes class types, but imagine how powerful getting any component "derived from"/ implementing a given Interface could be.
I was thinking the same thing. What's the point. Until I read you can do a GetComponent for the, say, I$$anonymous$$illable interface and it will return the first script on the gameobject that implements that interface. Later if you swap one I$$anonymous$$illable component for another everything still works.
It's not built into unity, but here is my own not-so-efficient version:
public static T GetInterface<T>(GameObject inObj) where T : class
{
if (!typeof(T).IsInterface)
{
return null;
}
inObj.GetComponents<Component>(allComponents);
foreach (Component component in allComponents)
{
if (typeof(T).IsAssignableFrom(component.GetType()))
{
return component as T;
}
}
return null;
}
Your answer
![](https://koobas.hobune.stream/wayback/20220612151853im_/https://answers.unity.com/themes/thub/images/avi.jpg)