Ejemplo n.º 1
0
 public CollisionMessageData(
     float thisImpulse, float otherImpulse,
     SurfaceContact primaryContact,
     List <SurfaceContact> contacts
     )
 {
     this.ThisImpulse    = thisImpulse;
     this.OtherImpulse   = otherImpulse;
     this.PrimaryContact = primaryContact;
     this.Contacts       = contacts;
 }
Ejemplo n.º 2
0
    private void AddForceAndReaction(Vector2 amountAndDir, ForceMode mode, SurfaceContact surfaceContact)
    {
        rigidbody2D.AddForce(amountAndDir, mode);

        if (surfaceContact != null)
        {
            var surfaceBody = surfaceContact.Collider.attachedRigidbody;

            if (surfaceBody != null)
            {
                amountAndDir = PhysicsHelper.ConvertToNewtons(amountAndDir, mode, surfaceBody.mass);
                surfaceBody.AddForce(-amountAndDir * SurfaceReactionFactor, ForceMode2D.Force);
            }
        }
    }
Ejemplo n.º 3
0
    private PhysicsMaterial2D GetSurfaceMaterial(SurfaceContact surfaceContact)
    {
        var surfaceMaterial = surfaceContact.Collider.sharedMaterial;

        if (surfaceMaterial == null)
        {
            if (DefaultSurfaceMaterial == null)
            {
                DefaultSurfaceMaterial = new PhysicsMaterial2D("DefaultSurfaceMaterial for the Character");
            }

            surfaceMaterial = DefaultSurfaceMaterial;
        }

        return(surfaceMaterial);
    }
Ejemplo n.º 4
0
    private float CalculateFrictionAccelFactor(SurfaceContact surfaceContact)
    {
        var surfaceMaterial = GetSurfaceMaterial(surfaceContact);

        float combinedFriction = PhysicsHelper.CombineFriction(
            collider2D.sharedMaterial.friction,
            surfaceMaterial.friction
            );

        float frictionAccelFactor = Mathf.Clamp(
            Mathf.Sqrt(combinedFriction),
            MinFrictionAccelFactor,
            MaxFrictionAccelFactor
            );

        return(frictionAccelFactor);
    }
Ejemplo n.º 5
0
    private void ProcessCollision(Collision2D collision)
    {
        foreach (var contact in collision.contacts)
        {
            var normal = contact.normal;
            var v      = ( Vector2 )collider2D.bounds.center - contact.point;

            /* Fix normal, as Unity (or Box2D) messing it up by randomly swapping
             * contact.collider and contact.otherCollider
             * as well as providing opposite normal direction. */
            if (Vector2.Dot(v, contact.normal) < 0)
            {
                normal = -normal;
            }

            if (CheckRelativeVelocity && Vector2.Dot(collision.relativeVelocity, normal) >= 0)
            {
                continue;
            }

            var surfaceContact = new SurfaceContact(contact.point, normal, collision.collider, Vector2.zero, 0);
            contacts.Add(surfaceContact);
        }
    }
