Ir al contenido

FOS Essentials Core

Versión 1.4.0

Base común de todas las herramientas FOS Essentials para VRChat. Unity 2022.3.22f1 · VRChat SDK Worlds 3.10.4 · UdonSharp 1.x


Lado Elemento Función
Runtime FOSBehaviour Clase base de todo comportamiento FOS: registros, propiedad, sincronización
Runtime FOSPlayerRegistry Lista de jugadores refrescada al entrar y salir, sin asignación por fotograma
Runtime FOSToolAttribute Permite que una herramienta se declare al Hub
Runtime FOSSectionAttribute Sustituto traducible de [Header]
Runtime FOSVersion Constantes de identidad y versión (uso solo en el editor)
Editor FOSBrandingSettings Logo, color, URL de Discord / tienda / web / documentación — un solo asset
Editor FOSDefaultInspector Banner FOS automático en todo lo que derive de FOSBehaviour
Editor FOSInspectorBase Base de un inspector a medida que conserva el banner
Editor FOSLocalizedInspector Etiquetas, descripciones emergentes y títulos de sección traducidos
Editor FOSLoc Búsqueda de traducciones
Editor FOSHubWindow Tools > FOS Essentials > Hub: entorno, herramientas instaladas, enlaces
Editor FOSProgramAssets Genera los UdonSharpProgramAsset que falten (ver abajo)
Editor FOSCoreBootstrap Registra cada ensamblado runtime de FOS en UdonSharp
Llamada Efecto
FOS Essentials > Hub Ventana de estado: entorno, prefabs, herramientas, enlaces
FOS Essentials > <Pack> > <Prefab> Coloca ese prefab en la escena abierta
FOS Essentials > Generate Missing Program Assets Crea los archivos .asset que falten
FOS Essentials > Rebuild Prefab Menu Regenera las entradas de menú de cada pack
FOS Essentials > Check event wiring Lista los controles de interfaz cuyo evento no corresponde a ningún método
FOSPrefabCatalog.Scan() Prefabs expuestos por cada pack, agrupados por pack
FOSPrefabCatalog.InstantiateInScene(path) Coloca un prefab en la escena abierta
FOSLoc.T("clave") Texto traducido; recurre al inglés y luego a la clave
FOSLoc.Format("clave", args) Igual, a través de string.Format
FOSLocalizedInspector.Draw(so, type) Inspector traducido, sustituye a DrawDefaultInspector()
FOSProgramAssets.GenerateMissing() Como la entrada de menú, desde código. Devuelve cuántos se crearon
FOSProgramAssets.FindMissing() Tipos que aún no tienen program asset
FOSCoreBootstrap.EnsureUdonSharpAssemblyRegistered() Registra cada asmdef runtime de FOS en UdonSharp
FOSBranding.Settings Acceso al branding — nunca devuelve null
FOSHeader.Draw(name, version) Dibuja el banner dentro de una ventana a medida

  1. Abre Unity y deja que termine de compilar.
  2. El Core crea por su cuenta lo siguiente:
    • Assets/FOS/<Herramienta>/Runtime/FOS.<Herramienta>.Runtime.asset — registro de UdonSharp, para cada pack de FOS
    • Assets/FOS/Core/Editor/Branding/FOSBrandingSettings.asset — el branding
    • un UdonSharpProgramAsset junto a cada comportamiento FOS concreto
  3. Abre FOS Essentials > Hub y comprueba que todos los indicadores estén en verde.
  4. Desde el Hub, pulsa Open branding settings y rellena el logo, la URL de Discord y la URL de la tienda. Mientras una URL esté vacía, su botón permanece desactivado en lugar de abrir un enlace muerto.

El Hub también lista todos los prefabs que traen los packs instalados, con un botón cada uno, y los coloca en la escena abierta, delante de la vista de escena.


Cada prefab encontrado bajo Assets/FOS/<Pack>/Prefabs/ recibe además su propia entrada en FOS Essentials > <Pack> >, de modo que se puede añadir un prefab sin abrir el Hub.

[MenuItem] es un atributo y por tanto está congelado en tiempo de compilación: esas entradas no pueden construirse a partir de un escaneo de carpetas en ejecución. FOSPrefabMenuGenerator las escribe en Assets/FOS/Core/Editor/Generated/FOSPrefabMenu.generated.cs, usando únicamente API pública — Unity sí expone un Menu.AddMenuItem dinámico, pero es interno y no está documentado, y eso no es algo que se entregue en un asset vendido.

