using UnityEngine; // Joystick Pack (Asset Store) // Drag any Joystick (Fixed/Floating/Dynamic/Variable) into moveJoystick [RequireComponent(typeof(Rigidbody))] public class CrateEscapePlayerControllerJoystick : MonoBehaviour { [Header("Joystick")] public Joystick moveJoystick; // assign from Joystick Pack [Header("Movement")] public float moveSpeed = 4f; // base speed public float moveDamp = 12f; // velocity smoothing public float maxSpeedMultiplier = 3f; // full-stick multiplier [Range(0f, 0.5f)] public float inputDeadZone = 0.1f; [Tooltip("If true, stick is interpreted relative to the camera view.")] public bool cameraRelative = true; public Transform cameraTransform; // auto-fills with Camera.main if null [Header("Facing")] public bool faceMoveDirection = true; // rotate toward move direction public float turnSpeed = 540f; // deg/sec rotation speed [Header("Grounding")] public LayerMask groundMask = ~0; public float groundCheckRadius = 0.2f; public Transform groundCheck; [Header("Optional Animator Driver")] public ZibuAnimDriver anim; [Header("Input Locks")] public float initialLockSeconds = 4f; // joystick disabled for first N seconds public bool useUnscaledForInitialLock = false; // set true if you pause time at start public bool autoLockOnGameOver = true; // auto-freeze if GameManager reports isGameOver // --- runtime --- Rigidbody _rb; Vector3 _vel; // smoothed velocity Vector3 _moveDirWorld; // desired movement direction in world space float _stickMag; // 0..1 bool _inputEnabled = false; // starts locked; enabled after initialLockSeconds void Awake() { _rb = GetComponent(); // keep physics stable _rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; if (!groundCheck) groundCheck = transform; if (!anim) anim = GetComponent(); if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform; } void OnEnable() { // start locked, then enable after delay _inputEnabled = false; StopAllCoroutines(); StartCoroutine(EnableAfterDelay(initialLockSeconds, useUnscaledForInitialLock)); } System.Collections.IEnumerator EnableAfterDelay(float seconds, bool unscaled) { if (seconds > 0f) { if (unscaled) yield return new WaitForSecondsRealtime(seconds); else yield return new WaitForSeconds(seconds); } _inputEnabled = true; } void Update() { bool blocked = IsBlocked(); if (!blocked) ReadJoystick(); else { // when blocked, zero input so the player stops + anim goes idle _stickMag = 0f; _moveDirWorld = Vector3.zero; } if (anim) { float speedUnits = Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag); anim.SetGrounded(IsGrounded()); anim.SetSpeed(speedUnits); } } void FixedUpdate() { bool blocked = IsBlocked(); Vector3 targetVel = Vector3.zero; if (!blocked && _stickMag > inputDeadZone) { Vector3 dir = _moveDirWorld.normalized; float targetSpeed = moveSpeed * Mathf.Lerp(0f, maxSpeedMultiplier, _stickMag); targetVel = dir * targetSpeed; if (faceMoveDirection) { Quaternion targetRot = Quaternion.LookRotation(dir, Vector3.up); _rb.MoveRotation(Quaternion.RotateTowards(_rb.rotation, targetRot, turnSpeed * Time.fixedDeltaTime)); } } if (blocked) { // hard-stop while locked _vel = Vector3.zero; _rb.velocity = Vector3.zero; return; // don't MovePosition this frame } // Smooth velocity and move _vel = Vector3.Lerp(_vel, targetVel, 1f - Mathf.Exp(-moveDamp * Time.fixedDeltaTime)); _rb.MovePosition(_rb.position + _vel * Time.fixedDeltaTime); } void ReadJoystick() { if (!moveJoystick) { _stickMag = 0f; _moveDirWorld = Vector3.zero; return; } float h = moveJoystick.Horizontal; // -1..1 float v = moveJoystick.Vertical; // -1..1 Vector2 stick = new Vector2(h, v); _stickMag = Mathf.Clamp01(stick.magnitude); if (_stickMag <= inputDeadZone) { _moveDirWorld = Vector3.zero; return; } Vector2 n = stick.normalized; if (cameraRelative && cameraTransform) { // Project camera forward to XZ and build a right vector Vector3 camF = cameraTransform.forward; camF.y = 0f; camF = camF.sqrMagnitude > 0.0001f ? camF.normalized : Vector3.forward; Vector3 camR = new Vector3(camF.z, 0f, -camF.x); _moveDirWorld = camR * n.x + camF * n.y; } else { // World-relative (X/Z) _moveDirWorld = new Vector3(n.x, 0f, n.y); } } bool IsGrounded() { return Physics.CheckSphere(groundCheck.position, groundCheckRadius, groundMask, QueryTriggerInteraction.Ignore); } void OnDrawGizmosSelected() { if (!groundCheck) groundCheck = transform; Gizmos.color = Color.yellow; Gizmos.DrawWireSphere(groundCheck.position, groundCheckRadius); } // ---------- Public API ---------- /// Enable/disable all player input & movement immediately. public void LockControls(bool locked) { _inputEnabled = !locked; if (locked) { _stickMag = 0f; _moveDirWorld = Vector3.zero; _vel = Vector3.zero; if (_rb) _rb.velocity = Vector3.zero; if (anim) { anim.SetSpeed(0f); } } } /// Enable controls now (e.g., if you want to skip the initial delay). public void EnableControlsNow() { StopAllCoroutines(); _inputEnabled = true; } // ---------- Helpers ---------- bool IsBlocked() { if (!_inputEnabled) return true; if (autoLockOnGameOver) { var gm = CrateEscapeGameManager.Instance; if (gm != null && gm.isGameOver) return true; // freeze after game over } return false; } // For runtime hookup if you spawn the joystick UI public void SetJoystick(Joystick j) => moveJoystick = j; }