FOS UI Culling
Versión 1.0.1 · requiere FOS Essentials Core 1.0.0 o posterior
Desactiva las UI del mundo cuando el jugador local queda fuera de alcance, desde un único gestor central. Requiere FOS Essentials Core 1.0.0.
Puesta en marcha
Sección titulada «Puesta en marcha»- Crea un GameObject vacío y añade FOS Essentials > UI Culling > UI Culling Manager.
- Pulsa Scan the scene. Cada canvas World Space recibe un
FOSUICullingTarget, se conectan suCanvasy suGraphicRaycaster, y se rellena la lista del gestor. - Ajusta Default distance. Sobrescríbela por UI en el componente objetivo cuando haga falta.
Vuelve a lanzar el escaneo tras añadir nuevas UI. Los objetivos existentes nunca se sobrescriben: cualquier ajuste manual sobrevive.
Componentes
Sección titulada «Componentes»| Componente | Función |
|---|---|
FOSUICullingManager |
Uno por escena. Posee el bucle y lo gobierna todo. |
FOSUICullingTarget |
Uno por UI. Solo guarda ajustes — ningún trabajo por fotograma. |
Opciones
Sección titulada «Opciones»| Opción | Efecto |
|---|---|
| Default distance | Metros por debajo de los cuales se muestra una UI, salvo que el objetivo lo sobrescriba. |
| Show distance (objetivo) | Sobrescritura por UI. -1 significa «heredar el valor del gestor». |
| Distance reference (objetivo) | Punto desde el que se mide la distancia. Vacío significa el propio Canvas. |
| Hysteresis | Metros adicionales antes de volver a ocultar. Evita que una UI parpadee cuando el jugador está justo en el límite. |
| Checks per frame | Cuántas UI se comprueban en cada fotograma. 4 por defecto. |
| Line of sight | Pasada de oclusión opcional. Desactivada por defecto. |
| Obstacle layers | Capas que cuentan como obstáculo para la pasada de oclusión. |
Cómo decide una UI su distancia
Sección titulada «Cómo decide una UI su distancia»Dos niveles, resueltos una sola vez al arrancar el gestor:
- La Default distance del gestor se aplica a todas las UI.
- La Show distance de un objetivo la sobrescribe, salvo que valga
-1, que significa heredar el valor del gestor.
float distance = target.showDistance;if (distance < 0f) distance = defaultShowDistance;Así, una escena con Default distance = 15 y un objetivo puesto a 8 muestra esa UI a 8
metros y todo lo demás a 15.
El inspector del objetivo deletrea el resultado — «Effective distance: 15 m (inherited
from the manager)» — para que nunca tengas que recordar qué significa -1.
Como el valor se lee una sola vez en Start, cambiar una distancia en ejecución exige
llamar a RefreshTargets() en el gestor.
Desde dónde se mide la distancia
Sección titulada «Desde dónde se mide la distancia»Desde el transform del Canvas, que en un panel es su pivote, normalmente su centro. Un panel ancho aparece por tanto en función de su centro y no de su borde más cercano.
Cuando eso importa, arrastra cualquier Transform a Distance reference en el objetivo — un hijo vacío colocado en el borde, por ejemplo — y la medición se hará desde ahí.
Notas de diseño
Sección titulada «Notas de diseño»Por qué un gestor central en lugar de un script por UI
Sección titulada «Por qué un gestor central en lugar de un script por UI»Udon es una máquina virtual interpretada, unas cien veces más lenta que C#. El coste dominante es el número de comportamientos en marcha, no la aritmética que hacen. Un comportamiento comparando treinta distancias al cuadrado sale mucho más barato que treinta comportamientos haciendo cualquier cosa.
Por eso FOSUICullingTarget solo guarda configuración. El gestor la lee una vez en
Start, la cachea en arrays planos, y a partir de ahí solo toca componentes de Unity
directamente. Treinta UI cuestan exactamente un comportamiento activo.
Por qué distancia y no un raycast
Sección titulada «Por qué distancia y no un raycast»Un rayo responde a «¿hay algo en medio?», no a «¿estoy cerca?». No puede sustituir a la prueba de distancia: harían falta las dos, y la distancia primero. El raycast sí tiene un uso real aquí, pero como segunda etapa: mira Line of sight más abajo.
Por qué distancia y no colliders de trigger
Sección titulada «Por qué distancia y no colliders de trigger»Los volúmenes de trigger son de verdad baratos: van por eventos y la fase amplia se resuelve en código nativo. Pierden en tres puntos prácticos:
- hay que dimensionar un collider por UI, lo que arruina la configuración automática;
OnPlayerTriggerEntersolo se dispara en el GameObject que lleva el collider, lo que obliga a un comportamiento por UI para recibirlo;- la cápsula del jugador tiene que solaparse de verdad con el volumen. Una UI alta en una pared con un trigger pequeño se pierde, y un error en la matriz de capas falla en silencio.
Qué se desactiva realmente
Sección titulada «Qué se desactiva realmente»El Canvas, el raycaster y los colliders — no SetActive(false).
- Desactivar el
Canvasdetiene el renderizado al instante y mantiene viva la jerarquía. - Desactivar los colliders es lo que detiene el puntero de VRChat. Este no es opcional: mira abajo.
- Desactivar el
GraphicRaycastersaca la UI de la resolución de eventos de interfaz de Unity. SetActive(false)forzaría una reconstrucción completa del layout al volver y reejecutaría todos losOnEnable, Udon incluido, lo que puede reiniciar el estado de scripts sin relación.
Por qué los colliders importan más que el raycaster
Sección titulada «Por qué los colliders importan más que el raycaster»El puntero de VRChat no pasa por el GraphicRaycaster. Su propio raycaster hace un
raycast físico y luego busca un VRC_UiShape en el collider que haya tocado:
int hitCount = Physics.RaycastNonAlloc(...); // toca COLLIDERSVRC_UiShape shape = hitObject.GetComponent<VRC_UiShape>();Desactivar solo el Canvas oculta la UI pero deja su collider en pie. El puntero sigue
enganchándose a él y sus botones siguen siendo pulsables: una UI invisible que aún
reacciona. Eso es un fallo funcional, no cosmético.
El escaneo rellena automáticamente la lista Colliders a partir del GameObject del canvas. Si una UI tiene colliders en otro sitio, añádelos a mano; el inspector del objetivo avisa cuando encuentra colliders sin listar.
Colliderno deriva deBehaviour: deriva directamente deComponent, con su propia propiedadenabled. Por eso los colliders tienen su propia lista en vez de figurar en Extra components.
La línea de visión
Sección titulada «La línea de visión»Al activarla, una UI que ya está dentro de alcance se oculta si un obstáculo tapa la
vista. Un Physics.Linecast por cada UI comprobada, lanzado desde la cabeza del jugador:
desde los pies rozaría el suelo y engancharía cualquier bulto.
Solo se ejecuta para las UI que ya han pasado la prueba de distancia, así que una UI a doscientos metros nunca cuesta un rayo. Aun así, déjala desactivada salvo que el mundo lo necesite de verdad.
El escalonado
Sección titulada «El escalonado»Los objetivos se comprueban por turnos, Checks per frame cada vez. Con 4 comprobaciones y 40 UI, la latencia en el peor caso es de 10 fotogramas: menos de una quinta parte de segundo, invisible para una UI que aparece a lo lejos.
El primer fotograma es la excepción: se evalúa todo de golpe, o las UI lejanas seguirían encendidas hasta que el cursor completara una vuelta entera.
La visibilidad solo se escribe cuando cambia, así que una escena estable no realiza ninguna escritura de componente.
Notas y límites
Sección titulada «Notas y límites»- Un gestor por escena. Varios gestores se pelearían por los mismos canvas; el inspector lo señala como error.
- Los canvas anidados se omiten. Desactivar el canvas raíz ya impide que sus hijos se dibujen, así que gestionar un objetivo por nivel solo abarrotaría la lista.
- Los canvas Screen Space se omiten por defecto: no se le muestran a nadie en un mundo de VRChat.
- Las UI creadas en ejecución no se recogen automáticamente. Rellena
targetsy llama aRefreshTargets(). ShowAll()lo vuelve a encender todo para depurar, sin quitar el gestor.