El archivo se regenera al cargar el editor, cuando se añade, mueve o borra un prefab bajo una carpeta Prefabs/, y desde Rebuild Prefab Menu. Solo se reescribe si su contenido difiere de verdad: de lo contrario, escribirlo dispararía una compilación, que dispararía la generación, que volvería a escribirlo.

No edites el archivo generado: se sobrescribe. Añade o renombra prefabs en su lugar.

Un pack vendido como Standard, Pro o en versión a medida pone cada edición en su propia carpeta:

Assets/FOS/<Pack>/<Edición>/Prefabs/

Ambas disposiciones se reconocen, y un pack con una sola edición no cambia nada. La edición se convierte en un submenú de verdad — FOS Essentials > FOS Tablet System > Standard > … — y en su propio bloque dentro del Hub.

Un prefab colocado en cualquier otro sitio sigue siendo invisible para ambos. Esa es toda la regla de descubrimiento: Prefabs/ está o bien directamente bajo el pack, o bien una carpeta más abajo.


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(); // la propiedad es obligatoria antes de escribir una variable sincronizada
_isOn = !_isOn;
Apply();
FOSSync(); // RequestSerialization(), pero solo si somos el propietario
}
public override void OnDeserialization()
{
Apply(); // repite el estado en los remotos Y en quienes llegan tarde
}
private void Apply()
{
target.SetActive(_isOn);
FOSLog("Switch: " + _isOn);
}
}
}

El banner con el logo y los botones de Discord / tienda aparece automáticamente: no hay que escribir ninguna clase de editor.

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

UdonSharp acepta solo un [CustomEditor] por tipo. Declarar dos registra un error y uno de ellos se ignora.

No llames aquí a DrawDefaultInspector(): pierde la traducción. base.OnFOSInspectorGUI() dibuja los campos mediante FOSLocalizedInspector.


Crear una herramienta nueva que dependa del Core

Sección titulada «Crear una herramienta nueva que dependa del Core»
Assets/FOS/<Herramienta>/
├── Runtime/
│ ├── FOS.<Herramienta>.Runtime.asmdef → referencias: FOS.Core.Runtime, UdonSharp.Runtime,
│ │ VRC.Udon, VRC.SDK3, VRC.SDKBase
│ ├── AssemblyInfo.cs → [assembly: FOSTool("FOS <Herramienta>", "1.0.0", ...)]
│ └── ...
├── Editor/ (opcional)
├── Prefabs/
└── README.md

El asmdef debe llamarse FOS.<Herramienta>.Runtime: así es como el Core lo encuentra y lo registra automáticamente en UdonSharp.

Una herramienta entregada en varias ediciones repite esa disposición una carpeta más abajo, y cada edición obtiene su ensamblado y su propia declaración [FOSTool]:

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

El nombre sigue teniendo que terminar en .Runtime, que es lo único que mira el escaneo del Core; la profundidad da igual. Cada edición aparece entonces en el Hub como una herramienta propia, con su propio número de versión.

AssemblyInfo.cs:

using FOS.Core;
[assembly: FOSTool(
"FOS Doors",
"1.0.0",
Description = "doors.tool.description", // una clave de traducción, resuelta por el Hub
MinimumCoreVersion = "1.0.0")]

La herramienta aparece entonces en el Hub con su versión, y el Hub avisa cuando el Core instalado es demasiado antiguo para ella. Description acepta tanto una clave de traducción como texto plano; es preferible la clave, para que el Hub pueda mostrarla en el idioma del usuario.


UdonSharp solo compila los scripts de Assembly-CSharp y los de ensamblados declarados por un asset UdonSharpAssemblyDefinition (CompilationContext.IsUdonSharpAssembly).

Un UdonSharpBehaviour situado en un asmdef no declarado se ignora en silencio: sin error, el componente se coloca en la escena, el inspector parece normal y el comportamiento no hace absolutamente nada en el juego.

FOSCoreBootstrap se encarga de esto para todos los packs: recorre Assets/FOS buscando asmdefs llamados FOS.*.Runtime y crea el asset de registro que falte. Una herramienta nueva no requiere por tanto ningún paso manual.

El Hub muestra el estado de ese registro, con un botón Repair now.


UdonSharp no genera los archivos UdonSharpProgramAsset. El compilador solo trata los scripts que ya tienen uno (UdonSharpCompilerV1, module.programAsset), y por eso cada script que viene con el SDK llega como pareja .cs + .asset. Sin su asset, un comportamiento no se puede añadir siquiera a un GameObject.

