Zum Inhalt springen

FOS Essentials Core

Version 1.4.0

Gemeinsame Grundlage aller FOS-Essentials-Werkzeuge für VRChat. Unity 2022.3.22f1 · VRChat SDK Worlds 3.10.4 · UdonSharp 1.x


Seite Element Aufgabe
Runtime FOSBehaviour Basisklasse jedes FOS-Verhaltens: Logging, Ownership, Sync
Runtime FOSPlayerRegistry Spielerliste, bei Beitritt und Verlassen aufgefrischt, ohne Zuweisung pro Frame
Runtime FOSToolAttribute Lässt ein Werkzeug sich beim Hub anmelden
Runtime FOSSectionAttribute Übersetzbarer Ersatz für [Header]
Runtime FOSVersion Identitäts- und Versionskonstanten (nur für den Editor)
Editor FOSBrandingSettings Logo, Farbe, Discord-/Shop-/Website-/Doku-URLs — ein einziges Asset
Editor FOSDefaultInspector Automatisches FOS-Banner an allem, was von FOSBehaviour abstammt
Editor FOSInspectorBase Basis für einen eigenen Inspektor, der das Banner behält
Editor FOSLocalizedInspector Übersetzte Feldbeschriftungen, Tooltips und Abschnittstitel
Editor FOSLoc Übersetzungssuche
Editor FOSHubWindow Tools > FOS Essentials > Hub: Umgebung, installierte Werkzeuge, Links
Editor FOSProgramAssets Erzeugt fehlende UdonSharpProgramAsset (siehe unten)
Editor FOSCoreBootstrap Meldet jede FOS-Runtime-Assembly bei UdonSharp an
Aufruf Wirkung
FOS Essentials > Hub Statusfenster: Umgebung, Prefabs, Werkzeuge, Links
FOS Essentials > <Pack> > <Prefab> Setzt dieses Prefab in die geöffnete Szene
FOS Essentials > Generate Missing Program Assets Erzeugt die fehlenden .asset-Dateien
FOS Essentials > Rebuild Prefab Menu Erzeugt die Menüeinträge jedes Packs neu
FOS Essentials > Check event wiring Listet jedes UI-Element, dessen Event zu keiner Methode passt
FOSPrefabCatalog.Scan() Von jedem Pack bereitgestellte Prefabs, nach Pack gruppiert
FOSPrefabCatalog.InstantiateInScene(path) Setzt ein Prefab in die geöffnete Szene
FOSLoc.T("schlüssel") Übersetzter Text; fällt auf Englisch zurück, dann auf den Schlüssel
FOSLoc.Format("schlüssel", args) Dasselbe, über string.Format
FOSLocalizedInspector.Draw(so, type) Übersetzter Inspektor, ersetzt DrawDefaultInspector()
FOSProgramAssets.GenerateMissing() Wie der Menüeintrag, aus Code. Gibt zurück, wie viele erzeugt wurden
FOSProgramAssets.FindMissing() Typen, die noch kein Program Asset haben
FOSCoreBootstrap.EnsureUdonSharpAssemblyRegistered() Meldet jede FOS-Runtime-asmdef bei UdonSharp an
FOSBranding.Settings Zugriff auf das Branding — gibt nie null zurück
FOSHeader.Draw(name, version) Zeichnet das Banner in einem eigenen Fenster

  1. Öffne Unity und lass es fertig kompilieren.
  2. Der Core erstellt von selbst Folgendes:
    • Assets/FOS/<Werkzeug>/Runtime/FOS.<Werkzeug>.Runtime.asset — UdonSharp-Registrierung, für jedes FOS-Pack
    • Assets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset — das Branding
    • ein UdonSharpProgramAsset neben jedem konkreten FOS-Verhalten
  3. Öffne FOS Essentials > Hub und prüfe, ob alle Statuspunkte grün sind.
  4. Klicke im Hub auf Open branding settings und trage Logo, Discord-URL und Shop-URL ein. Solange eine URL leer ist, bleibt ihre Schaltfläche deaktiviert, statt einen toten Link zu öffnen.

Das Hub listet außerdem jedes Prefab der installierten Packs auf, je eine Schaltfläche, und setzt es vor der Szenenansicht in die geöffnete Szene.


