/// <summary>
        /// Reset the collision flags of this gameObject to only None
        /// </summary>
        protected CollisionFlags2D ResetCollisionFlags(CollisionFlags2D collisions)
        {
            collisions |= CollisionFlags2D.None;
            collisions &= ~CollisionFlags2D.Back;
            collisions &= ~CollisionFlags2D.Top;
            collisions &= ~CollisionFlags2D.Bottom;
            collisions &= ~CollisionFlags2D.Front;

            return(collisions);
        }
 private void Awake()
 {
     characterTransform        = GetComponent <Transform>();
     characterBody             = GetComponent <Rigidbody2D>();
     characterBody.isKinematic = true;
     contactFilter.SetLayerMask(Physics2D.GetLayerCollisionMask(gameObject.layer));
     contactFilter.useLayerMask = true;
     hitList              = new List <RaycastHit2D>(16);
     collisionFlags       = CollisionFlags2D.None;
     characterUpDirection = Vector2.up;
 }
Exemplo n.º 3
0
    /**
     * // Useful just before teleporting the controller, for example.
     * public void ResetSlopes()
     * {
     *      switchCeiling = switchGround = false;
     *      ground = upGround = downGround = null;
     *      ceiling = upCeiling = downCeiling = null;
     *      groundTriggers.Clear();
     *      groundTriggers.TrimExcess();
     *      ceilingTriggers.Clear();
     *      ceilingTriggers.TrimExcess();
     * }
     * /**/

    #endregion

    #region Monobehaviour

    void Awake()
    {
        collisionFlags = new CollisionFlags2D();

        if (!self)
        {
            self = GetComponent <Transform>();
        }
        if (!box)
        {
            box = GetComponent <BoxCollider2D>();
        }

        //stepSize = 0.1f;

        //groundTriggers = new List<SlopeZone>();
        //ceilingTriggers = new List<SlopeZone>();
    }
        /// <summary>
        /// Move character while being contrained by collisions
        /// </summary>
        /// <param name="deltaPosition"></param>
        public void Move(Vector2 deltaPosition)
        {
            //---------------------------------------------------------------------------------------------------------
            // Reset the collision flags before invoking the movement method. If the user needs the flags from the
            // previous call they should be used before invoking this method. At this point the character will
            // move and recieve new flags.
            //---------------------------------------------------------------------------------------------------------
            collisionFlags = ResetCollisionFlags(collisionFlags);

            //---------------------------------------------------------------------------------------------------------
            // We first get our up direction and calculate the dot product with respect to the deltaposition direction.
            // We also calculate the total distance to travel this frame.
            //---------------------------------------------------------------------------------------------------------
            Vector2 upDirection = characterUpDirection;

            //---------------------------------------------------------------------------------------------------------
            // Initial cast. Here we use the full movement vector before splitting into side/up movement. If no
            // collisions are found we can early exit the method.
            //---------------------------------------------------------------------------------------------------------
            if (!MoveInitial(characterBody, deltaPosition, contactFilter, hitList, contactOffset))
            {
                return;
            }

            //---------------------------------------------------------------------------------------------------------
            // We decompose our movement into a normal and tangent vector with respect to the characters up direction.
            // The normal vector points in the direction of the up direction while the tangent Vector points
            // in the perpendicular direction to the normal with respect to the deltaposition. Remember that the
            // character up direction must be normalized and non zero.
            //---------------------------------------------------------------------------------------------------------
            Vector2 vertVector = upDirection * Vector2.Dot(upDirection, deltaPosition);
            Vector2 sideVector = deltaPosition - vertVector;

            //---------------------------------------------------------------------------------------------------------
            // Run the horizontal and vertical move methods. Note that we do the horizontal movement first. This
            // is by design. Changing the order of these calls requires a full remake of the controller.
            //---------------------------------------------------------------------------------------------------------
            collisionFlags |= MoveHorizontal(characterTransform, characterBody, sideVector, upDirection, contactFilter, hitList,
                                             contactOffset, positionIterations, collisionFlags, slopeLimit, slideOnGround, slideOnCeilings, forceSlide);
            collisionFlags |= MoveVertical(characterTransform, characterBody, vertVector, upDirection, contactFilter, hitList,
                                           contactOffset, positionIterations, collisionFlags, slopeLimit, slideOnGround, slideOnCeilings, forceSlide);
        }
        /// <summary>
        /// Contains casting and movement logic for the vertical movement of the character. This is a secondary movement meaning
        /// i only handles simple logic like moving down or up if not on the ground.
        /// </summary>
        /// <param name="body"></param>
        /// <param name="direction"></param>
        /// <param name="filter"></param>
        /// <param name="results"></param>
        /// <param name="skinWidth"></param>
        /// <param name="maxIterations"></param>
        protected CollisionFlags2D MoveVertical(Transform transform, Rigidbody2D body, Vector2 direction, Vector2 upDirection, ContactFilter2D filter,
                                                List <RaycastHit2D> results, float skinWidth, int maxIterations, CollisionFlags2D flags, float maxAngle, bool slideGround, bool slideCeilings, bool forceSlideCharacter)
        {
            //---------------------------------------------------------------------------------------------------------
            // Start up code before iteration
            //---------------------------------------------------------------------------------------------------------
            Vector2 currentPosition  = body.position;
            Vector2 currentDirection = direction;
            Vector2 targetPosition   = currentPosition + currentDirection;

            int i = 0;

            while (i < maxIterations)
            {
                //---------------------------------------------------------------------------------------------------------
                // Cast in the horizontal direction
                //---------------------------------------------------------------------------------------------------------
                float distance = currentDirection.magnitude;
                int   hits     = body.Cast(currentDirection, filter, results, distance + skinWidth);

                //---------------------------------------------------------------------------------------------------------
                // Nothing hit, move straight to target position
                //---------------------------------------------------------------------------------------------------------
                if (hits <= 0)
                {
                    currentPosition = targetPosition;
                    break;
                }

                //---------------------------------------------------------------------------------------------------------
                // Vertical collision detection
                // We first sort the list of hits and get the closest one. Then we update our the character controllers
                // position if the movement is larger than the skin width.
                //---------------------------------------------------------------------------------------------------------
                results.Sort((x, y) => y.distance.CompareTo(x.distance));
                RaycastHit2D hit = results[0];
                currentPosition = hit.distance > skinWidth ? currentPosition + currentDirection.normalized * (hit.distance - skinWidth) : currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // We decide if we hit ceiling or ground
                //---------------------------------------------------------------------------------------------------------
                float   normalDotUp       = Vector2.Dot(hit.normal, upDirection);
                Vector2 verticalDirection = Vector2.Dot(hit.normal, upDirection) >= 0 ? upDirection : -upDirection;
                float   angle             = Vector2.Angle(verticalDirection, hit.normal);

                //---------------------------------------------------------------------------------------------------------
                // Here we decide if we should apply collision responses on the given geometry during the vertical
                // collision call. We first find out if the character hits the ground or ceiling, then we
                // calculate the collision flags accordingly. In the end we apply collision responses.
                //---------------------------------------------------------------------------------------------------------
                bool applyCollisionResponse = false;

                if (normalDotUp >= 0)
                {
                    if (angle > maxAngle && slideGround && forceSlideCharacter)
                    {
                        applyCollisionResponse = true;
                    }

                    else
                    {
                        flags |= CollisionFlags2D.Bottom;
                    }
                }

                else
                {
                    if (angle > maxAngle && slideCeilings)
                    {
                        applyCollisionResponse = true;
                        flags |= CollisionFlags2D.Top;
                    }
                }

                targetPosition = applyCollisionResponse ? CollisionResponse(currentPosition, targetPosition, hit.normal, 0, 0) : currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // Calculate new target position and Move the body to current position
                //---------------------------------------------------------------------------------------------------------
                currentDirection = targetPosition - currentPosition;
                body.position    = currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // Exit the method if we reached our destination. This prevents the loop from iterating all the way
                // to maxIterations each time, saving computation time.
                //---------------------------------------------------------------------------------------------------------
                if (currentDirection.sqrMagnitude < Mathf.Epsilon * Mathf.Epsilon)
                {
                    break;
                }

                i++;
            }

            //---------------------------------------------------------------------------------------------------------
            // Final position update
            //---------------------------------------------------------------------------------------------------------
            body.position = currentPosition;
            return(flags);
        }
        /// <summary>
        /// Contains the primary logic for the horizontal casting and movement of the character controller. This is a dominant
        /// movement method, meaning the vertical movement is adjusted according to the horizontal movement. This method
        /// moves the character up/down slopes, checks the slope limit and does slope clamping. Also does ground
        /// checks in each iteration.
        /// </summary>
        /// <param name="body"></param>
        /// <param name="direction"></param>
        /// <param name="filter"></param>
        /// <param name="results"></param>
        /// <param name="skinWidth"></param>
        /// <param name="maxIterations"></param>
        protected CollisionFlags2D MoveHorizontal(Transform transform, Rigidbody2D body, Vector2 direction, Vector2 upDirection, ContactFilter2D filter,
                                                  List <RaycastHit2D> results, float skinWidth, int maxIterations, CollisionFlags2D flags, float maxAngle, bool slideGround, bool slideCeilings, bool forceSlideCharacter)
        {
            //---------------------------------------------------------------------------------------------------------
            // Start up code before iteration
            //---------------------------------------------------------------------------------------------------------
            Vector2 currentPosition  = body.position;
            Vector2 currentDirection = direction;
            Vector2 targetPosition   = currentPosition + currentDirection;

            int i = 0;

            while (i < maxIterations)
            {
                //---------------------------------------------------------------------------------------------------------
                // Cast in the horizontal direction
                //---------------------------------------------------------------------------------------------------------
                float distance = currentDirection.magnitude;
                int   hits     = body.Cast(currentDirection, filter, results, distance + skinWidth);

                //---------------------------------------------------------------------------------------------------------
                // Nothing hit, move straight to target position
                //---------------------------------------------------------------------------------------------------------
                if (hits <= 0)
                {
                    currentPosition = targetPosition;
                    break;
                }

                //---------------------------------------------------------------------------------------------------------
                // Horizontal collision detection
                // We first sort the list of hits and get the closest one. Then we update our the character controllers
                // position if the movement is larger than the skin width.
                //---------------------------------------------------------------------------------------------------------
                results.Sort((x, y) => y.distance.CompareTo(x.distance));
                RaycastHit2D hit = results[0];
                currentPosition = hit.distance > skinWidth ? currentPosition + currentDirection.normalized * (hit.distance - skinWidth) : currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // We decide if we hit ceiling or ground
                //---------------------------------------------------------------------------------------------------------
                float   normalDotUp       = Vector2.Dot(hit.normal, upDirection);
                Vector2 verticalDirection = normalDotUp >= 0 ? upDirection : -upDirection;
                float   angle             = Vector2.Angle(verticalDirection, hit.normal);

                //---------------------------------------------------------------------------------------------------------
                // Here we decide if we should apply collision responses on the given geometry during the horizontal
                // collision call. We first find out if the character hits the ground or ceiling. After this
                // we calculate if the character hit with it's back or front and set the collision flags
                // accordingly. In the end we apply collision responses
                //---------------------------------------------------------------------------------------------------------
                bool applyCollisionResponse = false;

                if (normalDotUp >= 0)
                {
                    if (angle <= maxAngle && slideGround)
                    {
                        applyCollisionResponse = true;
                    }
                }

                else
                {
                    if (angle <= maxAngle && slideCeilings)
                    {
                        applyCollisionResponse = true;
                    }
                }

                if (Vector2.Dot(transform.right, hit.normal) < 0)
                {
                    flags |= CollisionFlags2D.Front;
                }

                else
                {
                    flags |= CollisionFlags2D.Back;
                }

                targetPosition = applyCollisionResponse ? CollisionResponse(currentPosition, targetPosition, hit.normal, 0, 0) : currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // Move body to current position and get ready for the next iteration. If this is the last iteration,
                // the values calculated here will not be used.
                //---------------------------------------------------------------------------------------------------------
                currentDirection = targetPosition - currentPosition;
                body.position    = currentPosition;

                //---------------------------------------------------------------------------------------------------------
                // If we already reached our target we can exit the method. Saves us iterations in case we have many
                // position updates. If we didn't have this, the method would iterate to the end each time
                //---------------------------------------------------------------------------------------------------------
                if (currentDirection.sqrMagnitude < Mathf.Epsilon * Mathf.Epsilon)
                {
                    break;
                }

                i++;
            }

            //---------------------------------------------------------------------------------------------------------
            // Final position update
            //---------------------------------------------------------------------------------------------------------
            body.position = currentPosition;
            return(flags);
        }