Ir al contenido

Anidamiento

Una bola de fuego no es una sola cosa. Es una esfera brillante, un haz con textura que la sigue por detrás, y un suave halo de luz proyectado sobre el mundo que la rodea. Tres elementos visuales, todos moviéndose juntos, todos sincronizados con el mismo tiempo de vida, todos muriendo en el mismo instante.

Podrías construirlo con tres emisores coordinados activados por un script. Pero el plugin te ofrece una forma más directa: anida los tres elementos transformados como hijos de un Model, y el motor los emite todos juntos como una partícula compuesta. Una sola llamada a Particle:Emit() produce una partícula-esfera que ya tiene su propio haz-partícula y su propia partícula-luz adjuntos.

Eso es el anidamiento. Es la diferencia entre una partícula que es una sola Part y una partícula que es un conjunto coordinado de elementos visuales vinculados a un único tiempo de vida.

El anidamiento encaja en cualquier caso donde dos o más comportamientos visuales deban estar ligados juntos como una unidad:

  • Bola de fuegoModel que contiene una Part esférica con textura, un Beam entre dos de sus Attachments para el rastro de llama, y un PointLight para el brillo en el mundo. Un solo emit activa los tres.
  • Cada partícula deja rastroPart cuyo RenderTemplate contiene un Attachment con un Trail como hijo. Cada Part-partícula arrastra una pequeña estela mientras vuela.
  • Cada partícula brillaPart cuyo RenderTemplate contiene un PointLight. Cada Part-partícula emite luz real en la escena a su alrededor.
  • Sub-explosiónPart cuyo RenderTemplate contiene otro emisor Part, con un EmitCount más alto. Cada partícula padre genera una nube secundaria de tres o cinco sub-partículas al nacer.

Cada patrón compone emisores básicos en un efecto más rico. El trabajo del plugin es descubrir los emisores anidados cuando el padre emite, activar su emisión y limpiarlos cuando el tiempo de vida del padre termina. El trabajo del usuario es marcar los elementos interiores como Transformed y ajustar sus propiedades.

Cuando un emisor padre emite, el plugin clona el RenderTemplate del padre (o, en el caso de Model, el subárbol completo). Luego recorre el subárbol clonado en profundidad y, para cada descendiente, decide qué hacer en función de lo que encuentre:

  • Tiene el atributo Transformed = true: un emisor transformado anidado (una Part, Beam, Attachment, Model, PointLight, etc. que ha sido transformada por sí misma). El plugin lo dispara mediante el mismo pipeline de emisión que usaría un clic de nivel superior, respetando sus atributos EmitCount / EmitDuration / EmissionMode. El recorrido no desciende más dentro del subárbol propio de este sub-emisor: el sub-emisor gestiona sus propios descendientes cuando se dispara, lo que evita que el mismo elemento anidado se dispare dos veces.
  • Descendiente ParticleEmitter, Trail o Beam nativo: se dispara junto con la emisión del padre con cualesquiera atributos EmitCount / EmitDelay / EmitDuration estampados en la instancia nativa (cubierto en Edición Nativa). Los hijos directos del RT, los descendientes de un Attachment y los anidamientos más profundos se disparan todos de la misma manera. Los Beams nativos usan EmitDuration como un valor único en segundos (número, no rango) para controlar el pulso Enabled, igualando el comportamiento del Trail nativo. Transform pre-desactiva (Enabled = false) cada PE / Trail / Beam nativo que encuentra dentro del RT de origen una sola vez en el momento de Transform, para que el origen no filtre un flujo extra desde la ubicación oculta del RT.
  • Descendientes visuales planos sin ninguno de los dos marcadores: viajan junto a la partícula padre (aparecen dentro de la jerarquía clonada), pero no emiten nada por su cuenta.

El recorrido es de un solo disparo por emisión del padre. Para un padre Part emitiendo a Rate = 30, el recorrido se ejecuta treinta veces por segundo; para un Model con una jerarquía profundamente anidada, el recorrido visita cada descendiente cada vez, salvo cuando salta dentro del subárbol de un sub-emisor transformado. El coste es aproximadamente O(descendientes × tasa de emisión): generalmente insignificante, pero conviene saberlo si construyes estructuras profundamente anidadas con tasas de emisión altas.

Para cada emisor anidado descubierto, el plugin llama a su flujo interno EnableEmit con tres atributos relevantes:

  • EmitCount — cuántas explosiones únicas emitir (por defecto 1 para el caso anidado).
  • EmitDuration — segundos de emisión continua tras la explosión (por defecto 0, es decir, sin emisión continua).
  • EmissionMode"Emit" (clones por emisión) o "Animate" (animación en sitio de instancia única).

