333 lines
9.6 KiB
C#
333 lines
9.6 KiB
C#
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;
|
|
|
|
[Header("Setup")]
|
|
public Transform holdPoint; // Empty GameObject above head
|
|
public float pickupRange = 2.0f;
|
|
|
|
private GameObject heldObject;
|
|
private PlayerPlatformerController platformerController;
|
|
private bool isChargingThrow;
|
|
private float throwChargeStartTime;
|
|
|
|
private void OnEnable()
|
|
{
|
|
platformerController = GetComponent<PlayerPlatformerController>();
|
|
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)
|
|
{
|
|
Debug.Log("Interact action performed");
|
|
if(heldObject != null)
|
|
{
|
|
Debug.Log("Dropping held object");
|
|
DropObject(false);
|
|
return;
|
|
}
|
|
|
|
TryPickUp();
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (heldObject == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!heldObject.TryGetComponent<TrickController>(out _))
|
|
{
|
|
return;
|
|
}
|
|
|
|
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);
|
|
Debug.Log(colliders.Length + " colliders found in range");
|
|
foreach (var col in colliders)
|
|
{
|
|
if (col.gameObject == gameObject)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var hasTrick = col.TryGetComponent<TrickController>(out var trick);
|
|
var hasPlayer = col.TryGetComponent<PlayerPlatformerController>(out var playerController);
|
|
if (!hasTrick && !hasPlayer && !col.CompareTag("Interactable"))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
heldObject = col.gameObject;
|
|
|
|
// 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;
|
|
|
|
// Disable physics so it doesn't fall or collide with the player
|
|
if (heldObject.TryGetComponent<Rigidbody2D>(out Rigidbody2D rb))
|
|
{
|
|
rb.bodyType = RigidbodyType2D.Kinematic;
|
|
rb.gravityScale = 0;
|
|
rb.mass = 0;
|
|
}
|
|
|
|
if (trick != null)
|
|
{
|
|
trick.SetCarried(true);
|
|
}
|
|
|
|
if (playerController != null)
|
|
{
|
|
playerController.enabled = false;
|
|
if (heldObject.TryGetComponent<PlayerPickupController>(out var heldPickup))
|
|
{
|
|
heldPickup.enabled = false;
|
|
}
|
|
if (heldObject.TryGetComponent<Rigidbody2D>(out var heldBody))
|
|
{
|
|
heldBody.linearVelocity = Vector2.zero;
|
|
heldBody.angularVelocity = 0f;
|
|
}
|
|
}
|
|
|
|
if (col != null) col.enabled = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void DropObject(bool applyThrow)
|
|
{
|
|
// Re-enable physics
|
|
if (heldObject.TryGetComponent<Rigidbody2D>(out Rigidbody2D rb))
|
|
{
|
|
rb.bodyType = RigidbodyType2D.Dynamic;
|
|
rb.gravityScale = 1;
|
|
rb.linearVelocity = Vector2.zero;
|
|
}
|
|
if (heldObject.TryGetComponent<TrickController>(out var trick))
|
|
{
|
|
trick.SetCarried(false);
|
|
if (applyThrow)
|
|
{
|
|
trick.OnThrown();
|
|
}
|
|
}
|
|
if (heldObject.TryGetComponent<PlayerPlatformerController>(out var playerController))
|
|
{
|
|
playerController.enabled = true;
|
|
if (heldObject.TryGetComponent<PlayerPickupController>(out var heldPickup))
|
|
{
|
|
heldPickup.enabled = true;
|
|
}
|
|
}
|
|
if (applyThrow && heldObject.TryGetComponent<Rigidbody2D>(out Rigidbody2D throwBody))
|
|
{
|
|
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);
|
|
throwBody.linearVelocity = new Vector2(direction * throwSpeed, 0f);
|
|
throwBody.angularVelocity = 0f;
|
|
}
|
|
heldObject.transform.position += Vector3.up * dropClearance;
|
|
Collider2D col = heldObject.GetComponent<Collider2D>();
|
|
if (col != null) col.enabled = true;
|
|
|
|
// Unparent
|
|
heldObject.transform.SetParent(null);
|
|
heldObject = null;
|
|
isChargingThrow = false;
|
|
}
|
|
|
|
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<InputDevice>();
|
|
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;
|
|
}
|
|
}
|