Al desarrollar un juego en Unity, me encontré con una tarea interesante: cómo hacer que un tiempo de acción extensible de efectos negativos o positivos en un personaje.
En resumen, tengo un personaje al que se le pueden aplicar ciertos efectos, como debilitamiento, ganancia, aumento de velocidad, disminución de velocidad y otros. Para notificar al jugador sobre el efecto de un efecto, el juego proporciona una línea de estado.
Las primeras versiones de esta línea contenían íconos oscuros de todos los estados y cuando ocurrió el efecto, se iluminó el deseado.

Cada estado tenía corutina, que canceló el efecto después de un período de tiempo determinado.
Hay un inconveniente bastante importante en esta decisión. Si, como resultado de ciertos eventos en el juego, se aplica el mismo efecto al personaje después de un tiempo más corto que la duración del efecto similar anterior, entonces puede haber dos versiones de los eventos.
- Muy mal: se lanza una segunda corutina en paralelo con la primera. Cuando se completa el primero, vuelve a sus valores originales, es decir, el efecto se elimina antes de que la segunda rutina haya terminado el trabajo.

- También está mal, pero en algunos casos es aceptable: cancele la primera rutina y ejecute la segunda. En este caso, la duración del efecto será igual a la duración del primer efecto hasta que se cancele la rutina + la duración de la segunda.

Ambos métodos son inaceptables para mi tarea, ya que necesito extender la duración del efecto, de modo que obtenga la suma de la duración de cada efecto, independientemente de cuántas veces se aplique el efecto.
Si el personaje pisa los picos, su pierna está dañada condicionalmente y no puede continuar moviéndose a la misma velocidad. Digamos que la velocidad disminuye en 5 segundos. Si después de 3 segundos el personaje pisa otros picos, entonces la velocidad debería reducirse en otros 5 segundos. Es decir, han pasado 3 segundos, quedan 2 + 5 segundos de los nuevos picos. La duración del efecto debería durar otros 7 segundos desde el momento del ataque en los segundos picos (10 en total).
Y con la ayuda de la rutina, no encontré una solución al problema, ya que es imposible averiguar el tiempo restante hasta la finalización de la rutina para agregarla a la nueva rutina.
La solución que encontré para este problema es usar un diccionario. Su ventaja sobre List es que el diccionario tiene una clave y un valor, lo que significa que puedo acceder a cualquier valor por clave. Además, esta solución le permite deshacerse de los íconos de estado permanentes en la línea e incluir los necesarios según sea necesario y establecerlos en posiciones en la línea en el orden en que ocurren.
Dictionary<string, float> statusTime = new Dictionary<string, float>();
El diccionario en este caso es más ventajoso que usar la cola, ya que la cola funciona de acuerdo con los principios de Primero en entrar, primero en salir, pero la duración de los efectos es diferente, lo que significa que el estado que debe eliminarse puede no ser el primero en la cola.
Para esto, agregué tres métodos.
AddstatusAgregue el estado deseado al diccionario, si dicho estado ya existe en el diccionario, luego agregue la duración. Si no hay estado, calculamos la hora de finalización y la agregamos al diccionario.
private void AddStatus(string status, float duration) { if (statusTime.ContainsKey(status)) { statusTime[status] += duration; } else { float endTime = Time.timeSinceLevelLoad + duration; statusTime.Add(status, endTime); } }
Eliminar estadoEliminamos el estado del diccionario, restauramos los valores originales.
private void RemoveStatus(string status) { statusTime.Remove(status); RestoreStats(status); }
CheckstatusSi hay estados en el diccionario, verificamos si su tiempo ha expirado.
Si caducó, elimine el estado del diccionario. Dado que cambiar el diccionario en el bucle hace que sea imposible sincronizar los valores del diccionario, arrojamos las claves del diccionario aquí en una Lista regular.
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); } } } }
De las ventajas, obviamente, esta es una duración extensible de los efectos. Es decir, el problema está resuelto.
Pero aquí hay un signo menos significativo. La comprobación de los estados se realiza en Actualizar cada fotograma. En mi juego hay un máximo de 4 jugadores, lo que significa que este método se ejecutará cada cuadro 4 veces en paralelo. Con 4 caracteres, creo que esto no es crítico, pero estoy seguro de que con más caracteres, esto puede causar problemas de rendimiento. También vale la pena señalar que el método está protegido al verificar la presencia de elementos en el diccionario, y con un diccionario vacío, la carga debe reducirse.
Mirando hacia el futuro (que todavía está completamente nublado para este juego), también confío en esta decisión en el modo en línea, ya que se verificará el estado de los jugadores solo para el jugador local actual, y no para todos los jugadores instanciados.