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; void OnEnable() { if (!AllZibus.Contains(this)) AllZibus.Add(this); } void OnDisable() { AllZibus.Remove(this); } 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; } void Update() { if (isPlayerControlled && !ControlLocked) HandleMovement(); UpdateAnimations(); FaceVelocity(); // always face movement } private void HandleMovement() { if (joystick == null) return; Vector3 move = new Vector3(joystick.Horizontal, 0, joystick.Vertical); if (move.sqrMagnitude > 0.01f) { Vector3 targetVel = move.normalized * moveSpeed; rb.velocity = new Vector3(targetVel.x, rb.velocity.y, targetVel.z); // smooth yaw-only rotation Quaternion targetRot = Quaternion.LookRotation(new Vector3(move.x, 0, move.z), Vector3.up); transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, Time.deltaTime * 10f); } else { rb.velocity = new Vector3(0f, rb.velocity.y, 0f); } } private void FaceVelocity() { Vector3 v = rb.velocity; v.y = 0; if (v.sqrMagnitude > 0.05f) // ignore tiny jitter { 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): limit drift so dash reads crisp Vector3 horiz = new Vector3(rb.velocity.x, 0f, rb.velocity.z); rb.velocity = new Vector3(0f, rb.velocity.y, 0f) + horiz * 0.25f; // --- Dash forward impulse (pure physics) --- 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); // Collisions are “hot” only during this window isHitActive = true; yield return new WaitForSeconds(hitWindow); isHitActive = false; // End bump; control resumes, velocity continues naturally isBumping = false; // Cooldown before next bump yield return new WaitForSeconds(bumpCooldown); canBump = true; } // External: short lock so physics can show the shove 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 both are in hit window, let raw physics resolve if (other.isHitActive) return; var otherRb = other.GetComponent(); if (otherRb == null) return; // Push direction = our forward 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; // pure shove, no torque otherRb.AddForce(impulse, ForceMode.Impulse); // Lock their movement briefly so they don't cancel the shove other.ApplyPushStun(pushStunTime); if (selfMomentumLoss > 0f) { Vector3 ourForward = Vector3.Project(rb.velocity, pushDir); rb.AddForce(-ourForward * selfMomentumLoss * rb.mass, ForceMode.Impulse); } } }