Las combinaciones cubren los casos más comunes:

  • EmitCount = 1, EmitDuration = 0 — una explosión al emitir el padre. El más habitual para PointLights y Beams anidados (el visual es la propia explosión).
  • EmitCount = 5, EmitDuration = 0 — cinco explosiones al emitir el padre. Patrón de sub-explosión.
  • EmitCount = 1, EmitDuration = 0.3 — una explosión más 0,3 segundos de emisión continua. Útil para efectos de rastro.

EmitDelay en un emisor anidado queda suprimido para el activado anidado. Cuando el padre emite y el motor recorre el árbol clonado para activar sus descendientes transformados, el propio EmitDelay de cada descendiente se fuerza a 0 para ese activado anidado — la cascada la dirige el padre, no se acumulan retrasos. Las rutas en modo Animate capturan el retraso original y lo restauran tras la llamada para que el valor definido no quede a cero de forma permanente.

El propio EmitDelay del padre sigue controlando cuándo emite el padre. Si llamas a :AbsoluteEmit(parent) con EmitDelay = 2 en el padre, todo el conjunto espera 2 segundos, luego el padre dispara y cada descendiente transformado anidado dispara al mismo tiempo. No hay escalonamiento por capa. Para temporizaciones escalonadas, controla los emisores anidados desde un script con task.wait explícitos entre llamadas.

Manejo del valor cero por defecto para Parts y PointLights anidados

Sección titulada «Manejo del valor cero por defecto para Parts y PointLights anidados»

Los emisores Part, Attachment, Model y PointLight tienen EmitCount en 0 por defecto al transformarse — se asume que activarás la emisión desde un script, no como parte del disparo automático de un padre. Para el caso anidado, EnableEmit incrementa automáticamente EmitCount a 1 cuando tanto EmitCount como EmitDuration están en sus valores por defecto, de modo que el subárbol clonado realmente dispara cuando su padre emite, independientemente del tipo de padre (Part, Attachment o Model).

Beam ni siquiera necesita ese incremento: Transform establece EmitCount = 1 específicamente en Beam.

Trail no pasa por esta ruta: un Trail transformado usa una ruta de almacenamiento diferente sin hijo Configuration, y el entorno de ejecución lo activa mediante un helper específico de Trail que lee EmitDuration directamente, no EmitCount.

Las partículas emitidas por emisores anidados siguen automáticamente a la partícula padre mientras se mueve. El mecanismo es estructural: los emisores anidados son descendientes del visual clonado del padre, así que cuando el motor actualiza el CFrame del padre cada fotograma (impulsado por Speed, Acceleration, Drag, etc. del padre), los descendientes se mueven con él de forma automática, gracias a la composición padre-hijo de CFrame de Roblox.

Tres formas de herencia:

  • Part dentro de Part / Model: cada fotograma, el CFrame del padre se establece mediante :PivotTo() (Model) o asignándolo directamente al CFrame del BasePart padre (Part). Los descendientes siguen porque son hijos. No se necesita seguimiento explícito.
  • Attachment dentro de Part: igual — el Attachment clonado es hijo del Part clonado, por lo que hereda el marco del Part. Las partículas emitidas por el Attachment anidado se generan en la posición mundial actual del Attachment (que es la posición del Part compuesta con el desplazamiento local del Attachment).
  • Beam dentro de Part: un Beam anidado mantiene intactas sus referencias Attachment0/Attachment1 en el momento de la clonación. Si los attachments del Beam son hijos del Part padre (lo más habitual), el Beam se mueve con el Part. Si los attachments son externos (a veces configurados para casos de efectos cruzados), el Beam anidado permanece fijo donde estén esos attachments, independientemente del movimiento del padre.

El modelo de CFrame de Roblox hace el trabajo pesado. El plugin no reposiciona manualmente los clones anidados en cada fotograma — solo posiciona al padre, y Roblox propaga el cambio.

La escala es la única propiedad heredada que el plugin gestiona de forma explícita, porque la relación padre-hijo de Roblox no la propaga. Model:ScaleTo() multiplica el Size de cada descendiente una vez, pero eso es una sola llamada — si el gráfico Scale del padre se anima a lo largo del tiempo de vida, los tamaños de los emisores anidados necesitan seguirlo.

