Ir al contenido

FOS Mirror

Versión 1.0.1 · requiere FOS Essentials Core 1.0.0 o posterior

Enciende y apaga los espejos — por distancia, por botón, por interacción — desde un único gestor central, con ajustes predefinidos de calidad y una estimación indicativa del coste. Requiere FOS Essentials Core 1.0.0.

Los espejos son lo primero que hunde la tasa de fotogramas de un mundo de VRChat. Este pack no los abarata: nada puede hacerlo. Los vuelve controlados: apagados por defecto, encendidos solo donde y cuando alguien lo ha pedido de verdad.


  1. Crea un GameObject vacío y añade FOS Essentials > Mirror > Mirror Manager.
  2. Pulsa Scan the scene. Cada espejo de VRChat recibe un FOSMirrorTarget, se conectan su componente de espejo y su gestor, y se rellena la lista del gestor.
  3. Elige el Mode: exclusivo, o cada espejo independiente.
  4. Añade un control donde un jugador deba poder conmutar un espejo: Mirror Control (UI Button) o Mirror Control (Interact).

Vuelve a lanzar el escaneo tras añadir espejos. Los objetivos existentes nunca se sobrescriben: cualquier ajuste manual sobrevive.


Componente Función
FOSMirrorManager Uno por escena. Posee el bucle, la prueba de distancia y el modo exclusivo.
FOSMirrorTarget Uno por espejo. Guarda ajustes y el estado encendido/apagado — ningún trabajo por fotograma.
FOSMirrorButton Conmuta un espejo desde un Button de interfaz. Conecta el OnClick por ti.
FOSMirrorInteract Conmuta un espejo cuando un jugador usa el objeto que lo lleva.

El pack no declara deliberadamente ningún inspector propio para VRC_MirrorReflection. Unity solo acepta uno por tipo y VRWorldToolkit ya trae el suyo. Aquí todo pasa por el componente FOS, de modo que ambas herramientas siguen funcionando en paralelo.


El componente VRC_MirrorReflection, mediante su .enabled — nunca el GameObject.

SetActive(false) reejecutaría todos los OnEnable de la jerarquía, Udon incluido, lo que puede reiniciar el estado de scripts sin relación. Además ocultaría el propio objeto espejo, cuando un espejo suele ser un marco decorado que debe seguir visible al apagarse.

Los Extra components y los Extra colliders se conmutan con él.

Collider no deriva de Behaviour: deriva directamente de Component, con su propia propiedad enabled. Por eso los colliders tienen su propia lista.

Nunca metas en esa lista el collider que sirve para volver a encender un espejo: se desactivaría junto con él y el espejo ya no podría encenderse nunca más. El inspector del objetivo señala ese caso como error.


Desactivar el componente de espejo detiene el reflejo, pero la superficie se sigue dibujando, y un shader de espejo sin nada que reflejar pinta negro puro. Un espejo apagado es por tanto un rectángulo negro, salvo que digas otra cosa.

Dos campos del objetivo deciden el aspecto:

Mirror surface Off material Resultado
vacío — El rectángulo negro. No se toca nada.
puesto vacío La superficie se oculta. Su draw call desaparece también.
puesto puesto La superficie cambia a ese material: cristal oscuro, panel esmerilado, placa pintada.

El escaneo rellena Mirror surface con el renderer que está en el espejo, así que ocultar es el comportamiento por defecto. Vuelve a escanear una escena antigua para aprovecharlo, o usa el botón Use the mirror’s renderer del inspector del objetivo.

El pack incluye un material de apagado listo, Materials/FOSMirrorOff.mat: una placa oscura y brillante. Usa el shader Standard de Unity, elegido para que el material no dependa de nada ajeno a Unity. En Quest, cambia su shader por uno móvil como VRChat/Mobile/Standard Lite, o simplemente deja Off material vacío: ocultar la superficie no cuesta nada y no necesita ningún material.

El cambio escribe renderer.material, nunca sharedMaterial: escribir sharedMaterial en ejecución modifica el asset de material en disco, lo que en ClientSim dejaría el material del espejo sobrescrito para siempre en tu proyecto.

Renderer deriva de Component, no de Behaviour, y lleva su propia propiedad enabled: la misma razón por la que los colliders tienen su propia lista.

