Zum Inhalt springen

FOS Mirror

Version 1.0.1 · benötigt FOS Essentials Core 1.0.0 oder neuer

Schaltet Spiegel ein und aus — nach Entfernung, per Schaltfläche, per Interaktion — von einem einzigen zentralen Manager aus, mit Qualitäts-Voreinstellungen und einer richtungsweisenden Kostenschätzung. Benötigt FOS Essentials Core 1.0.0.

Spiegel sind das Erste, was die Bildrate einer VRChat-Welt ruiniert. Dieses Pack macht sie nicht billig — das kann nichts. Es macht sie kontrolliert: standardmäßig aus, an nur dort und dann, wo es jemand tatsächlich verlangt hat.


  1. Erstelle ein leeres GameObject und füge FOS Essentials > Mirror > Mirror Manager hinzu.
  2. Klicke auf Scan the scene. Jeder VRChat-Spiegel erhält ein FOSMirrorTarget, seine Spiegelkomponente und sein Manager werden verdrahtet, und die Liste des Managers wird gefüllt.
  3. Entscheide den Mode: exklusiv, oder jeder Spiegel unabhängig.
  4. Füge dort ein Bedienelement hinzu, wo jemand einen Spiegel schalten können soll: Mirror Control (UI Button) oder Mirror Control (Interact).

Führe den Scan nach dem Hinzufügen neuer Spiegel erneut aus. Bestehende Ziele werden nie überschrieben, jede manuelle Feinabstimmung bleibt erhalten.


Komponente Aufgabe
FOSMirrorManager Einer je Szene. Besitzt die Schleife, den Entfernungstest und den Exklusivmodus.
FOSMirrorTarget Eines je Spiegel. Hält Einstellungen und den Ein/Aus-Zustand — keine Arbeit pro Frame.
FOSMirrorButton Schaltet einen Spiegel über einen UI-Button. Verdrahtet das OnClick für dich.
FOSMirrorInteract Schaltet einen Spiegel, wenn jemand das tragende Objekt benutzt.

Das Pack deklariert bewusst keinen eigenen Inspektor für VRC_MirrorReflection. Unity akzeptiert nur einen je Typ, und VRWorldToolkit bringt bereits einen mit. Hier läuft alles über die FOS-Komponente, sodass beide Werkzeuge nebeneinander weiterarbeiten.


Die Komponente VRC_MirrorReflection, über ihr .enabled — nie das GameObject.

SetActive(false) würde jedes OnEnable der Hierarchie erneut ausführen, Udon eingeschlossen, was den Zustand unbeteiligter Skripte zurücksetzen kann. Es würde außerdem das Spiegelobjekt selbst verbergen — dabei ist ein Spiegel meist ein verzierter Rahmen, der sichtbar bleiben soll, wenn er dunkel wird.

Extra components und Extra colliders werden mitgeschaltet.

Collider leitet nicht von Behaviour ab — er leitet direkt von Component ab, mit eigener enabled-Eigenschaft. Deshalb haben Collider eine eigene Liste.

Setze niemals den Collider, mit dem ein Spiegel wieder eingeschaltet wird, in diese Liste: Er würde mit dem Spiegel deaktiviert, und der Spiegel ließe sich nie wieder einschalten. Der Inspektor des Ziels meldet diesen Fall als Fehler.


Die Spiegelkomponente zu deaktivieren stoppt die Spiegelung, aber die Oberfläche wird weiter gezeichnet — und ein Spiegel-Shader ohne etwas zu spiegeln malt reines Schwarz. Ein ausgeschalteter Spiegel ist daher ein schwarzes Rechteck, sofern du nichts anderes sagst.

Zwei Felder am Ziel bestimmen das Aussehen:

Mirror surface Off material Ergebnis
leer — Das schwarze Rechteck. Nichts wird angefasst.
gesetzt leer Die Oberfläche wird verborgen. Ihr Draw Call entfällt ebenfalls.
gesetzt gesetzt Die Oberfläche wechselt auf dieses Material: dunkles Glas, mattierte Scheibe, lackierte Platte.

