using System; using UnityEngine; using UnityEngine.InputSystem; public class PlayerPickupController : MonoBehaviour { [SerializeField] private InputActionAsset inputActions; [SerializeField] private string actionMapName = "Player"; [SerializeField] private string interactActionName = "Interact"; private InputActionAsset runtimeActions; private InputAction interactAction; [SerializeField] private string controlSchemeName = ""; [SerializeField] private int gamepadIndex = -1; private bool deviceFilterEnabled; [SerializeField] private float minThrowSpeed = 6f; [SerializeField] private float maxThrowSpeed = 20f; [SerializeField] private float throwChargeTime = 0.75f; [SerializeField] private float throwUpwardForce = 0f; [SerializeField] private float throwTriggerThreshold = 0.6f; [SerializeField] private float dropClearance = 0.2f; [SerializeField] private float holdToSpawnTime = 3f; [Header("Setup")] public Transform holdPoint; // Empty GameObject above head public float pickupRange = 2.0f; public GameObject heldObject; private Rigidbody2D heldBody; private float heldBodyMass; private float heldBodyGravity; private RigidbodyType2D heldBodyType; private bool heldBodySimulated; private PlayerPlatformerController platformerController; private PlayerStressController playerStressController; private bool isChargingThrow; private float throwChargeStartTime; private float holdStartTime; private bool spawnedDuringHold; private float nextHoldSpawnTime; private void OnEnable() { platformerController = GetComponent(); playerStressController = GetComponent(); if (inputActions == null) { Debug.LogError("PlayerPickupController missing InputActionAsset reference.", this); return; } runtimeActions = Instantiate(inputActions); if (!string.IsNullOrWhiteSpace(controlSchemeName)) { runtimeActions.bindingMask = InputBinding.MaskByGroup(controlSchemeName); } deviceFilterEnabled = ShouldFilterDevices(); ApplyDeviceFilter(); InputSystem.onDeviceChange += HandleDeviceChange; var map = runtimeActions.FindActionMap(actionMapName, true); interactAction = map.FindAction(interactActionName, true); if (interactAction != null) { interactAction.Enable(); interactAction.performed += OnInteractPerformed; } } private void OnDisable() { if (interactAction != null) { interactAction.performed -= OnInteractPerformed; interactAction.Disable(); } if (runtimeActions != null) { runtimeActions.Disable(); runtimeActions = null; } InputSystem.onDeviceChange -= HandleDeviceChange; platformerController = null; } private void OnInteractPerformed(InputAction.CallbackContext context) { if (playerStressController.isPanicking) return; if (heldObject != null) { DropObject(false); return; } TryPickUp(); } private void Update() { if (heldObject == null) { return; } if(playerStressController.isPanicking) { DropObject(false); return; } if (!heldObject.TryGetComponent(out _)) { return; } if (Time.time >= nextHoldSpawnTime) { if (heldObject == null) { return; } if (!TrickController.TrySpawnFromPrefabAt(heldObject.transform.position)) { TrickController.TrySpawnAt(heldObject.transform.position); } spawnedDuringHold = true; nextHoldSpawnTime = Time.time + holdToSpawnTime; } var gamepad = GetGamepad(); if (gamepad == null) { return; } var triggerValue = gamepad.rightTrigger.ReadValue(); if (triggerValue >= throwTriggerThreshold) { if (!isChargingThrow) { isChargingThrow = true; throwChargeStartTime = Time.time; } } else if (isChargingThrow) { DropObject(true); } } void TryPickUp() { // Find all colliders within range Collider2D[] colliders = Physics2D.OverlapCircleAll(transform.position, pickupRange); foreach (var col in colliders) { if (col.gameObject == gameObject) { continue; } var hasTrick = col.TryGetComponent(out var trick); var hasPlayer = col.TryGetComponent(out var playerController); if (hasPlayer) continue; if (!hasTrick && !hasPlayer && !col.CompareTag("Interactable")) { continue; } heldObject = col.gameObject; holdStartTime = Time.time; spawnedDuringHold = false; nextHoldSpawnTime = holdStartTime + holdToSpawnTime; // Parent the object to the HoldPoint and reset its local position heldObject.transform.SetParent(holdPoint); heldObject.transform.localPosition = Vector3.zero; heldObject.transform.localRotation = Quaternion.identity; if (trick != null) { trick.SetCarried(true); } // Disable physics so it doesn't fall or collide with the player if (heldObject.TryGetComponent(out Rigidbody2D rb)) { heldBody = rb; heldBodyMass = rb.mass; heldBodyGravity = rb.gravityScale; heldBodyType = rb.bodyType; heldBodySimulated = rb.simulated; rb.simulated = false; rb.bodyType = RigidbodyType2D.Kinematic; rb.gravityScale = 0f; rb.linearVelocity = Vector2.zero; rb.angularVelocity = 0f; } if (playerController != null) { playerController.enabled = false; if (heldObject.TryGetComponent(out var heldPickup)) { heldPickup.enabled = false; } if (heldObject.TryGetComponent(out var heldBody)) { heldBody.linearVelocity = Vector2.zero; heldBody.angularVelocity = 0f; } } if (col != null) col.enabled = false; break; } } public void DropObject(bool applyThrow) { // Re-enable physics if (heldBody != null) { heldBody.simulated = heldBodySimulated; heldBody.bodyType = heldBodyType; heldBody.gravityScale = heldBodyGravity; heldBody.mass = heldBodyMass; heldBody.linearVelocity = Vector2.zero; heldBody.angularVelocity = 0f; } if (heldObject.TryGetComponent(out var trick)) { trick.SetCarried(false); if (applyThrow) { trick.OnThrown(); } } if (heldObject.TryGetComponent(out var playerController)) { playerController.enabled = true; if (heldObject.TryGetComponent(out var heldPickup)) { heldPickup.enabled = true; } } if (applyThrow && heldBody != null) { var direction = 1f; if (platformerController != null && Mathf.Abs(platformerController.LastMoveX) > 0.01f) { direction = Mathf.Sign(platformerController.LastMoveX); } var charge01 = 1f; if (isChargingThrow) { var heldTime = Time.time - throwChargeStartTime; charge01 = Mathf.Clamp01(heldTime / Mathf.Max(0.01f, throwChargeTime)); } var throwSpeed = Mathf.Lerp(minThrowSpeed, maxThrowSpeed, charge01); var upwardSpeed = throwUpwardForce * charge01; heldBody.linearVelocity = new Vector2(direction * throwSpeed, upwardSpeed); heldBody.angularVelocity = 0f; } heldObject.transform.position += Vector3.up * dropClearance; Collider2D col = heldObject.GetComponent(); if (col != null) col.enabled = true; // Unparent heldObject.transform.SetParent(null); heldObject = null; heldBody = null; isChargingThrow = false; spawnedDuringHold = false; nextHoldSpawnTime = 0f; } private Gamepad GetGamepad() { if (runtimeActions == null) { return null; } var devices = runtimeActions.devices; if (devices != null) { foreach (var device in devices) { if (device is Gamepad gamepad) { return gamepad; } } } return Gamepad.current; } private bool ShouldFilterDevices() { return string.Equals(controlSchemeName, "Gamepad", StringComparison.OrdinalIgnoreCase) || gamepadIndex >= 0; } private void ApplyDeviceFilter() { if (!deviceFilterEnabled || runtimeActions == null) { return; } var targetIndex = ResolveGamepadIndex(); if (targetIndex < 0) { return; } if (Gamepad.all.Count <= targetIndex) { runtimeActions.devices = Array.Empty(); return; } runtimeActions.devices = new InputDevice[] { Gamepad.all[targetIndex] }; } private void HandleDeviceChange(InputDevice device, InputDeviceChange change) { if (!deviceFilterEnabled) { return; } if (change == InputDeviceChange.Added || change == InputDeviceChange.Removed || change == InputDeviceChange.Reconnected) { ApplyDeviceFilter(); } } private int ResolveGamepadIndex() { if (gamepadIndex >= 0) { return gamepadIndex; } if (string.IsNullOrWhiteSpace(actionMapName)) { return -1; } if (string.Equals(actionMapName, "Player", StringComparison.OrdinalIgnoreCase)) { return 0; } var end = actionMapName.Length - 1; var start = end; while (start >= 0 && char.IsDigit(actionMapName[start])) { start--; } start++; if (start > end) { return -1; } if (!int.TryParse(actionMapName.Substring(start, end - start + 1), out var parsed)) { return -1; } return parsed > 0 ? parsed - 1 : -1; } }