Jedes Prefab unter Assets/FOS/<Pack>/Prefabs/ erhält zusätzlich einen eigenen Eintrag unter FOS Essentials > <Pack> >, sodass sich ein Prefab ohne das Hub hinzufügen lässt.

[MenuItem] ist ein Attribut und damit zur Kompilierzeit festgeschrieben: Diese Einträge lassen sich nicht zur Laufzeit aus einem Ordner-Scan bauen. FOSPrefabMenuGenerator schreibt sie stattdessen nach Assets/FOS/Core/Editor/Generated/FOSPrefabMenu.generated.cs und nutzt dabei ausschließlich öffentliche API — Unity bietet zwar ein dynamisches Menu.AddMenuItem, es ist aber intern und undokumentiert, was man in einem verkauften Asset nicht ausliefert.

Die Datei wird beim Laden des Editors neu erzeugt, wenn ein Prefab unter einem Prefabs/-Ordner hinzugefügt, verschoben oder gelöscht wird, und über Rebuild Prefab Menu. Sie wird nur neu geschrieben, wenn sich ihr Inhalt tatsächlich unterscheidet — sonst löste das Schreiben eine Kompilierung aus, die die Erzeugung auslöste, die sie erneut schriebe.

Bearbeite die erzeugte Datei nicht: Sie wird überschrieben. Füge stattdessen Prefabs hinzu oder benenne sie um.

Ein Pack, das als Standard, Pro oder als Sonderfassung verkauft wird, legt jede Edition in einen eigenen Ordner:

Assets/FOS/<Pack>/<Edition>/Prefabs/

Beide Anordnungen werden erkannt, und ein Pack mit nur einer Edition ändert nichts. Die Edition wird zu einem echten Untermenü — FOS Essentials > FOS Tablet System > Standard > … — und zu einem eigenen Block im Hub.

Ein anderswo abgelegtes Prefab bleibt für beide unsichtbar. Das ist die ganze Regel des Auffindens: Prefabs/ liegt entweder direkt unter dem Pack oder einen Ordner tiefer.


using FOS.Core;
using UdonSharp;
using UnityEngine;
namespace FOS.MyTool
{
[UdonBehaviourSyncMode(BehaviourSyncMode.Manual)]
public class MySwitch : FOSBehaviour
{
[SerializeField] private GameObject target;
[UdonSynced] private bool _isOn;
public override void Interact()
{
FOSTakeOwnership(); // Ownership ist Pflicht, bevor eine synchronisierte Variable geschrieben wird
_isOn = !_isOn;
Apply();
FOSSync(); // RequestSerialization(), aber nur wenn wir Besitzer sind
}
public override void OnDeserialization()
{
Apply(); // spielt den Zustand auf Remotes UND bei spät Hinzukommenden nach
}
private void Apply()
{
target.SetActive(_isOn);
FOSLog("Switch: " + _isOn);
}
}
}

Das Banner mit Logo und den Discord-/Shop-Schaltflächen erscheint automatisch: Es ist keine Editor-Klasse zu schreiben.

using FOS.Core.EditorTools;
using UnityEditor;
[CustomEditor(typeof(MySwitch))]
public class MySwitchEditor : FOSInspectorBase
{
protected override void OnFOSInspectorGUI()
{
base.OnFOSInspectorGUI(); // übersetzte Felder
EditorGUILayout.HelpBox("What this tool does.", MessageType.Info);
}
}

UdonSharp akzeptiert nur ein [CustomEditor] je Typ. Zwei zu deklarieren protokolliert einen Fehler, und einer davon wird ignoriert.

Rufe hier nicht DrawDefaultInspector() auf: Es verliert die Übersetzung. base.OnFOSInspectorGUI() zeichnet die Felder über FOSLocalizedInspector.


Assets/FOS/<Werkzeug>/
├── Runtime/
│ ├── FOS.<Werkzeug>.Runtime.asmdef → Referenzen: FOS.Core.Runtime, UdonSharp.Runtime,
│ │ VRC.Udon, VRC.SDK3, VRC.SDKBase
│ ├── AssemblyInfo.cs → [assembly: FOSTool("FOS <Werkzeug>", "1.0.0", ...)]
│ └── ...
├── Editor/ (optional)
├── Prefabs/
└── README.md

