Soy nuevo en Unity. Estaba aprendiendo corutinas y escribí esto.
private void Fire()
{
if(Input.GetButtonDown("Fire1"))
{
StartCoroutine(FireContinuously());
}
if(Input.GetButtonUp("Fire1"))
{
StopAllCoroutines();
}
}
IEnumerator FireContinuously()
{
while(true)
{
GameObject laser = Instantiate(LaserPrefab, transform.position, Quaternion.identity) as GameObject;
laser.GetComponent<Rigidbody2D>().velocity = new Vector2(0, 10f);
yield return new WaitForSeconds(firetime);
}
}
Cuando se presiona el botón, se llama a la rutina y entra en el ciclo 'while'. Cuando dejo el botón, se detiene la rutina. ¿No debería quedar atascado en el ciclo 'while' ya que es un ciclo infinito? ¿Por qué?
unity
c#
coroutines
babybrain
fuente
fuente
"Fire1"
, ¿es algo que puede configurar en el motor para permitir reasignaciones de teclas en lugar de escribirKeycode.Foo
?yield
efectivamente es la abreviatura de "Ceda el control a la persona que llama hasta que se solicite el siguiente elemento en el Enumerable".StopAllCoroutines()
en este caso. Está bien cuando solo usas una rutina, pero si alguna vez planeaste tener más de una, esto tendría efectos no deseados. En su lugar, debe usarStopCoroutine()
y simplemente detener el que sea relevante en lugar de todos. (StopAllCoroutines()
sería útil, por ejemplo, al finalizar el nivel o cargar una nueva área, etc., pero no para cosas específicas como "Ya no estoy disparando".)Respuestas:
La razón es la palabra clave
yield
que tiene un significado específico en C #.Al encontrar las palabras,
yield return
una función en C # regresa, como cabría esperar.Entonces no hay un bucle infinito. Hay una función / iterador que se puede llamar un número infinito de veces.
La función Unity
StartCoroutine()
hace que el marco Unity llame a la función / iterador una vez por cuadro.La función Unity
StopAllCoroutines
hace que el marco Unity deje de llamar a la función / iterador.Y al regresar
WaitForSeconds(time)
del iterador, el marco de Unity deja de llamar a la función / iteradortime
.Un comentario confuso y un voto positivo igualmente confuso sobre ese comentario me animaron a profundizar en lo que
yield
hace y no hace la palabra clave .Si escribes esto:
En su lugar, también puede escribir esto:
De ello se deduce que la palabra clave
yield
no está relacionada con subprocesos múltiples y absolutamente no llamaSystem.Threading.Thread.Yield()
.fuente
On encountering the words yield return a function in C# returns
". No, no lo hace. El texto que usted cita lo explica, al igual que Wikipedia - "In computer science, yield is an action that occurs in a computer program during multithreading, of forcing a processor to relinquish control of the current running thread, and sending it to the end of the running queue, of the same scheduling priority.
". Básicamente, "por favor detenme donde estoy y deja que alguien más corra por un tiempo".Cuando se levanta el botón de disparo, se ingresa el segundo if, y se ejecuta StopAllCoroutines. Esto significa que la Corutina en la que se está ejecutando el ciclo while finaliza, por lo que no hay más ciclos infinitos. La corutina es como un contenedor para que el código se ejecute.
Puedo recomendar el Manual de Unity y la API de Unity Scripting para comprender mejor qué son las corutinas y cuán poderosas pueden ser.
Este blog y la búsqueda en la publicación de YouTube también me ayudaron a usar mejor las corutinas.
fuente
Las corutinas son una bestia extraña. El rendimiento de rendimiento hace que el método suspenda la ejecución hasta que luego se escalone. Detrás de escena, podría verse más o menos así:
E interno a Unity / C # (dado que el rendimiento devuelto es una característica nativa de C #), cuando llama a StartCoroutine, crea un
FireContinuouslyData
objeto y lo pasa al método. Según el valor de retorno, determina cuándo volver a llamarlo más tarde, simplemente almacenando el objeto FireContinuouslyData para pasarlo la próxima vez.Si alguna vez hiciste un corte de rendimiento, podría establecerse internamente
data.shouldBreak = true
y luego Unity simplemente tiraría los datos y no volvería a programarlos.Y si hubiera datos que debían guardarse entre ejecuciones, también se almacenarían en los datos para más adelante.
Un ejemplo de cómo Unity / C # podría implementar la funcionalidad de rutina:
fuente
Otra respuesta menciona que está deteniendo las co-rutinas cuando
"Fire1"
está activo; esto es completamente correcto, en la medida en que la rutina no continúa creando instancias de GameObjects después de la primera pulsación de"Fire1"
.Sin embargo, en su caso, este código no se 'quedará atascado' en un bucle infinito, que es lo que parece que está buscando una respuesta, es decir, el
while(true) {}
bucle, incluso si no lo detuvo externamente.No se atascará, pero su rutina no terminará (sin llamar
StopCoroutine()
oStopAllCoroutines()
) tampoco. Esto se debe a que las corutinas de Unity ceden el control a su interlocutor.yield
ing es diferente areturning
:return
declaración dejará de ejecutar una función, incluso si hay más código siguiéndolayield
instrucción pausará la función, comenzando en la siguiente línea después deyield
cuando se reanude.Por lo general, las rutinas se reanudarán en cada cuadro, pero también devolverá un
WaitForSeconds
objeto.La línea
yield return new WaitForSeconds(fireTime)
se traduce aproximadamente como "ahora suspenderme, y no volver hasta quefireTime
hayan pasado los segundos".A menos que se detenga, esta es una rutina que, una vez iniciada, realizará todo el ciclo una vez cada
fireTime
segundo.fuente
Una explicación simple: debajo del capó, Unity está iterando sobre una colección (de YieldInstruction s o nulls o lo que sea que usted
yield return
use) usando laIEnumerator
función que devuelve su función.Como usa la
yield
palabra clave, su método es un iterador . No es lo de Unity, es una característica del lenguaje C #. ¿Como funciona?Es vago y no genera toda la colección a la vez (y la colección puede ser infinita e imposible de generar a la vez). Los elementos de la colección se generan según sea necesario. Su función devuelve un iterador para que Unity trabaje. Llama a su
MoveNext
método para generar un nuevo elemento yCurrent
propiedad para acceder a él.Por lo tanto, su bucle no es interminable, ejecuta un código, devuelve un elemento y devuelve el control a Unity para que no se atasque y pueda hacer otro trabajo, como manejar su entrada para detener la rutina.
fuente
Piensa en cómo
foreach
funciona un :El control sobre la iteración está en la persona que llama; si detiene la iteración (aquí con
break
), eso es todo.La
yield
palabra clave es una forma simple de hacer un enumerable en C #. El nombre sugiere esto:yield return
devuelve el control a la persona que llama (en este caso, nuestraforeach
); Es la persona que llama quien decide cuándo continuar con el siguiente elemento. Entonces puedes hacer un método como este:Parece ingenuo que se ejecutará para siempre; pero en realidad, depende completamente de la persona que llama. Puedes hacer algo como esto:
Esto puede ser un poco confuso si no estás acostumbrado a este concepto, pero espero que también sea obvio que esta es una propiedad muy útil. Era la forma más sencilla en la que podía ceder el control a su interlocutor, y cuando la persona que llama decide hacer un seguimiento, solo puede hacer el siguiente paso (si Unity se creó hoy, probablemente lo usaría en
await
lugar deyield
, peroawait
no existía) entonces).Todo lo que necesita para implementar sus propias rutinas (no hace falta decir que las más simples y más estúpidas) es esto:
Para agregar una
WaitForSeconds
implementación muy simple , solo necesita algo como esto:Y el código correspondiente en nuestro bucle principal:
Ta-da: eso es todo lo que necesita un sistema de rutina simple. Y al ceder el control a la persona que llama, la persona que llama puede decidir cualquier cantidad de cosas; podrían tener una tabla de eventos ordenada en lugar de iterar a través de todas las rutinas en cada cuadro; pueden tener prioridades o dependencias. Permite una implementación muy simple de la multitarea cooperativa. Y mira lo simple que es esto, gracias a
yield
:)fuente