예제 #1
0
    /// <summary>
    /// This function is called every fixed framerate frame, if the MonoBehaviour is enabled.
    /// </summary>
    void FixedUpdate()
    {
        // For convenience
        bodyBox = new Rect(
            bodyCd.bounds.min.x,
            bodyCd.bounds.min.y,
            bodyCd.bounds.size.x,
            bodyCd.bounds.size.y
            );

        // Set default values
        finalAccel = accel;

        // --- Gravity ---
        // If enemy is not grounded, apply gravity
        if (!grounded)
        {
            velocity = new Vector2(velocity.x, Mathf.Max(velocity.y - gravity, -maxFall));
        }

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

        // Determine first and last rays
        Vector2 minDownRay = new Vector2(bodyBox.xMin, bodyBox.center.y);
        Vector2 maxDownRay = new Vector2(bodyBox.xMax, bodyBox.center.y);

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

        // Check below for ground
        RaycastHit2D[] hitDownInfo         = new RaycastHit2D[vRays];
        bool           hit                 = false;
        float          closestDownHit      = float.MaxValue;
        int            closestDownHitIndex = 0;

        for (int i = 0; i < vRays; i++)
        {
            // Create and cast ray
            float   lerpDistance = (float)i / (float)(vRays - 1);
            Vector2 rayOrigin    = Vector2.Lerp(minDownRay, maxDownRay, lerpDistance);
            Ray2D   ray          = new Ray2D(rayOrigin, Vector2.down);
            hitDownInfo[i] = Physics2D.Raycast(rayOrigin, Vector2.down, rayDownDistance, RayLayers.downRay);

            // Check raycast results and keep track of closest ground hit
            if (hitDownInfo[i].fraction > 0)
            {
                hit = true;
                if (hitDownInfo[i].fraction < closestDownHit)
                {
                    closestDownHit      = hitDownInfo[i].fraction;
                    closestDownHitIndex = i;
                    closestDownHitInfo  = hitDownInfo[i];
                }
            }
        }

        // If enemy hits ground, snap to the closest ground
        if (hit)
        {
            // Check if enemy is landing this frame
            if (state.Has(MovementState.Falling) && state.Missing(MovementState.Landing))
            {
                if (OnLand != null)
                {
                    OnLand(this, System.EventArgs.Empty);
                }
                state = state.Include(MovementState.Landing);
                state = state.Remove(MovementState.Jumping);
            }
            grounded = true;
            state    = state.Remove(MovementState.Falling);
            velocity = new Vector2(velocity.x, 0);
        }
        else
        {
            grounded = false;
        }

        // --- Behavior ---
        // Idle behavior
        if (behaviorState.Has(BehaviorState.Idle))
        {
            behaviorTime += Time.deltaTime;

            // Set new behavior
            if (behaviorTime > idleBehaviorTime)
            {
                float newHAxis = Random.Range(-1, 1);
                // Flip sprite if changing direction of movement
                if (newHAxis != 0 && newHAxis != ((rend.flipX) ? -1 : 1))
                {
                    rend.flipX = !rend.flipX;
                }
                hAxis            = newHAxis;
                behaviorTime     = 0f;
                idleBehaviorTime = GetNewIdleBehaviorTime();
                //Debug.Log("Rolled: " + hAxis + " for " + idleBehaviorTime);
            }
        }

        // --- Lateral Collisions & Movement ---
        ApplyGroundEffects();

        // Move horizontally
        float newVelocityX = velocity.x;

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

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

        // Lateral collisions
        bool lateralCollision = false;

        // Determine first and last rays
        Vector2 minLatRay = new Vector2(bodyBox.center.x, bodyBox.yMin);
        Vector2 maxLatRay = new Vector2(bodyBox.center.x, bodyBox.yMax);

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

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

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

        // TODO: Ceiling collisions?
        // TODO: Check if accel/decel is making enemy sink into slopes
    }