El material «encendido» se captura del renderer al arrancar el mundo. No dejes el material de apagado asignado al renderer en la escena: se capturaría como aspecto del espejo y el reflejo no volvería nunca. El inspector señala ese caso como error.

Esto vale también para el culling por distancia, que suele ser lo que quieres: un espejo fuera de alcance pierde a la vez su reflejo y su superficie.


Un espejo solo se dibuja si ambas son ciertas:

Mitad Propietario Lo fija
Estado manual — ¿alguien lo quiere encendido? El objetivo Botón, interacción, startsOn
Alcance — ¿está el jugador bastante cerca? El gestor Prueba de distancia, presupuesto por fotograma

Un espejo que el jugador ha apagado sigue apagado por mucho que se acerque. El alcance nunca enciende un espejo por sí solo: solo deja volver al que ya está encendido.

La misma lógica que FOS UI Culling: distancias al cuadrado, dos umbrales, comprobaciones por turnos y una escritura solo cuando el estado cambia de verdad.

Opción Efecto
Default distance Metros por debajo de los cuales un espejo puede dibujarse, salvo sobrescritura del objetivo.
Show distance (objetivo) Sobrescritura por espejo. -1 significa «heredar el valor del gestor».
Distance reference (objetivo) Punto desde el que se mide. Vacío significa el propio espejo.
Hysteresis Metros adicionales antes de volver a apagar.
Checks per frame Cuántos espejos se comprueban por fotograma. 2 por defecto.

El inspector del objetivo deletrea el resultado — «Effective distance: 12 m (inherited from the manager)» — para que nunca tengas que recordar qué significa -1.

Las distancias se leen una sola vez en Start. Cambiar una en ejecución exige llamar a RefreshTargets() en el gestor.

Un espejo que el jugador acaba de encender se mide de inmediato en lugar de esperar su turno: plantarse ante un espejo que tarda cuatro fotogramas en aparecer se siente roto.


Modo Comportamiento
Free Cada espejo es independiente. Nada impide que un jugador los encienda todos.
Exclusive Encender uno apaga todos los demás.

El modo exclusivo es el ajuste más eficaz de este pack, porque el segundo factor de coste de un mundo es cuántos espejos se dibujan a la vez, y ese número lo decide, si no, el primer curioso que pase.

También se aplica al arrancar: si varios espejos están configurados para empezar encendidos, solo lo hace el primero, y el inspector del gestor lo indica.


Alcance Red Quién paga
Local Ninguna Solo el jugador que lo conmutó
Synchronized [UdonSynced], sincronización manual Todos en la instancia

Local es el valor por defecto, y es el adecuado para un espejo público: quien quiera comprobar su avatar tiene uno, y nadie más lo paga.

Synchronized es para un espejo que forma parte del mundo: un escenario de baile, un vestuario compartido. La propiedad se toma antes de escribir la variable, y el estado se reconstruye para quienes llegan después desde OnDeserialization.

Un SendCustomNetworkEvent no se repite para quienes llegan tarde. Por eso el estado vive en una variable sincronizada y no en un evento.

Como el estado se guarda en el objetivo, ambos alcances se mezclan sin problema en una misma escena. En modo exclusivo, ten presente lo que implica mezclarlos: apagar un espejo local solo lo apaga para ti.

La sincronización manual está limitada a aproximadamente una serialización por segundo y por comportamiento. Encender un espejo sincronizado en modo exclusivo escribe también en los demás espejos sincronizados, así que evita volver sincronizados una docena de espejos cuando solo uno lo necesita.


Se exponen en el objetivo dos ajustes del espejo de VRChat: las capas reflejadas y la desactivación de luces por píxel.

Ajuste Capas reflejadas
Untouched (por defecto) No se aplica nada; el espejo conserva sus propios ajustes.
Players only Player + MirrorReflection — los avatares sobre el skybox.
Players and world Default + Player + Environment + MirrorReflection.
World only Default + Environment. Ningún avatar se dibuja dos veces.
Nothing Una máscara vacía. El espejo dibuja un reflejo en blanco.
Custom Lo que fijes a mano en Reflected layers.

El ajuste se resuelve en el campo Reflected layers en cuanto lo eliges, así que la máscara que ves es la que se aplicará. El gestor la escribe en el espejo en Start; el inspector del objetivo también ofrece Apply to the mirror now para escribirla desde el editor, algo útil cuando quieres que las comprobaciones de VRWorldToolkit la vean.

