📜 ⬆️ ⬇️

Managing state and events between components in a GameObject

Managing state and events between components in a GameObject


Link to the project

As 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 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 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

Source: https://habr.com/ru/post/438554/