Der Scan füllt Mirror surface mit dem Renderer am Spiegel, Verbergen ist also das Standardverhalten. Führe den Scan in einer älteren Szene erneut aus, um das zu übernehmen, oder nutze die Schaltfläche Use the mirror’s renderer im Inspektor des Ziels.

Das Pack liefert ein fertiges Aus-Material mit, Materials/FOSMirrorOff.mat — eine dunkle, glänzende Scheibe. Es nutzt Unitys Standard-Shader, damit das Material von nichts außerhalb von Unity abhängt. Tausche seinen Shader auf Quest gegen einen mobilen wie VRChat/Mobile/Standard Lite, oder lass Off material einfach leer: Die Oberfläche zu verbergen kostet nichts und braucht überhaupt kein Material.

Der Wechsel schreibt renderer.material, nie sharedMaterial — sharedMaterial zur Laufzeit zu schreiben verändert das Material-Asset auf der Festplatte, was in ClientSim das Spiegelmaterial dauerhaft überschrieben in deinem Projekt zurückließe.

Renderer leitet von Component ab, nicht von Behaviour, und trägt eine eigene enabled-Eigenschaft — derselbe Grund, aus dem Collider eine eigene Liste bekommen.

Das „Ein”-Material wird beim Weltstart vom Renderer erfasst. Lass das Aus-Material in der Szene nicht am Renderer zugewiesen: Es würde als Spiegel-Aussehen erfasst, und die Spiegelung käme nie zurück. Der Inspektor meldet diesen Fall als Fehler.

Das gilt auch für das Entfernungs-Culling, und das ist meist erwünscht: Ein Spiegel außer Reichweite verliert sowohl seine Spiegelung als auch seine Oberfläche.


Ein Spiegel wird nur gezeichnet, wenn beide zutreffen:

Hälfte Besitzer Gesetzt durch
Manueller Zustand — will ihn jemand an? Das Ziel Schaltfläche, Interaktion, startsOn
Reichweite — ist die Person nah genug? Der Manager Entfernungstest, Budget je Frame

Ein Spiegel, den jemand ausgeschaltet hat, bleibt aus, so nah man auch herangeht. Die Reichweite schaltet nie von selbst einen Spiegel ein — sie lässt nur einen zurückkommen, der bereits an ist.

Dieselbe Logik wie bei FOS UI Culling: quadrierte Entfernungen, zwei Schwellen, Prüfungen reihum, und ein Schreibvorgang nur, wenn sich der Zustand wirklich ändert.

Option Wirkung
Default distance Meter, unterhalb derer ein Spiegel gezeichnet werden darf, sofern das Ziel es nicht überschreibt.
Show distance (Ziel) Überschreibung je Spiegel. -1 bedeutet „den Wert des Managers erben”.
Distance reference (Ziel) Punkt, von dem aus gemessen wird. Leer bedeutet der Spiegel selbst.
Hysteresis Zusätzliche Meter, bevor wieder abgeschaltet wird.
Checks per frame Wie viele Spiegel pro Frame geprüft werden. Standard 2.

Der Inspektor des Ziels schreibt das Ergebnis aus — „Effective distance: 12 m (inherited from the manager)“ — damit du dir nie merken musst, was -1 bedeutet.

Entfernungen werden einmalig im Start gelesen. Eine zur Laufzeit zu ändern erfordert einen Aufruf von RefreshTargets() am Manager.

Ein gerade eingeschalteter Spiegel wird sofort gemessen, statt auf seinen Turnus zu warten: Direkt vor einem Spiegel zu stehen, der vier Frames zum Erscheinen braucht, wirkt kaputt.


Modus Verhalten
Free Jeder Spiegel ist unabhängig. Nichts hindert jemanden daran, alle einzuschalten.
Exclusive Einen einzuschalten schaltet alle anderen aus.

