Aller au contenu

FOS Essentials Core

Version 1.4.0

Socle commun de tous les outils FOS Essentials pour VRChat. Unity 2022.3.22f1 · VRChat SDK Worlds 3.10.4 · UdonSharp 1.x


Côté Élément Rôle
Runtime FOSBehaviour Classe de base de tout comportement FOS : logs, ownership, synchro
Runtime FOSPlayerRegistry Liste de joueurs rafraîchie à l’arrivée et au départ, sans allocation par frame
Runtime FOSToolAttribute Permet à un outil de se déclarer au Hub
Runtime FOSSectionAttribute Remplaçant traduisible de [Header]
Runtime FOSVersion Constantes d’identité et de version (usage éditeur seulement)
Éditeur FOSBrandingSettings Logo, couleur, URL Discord / boutique / site / doc — un seul asset
Éditeur FOSDefaultInspector Bandeau FOS automatique sur tout ce qui dérive de FOSBehaviour
Éditeur FOSInspectorBase Base d’un inspecteur sur mesure qui conserve le bandeau
Éditeur FOSLocalizedInspector Libellés, infobulles et titres de section traduits
Éditeur FOSLoc Recherche de traduction
Éditeur FOSHubWindow Tools > FOS Essentials > Hub : environnement, outils installés, liens
Éditeur FOSProgramAssets Génère les UdonSharpProgramAsset manquants (voir plus bas)
Éditeur FOSCoreBootstrap Déclare chaque assembly runtime FOS auprès d’UdonSharp
Appel Effet
FOS Essentials > Hub Fenêtre d’état : environnement, prefabs, outils, liens
FOS Essentials > <Pack> > <Prefab> Pose ce prefab dans la scène ouverte
FOS Essentials > Generate Missing Program Assets Crée les fichiers .asset manquants
FOS Essentials > Rebuild Prefab Menu Régénère les entrées de menu de chaque pack
FOS Essentials > Check event wiring Liste les contrôles d’interface dont l’événement ne correspond à aucune méthode
FOSPrefabCatalog.Scan() Prefabs exposés par chaque pack, groupés par pack
FOSPrefabCatalog.InstantiateInScene(path) Pose un prefab dans la scène ouverte
FOSLoc.T("cle") Texte traduit ; retombe sur l’anglais, puis sur la clé
FOSLoc.Format("cle", args) Idem, via string.Format
FOSLocalizedInspector.Draw(so, type) Inspecteur traduit, remplace DrawDefaultInspector()
FOSProgramAssets.GenerateMissing() Comme l’entrée de menu, depuis du code. Renvoie le nombre créé
FOSProgramAssets.FindMissing() Types qui n’ont pas encore de program asset
FOSCoreBootstrap.EnsureUdonSharpAssemblyRegistered() Déclare chaque asmdef runtime FOS auprès d’UdonSharp
FOSBranding.Settings Accès au branding — ne renvoie jamais null
FOSHeader.Draw(name, version) Dessine le bandeau dans une fenêtre sur mesure

  1. Ouvrez Unity et laissez-le finir de compiler.
  2. Le Core crée tout seul ce qui suit :
    • Assets/FOS/<Outil>/Runtime/FOS.<Outil>.Runtime.asset — déclaration UdonSharp, pour chaque pack FOS
    • Assets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset — le branding
    • un UdonSharpProgramAsset à côté de chaque comportement FOS concret
  3. Ouvrez FOS Essentials > Hub et vérifiez que tous les voyants sont au vert.
  4. Depuis le Hub, cliquez sur Open branding settings et renseignez le logo, l’URL Discord et l’URL de la boutique. Tant qu’une URL est vide, son bouton reste désactivé plutôt que d’ouvrir un lien mort.

Le Hub liste aussi tous les prefabs livrés par les packs installés, un bouton chacun, et les pose dans la scène ouverte, devant la vue de scène.


Chaque prefab trouvé sous Assets/FOS/<Pack>/Prefabs/ reçoit également son entrée sous FOS Essentials > <Pack> >, ce qui permet d’ajouter un prefab sans ouvrir le Hub.

[MenuItem] est un attribut, donc figé à la compilation : ces entrées ne peuvent pas être construites depuis un balayage de dossier à l’exécution. FOSPrefabMenuGenerator les écrit donc dans Assets/FOS/Core/Editor/Generated/FOSPrefabMenu.generated.cs, en n’utilisant que de l’API publique — Unity expose bien un Menu.AddMenuItem dynamique, mais il est interne et non documenté, ce qui n’est pas quelque chose à livrer dans un asset vendu.

