using UnityEngine; using DG.Tweening; /// Hold anywhere to move at base speed; drag up to boost speed. /// Tap left/right half of screen for rotation. /// Keyboard still works (Up hold to move, Left/Right to rotate). [RequireComponent(typeof(Rigidbody))] public class CratePlayerController : MonoBehaviour { [Header("Movement")] public float moveSpeed = 4f; // base forward speed public float moveDamp = 12f; // smoothing of velocity changes public float maxSpeedMultiplier = 3f; // boost cap when swiping up [Header("Rotation (single-step)")] public float quarterTurnDuration = 0.12f; public Ease quarterTurnEase = Ease.OutCubic; [Header("Touch Settings")] public float tapMaxDuration = 0.18f; // max time to register as tap public float tapMaxMovement = 22f; // px movement allowance for tap public float swipeUpDeadZonePx = 10f; // upward drift before speed changes public float swipeUpPixelsForMax = 260f; // px up to hit max multiplier [Header("Grounding")] public LayerMask groundMask = ~0; public float groundCheckRadius = 0.2f; public Transform groundCheck; [Header("Optional Animator Driver")] public ZibuAnimDriver anim; Rigidbody _rb; Tween _rotTween; Vector3 _vel; // Touch state int _fingerId = -1; Vector2 _startPos; float _startTime; bool _movingViaTouch = false; float _touchSpeedMult = 1f; float _targetY; // rotation target memory void Awake() { _rb = GetComponent(); _rb.constraints = RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; if (!groundCheck) groundCheck = transform; if (!anim) anim = GetComponent(); } void Update() { ReadKeyboard(); ReadTouch(); if (anim) { // Use desired speed (instant) instead of current velocity (which lags due to damping) bool forwardHeld = _movingViaTouch || Input.GetKey(KeyCode.UpArrow); float mult = _movingViaTouch ? _touchSpeedMult : 1f; float speedUnits = forwardHeld ? mult : 0f; // 0=idle, 1=run, 2=runfast, 3=sprint anim.SetGrounded(IsGrounded()); anim.SetSpeed(Mathf.Clamp(speedUnits, 0f, maxSpeedMultiplier)); } } void FixedUpdate() { bool forwardHeld = _movingViaTouch || Input.GetKey(KeyCode.UpArrow); float mult = _movingViaTouch ? _touchSpeedMult : 1f; Vector3 targetVel = forwardHeld ? transform.forward * (moveSpeed * mult) : Vector3.zero; _vel = Vector3.Lerp(_vel, targetVel, 1f - Mathf.Exp(-moveDamp * Time.fixedDeltaTime)); _rb.MovePosition(_rb.position + _vel * Time.fixedDeltaTime); } // ---------------- Keyboard ---------------- void ReadKeyboard() { if (Input.GetKeyDown(KeyCode.LeftArrow)) StepTurn(-1); if (Input.GetKeyDown(KeyCode.RightArrow)) StepTurn(+1); } // ---------------- Touch ---------------- void ReadTouch() { if (Input.touchCount == 0) { if (_fingerId != -1) { _movingViaTouch = false; _touchSpeedMult = 1f; _fingerId = -1; } return; } for (int i = 0; i < Input.touchCount; i++) { var t = Input.GetTouch(i); // Start tracking first finger if (t.phase == TouchPhase.Began && _fingerId == -1) { _fingerId = t.fingerId; _startPos = t.position; _startTime = Time.time; _movingViaTouch = true; // start moving immediately _touchSpeedMult = 1f; // base speed } if (t.fingerId != _fingerId) continue; Vector2 delta = t.position - _startPos; float dt = Time.time - _startTime; // Boost speed if swiping upward if (delta.y > swipeUpDeadZonePx) { float up = Mathf.Clamp(delta.y, 0f, swipeUpPixelsForMax); float t01 = up / Mathf.Max(1f, swipeUpPixelsForMax); _touchSpeedMult = Mathf.Lerp(1f, maxSpeedMultiplier, t01); } else { _touchSpeedMult = 1f; } if (t.phase == TouchPhase.Ended || t.phase == TouchPhase.Canceled) { bool isTap = (dt <= tapMaxDuration) && (delta.magnitude <= tapMaxMovement); if (isTap) { // tap rotation if (_startPos.x <= Screen.width * 0.5f) StepTurn(-1); else StepTurn(+1); } // stop movement _movingViaTouch = false; _touchSpeedMult = 1f; _fingerId = -1; } } } // ---------------- Turning ---------------- public void StepTurn(int direction) { if (direction == 0) return; float baseY = transform.eulerAngles.y; if (_rotTween != null && _rotTween.active && _rotTween.IsPlaying()) baseY = _targetY; _targetY = Snap90(baseY + 90f * Mathf.Sign(direction)); // ensure only one tween & unlock yaw for the duration _rotTween?.Kill(false); // Unlock Y so tween can rotate var frozen = _rb.constraints; _rb.constraints = frozen & ~RigidbodyConstraints.FreezeRotationY; _rotTween = transform .DORotate(new Vector3(0f, _targetY, 0f), quarterTurnDuration, RotateMode.Fast) .SetEase(quarterTurnEase) .OnKill(() => { // Re-freeze Y after rotation completes or is killed _rb.constraints = frozen | RigidbodyConstraints.FreezeRotationY; }) .OnComplete(() => { // also re-freeze on normal completion (redundant but safe) _rb.constraints = frozen | RigidbodyConstraints.FreezeRotationY; }); } static float Snap90(float y) { float snapped = Mathf.Round(y / 90f) * 90f; return Mathf.Repeat(snapped, 360f); } // ---------------- Grounding ---------------- 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); } }