Let's recap some dimensional analysis:
a Displacement measured in \$m\$, which is different from...
a Velocity measured in \$\frac m s\$, which is different from...
a Force measured in \$\frac {kg \ m} s\$
Because these are different quantities, taking code for one and copying it into another will not yield the same effect.
Let's dimensionally analyze what you're trying to add:
SharkCamera.transform.forward * CurrentSpeed * Time.fixedDeltaTime
$$\require{cancel}\text{(dimensionless)} \cdot \frac m {\cancel s } \cdot \cancel s = m$$
So this is producing a value in metres, so it's a displacement. A displacement is not a velocity, so you can't add it to your velocity. What your code there does is keep adding more and more to your velocity every frame, so your object keeps getting faster and faster.
If you want to make the velocity point in a certain direction with a certain magnitude, just assign it with =
, don't accumulate it with +=
:
Rb.velocity = SharkCamera.transform.forward * CurrentSpeed;
Note that we don't need to multiply by delta time - we're not taking the speed and integrating it over a time interval to make a displacement, we're using the speed as a speed, natively. Repeating the dimensional analysis on this, we get \$ \frac m s\$, which is exactly the unit we expect for a velocity.
Try this exercise of checking whether your units make sense anytime you're doing anything with physics - it's a great debugger to spot when you're writing code that makes no sense.
To help this play more nicely with other forces, you can instead apply an acceleration toward your target velocity, rather than setting the velocity directly. That might look like this:
// Compute the velocity we want to have:
var targetVelocity = SharkCamera.transform.forward * CurrentSpeed;
// Compute its difference from our current velocity.
var deltaV = targetVelocity - Rb.velocity;
// Set a budget for how much we can change our velocity this frame.
var accelLimit = acceleration * Time.deltaTime;
// Scale our change so that it does not exceed the acceleration limit.
deltaV = Vector3.ClampMagnitude(deltaV, accelLimit);
// Apply the velocity change.
Rb.AddForce(deltaV, ForceMode.VelocityChange);
You can increase your acceleration
parameter to make the controls tighter and more responsive, or decrease it to make more gradual ramp-up.
You can apply lots of other changes too - like taking some of Rb.velocity.y
and blending it into targetVelocity.y
, or scaling deltaV
so that your vertical acceleration change is damped relative to horizontal, or scaling your acceleration limit by how closely the direction of velocity change aligns with the shark's forward axis, etc. Any of these techniques can help you dial in the right swimming feel.
You can also just get continuous collision detection with MovePosition
and keep your existing code, contrary to your assumption that this is impossible.
Just scan for collisions before you move:
// Compute length of a full move.
var distance = CurrentSpeed * Time.deltaTime;
// Sweep a capsule from where we are to the end of the move.
if (Physics.CapsuleCast(
transform.position - transform.forward * capsuleLength,
transform.position + transform.forward * capsuleLength,
capsuleRadius,
SharkCamera.transform.forward,
out RaycastHit hitInfo,
distance,
obstacleLayers)
) {
// If the swept test hits something, stop just a hair before the collision.
distance = hitInfo.distance - 2 * Physics.defaultContactOffset;
}
Rb.MovePosition(transform.position + SharkCamera.transform.forward * distance);