Le fichier est régénéré au chargement de l’éditeur, quand un prefab est ajouté, déplacé ou supprimé sous un dossier Prefabs/, et depuis Rebuild Prefab Menu. Il n’est réécrit que si son contenu diffère réellement — sinon l’écrire déclencherait une compilation, qui déclencherait la génération, qui le réécrirait encore.

N’éditez pas le fichier généré : il est écrasé. Ajoutez ou renommez des prefabs à la place.

Un pack vendu en Standard, Pro ou en version sur mesure place chaque édition dans son propre dossier :

Assets/FOS/<Pack>/<Édition>/Prefabs/

Les deux dispositions sont reconnues, et un pack qui n’a qu’une édition ne change rien. L’édition devient un vrai sous-menu — FOS Essentials > FOS Tablet System > Standard > … — et son propre bloc dans le Hub.

Un prefab rangé ailleurs reste invisible pour les deux. C’est toute la règle de découverte : Prefabs/ se trouve soit directement sous le pack, soit un dossier plus bas.


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(); // l'ownership est obligatoire avant d'écrire une variable synchronisée
_isOn = !_isOn;
Apply();
FOSSync(); // RequestSerialization(), mais seulement si nous sommes propriétaire
}
public override void OnDeserialization()
{
Apply(); // rejoue l'état sur les distants ET sur les arrivants tardifs
}
private void Apply()
{
target.SetActive(_isOn);
FOSLog("Switch: " + _isOn);
}
}
}

Le bandeau avec le logo et les boutons Discord / Boutique apparaît automatiquement : aucune classe éditeur à écrire.

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

UdonSharp n’accepte qu’un seul [CustomEditor] par type. En déclarer deux journalise une erreur et l’un des deux est ignoré.

N’appelez pas DrawDefaultInspector() ici : il perd la traduction. base.OnFOSInspectorGUI() dessine les champs via FOSLocalizedInspector.


Assets/FOS/<Outil>/
├── Runtime/
│ ├── FOS.<Outil>.Runtime.asmdef → références : FOS.Core.Runtime, UdonSharp.Runtime,
│ │ VRC.Udon, VRC.SDK3, VRC.SDKBase
│ ├── AssemblyInfo.cs → [assembly: FOSTool("FOS <Outil>", "1.0.0", ...)]
│ └── ...
├── Editor/ (facultatif)
├── Prefabs/
└── README.md

L’asmdef doit s’appeler FOS.<Outil>.Runtime : c’est ainsi que le Core le trouve et le déclare automatiquement auprès d’UdonSharp.

Un outil livré en plusieurs éditions répète cette disposition un dossier plus bas, et chaque édition obtient son assembly et sa propre déclaration [FOSTool] :

Assets/FOS/<Outil>/
├── Standard/
│ ├── Runtime/ → FOS.<Outil>.Standard.Runtime.asmdef
│ ├── Editor/ → FOS.<Outil>.Standard.Editor.asmdef
│ └── Prefabs/
└── Pro/
├── Runtime/ → FOS.<Outil>.Pro.Runtime.asmdef, référence FOS.<Outil>.Standard.Runtime
├── Editor/
└── Prefabs/

Le nom doit toujours se terminer par .Runtime, ce qui est tout ce que le balayage du Core regarde — la profondeur n’a pas d’importance. Chaque édition apparaît alors dans le Hub comme un outil à part entière, avec son propre numéro de version.

AssemblyInfo.cs :

using FOS.Core;
[assembly: FOSTool(
"FOS Doors",
"1.0.0",
Description = "doors.tool.description", // une clé de traduction, résolue par le Hub
MinimumCoreVersion = "1.0.0")]

L’outil apparaît alors dans le Hub avec sa version, et le Hub avertit quand le Core installé est trop ancien pour lui. Description accepte aussi bien une clé de traduction qu’un texte brut ; la clé est préférable, pour que le Hub puisse l’afficher dans la langue de l’utilisateur.


UdonSharp ne compile que les scripts d’Assembly-CSharp et ceux des assemblies déclarées par un asset UdonSharpAssemblyDefinition (CompilationContext.IsUdonSharpAssembly).

Un UdonSharpBehaviour placé dans un asmdef non déclaré est ignoré en silence : aucune erreur, le composant se pose dans la scène, l’inspecteur paraît normal, et le comportement ne fait strictement rien en jeu.

FOSCoreBootstrap s’en charge pour tous les packs : il balaie Assets/FOS à la recherche d’asmdefs nommés FOS.*.Runtime et crée les assets de déclaration manquants. Un nouvel outil ne demande donc aucune étape manuelle.

Le Hub affiche l’état de cette déclaration, avec un bouton Repair now.


