- Home /
Action delegates in interfaces
I have an interface which allows me to pass any object which scores points when it's killed (regardless of the script itself) to a UIManager which displays those points if necessary. It looks like this and works fine:
using System;
using UnityEngine;
public interface ICanScorePoints {
// gameObject is needed for finding the entity's transform position
// This is inherited from MonoBehaviour and doesn't need explicit implementation
GameObject gameObject { get; }
// The PointValue property is set privately by each entity internally,
// based on a public field in the Inspector (declared in the entity).
// We only need a getter here.
int PointValue { get; }
// This boolean property is set by each entity internally,
// based on a public field in the Inspector (declared in the entity).
// This allows selecting, in the prefab, whether the entity
// should show its score value at its position when destroyed.
bool DisplayPointsWhenKilled { get; }
}
I would like to add an Action to this interface, because all objects implementing this interface should be able to fire a "score points" event. I know that interfaces can't include fields, but I've read that I could use a property instead. So I tried adding this to the interface:
Action<ICanScorePoints> OnScorePoints { get; set; }
This doesn't generate any errors. However, when I try to add this in one of the scripts implementing the interface, like this...
public static Action<ICanScorePoints> OnScorePoints { get; set; }
...I get an error saying that my script "does not implement interface member ICanScorePoints.OnScorePoints.get and the best implementing candidate AsteroidController.OnScorePoints.get is static" (Visual Studio says more or less the same thing, that the object cannot implement an interface member because it is static).
The reason I'm doing this like that is because the objects that can score points have nothing to do with each other conceptually, and can't use an inheritance system. And I don't want my UIManager to subscribe to each and every enemy type individually to listen for scoring events.
Is it possible to do what I'm trying to do, is it only a question of syntax or is this fundamentally impossible?
Thanks.
Why does OnScorePoints need to be static? Shouldn't it be for each individual instance?
If the Action is not static, I cannot get other scripts to subscribe to the event without grabbing a reference to the instance first, which my whole conceptual problem with the UI$$anonymous$$anager, as I explain in the comment to @OusedGames' answer
But static means ALL instances will trigger the SA$$anonymous$$E event. If you want to get instance-specific OnSpeedChanged events (like for a GUI marker over each object), you must subscribe to each instance's own event. I would say that keeping references when you instantiate the objects is much more elegant than even Unity's built-in Invoke system (like Awake and Update functions).
Also, compared to the performance cost of instantiating prefabs, I think using FindObject methods are much less costly.
Answer by ShadyProductions · Feb 07, 2018 at 08:52 PM
You can't do that. Interfaces, abstract, etc. cannot apply to static members. If you want to accomplish this, you will have to manually remember to do it on all deriving classes.
I thought of that, but even so, I would still need my UI$$anonymous$$anager to subscribe to every single object that implements ICanScorePoints, which would negate the whole point of the interface... Is there a cleaner way to do this (I'm not particularly crazy about this interface, if there's a better way to accomplish this I'd be delighted). Thanks.
It's no big deal, the only difference from using static is that: you will need to GetComponent()-ICanScorePoints-..... then subscribe to the Action/delegate. And remember to always check for null, when invoking an Action
I'm not sure I understand. Could you please explain this is more detail? Thank you.
Answer by OusedGames · Feb 07, 2018 at 09:30 PM
Hello buddy,
The problem is:
- You can't use static keyword on an interface component.
You can solve it two ways:
- UIManager will get all of objects references, then subscribe to the action
- OR the other way around
- Each object that implements ICanScorePoints will look for the UIManager instance, and add itself to a list there -
//Exemple
public class UiManager : MonoBehaviour{
private void Start(){
var myArray = FindObjectsOfType<ICanScorePoints>();
for(int i = 0; i < lala.Length; i++)
myArray[i].myActionVariable += HandleScorePointsObjects; //subscribe here
}
private void HandleScorePointsObjects(){
//Do what you need
}
}
Thanks, well that's what I don't get. Imagine a scenario (pretty close to my reality, in fact), where you'd like your UI$$anonymous$$anager to display an object's speed, but only when the speed changes (I don't need it to check it 60 times per second). Now, I cannot make the UI$$anonymous$$anager look for the object like you describe in the code snippet, because the object doesn't exist yet when UI$$anonymous$$anager starts (the object is instantiated somewhat later). And I don't want to do a FindObjectsOfType in the middle of the game because I was taught it was a bad idea (i.e. that I should cache all reference in Awake or Start). And it's the same way in the other direction, it's either running a slow "FindByName" or Tag or whatever in order to find the UI$$anonymous$$anager, or having the object update the UI$$anonymous$$anager via a static variable or a singleton or what have you, which is something I'm trying to avoid (the object shouldn't worry about updating the UI, its classes only do what they have to do, as separately from any GUI stuff as possible). So I'm caught between several design principles and my head is starting to spin... I can solve this of course with having UI$$anonymous$$anager check in Update() if the object is there and if its speed has changed, but this seems very, very inelegant.
You're right about most of what you say, and these are generally all good practices. The first thing that jumped out is when you said "the object is instantiated somewhat later" - wouldn't this be the best place to hook your data to your UI? $$anonymous$$eaning you keep a reference to the UI$$anonymous$$anager in the script that spawns stuff, and every time it spawns stuff you tell the UI$$anonymous$$anager about the new stuff. Alternatively you could make your ICanScorePoints interface a static class, and use static methods that way. Alternatively you could just make the script its own monobehavior, and keep it fully separated from other scripts' logic and remove the translation layer from public var to interface property.
Yes, passing the object to the UI when I spawn it is probably a good idea. As are the others, of course. Problem is, since I'm a beginner my code is shifting constantly. I do something with an interface, hit a problem I can't solve, decide it's better to do it with an abstract class, then try a whole new approach with statics... Seems like a lot of time lost, but not really. I'm learning a lot through this, especially with the help of everyone here.
Answer by ElijahShadbolt · Feb 09, 2018 at 07:30 AM
I assume you have just one static OnScorePoints event which works for all instances of the ICanScorePoints interface.
Both my options below allow access to a ScorePoints() method on any ICanScorePoints instance.
myClassInstance.ScorePoints();
Option 1: Extension Method
Pros: Can be called for all class instances implementing the ICanScorePoints interface.
Cons: Cannot be overridden (since it cannot be virtual).
public interface ICanScorePoints {
GameObject gameObject { get; }
int PointValue { get; }
bool DisplayPointsWhenKilled { get; }
// No method declaration needed!
}
public static class ScoreManager {
// Static event for every class implementing ICanScorePoints.
public static Action<GameObject, int, bool> OnScorePoints;
// Extension method avaliable on every class implemeting ICanScorePoints.
public static void ScorePoints(this ICanScorePoints obj)
{
if (OnScorePoints != null)
OnScorePoints(obj.gameObject, obj.PointValue, obj.DisplayPointsWhenKilled);
}
}
public class MyClass : MonoBehaviour, ICanScorePoints {
public int PointValue { get { return 22; } }
public bool DisplayPointsWhenKilled { get { return false; } }
// No method definition needed!
}
Option 2: Static Function
Pros: Can be (kind of) overridden, because...
Cons: You must define a normal method for every class implementing the interface.
public interface ICanScorePoints {
GameObject gameObject { get; }
int PointValue { get; }
bool DisplayPointsWhenKilled { get; }
// non-static method must be implemented in every class,
// because interfaces cannot have virtual methods.
void ScorePoints();
}
public static class ScoreManager {
// Static event for every class implementing ICanScorePoints.
public static Action<GameObject, int, bool> OnScorePoints;
// This is like a virtual method, but not really, since
// it still needs to be called explicitly in every class implementing ICanScorePoints.
public static void ScorePoints(GameObject gameObject, int points, bool displayPointsWhenKilled)
{
if (OnScorePoints != null)
OnScorePoints(gameObject, points, displayPointsWhenKilled);
}
}
public class MyClass : MonoBehaviour, ICanScorePoints {
public int PointValue { get { return 22; } }
public bool DisplayPointsWhenKilled { get { return false; } }
// implement ICanScorePoints.ScorePoints() for this class.
public void ScorePoints() {
ScoreManager.ScorePoints(this.gameObject, this.PointValue, this.DisplayPointsWhenKilled);
}
// Note: If you need something like an overridden virtual method,
// put it as a new static function in the ScoreManager class.
}
Your answer
![](https://koobas.hobune.stream/wayback/20220612152152im_/https://answers.unity.com/themes/thub/images/avi.jpg)
Follow this Question
Related Questions
I want to set up a custom event class in javascript. How can that be done in Unity? 0 Answers
Delegate or Event: Reacting to another object's collision functions 2 Answers
Help with multi menu closing using bool 1 Answer
How to check a value from the last frame, while using events. 1 Answer
Events that are triggered based on a random music's beats. 0 Answers