Por eso el plugin registra el padre de cada emisor anidado en el momento de la emisión y, cada fotograma, muestrea el gráfico Scale del padre y lo aplica como multiplicador sobre las propiedades de tamaño de la partícula anidada. No verás que esto ocurra — desde tu punto de vista, defines un gráfico Scale en el Model padre y los tamaños de las partículas Beam/Part anidadas lo siguen.

Qué se multiplica:

  • Partículas Part / Attachment anidadasSizeX, SizeY, SizeZ cada fotograma.
  • Partículas Beam anidadasWidth0, Width1, CurveSize0, CurveSize1 y Segments cada fotograma. TextureLength se divide por el mismo factor — el valor representa «studs por mosaico de textura», así que dividir mantiene la densidad visible de mosaicos proporcional a la longitud aparente ahora mayor del haz.

El plugin no pre-escala la configuración en el momento de la emisión; aplica la escala en tiempo de ejecución. El motivo: si vuelves a emitir con la misma fuente tras una emisión anterior, el pre-escalado se acumularía (la configuración quedaría escalada y luego escalada de nuevo), produciendo tamaños incorrectos. El escalado solo en tiempo de ejecución mantiene las configuraciones limpias y las re-emisiones consistentes.

El mapa de herencia de escala tiene un solo nivel de profundidad. Si un emisor anidado dentro de un Model tiene su propio emisor anidado (un emisor nieto), el nieto NO hereda la escala del Model a través del intermedio. El nieto emite con su propio tamaño configurado, sin que ningún padre lo afecte.

Para la mayoría de los efectos prácticos, la herencia de un nivel es suficiente — la mayoría del anidamiento tiene dos capas (emisor padre + hijos inmediatos), no tres. Si necesitas una herencia más profunda, considera si la estructura puede aplanarse (subir los nietos para que sean hijos directos del Model) o si un enfoque controlado por script es más limpio.

Cuando el Lifetime de la partícula padre termina, el motor destruye el visual clonado del padre mediante Instance:Destroy(). La destrucción natural del padre en Roblox se ocupa de todo lo que hay debajo: cada descendiente del visual destruido también se destruye automáticamente.

Para los emisores anidados, esto significa:

  • Los clones de partícula Part anidados dentro de la jerarquía del padre se destruyen cuando el padre muere.
  • Los clones de Beam y PointLight anidados, igualmente.
  • Las partículas que los emisores anidados emitieron en un EmitParent separado (no dentro de la jerarquía del padre) viven de forma independiente — sus tiempos de vida no están vinculados al del padre. Esto rara vez es lo que los usuarios desean, así que deja EmitParent vacío en los emisores anidados.

El plugin marca cada clon emitido con un atributo _PartIcleEmit. Puedes usarlo para identificar clones emitidos por el plugin en el Explorador de Studio.

Si el padre está en modo Animate con AnimateLoop = true, el motor no destruye el visual del padre al final del ciclo — lo reinicia y lo reproduce de nuevo. El propio estado del padre se vuelve a leer desde su instancia fuente en el momento del reinicio, por lo que las ediciones en vivo al padre surten efecto en el siguiente bucle.

Los emisores anidados, en cambio, no se capturan y restauran entre bucles. El valor que tenga un emisor anidado al final del ciclo 1 es el valor desde el que parte el ciclo 2. Si un script modificó el gráfico Speed de un emisor anidado durante el ciclo 1, el ciclo 2 arrastra esa modificación. Evita mutar propiedades de emisores anidados desde scripts durante un ciclo Animate activo si necesitas que los ciclos sean idénticos.

La conclusión: en configuraciones de padre en modo Animate con emisores anidados, edita las propiedades de la fuente mediante el panel antes de emitir, no en tiempo de ejecución a mitad del ciclo.

No hay un límite incorporado en la profundidad de anidamiento. Un emisor anidado puede contener su propio emisor anidado, que puede contener otro, y así sucesivamente.

En la práctica, dos capas es el caso más habitual. Tres capas es raro pero funciona. Cuatro o más capas normalmente indica que la estructura podría aplanarse o reestructurarse para ser más limpia.

El coste del anidamiento profundo es principalmente el recorrido de descubrimiento (cada emisión del padre recorre todos los descendientes de su subárbol clonado). Con tasas de emisión altas, el coste de ese recorrido se acumula. Una estructura anidada de 5 capas con un padre de 30 partículas por segundo realiza 30 recorridos de un árbol de 5 capas de profundidad por segundo. No es patológico, pero tampoco es gratuito.

