using UnityEngine; using System.Collections; using System.Collections.Generic; [RequireComponent(typeof(Rigidbody))] public class CubeClash_ZibuController : MonoBehaviour { public static List AllZibus = new List(); [Header("Control")] public bool isPlayerControlled = true; public float moveSpeed = 5f; public Joystick joystick; [Header("Bump (Physics)")] [Tooltip("Impulse strength added in forward direction when bump starts.")] public float dashImpulse = 12f; [Tooltip("How long collisions count as 'bumping' (hit-active window).")] public float hitWindow = 0.22f; [Tooltip("Cooldown after bump sequence finishes.")] public float bumpCooldown = 1.0f; [Header("Momentum Transfer")] [Tooltip("Scales impulse delivered to the other Zibu on hit.")] public float momentumTransferScale = 0.9f; [Tooltip("Extra kick added to the victim on hit.")] public float bonusKnockImpulse = 2f; [Tooltip("How much of our forward momentum we lose on hit (0..1).")] public float selfMomentumLoss = 0.25f; [Header("Hit Reaction")] [Tooltip("Time (sec) the victim loses control after being pushed.")] public float pushStunTime = 0.25f; [Header("Animation")] public Animator animator; // optional // --- runtime private Rigidbody rb; private bool canBump = true; [HideInInspector] public bool isBumping = false; // only during dash + hit window [HideInInspector] public bool isHitActive = false; private float stunUntilTime = -999f; public bool ControlLocked => isBumping || Time.time < stunUntilTime; [Header("Camera-Relative Movement")] public Transform cameraTransform; // assign your gameplay camera; falls back to Camera.main public bool rotateToMovement = true; public float rotationLerp = 12f; // Parachute visual (already in your script) public GameObject ParachuteObj; // ---------------- NEW: Parachute behavior ---------------- [Header("Parachute Settings")] [Tooltip("Extra drag while parachute is open (slows fall).")] public float parachuteDrag = 3.5f; [Tooltip("Minimum time the chute stays open after drop to avoid flicker.")] public float minParachuteSeconds = 0.15f; [Tooltip("Radius for ground check sphere cast.")] public float groundCheckRadius = 0.25f; [Tooltip("Downward distance for ground check.")] public float groundCheckDistance = 0.5f; [Tooltip("Layers considered as ground (must be set in Inspector).")] public LayerMask groundMask = ~0; private bool _parachuteActive = false; private float _parachuteMinOffTime = 0f; private float _origDrag = -1f; // --------------------------------------------------------- void OnEnable() { if (!AllZibus.Contains(this)) AllZibus.Add(this); } void OnDisable() { AllZibus.Remove(this); ForceCloseParachute(); // restore drag & hide visual if this gets disabled mid-air } void Awake() { rb = GetComponent(); if (animator == null) animator = GetComponentInChildren(); // Physics stability rb.collisionDetectionMode = CollisionDetectionMode.Continuous; rb.interpolation = RigidbodyInterpolation.Interpolate; rb.solverIterations = Mathf.Max(rb.solverIterations, 12); rb.solverVelocityIterations = Mathf.Max(rb.solverVelocityIterations, 12); // freeze tilt rotations rb.constraints |= RigidbodyConstraints.FreezeRotationX | RigidbodyConstraints.FreezeRotationZ; if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform; if (ParachuteObj) ParachuteObj.SetActive(false); } void Update() { if (isPlayerControlled && !ControlLocked) HandleMovement(); UpdateAnimations(); FaceVelocity(); // always face movement // --- NEW: auto-close parachute when grounded on groundMask --- if (_parachuteActive && Time.time >= _parachuteMinOffTime && IsGroundedOnGround()) { CloseParachute(); } } // -------- NEW: Parachute API (called by bird on drop) -------- public void TriggerParachuteDrop(float minOpenTime = -1f) { if (!_parachuteActive) { if (_origDrag < 0f) _origDrag = rb.drag; // remember once if (ParachuteObj) ParachuteObj.SetActive(true); Invoke(nameof(CloseParachute), 1.5f); rb.drag = Mathf.Max(_origDrag, parachuteDrag); _parachuteActive = true; } _parachuteMinOffTime = Time.time + ((minOpenTime > 0f) ? minOpenTime : minParachuteSeconds); } private void CloseParachute() { CancelInvoke(nameof(CloseParachute)); if (ParachuteObj) ParachuteObj.SetActive(false); if (_origDrag >= 0f) rb.drag = _origDrag; _parachuteActive = false; } public void ForceCloseParachute() { if (!_parachuteActive) return; CloseParachute(); } private bool IsGroundedOnGround() { Vector3 origin = transform.position + Vector3.up * 0.1f; // Only test groundMask so we don't close when standing on another Zibu return Physics.SphereCast(origin, groundCheckRadius, Vector3.down, out _, groundCheckDistance, groundMask, QueryTriggerInteraction.Ignore); } // ------------------------------------------------------------- private void HandleMovement() { if (joystick == null) return; // 1) Read joystick Vector2 input = new Vector2(joystick.Horizontal, joystick.Vertical); if (input.sqrMagnitude < 0.01f) { // No input: keep gravity/vertical velocity only rb.velocity = new Vector3(0f, rb.velocity.y, 0f); return; } // 2) Build a camera-aligned basis on the XZ plane Vector3 camFwd = Vector3.forward; Vector3 camRight = Vector3.right; if (cameraTransform) { camFwd = cameraTransform.forward; camFwd.y = 0f; camFwd.Normalize(); camRight = cameraTransform.right; camRight.y = 0f; camRight.Normalize(); } // 3) Convert input into world space using camera basis Vector3 desiredDir = (camRight * input.x + camFwd * input.y); if (desiredDir.sqrMagnitude > 1f) desiredDir.Normalize(); // 4) Apply velocity (preserve vertical) Vector3 targetVel = desiredDir * moveSpeed; rb.velocity = new Vector3(targetVel.x, rb.velocity.y, targetVel.z); // 5) Face movement direction (yaw only) if (rotateToMovement && desiredDir.sqrMagnitude > 0.0001f) { Quaternion targetRot = Quaternion.LookRotation(desiredDir, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotationLerp * Time.deltaTime); } } private void FaceVelocity() { Vector3 v = rb.velocity; v.y = 0; if (v.sqrMagnitude > 0.05f) { Quaternion targetRot = Quaternion.LookRotation(v.normalized, Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, Time.deltaTime * 10f); } } private void UpdateAnimations() { if (!animator) return; Vector3 horiz = new Vector3(rb.velocity.x, 0, rb.velocity.z); animator.SetFloat("Speed", horiz.magnitude); } // Player UI public void PerformBump() { if (!canBump || ControlLocked) return; StartCoroutine(BumpSequence(transform.forward)); } // AI public void PerformBump(Vector3 direction) { if (!canBump || ControlLocked) return; StartCoroutine(BumpSequence(direction)); } private IEnumerator BumpSequence(Vector3 direction) { canBump = false; isBumping = true; // Clean start (optional) Vector3 horiz = new Vector3(rb.velocity.x, 0f, rb.velocity.z); rb.velocity = new Vector3(0f, rb.velocity.y, 0f) + horiz * 0.25f; // Dash impulse Vector3 fwd = new Vector3(direction.x, 0, direction.z); if (fwd.sqrMagnitude < 0.001f) fwd = transform.forward; fwd.Normalize(); rb.AddForce(fwd * dashImpulse, ForceMode.Impulse); isHitActive = true; yield return new WaitForSeconds(hitWindow); isHitActive = false; isBumping = false; yield return new WaitForSeconds(bumpCooldown); canBump = true; } public void ApplyPushStun(float seconds) { float until = Time.time + Mathf.Max(0f, seconds); if (until > stunUntilTime) stunUntilTime = until; } private void OnCollisionEnter(Collision c) => HandleBumpCollision(c); private void OnCollisionStay(Collision c) => HandleBumpCollision(c); private void HandleBumpCollision(Collision c) { if (!isHitActive) return; var other = c.collider.GetComponent(); if (other == null) return; if (other.isHitActive) return; var otherRb = other.GetComponent(); if (otherRb == null) return; Vector3 pushDir = transform.forward; pushDir.y = 0; pushDir.Normalize(); Vector3 relVel = rb.velocity - otherRb.velocity; float approach = Vector3.Dot(relVel, pushDir); if (approach <= 0f) return; float impulseMag = (rb.mass * approach * momentumTransferScale) + bonusKnockImpulse; Vector3 impulse = pushDir * impulseMag; otherRb.AddForce(impulse, ForceMode.Impulse); other.ApplyPushStun(pushStunTime); if (selfMomentumLoss > 0f) { Vector3 ourForward = Vector3.Project(rb.velocity, pushDir); rb.AddForce(-ourForward * selfMomentumLoss * rb.mass, ForceMode.Impulse); } } }