FOS Essentials Core
Ce contenu n’est pas encore disponible dans votre langue.
Version 1.2.0
Shared foundation of every FOS Essentials tool for VRChat. Unity 2022.3.22f1 · VRChat SDK Worlds 3.10.4 · UdonSharp 1.x
What the Core provides
Section titled “What the Core provides”| Side | Item | Role |
|---|---|---|
| Runtime | FOSBehaviour |
Base class of every FOS behaviour: logging, ownership, sync |
| Runtime | FOSPlayerRegistry |
Player list refreshed on join/leave, with no per-frame allocation |
| Runtime | FOSToolAttribute |
Lets a tool declare itself to the Hub |
| Runtime | FOSSectionAttribute |
Localisable replacement for [Header] |
| Runtime | FOSVersion |
Identity and version constants (editor use only) |
| Editor | FOSBrandingSettings |
Logo, colour, Discord / Shop / site / docs URLs — one single asset |
| Editor | FOSDefaultInspector |
Automatic FOS banner on anything deriving from FOSBehaviour |
| Editor | FOSInspectorBase |
Base for a custom inspector that keeps the banner |
| Editor | FOSLocalizedInspector |
Translated field labels, tooltips and section titles |
| Editor | FOSLoc |
Translation lookup |
| Editor | FOSHubWindow |
Tools > FOS Essentials > Hub: environment, installed tools, links |
| Editor | FOSProgramAssets |
Generates missing UdonSharpProgramAsset (see below) |
| Editor | FOSCoreBootstrap |
Registers every FOS runtime assembly with UdonSharp |
Editor entry points
Section titled “Editor entry points”| Call | Effect |
|---|---|
FOS Essentials > Hub |
Status window: environment, prefabs, tools, links |
FOS Essentials > <Pack> > <Prefab> |
Drops that prefab into the open scene |
FOS Essentials > Generate Missing Program Assets |
Creates the missing .asset files |
FOS Essentials > Rebuild Prefab Menu |
Regenerates the per-pack menu entries |
FOSPrefabCatalog.Scan() |
Prefabs exposed by every pack, grouped by pack |
FOSPrefabCatalog.InstantiateInScene(path) |
Drops a prefab into the open scene |
FOSLoc.T("key") |
Translated text; falls back to English, then to the key |
FOSLoc.Format("key", args) |
Same, through string.Format |
FOSLocalizedInspector.Draw(so, type) |
Translated inspector, replaces DrawDefaultInspector() |
FOSProgramAssets.GenerateMissing() |
Same as the menu item, from code. Returns how many were created |
FOSProgramAssets.FindMissing() |
Types that have no program asset yet |
FOSCoreBootstrap.EnsureUdonSharpAssemblyRegistered() |
Registers every FOS runtime asmdef with UdonSharp |
FOSBranding.Settings |
Branding access — never returns null |
FOSHeader.Draw(name, version) |
Draws the banner inside a custom window |
First launch
Section titled “First launch”- Open Unity and let it finish compiling.
- The Core creates the following on its own:
Assets/FOS/<Tool>/Runtime/FOS.<Tool>.Runtime.asset— UdonSharp registration, for every FOS packAssets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset— branding- an
UdonSharpProgramAssetnext to every concrete FOS behaviour
- Open
FOS Essentials > Huband check that every status dot is green. - From the Hub, click Open branding settings and fill in the logo, the Discord URL and the shop URL. While a URL is empty its button stays disabled instead of opening a dead link.
The Hub also lists every prefab shipped by the installed packs, one button each, and drops it into the open scene in front of the scene view.
The prefab menu
Section titled “The prefab menu”Every prefab found under Assets/FOS/<Pack>/Prefabs/ also gets its own entry under
FOS Essentials > <Pack> >, so a prefab can be added without opening the Hub.
[MenuItem] is an attribute, therefore frozen at compile time: those entries cannot be
built from a folder scan at runtime. FOSPrefabMenuGenerator writes them instead into
Assets/FOS/Core/Editor/Generated/FOSPrefabMenu.generated.cs, using nothing but public
API — Unity does expose a dynamic Menu.AddMenuItem, but it is internal and undocumented,
which is not something to ship in a sold asset.
The file is regenerated when the editor loads, when a prefab is added, moved or deleted
under a Prefabs/ folder, and from Rebuild Prefab Menu. It is only rewritten when its
content actually differs — otherwise writing it would trigger a compile, which would
trigger generation, which would rewrite it again.
Do not edit the generated file: it is overwritten. Add or rename prefabs instead.
Packs shipped in several editions
Section titled “Packs shipped in several editions”A pack sold as Standard, Pro or as a custom build puts each edition in its own folder:
Assets/FOS/<Pack>/<Edition>/Prefabs/Both layouts are recognised, and a pack that has a single edition changes nothing. The
edition becomes a real submenu — FOS Essentials > FOS Tablet System > Standard > … —
and its own block in the Hub.
A prefab put anywhere else is still invisible to both. That is the whole discovery rule:
Prefabs/ sits either directly under the pack, or one folder deeper.
Writing a FOS behaviour
Section titled “Writing a FOS behaviour”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 is mandatory before writing a synced variable _isOn = !_isOn; Apply(); FOSSync(); // RequestSerialization(), but only when we are the owner }
public override void OnDeserialization() { Apply(); // replays the state on remotes AND on late joiners }
private void Apply() { target.SetActive(_isOn); FOSLog("Switch: " + _isOn); } }}The banner with the logo and the Discord / Shop buttons shows up automatically: no editor class to write.
Custom inspector
Section titled “Custom inspector”using FOS.Core.EditorTools;using UnityEditor;
[CustomEditor(typeof(MySwitch))]public class MySwitchEditor : FOSInspectorBase{ protected override void OnFOSInspectorGUI() { base.OnFOSInspectorGUI(); // translated fields EditorGUILayout.HelpBox("What this tool does.", MessageType.Info); }}UdonSharp accepts only one
[CustomEditor]per type. Declaring two logs an error and one of them is ignored.Do not call
DrawDefaultInspector()here: it loses the translation.base.OnFOSInspectorGUI()draws the fields throughFOSLocalizedInspector.
Creating a new tool that depends on the Core
Section titled “Creating a new tool that depends on the Core”Assets/FOS/<Tool>/├── Runtime/│ ├── FOS.<Tool>.Runtime.asmdef → references: FOS.Core.Runtime, UdonSharp.Runtime,│ │ VRC.Udon, VRC.SDK3, VRC.SDKBase│ ├── AssemblyInfo.cs → [assembly: FOSTool("FOS <Tool>", "1.0.0", ...)]│ └── ...├── Editor/ (optional)├── Prefabs/└── README.mdThe asmdef must be named FOS.<Tool>.Runtime: that is how the Core finds it and
registers it with UdonSharp automatically.
A tool shipped in several editions repeats that layout one folder deeper, and each
edition gets its own assembly and its own [FOSTool] declaration:
Assets/FOS/<Tool>/├── Standard/│ ├── Runtime/ → FOS.<Tool>.Standard.Runtime.asmdef│ ├── Editor/ → FOS.<Tool>.Standard.Editor.asmdef│ └── Prefabs/└── Pro/ ├── Runtime/ → FOS.<Tool>.Pro.Runtime.asmdef, references FOS.<Tool>.Standard.Runtime ├── Editor/ └── Prefabs/The name still has to end in .Runtime, which is all the Core scan looks at — the depth
does not matter. Each edition then shows up in the Hub as its own tool, with its own
version number.
AssemblyInfo.cs:
using FOS.Core;
[assembly: FOSTool( "FOS Doors", "1.0.0", Description = "doors.tool.description", // a translation key, resolved by the Hub MinimumCoreVersion = "1.0.0")]The tool then shows up in the Hub with its version, and the Hub warns when the
installed Core is too old for it. Description accepts either a translation key or
plain text; a key is preferred so the Hub can show it in the user’s language.
The asmdef × UdonSharp trap
Section titled “The asmdef × UdonSharp trap”UdonSharp only compiles scripts from Assembly-CSharp and from assemblies declared by
an UdonSharpAssemblyDefinition asset (CompilationContext.IsUdonSharpAssembly).
An UdonSharpBehaviour sitting in an unregistered asmdef is silently ignored: no
error, the component drops into the scene, the inspector looks normal, and the
behaviour does strictly nothing in game.
FOSCoreBootstrap handles this for every pack: it scans Assets/FOS for asmdefs
named FOS.*.Runtime and creates whatever registration asset is missing. A new tool
therefore needs no manual step.
The Hub shows the state of that registration, with a Repair now button.
The program asset trap
Section titled “The program asset trap”UdonSharp does not generate UdonSharpProgramAsset files. The compiler only handles
scripts that already have one (UdonSharpCompilerV1, module.programAsset) — which is
why every script shipped with the SDK comes as a .cs + .asset pair. Without its
asset, a behaviour cannot be added to a GameObject at all.
The Core takes care of it: FOSProgramAssets scans types deriving from FOSBehaviour
and creates the missing .asset next to the .cs. It runs when the editor loads, and
on demand from the Hub or the Tools menu.
Practical consequences:
- Writing a new FOS behaviour needs no manual step. Let Unity compile, the asset appears.
- Abstract and generic classes are deliberately skipped: UdonSharp refuses to
associate a program asset with them (
Abstract U# behaviours cannot have an associated U# program asset). That is whyFOSBehaviouris abstract. - A class must be named exactly like its
.csfile, otherwise the match fails and U# raisesUdonSharpBehaviour classes must have the same name as their containing .cs file. - An existing
.assetfile is never overwritten.
Localisation
Section titled “Localisation”The editor interface is translated into English, French, Spanish and German.
The language is picked in the Hub or in the branding settings; it is stored in
EditorPrefs, so it is per-machine, and defaults to Auto (follows Unity’s language,
falling back to English).
Translating a new string
Section titled “Translating a new string”- Add the key to the four tables in
FOSCoreStrings.cs. - Call it with
FOSLoc.T("my.key"), orFOSLoc.Format("my.key", arg)when the text contains a{0}.
A missing key falls back to English, then to the raw key. Seeing the raw key in the interface is intentional: a missing translation is spotted immediately instead of going unnoticed.
Translating a component’s fields
Section titled “Translating a component’s fields”Labels and tooltips do not come from [Tooltip] but from the keys
field.DeclaringType.fieldName and tip.DeclaringType.fieldName.
The type that declares the field is what counts, not the inspected type: a field on
FOSBehaviour therefore has a single translation, valid for every tool deriving from it.
[Tooltip] attributes stay written in English in the code: they are the fallback and
they document the field for anyone reading the sources.
For a section title, do not use [Header] — its text is frozen at compile time. Use:
[FOSSection("section.core.diagnostic", "Diagnostics")][Tooltip("English fallback tooltip.")][SerializeField] private bool myField;The rule, no exceptions
Section titled “The rule, no exceptions”1. Every user-visible string must be translatable, so it must go through
FOSLoc. 2. Whatever cannot be translated is written in English.
What cannot be translated, and is therefore written in English
Section titled “What cannot be translated, and is therefore written in English”| Item | Reason |
|---|---|
| Variable, field, method and class names | C# identifiers |
| Namespaces, asmdef and assembly names | Identifiers |
[MenuItem] and [AddComponentMenu] paths |
Unity freezes them at compile time |
[Tooltip] fallback text |
Used when the key is missing |
Second argument of [FOSSection("key", "Fallback")] |
Same |
| Udon runtime logs | Udon has neither Dictionary nor generics |
| Messages aimed at a tool author | They address the developer |
| File names, folder names, translation keys | Identifiers |
Text displayed in game by a future tool is a different mechanism (index-based arrays on the Udon side) and is not covered by the Core at this stage.
Adding a language
Section titled “Adding a language”Add a value at the end of the FOSLanguage enum — never in the middle, the values
are written as-is into the user’s EditorPrefs — then a table in FOSCoreStrings and a
case in FOSLoc.LanguageLabel.
UdonSharp reminders that apply to the whole pack
Section titled “UdonSharp reminders that apply to the whole pack”- Not supported: user classes/structs other than
UdonSharpBehaviour, interfaces, generics,delegate/event/lambdas, LINQ,try/catch,async, coroutines,[,]arrays,params, default parameter values,ref/out. - Delays:
SendCustomEventDelayedSeconds("Method", t), never a coroutine. - Networking: ownership is mandatory before writing an
[UdonSynced]variable;SendCustomNetworkEventis not replayed for late joiners — state must be rebuildable from synced variables andOnDeserialization. - Budgets: Continuous ≈ 200 bytes per behaviour; Manual is larger but rate-limited
(≈ 1 useful serialisation per second). Never spam
RequestSerialization(). - Quest: mind the draw call budget, use mobile shaders, avoid heavy post-processing.