Die asmdef muss FOS.<Werkzeug>.Runtime heißen: So findet der Core sie und meldet sie automatisch bei UdonSharp an.

Ein Werkzeug in mehreren Editionen wiederholt diese Anordnung einen Ordner tiefer, und jede Edition bekommt ihre eigene Assembly und ihre eigene [FOSTool]-Deklaration:

Assets/FOS/<Werkzeug>/
├── Standard/
│ ├── Runtime/ → FOS.<Werkzeug>.Standard.Runtime.asmdef
│ ├── Editor/ → FOS.<Werkzeug>.Standard.Editor.asmdef
│ └── Prefabs/
└── Pro/
├── Runtime/ → FOS.<Werkzeug>.Pro.Runtime.asmdef, referenziert FOS.<Werkzeug>.Standard.Runtime
├── Editor/
└── Prefabs/

Der Name muss weiterhin auf .Runtime enden — mehr schaut der Scan des Core nicht an, die Tiefe spielt keine Rolle. Jede Edition erscheint dann im Hub als eigenes Werkzeug, mit eigener Versionsnummer.

AssemblyInfo.cs:

using FOS.Core;
[assembly: FOSTool(
"FOS Doors",
"1.0.0",
Description = "doors.tool.description", // ein Übersetzungsschlüssel, vom Hub aufgelöst
MinimumCoreVersion = "1.0.0")]

Das Werkzeug erscheint dann mit seiner Version im Hub, und das Hub warnt, wenn der installierte Core zu alt dafür ist. Description nimmt sowohl einen Übersetzungsschlüssel als auch reinen Text; ein Schlüssel ist vorzuziehen, damit das Hub ihn in der Sprache der Nutzenden zeigen kann.


UdonSharp kompiliert nur Skripte aus Assembly-CSharp und aus Assemblies, die durch ein UdonSharpAssemblyDefinition-Asset deklariert sind (CompilationContext.IsUdonSharpAssembly).

Ein UdonSharpBehaviour in einer nicht angemeldeten asmdef wird stillschweigend ignoriert: kein Fehler, die Komponente landet in der Szene, der Inspektor sieht normal aus, und das Verhalten tut im Spiel schlicht gar nichts.

FOSCoreBootstrap erledigt das für alle Packs: Es durchsucht Assets/FOS nach asmdefs namens FOS.*.Runtime und legt jedes fehlende Registrierungs-Asset an. Ein neues Werkzeug braucht daher keinen manuellen Schritt.

Das Hub zeigt den Zustand dieser Registrierung, mit einer Schaltfläche Repair now.


UdonSharp erzeugt keine UdonSharpProgramAsset-Dateien. Der Compiler behandelt nur Skripte, die bereits eines haben (UdonSharpCompilerV1, module.programAsset) — deshalb kommt jedes mit dem SDK ausgelieferte Skript als Paar .cs + .asset. Ohne sein Asset lässt sich ein Verhalten überhaupt nicht an ein GameObject hängen.

Der Core übernimmt das: FOSProgramAssets durchsucht die von FOSBehaviour abgeleiteten Typen und legt das fehlende .asset neben der .cs an. Es läuft beim Laden des Editors und auf Anforderung aus dem Hub oder dem Menü.