Ejemplo n.º 6
0
    private bool CheckCanClimbStaircase(float horzMoveDir, float stepHeight, float stepDepth, out SurfaceContact staircaseContact)
    {
        staircaseContact = null;

        if (frameContacts.Count == 0)
        {
            return(false);
        }

        var circleCollider    = collider2D as CircleCollider2D;
        var center            = ( Vector2 )circleCollider.bounds.center;
        var horzMoveDirVector = new Vector2(horzMoveDir, 0);

        // Seek forward for obstacle to climb on.
        var steepestContact = frameContacts.WithMin(c => Vector2.Dot(c.Normal, horzMoveDirVector));

        if (Vector2.Dot(steepestContact.Normal, horzMoveDirVector) >= 0 || IsWalkable(steepestContact.Normal))
        {
            return(false);
        }

        staircaseContact = steepestContact;

        // Cast up.
        Debug.DrawRay(center, Vector2.up * stepHeight, Color.cyan);

        var hit = Spatial.CircleCastFiltered(
            center, circleCollider.radius, Vector2.up, stepHeight,
            h => h.rigidbody == this.rigidbody2D,
            contactsLayerMask
            );

        if (hit.collider != null)
        {
            Debug.DrawRay(hit.point, hit.normal * 0.1f, Color.red);

            return(false);
        }

        center += Vector2.up * stepHeight;

        // Cast forward.
        Debug.DrawRay(center, horzMoveDirVector * stepDepth, Color.cyan);

        hit = Spatial.CircleCastFiltered(
            center, circleCollider.radius, horzMoveDirVector, stepDepth,
            h => h.rigidbody == this.rigidbody2D,
            contactsLayerMask
            );

        if (hit.collider != null)
        {
            Debug.DrawRay(hit.point, hit.normal * 0.1f, Color.red);

            return(false);
        }

        return(true);
    }
