/// <summary>
    /// This function is called every fixed framerate frame, if the MonoBehaviour is enabled.
    /// </summary>
    void FixedUpdate()
    {
        // For convenience
        box = new Rect(
            cd.bounds.min.x,
            cd.bounds.min.y,
            cd.bounds.size.x,
            cd.bounds.size.y
            );

        // Set default values
        finalJumpSpeed = jumpSpeed;
        finalAccel     = accel;

        // --- Input ---
        // TODO: Put this in update?
        // Get input and set idle state if applicable
        float hAxis     = 0;
        bool  dashInput = false;
        bool  jumpInput = false;

        // Restrict input if player is hit or attacking with restricted movement
        if (state.condState.Missing(PlayerState.Condition.Hit) && state.condState.Missing(PlayerState.Condition.RestrictedAttacking))
        {
            hAxis     = Input.GetAxisRaw("Horizontal");
            dashInput = Input.GetButtonDown("Fire1");
            jumpInput = Input.GetButton("Jump");
        }

        // Set facing direction
        if (hAxis != 0)
        {
            facing = new Vector2(hAxis, 0);
        }

        // Set idle state according to input
        if (hAxis == 0f && !dashInput && !jumpInput)
        {
            state.moveState = state.moveState.Include(PlayerState.Movement.Idle);
        }
        else
        {
            state.moveState = state.moveState.Remove(PlayerState.Movement.Idle);
        }

        // --- Gravity & Ground Check ---
        // Set flag to prevent player from jumping before character lands
        state.moveState = state.moveState.Remove(PlayerState.Movement.Landing);

        // If player is not grounded or sticking to wall, apply gravity
        if (!grounded && state.moveState.Missing(PlayerState.Movement.WallSticking) && state.moveState.Missing(PlayerState.Movement.WallSliding))
        {
            velocity = new Vector2(velocity.x, Mathf.Max(velocity.y - gravity, -maxFall));
        }

        // Check if player is currently falling
        if (velocity.y < 0)
        {
            if (state.moveState.Missing(PlayerState.Movement.Falling))
            {
                state.moveState = state.moveState.Include(PlayerState.Movement.Falling);
                if (OnFall != null)
                {
                    OnFall(this, System.EventArgs.Empty);
                }
            }
        }

        // Check for collisions below
        // (No need to check if player is in mid-air but not falling)
        if (grounded || state.moveState.Has(PlayerState.Movement.Falling))
        {
            // Determine first and last rays
            Vector2 minRay = new Vector2(box.xMin + margin, box.center.y);
            Vector2 maxRay = new Vector2(box.xMax - margin, box.center.y);

            // Calculate ray distance (if not grounded, set to current fall speed)
            float rayDistance = box.height / 2 + ((grounded) ? margin : Mathf.Abs(velocity.y * Time.deltaTime));

            // Check below for ground
            RaycastHit2D[] hitInfo         = new RaycastHit2D[vRays];
            bool           hit             = false;
            float          closestHit      = float.MaxValue;
            int            closestHitIndex = 0;
            for (int i = 0; i < vRays; i++)
            {
                // Create and cast ray
                float   lerpDistance = (float)i / (float)(vRays - 1);
                Vector2 rayOrigin    = Vector2.Lerp(minRay, maxRay, lerpDistance);
                Ray2D   ray          = new Ray2D(rayOrigin, Vector2.down);
                hitInfo[i] = Physics2D.Raycast(rayOrigin, Vector2.down, rayDistance, RayLayers.downRay);

                // Check raycast results and keep track of closest ground hit
                if (hitInfo[i].fraction > 0)
                {
                    hit = true;
                    if (hitInfo[i].fraction < closestHit)
                    {
                        closestHit      = hitInfo[i].fraction;
                        closestHitIndex = i;
                        closestHitInfo  = hitInfo[i];
                    }
                }
            }

            // If player hits ground, snap to the closest ground
            if (hit)
            {
                // Check if player is landing this frame
                if (state.moveState.Has(PlayerState.Movement.Falling) && state.moveState.Missing(PlayerState.Movement.Landing))
                {
                    if (OnLand != null)
                    {
                        OnLand(this, System.EventArgs.Empty);
                    }
                    state.moveState = state.moveState.Include(PlayerState.Movement.Landing);
                    state.moveState = state.moveState.Remove(PlayerState.Movement.Jumping);
                }
                grounded        = true;
                state.moveState = state.moveState.Remove(PlayerState.Movement.Falling);
                state.moveState = state.moveState.Remove(PlayerState.Movement.WallSticking | PlayerState.Movement.WallSliding);
                exitingDash     = false;
                Debug.DrawLine(box.center, hitInfo[closestHitIndex].point, Color.white, 1f);
                transform.Translate(Vector2.down * (hitInfo[closestHitIndex].distance - box.height / 2));
                velocity = new Vector2(velocity.x, 0);

                // Check if player is on a moving platform
                MovingPlatform newMovingPlatform = hitInfo[closestHitIndex].transform.parent.gameObject.GetComponent <MovingPlatform>();
                if (newMovingPlatform != null)
                {
                    movingPlatform = newMovingPlatform;
                    movingPlatform.GetOnPlatform(gameObject);
                }

                // Check ground for special attributes
                groundTypes = hitInfo[closestHitIndex].collider.gameObject.GetComponents <SpecialGround>();
            }
            else
            {
                grounded = false;
                // Clear ground properties
                groundTypes = Enumerable.Empty <SpecialGround>();
                if (movingPlatform != null)
                {
                    movingPlatform.GetOffPlatform(gameObject);
                    movingPlatform = null;
                }
            }
        }

        if (state.moveState.Has(PlayerState.Movement.Landing))
        {
            state.moveState = state.moveState.Remove(PlayerState.Movement.Dashing);
            exitingDash     = false;
        }

        // --- Lateral Movement & Collisions ---
        // Get input
        float newVelocityX = velocity.x;

        ApplyGroundEffects();

        // Move if input exists
        if (hAxis != 0)
        {
            newVelocityX += finalAccel * hAxis;
            // Clamp speed to max if not exiting a dash (in order to keep air momentum)
            newVelocityX = Mathf.Clamp(newVelocityX, -maxSpeed, maxSpeed);

            // Dash
            if (canDash)
            {
                if (dashInput)
                {
                    if (grounded && dashReady)
                    {
                        StartCoroutine(Dash());
                        newVelocityX = dashSpeed * hAxis;
                    }
                    else if ((state.moveState.Has(PlayerState.Movement.WallSticking) || state.moveState.Has(PlayerState.Movement.WallSliding)) & dashReady)
                    {
                        StartCoroutine(Dash());
                        //newVelocityX = dashSpeed * hAxis;
                    }
                }
            }

            // Account for slope
            if (Mathf.Abs(closestHitInfo.normal.x) > 0.1f)
            {
                float friction = 0.7f;
                newVelocityX = Mathf.Clamp((newVelocityX - (closestHitInfo.normal.x * friction)), -maxSpeed, maxSpeed);
                Vector2 newPosition = transform.position;
                newPosition.y     += -closestHitInfo.normal.x * Mathf.Abs(newVelocityX) * Time.deltaTime * ((newVelocityX - closestHitInfo.normal.x > 0) ? 1 : -1);
                transform.position = newPosition;
                state.moveState    = state.moveState.Remove(PlayerState.Movement.Landing);
            }
        }
        // Decelerate if moving without input
        else if (velocity.x != 0)
        {
            int decelDir = (velocity.x > 0) ? -1 : 1;
            // Ensure player doesn't decelerate past zero
            newVelocityX += (velocity.x > 0) ?
                            ((newVelocityX + finalAccel * decelDir) < 0) ?
                            -newVelocityX : finalAccel * decelDir
                                : ((newVelocityX + finalAccel * decelDir) > 0) ?
                            -newVelocityX : finalAccel * decelDir;
        }

        velocity = new Vector2(newVelocityX, velocity.y);

        // Check for lateral collisions
        lateralCollision = false;

        // (This condition will always be true, of course. It's temporary, but allows for moving platforms to push you while not riding them.)
        if (velocity.x != 0 || velocity.x == 0)
        {
            // Determine first and last rays
            Vector2 minRay = new Vector2(box.center.x, box.yMin);
            Vector2 maxRay = new Vector2(box.center.x, box.yMax);

            // Calculate ray distance and determine direction of movement
            float   rayDistance  = box.width / 2 + Mathf.Abs(newVelocityX * Time.deltaTime);
            Vector2 rayDirection = (newVelocityX > 0) ? Vector2.right : Vector2.left;

            RaycastHit2D[] hitInfo         = new RaycastHit2D[hRays];
            float          closestHit      = float.MaxValue;
            int            closestHitIndex = 0;
            float          lastFraction    = 0;
            int            numHits         = 0; // for debugging
            for (int i = 0; i < hRays; i++)
            {
                // Create and cast ray
                float   lerpDistance = (float)i / (float)(hRays - 1);
                Vector2 rayOrigin    = Vector2.Lerp(minRay, maxRay, lerpDistance);
                hitInfo[i] = Physics2D.Raycast(rayOrigin, rayDirection, rayDistance, RayLayers.sideRay);
                Debug.DrawRay(rayOrigin, rayDirection * rayDistance, Color.cyan, Time.deltaTime);
                // Check raycast results
                if (hitInfo[i].fraction > 0)
                {
                    lateralCollision = true;
                    numHits++;                     // for debugging
                    if (hitInfo[i].fraction < closestHit)
                    {
                        closestHit      = hitInfo[i].fraction;
                        closestHitIndex = i;
                    }
                    // If more than one ray hits, check the slope of what player is colliding with
                    if (lastFraction > 0)
                    {
                        float slopeAngle = Vector2.Angle(hitInfo[i].point - hitInfo[i - 1].point, Vector2.right);
                        //Debug.Log(Mathf.Abs(slopeAngle)); // for debugging
                        // If we hit a wall, snap to it
                        if (Mathf.Abs(slopeAngle - 90) < angleLeeway)
                        {
                            transform.Translate(rayDirection * (hitInfo[i].distance - box.width / 2));
                            if (OnLateralCollision != null)
                            {
                                OnLateralCollision(this, System.EventArgs.Empty);
                            }
                            velocity = new Vector2(0, velocity.y);

                            // Wall sticking
                            if (canStickWall && !grounded && (state.moveState.Missing(PlayerState.Movement.WallSticking) && state.moveState.Missing(PlayerState.Movement.WallSliding)))
                            {
                                // Only stick if moving towards wall
                                if (hAxis != 0 && ((hAxis < 0) == (rayDirection.x < 0)))
                                {
                                    state.moveState = state.moveState.Include(PlayerState.Movement.WallSticking);
                                    state.moveState = state.moveState.Remove(PlayerState.Movement.Jumping);
                                    wallDirection   = rayDirection;
                                    velocity        = new Vector2(0, 0);
                                    wallSlideTime   = Time.time + wallSlideDelay;
                                }
                            }

                            break;
                        }
                    }
                    lastFraction = hitInfo[i].fraction;
                }
            }

            // Wall sticking
            if (state.moveState.Has(PlayerState.Movement.WallSticking) || state.moveState.Has(PlayerState.Movement.WallSliding))
            {
                velocity = new Vector2(0, velocity.y);
                bool         onWall     = false;
                float        lowestY    = float.MaxValue;
                RaycastHit2D lowestYHit = new RaycastHit2D();

                // Check for wall regardless of horizontal velocity (allows player to hold direction away from wall)
                for (int i = 0; i < hRays; i++)
                {
                    // Create and cast ray
                    float   lerpDistance = (float)i / (float)(hRays - 1);
                    Vector2 rayOrigin    = Vector2.Lerp(minRay, maxRay, lerpDistance);
                    hitInfo[i] = Physics2D.Raycast(rayOrigin, wallDirection, (box.width / 2) + .001f, RayLayers.sideRay);
                    if (hitInfo[i].fraction > 0)
                    {
                        onWall = true;
                        if (hitInfo[i].point.y < lowestY)
                        {
                            lowestY    = hitInfo[i].point.y;
                            lowestYHit = hitInfo[i];
                        }
                    }
                }

                // If hitting wall while sliding, update wall slide PS position
                if (onWall && state.moveState.Has(PlayerState.Movement.WallSliding))
                {
                    if (currentWallSlidePS == null)
                    {
                        // TODO: Create a new PS if the old one hasn't disappeared yet
                        currentWallSlidePS = Instantiate(wallSlidePS, new Vector3(lowestYHit.point.x, lowestYHit.point.y, 0f), Quaternion.identity);
                    }
                    else
                    {
                        currentWallSlidePS.transform.position = new Vector3(lowestYHit.point.x, lowestYHit.point.y, 0f);
                        ParticleSystem ps = currentWallSlidePS.GetComponentInChildren <ParticleSystem>();
                        ps.Emit(1);
                        lastWallSlidePSEmission = Time.time;
                    }
                }
                // If no wall hit, end wallstick/slide
                if (!onWall)
                {
                    state.moveState = state.moveState.Remove(PlayerState.Movement.WallSticking | PlayerState.Movement.WallSliding);
                }
            }
            // If not wall sliding, remove PS
            else if (currentWallSlidePS != null)
            {
                Destroy(currentWallSlidePS, lastWallSlidePSEmission * .75f);
            }

            // Wall sliding
            if (state.moveState.Has(PlayerState.Movement.WallSticking) && Time.time >= wallSlideTime)
            {
                velocity        = Vector2.down * wallSlideSpeed;
                state.moveState = state.moveState.Remove(PlayerState.Movement.WallSticking);
                state.moveState = state.moveState.Include(PlayerState.Movement.WallSliding);
            }
        }

        // --- Jumping ---
        if (canJump && state.moveState.Missing(PlayerState.Movement.Landing))
        {
            // Prevent player from holding down jump to autobounce
            if (jumpInput && !jumpPressedLastFrame)
            {
                prevJumpDownTime = Time.time;
            }
            else if (!jumpInput)
            {
                prevJumpDownTime = 0f;
            }

            if (Time.time - prevJumpDownTime < jumpPressLeeway)
            {
                // Normal jump
                if (grounded)
                {
                    velocity         = new Vector2(velocity.x, finalJumpSpeed);
                    prevJumpDownTime = 0f;
                    canDoubleJump    = true;
                }
                // Wall jump
                else if (state.moveState.Has(PlayerState.Movement.WallSticking) || state.moveState.Has(PlayerState.Movement.WallSliding))
                {
                    velocity        = new Vector2(-wallDirection.x * wallJumpAwayDistance, finalJumpSpeed * .8f);
                    state.moveState = state.moveState.Remove(PlayerState.Movement.WallSticking | PlayerState.Movement.WallSliding);
                    canDoubleJump   = false;
                }
                // Double jump
                else if (!grounded && dashReady && canDoubleJump)
                {
                    velocity = new Vector2(velocity.x, finalJumpSpeed * .75f);
                    Instantiate(doubleJumpPS, new Vector2(box.center.x, box.center.y - box.height / 2), Quaternion.identity);
                    prevJumpDownTime = 0f;
                    canDoubleJump    = false;
                }
                state.moveState = state.moveState.Remove(PlayerState.Movement.Falling);
                state.moveState = state.moveState.Include(PlayerState.Movement.Jumping);
            }
            jumpPressedLastFrame = jumpInput;
        }


        // --- Ceiling Check ---
        // Only check if we're grounded or jumping
        if (grounded || velocity.y > 0)
        {
            // Determine first and last rays
            Vector2 minRay = new Vector2(box.xMin + margin, box.center.y);
            Vector2 maxRay = new Vector2(box.xMax - margin, box.center.y);

            // Calculate ray distance (if not grounded, set to current jump speed)
            float rayDistance = box.height / 2 + ((grounded) ? margin : velocity.y * Time.deltaTime);

            // Check above for ceiling
            RaycastHit2D[] hitInfo         = new RaycastHit2D[vRays];
            bool           hit             = false;
            float          closestHit      = float.MaxValue;
            int            closestHitIndex = 0;
            for (int i = 0; i < vRays; i++)
            {
                // Create and cast ray
                float   lerpDistance = (float)i / (float)(vRays - 1);
                Vector2 rayOrigin    = Vector2.Lerp(minRay, maxRay, lerpDistance);
                Ray2D   ray          = new Ray2D(rayOrigin, Vector2.up);
                hitInfo[i] = Physics2D.Raycast(rayOrigin, Vector2.up, rayDistance, RayLayers.upRay);

                // Check raycast results and keep track of closest ceiling hit
                if (hitInfo[i].fraction > 0)
                {
                    hit = true;
                    if (hitInfo[i].fraction < closestHit)
                    {
                        closestHit      = hitInfo[i].fraction;
                        closestHitIndex = i;
                    }
                }
            }

            // If we hit ceiling, snap to the closest ceiling
            // TODO: Maybe give rebound instead of snapping?
            if (hit)
            {
                transform.Translate(Vector3.up * (hitInfo[closestHitIndex].distance - box.height / 2));
                if (OnCeilingCollision != null)
                {
                    OnCeilingCollision(this, System.EventArgs.Empty);
                }
                velocity = new Vector2(velocity.x, 0);
            }
        }

        // --- Damage ---
        // Apply hit if detected by collider
        if (enemyHurtbox != null && state.condState.Missing(PlayerState.Condition.Hit) && state.condState.Missing(PlayerState.Condition.Recovering))
        {
            state.condState = state.condState.Include(PlayerState.Condition.Hit);
            state.condState = state.condState.Remove(PlayerState.Condition.Normal);
            state.moveState = state.moveState.Remove(PlayerState.Movement.Dashing | PlayerState.Movement.WallSticking | PlayerState.Movement.WallSliding);
            velocity        = Vector2.zero;
            StartCoroutine(ApplyHit(enemyHurtbox.damage, enemyHurtbox.knockback, enemyHurtbox.knockbackDirection));
        }

        //Debug.Log(state);
    }