Configuración:

  1. Crea un Model en el workspace.
  2. Establece su PrimaryPart como una Part esférica (el orbe visible, con una textura de fuego).
  3. Añade un Beam como hijo del orbe, con Attachment0 en el centro del orbe y Attachment1 en una posición que quedará por detrás durante el movimiento (o usa un script para actualizar el attachment del rastro).
  4. Añade un PointLight como hijo del orbe (o directamente bajo el Model).
  5. Transforma el Model. La esfera, el Beam y el PointLight se detectan automáticamente como emisores anidados.
  6. Establece Speed y Acceleration en el Model para dar movimiento a la bola de fuego.

Resultado: cada Particle:Emit() produce una partícula-bola de fuego que es una esfera con un haz de rastro y una luz mundial brillante, todo moviéndose juntos como una sola unidad.

Configuración:

  1. Crea una Part en el workspace.
  2. Dentro de la Part, añade un Attachment y transforma ese Attachment.
  3. Dentro del Attachment, añade una instancia Trail con las propiedades apropiadas (gráficos de Color, Transparency, WidthScale). El Trail usa dos Attachments como sus extremos — pueden estar dentro de la Part o ser hermanos fijos.
  4. Transforma la Part padre.
  5. Establece Speed y EmissionDirection en la Part padre para darle movimiento.

Resultado: cada Part-partícula emitida lleva un pequeño Trail que dibuja una estela mientras la Part vuela. Útil para rastros de misiles, colas de cometas, estelas brillantes detrás de chispas individuales.

Configuración:

  1. Crea una Part, transfórmala.
  2. Dentro de la Part, añade un PointLight y transfórmalo.
  3. Ajusta los gráficos Range, Brightness y Color del PointLight a tu gusto.

Resultado: cada Part-partícula emitida tiene su propio PointLight emitiendo luz real en la escena. Combinado con valores altos de Brightness/Range, obtienes luciérnagas, chispas mágicas que iluminan su entorno, efectos brillantes en la oscuridad.

Configuración:

  1. Crea una Part padre, transfórmala.
  2. Dentro del padre, añade otra Part (una «sub-partícula» más pequeña), transfórmala.
  3. Establece en la Part interior EmitCount = 5, Lifetime = 0.3, Speed más alto que el del padre.

Resultado: cada partícula-padre, al emitirse, produce inmediatamente cinco sub-partículas hacia el exterior. El padre vuela por su propia trayectoria; las sub-partículas vuelan hacia fuera desde donde estaba el padre en ese momento, cada una con su propio tiempo de vida corto.

Algunas cosas que sorprenden a los usuarios por primera vez.

El elemento anidado debe estar Transformed. Un elemento hijo no transformado es solo una decoración visual estática del RenderTemplate del padre — viaja junto a él pero no emite. Marca Transformed = true (o equivalentemente, ejecuta Transform sobre él en Studio) antes de esperar que emita.

EmitCount = 0 por defecto en Part/Attachment/Model/PointLight. Estos tipos tienen cero por defecto, pensados para que Particle:Emit() los active desde un script. El incremento automático en :EnableEmit cubre cualquier caso anidado — un elemento transformado anidado con EmitCount = 0 y EmitDuration = 0 se incrementa a un disparo único cuando su padre emite, independientemente de si el padre es Part, Attachment, Model u otro tipo. Si quieres más de un disparo por emisión del padre, establece EmitCount explícitamente en el elemento anidado.

EmitDelay anidado se sobreescribe a 0. No puedes escalonar temporalmente un emisor anidado respecto al emit de su padre. Si necesitas temporización escalonada, reestructura: un padre con múltiples emisores no anidados que cada uno emita desde scripts con distintos retrasos, en lugar de una configuración anidada.

Los attachments del Beam importan. Si Attachment0/Attachment1 de un Beam anidado referencian attachments fuera de la jerarquía del padre, el Beam queda fijado a esos attachments externos y no sigue el movimiento del padre. Para «cola de haz que sigue a la partícula», asegúrate de que ambos attachments estén dentro de la jerarquía del padre.

La herencia de escala es de un solo nivel. Anidar más de dos capas pierde la propagación de escala más allá del segundo nivel. Si lo necesitas más profundo, aplana la estructura o usa gestión de escala por script.

El modo Animate reinicia las configuraciones anidadas. Las ediciones realizadas en la jerarquía clonada en vivo durante el tiempo de ejecución se revierten en cada iteración del bucle. Edita el elemento fuente, no el clon en tiempo de ejecución.

Eventos es el siguiente capítulo — callbacks por emisor que permiten que un emisor active a otro en momentos del ciclo de vida (emit, hit, death, destruction).