Ejemplo n.º 7
0
    void FixedUpdate()
    {
        TrackContacts();

        bool controlWasLost = false;

        if (controlLossTimer > 0)
        {
            if (IsGrounded)
            {
                controlLossTimer -= Time.fixedDeltaTime;

                if (controlLossTimer < 0)
                {
                    controlLossTimer = 0;
                }

                if (controlLossTimer == 0)
                {
                    controlRegainStartTimestamp = Time.fixedTime;
                }
            }

            controlWasLost = true;
        }

        // Allow using item only in certain few states.
        inventoryUser.CarriedItemIsActive = false;

        // Allow performing idle action only in truly idle state.
        bool canPerformIdleAction = false;

        // Some active actions must be viewed by the camera.
        bool performedMoveAction = false;

        bool regainingControl = Time.fixedTime - controlRegainStartTimestamp < ControlRegainDuration;

        /* TODO: move groups of statements inside if/else into their own methods,
         * make FixedUpdate () method less "heavy" and more readable. */
        if (!IsGrounded && footWeldJoint != null)
        {
            /* Sometimes weld joint can move character into position where
             * no ground contacts can be found by circle trace method.
             * Such state is not valid since visually character is grounded,
             * but it's not able to move or jump. */
            BreakFootWeld();
        }

        if (regainingControl)
        {
            bumpedWhileControlWasLost = false;
            charController.SetState("ControlRegain", StateMaterials.ControlRegain);
        }
        else if (controlWasLost)
        {
            BreakFootWeld();
            IsPreparingToJump = false;

            var  velocity      = rigidbody2D.velocity;
            bool changeHeading = false;

            if (IsGrounded)
            {
                if (velocity.magnitude > ControlLostChangeHeadingMinVelocity)
                {
                    changeHeading = true;
                }
            }
            else if (velocity.magnitude > 0)
            {
                changeHeading = true;
            }

            if (AnyBumpBetweenFrames)
            {
                bumpedWhileControlWasLost = true;
            }

            if (changeHeading)
            {
                Heading = System.Math.Sign(velocity.x);
            }

            if (IsGrounded || bumpedWhileControlWasLost)
            {
                charController.SetState("ControlLostOnGround", StateMaterials.ControlLostOnGround);
            }
            else
            {
                charController.SetState("ControlLostMidAir", StateMaterials.ControlLostMidAir);
            }
        }
        else
        {
            if (IsPreparingToJump)
            {
                if (Time.fixedTime - jumpPreparationStartTimestamp >= JumpPreparationDuration)
                {
                    IsPreparingToJump = false;

                    if (IsGrounded)
                    {
                        BreakFootWeld();

                        float jumpDirAngle;
                        float jumpStartVelocity;

                        if (jumpForward)
                        {
                            jumpDirAngle      = ForwardJumpDirectionAngle;
                            jumpStartVelocity = ForwardJumpStartVelocity;
                            IsJumpingForward  = true;
                        }
                        else
                        {
                            jumpDirAngle      = BackJumpDirectionAngle;
                            jumpStartVelocity = BackJumpStartVelocity;
                            IsJumpingBackward = true;
                        }

                        float jumpDirAngleRad = jumpDirAngle * Mathf.Deg2Rad;
                        jumpDir    = new Vector2(Mathf.Cos(jumpDirAngleRad), Mathf.Sin(jumpDirAngleRad));
                        jumpDir.x *= Heading;

                        var   flatestFloorContact = FloorContacts.WithMax(c => Vector2.Dot(jumpDir, c.Normal));
                        float frictionAccelFactor = CalculateFrictionAccelFactor(flatestFloorContact);

                        if (frictionAccelFactor > 1)
                        {
                            frictionAccelFactor = 1;
                        }

                        var velocityChange = jumpDir * jumpStartVelocity;
                        velocityChange.x *= frictionAccelFactor;
                        AddForceAndReaction(velocityChange, ForceMode.VelocityChange, flatestFloorContact);
                        lastJumpTimestamp = Time.fixedTime;

                        stopJumpBounciness  = false;
                        performedMoveAction = true;

                        soundPlayer.PlayVariation(JumpSounds);
                        charController.SetState(IsJumpingForward ? "JumpingForward" : "JumpingBackward", StateMaterials.JumpBouncy);
                    }
                    else
                    {
                        // TODO: transfer control to fall-move processing code.
                        EnterStandState();
                    }
                }
                else
                {
                    charController.SetState("PrepareToJump", StateMaterials.Stand);
                }
            }
            else if (IsJumping)
            {
                bool isBouncy = Time.fixedTime - lastJumpTimestamp < JumpBouncinessDuration;

                if ((BumpTheFloorBetweenFrames || IsGrounded) && !isBouncy)
                {
                    IsJumpingForward  = false;
                    IsJumpingBackward = false;
                    EnterStandState();
                }
                else
                {
                    // Stop bouncing after first hit.
                    if (BumpTheWallOrCeilingBetweenFrames)
                    {
                        stopJumpBounciness = true;
                    }

                    StateMaterial stateMaterial;

                    if (isBouncy && !stopJumpBounciness)
                    {
                        // Enable character bounciness.
                        float angle = rigidbody2D.velocity.AngleRad();
                        float t     = Mathf.Abs(Mathf.Cos(angle));
                        stateMaterial = StateMaterial.Lerp(StateMaterials.JumpNormal, StateMaterials.JumpBouncy, t);
                    }
                    else
                    {
                        // Disable bouncing.
                        stateMaterial = StateMaterials.JumpNormal;
                    }

                    if (IsJumpingBackward && Time.fixedTime - lastJumpTimestamp < BackJumpSideAccelerationDuration)
                    {
                        /* Apply little amount of side acceleration so that the character
                         * can climb to the edge of a peak rather than bounce backward and fall down. */
                        int sign  = System.Math.Sign(jumpDir.x);
                        var accel = new Vector2(sign * BackJumpSideAcceleration, 0);
                        rigidbody2D.AddForce(accel, ForceMode.Acceleration);

                        DebugHelper.DrawRay(transform.position, accel, Color.blue, 0, false);
                    }

                    charController.SetState(IsJumpingForward ? "JumpingForward" : "JumpingBackward", stateMaterial);
                }
            }
            else
            {
                bool  movingDown   = Vector2.Dot(rigidbody2D.velocity, -Vector2.up) > 0;
                float downVelocity = movingDown ? rigidbody2D.velocity.magnitude : 0;
                // TODO: apply "Fall" animation after some time of being in the air. Velocity should not be accounted.

                if (!IsGrounded && downVelocity >= FallVelocity)
                {
                    charController.SetState("Fall", StateMaterials.JumpNormal);
                }
                else
                {
                    bool  noMovementPerformed = false;
                    var   move       = new Vector2(MoveX, MoveY);
                    float moveAmount = move.magnitude;

                    if (moveAmount != 0)
                    {
                        var moveDir = move.normalized;

                        if (moveAmount > 1)
                        {
                            moveAmount = 1;
                        }

                        SurfaceContact surfaceContact      = null;
                        Vector2        moveDirAlongSurface = Vector2.zero;
                        float          surfaceAcceleration = 0;
                        float          gravityResistance   = 0;
                        float          maxVelocity         = 0;
                        bool           wantToWalk          = false;
                        bool           canTurn             = false;

                        if (IsGrounded)
                        {
                            canTurn = true;
                            var steepestFloorContact = FloorContacts.WithMin(c => Vector2.Dot(moveDir, c.Normal));
                            var tangent = Common.RightOrthogonal(steepestFloorContact.Normal);
                            moveDirAlongSurface = Vector2.Dot(moveDir, tangent) > 0 ? tangent : -tangent;

                            bool noObstaclesOnTheWay = frameContacts
                                                       .All(c =>
                                                            Vector2.Dot(c.Normal, moveDirAlongSurface) >= 0 ||
                                                            // Push dynamic body if it doesn't belong to other character.
                                                            (c.Collider.attachedRigidbody != null && !IsCharacter(c.Collider))

                                                            /* TODO: check mass of the body? Presumably evaluate to false when object can't be moved.
                                                             * UPD: well, it depends on friction force applied from the floor to an obstacle object,
                                                             * so everything is much more complicated.. */
                                                            );

                            if (noObstaclesOnTheWay)
                            {
                                wantToWalk          = true;
                                surfaceContact      = steepestFloorContact;
                                surfaceAcceleration = WalkAcceleration;
                                gravityResistance   = WalkGravityResistance;
                                maxVelocity         = MaxWalkVelocity;
                            }
                            else
                            {
                                var obstacleContact = frameContacts.First(c => Vector2.Dot(c.Normal, moveDirAlongSurface) < 0);
                                Debug.DrawRay(obstacleContact.Point, obstacleContact.Normal * 0.1f, Color.magenta);
                            }
                        }

                        if (!wantToWalk)
                        {
                            SurfaceContact staircaseContact;
                            bool           canClimb = CheckCanClimbStaircase(moveDir.x, ClimbStepHeight, ClimbStepDepth, out staircaseContact);

                            if (canClimb)
                            {
                                canTurn        = true;
                                wantToWalk     = true;
                                surfaceContact = staircaseContact;

                                float climbMoveDirAngleRad = ClimbMovementDirectionAngle * Mathf.Deg2Rad;
                                var   climbDir             = new Vector2(Mathf.Cos(climbMoveDirAngleRad), Mathf.Sin(climbMoveDirAngleRad));
                                climbDir.x         *= moveDir.x;
                                moveDirAlongSurface = climbDir;

                                surfaceAcceleration = ClimbAcceleration;
                                gravityResistance   = ClimbGravityResistance;
                                maxVelocity         = MaxClimbVelocity;
                            }
                        }

                        if (wantToWalk)
                        {
                            BreakFootWeld();
                            StateMaterial stateMaterial;

                            if (IsCharacter(surfaceContact.Collider))
                            {
                                stateMaterial = StateMaterials.StepOverCharacter;
                            }
                            else
                            {
                                stateMaterial = StateMaterials.Walk;
                            }

                            var otherRigidbody   = surfaceContact.Collider.attachedRigidbody;
                            var platformVelocity = Vector2.zero;

                            if (otherRigidbody != null)
                            {
                                platformVelocity = otherRigidbody.GetPointVelocity(surfaceContact.Point);
                            }

                            var relVelocity          = rigidbody2D.velocity - platformVelocity;
                            var velocityAlongSurface = Common.Project(relVelocity, moveDirAlongSurface);

                            float frictionAccelFactor = CalculateFrictionAccelFactor(surfaceContact);
                            surfaceAcceleration *= frictionAccelFactor;

                            var     velocityDelta           = moveDirAlongSurface * surfaceAcceleration * Time.fixedDeltaTime;
                            var     newVelocityAlongSurface = velocityAlongSurface + velocityDelta;
                            Vector2 velocityChange;

                            // DEV: new approach.
                            // TODO: slow down when walking down steep ground.
                            if (newVelocityAlongSurface.magnitude > maxVelocity)
                            {
                                if (newVelocityAlongSurface.magnitude > velocityAlongSurface.magnitude)
                                {
                                    if (velocityAlongSurface.magnitude < maxVelocity)
                                    {
                                        velocityChange = velocityAlongSurface.normalized * maxVelocity - velocityAlongSurface;
                                    }
                                    else
                                    {
                                        velocityChange = Vector2.zero;
                                    }
                                }
                                else
                                {
                                    velocityChange = newVelocityAlongSurface - velocityAlongSurface;
                                }
                            }
                            else
                            {
                                velocityChange = newVelocityAlongSurface - velocityAlongSurface;
                            }

                            // TODO: old approach.
                            //newVelocityAlongSurface = Vector2.ClampMagnitude ( newVelocityAlongSurface, maxVelocity );
                            //velocityChange = newVelocityAlongSurface - velocityAlongSurface;

                            Debug.DrawRay(transform.position, velocityChange, Color.red, 0, false);

                            AddForceAndReaction(velocityChange, ForceMode.VelocityChange, surfaceContact);
                            AddForceAndReaction(-Physics2D.gravity * gravityResistance, ForceMode.Acceleration, surfaceContact);

                            soundPlayer.PlayVariation(WalkSounds, restart: false);
                            charController.SetState("Walk", stateMaterial);
                        }
                        else
                        {
                            noMovementPerformed = true;
                        }

                        if (canTurn)
                        {
                            var headingScale = headingTransform.localScale;
                            headingScale.x = moveDir.x;
                            headingTransform.localScale = headingScale;
                        }
                    }
                    else
                    {
                        noMovementPerformed = true;
                    }

                    bool readyToJump        = Time.fixedTime - lastJumpTimestamp >= JumpCooldown;
                    bool wantsToPerformJump = PerformForwardJump || PerformBackJump;

                    if (wantsToPerformJump && IsGrounded && readyToJump)
                    {
                        jumpPreparationStartTimestamp = Time.fixedTime;
                        IsPreparingToJump             = true;

                        /* TODO: disable IsPreparingToJump when control lost.
                         * And maybe in some other cases too. */
                        jumpForward = PerformForwardJump;
                    }

                    if (noMovementPerformed)
                    {
                        canPerformIdleAction = true;
                        bool performedIdleAction = false;

                        if (IsStandingStill(FootWeldMaxVelocity))                               // TODO: use other value instead of FootWeldMaxVelocity?
                        {
                            performedIdleAction = charController.PerformIdleAction();
                        }

                        if (!performedIdleAction)
                        {
                            EnterStandState();
                        }
                    }
                    else
                    {
                        performedMoveAction = true;
                    }
                }
            }
        }

        if (!canPerformIdleAction)
        {
            charController.InterruptIdleAction();
        }

        if (performedMoveAction)
        {
            CameraEvents.Moved.Restart();
        }
    }
