using UdonSharp; using UnityEngine; using VRC.SDK3.Components; using VRC.SDKBase; [UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] [DisallowMultipleComponent] [AddComponentMenu("Artyom Scripting System/Pickup Following Mover Fix")] public class PickupMovementFixer : UdonSharpBehaviour { public GameObject targetObject; // The object to follow private int pickupLayer = 13; // The layer assigned to pickup objects private Collider triggerCollider; private Vector3 previousTargetPosition; private Vector3 targetVelocity; private bool carrying; // While the target moves, and for one frame after it stops private bool ready; private void Start() { triggerCollider = GetComponent(); if (triggerCollider == null || !triggerCollider.isTrigger) { Debug.LogError("[AUMSS] Pickup Following Mover Fix: Collider must be set as a trigger."); } if (targetObject == null) { Debug.LogError("[AUMSS] Pickup Following Mover Fix: Target Object isn't assigned."); enabled = false; return; } previousTargetPosition = targetObject.transform.position; ready = true; } // LateUpdate runs after Update scripts and Animators have moved the target this frame, // so the movement measured here matches this frame's deltaTime private void LateUpdate() { if (!ready) return; // Not set up (enabled = false only takes effect next frame) Vector3 currentTargetPosition = targetObject.transform.position; Vector3 newVelocity = (currentTargetPosition - previousTargetPosition) / Time.deltaTime; previousTargetPosition = currentTargetPosition; // A still target leaves the pickups to their own physics, so they can fall and slide. // The frame it stops still applies (zero), so the pickups stop with it instead of sliding off. bool moving = newVelocity.sqrMagnitude > 0.0001f; carrying = moving || targetVelocity.sqrMagnitude > 0.0001f; targetVelocity = newVelocity; } private void OnTriggerEnter(Collider other) { Carry(other); } private void OnTriggerStay(Collider other) { Carry(other); } private void Carry(Collider other) { if (!ready || !carrying || other == null || other.gameObject.layer != pickupLayer) return; VRC_Pickup pickup = other.GetComponent(); if (pickup == null || pickup.IsHeld) return; // A synced pickup is moved by its owner; anyone else setting its velocity just fights the sync if (other.GetComponent() != null && !Networking.IsOwner(other.gameObject)) return; Rigidbody pickupRigidbody = other.GetComponent(); if (pickupRigidbody != null) { pickupRigidbody.velocity = targetVelocity; } } }