Exemplo n.º 1
0
        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);
        }
Exemplo n.º 2
0
        /// <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;
        }