public override bool FindGround(Vector3 direction, out RaycastHit hitInfo, float distance = Mathf.Infinity, float backstepDistance = kBackstepDistance) { var radius = capsuleCollider.radius; var height = Mathf.Max(0.0f, capsuleCollider.height * 0.5f - radius); var center = capsuleCollider.center - Vector3.up * height; var origin = transform.TransformPoint(center); var up = transform.up; if (!SphereCast(origin, radius, direction, out hitInfo, distance, backstepDistance) || Vector3.Angle(hitInfo.normal, /*Vector3.up*/ up) >= 89.0f) { return(false); } var p = transform.position - transform.up * hitInfo.distance; var q = transform.rotation; var groundHitInfo = new GroundHit(hitInfo); DetectLedgeAndSteps(p, q, ref groundHitInfo, castDistance, hitInfo.point, hitInfo.normal); groundHitInfo.isOnGround = true; groundHitInfo.isValidGround = !groundHitInfo.isOnLedgeEmptySide && Vector3.Angle(groundHitInfo.surfaceNormal, /*Vector3.up*/ up) < groundLimit; return(groundHitInfo.isOnGround && groundHitInfo.isValidGround); }
/// <summary> /// Reset the 'ground' hit info. /// </summary> public virtual void ResetGroundInfo() { var up = transform.up; prevGroundHit = new GroundHit(_groundHitInfo); _groundHitInfo = new GroundHit { groundPoint = transform.position, groundNormal = up, surfaceNormal = up }; }
public override bool ComputeGroundHit(Vector3 position, Quaternion rotation, ref GroundHit groundHitInfo, float distance = Mathf.Infinity) { var up = rotation * Vector3.up; // Downward cast from capsule's bottom sphere (filter any 'wall') RaycastHit hitInfo; if (BottomSphereCast(position, rotation, out hitInfo, distance) && Vector3.Angle(hitInfo.normal, up) < 89.0f) { // Update ground hit info groundHitInfo.SetFrom(hitInfo); // Check if standing on a ledge or step DetectLedgeAndSteps(position, rotation, ref groundHitInfo, distance, hitInfo.point, hitInfo.normal); // Inform we found 'ground' and if is valid groundHitInfo.isOnGround = true; groundHitInfo.isValidGround = !groundHitInfo.isOnLedgeEmptySide && Vector3.Angle(groundHitInfo.surfaceNormal, up) < groundLimit; return(true); } // If initial sphere cast fails, or found a 'wall', // fallback to a raycast from character's bottom position if (!BottomRaycast(position, rotation, out hitInfo, distance)) { return(false); } // If found 'ground', update ground info and return groundHitInfo.SetFrom(hitInfo); groundHitInfo.surfaceNormal = hitInfo.normal; groundHitInfo.isOnGround = true; groundHitInfo.isValidGround = Vector3.Angle(groundHitInfo.surfaceNormal, /*Vector3.up*/ up) < groundLimit; return(true); }
public GroundHit(GroundHit other) : this() { isOnGround = other.isOnGround; isValidGround = other.isValidGround; isOnLedgeSolidSide = other.isOnLedgeSolidSide; isOnLedgeEmptySide = other.isOnLedgeEmptySide; ledgeDistance = other.ledgeDistance; isOnStep = other.isOnStep; stepHeight = other.stepHeight; groundPoint = other.groundPoint; groundNormal = other.groundNormal; groundDistance = Mathf.Max(0.0f, other.groundDistance); groundCollider = other.groundCollider; groundRigidbody = other.groundRigidbody; surfaceNormal = other.surfaceNormal; }
/// <summary> /// Check if we are standing on a ledge or a step and update 'grounding' info. /// </summary> /// <param name="position">A probing position. This can be different from character's position.</param> /// <param name="rotation">A probing rotation. This can be different from character's rotation.</param> /// <param name="distance">The cast distance.</param> /// <param name="point">The current contact point.</param> /// <param name="normal">The current contact normal.</param> /// <param name="groundHitInfo">If found any 'ground', hitInfo will contain more information about it.</param> private void DetectLedgeAndSteps(Vector3 position, Quaternion rotation, ref GroundHit groundHitInfo, float distance, Vector3 point, Vector3 normal) { Vector3 up = rotation * Vector3.up, down = -up; var projectedNormal = Vector3.ProjectOnPlane(normal, up).normalized; var nearPoint = point + projectedNormal * kHorizontalOffset; var farPoint = point - projectedNormal * kHorizontalOffset; var ledgeStepDistance = Mathf.Max(kMinLedgeDistance, Mathf.Max(stepOffset, distance)); RaycastHit nearHitInfo; var nearHit = Raycast(nearPoint, down, out nearHitInfo, ledgeStepDistance); var isNearGroundValid = nearHit && Vector3.Angle(nearHitInfo.normal, up) < groundLimit; RaycastHit farHitInfo; var farHit = Raycast(farPoint, down, out farHitInfo, ledgeStepDistance); var isFarGroundValid = farHit && Vector3.Angle(farHitInfo.normal, up) < groundLimit; // Flush if (farHit && !isFarGroundValid) { groundHitInfo.surfaceNormal = farHitInfo.normal; // Attemp to retrieve the 'ground' below us RaycastHit secondaryHitInfo; if (BottomRaycast(position, rotation, out secondaryHitInfo, distance)) { // Update ground info and return groundHitInfo.SetFrom(secondaryHitInfo); groundHitInfo.surfaceNormal = secondaryHitInfo.normal; } return; } // Steps if (isNearGroundValid && isFarGroundValid) { // Choose nearest normal groundHitInfo.surfaceNormal = (point - nearHitInfo.point).sqrMagnitude < (point - farHitInfo.point).sqrMagnitude ? nearHitInfo.normal : farHitInfo.normal; // Check if max vertical distance between points is steep enough to be considered as a step var nearHeight = Vector3.Dot(point - nearHitInfo.point, up); var farHeight = Vector3.Dot(point - farHitInfo.point, up); var height = Mathf.Max(nearHeight, farHeight); if (height > kMinLedgeDistance && height < stepOffset) { groundHitInfo.isOnStep = true; groundHitInfo.stepHeight = height; } return; } // Ledges // If only one of the near / far rays hit we are on a ledge var isOnLedge = isNearGroundValid != isFarGroundValid; if (!isOnLedge) { return; } // On ledge, compute ledge info and check which side of the ledge we are groundHitInfo.surfaceNormal = isFarGroundValid ? farHitInfo.normal : nearHitInfo.normal; groundHitInfo.ledgeDistance = Vector3.ProjectOnPlane(point - position, up).magnitude; if (isFarGroundValid && groundHitInfo.ledgeDistance > ledgeOffset) { // On possible ledge 'empty' side, // cast downwards using de ledgeOffset as radius groundHitInfo.isOnLedgeEmptySide = true; var radius = ledgeOffset; var offset = Mathf.Max(0.0f, capsuleCollider.height * 0.5f - radius); var bottomSphereCenter = capsuleCollider.center - Vector3.up * offset; var bottomSphereOrigin = position + rotation * bottomSphereCenter; RaycastHit hitInfo; if (SphereCast(bottomSphereOrigin, radius, down, out hitInfo, Mathf.Max(stepOffset, distance))) { var verticalSquareDistance = Vector3.Project(point - hitInfo.point, up).sqrMagnitude; if (verticalSquareDistance <= stepOffset * stepOffset) { groundHitInfo.isOnLedgeEmptySide = false; } } } groundHitInfo.isOnLedgeSolidSide = !groundHitInfo.isOnLedgeEmptySide; }
/// <summary> /// Compute 'ground' hit info casting downwards the character's volume, /// if found any 'ground' groundHitInfo will contain additional information about it. /// Returns true when intersects any 'ground' collider, otherwise false. /// </summary> /// <param name="position">A probing position. This can be different from character's position.</param> /// <param name="rotation">A probing rotation. This can be different from character's rotation.</param> /// <param name="groundHitInfo">If true is returned, will contain more information about where the collider was hit.</param> /// <param name="distance">The length of the cast.</param> public abstract bool ComputeGroundHit(Vector3 position, Quaternion rotation, ref GroundHit groundHitInfo, float distance = Mathf.Infinity);