- Home /
Attach a non Monobehavior class to a GameObject
This is my issue:
I want to assign a script that inherits from an abstract class to a GameObject. I used to create MonoBehaviour scripts like "BirdScript" or "CarScript" but I want use proper classes and be able to control the gameobject the class is attached to.
Let me show you an example:
I have to following classes
public abstract class Shape : MonoBehaviour {
private int weight;
private string color;
protected int Weight
{
get { return weight; }
set { weight = value; }
}
protected string Color
{
get { return color; }
set { color = value; }
}
//Example abstract method
public abstract void calculateVolume();
}
public class Cube : Shape {
public void Move()
{
Vector3 pos = transform.position;
pos.x = 2;
transform.position = pos;
}
public override void calculateVolume()
{
//Just print a string
MonoBehaviour.print("Cube volume");
}
}
I also have a prefab GameObject called MyCube which has the Cube class attached
Now I want to create an instance of MyCube from another GameObject which has the following script attached
public class ShapeManager : MonoBehaviour
{
[SerializeField]
public GameObject cube;
// Use this for initialization
void Start()
{
GameObject c = Instantiate(cube);
c.GetComponent<Cube>().calculateVolume();
c.GetComponent<Cube>().Move();
}
// Update is called once per frame
void Update() {}
}
The GameObject cube has the MyCube prefab attached using the Inspector.
My questions are:
What if Cube wouldn't inherit a Monobehavior class ?
Is this approach correct ?
Does it violate OOP ?
The way ShapeManager controls the cube is right ?
GameObject c = Instantiate(cube);
c.GetComponent<Cube>().calculateVolume();
c.GetComponent<Cube>().Move();
My apologies for this long post :)
You can only attach $$anonymous$$onoBehaviour to a GameObject.
You don't need put [SerializeField] in public fields.
The way Shape$$anonymous$$anager controls the cube is correct but I recommend check if you have get the cube. You can use Shape class in Shape$$anonymous$$anager.
GameObject c = Instantiate(cube);
Shape s=c.GetComponent<Shape>();
if(s){// chack if Shape exists
s.calculateVolume();
if(s is Cube){
s.$$anonymous$$ove();
}
}
Edit:
@stbn0 Unity engine is divided 2 part the C++( or C) internal engine and a C# scripts, any $$anonymous$$onoBehaviour can be passed between the 2 parts, any script that is not a $$anonymous$$onoBehaviour can only interact with the C# scripts part.
Only $$anonymous$$onoBehaviour can be added to a object because internally a GameObject have a array/collection of Component, and $$anonymous$$onoBehaviour extend Component class.
You need extend $$anonymous$$onoBehaviour and can't extend Component directly because C# code in unity is treated as $$anonymous$$onoScript and the C++ part of unity will only understand a $$anonymous$$onoScript with a $$anonymous$$onoBehaviour or a ScriptableObject.
thank you for your answer @Sergio7888
Why should I get Shape and check if I get Cube ?
Wouldn't this be a one line solution ? Cube c = Instantiate(cube).GetComponent();
this way you can use any other Shape class derived object Like a Circle or Triangle or other shape you created in the same method and only call move if its is a cube because only cube has $$anonymous$$ove method.
What if Cube wouldn't inherit a $$anonymous$$onobehavior class ?
Then the script would not work because only $$anonymous$$onoBehaviour derived classes are allowed able to exist on a GameObject.
Is this approach correct ?
No, cause it does not work :)
Does it violate OOP ?
I would say it's legal to have an abstract class based on $$anonymous$$onoBehaviour. It's rather violating the "composition over inheritance" principle Unity's whole concept is based on
thank you for your answer @hexagonius
I have this code working, why do you say it doesn't work ? $$anonymous$$aybe I did not explain the full issue.
If shape wasn't an abstract class, would it still be an ilegal implementation ?
Answer by Glurth · Oct 27, 2016 at 10:08 PM
If neither shape nor cube actually use any of the features of a MonoBehavior, like say... defining Update(), serializing data to be saved/loaded, or just accessing the gameObject member, then I would say you can certainly choose to NOT inherit from monobehavior.
However, the cube's move function, DOES access "transform", a property of the monobehavior.
Still, if not actually overriding Monobehavior functions, it's always possible to store a reference to a game object in the class itself (specified as a parameter to the constructor of the class), rather than derive from Monobehvior.
Is it correct? Well, as long as it works, it's just a mater of opinion. Using a reference to an object, rather than deriving from an unnecessary class- does not violate OOP.
I'm not quite sure what shape manager is supposed to do, but it looks like it will create a new object, and if that object has a cube compoent (that is derived from monobehavior), it will call move on it. If the object does not have a cube component it will generate a null exception. If cube is not derived from monobehavior, use of GetComponent will fail, or possibly not even compile.
Update/Edit: Here is an example of how I would do it using both classes derived from monobehavior, and a class NOT derived from monobehavior.
abstract public class Candy : MonoBehaviour
{
public int pointValue;
public float speed;
abstract public void Move();
abstract public bool MoveCondition();
void Start() { }
void Update() { if (MoveCondition()) Move(); }
}
public class LemonDrop : Candy
{
override public void Move() { /* manipulate member variable "transform" to drop down candy*/ }
override public bool MoveCondition() { /* use gameObject to check for certain collisions */ }
}
public class CottonCandy : Candy
{
override public void Move() { /* manipulate member variable "transform" to float around*/ }
override public bool MoveCondition() { return true; }
}
//usage
static class CandyManager
{
public static List<Candy> allCandy=null; //note that this is "effectively" a list of gameobjects, that all have a candy component
//dont let the <Tparam> throw you off, just think of it as a parameter that takes a "type"
static void CreateCandy<Tparam>(Vector3 position) where Tparam : Candy
{
//create gameobject & add component (or load prefab with component)
GameObject g = new GameObject("candy");
g.transform.position = position;
Candy c=null;
c=g.AddComponent<Tparam>();
//add it to allCandy List
if (allCandy == null)
allCandy = new List<Candy>();
allCandy.Add(c);
}
static void InitLevel()
{
CreateCandy<LemonDrop>(Vector3.up);
CreateCandy<LemonDrop>(Vector3.up+ Vector3.right);
CreateCandy<CottonCandy>(Vector3.right);
CreateCandy<CottonCandy>(Vector3.forward);
}
}
thank you for your answer @Glurth
$$anonymous$$aybe the Shape/Cube was not the best example I should have use for my question.
Suppose I have these classes
public class Animal {
}
public class Dog : Animal {
public void Walk(){}
}
I want to attach the Dog class to a Dog GameObject.
If Animal does not inherit from $$anonymous$$onoBehaviour it wouldn't be possible to attach the Dog class to the Dog GameObject.
In the Shape/Cube example, this is the reason why Shape inherits from $$anonymous$$onoBehaviour.
As I said you cant attach non $$anonymous$$onoBehaviour classes, you still can use these classes as field in a $$anonymous$$onoBehaviour class if these classes are [Serializable], and if are not serializable still can be used in your code but can't be sarialized(saved) by the engine.
Lets start by defining what exactly you mean by attach: If you mean "attach" in the same way that a component is attached, then yes, you will need to derive from monobehavior.
However, if the only reason you are attaching to the object, is to get a reference to the object, I would suggest that you do not need to actually "attach" to the object. Rather, one simply stores a reference to the game object in ones class. Edit: while the does not violate Object oriented progra$$anonymous$$g, it might be considered as violating the "component based model" used so heavily by unity,
Here is an uncomplied example, pretty sure I messed up the exact syntax a bit...but should give you the idea.
public class Animal
{
public GameObject referencedSceneObject;
public Animal(GameObject refObj)
{
referencedSceneObject=refObj;
}
}
class Dog:Animal
{
public void walk()
{
referencedSceneObject.transform.x+=1;
}
}
usage...
GameObject c=Instantiate(Cube);
Dog d= new Dog(c);
@Glurth You said: "However, if the only reason you are attaching to the object, is to get a reference to the object", but get a reference is not the only reason, save the values of the object is a reason, in you example you used a Animal with a GameObject reference, but the other possible field values will not be serialized(saved) without a custom code to serialize it or without putting the animal as a field in a $$anonymous$$onoBehaviour or ScriptableObject.
For a question of praticity if you need keep the value data you have as option:
use a $$anonymous$$onoBehaviour and put it in a object.
use a ScriptableObject and create a asset (readonly at runtime).
use a [Serializable] and use a custom code to serialize.
use a [Serializable] and use in a field of a $$anonymous$$onoBehaviour or ScriptableObject.