El Core se ocupa: FOSProgramAssets recorre los tipos que derivan de FOSBehaviour y crea el .asset que falte junto al .cs. Se ejecuta al cargar el editor y a petición desde el Hub o el menú.

Consecuencias prácticas:

  • Escribir un comportamiento FOS nuevo no requiere ningún paso manual. Deja que Unity compile y el asset aparece.
  • Las clases abstractas y genéricas se omiten deliberadamente: UdonSharp se niega a asociarles un program asset (Abstract U# behaviours cannot have an associated U# program asset). Por eso FOSBehaviour es abstracta.
  • Una clase debe llamarse exactamente como su archivo .cs; si no, la correspondencia falla y U# lanza UdonSharpBehaviour classes must have the same name as their containing .cs file.
  • Un archivo .asset existente nunca se sobrescribe.

La interfaz del editor está traducida al inglés, francés, español y alemán. El idioma se elige en el Hub o en los ajustes de branding; se guarda en EditorPrefs, así que es por máquina, y su valor por defecto es Auto (sigue el idioma de Unity y recurre al inglés).

  1. Añade la clave a las cuatro tablas de FOSCoreStrings.cs.
  2. Llámala con FOSLoc.T("mi.clave"), o FOSLoc.Format("mi.clave", arg) cuando el texto contenga un {0}.

Una clave ausente recurre al inglés y luego a la clave en bruto. Ver la clave en bruto en la interfaz es intencionado: una traducción que falta se detecta al instante en vez de pasar inadvertida.

Las etiquetas y las descripciones emergentes no vienen de [Tooltip] sino de las claves field.TipoQueDeclara.nombreDelCampo y tip.TipoQueDeclara.nombreDelCampo.

Lo que cuenta es el tipo que declara el campo, no el tipo inspeccionado: un campo de FOSBehaviour tiene por tanto una sola traducción, válida para todas las herramientas que deriven de él.

Los atributos [Tooltip] se escriben en inglés en el código: sirven de reserva y documentan el campo para quien lea las fuentes.

Para un título de sección, no uses [Header]: su texto queda congelado en compilación. Usa:

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

1. Toda cadena visible para el usuario debe poder traducirse, así que debe pasar por FOSLoc. 2. Lo que no puede traducirse se escribe en inglés.

Lo que no puede traducirse, y por tanto va en inglés

Sección titulada «Lo que no puede traducirse, y por tanto va en inglés»
Elemento Motivo
Nombres de variables, campos, métodos y clases Identificadores de C#
Namespaces, nombres de asmdef y de ensamblado Identificadores
Rutas de [MenuItem] y [AddComponentMenu] Unity las congela en compilación
Texto de reserva de los [Tooltip] Se usa cuando falta la clave
Segundo argumento de [FOSSection("clave", "Fallback")] Ídem
Registros de Udon en ejecución Udon no tiene Dictionary ni genéricos
Mensajes dirigidos al autor de una herramienta Se dirigen a quien programa
Nombres de archivo, de carpeta, claves de traducción Identificadores

El texto mostrado en el juego por una futura herramienta es otro mecanismo (arrays indexados del lado de Udon) y de momento no lo cubre el Core.

Añade un valor al final de la enumeración FOSLanguage — nunca en medio, los valores se escriben tal cual en los EditorPrefs del usuario — y luego una tabla en FOSCoreStrings y un caso en FOSLoc.LanguageLabel.


Recordatorios de UdonSharp válidos para todo el pack

Sección titulada «Recordatorios de UdonSharp válidos para todo el pack»
  • No soportado: clases y structs propios distintos de UdonSharpBehaviour, interfaces, genéricos, delegate/event/lambdas, LINQ, try/catch, async, corrutinas, arrays [,], params, valores por defecto en parámetros, ref/out.
  • Retardos: SendCustomEventDelayedSeconds("Method", t), nunca una corrutina.
  • Red: la propiedad es obligatoria antes de escribir una variable [UdonSynced]; SendCustomNetworkEvent no se repite para quienes llegan tarde — el estado debe poder reconstruirse a partir de las variables sincronizadas y de OnDeserialization.
  • Presupuestos: Continuous ≈ 200 bytes por comportamiento; Manual es más amplio pero está limitado en frecuencia (≈ 1 serialización útil por segundo). Nunca satures RequestSerialization().
  • Quest: vigila el presupuesto de draw calls, usa shaders móviles y evita el postprocesado pesado.