Praktische Folgen:

  • Ein neues FOS-Verhalten zu schreiben erfordert keinen manuellen Schritt. Lass Unity kompilieren, das Asset erscheint.
  • Abstrakte und generische Klassen werden bewusst übersprungen: UdonSharp weigert sich, ihnen ein Program Asset zuzuordnen (Abstract U# behaviours cannot have an associated U# program asset). Deshalb ist FOSBehaviour abstrakt.
  • Eine Klasse muss exakt so heißen wie ihre .cs-Datei, sonst schlägt die Zuordnung fehl und U# meldet UdonSharpBehaviour classes must have the same name as their containing .cs file.
  • Eine vorhandene .asset-Datei wird nie überschrieben.

Die Editor-Oberfläche ist auf Englisch, Französisch, Spanisch und Deutsch übersetzt. Die Sprache wird im Hub oder in den Branding-Einstellungen gewählt; sie liegt in den EditorPrefs, gilt also pro Rechner, und steht standardmäßig auf Auto (folgt Unitys Sprache, mit Rückfall auf Englisch).

  1. Trage den Schlüssel in die vier Tabellen in FOSCoreStrings.cs ein.
  2. Rufe ihn mit FOSLoc.T("mein.schlüssel") auf, oder mit FOSLoc.Format("mein.schlüssel", arg), wenn der Text ein {0} enthält.

Ein fehlender Schlüssel fällt auf Englisch zurück, dann auf den rohen Schlüssel. Den rohen Schlüssel in der Oberfläche zu sehen ist beabsichtigt: Eine fehlende Übersetzung fällt sofort auf, statt unbemerkt zu bleiben.

Beschriftungen und Tooltips stammen nicht aus [Tooltip], sondern aus den Schlüsseln field.DeklarierenderTyp.feldName und tip.DeklarierenderTyp.feldName.

Maßgeblich ist der Typ, der das Feld deklariert, nicht der untersuchte Typ: Ein Feld an FOSBehaviour hat daher genau eine Übersetzung, gültig für jedes davon abgeleitete Werkzeug.

[Tooltip]-Attribute bleiben im Code auf Englisch: Sie sind der Rückfall und dokumentieren das Feld für alle, die den Quelltext lesen.

Verwende für einen Abschnittstitel kein [Header] — sein Text ist zur Kompilierzeit festgeschrieben. Nimm:

[FOSSection("section.core.diagnostic", "Diagnostics")]
[Tooltip("English fallback tooltip.")]
[SerializeField] private bool myField;

1. Jede für Nutzende sichtbare Zeichenkette muss übersetzbar sein, also über FOSLoc laufen. 2. Was nicht übersetzt werden kann, wird auf Englisch geschrieben.

Was nicht übersetzt werden kann und deshalb englisch bleibt

Abschnitt betitelt „Was nicht übersetzt werden kann und deshalb englisch bleibt“
Element Grund
Namen von Variablen, Feldern, Methoden und Klassen C#-Bezeichner
Namespaces, asmdef- und Assembly-Namen Bezeichner
Pfade in [MenuItem] und [AddComponentMenu] Unity schreibt sie beim Kompilieren fest
Rückfalltext der [Tooltip] Wird genutzt, wenn der Schlüssel fehlt
Zweites Argument von [FOSSection("schlüssel", "Fallback")] Dasselbe
Udon-Laufzeitlogs Udon hat weder Dictionary noch Generics
Meldungen an die Autorin oder den Autor eines Werkzeugs Sie richten sich an die Entwicklung
Datei- und Ordnernamen, Übersetzungsschlüssel Bezeichner

Text, den ein künftiges Werkzeug im Spiel anzeigt, folgt einem anderen Mechanismus (indexbasierte Arrays auf der Udon-Seite) und wird vom Core derzeit nicht abgedeckt.

Füge einen Wert am Ende der Aufzählung FOSLanguage hinzu — nie in der Mitte, die Werte landen unverändert in den EditorPrefs der Nutzenden — danach eine Tabelle in FOSCoreStrings und einen Fall in FOSLoc.LanguageLabel.


  • Nicht unterstützt: eigene Klassen und Structs außer UdonSharpBehaviour, Interfaces, Generics, delegate/event/Lambdas, LINQ, try/catch, async, Coroutinen, [,]-Arrays, params, Standardwerte für Parameter, ref/out.
  • Verzögerungen: SendCustomEventDelayedSeconds("Method", t), nie eine Coroutine.
  • Netzwerk: Ownership ist Pflicht, bevor eine [UdonSynced]-Variable geschrieben wird; SendCustomNetworkEvent wird für spät Hinzukommende nicht wiederholt — der Zustand muss aus synchronisierten Variablen und OnDeserialization rekonstruierbar sein.
  • Budgets: Continuous ≈ 200 Byte je Verhalten; Manual trägt mehr, ist aber ratenbegrenzt (≈ 1 nutzbare Serialisierung pro Sekunde). Spamme nie RequestSerialization().
  • Quest: Achte auf das Draw-Call-Budget, nutze mobile Shader, vermeide schweres Post-Processing.