Der Exklusivmodus ist die wirksamste Einstellung dieses Packs, denn der zweite Kostenfaktor einer Welt ist, wie viele Spiegel gleichzeitig gezeichnet werden — und diese Zahl bestimmt sonst die erste neugierige Person.

Er greift auch beim Start: Sind mehrere Spiegel auf „startet an” gestellt, tut es nur der erste, und der Inspektor des Managers sagt das.


Bereich Netzwerk Wer zahlt
Local Gar keines Nur wer geschaltet hat
Synchronized [UdonSynced], manuelle Synchronisierung Alle in der Instanz

Local ist der Standard und die richtige Wahl für einen öffentlichen Spiegel: Wer den eigenen Avatar prüfen will, bekommt einen, und niemand sonst bezahlt dafür.

Synchronized ist für einen Spiegel gedacht, der zur Welt gehört — eine Tanzbühne, eine gemeinsame Umkleide. Der Besitz wird vor dem Schreiben der Variablen übernommen, und der Zustand wird für später Beitretende aus OnDeserialization wiederhergestellt.

Ein SendCustomNetworkEvent wird für spät Hinzukommende nicht wiederholt. Deshalb lebt der Zustand in einer synchronisierten Variablen und nicht in einem Ereignis.

Da der Zustand am Ziel gespeichert wird, lassen sich beide Bereiche in einer Szene frei mischen. Im Exklusivmodus solltest du wissen, was Mischen bedeutet: Einen lokalen Spiegel auszuschalten schaltet ihn nur für dich aus.

Manuelle Synchronisierung ist auf etwa eine Serialisierung pro Sekunde und Verhalten begrenzt. Einen synchronisierten Spiegel im Exklusivmodus einzuschalten schreibt auch in die übrigen synchronisierten Spiegel — mach also nicht ein Dutzend Spiegel synchronisiert, wenn nur einer es braucht.


Zwei Einstellungen des VRChat-Spiegels liegen am Ziel offen: die gespiegelten Layer und das Abschalten der Pixel-Lichter.

Voreinstellung Gespiegelte Layer
Untouched (Standard) Nichts wird angewandt; der Spiegel behält seine eigenen Einstellungen.
Players only Player + MirrorReflection — Avatare vor der Skybox.
Players and world Default + Player + Environment + MirrorReflection.
World only Default + Environment. Kein Avatar wird je zweimal gezeichnet.
Nothing Eine leere Maske. Der Spiegel zeichnet eine leere Spiegelung.
Custom Was du von Hand in Reflected layers einstellst.

Die Voreinstellung löst sich beim Auswählen sofort in das Feld Reflected layers auf — die Maske, die du siehst, ist also die, die angewandt wird. Der Manager schreibt sie im Start in den Spiegel; der Inspektor des Ziels bietet außerdem Apply to the mirror now, um sie schon im Editor zu schreiben, praktisch, wenn die Prüfungen von VRWorldToolkit sie sehen sollen.

Untouched existiert, weil ein Werkzeug niemals stillschweigend von Hand gemachte Einstellungen überschreiben darf. Es wird nichts geschrieben, bis du eine Voreinstellung wählst.

Der Inspektor warnt vor den klassischen Fehlern: die unangetastete Standardmaske, gespiegeltes UiMenu, gespiegeltes PlayerLocal, und gespiegelte Personen ohne den Layer MirrorReflection, was dazu führt, dass sie einander sehen, sich selbst aber nicht.


