📜 ⬆️ ⬇️

Work with character statuses. Unity Experiments

While developing the game on Unity, I was faced with an interesting challenge: how to make an extensible time for negative or positive effects on a character.

In short, I have a character to which some effects can be applied, such as weakening, amplifying, increasing speed, reducing speed and others. To notify the player about the effect of a particular effect, the game provides a status line.

The first versions of this line contained darkened icons of all statuses, and when the effect occurred, the necessary one caught fire.

image
')
On each status was a korutina, which canceled the effect after a specified amount of time.
There is a rather important minus in this decision. If, as a result of certain events in the game, the character has the same effect over a time shorter than the duration of the previous similar effect, then there may be two variants of events.

  1. Absolutely wrong: Runs the second korutina parallel to the first. When the first one completes, it returns the original values, that is, the effect is removed before the second quorutine has finished its work.

    image
  2. Too wrong, but in some cases acceptable: Cancel first quark and start the second. In this case, the duration of the effect will be equal to the duration of the first effect until the moment of cancellation of the corutina + the duration of the effect of the second coroutine.

    image

For my task, both methods are unacceptable, since I need the effect time to be prolonged, so that I get a total of the duration of each effect that occurs, regardless of how many times the effect has been applied.

If a character steps on spikes, his leg is conditionally damaged and he cannot continue to move at the same speed. Let's say the speed is reduced by 5 seconds. If after 3 seconds the character strikes on other spikes, then the speed should be reduced by another 5 seconds. That is, 3 seconds have passed, 2 + 5 seconds left from the new spikes. The duration of the effect should last another 7 seconds from the onset of the second spikes (10 as a whole).

And with the help of Corutin, I did not find a solution to the problem, since it is impossible to find out the remaining time before the completion of the Corutina, in order to add it to the new Corutina.

The solution I found for this task is to use a dictionary. Its advantage over the List is that the dictionary has a key and a value, which means I can refer to any value by key. Plus, this solution allows you to get rid of the permanent status icons in the line and include the necessary ones as needed and install them in the positions in the line in the order of their occurrence.

Dictionary<string, float> statusTime = new Dictionary<string, float>(); 

The dictionary in this case is more profitable to use the queue, since the queue works according to the principles of First In First Out, but the duration of the effects is different, which means that the status that needs to be removed may not be the first in the queue.

For this, I added three methods.

Addstatus

Add the desired status to the dictionary, if there is already such a status in the dictionary, then add the action time. If there is no status, then we calculate the end time and add it to the dictionary.

 private void AddStatus(string status, float duration) { if (statusTime.ContainsKey(status)) { statusTime[status] += duration; } else { float endTime = Time.timeSinceLevelLoad + duration; statusTime.Add(status, endTime); } } 

RemoveStatus

Delete the status from the dictionary, restore the original values.

 private void RemoveStatus(string status) { statusTime.Remove(status); RestoreStats(status); } 

Checkstatus

If there are statuses in the dictionary, then we check if their time has expired.

If expired, delete the status from the dictionary. Since changes of the dictionary in the cycle makes it impossible to synchronize the values ​​of the dictionary, then we transfer the dictionary keys here to the usual List.

 private void CheckStatuses() { if (statusTime.Count > 0) { float currTime = Time.timeSinceLevelLoad; List<string> statuses = new List<string>(statusTime.Keys);  foreach (string stat in statuses) { if (currTime > statusTime[stat]) { RemoveStatus(stat); } } } } 

Of the benefits, obviously, this is the extensible duration of the effects. That is, the problem is solved.
But a rather significant minus is present here. Check for status is carried out in Update every frame. In my game there is a maximum of 4 players, which means this method will be executed each frame 4 times in parallel. With 4 characters, I think this is not critical, but I am sure that with more characters, this can cause performance problems. It is also worth noting that the method is protected by checking for the presence of elements in the dictionary, and with an empty dictionary the load should decrease.

Running into the future (which is still completely vague for this game), I am also confident in this decision online as well, since the check for player statuses will occur only for the current local player, and not for all instanced players.

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


All Articles