Managing state and events between components in a GameObject
- From the sandbox
Managing state and events between components in a GameObject
As everyone knows, more or less familiar with the Unity platform, each GameObject game objectconsists of components (built-in or custom, commonly 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. one component for another component data, we get the last using the method GetComponent <...> () , like this:
In this example, the variable someComponent will put a link to the component type 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 pictures
Creating 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 experiments.
Now let's 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, we need to get a link to the component and request the necessary data in the SecondComponent component :
After we start the game in the console, it will be clear that we received data from the FisrtComponent from the SecondComponent and changed the state of the first
Now we can also get data in the opposite direction the FirstComponent component 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:
To expand even one game object with new components, if they need to interact with already existing ones, it will be quite 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:
Common state 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 define the code for 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 you can see by the component code, we are no longer we store fields, instead we use the general state and have access to its data by the key “state1” or “counter”. 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. ATFisrtComponent :
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 get 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 derive the basic infrastructure to access the general state of the base class, so as not to do it all in each component separately:
And I will make it abstract, so as not to create it by chance an instance ... And it is also desirable to add an attribute indicating that this base component requires the SharedState component :
Now you need to change the components FirstComponent and SecondComponent so that they inherit from 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.
Let's create the SharedEvents component :
And define the structure necessary for managing subscriptions and publications.
For the data exchange between publication subscriptions, a base class is defined, a specific one will be defined for the author of each event independently, then several will be defined for an example
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 process in FisrtComponent and the class for transferring data through this type of event, and also change the SecondComponent so that the event for this subscription is published:
Now we subscribed to any event in the name “writesomedata” in the FirstComponent component and simply 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 :
Now if you need to expand the components of this game object, which will also use the general state and common events, you need to add a class and simply inherit from SharedStateComponent :
Continuing the theme