Ejemplo n.º 8
0
    // TODO: make predicate instead of ignorePredicate (accept "true" instead of "false").
    public static List <SurfaceContact> GetCircleContacts(
        Vector2 center, float radius, float minDistance, float maxDistance,
        System.Func <RaycastHit2D, bool> ignorePredicate = null,
        int numSweepDirections = 4,
        int layerMask          = Physics2D.DefaultRaycastLayers
        )
    {
        var   contacts      = new List <SurfaceContact> ();
        float angle         = 0;
        float dAngle        = Mathf.PI * 2 / numSweepDirections;
        float offset        = radius - minDistance + CastOffset;
        float radiusSq      = radius * radius;
        float sweepDistance = maxDistance - minDistance + CastOffset * 2;

        // DEBUG
        //DebugHelper.DrawCircle ( center, minDistance, new Color ( 1, 0.5f, 0.25f ) );
        //DebugHelper.DrawCircle ( center, maxDistance, new Color ( 1, 0.5f, 0.25f ) );

        for (int i = 0; i < numSweepDirections; i++, angle += dAngle)
        {
            var dir = new Vector2(
                Mathf.Cos(angle),
                Mathf.Sin(angle)
                );

            var origin = center - dir * offset;
            var hits   = Physics2D.CircleCastAll(origin, radius, dir, sweepDistance, layerMask);
            //Debug.DrawRay ( hits [1].point, hits [1].normal, Color.red );
            //DebugHelper.DrawCircle ( hits [1].point, 0.1f, Color.yellow );
            //Debug.Log ( hits [1].collider );
            //DebugHelper.DrawCircle ( origin, radius, Color.yellow );

            for (int j = 0; j < hits.Length; j++)
            {
                var hit = hits [j];

                // TODO: make argument "acceptInnerCollisions"

                /* TODO: make option to discard inner collisions when outer collisions for the same collider were found.
                 * UPD: or perform this by default? */
                // Filter inner collisions and fix their normals.
                if (hit.distance <= 0)
                {
                    // Some points can be out of original circle.
                    float distFromCenterSq = Common.DistanceSq(hit.point, center);

                    if (distFromCenterSq > radiusSq)
                    {
                        continue;
                    }

                    /* Some inner collisions are reported outside of bounding box
                     * of other collider. Filter them out as they're quite inaccurate. */
                    var otherBounds      = hit.collider.bounds;
                    var hitPointInZPlane = ( Vector3 )hit.point;
                    hitPointInZPlane.z = otherBounds.center.z;
                    bool hitIsInsideBb = otherBounds.Contains(hitPointInZPlane);

                    if (!hitIsInsideBb)
                    {
                        continue;
                    }

                    /* Now we definitely sure that this is appropriate inner collision
                     * and we can fix its normal. */
                    var vToCenter = center - hit.point;
                    hit.normal = vToCenter.normalized;
                }

                if (ignorePredicate != null && ignorePredicate(hit))
                {
                    continue;
                }

                var   vToPoint = hit.point - center;
                float distance = vToPoint.magnitude;

                if (distance < minDistance || distance > maxDistance)
                {
                    continue;
                }

                var dirToPoint = vToPoint.normalized;

                var contact = new SurfaceContact(
                    hit.point, hit.normal,
                    hit.collider,
                    dirToPoint, distance
                    );
                contacts.Add(contact);
            }
        }

        const float sameVertexDistanceThreshold   = 0.001f;             // TODO: promote to arguments.
        const float sameVertexDistanceThresholdSq = sameVertexDistanceThreshold * sameVertexDistanceThreshold;

        for (int i = 0; i < contacts.Count; i++)
        {
            var  c1 = contacts [i];
            bool foundCloseVerices = false;

            for (int j = contacts.Count - 1; j > i; j--)
            {
                var c2 = contacts [j];

                if (c1.Collider == c2.Collider &&
                    Common.DistanceSq(c1.Point, c2.Point) < sameVertexDistanceThresholdSq
                    )
                {
                    contacts.RemoveAt(j);
                    foundCloseVerices = true;
                }
            }

            if (foundCloseVerices)
            {
                // Most probable it's a corner collision. Calculate precise normal.
                c1.Normal = (center - c1.Point).normalized;
            }
        }

        return(contacts);
    }
Ejemplo n.º 9
0
 public ContactForceFactor(SurfaceContact contact, float factor)
 {
     this.Contact = contact;
     this.Factor  = factor;
 }