UdonSharp ne génère pas les fichiers UdonSharpProgramAsset. Le compilateur ne traite que les scripts qui en ont déjà un (UdonSharpCompilerV1, module.programAsset) — d’où les paires .cs + .asset livrées avec le SDK. Sans son asset, un comportement ne peut tout simplement pas être ajouté à un GameObject.

Le Core s’en occupe : FOSProgramAssets balaie les types dérivant de FOSBehaviour et crée le .asset manquant à côté du .cs. Il s’exécute au chargement de l’éditeur, et à la demande depuis le Hub ou le menu.

Conséquences pratiques :

  • Écrire un nouveau comportement FOS ne demande aucune étape manuelle. Laissez Unity compiler, l’asset apparaît.
  • Les classes abstraites et génériques sont volontairement ignorées : UdonSharp refuse de leur associer un program asset (Abstract U# behaviours cannot have an associated U# program asset). C’est pourquoi FOSBehaviour est abstraite.
  • Une classe doit porter exactement le nom de son fichier .cs, sans quoi la correspondance échoue et U# lève UdonSharpBehaviour classes must have the same name as their containing .cs file.
  • Un fichier .asset existant n’est jamais écrasé.

L’interface de l’éditeur est traduite en anglais, français, espagnol et allemand. La langue se choisit dans le Hub ou dans les réglages de branding ; elle est stockée dans les EditorPrefs, donc par machine, et vaut Auto par défaut (suit la langue d’Unity, avec repli sur l’anglais).

  1. Ajoutez la clé aux quatre tables de FOSCoreStrings.cs.
  2. Appelez-la avec FOSLoc.T("ma.cle"), ou FOSLoc.Format("ma.cle", arg) quand le texte contient un {0}.

Une clé absente retombe sur l’anglais, puis sur la clé brute. Voir la clé brute dans l’interface est voulu : une traduction manquante se remarque immédiatement au lieu de passer inaperçue.

Les libellés et les infobulles ne viennent pas de [Tooltip] mais des clés field.TypeQuiDeclare.nomDuChamp et tip.TypeQuiDeclare.nomDuChamp.

C’est le type qui déclare le champ qui compte, pas le type inspecté : un champ porté par FOSBehaviour n’a donc qu’une seule traduction, valable pour tous les outils qui en dérivent.

Les attributs [Tooltip] restent écrits en anglais dans le code : ils servent de repli et documentent le champ pour qui lit les sources.

Pour un titre de section, n’utilisez pas [Header] — son texte est figé à la compilation. Utilisez :

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

1. Toute chaîne visible par l’utilisateur doit être traduisible, donc passer par FOSLoc. 2. Ce qui ne peut pas être traduit s’écrit en anglais.

Ce qui ne peut pas être traduit, et s’écrit donc en anglais

Section intitulée « Ce qui ne peut pas être traduit, et s’écrit donc en anglais »
Élément Raison
Noms de variables, champs, méthodes et classes Identifiants C#
Namespaces, noms d’asmdef et d’assembly Identifiants
Chemins [MenuItem] et [AddComponentMenu] Unity les fige à la compilation
Texte de repli des [Tooltip] Utilisé quand la clé manque
Second argument de [FOSSection("cle", "Fallback")] Idem
Logs runtime Udon Udon n’a ni Dictionary ni généricité
Messages destinés à l’auteur d’un outil Ils s’adressent au développeur
Noms de fichiers, de dossiers, clés de traduction Identifiants

Le texte affiché en jeu par un futur outil relève d’un autre mécanisme (tableaux indexés côté Udon) et n’est pas couvert par le Core à ce stade.

Ajoutez une valeur à la fin de l’énumération FOSLanguage — jamais au milieu, les valeurs sont écrites telles quelles dans les EditorPrefs de l’utilisateur — puis une table dans FOSCoreStrings et un cas dans FOSLoc.LanguageLabel.


  • Non supporté : classes et structs utilisateur autres qu’UdonSharpBehaviour, interfaces, génériques, delegate/event/lambdas, LINQ, try/catch, async, coroutines, tableaux [,], params, valeurs de paramètres par défaut, ref/out.
  • Délais : SendCustomEventDelayedSeconds("Method", t), jamais une coroutine.
  • Réseau : l’ownership est obligatoire avant d’écrire une variable [UdonSynced] ; SendCustomNetworkEvent n’est pas rejoué pour les arrivants tardifs — l’état doit être reconstructible depuis les variables synchronisées et OnDeserialization.
  • Budgets : Continuous ≈ 200 octets par comportement ; Manual est plus large mais rate-limité (≈ 1 sérialisation utile par seconde). Ne spammez jamais RequestSerialization().
  • Quest : surveillez le budget de draw calls, utilisez des shaders mobiles, évitez le post-traitement lourd.