- Home /
Writing Reusable Components for both SpriteRenderer and UI.Image?
Hey,
I'm working on a bunch of 2D Projects and often find that a lot of things I want to do with Sprites I also want to do with UI.
For example I might have an alpha fade script which gently fades out SpriteRenderer (useful for when monsters die or coins are collected etc.) But if I wanted to fade out an Image in the UI, I'd need to write a separate component for Image filled with nearly identical code, just with
void Foo(SpriteRenderer s) {
s.color = /* Magic */
}
replaced with
void Foo(Image s) {
s.color = /* Magic */
}
C# 4 has a solution, we could just use the dynamic
keyword and replace them both with
void Foo(dynamic s) {
s.color = /* Magic */
}
but its not available in Unity C#, so how do you guys get around this type of thing? would you use reflection or is that overengineering/too slow? I just don't like the idea of writing modules twice. Maybe there is a pattern that can help, but It's not clear to me.
Both Image and SpriteRenderer are Components. Try something like:
void Foo < Type >( Type s ) where Type : Component {
//some cast to Type needed to use .color without build errors
s.color = /* $$anonymous$$agic */;
}
Could you expand a bit? As far as i'm aware there is no class which both SpriteRenderer and Image can be cast to which has a .color property so I'm not sure how this would help
Answer by TickTakashi · Dec 31, 2015 at 11:50 PM
I ended up using the Adapter Pattern to get around using reflection and wrote a blog post (Link in comments below post). But here's the relevant excerpt just in case someone stumbles upon this question in the future:
First we define the adapter abstract base class, ColorAdapter
, this promises to give us a way of modifying the color. This is what our generic components will use instead of SpriteRenderer
or Image
public abstract class ColorAdapter {
public abstract Color color { get; set; }
}
Next we subclass ColorAdapter
for both Image
and SpriteRenderer
, these each simply delegate color modifications to their "adaptee" member variables.
public class ImageAdapter : ColorAdapter {
Image adaptee;
public ImageAdapter(Image adaptee) { this.adaptee = adaptee; }
public override Color color { get { return adaptee.color; } set { adaptee.color = value; } }
}
public class SpriteRendererAdapter : ColorAdapter {
SpriteRenderer adaptee;
public SpriteRendererAdapter(SpriteRenderer adaptee) { this.adaptee = adaptee; }
public override Color color { get { return adaptee.color; } set { adaptee.color = value; } }
}
Now, in our alpha fade component we simply define Foo
to take a ColorAdapter
instead.
void Foo(ColorAdapter s) {
s.color = /* Magic */
}
Edit: Note that these don't need to be abstract classes and we can instead use an interface ColorAdapter
. It happens to be an abstract class in this case because in my actual implementation there were additional methods with implementations (such as a SetAlpha(float a) function to make the magic easier).
For those interested, the full blog post is here: http://www.ticktakashi.com/2015/12/unity-how-adapters-can-help-you-write.html
Very nice, hadn't heard of the adapter pattern before. Ty for sharing, I guess it kinda resembles my solution (interface), but without the templating step. Great info :)
I should probably add that this doesn't need to be an abstract class, it just happens to be in this case because in my application I had some additional methods in the adapter for easier manipulation. I'll edit.
"If it looks like a duck, swims like a duck, and quacks like a duck, it might be a cow with a Duck-adapter". Something i learned from one of my $$anonymous$$chers back then ^^.
btw: The new dynamic type seems to use reflection behind the scenes to actually call the methods. So even when that pseudo-type was available in Unity it's just syntactical sugar for reflection. An adapter pattern is more clean in my opinion. Duck-typing might has it's uses but in most cases it makes it harder to understand the code since you don't have a clear dependency / relationship between classes. Also in general Duck-typing is usually slower than normal static typing.
Somehow my brain only wanted to read "duct taping"... hehe. Had also never heard of Duck-typing, but from what I see that is exactly what the interfaces give us, or there are details in the wording/implementation that are different?
On an unrelated note, are there any good in depth analysis of c# reflection performance you would recommend? Is it proven to be slow? I end up using it a lot, since you can't serialize dictionaries and other goodies. I make simple classes that are serializable for the inspector, but use reflection heavily :/
Answer by Socapex · Dec 31, 2015 at 08:08 PM
I use templates, but @Gnorpelzwerg's solution is super sexy. It uses reflection, you could probably change it to do without the reflection (hopefully this puts you on the right track). Be sure to share your final code, as this is a good question.
public interface IColorComponent {
Color color {get; set; }
}
public ColorComponent<T> : IColorComponent {
T myComponent;
public ColorComponent(T c)
{
myComponent = c;
}
public Color color {
get {
return (Color)myComponent.GetType().GetProperty("color").GetValue(myComponent, null);
} set {
myComponent.GetType().GetProperty("color").SetValue(myComponent, value, null);
}
}
}
Declare it as an IColorComponent
(that way you don't need to know the type). GetComponent on Image and SpriteRenderer, pass the one that isn't null to new ColorComponent<Image>(blah);
I actually ended up with something similar to this using the adapter pattern to avoid the reflection, just neglected to post it here since there didn't seem to be interest, will post it now though.
Answer by edincanada · Feb 05, 2017 at 02:23 AM
It is awesome that you used the adapter pattern for this. I have had to use it when dealing with "enableable" components, because for some reason, unity does not give us an EnableableComponent interface (colliders, renderers, behaviours, rigidbodies, etc) . My case is mpre frustrating tho, because I don't know when a new enableable component will come out with a new version, and it will require yet a new adapter.
Your answer
Follow this Question
Related Questions
How do I set a sprite to fill the entire screen in UI? 1 Answer
Image.enable not working properly 1 Answer
Change the button target image alpha 1 Answer
Best Practices for Modifying Core Components,Best Practice for Editing Default Components? 0 Answers
Instantiated UI objects with image components not appearing, rest of object works fine. 0 Answers