void moveVertically(ref FixVec2 deltaMovement) { var isGoingUp = deltaMovement.y > 0; var rayDistance = FixMath.Abs(deltaMovement.y) + _skinWidth; var rayDirection = isGoingUp ? FixVec2.up : -FixVec2.up; var initialRayOrigin = isGoingUp ? _raycastOrigins.topLeft : _raycastOrigins.bottomLeft; // apply our horizontal deltaMovement here so that we do our raycast from the actual position we would be in if we had moved initialRayOrigin.x += deltaMovement.x; // if we are moving up, we should ignore the layers in oneWayPlatformMask var mask = platformMask; if ((isGoingUp && !collisionState.wasGroundedLastFrame) || ignoreOneWayPlatformsThisFrame) { mask &= ~oneWayPlatformMask; } for (var i = 0; i < totalVerticalRays; i++) { FixVec2 ray = new FixVec2(initialRayOrigin.x + i * _horizontalDistanceBetweenRays, initialRayOrigin.y); DrawRay((Vector3)ray, (Vector3)(rayDirection * rayDistance), Color.red); _raycastHit = TFPhysics.Raycast(ray, rayDirection, rayDistance, mask); if (_raycastHit) { // set our new deltaMovement and recalculate the rayDistance taking it into account deltaMovement.y = _raycastHit.point.y - ray.y; rayDistance = FixMath.Abs(deltaMovement.y); // remember to remove the skinWidth from our deltaMovement if (isGoingUp) { deltaMovement.y -= _skinWidth; collisionState.above = true; } else { deltaMovement.y += _skinWidth; collisionState.below = true; } _raycastHitsThisFrame.Add(_raycastHit); // this is a hack to deal with the top of slopes. if we walk up a slope and reach the apex we can get in a situation // where our ray gets a hit that is less then skinWidth causing us to be ungrounded the next frame due to residual velocity. if (!isGoingUp && deltaMovement.y > Fix.zero) { _isGoingUpSlope = true; } // we add a small fudge factor for the float operations here. if our rayDistance is smaller // than the width + fudge bail out because we have a direct impact if (rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor) { break; } } } }
private void Update() { if (Input.GetMouseButtonDown(0)) { var v3 = Input.mousePosition; v3.z = 0; v3 = Camera.main.ScreenToWorldPoint(v3); FixVec2 pointB = new FixVec2((Fix)v3.x, (Fix)v3.y); TFRaycastHit2D h = TFPhysics.Raycast((FixVec2)tfTransform.Position, (FixVec2)(pointB - tfTransform.Position).Normalized(), 5, platformMask); } }
/// <summary> /// checks the center point under the BoxCollider2D for a slope. If it finds one then the deltaMovement is adjusted so that /// the player stays grounded and the slopeSpeedModifier is taken into account to speed up movement. /// </summary> /// <param name="deltaMovement">Delta movement.</param> private void handleVerticalSlope(ref FixVec2 deltaMovement) { // slope check from the center of our collider Fix centerOfCollider = (_raycastOrigins.bottomLeft.x + _raycastOrigins.bottomRight.x) * (Fix.one / 2); FixVec2 rayDirection = -FixVec2.up; // the ray distance is based on our slopeLimit Fix slopeCheckRayDistance = _slopeLimitTangent * (_raycastOrigins.bottomRight.x - centerOfCollider); FixVec2 slopeRay = new FixVec2(centerOfCollider, _raycastOrigins.bottomLeft.y); _raycastHit = TFPhysics.Raycast(slopeRay, rayDirection, slopeCheckRayDistance, platformMask); if (_raycastHit) { // bail out if we have no slope var angle = FixVec2.Angle(_raycastHit.normal, FixVec2.up); if (angle == 0) { return; } // we are moving down the slope if our normal and movement direction are in the same x direction var isMovingDownSlope = FixMath.Sign(_raycastHit.normal.x) == FixMath.Sign(deltaMovement.x); if (isMovingDownSlope) { // going down we want to speed up in most cases so the slopeSpeedMultiplier curve should be > 1 for negative angles Fix slopeModifier = (Fix)slopeSpeedMultiplier.Evaluate((float)-angle); // we add the extra downward movement here to ensure we "stick" to the surface below deltaMovement.y += _raycastHit.point.y - slopeRay.y - skinWidth; deltaMovement = new FixVec2(deltaMovement.x, deltaMovement.y) + new FixVec2(deltaMovement.x * slopeModifier * (1 - (angle / 90)), (deltaMovement.x * slopeModifier * (angle / 90))); collisionState.movingDownSlope = true; collisionState.slopeAngle = angle; } } }
/// <summary> /// handles adjusting deltaMovement if we are going up a slope. /// </summary> /// <returns><c>true</c>, if horizontal slope was handled, <c>false</c> otherwise.</returns> /// <param name="deltaMovement">Delta movement.</param> /// <param name="angle">Angle.</param> bool handleHorizontalSlope(ref FixVec2 deltaMovement, Fix angle) { // disregard 90 degree angles (walls) if (FixMath.Round(angle) == 90) { return(false); } if (angle < slopeLimit) { // we only need to adjust the deltaMovement if we are not jumping // TODO: this uses a magic number which isn't ideal! The alternative is to have the user pass in if there is a jump this frame if (deltaMovement.y < jumpingThreshold) { // apply the slopeModifier to slow our movement up the slope Fix slopeModifier = (Fix)slopeSpeedMultiplier.Evaluate((float)angle); deltaMovement.x *= slopeModifier; // we dont set collisions on the sides for this since a slope is not technically a side collision. // smooth y movement when we climb. we make the y movement equivalent to the actual y location that corresponds // to our new x location using our good friend Pythagoras deltaMovement.y = FixMath.Abs(FixMath.Tan(angle * FixMath.Deg2Rad) * deltaMovement.x); var isGoingRight = deltaMovement.x > 0; // safety check. we fire a ray in the direction of movement just in case the diagonal we calculated above ends up // going through a wall. if the ray hits, we back off the horizontal movement to stay in bounds. var ray = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft; TFRaycastHit2D raycastHit; if (collisionState.wasGroundedLastFrame) { raycastHit = TFPhysics.Raycast((FixVec2)ray, (FixVec2)deltaMovement.Normalized(), deltaMovement.GetMagnitude(), platformMask); } else { raycastHit = TFPhysics.Raycast((FixVec2)ray, (FixVec2)deltaMovement.Normalized(), deltaMovement.GetMagnitude(), platformMask & ~oneWayPlatformMask); } if (raycastHit) { // we crossed an edge when using Pythagoras calculation, so we set the actual delta movement to the ray hit location deltaMovement = raycastHit.point - ray; if (isGoingRight) { deltaMovement.x -= _skinWidth; } else { deltaMovement.x += _skinWidth; } } _isGoingUpSlope = true; collisionState.below = true; collisionState.slopeAngle = -angle; } } else // too steep. get out of here { deltaMovement.x = Fix.zero; } return(true); }
/// <summary> /// we have to use a bit of trickery in this one. The rays must be cast from a small distance inside of our /// collider (skinWidth) to avoid zero distance rays which will get the wrong normal. Because of this small offset /// we have to increase the ray distance skinWidth then remember to remove skinWidth from deltaMovement before /// actually moving the player /// </summary> void moveHorizontally(ref FixVec2 deltaMovement) { var isGoingRight = deltaMovement.x > 0; var rayDistance = FixMath.Abs(deltaMovement.x) + _skinWidth; var rayDirection = isGoingRight ? FixVec2.right : -FixVec2.right; var initialRayOrigin = isGoingRight ? _raycastOrigins.bottomRight : _raycastOrigins.bottomLeft; for (var i = 0; i < totalHorizontalRays; i++) { var ray = new FixVec2(initialRayOrigin.x, initialRayOrigin.y + i * _verticalDistanceBetweenRays); // if we are grounded we will include oneWayPlatforms only on the first ray (the bottom one). this will allow us to // walk up sloped oneWayPlatforms. if (i == 0 && collisionState.wasGroundedLastFrame) { _raycastHit = TFPhysics.Raycast(ray, rayDirection, rayDistance, platformMask); } else { _raycastHit = TFPhysics.Raycast(ray, rayDirection, rayDistance, platformMask & ~oneWayPlatformMask); } if (_raycastHit) { // the bottom ray can hit a slope but no other ray can so we have special handling for these cases if (i == 0 && handleHorizontalSlope(ref deltaMovement, FixVec2.Angle(_raycastHit.normal, FixVec2.up))) { _raycastHitsThisFrame.Add(_raycastHit); // if we weren't grounded last frame, that means we're landing on a slope horizontally. // this ensures that we stay flush to that slope if (!collisionState.wasGroundedLastFrame) { Fix flushDistance = FixMath.Sign(deltaMovement.x) * (_raycastHit.distance - skinWidth); tfTransform.Position += new FixVec2(flushDistance, 0); } break; } // set our new deltaMovement and recalculate the rayDistance taking it into account deltaMovement.x = _raycastHit.point.x - ray.x; rayDistance = FixMath.Abs(deltaMovement.x); // remember to remove the skinWidth from our deltaMovement if (isGoingRight) { deltaMovement.x -= _skinWidth; collisionState.right = true; } else { deltaMovement.x += _skinWidth; collisionState.left = true; } _raycastHitsThisFrame.Add(_raycastHit); // we add a small fudge factor for the float operations here. if our rayDistance is smaller // than the width + fudge bail out because we have a direct impact if (rayDistance < _skinWidth + kSkinWidthFloatFudgeFactor) { break; } } } }