예제 #1
0
        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
            };
        }
예제 #3
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);
        }
예제 #4
0
        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;
        }
예제 #5
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;
        }
        /// <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);