瀏覽代碼

Fix 1.2.0: VRCStation ambiguity, players seated away from the trigger

79702cc didn't compile: VRCStation is in both VRC.SDK3.Components and VRC.SDKBase, so it's named in full.
A player seated in a station away from the trigger never got an exit, keeping the output on; while
anyone is inside, a once-a-second check now drops players whose feet and head are over a metre outside.
Tested in SmashHitVRC: 70/70.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ArtyomV2X 1 月之前
父節點
當前提交
833d528ee9
共有 2 個文件被更改,包括 50 次插入 和 4 次删除
  1. 2 2
      Documentation~/LinkedObjectSystem.md
  2. 48 2
      Runtime/LinkedObjectSystem.cs

+ 2 - 2
Documentation~/LinkedObjectSystem.md

@@ -46,7 +46,7 @@ Drives Animator bools, two audio objects, Udon scripts and a station from intera
 ## Station - Type: VRCStation
 - The player who turns the output on by interacting, or who walks into the trigger, is seated in this station. They leave it themselves; turning the output off doesn't eject anyone.
 - A station holds one player. For several seats, use a Linked Object System per seat.
-- A seated player may be moved out of the trigger, which counts as leaving it. Put the station outside the trigger, or use an interaction, if the output should stay on.
+- A player seated in a station more than a metre outside the trigger stops counting as inside it within a second, so the output can turn off while they ride. Put the station inside the trigger if the output should stay on.
 
 ---
 
@@ -54,7 +54,7 @@ Drives Animator bools, two audio objects, Udon scripts and a station from intera
 - The output is on while the interaction is on, **or** anyone or anything counted is inside the trigger.
 - Interactions are synced: everyone sees them, including players who join later.
 - Script events and the station follow the output: events on every change, the station when the local player turns it on or walks in.
-- Triggers aren't synced, because every player sees the same players and synced pickups enter. A player is only counted once, players who leave the instance stop counting, and pickups that are disabled or destroyed inside stop counting within a second.
+- Triggers aren't synced, because every player sees the same players and synced pickups enter. A player is only counted once, and players who leave the instance, or end up more than a metre away without walking out (seated in a station elsewhere), stop counting within a second. Pickups that are disabled or destroyed inside stop counting within a second too.
 
 ### Upgrading from 1.0.x:
 - Interactions used to show only for the player who interacted; now they're synced.

+ 48 - 2
Runtime/LinkedObjectSystem.cs

@@ -1,6 +1,5 @@
 using UdonSharp;
 using UnityEngine;
-using VRC.SDK3.Components;
 using VRC.SDKBase;
 using VRC.Udon;
 using VRC.Udon.Common.Interfaces;
@@ -44,7 +43,7 @@ public class LinkedObjectSystem : UdonSharpBehaviour
 
     [Header("Station\n")]
     [Tooltip("The player who turns this on by interacting, or who walks into the trigger, is seated here. They leave it themselves")]
-    public VRCStation station;
+    public VRC.SDK3.Components.VRCStation station; // Named in full: VRC.SDKBase has a VRCStation too
 
     // PRIVATE VARS
 
@@ -57,6 +56,8 @@ public class LinkedObjectSystem : UdonSharpBehaviour
     private bool pickupTriggerEnabled = false;
     private int[] playersInside = new int[100]; // Player IDs: a player can't be counted twice, and leaving the instance removes them
     private int playerCount;
+    private bool playerCheckScheduled;
+    private Collider triggerCollider;
     private Collider[] pickupsInside = new Collider[32];
     private int pickupCount;
     private bool pickupCheckScheduled;
@@ -66,6 +67,7 @@ public class LinkedObjectSystem : UdonSharpBehaviour
     {
         playerTriggerEnabled = triggerType == TriggerType.Player || triggerType == TriggerType.BothPlayerAndObject;
         pickupTriggerEnabled = triggerType == TriggerType.Object || triggerType == TriggerType.BothPlayerAndObject;
+        triggerCollider = GetComponent<Collider>();
         if (interactionType == InteractionType.None)
         {
             DisableInteractive = true;
@@ -142,10 +144,54 @@ public class LinkedObjectSystem : UdonSharpBehaviour
         playersInside[playerCount] = id;
         playerCount++;
         if (debugMode) Debug.Log("Linked Object System: players inside: " + playerCount.ToString());
+        if (!playerCheckScheduled)
+        {
+            playerCheckScheduled = true;
+            SendCustomEventDelayedSeconds(nameof(_CheckPlayers), 1f);
+        }
         UpdateOutput();
         if (player.isLocal) SeatLocalPlayer();
     }
 
+    /// <summary>
+    /// Drops players who left without an exit: seated in a station elsewhere, or gone from the instance. Runs every
+    /// second while any player is inside.
+    /// </summary>
+    public void _CheckPlayers()
+    {
+        playerCheckScheduled = false;
+        for (int i = playerCount - 1; i >= 0; i--)
+        {
+            VRCPlayerApi player = VRCPlayerApi.GetPlayerById(playersInside[i]);
+            if (!Utilities.IsValid(player) || !NearTrigger(player)) RemovePlayer(playersInside[i]);
+        }
+        if (playerCount > 0)
+        {
+            playerCheckScheduled = true;
+            SendCustomEventDelayedSeconds(nameof(_CheckPlayers), 1f);
+        }
+    }
+
+    /// <summary>
+    /// Whether the player's feet or head are within a metre of the trigger's bounds. Generous on purpose: a player
+    /// dropped here gets no new enter until they walk out and back in.
+    /// </summary>
+    private bool NearTrigger(VRCPlayerApi player)
+    {
+        if (triggerCollider == null) return true;
+        Bounds bounds = triggerCollider.bounds;
+        Vector3 center = bounds.center;
+        Vector3 reach = bounds.extents + Vector3.one;
+        return Within(player.GetPosition(), center, reach) ||
+               Within(player.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).position, center, reach);
+    }
+
+    private bool Within(Vector3 point, Vector3 center, Vector3 reach)
+    {
+        Vector3 d = point - center;
+        return Mathf.Abs(d.x) <= reach.x && Mathf.Abs(d.y) <= reach.y && Mathf.Abs(d.z) <= reach.z;
+    }
+
     public override void OnPlayerTriggerExit(VRCPlayerApi player)
     {
         if (!playerTriggerEnabled || player == null) return;