MiniGames/Assets/Scripts/CrateEscape/CrateEscapePlayerControllerJoystick.cs
2025-08-14 20:29:09 +05:00

209 lines
6.6 KiB
C#

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<Rigidbody>();
// keep physics stable
_rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ;
if (!groundCheck) groundCheck = transform;
if (!anim) anim = GetComponent<ZibuAnimDriver>();
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 ----------
/// <summary>Enable/disable all player input & movement immediately.</summary>
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); }
}
}
/// <summary>Enable controls now (e.g., if you want to skip the initial delay).</summary>
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;
}