and another thing that I don't understand is why do I need to write GetComponent() for every component that I need and not just gameObjectName.component
I'm not Unity user, so I will answer based on my understand of writting game engines.
That allows to test if an object has some component or not by using some sort of function "has_component()". Notice that Unity is strongly centered around the parent-child and subling-subling system.
Parent-child: the object need to test if the parent contains a component. Example: the global position of an object is affected by the parent's position, so the object, to get its global transform, needs to check if the parent contains the 'transform' component first.
Sibling-sibling: when one component is dependent on another, the component needs to check if the owner object contains that other component first. Example: shader component and mesh component.
How's that acomplished? Everything is in that list owned by the object is a "component", that contains information (maybe a string variable "type") about what is the type of the component. If I was to reproduce the same thing in C, I could just make an structure holding the string and a pointer to the actual data of the component:
struct ccomponent {
char type[200];
void *data;
void (*on_update)(ccomponent *);
void (*on_render)(ccomponent *);
}
struct cobject {
char type[200];
int parent_id;
list/array *components;
...
}
As ApochPiQ said:
This is not really a situation where one approach is "right" and another is "wrong" - there are tradeoffs involved in both scenarios, and sometimes those are nice and sometimes they are pesky.
I've implemented the situation above before, and it has pros and cons. Wasn't suitable for my case, as to me having to search for parent/sibling components and comparing strings to identify their types was wasteful. However, the pro is the agnosticity regarding the type of the owner. With that system, I can access the 'transform' of a parent without knowing what's the type of the parent, just retrieving the 'transform' component.
The other approach (that is: without that system), update() and render() goes to the object, and not the component, and the "component" itself doesn't exist anymore, it's something like this:
struct cplayer {
ctransform transform; ----or---- double x; double y;
int life;
}
struct cobject {
char type[200];
void *data; ----> set to cplayer
int parent_id;
void (*on_update)(ccomponent *);
void (*on_render)(ccomponent *);
...
}
Here, when I allocate a 'cobject', I will also allocate the 'cplayer' and assign 'void *data' to it, and I will also assign 'on_update()' and 'on_render()' that will deal with it. I don't need to parse a list/array of components anymore, since I have direct access (through a pointer) to the player variables. However, with that abstraction lost, I don't have access to a parent data UNLESS I write a code for each case: for each different type of parent.
Pros and cons.