Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[SOLVED] How to make a public component reference accept specific types?

Discussion in 'Scripting' started by BumblePux, Oct 13, 2018.

  1. BumblePux

    BumblePux

    Joined:
    Jun 14, 2017
    Posts:
    5
    Hello all,

    I have put together a "ColorCycler" component, which currently accepts a drag-drop reference to a Camera component so that the script can access the camera's "backgroundColor" property and cycle through a List of colours, which are defined via a public list. I've added an image for reference.

    ColorCycler_Reference.PNG

    With this working, I have seen that it would be useful to have the component also accept a SpriteRenderer reference, so that I can just place this component on another GameObject and drag the objects' SpriteRenderer onto the ColorCycler component. The ColorCycler would then simply access the SpriteRenderer's "color" property and cycle through the list of colours as before.

    I know I could just have 2 public references, but I'm trying to find a cleaner way so that I can simply drag-drop a component into a single public reference field, which has a "color" property, and have that colour be changed by the ColorCycler.

    I have attempted to use generics to solve this, which seems like the right way to go, but my lack of experience with generics (read: none!) is causing me to simply not come up with a working solution.

    I would appreciate any help and/or advice with this!

    Many Thanks!

    Pux
     
  2. Antistone

    Antistone

    Joined:
    Feb 22, 2014
    Posts:
    2,836
    You can't directly use a generic MonoBehaviour in the Unity inspector, although you can define subclasses that inherit from a generic MonoBehaviour and use those in the inspector; e.g.

    Code (CSharp):
    1. public class MyGenericThing<T> : MonoBehaviour
    2. {
    3.     // Stuff
    4. }
    5. public class MyCameraThing : MyGenericThing<Camera> {}
    However, I don't think generics are going to be much help in this case. If you have a class
    MyGenericThing<T>
    , your code for that class isn't allowed to just assume that T will always have a field called "color"; the compiler won't let you. (Because what if someone tries to create a
    MyGenericThing<int>
    ?)

    In order to get around this, you'd need to specify a constraint on the parameter T. You could require that T implements a specific interface or inherits a specific class, in order to guarantee that it has a thing called "color" (or whatever else your class needs in order to work).

    However, if Camera and SpriteRenderer both have a supertype in common that implements all of the functionality that you need, then you probably don't need to use a generic in the first place; you can just declare your field to be that supertype. For instance, if you wanted to create a script that could change the color of either an Image or a Text, then instead of making MyType<Image> and MyType<Text>, you could just have MyType with a field of type Graphic, and then you can drag either an Image or a Text (or several other things) to that field in Unity's inspector.

    I don't think Camera and SpriteRenderer have any common supertype that you could use in this case, though.

    When there are two classes that have some concepts in common, but don't actually share a common type, the usual solution is to create an adapter (also called a wrapper) that implements a new interface you define by calling the appropriate functions on some other object. By adapting several different classes to a common interface, you gain the ability to write code that can treat all of those other classes as if they were the same--as long as you instantiate all the necessary adapters to sit between them.

    But while that solution makes sense for passing an object around in code, it doesn't really solve your problem in the Unity inspector, because Unity will have no way of knowing about the adapters.

    So you're probably going to be stuck just writing two separate classes (one for Cameras, one for SpriteRenderers) that have a lot of copy-and-pasted code between them. It's usually a good idea to avoid that sort of thing, but I don't think you really have a better option in this particular case.
     
  3. igdeman

    igdeman

    Joined:
    May 15, 2015
    Posts:
    4
    You could make your property to be a type of GameObject and then latter in your script check if they have SpriteRenderer or Camera components on it. If yes you do yours magic otherwise you can throw the error.
     
  4. Munchy2007

    Munchy2007

    Joined:
    Jun 16, 2013
    Posts:
    1,732
    Antistone's adapter solution plus a custom inspector might be a good way to approach it.
     
  5. BumblePux

    BumblePux

    Joined:
    Jun 14, 2017
    Posts:
    5
    Thanks for all of your responses everyone! I appreciate you giving your time to answer this.

    This problem fortunately isn't critical to moving forward on the project, but thought I should at least ask and see what the possibilities were.

    To solely not waste too much time, I may just quickly adjust the script as suggested by @igdeman so that at least the component can be used in multiple places for now without a lot of work.

    At a later stage (and for my own learning), I'll definitely look into creating an adaptor and mess around with creating a custom inspector to see if I can come up with a more elegant/extendable solution, as suggested by @Antistone and @Munchy2007.

    Also just another quick thanks @Antistone for giving me some background on generics. I'll look into this further as well in the near future so that I can work with and know how to use them.

    I'll change the title as I consider this question solved.