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
Was der Core mitbringt
Abschnitt betitelt „Was der Core mitbringt“| 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 |
Einstiegspunkte im Editor
Abschnitt betitelt „Einstiegspunkte im Editor“| 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 |
Erster Start
Abschnitt betitelt „Erster Start“- Öffne Unity und lass es fertig kompilieren.
- Der Core erstellt von selbst Folgendes:
Assets/FOS/<Werkzeug>/Runtime/FOS.<Werkzeug>.Runtime.asset— UdonSharp-Registrierung, für jedes FOS-PackAssets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset— das Branding- ein
UdonSharpProgramAssetneben jedem konkreten FOS-Verhalten
- Öffne
FOS Essentials > Hubund prüfe, ob alle Statuspunkte grün sind. - 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.
Das Prefab-Menü
Abschnitt betitelt „Das Prefab-Menü“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.
Packs in mehreren Editionen
Abschnitt betitelt „Packs in mehreren Editionen“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.
Ein FOS-Verhalten schreiben
Abschnitt betitelt „Ein FOS-Verhalten schreiben“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.
Eigener Inspektor
Abschnitt betitelt „Eigener Inspektor“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 überFOSLocalizedInspector.
Ein neues Werkzeug bauen, das den Core benötigt
Abschnitt betitelt „Ein neues Werkzeug bauen, das den Core benötigt“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.mdDie 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.
Die Falle asmdef × UdonSharp
Abschnitt betitelt „Die Falle asmdef × UdonSharp“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.
Die Falle des Program Assets
Abschnitt betitelt „Die Falle des Program Assets“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 istFOSBehaviourabstrakt. - Eine Klasse muss exakt so heißen wie ihre
.cs-Datei, sonst schlägt die Zuordnung fehl und U# meldetUdonSharpBehaviour classes must have the same name as their containing .cs file. - Eine vorhandene
.asset-Datei wird nie überschrieben.
Lokalisierung
Abschnitt betitelt „Lokalisierung“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).
Eine neue Zeichenkette übersetzen
Abschnitt betitelt „Eine neue Zeichenkette übersetzen“- Trage den Schlüssel in die vier Tabellen in
FOSCoreStrings.csein. - Rufe ihn mit
FOSLoc.T("mein.schlüssel")auf, oder mitFOSLoc.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.
Die Felder einer Komponente übersetzen
Abschnitt betitelt „Die Felder einer Komponente übersetzen“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;Die Regel, ohne Ausnahme
Abschnitt betitelt „Die Regel, ohne Ausnahme“1. Jede für Nutzende sichtbare Zeichenkette muss übersetzbar sein, also über
FOSLoclaufen. 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.
Eine Sprache hinzufügen
Abschnitt betitelt „Eine Sprache hinzufügen“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.
UdonSharp-Erinnerungen für das gesamte Pack
Abschnitt betitelt „UdonSharp-Erinnerungen für das gesamte Pack“- 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;SendCustomNetworkEventwird für spät Hinzukommende nicht wiederholt — der Zustand muss aus synchronisierten Variablen undOnDeserializationrekonstruierbar 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.
