@@ -0,0 +1,81 @@ using UnityEngine;
namespace Terminus.WaterPhysics
{
///
[Header("References")]
[Tooltip("World-space point at the stern where rudder force is applied. " +
"If unset, defaults to -transform.forward * 1m.")]
[SerializeField] private Transform rudderPoint;
[Header("Thrust")]
[Tooltip("Maximum forward thrust in Newtons.")]
[SerializeField] private float thrustForce = 500f;
[Tooltip("Reverse thrust as a fraction of forward thrust.")]
[SerializeField, Range(0f, 1f)] private float reverseMultiplier = 0.3f;
[Header("Rudder")]
[Tooltip("Maximum lateral force at the rudder point in Newtons.")]
[SerializeField] private float rudderForce = 300f;
[Tooltip("How much rudder effectiveness depends on forward speed.\n" +
"0 = always full rudder, 1 = no rudder when stationary.")]
[SerializeField, Range(0f, 1f)] private float rudderSpeedDependence = 0.5f;
[Tooltip("Forward speed (m/s) at which rudder reaches full effectiveness.")]
[SerializeField] private float rudderFullSpeedReference = 5f;
private WaterForces waterForces;
void Awake()
{
waterForces = GetComponent<WaterForces>();
}
void FixedUpdate()
{
Vector3 forward = transform.forward;
forward.y = 0f;
forward.Normalize();
// Thrust
float force = throttle >= 0f
? throttle * thrustForce
: throttle * thrustForce * reverseMultiplier;
waterForces.AddForce(forward * force);
// Rudder — lateral force at stern
if (Mathf.Abs(rudder) > 0.001f)
{
float forwardSpeed = Vector3.Dot(waterForces.LastVelocity, forward);
float speedRatio = Mathf.Clamp01(Mathf.Abs(forwardSpeed) / Mathf.Max(0.01f, rudderFullSpeedReference));
float speedFactor = Mathf.Lerp(1f, speedRatio, rudderSpeedDependence);
Vector3 right = transform.right;
right.y = 0f;
right.Normalize();
Vector3 lateralForce = right * rudder * rudderForce * speedFactor;
Vector3 applicationPoint = rudderPoint != null
? rudderPoint.position
: transform.position - transform.forward;
waterForces.AddForceAtPosition(lateralForce, applicationPoint);
}
}
}
}