Skip to content

Instantly share code, notes, and snippets.

@Nesciosquid
Last active January 12, 2021 21:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Nesciosquid/7dadf4e0dcc6d9635b4f0a8fd9fd21f0 to your computer and use it in GitHub Desktop.
Save Nesciosquid/7dadf4e0dcc6d9635b4f0a8fd9fd21f0 to your computer and use it in GitHub Desktop.
/// <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