using UdonSharp; using UnityEngine; using VRC.SDK3.Components; using VRC.SDKBase; [UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)] [DisallowMultipleComponent] [AddComponentMenu("Artyom Scripting System/Jump Pad Controller")] public class JumpPad : UdonSharpBehaviour { public bool debug; public Animator launchAnim; public string animName = "isLaunch"; public Transform jumpTarget; public Transform self; public float arcTime = 1.2f; public float walkLimit = 0.2f; public float runLimit = 0.4f; public float strafeLimit = 0.2f; private Vector3 appliedVelocity; private float defaultWalk; private float defaultStrafe; private float defaultRun; private VRCPlayerApi player; private bool ready; private int localLaunches; // Launches of the local player still in the air private int animLaunches; // Launches of anyone still holding the launch animation on void Start() { // The inspector's Animator wins; only fall back to one on this object if (launchAnim == null) launchAnim = GetComponent(); if (self == null) self = transform; if (jumpTarget == null) { Debug.LogError("[AUMSS] Jump Pad Controller: Jump Target isn't assigned, so the pad won't launch anything."); return; } ready = true; if (debug) Debug.Log("Jump Pad Controller: Velocity Calculated to:" + CalculateInitialVelocity(self.position, jumpTarget.position, Physics.gravity.y, arcTime).ToString()); } public static Vector3 CalculateInitialVelocity(Vector3 origin, Vector3 target, float gravity, float time) { // Compute the differences in position Vector3 displacement = target - origin; // Calculate the initial velocity components Vector3 initialVelocity = new Vector3( displacement.x / time, // Horizontal X velocity (displacement.y - 0.5f * gravity * time * time) / time, // Vertical Y velocity displacement.z / time // Horizontal Z velocity ); return initialVelocity; } private float LaunchDuration() { return Mathf.Max(arcTime - 0.2f, 0f); } public override void OnPlayerTriggerEnter(VRCPlayerApi collidedPlayer) { if (!ready) return; StartLaunchAnim(); SendCustomEventDelayedSeconds(nameof(EndLaunchAnim), LaunchDuration()); // Remote players get launched by their own client, and speed/velocity calls on them throw and halt this script if (!collidedPlayer.isLocal) return; player = collidedPlayer; // Only save the speeds when not already mid-launch: mid-launch they're the limits, and would be kept forever if (localLaunches == 0) { defaultRun = player.GetRunSpeed(); defaultStrafe = player.GetStrafeSpeed(); defaultWalk = player.GetWalkSpeed(); } localLaunches++; // Players fall with the world's gravity scaled by their own gravity strength appliedVelocity = CalculateInitialVelocity(self.position, jumpTarget.position, Physics.gravity.y * player.GetGravityStrength(), arcTime); LimitMobility(); player.SetVelocity(appliedVelocity); if (debug) Debug.Log("Jump Pad Controller: Player Velocity Set to:" + appliedVelocity.ToString()); SendCustomEventDelayedSeconds(nameof(Mobilize), LaunchDuration()); } private void OnTriggerEnter(Collider other) { if (!ready || other == null) return; VRC_Pickup pickup = other.GetComponent(); Rigidbody body = other.GetComponent(); if (pickup == null || body == null) return; // A synced pickup is launched by its owner; anyone else setting its velocity just fights the sync if (other.GetComponent() != null && !Networking.IsOwner(other.gameObject)) return; appliedVelocity = CalculateInitialVelocity(self.position, jumpTarget.position, Physics.gravity.y, arcTime); pickup.Drop(); body.velocity = appliedVelocity; } public void LimitMobility() { if (!Utilities.IsValid(player)) return; player.SetWalkSpeed(walkLimit); player.SetRunSpeed(runLimit); player.SetStrafeSpeed(strafeLimit); if (debug) Debug.Log("Jump Pad Controller: Limited Player Mobility for " + arcTime.ToString() + " seconds"); } public void Mobilize() { if (localLaunches > 0) localLaunches--; if (localLaunches > 0 || !Utilities.IsValid(player)) return; // Still in the air from a later launch player.SetWalkSpeed(defaultWalk); player.SetRunSpeed(defaultRun); player.SetStrafeSpeed(defaultStrafe); if (debug) Debug.Log("Jump Pad Controller: De-Limited Player Mobility after" + arcTime.ToString() + " seconds"); } private void StartLaunchAnim() { animLaunches++; if (launchAnim == null || string.IsNullOrEmpty(animName)) return; launchAnim.SetBool(animName, true); if (debug) Debug.Log("Jump Pad Controller: Animator State attempted to be set to True"); } public void EndLaunchAnim() { if (animLaunches > 0) animLaunches--; if (animLaunches > 0) return; // Someone else launched since; their launch ends it if (launchAnim == null || string.IsNullOrEmpty(animName)) return; launchAnim.SetBool(animName, false); if (debug) Debug.Log("Jump Pad Controller: Animator State attempted to be set to False"); } }