FOS UI Culling
Version 1.0.1 · exige FOS Essentials Core 1.0.0 ou plus récent
Désactive les UI du monde quand le joueur local est hors de portée, depuis un unique manager central. Exige FOS Essentials Core 1.0.0.
Mise en place
Section intitulée « Mise en place »- Créez un GameObject vide et ajoutez FOS Essentials > UI Culling > UI Culling Manager.
- Cliquez sur Scan the scene. Chaque canvas World Space reçoit un
FOSUICullingTarget, sonCanvaset sonGraphicRaycastersont câblés, et la liste du manager est remplie. - Réglez Default distance. Surchargez-la UI par UI dans le composant cible, au besoin.
Relancez le scan après avoir ajouté des UI. Les cibles existantes ne sont jamais écrasées : tout réglage manuel survit.
Composants
Section intitulée « Composants »| Composant | Rôle |
|---|---|
FOSUICullingManager |
Un par scène. Possède la boucle et pilote tout. |
FOSUICullingTarget |
Un par UI. Ne porte que des réglages — aucun travail par frame. |
| Option | Effet |
|---|---|
| Default distance | Mètres en deçà desquels une UI est affichée, sauf surcharge par la cible. |
| Show distance (cible) | Surcharge par UI. -1 signifie « hériter de la valeur du manager ». |
| Distance reference (cible) | Point depuis lequel la distance est mesurée. Vide signifie le Canvas lui-même. |
| Hysteresis | Mètres supplémentaires avant de masquer à nouveau. Empêche une UI de clignoter quand le joueur se tient sur la limite. |
| Checks per frame | Nombre d’UI testées à chaque image. 4 par défaut. |
| Line of sight | Passe d’occlusion optionnelle. Désactivée par défaut. |
| Obstacle layers | Layers considérés comme bloquants pour la passe d’occlusion. |
Comment une UI détermine sa distance
Section intitulée « Comment une UI détermine sa distance »Deux niveaux, résolus une fois au démarrage du manager :
- La Default distance du manager s’applique à toutes les UI.
- La Show distance d’une cible la surcharge — sauf si elle vaut
-1, ce qui signifie hériter de la valeur du manager.
float distance = target.showDistance;if (distance < 0f) distance = defaultShowDistance;Une scène avec Default distance = 15 et une cible réglée sur 8 affiche donc cette UI à
8 mètres et toutes les autres à 15.
L’inspecteur de la cible énonce le résultat — « Effective distance: 15 m (inherited from
the manager) » — pour que vous n’ayez jamais à vous rappeler ce que veut dire -1.
Comme la valeur est lue une seule fois au Start, changer une distance en cours
d’exécution demande d’appeler RefreshTargets() sur le manager.
Depuis quel point la distance est mesurée
Section intitulée « Depuis quel point la distance est mesurée »Depuis le transform du Canvas, c’est-à-dire, pour un panneau, son pivot, en général son centre. Un panneau large apparaît donc en fonction de son centre plutôt que de son bord le plus proche.
Quand cela compte, glissez n’importe quel Transform dans Distance reference sur la cible — un enfant vide placé au bord, par exemple — et la mesure se fait depuis lui.
Notes de conception
Section intitulée « Notes de conception »Pourquoi un manager central plutôt qu’un script par UI
Section intitulée « Pourquoi un manager central plutôt qu’un script par UI »Udon est une machine virtuelle interprétée, environ cent fois plus lente que le C#. Le coût dominant est le nombre de comportements qui tournent, pas l’arithmétique qu’ils font. Un comportement comparant trente distances au carré coûte bien moins cher que trente comportements faisant quoi que ce soit.
FOSUICullingTarget ne porte donc que de la configuration. Le manager la lit une fois au
Start, la met en cache dans des tableaux plats, puis ne touche plus que directement aux
composants Unity. Trente UI coûtent exactement un comportement actif.
Pourquoi la distance et pas un raycast
Section intitulée « Pourquoi la distance et pas un raycast »Un rayon répond à « quelque chose fait-il obstacle », pas à « suis-je proche ». Il ne peut pas remplacer le test de distance — il en faudrait un d’abord de toute façon. Le raycast a un vrai usage ici, mais en seconde étape : voir Line of sight plus bas.
Pourquoi la distance et pas des colliders de trigger
Section intitulée « Pourquoi la distance et pas des colliders de trigger »Les volumes de trigger sont réellement bon marché — événementiels, avec la phase large gérée en code natif. Ils perdent sur trois points pratiques :
- un collider doit être dimensionné pour chaque UI, ce qui ruine la configuration automatique ;
OnPlayerTriggerEnterne se déclenche que sur le GameObject qui porte le collider, ce qui impose un comportement par UI pour le recevoir ;- la capsule du joueur doit réellement chevaucher le volume. Une UI haut sur un mur avec un petit trigger est manquée, et une erreur de matrice de layers échoue en silence.
Ce qui est réellement désactivé
Section intitulée « Ce qui est réellement désactivé »Le Canvas, le raycaster et les colliders — et non SetActive(false).
- Désactiver le
Canvasarrête le rendu immédiatement tout en gardant la hiérarchie vivante. - Désactiver les colliders est ce qui arrête le pointeur de VRChat. Celui-là n’est pas optionnel : voir plus bas.
- Désactiver le
GraphicRaycastersort l’UI de la résolution des événements d’interface d’Unity. SetActive(false)forcerait une reconstruction complète de la mise en page au retour et relancerait tous lesOnEnable, Udon compris, ce qui peut réinitialiser l’état de scripts sans rapport.
Pourquoi les colliders comptent plus que le raycaster
Section intitulée « Pourquoi les colliders comptent plus que le raycaster »Le pointeur de VRChat ne passe pas par le GraphicRaycaster. Son propre raycaster
effectue un raycast physique, puis cherche un VRC_UiShape sur le collider touché :
int hitCount = Physics.RaycastNonAlloc(...); // touche des COLLIDERSVRC_UiShape shape = hitObject.GetComponent<VRC_UiShape>();Désactiver le seul Canvas masque donc l’UI en laissant son collider en place. Le
pointeur continue de s’y accrocher, et ses boutons restent cliquables — une UI
invisible qui réagit encore. C’est un bug fonctionnel, pas cosmétique.
Le scan remplit automatiquement la liste Colliders depuis le GameObject du canvas. Si une UI a des colliders ailleurs, ajoutez-les à la main ; l’inspecteur de la cible avertit quand il trouve des colliders non listés.
Colliderne dérive pas deBehaviour— il dérive directement deComponent, avec sa propre propriétéenabled. C’est pourquoi les colliders ont leur propre liste plutôt que de figurer dans Extra components.
La ligne de vue
Section intitulée « La ligne de vue »Une fois activée, une UI déjà à portée est masquée si un obstacle bouche la vue. Un
Physics.Linecast par UI vérifiée, lancé depuis la tête du joueur — depuis les pieds, il
raserait le sol et accrocherait la moindre bosse.
Elle ne s’exécute que pour les UI ayant déjà passé le test de distance : une UI à deux cents mètres ne coûte donc jamais un rayon. Laissez-la tout de même désactivée sauf si le monde en a réellement besoin.
L’étalement
Section intitulée « L’étalement »Les cibles sont vérifiées à tour de rôle, Checks per frame à la fois. Avec 4 vérifications et 40 UI, la latence dans le pire des cas est de 10 images — moins d’un cinquième de seconde, invisible pour une UI qui apparaît au loin.
La première image fait exception : tout est évalué d’un coup, sans quoi les UI lointaines resteraient allumées jusqu’à ce que le curseur ait bouclé un tour complet.
La visibilité n’est écrite que lorsqu’elle change : une scène stable n’effectue donc aucune écriture de composant.
Notes et limites
Section intitulée « Notes et limites »- Un manager par scène. Plusieurs managers se disputeraient les mêmes canvas ; l’inspecteur le signale comme une erreur.
- Les canvas imbriqués sont ignorés. Désactiver le canvas racine empêche déjà ses enfants de s’afficher : gérer une cible par niveau ne ferait qu’encombrer la liste.
- Les canvas Screen Space sont ignorés par défaut : ils ne sont affichés à personne dans un monde VRChat.
- Les UI créées à l’exécution ne sont pas prises en compte automatiquement.
Remplissez
targetset appelezRefreshTargets(). ShowAll()rallume tout pour du débogage, sans retirer le manager.
