Ir al contenido

FOS Essentials Core

Esta página aún no está disponible en tu idioma.

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


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
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

  1. Open Unity and let it finish compiling.
  2. The Core creates the following on its own:
    • Assets/FOS/<Tool>/Runtime/FOS.<Tool>.Runtime.asset — UdonSharp registration, for every FOS pack
    • Assets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset — branding
    • an UdonSharpProgramAsset next to every concrete FOS behaviour
  3. Open FOS Essentials > Hub and check that every status dot is green.
  4. 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.


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.

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.


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.

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 through FOSLocalizedInspector.


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.md

The 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.


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.


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 why FOSBehaviour is abstract.
  • A class must be named exactly like its .cs file, otherwise the match fails and U# raises UdonSharpBehaviour classes must have the same name as their containing .cs file.
  • An existing .asset file is never overwritten.

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).

  1. Add the key to the four tables in FOSCoreStrings.cs.
  2. Call it with FOSLoc.T("my.key"), or FOSLoc.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.

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;

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.

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; SendCustomNetworkEvent is not replayed for late joiners — state must be rebuildable from synced variables and OnDeserialization.
  • 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.