Untouched existe porque una herramienta nunca debe sobrescribir en silencio ajustes hechos a mano. No se escribe nada hasta que eliges un ajuste.

El inspector avisa de los errores clásicos: la máscara por defecto sin tocar, UiMenu reflejado, PlayerLocal reflejado, y los jugadores reflejados sin la capa MirrorReflection, lo que hace que se vean entre ellos pero no a sí mismos.


Los inspectores del objetivo y del gestor muestran una nota indicativa sobre 100. Es una estimación ponderada, no una medición, y se construye en este orden:

  1. Las capas reflejadas, factor dominante. Un espejo dibuja la escena por segunda vez — dos veces en RV. Lo que cuesta es por tanto lo que vuelve a dibujar. Reflejar avatares duplica el elemento más pesado de una instancia poblada, y ese elemento crece con el número de jugadores, no con el mundo. Un espejo de «solo mundo» cuesta una fracción del mismo espejo reflejando jugadores, a igual tamaño y posición.

  2. Cuántos espejos pueden dibujarse a la vez. Dos espejos encendidos son dos pasadas de render extra; entre ellos no se comparte nada. El modo exclusivo elimina este factor de raíz, así que la nota de la escena toma el peor espejo en lugar de la suma.

  3. La cobertura de pantalla, la última y con poco peso. VRChat dibuja un espejo en una textura dimensionada por la resolución del visor, no por el espejo: un espejo grande no cuesta mecánicamente más que uno pequeño. El tamaño cuenta de forma indirecta: un espejo grande se ve desde más lejos, se llena de avatares más a menudo y se descarta por distancia con menos eficacia.

Usa la nota para comparar dos ajustes de un mismo mundo. No es una cifra absoluta y no sustituye a un análisis de rendimiento.


Seleccionar un espejo dibuja su plano, una flecha a lo largo de la cara reflectante y una cruz roja sobre el dorso. Un espejo es un objeto plano: nada en la vista de escena distingue uno bien colocado de otro girado 180°, y normalmente te enteras dentro del juego, plantado ante un rectángulo negro.

La normal se lee del mesh, no se adivina del transform, así que sigue siendo correcta sea cual sea el mesh que use el espejo.

El gizmo vive en un script de editor con [DrawGizmo]. Deliberadamente no es un OnDrawGizmos dentro del comportamiento de Udon: Udon nunca llama a ese método, y UdonSharp tendría que compilarlo igualmente, junto con Gizmos y Handles, que no están expuestos.


Todos los puntos de entrada públicos son sin parámetros, así que cualquier script puede alcanzarlos con SendCustomEvent:

Llamada En Efecto
FOSRequestToggle() FOSMirrorTarget Invierte el espejo
FOSRequestOn() / FOSRequestOff() FOSMirrorTarget Fuerza un estado
TriggerMirror() FOSMirrorButton / FOSMirrorInteract Ejecuta la acción del control
FOSTurnAllOff() FOSMirrorManager Botón de pánico: apaga todos los espejos
RefreshTargets() FOSMirrorManager Reconstruye las cachés tras un cambio de escena

El cableado OnClick que necesita un botón de interfaz es SendCustomEvent("TriggerMirror") sobre el UdonBehaviour del control. Acertarlo a mano implica apuntar al UdonBehaviour y no al componente visible, y escribir el nombre del evento sin erratas: una errata no lanza ningún error, el botón simplemente no hace nada. El inspector lo conecta con un clic y te dice si ya lo está.


  • Un gestor por escena. Varios gestores se pelearían por los mismos espejos; el inspector lo señala como error.
  • Los espejos heredados se omiten. Solo el componente de espejo del SDK3 está expuesto a Udon. El escaneo los cuenta y te lo dice.
  • Los espejos creados en ejecución no se recogen automáticamente. Rellena targets y llama a RefreshTargets().
  • Un espejo sin gestor no se puede conmutar. El escaneo se lo asigna; el inspector del objetivo lo indica cuando falta.
  • Quest y PC se comportan igual aquí: no hay código específico de plataforma, ni podría haberlo, porque Udon se compila una sola vez para ambas.