Our brave hero, who accidentally wandered into the forbidden region of the venomous venus jungle, is surrounded by a swarm of ten thousand hungry semi-intelligent mosquitoes. The question of human edibility for alien organisms, from a scientific point of view, is generally quite controversial - but Venusian mosquitoes, as is well known, sincerely share Marx’s position that experiment is a criterion of truth. It would seem that the situation is hopeless - but the hero, without being taken aback, removes a marching thermonuclear fumigator from the wide leg of his inventory, turns him on with a beautiful movement, and ...
And at this point, the development of the game seriously stalled, rapidly gathering crutches and props, since the game developer was not familiar with one small, but extremely important engine mechanism.
The mechanism that the developer did not know, sleeping on the keyboard after a long programming sprint of an absolutely unnecessary in essence mosquito control manager, is an embedded event system .
As shown by a survey conducted recently in the Russian-speaking segment of the Unity-community, a frighteningly large percentage of domestic developers do not even suspect that such a thing as events was once crafted by the human mind. In part, this general lacuna in knowledge was contributed by official documentation, which circumvented the practical application of events in Unity with a cunning anti-submarine zigzag, in part, the overall intricacy of the architecture of this part of a simple and convenient engine.
In addition, you probably use one of the most well-known mechanisms provided by C # tools. This is a good choice - but, perhaps, it will be interesting for you to learn about the capabilities of the built-in Unity system and its own "buns"?
(if you are familiar with what game events are, quietly skip this section)
Most modern game engines are somewhat similar to conventional mechanical watches. The objects of the engine are the gears of the large clock mechanism of the game world. And, as the clockwork functions due to the fact that the gears are cleverly interconnected, so the life and movement of the game world are carried out due to the interaction of objects with each other.
The metaphor is not entirely accurate - however, it is obvious that every object must have data not only about its own internal state, but also to have some information about the world around it. Methods for obtaining this information are divided into two groups:
Consider an example from the real world. Imagine that we are hungry, and put on this occasion on the fire to cook a large pan of dumplings. The algorithm of this action is simple - throw a pack of dumplings into hot salted water, turn on the fire, leave for ten minutes (sit on the Internet for half an hour) , remove the coals of dumplings from the fire.
Imagine that this is a game, and we are in it - a game object. How do we determine when the time comes to go off the dumplings?
You can use the active way to get information about the time - put a timer / clock in front of your nose - and regularly ask them for time by casting oblique views on the dial. Once, looking again, we will see that the clock has finally counted ten minutes since the start of cooking, we run to remove the pan from the fire.
You can use the passive method - set the alarm by setting it at the time when dumplings should be cooked according to our estimates. When the alarm clock is ringing, we respond to this event and go to take action with regard to food.
Definitely, the second method is more convenient - you do not need to constantly break away from current affairs, look for the “clock” object around you, and then find out from him the data we are interested in. In addition, home ones who are not indifferent to the fate of a close dinner can also subscribe to the “alarm clock rang” event and go about their business, freed from the need to regularly approach and lift our favorite watches to their eyes, or ask us personally about dinner.
In turn, the first method - the method of regular active circulation to get some information - is called polling (from the English. "Poll" ). The object that triggers an event is called the sender , the object that perceives it as the receiver , the event tracking process is called a listen , and the function called by the receiver when an event is received is called the handler .
Good, good, another example. We watched the second "Shrek"?
Remember the moment when the three heroes ride in a carriage to the Distant-Distant Kingdom. Wonderful humorous scene where restless Donkey gets heroes, regularly interested in:
-We already arrived?
-Not.
- ... we have already arrived?
-No, did not come!
- ... we have already arrived?
- Nooooo!
What the Donkey does is a good example of pure polling . As you can see, it is great tires :)
The remaining heroes, in their turn, are conditionally signed for the “arrival” event, and therefore do not waste system time, resources, and also do not require additional programmer efforts to implement an additional hard link between objects.
So how to implement events on Unity? Let's look at a simple example. Let's create two objects in the new scene - we want one to generate a message by clicking on it (or past it, or not the mouse at all), and the second to react to the event and, say, repaint. Let's call the objects appropriately and create the necessary scripts - Sender (generating event) and Receiver (receiving event):
public var testEvent : Events.UnityEvent = new Events.UnityEvent (); function Start () { } function Update () { // // ( ) if (Input.anyKeyDown && testEvent != null) testEvent.Invoke (); }
Pay special attention to the line:
public var testEvent: Events.UnityEvent = new Events.UnityEvent ();
function Start () { } function Update () { } // function Recolor() { renderer.material.color = Color(Random.value, Random.value, Random.value); }
Now look at the Sender object, and see:
Aha Our event, declared as a public variable - an object of type UnityEvent - was not slow to appear in the property editor. True, while it looks lonely and lost, since not one of the objects has been signed on it. We fix this flaw by specifying Receiver as an object listening to an event, and as a handler called upon receiving an event, our Recolor () function:
Let's launch, press any button a couple of times ... there is, the result is achieved - the object obediently repainted in the most unexpected colors:
"Hey! Stop stop! But we already used it when we worked with Unity UI ! ”- you will say ... and you will be suddenly right.
The UI that ships with Unity version 4.6 uses the same event mechanism. Here only the set of UI events is limited to the invoked user input, while the engine allows the programmer to create them for any reason and call them on any convenient occasion.
But let's move on!
Ok, works. Works! But from the hall there are already voices of people who launched the environment, tested, and begin to incriminate the author in ... inaccuracies. And the truth is - after a short study, the inquisitive reader finds out that the Unity editor allows you to associate the event with only one instance of Receiver , and begins to swear. The second copy simply has nowhere to enter - the field of the object receiving the signal does not provide for a plurality of records.
( upd: as it turned out later, actually provides, see comment )
And this is a failure, it would seem.
However, everything is not so bad. Putting “programming with the mouse” aside, we dive into the code. It turns out that at the code level everything is easy, and it is quite possible to make several objects listeners of one event.
The command syntax for this is simple:
Event copy . AddListener ( function handler );
If, looking ahead, there is a desire to stop listening to the event during the execution of the code, the function will help:
Event copy . RemoveListener ( Handler function );
Well, now the power of events in our hands, and, it would seem, the addition of a second recipient is a resolved question.
But there is a nuance - a copy of the event is stored in the Sender , and Receiver knows nothing about it. What to do? Do not drag the pointer to Sender inside Receiver ?
Smart books on this site are often limited to formulations like: " And we leave the solution of this issue to the reader for independent study ." However, I will show here one tricky, not quite obvious (and not the most elegant) solution. It is not always suitable and not everywhere, but if you can get to a situation where you need something more, then by this time you probably already have enough knowledge to cope with this problem yourself.
So, the solution: an event, like any self-respecting variable, can be made static ... without any problems !
Change the Sender code as follows:
public static var testEvent: Events.UnityEvent = new Events.UnityEvent ();
The variable naturally disappeared from the editor, since now it does not belong to any instance of the class. But now it is available globally. To use it, change the Receiver code:
function Start () { OnEnabled(); } function Update () { } function Recolor() { renderer.material.color = Color(Random.value, Random.value, Random.value); } function OnEnabled() { Sender.testEvent.AddListener(Recolor); } function OnDisable() { Sender.testEvent.RemoveListener(Recolor); }
Multiply the Receiver object and launch the game:
Yes, now the ensemble of Receivers is repainted in different colors at any keystroke. But what did we do inside the script?
Everything is very simple - since the variable is static, we now turn not to a specific object instance, but by the class name. Another point of interest is the OnEnabled and OnDisable functions . Objects, as the game progresses, tend to turn off or be removed from the level altogether. Unity, on the other hand, reacts quite nervously when someone has signed up to the event before, but now no object is listening anymore. Like, hey, where is everyone?
And when an object is inactive, it usually does not have a real need to continue to continue to catch events - this, if implemented, could lead to interesting consequences . Accordingly, at least it is reasonable to bind / untie the functions at the moment when the object is turned on / off. And when an object is deleted, the OnDisable function is automatically called in advance — so you can and do not worry about it.
It would seem that everything is elementary - in the same Recolor we now call Destroy (this.gameObject) - and it's done? Let's try, and ...
And it does not work. Only the very first (sometimes two) of the objects receiving the event are deleted, and for some reason the rest does not reach the rest for some reason. Strange? Strange and inexplicable. Maybe someone from the Unity guru will tell me the ideologically correct approach, but since I like to invent bicycles until I found a more elegant solution, I will share my own, again.
If the deletion of an event-handling object in the handler hinders further processing, then let's not delete the object in the handler . We delete it in a coroutine that runs after the event has been processed - for which, by the way, the engine has a standard method. We will delete the object using the Destroy command (this.gameObject, 0.0001) . Removal will occur, but not immediately, but will be delayed by 0.0001 seconds . With the naked eye this pause will not be noticed, but the process of processing the event will not get stuck on the object and calmly continue further.
Sometimes it is necessary to transfer an event, accompanying it with some characteristic of what happened - radius, damage, quantity, subtype, obscene comment of the player, and so on. For these purposes, varieties of UnityEvent with the number of arguments from one to four are invented. Their practical application is not more difficult than the above.
class MyIntEvent extends Events.UnityEvent.<int> {} public static var testEvent : MyIntEvent = new MyIntEvent (); function Start () { } function Update () { // // ( ) if (Input.anyKeyDown && testEvent != null) testEvent.Invoke (15); }
public var health : int = 100; function Start () { OnEnabled(); } function Update () { } function Recolor(damage: int) { health = health - damage; if (health <=0) Destroy(this.gameObject, 0.0001); else renderer.material.color = Color(1.0f, health/100.0, health/100.0); } function OnEnabled() { Sender.testEvent.AddListener(Recolor); } function OnDisable() { Sender.testEvent.RemoveListener(Recolor); }
Works - here are a few stuck together in one frame:
As you can see, nothing particularly tricky to do.
It often happens that an object needs to constantly have information about whether a certain action has happened or whether a certain condition has been fulfilled. A monster in ambush checks if any of the characters have crossed the line beyond which he is allowed to see and attack. Venus mosquitoes are checking whether the fumigator has turned on, during the operation of which they are supposed to scatter with all their squeals in all directions? An impotent and intangible trigger on the map checks whether the player has already destroyed a dozen of the gophers assigned to him on the quest?
If you start to implement all similar conditions in the game with active poling - that is, through regular checks of the values ​​by the object itself - here the developer is waiting for a set of the most interesting difficulties. A monster in ambush must regularly “ask” each item on this side of the line — isn’t he supposedly a character? Mosquitoes should “know by sight” a player with a fumigator (get a pointer to it when they are created?) And periodically specify whether he has turned on this terrible weapon. The trigger is even more difficult to live - it must keep / complete set of all gophers / and regularly check how many are still alive.
Of course, the proposed examples offer a very primitive implementation. Mosquitoes, for example, can be driven at birth into an array of special / mosquito manager /, and the player has a pointer to the mosquito manager. When you turn on the fumigator, the manager's method will be invoked, which will go through the array and cause each mosquito the “scare-and-fly” method ... but redundant managers - do we really need it?
If you work not the first year in Unity, then for certain already somehow implemented events in the projects. Now you know about another event mechanism in Unity. What better UnityEvent similar tools, for example, from among the standard functionality of C #?
There are developers who write in JavaScript . Yes, it is for them, first of all, in the article the code is given in this lightweight language. For obvious reasons, the tools provided by C # are not available to them. For them, UnityEvent is a decent and convenient mechanism available out of the box - take it and use it.
It is also convenient that UnityEvent is the same for JS code and C #. An event can be created in C # code and listen to JS code - which, again, is not directly possible with standard C # delegates. Consequently, these events can, without shame of conscience, link fragments of code in different languages, if such obscenity started in your project (for example, after purchasing a particularly important and complex asset in the Unity Store).
Analysis of the speed of events of different types in Unity showed that UnityEvent can be from 2 to 40 times slower (in terms of time spent on calling the handler) than the same C # delegate system. Well, the difference is not so great, because it is still VERY QUICKLY , if not abused.
Compare with the built-in SendMessage and be horrified :)
I hope that this article was able to add another tool to someone's piggy bank of techniques and approaches, and another secret of a good, in essence, engine became a little less mysterious for someone. Events are extremely powerful tools in your hands.
Of course, this is not a “silver bullet” that solves all the problems of architecture, but they are a convenient tool that solves “one of the primary tasks of developing an application's architecture — searching for such fragmentation into components and such interaction so that the number of links is minimal. Only in this case, it will be possible to further refine the components separately . "
Events are a powerful mechanism in your hands . Never forget its existence. Use it wisely - and the development of your projects will be faster, more pleasant and successful .
In compiling the article were, in addition to those indicated directly in the text, in a variety used various materials of publications.
Here are the most interesting of them, as well as simply useful links on the topic:
Source: https://habr.com/ru/post/308570/
All Articles