Managing state and events between components in a GameObject
Link to the projectAs is known to everyone more or less familiar with the Unity platform, each
GameObject game object consists of components (built-in or custom, which is usually called a “script”). Components are inherited from the
MonoBehavior base class.

And usually, well or often, a direct connection is made to bind the components.

Those. in one component, to obtain data of another component, we obtain the latter using the
GetComponent <...> () method, for example:

In this example, a reference to a component of type
SomeComponent will be placed in the variable
someComponent .
With such a “strongly connected” approach, especially if there are a large number of components, it is quite simple to get confused and maintain the integrity of such a connection. For example, if the name of a property or method changes in one component, you will have to correct it in all components using this one. And this is hemorrhagic.
Under the cut a lot of picturesCreating a solution based on "strong connectivity" components
Let's create an empty project to reproduce the usual situation, when we have certain components and each of them refers to each other, to receive data or to control.

I added two scripts
FirstComponent and
SecondComponent , which will be used as components in the game object:

Now I will define a simple structure for each of the components needed for the experiments.


Now imagine a situation in which we would need to get the values of the
state1 fields from the
FirstComponent component and call its
ChangeState (...) method in the
SecondComponent component. To do this, get a reference to the component and request the necessary data in the
SecondComponent component:

After we start the game in the console, it will be seen that we have received data from
FisrtComponent from
SecondComponent and changed the state of the first

Now, just as well, we can get data in the opposite direction from the
FirstComponent component
and get the data of the
SecondComponent component.

After starting the game, it will also be seen that the data is received and we can control the
SecondComponent component from
FirstComponent .

It was a rather simple example, and in order to understand what kind of problem I want to describe, it would take a lot to complicate the structure and connections of all components, but the meaning is clear. Now the connection between the components is as follows:


Even expanding one game object with new components, if they need to interact with already existing ones, will be rather routine. And especially if, for example, the name of the field
state1 in the
FirstComponent component changes, for example, to
state_1 and you have to change the name in all components where it is used. Or when a component becomes too many fields, then it becomes quite difficult to navigate on them.
Creating a solution based on the "General Status" between the components
Now imagine that we would not need to receive a link to each component of interest and receive data from it, but there would be some object that contains the states and data of all components in the game object. On the diagram, it would look like this:

SharedState or SharedState is also a component that will play the role of a service component and store the state of all components of a game object.
I will create a new component and name it SharedState:

And determine the code of this universal component. It will keep a closed dictionary and an indexer for more convenient work with the component dictionary, also it will be encapsulation and it will not work directly with the dictionary from other components.

Now this component needs to be placed on the game object so that other components can access it:

Next, you need to make some edits to the
FirstComponent and
SecondComponent components so that they use the
SharedState component to store their state or data:


As can be seen from the component code, we no longer store the fields, instead we use the general state and have access to its data using the “state1” or “counter” key. Now this data is not tied to any component, and if a third component appears, then by accessing the SharedState it can access all this data.
Now, to demonstrate the operation of this scheme, you need to change the Update methods in both components. In
FisrtComponent :

And in the
SecondComponent component:

Now the components do not know the origin of these values, that is, they used to access some specific component to obtain them, and now they are simply stored in a common space and any component has access to them.
After starting the game, you can see that the components get the necessary values:

Now that you know how it works, you can bring the basic infrastructure to access the overall state of the base class, so as not to do it all in each component separately:

And I will make it abstract in order not to accidentally create an instance of it ... And it is also desirable to add an attribute indicating that this basic component requires the
SharedState component:

Now we need to change the
FirstComponent and
SecondComponent components so that they inherit from the
SharedStateComponent and remove all unnecessary:


OK. What about calling methods? It is proposed to do the same not directly, but through the Publisher-Subscriber pattern. Simplified.
To implement this, you need to add another common component, by analogy with the one that contains the data, except that this one will contain only subscriptions and will be called
SharedEvents :

The principle is as follows. A component that wants to call some method from another component will not do it directly, but by calling an event, just by name, as we get data from the general state.
Each component subscribes to some events that it is ready to track. And if it catches this event, it executes a handler that is defined in the component itself.
Create the
SharedEvents component:

And we define the structure necessary to manage subscriptions and publications.

For the exchange of data between subscriptions, publications, a base class is defined, a specific one will be determined for the author of each event independently, then several will be defined for example:

Now we need to add a new component to the game object:

and slightly expand the base class of
SharedStateComponent and add the requirement that the object contain
SharedEvents

As well as the general state object, the general subscriptions object must be obtained from the game object:


Now we define the event subscription, which we will process in
FisrtComponent and the class for transmitting data through this type of event, and also change the
SecondComponent so that the event for this subscription will be published:


Now we have subscribed to any event in the name “writesomedata” in the
FirstComponent component and just
print the message to the console when it occurs. And it occurs in this example by calling the publication of an event with the name “writesomedata” in the
SecondComponent component and transmitting some information that can be used in the component that catches events by such a name.
After starting the game in 5 seconds we will see the result of processing the event in
FirstComponent :

Total
Now, if you need to expand the components of this game object, which will also use the general state and general events, you need to add a class and just inherit from
SharedStateComponent :
Continuing the theme