Last active
January 12, 2021 21:24
-
-
Save Nesciosquid/7dadf4e0dcc6d9635b4f0a8fd9fd21f0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// <summary> | |
/// Compute a force that will redirect a character's speed into movement along a circle. | |
/// </summary> | |
/// <param name="characterPosition">The current position of the character in world space.</param> | |
/// <param name="swingTarget">The position of the center of the swinging circle in world space.</param> | |
/// <param name="swingDistance">The intended fixed distance between the character and the hook target.</param> | |
/// <param name="controller">The character's controller.</param> | |
/// <returns>A force that, if set on the character, will move them to a new position along the swinging | |
/// circle relative to their current velocity. | |
protected static Vector2 ComputeSwingForce(Vector3 characterPosition, Vector3 swingTarget, float swingDistance, CorgiController controller) | |
{ | |
// estimate the intended change in the character's speed due to Gravity, this frame | |
Vector2 gravity = new Vector2(0, controller.Parameters.Gravity * Time.deltaTime); | |
// define a minimum distance to the swing target. | |
// this keeps the rope from acting on us while it should be "slack" | |
float minDistance = swingDistance * (controller.State.IsGrounded ? 1f : 0.95f); | |
// if time isn't moving because the game is paused, or we're within the min distance... | |
if ((swingTarget - characterPosition).magnitude < minDistance || Time.deltaTime == 0) | |
{ | |
// just apply our estimated gravity and return, since it's turned off while swinging | |
return controller.Speed + gravity; | |
} | |
// Direction vector from our character position to the swing target | |
Vector3 swingTargetDirection = swingTarget - characterPosition; | |
// Direction vector from the swing target to our current character position | |
Vector3 characterDirection = characterPosition - swingTarget; | |
// estimate what the character's velocity would have been this frame, after gravity is applied | |
Vector3 characterVelocity = controller.Speed + new Vector2(0, controller.Parameters.Gravity * Time.deltaTime); | |
// The absolute angle of our character, relative to the swing target | |
float characterAngle = Vector2.SignedAngle(Vector2.right, characterDirection.normalized) * Mathf.Deg2Rad; | |
// angle between the character's current velocity and the target direction | |
float ropeAngle = Vector2.SignedAngle(swingTargetDirection.normalized, characterVelocity.normalized) * Mathf.Deg2Rad; | |
// compute a swingForce that will push the character towards the target direction such that | |
// we redirect the character's current velocity to be tangential to the circle | |
Vector3 swingForce = Mathf.Max(0, -Mathf.Cos(ropeAngle) * characterVelocity.magnitude) * swingTargetDirection.normalized; | |
// What the character's velocity will be after we redirect it | |
Vector3 adjustedVelocity = characterVelocity + swingForce; | |
// Just using adjustedVelocity would work OK, but your ending position won't actually be on the circle | |
// anymore, which causes small errors to accumulate as you swing, which gradually lengthen the "rope" | |
// Instead, we can calculate how far around the circle we would have rotated, given the magnitude | |
// of the adjusted velocity, and compute a new target position somewhere on the circle | |
// How many radians around the circle would we swing, if we used the magnitude of our adjusted velocity as an arc length? | |
float adjustedArcAngle = (adjustedVelocity.magnitude * Time.deltaTime) / swingDistance; | |
// Since the magnitude has no sign, we don't know whether to swing clockwise or counter-clockwise around the circle. | |
// We can use our earlier adjusted velocity to predict where we would have ended up, and | |
// use that to guess the correct way to rotate. | |
// Where we would have been, if we had just used our adjusted velocity... | |
Vector3 adjustedPosition = characterPosition + adjustedVelocity * Time.deltaTime; | |
// The direction vector pointing from the swing target to our newly adjusted position | |
Vector3 adjustedDirection = adjustedPosition - swingTarget; | |
// The angle value of the character's current position | |
float adjustedAngle = Vector2.SignedAngle(Vector2.right, adjustedDirection.normalized) * Mathf.Deg2Rad; | |
// The difference in angle around the circle between our original and adjusted positions | |
float adjustedAngleDiff = adjustedAngle - characterAngle; | |
// If the difference in angle between our adjusted and current position is negative, | |
// then we want to swing clockwise, otherwise we want to swing counter-clockwise | |
if (adjustedAngleDiff < 0) | |
{ | |
adjustedArcAngle *= -1; | |
} | |
// The current angle value, plus our offset angle from our projected velocity | |
float targetAngle = characterAngle + adjustedArcAngle; | |
// Direction vector from the hook target to the new position, based on our target angle value | |
Vector3 targetDirection = Quaternion.AngleAxis(targetAngle * Mathf.Rad2Deg, Vector3.forward) * Vector3.right; | |
// The new target position, which is the result of traveling along the target vector by our fixed hook distance | |
Vector3 targetPosition = swingTarget + (targetDirection.normalized * swingDistance); | |
// our target velocity, which is simply the difference between our target position | |
//and our current position, adjusted for the duration of time since last frame | |
return (targetPosition - characterPosition) / Time.deltaTime; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment