Initial commit

This commit is contained in:
2025-12-10 21:06:22 +01:00
commit cc9190b9ce
476 changed files with 320218 additions and 0 deletions

View File

@@ -0,0 +1,249 @@
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon.Common;
using UdonSharp;
using VRC.Udon.Wrapper.Modules;
namespace Superbstingray
{
[UdonBehaviourSyncMode(BehaviourSyncMode.NoVariableSync)]
public class UdonPlatformHook : UdonSharp.UdonSharpBehaviour
{
[Tooltip("Layers that the Player will move with.")]
public LayerMask hookLayerMask;
[Tooltip("Vertical distance between the Player and Platform before the Script Unhooks from Colliders. You may want to increase this value if your world has a higher than average jump impulse.")]
public float hookDistance = 0.75F;
[Tooltip("Will make the Player keep their Velocity + the Platforms Velocity when they detach from it.")]
public bool inheritVelocity = true;
[Tooltip("Will Immobilize(true) players when standing still on moving platforms to prevent Avatars from having IK drift / IK walk.")]
public bool reduceIKDrift = true;
[Tooltip("Try Detect if the Player has their Main Menu open and stop moving them. Will help with menu interactions.")]
public bool mainMenuPause = true;
[Tooltip("Try Detect if the Player has their Quick Menu open and stop moving them. Will help with menu interactions.")]
public bool quickMenuPause = false;
BoxCollider platformOverride;
Collider[] colliderArray;
Collider sceneCollider;
Quaternion headRotation;
RaycastHit hitInfo;
Transform PlayerOffset, playerTracker, hook;
Vector3 playerVelocity, hookLastPos, hookOffsetPos, lastFramePos;
VRCPlayerApi localPlayer;
bool menuOpen;
float InputMoveH, InputMoveV;
int unhookThreshold, localColliders, intUI;
[FieldChangeCallback(nameof(isHookedCallback))]
bool isHooked;
bool isHookedCallback
{ set
{ isHooked = value;
if (isHooked) // isHooked=true functions
{
hook.localPosition = Vector3.zero; hook.eulerAngles = Vector3.zero;
platformOverride.enabled = true;
PlayerOffset.SetPositionAndRotation(hook.position, hook.rotation);
// When hooking count the number of PlayerLocal colliders the player has
// as this will help us know when the Player enters a station.
localColliders = Mathf.Clamp(Physics.OverlapSphere((localPlayer.GetPosition()), 1024f, 1024).Length, 1, 100);
}
else // isHooked=false functions
{
if(inheritVelocity) { localPlayer.SetVelocity(playerVelocity); } // Sets the players velocity to their actual worldspace velocity when they Unhook.
OverridesOff();
hook.localPosition = Vector3.zero; hook.eulerAngles = Vector3.zero;
PlayerOffset.SetPositionAndRotation(hook.position, hook.rotation);
}
}
}
void Start()
{
InitializeVariables();
IgnoreSceneCollision();
}
void FixedUpdate()
{
FixedUpdateFunctions();
}
void LateUpdate()
{
LateUpdateFunctions();
}
void FixedUpdateFunctions()
{
if (!VRC.SDKBase.Utilities.IsValid(localPlayer)) { return; }
if (isHooked && inheritVelocity) // Average the last X frames of the players global velocity.
{
playerVelocity = Vector3.ClampMagnitude((playerVelocity * 3f + (localPlayer.GetPosition() - lastFramePos) / Time.deltaTime) / 4f, 100f);
lastFramePos = localPlayer.GetPosition();
}
if (!menuOpen)
{
// Override_Spherecast. Set position of override collider.
Physics.SphereCast(localPlayer.GetPosition() + new Vector3(0F, .3f, 0f), 0.25f, Vector3.down, out hitInfo, 10f, hookLayerMask.value);
platformOverride.center = hitInfo.point;
// FixedUpdate_Spherecast. Check for valid platforms.
// Add to the unhookThreshold if it misses a valid platform and unhook if unhookThreshold is greater than X.
if (!Physics.SphereCast(localPlayer.GetPosition() + new Vector3(0f, .3f, 0f), 0.25f, Vector3.down, out hitInfo, hookDistance + .3f, hookLayerMask.value))
{
unhookThreshold++;
if (unhookThreshold > 10 && isHooked)
{
hook.parent = transform;
SetProgramVariable(nameof(isHooked), false);
SendCustomEventDelayedSeconds(nameof(OverridesOff), 0.5f);
}
}
}
}
void LateUpdateFunctions()
{
if (!VRC.SDKBase.Utilities.IsValid(localPlayer)) { return; }
#if !UNITY_EDITOR
if (isHooked) // Count the number of InterntalUI colliders as a means to know if the menu is open or not.
{
intUI = Physics.OverlapSphereNonAlloc(localPlayer.GetPosition(), 10f, colliderArray, 524288);
menuOpen = (mainMenuPause && (intUI >= 7 && intUI <= 14)) || (quickMenuPause && (intUI >= 15 && intUI <= 19));
}
if (isHooked && !menuOpen) // Move the parented hook to the Players position
{
hookLastPos = hook.position;
hook.position = localPlayer.GetPosition();
hookOffsetPos = hook.position - hookLastPos;
}
if (isHooked && menuOpen) { localPlayer.SetVelocity(Vector3.zero); } // Override Player Velocity to make it easier to use their menu.
// Teleport the player to the new offset position only if the players PlayerLocal collider count
// didn't decrease otherwise assume the player entered a station.
if (isHooked && !menuOpen)
{
playerTracker.SetPositionAndRotation(localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Origin).position, localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Origin).rotation);
PlayerOffset.SetPositionAndRotation(hook.position, hook.rotation);
if (Physics.OverlapSphere(localPlayer.GetPosition(), 1024f, 1024).Length >= localColliders)
{
localPlayer.TeleportTo(playerTracker.position - hookOffsetPos, playerTracker.rotation, VRC_SceneDescriptor.SpawnOrientation.AlignRoomWithSpawnPoint, true);
}
}
#else
// "EditorOnly" duplicate funtion from above specifically for CyanEmu/ClientSim as Origin tracking does not behave the same in editor as in client.
if (isHooked)
{
playerTracker.SetPositionAndRotation(localPlayer.GetPosition(), localPlayer.GetRotation());
PlayerOffset.SetPositionAndRotation(hook.position, hook.rotation);
if (Physics.OverlapSphere(localPlayer.GetPosition(), 1024f, 1024).Length >= localColliders)
{
localPlayer.TeleportTo(playerTracker.position - hookOffsetPos, playerTracker.rotation, VRC_SceneDescriptor.SpawnOrientation.AlignPlayerWithSpawnPoint, true);
hookOffsetPos = Vector3.zero;
}
}
#endif
// Players in Desktop or 3 Point tracking will have their Inverse Kinematics drag behind and "IK Walk" while being moved when they aren't intentionally locomoting.
// This function is to prevent that from occuring by Immobilizing the player when they are hooked to a platform and aren't moving relative to the platform.
// This is scuffed and I should optimize it.
if(reduceIKDrift && isHooked)
{
headRotation = localPlayer.GetTrackingData(VRCPlayerApi.TrackingDataType.Head).rotation;
localPlayer.Immobilize(!(InputMoveH * 0.1f + InputMoveV != 0f)
&& Mathf.Abs(playerVelocity.x) + Mathf.Abs(playerVelocity.z) > .1f
&& Mathf.Abs(localPlayer.GetVelocity().x) + Mathf.Abs(localPlayer.GetVelocity().z) < .01f
&& Quaternion.Angle(new Quaternion(0f, headRotation.y, 0f, headRotation.w).normalized, localPlayer.GetRotation()) < 90f);
}
// LateUpdate_Spherecast. Check for valid platforms.
if (Physics.SphereCast(localPlayer.GetPosition() + new Vector3(0f, .3f, 0f), 0.25f, Vector3.down, out hitInfo, hookDistance + .3f, hookLayerMask.value))
{
unhookThreshold = 0;
if (unhookThreshold < 10 && (localPlayer.IsPlayerGrounded())) // Hook to the valid platform if the Player is grounded.
{
hook.parent = hitInfo.transform;
SetProgramVariable(nameof(isHooked), true);
}
}
}
// INPUT EVENTS: MoveVertical, MoveHorizontal, InputJump.
public override void InputMoveVertical(float Value, UdonInputEventArgs InputMoveVerticalArgs)
{
InputMoveV = InputMoveVerticalArgs.floatValue;
}
public override void InputMoveHorizontal(float Value, UdonInputEventArgs InputMoveHorizontalArgs)
{
InputMoveH = InputMoveHorizontalArgs.floatValue;
}
public override void InputJump(bool outputJumpBool, UdonInputEventArgs inputJumpArgs)
{
if (reduceIKDrift) { localPlayer.Immobilize(false); }
}
// Reset prefab state and call unhook on Respawn.
public override void OnPlayerRespawn(VRCPlayerApi onPlayerRespawnPlayer)
{
hook.parent = transform;
unhookThreshold = System.Int32.MaxValue;
localPlayer.SetVelocity(Vector3.zero);
SetProgramVariable(nameof(isHooked), false);
OverridesOff();
}
// The prefab uses an additional overriding collider to prevent the VRC player controller from being affected by moving world
// colliders which can affect locomotion and animations, and this is to prevent the override collider from interacting with other colliders in the scene.
public void IgnoreSceneCollision()
{
SendCustomEventDelayedSeconds(nameof(IgnoreSceneCollision), 60f);
colliderArray = Physics.OverlapSphere(Vector3.zero, System.Single.MaxValue);
for (int i = 0; i < colliderArray.Length; i++)
{
sceneCollider = colliderArray[i];
if (VRC.SDKBase.Utilities.IsValid(sceneCollider))
{
Physics.IgnoreCollision(sceneCollider, platformOverride);
}
}
}
void InitializeVariables() // Set Prefab Variables
{
localPlayer = Networking.LocalPlayer;
if (!VRC.SDKBase.Utilities.IsValid(localPlayer)) { return; }
lastFramePos = localPlayer.GetPosition();
playerTracker = transform.GetChild(0).GetChild(0);
PlayerOffset = transform.GetChild(0);
hook = transform.GetChild(1);
platformOverride = transform.GetComponent<BoxCollider>();
platformOverride.size = new Vector3(0.5f, 0.05f, 0.5f);
transform.position = Vector3.zero;
if (hookLayerMask.value == -1) // Override if using "Everything" as a layermask to Everything -PlayerLocal,-MirrorReflection to prevent interference with the prefabs functionality.
{
hookLayerMask.value = -263369;
}
}
// Disable Override Collider and set Immobilize state false.
void OverridesOff()
{
if (!localPlayer.IsPlayerGrounded()) { platformOverride.enabled = false; }
if (reduceIKDrift) { localPlayer.Immobilize(false); }
}
}
}