Die Inspektoren von Ziel und Manager zeigen eine richtungsweisende Punktzahl von 100. Sie ist eine gewichtete Schätzung, keine Messung, und entsteht in dieser Reihenfolge:

  1. Die gespiegelten Layer, der beherrschende Faktor. Ein Spiegel zeichnet die Szene ein zweites Mal — in VR zweimal. Was er kostet, ist also das, was er erneut zeichnet. Avatare zu spiegeln verdoppelt den schwersten Posten einer belebten Instanz, und dieser Posten wächst mit der Zahl der Anwesenden, nicht mit der Welt. Ein Spiegel „nur Welt” kostet einen Bruchteil desselben Spiegels mit Personen, bei gleicher Größe und Position.

  2. Wie viele Spiegel gleichzeitig gezeichnet werden können. Zwei eingeschaltete Spiegel bedeuten zwei zusätzliche Render-Durchgänge; zwischen ihnen wird nichts geteilt. Der Exklusivmodus streicht diesen Faktor ganz, weshalb die Szenenwertung den schlechtesten Spiegel nimmt statt der Summe.

  3. Die Bildschirmabdeckung, zuletzt und schwach gewichtet. VRChat rendert einen Spiegel in eine Textur, deren Größe die Auflösung des Headsets bestimmt, nicht der Spiegel: Ein großer Spiegel kostet also nicht mechanisch mehr als ein kleiner. Größe zählt indirekt — ein großer Spiegel ist von weiter weg sichtbar, füllt sich öfter mit Avataren und lässt sich schlechter über die Entfernung ausblenden.

Nutze die Punktzahl, um zwei Einstellungen derselben Welt zu vergleichen. Sie ist kein absoluter Wert und ersetzt kein Profiling.


Wird ein Spiegel ausgewählt, zeichnet es seine Ebene, einen Pfeil entlang der spiegelnden Seite und ein rotes Kreuz auf der Rückseite. Ein Spiegel ist ein flaches Objekt: Nichts in der Szenenansicht unterscheidet einen richtig platzierten von einem, der um 180° gedreht wurde — man merkt es üblicherweise im Spiel, vor einem schwarzen Rechteck stehend.

Die Normale wird aus dem Mesh gelesen, nicht aus dem Transform erraten, und bleibt damit richtig, welches Mesh der Spiegel auch verwendet.

Das Gizmo lebt in einem Editor-Skript mit [DrawGizmo]. Es ist bewusst kein OnDrawGizmos im Udon-Verhalten: Udon ruft diese Methode nie auf, und UdonSharp müsste sie trotzdem kompilieren — samt Gizmos und Handles, die nicht bereitstehen.


Jeder öffentliche Einstiegspunkt kommt ohne Parameter aus, jedes Skript erreicht ihn also mit SendCustomEvent:

Aufruf An Wirkung
FOSRequestToggle() FOSMirrorTarget Kippt den Spiegel
FOSRequestOn() / FOSRequestOff() FOSMirrorTarget Erzwingt einen Zustand
TriggerMirror() FOSMirrorButton / FOSMirrorInteract Führt die Aktion des Bedienelements aus
FOSTurnAllOff() FOSMirrorManager Notschalter: schaltet jeden Spiegel aus
RefreshTargets() FOSMirrorManager Baut die Caches nach einer Szenenänderung neu auf

Die OnClick-Verdrahtung, die eine UI-Schaltfläche braucht, ist SendCustomEvent("TriggerMirror") am UdonBehaviour des Bedienelements. Von Hand richtig hinzubekommen heißt, auf das UdonBehaviour statt auf die sichtbare Komponente zu zielen und den Ereignisnamen ohne Tippfehler zu schreiben — ein Tippfehler löst keinen Fehler aus, die Schaltfläche tut schlicht nichts. Der Inspektor verdrahtet sie mit einem Klick und sagt dir, ob sie es schon ist.


  • Ein Manager je Szene. Mehrere Manager würden sich um dieselben Spiegel streiten; der Inspektor meldet das als Fehler.
  • Alte Spiegel werden übersprungen. Nur die Spiegelkomponente des SDK3 ist Udon zugänglich. Der Scan zählt sie und sagt es dir.
  • Zur Laufzeit erzeugte Spiegel werden nicht automatisch erfasst. Fülle targets und rufe RefreshTargets() auf.
  • Ein Spiegel ohne Manager lässt sich nicht schalten. Der Scan weist ihn zu; der Inspektor des Ziels sagt es, wenn er fehlt.
  • Quest und PC verhalten sich hier identisch — es gibt keinen plattformspezifischen Code, und es könnte ihn nicht geben: Udon wird einmal für beide kompiliert.