/// <summary> /// Casts rays to the sides of the character, from its center axis. /// If we hit a wall/slope, we check its angle and move or not according to it. /// </summary> protected override void CastRaysToTheSides(float raysDirection) { // we determine the origin of our rays _horizontalRayCastFromBottom = (_boundsBottomRightCorner + _boundsBottomLeftCorner) / 2; _horizontalRayCastToTop = (_boundsTopLeftCorner + _boundsTopRightCorner) / 2; _horizontalRayCastFromBottom = _horizontalRayCastFromBottom + (Vector2)transform.up * _obstacleHeightTolerance; _horizontalRayCastToTop = _horizontalRayCastToTop - (Vector2)transform.up * _obstacleHeightTolerance; // we determine the length of our rays float horizontalRayLength = Mathf.Abs(_speed.x * Time.deltaTime) + _boundsWidth / 2 + RayOffset * 2; // we resize our storage if needed if (_sideHitsStorage.Length != NumberOfHorizontalRays) { _sideHitsStorage = new RaycastHit2D[NumberOfHorizontalRays]; } SetTemporaryLayer(); // we cast rays to the sides for (int i = 0; i < NumberOfHorizontalRays; i++) { Vector2 rayOriginPoint = Vector2.Lerp(_horizontalRayCastFromBottom, _horizontalRayCastToTop, (float)i / (float)(NumberOfHorizontalRays - 1)); // if we were grounded last frame and if this is our first ray, we don't cast against one way platforms if (State.WasGroundedLastFrame && i == 0) { _sideHitsStorage[i] = MMDebug.RayCast(rayOriginPoint, raysDirection * (transform.right), horizontalRayLength, PlatformMask, MMColors.Indigo, Parameters.DrawRaycastsGizmos); } else { _sideHitsStorage[i] = MMDebug.RayCast(rayOriginPoint, raysDirection * (transform.right), horizontalRayLength, PlatformMask & ~OneWayPlatformMask & ~MovingOneWayPlatformMask, MMColors.Indigo, Parameters.DrawRaycastsGizmos); } // if we've hit something if (_sideHitsStorage[i].distance > 0) { // if this collider is on our ignore list, we break if (_sideHitsStorage[i].collider == _ignoredCollider) { break; } // we determine and store our current lateral slope angle float hitAngle = Mathf.Abs(Vector2.Angle(_sideHitsStorage[i].normal, transform.up)); if (OneWayPlatformMask.MMContains(_sideHitsStorage[i].collider.gameObject)) { if (hitAngle > 90) { break; } } // we check if this is our movement direction if (_movementDirection == raysDirection) { State.LateralSlopeAngle = hitAngle; } // if the lateral slope angle is higher than our maximum slope angle, then we've hit a wall, and stop x movement accordingly if (hitAngle > Parameters.MaximumSlopeAngle) { if (raysDirection < 0) { State.IsCollidingLeft = true; State.DistanceToLeftCollider = _sideHitsStorage[i].distance; } else { State.IsCollidingRight = true; State.DistanceToRightCollider = _sideHitsStorage[i].distance; } if (_movementDirection == raysDirection) { CurrentWallCollider = _sideHitsStorage[i].collider.gameObject; State.SlopeAngleOK = false; float distance = MMMaths.DistanceBetweenPointAndLine(_sideHitsStorage[i].point, _horizontalRayCastFromBottom, _horizontalRayCastToTop); if (raysDirection <= 0) { _newPosition.x = -distance + _boundsWidth / 2 + RayOffset * 2; } else { _newPosition.x = distance - _boundsWidth / 2 - RayOffset * 2; } // if we're in the air, we prevent the character from being pushed back. if (!State.IsGrounded && (Speed.y != 0)) { _newPosition.x = 0; } _contactList.Add(_sideHitsStorage[i]); _speed.x = 0; } break; } } } SetOriginalLayer(); }
/// <summary> /// Every frame, we cast a number of rays below our character to check for platform collisions /// </summary> protected override void CastRaysBelow() { _friction = 0; if (_newPosition.y < -_smallValue) { State.IsFalling = true; } else { State.IsFalling = false; } if ((Parameters.Gravity > 0) && (!State.IsFalling)) { State.IsCollidingBelow = false; return; } float rayLength = (_boundsHeight / 2) + RayOffset; if (State.OnAMovingPlatform) { rayLength *= 2; } if (_newPosition.y < 0) { rayLength += Mathf.Abs(_newPosition.y); } _verticalRayCastFromLeft = (_boundsBottomLeftCorner + _boundsTopLeftCorner) / 2; _verticalRayCastToRight = (_boundsBottomRightCorner + _boundsTopRightCorner) / 2; _verticalRayCastFromLeft += (Vector2)transform.up * RayOffset; _verticalRayCastToRight += (Vector2)transform.up * RayOffset; _verticalRayCastFromLeft += (Vector2)transform.right * _newPosition.x; _verticalRayCastToRight += (Vector2)transform.right * _newPosition.x; if (_belowHitsStorage.Length != NumberOfVerticalRays) { _belowHitsStorage = new RaycastHit2D[NumberOfVerticalRays]; } _raysBelowLayerMaskPlatforms = PlatformMask; _raysBelowLayerMaskPlatformsWithoutOneWay = PlatformMask & ~MidHeightOneWayPlatformMask & ~OneWayPlatformMask & ~MovingOneWayPlatformMask; _raysBelowLayerMaskPlatformsWithoutMidHeight = _raysBelowLayerMaskPlatforms & ~MidHeightOneWayPlatformMask; // if what we're standing on is a mid height oneway platform, we turn it into a regular platform for this frame only if (StandingOnLastFrame != null) { _savedBelowLayer = StandingOnLastFrame.layer; if (MidHeightOneWayPlatformMask.MMContains(StandingOnLastFrame.layer)) { StandingOnLastFrame.layer = LayerMask.NameToLayer("Platforms"); } } // if we were grounded last frame, and not on a one way platform, we ignore any one way platform that would come in our path. if (State.WasGroundedLastFrame) { if (StandingOnLastFrame != null) { if (!MidHeightOneWayPlatformMask.MMContains(StandingOnLastFrame.layer)) { _raysBelowLayerMaskPlatforms = _raysBelowLayerMaskPlatformsWithoutMidHeight; } } } // stairs management if (State.WasGroundedLastFrame) { if (StandingOnLastFrame != null) { if (StairsMask.MMContains(StandingOnLastFrame.layer)) { // if we're still within the bounds of the stairs if (StandingOnCollider.bounds.Contains(_colliderBottomCenterPosition)) { _raysBelowLayerMaskPlatforms = _raysBelowLayerMaskPlatforms & ~OneWayPlatformMask | StairsMask; } } } } if (State.OnAMovingPlatform && (_newPosition.y > 0)) { _raysBelowLayerMaskPlatforms = _raysBelowLayerMaskPlatforms & ~OneWayPlatformMask; } float smallestDistance = float.MaxValue; int smallestDistanceIndex = 0; bool hitConnected = false; SetTemporaryLayer(); for (int i = 0; i < NumberOfVerticalRays; i++) { Vector2 rayOriginPoint = Vector2.Lerp(_verticalRayCastFromLeft, _verticalRayCastToRight, (float)i / (float)(NumberOfVerticalRays - 1)); if ((_newPosition.y > 0) && (!State.WasGroundedLastFrame)) { _belowHitsStorage[i] = MMDebug.RayCast(rayOriginPoint, -transform.up, rayLength, _raysBelowLayerMaskPlatformsWithoutOneWay, Color.blue, Parameters.DrawRaycastsGizmos); } else { _belowHitsStorage[i] = MMDebug.RayCast(rayOriginPoint, -transform.up, rayLength, _raysBelowLayerMaskPlatforms, Color.blue, Parameters.DrawRaycastsGizmos); } float distance = MMMaths.DistanceBetweenPointAndLine(_belowHitsStorage [smallestDistanceIndex].point, _verticalRayCastFromLeft, _verticalRayCastToRight); if (_belowHitsStorage[i]) { if (_belowHitsStorage[i].collider == _ignoredCollider) { continue; } hitConnected = true; State.BelowSlopeAngle = Vector2.Angle(_belowHitsStorage[i].normal, transform.up); _crossBelowSlopeAngle = Vector3.Cross(transform.up, _belowHitsStorage [i].normal); if (_crossBelowSlopeAngle.z < 0) { State.BelowSlopeAngle = -State.BelowSlopeAngle; } if (_belowHitsStorage[i].distance < smallestDistance) { smallestDistanceIndex = i; smallestDistance = _belowHitsStorage[i].distance; } } if (distance < _smallValue) { break; } } SetOriginalLayer(); if (hitConnected) { StandingOn = _belowHitsStorage[smallestDistanceIndex].collider.gameObject; StandingOnCollider = _belowHitsStorage [smallestDistanceIndex].collider; // if the character is jumping onto a (1-way) platform but not high enough, we do nothing if ( !State.WasGroundedLastFrame && (smallestDistance < _boundsHeight / 2) && ( OneWayPlatformMask.MMContains(StandingOn.layer) || MovingOneWayPlatformMask.MMContains(StandingOn.layer) ) ) { State.IsCollidingBelow = false; return; } State.IsFalling = false; State.IsCollidingBelow = true; // if we're applying an external force (jumping, jetpack...) we only apply that if (_externalForce.y > 0 && _speed.y > 0) { _newPosition.y = _speed.y * Time.deltaTime; State.IsCollidingBelow = false; } // if not, we just adjust the position based on the raycast hit else { float distance = MMMaths.DistanceBetweenPointAndLine(_belowHitsStorage [smallestDistanceIndex].point, _verticalRayCastFromLeft, _verticalRayCastToRight); _newPosition.y = -distance + _boundsHeight / 2 + RayOffset; } if (!State.WasGroundedLastFrame && _speed.y > 0) { _newPosition.y += _speed.y * Time.deltaTime; } if (Mathf.Abs(_newPosition.y) < _smallValue) { _newPosition.y = 0; } // we check if whatever we're standing on applies a friction change _frictionTest = _belowHitsStorage[smallestDistanceIndex].collider.gameObject.MMGetComponentNoAlloc <SurfaceModifier>(); if (_frictionTest != null) { _friction = _belowHitsStorage[smallestDistanceIndex].collider.GetComponent <SurfaceModifier>().Friction; } // we check if the character is standing on a moving platform _movingPlatformTest = _belowHitsStorage[smallestDistanceIndex].collider.gameObject.MMGetComponentNoAlloc <MMPathMovement>(); if (_movingPlatformTest != null && State.IsGrounded) { _movingPlatform = _movingPlatformTest.GetComponent <MMPathMovement>(); } else { DetachFromMovingPlatform(); } } else { State.IsCollidingBelow = false; if (State.OnAMovingPlatform) { DetachFromMovingPlatform(); } } if (StickToSlopes) { StickToSlope(); } }