Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

How to pass multiple parameters to AddComponent

Discussion in 'Scripting' started by JGattes, Nov 20, 2015.

  1. JGattes

    JGattes

    Joined:
    Nov 18, 2015
    Posts:
    10
    Hi all, is there a way from which we can AddComponent to a GameObject passing more than one parameter?

    As a very simple example, suppose I have a class that draws a 2D line segment when its awaken and uses 4 public variables for it:

    Code (CSharp):
    1. public class ClassExample: MonoBehaviour {
    2.  
    3.     public int x1, x2, y1, y2;
    4.  
    5.     private void Awake () {
    6.         //the code here draws the line segment
    7.     }
    8.  
    9. }
    Now, suppose I want to call that class by just adding to it to a new GameObject as new component, but passing the values I desire for x1, x2, y1 and y2. The following is a pseudo example, since it does not work:

    Code (CSharp):
    1. GameObject mygameobj = new GameObject();
    2. mygameobj.AddComponent<ClassExample>(1,1,2,4);
    How can I do that?

    Many thanks!
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    I like to use a class-local factory pattern for this. It looks something like so:

    Code (csharp):
    1. public class ClassExample : MonoBehaviour
    2. {
    3.     public static ClassExample Attach( GameObject go, int arg1, int arg2, string arg3)
    4.     {
    5.         ClassExample classExample = go.AddComponent<ClassExample>();
    6.         classExample.SetupSomeStuffWithTheArgs(arg1,arg2,arg3);
    7.         // do any other Awake() or Start() like stuff you want here
    8.         return classExample;
    9.     }
    10.  
    11.     public static ClassExample Create( int arg1, int arg2, string arg3)
    12.     {
    13.         return Attach ( new GameObject( "ClassExample.Create();"), arg1, arg2, arg3);
    14.     }
    15.  
    16.     void Awake () {
    17.         // try NOT to do anything here!    
    18.     }
    19.     void Start () {
    20.         // try NOT to do anything here!    
    21.     }
    22. }
    You would not necessarily have BOTH a Create() and Attach(). I almost always have one or the other, depending on what kind of script it is.

    You can also pass references to other scripts (obviously) as part of the arguments to the creator, which lets you be explicit about what components are involved at each stage of your setup, as well as explicitly control the startup order.

    EDIT from January 3, 2023: this is my current blurb for this:

    Factory Pattern in lieu of AddComponent (for timing and dependency correctness):

    https://gist.github.com/kurtdekker/5dbd1d30890c3905adddf4e7ba2b8580
     
    Last edited: Jan 3, 2023
  3. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I second the factory pattern. You can go one step further and create a flag and then throw an error if the component is created via AddComponent from anywhere else. Unfortunately this can only be a runtime check, not a compile time one, but it's better then nothing.
     
    JGattes and Kurt-Dekker like this.
  4. JGattes

    JGattes

    Joined:
    Nov 18, 2015
    Posts:
    10
    Thanks for your both replies. So, I think I understand what you are proposing, but one problem persists: how can I pass the parameters when calling AddComponent? Even if I set my class like you did, the Attach and Create functions do not appear as valid function to be called like:

    Code (CSharp):
    1. mygameobj.AddComponent<ClassExample>().Attach(1,2,2,4);
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    I wonder if you could do something like make a private constructor, or some other decorator on the class itself that would prevent .AddComponent<>() from working except from within it.
     
    JGattes likes this.
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    Instead of this:

    Code (csharp):
    1. mygameobj.AddComponent<ClassExample>().Attach(1,2,2,4);
    You want to do:

    Code (csharp):
    1. ClassExample.Attach( mygameobject, 1,2,3,4);
    If you actually want a reference to the created component, take it from the return value that Attach() gives you.

    The main beauty of this pattern is:

    - private variables can be set up in the Attach/Create function
    - all knowledge of the class and how to make it is right there in one place
    - you explicitly control the order of operation
    - you can hand those static factory methods around and have other objects make stuff that they don't even have to know about.
     
    JGattes and Kiwasi like this.
  7. JGattes

    JGattes

    Joined:
    Nov 18, 2015
    Posts:
    10
    Ah, of course! I feel silly, hehe. Instead of passing the component we just call a normal function from the script "ClassExample" and let the component assignment to be done there. You are right, that's beautiful! It's indeed a sweet flexible solution that allows for a much more organized code.

    So, following what BoredMormon has suggested, is it possible to detect within the Class whether it was called as a you said told me or called by a component that was added? I ask that because then I could separate specific behavior for each case (of course knowing that when the class is added with AddComponent the parameters would be set to their default values).
     
  8. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,814
    You could add a check to Start() to see if the "magic flag of Attach" (what @BoredMormon said above) was set by the attach function, and if not, then call a common "set up default values" function.

    You cannot put it in Awake because that gets called when you call .AddComponent, and at that point there is no way you could have set the instance variable because you wouldn't even have an access to the class yet.
     
    JGattes likes this.
  9. JGattes

    JGattes

    Joined:
    Nov 18, 2015
    Posts:
    10
    Excellent, now it works perfectly: I can both assign the component in the Editor or via code with full flexibility. Many thanks!