2025-09-01 05:03:00 +05:00
|
|
|
|
using UnityEngine;
|
|
|
|
|
using System.Collections;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
|
|
|
|
[RequireComponent(typeof(Rigidbody))]
|
|
|
|
|
public class CubeClash_ZibuController : MonoBehaviour
|
|
|
|
|
{
|
|
|
|
|
public static List<CubeClash_ZibuController> AllZibus = new List<CubeClash_ZibuController>();
|
|
|
|
|
|
|
|
|
|
[Header("Control")]
|
|
|
|
|
public bool isPlayerControlled = true;
|
|
|
|
|
public float moveSpeed = 5f;
|
|
|
|
|
public Joystick joystick;
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
[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;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
|
|
|
|
[Header("Animation")]
|
2025-09-03 00:04:11 +05:00
|
|
|
|
public Animator animator; // optional
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
// --- runtime
|
2025-09-01 05:03:00 +05:00
|
|
|
|
private Rigidbody rb;
|
|
|
|
|
private bool canBump = true;
|
2025-09-03 00:04:11 +05:00
|
|
|
|
|
|
|
|
|
[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;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
[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;
|
|
|
|
|
// ---------------------------------------------------------
|
|
|
|
|
|
2025-09-01 05:03:00 +05:00
|
|
|
|
void OnEnable()
|
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
if (!AllZibus.Contains(this)) AllZibus.Add(this);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
void OnDisable()
|
|
|
|
|
{
|
|
|
|
|
AllZibus.Remove(this);
|
2025-09-04 12:13:13 +05:00
|
|
|
|
ForceCloseParachute(); // restore drag & hide visual if this gets disabled mid-air
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Awake()
|
|
|
|
|
{
|
|
|
|
|
rb = GetComponent<Rigidbody>();
|
|
|
|
|
if (animator == null) animator = GetComponentInChildren<Animator>();
|
2025-09-03 00:04:11 +05:00
|
|
|
|
|
|
|
|
|
// 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;
|
2025-09-04 12:13:13 +05:00
|
|
|
|
if (!cameraTransform && Camera.main) cameraTransform = Camera.main.transform;
|
|
|
|
|
|
|
|
|
|
if (ParachuteObj) ParachuteObj.SetActive(false);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void Update()
|
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
if (isPlayerControlled && !ControlLocked)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
HandleMovement();
|
|
|
|
|
|
|
|
|
|
UpdateAnimations();
|
2025-09-03 00:04:11 +05:00
|
|
|
|
FaceVelocity(); // always face movement
|
2025-09-04 12:13:13 +05:00
|
|
|
|
|
|
|
|
|
// --- 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);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// -------------------------------------------------------------
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
|
|
|
|
private void HandleMovement()
|
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
if (joystick == null) return;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// 1) Read joystick
|
|
|
|
|
Vector2 input = new Vector2(joystick.Horizontal, joystick.Vertical);
|
|
|
|
|
if (input.sqrMagnitude < 0.01f)
|
2025-09-03 00:04:11 +05:00
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// No input: keep gravity/vertical velocity only
|
|
|
|
|
rb.velocity = new Vector3(0f, rb.velocity.y, 0f);
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-09-03 00:04:11 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// 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();
|
2025-09-03 00:04:11 +05:00
|
|
|
|
}
|
2025-09-04 12:13:13 +05:00
|
|
|
|
|
|
|
|
|
// 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)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Quaternion targetRot = Quaternion.LookRotation(desiredDir, Vector3.up);
|
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, rotationLerp * Time.deltaTime);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
private void FaceVelocity()
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Vector3 v = rb.velocity; v.y = 0;
|
|
|
|
|
if (v.sqrMagnitude > 0.05f)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
Quaternion targetRot = Quaternion.LookRotation(v.normalized, Vector3.up);
|
|
|
|
|
transform.rotation = Quaternion.Slerp(transform.rotation, targetRot, Time.deltaTime * 10f);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
private void UpdateAnimations()
|
|
|
|
|
{
|
|
|
|
|
if (!animator) return;
|
|
|
|
|
Vector3 horiz = new Vector3(rb.velocity.x, 0, rb.velocity.z);
|
|
|
|
|
animator.SetFloat("Speed", horiz.magnitude);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Player UI
|
2025-09-01 05:03:00 +05:00
|
|
|
|
public void PerformBump()
|
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
if (!canBump || ControlLocked) return;
|
|
|
|
|
StartCoroutine(BumpSequence(transform.forward));
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
// AI
|
2025-09-01 05:03:00 +05:00
|
|
|
|
public void PerformBump(Vector3 direction)
|
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
if (!canBump || ControlLocked) return;
|
|
|
|
|
StartCoroutine(BumpSequence(direction));
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
private IEnumerator BumpSequence(Vector3 direction)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
|
|
|
|
canBump = false;
|
|
|
|
|
isBumping = true;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Clean start (optional)
|
2025-09-03 00:04:11 +05:00
|
|
|
|
Vector3 horiz = new Vector3(rb.velocity.x, 0f, rb.velocity.z);
|
|
|
|
|
rb.velocity = new Vector3(0f, rb.velocity.y, 0f) + horiz * 0.25f;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
// Dash impulse
|
2025-09-03 00:04:11 +05:00
|
|
|
|
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);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
isHitActive = true;
|
|
|
|
|
yield return new WaitForSeconds(hitWindow);
|
|
|
|
|
isHitActive = false;
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
|
|
|
|
isBumping = false;
|
2025-09-03 00:04:11 +05:00
|
|
|
|
yield return new WaitForSeconds(bumpCooldown);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
canBump = true;
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
public void ApplyPushStun(float seconds)
|
2025-09-01 05:03:00 +05:00
|
|
|
|
{
|
2025-09-03 00:04:11 +05:00
|
|
|
|
float until = Time.time + Mathf.Max(0f, seconds);
|
|
|
|
|
if (until > stunUntilTime) stunUntilTime = until;
|
|
|
|
|
}
|
2025-09-01 05:03:00 +05:00
|
|
|
|
|
2025-09-03 00:04:11 +05:00
|
|
|
|
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<CubeClash_ZibuController>();
|
|
|
|
|
if (other == null) return;
|
|
|
|
|
|
|
|
|
|
if (other.isHitActive) return;
|
|
|
|
|
|
|
|
|
|
var otherRb = other.GetComponent<Rigidbody>();
|
|
|
|
|
if (otherRb == null) return;
|
|
|
|
|
|
2025-09-04 12:13:13 +05:00
|
|
|
|
Vector3 pushDir = transform.forward; pushDir.y = 0; pushDir.Normalize();
|
2025-09-03 00:04:11 +05:00
|
|
|
|
|
|
|
|
|
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);
|
2025-09-01 05:03:00 +05:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|