using System; using UnityEngine; using UnityEngine.InputSystem; [RequireComponent(typeof(Rigidbody2D))] [RequireComponent(typeof(BoxCollider2D))] public class PlayerPlatformerController : MonoBehaviour { [SerializeField] private InputActionAsset inputActions; [SerializeField] private string actionMapName = "Player"; [SerializeField] private string moveActionName = "Move"; [SerializeField] private string jumpActionName = "Jump"; [SerializeField] private string healActionName = "Heal"; [SerializeField] private string controlSchemeName = ""; [SerializeField] private int gamepadIndex = -1; [SerializeField] private float moveSpeed = 6f; [SerializeField] private float jumpImpulse = 8f; [SerializeField] private LayerMask groundMask = ~0; [SerializeField] private float groundCheckDistance = 0.05f; private PlayerStressController playerStressController; private PlayerPickupController playerPickupController; private Animator animator; private Rigidbody2D body; private BoxCollider2D bodyCollider; private InputActionAsset runtimeActions; private InputAction moveAction; private InputAction jumpAction; private InputAction healAction; public float LastMoveX { get; private set; } private bool deviceFilterEnabled; private int healCoolDownTicks = 15; private int currentHealCoolDown = 0; private bool canHeal = true; private void Awake() { body = GetComponent(); bodyCollider = GetComponent(); playerStressController = GetComponent(); playerPickupController = GetComponent(); animator = GetComponent(); } private void OnEnable() { if (inputActions == null) { Debug.LogError("PlayerPlatformerController 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); moveAction = map.FindAction(moveActionName, true); jumpAction = map.FindAction(jumpActionName, true); healAction = map.FindAction(healActionName, true); moveAction.Enable(); jumpAction.Enable(); jumpAction.performed += OnJumpPerformed; healAction.Enable(); } private void OnDisable() { if (jumpAction != null) { jumpAction.performed -= OnJumpPerformed; jumpAction.Disable(); } if (moveAction != null) { moveAction.Disable(); } if (healAction != null) { healAction.Disable(); } if (runtimeActions != null) { runtimeActions.Disable(); runtimeActions = null; } InputSystem.onDeviceChange -= HandleDeviceChange; } private void FixedUpdate() { if(IsGrounded()) { animator.SetBool("isJumping", false); } if (healAction == null) return; currentHealCoolDown++; if (currentHealCoolDown >= healCoolDownTicks) { currentHealCoolDown = 0; canHeal = true; } if (moveAction == null) { return; } if (playerStressController.isPanicking) return; if (healAction.IsPressed()) { animator.SetBool("isBreathing", true); if (playerPickupController.heldObject != null) { playerPickupController.DropObject(false); } if (canHeal) { playerStressController.ReduceStress(3f); canHeal = false; } return; } else { animator.SetBool("isBreathing", false); } var input = moveAction.ReadValue(); if (Mathf.Abs(input.x) > 0.01f) { LastMoveX = input.x; } var velocity = body.linearVelocity; velocity.x = input.x * moveSpeed; body.linearVelocity = velocity; if(velocity.x != 0) { animator.SetBool("isRunning", true); if (velocity.x > 0 && IsGrounded()) { transform.localScale = new Vector3(.25f, .25f, .25f); } else { transform.localScale = new Vector3(-.25f, .25f, .25f); } } else { animator.SetBool("isRunning", false); } } private void OnJumpPerformed(InputAction.CallbackContext context) { if (playerStressController.isPanicking || healAction.IsPressed()) return; if (!IsGrounded()) { return; } var velocity = body.linearVelocity; velocity.y = 0f; body.linearVelocity = velocity; body.AddForce(Vector2.up * jumpImpulse, ForceMode2D.Impulse); animator.SetBool("isJumping", true); } 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; } private bool IsGrounded() { var bounds = bodyCollider.bounds; var checkSize = new Vector2(bounds.size.x * 0.9f, groundCheckDistance * 2f); var checkCenter = new Vector2(bounds.center.x, bounds.min.y - groundCheckDistance); var hits = Physics2D.OverlapBoxAll(checkCenter, checkSize, 0f, groundMask); for (var i = 0; i < hits.Length; i++) { var hit = hits[i]; if (hit == null || hit == bodyCollider || hit.isTrigger) { continue; } if (hit.GetComponent() != null) { continue; } if (hit.GetComponent() != null) { continue; } return true; } return false; } }