You can use a true PID controller in Unity for position, but not for rotation. The problem is the feedback signal: Unity stores the rotation as a quaternion, which cannot be translated to the 3-axes notation reliably (not a quaternion’s fault: 3-axes notation is redundant, thus many different representations may be returned for the same quaternion).
But the show can go on! The quaternion->angle conversion is unreliable, but you can bet your life on the conversion angle->quaternion! The solution is to keep the current angle in a variable, calculate the proportional and differential errors (integral error only mess things up in this case), add them to generate the desired “torque” (angular acceleration, to be more precise), apply it to the angle, convert the angle to a quaternion and assign it to the object’s rotation. It’s simulated, but is physically correct, and works very well. The simulated PD controller (set to rotate around the Y axis) is:
var targetAngle: float = 0; // the desired angle
var curAngle: float; // current angle
var accel: float; // applied accel
var angSpeed: float = 0; // current ang speed
var maxAccel: float = 180; // max accel in degrees/second2
var maxASpeed: float = 90; // max angular speed in degrees/second
var pGain: float = 20; // the proportional gain
var dGain: float = 10; // differential gain
private var lastError: float;
function Start(){
targetAngle = transform.eulerAngles.y; // get the current angle just for start
curAngle = targetAngle;
}
function FixedUpdate(){
var error = targetAngle - curAngle; // generate the error signal
var diff = (error - lastError)/ Time.deltaTime; // calculate differential error
lastError = error;
// calculate the acceleration:
accel = error * pGain + diff * dGain;
// limit it to the max acceleration
accel = Mathf.Clamp(accel, -maxAccel, maxAccel);
// apply accel to angular speed:
angSpeed += accel * Time.deltaTime;
// limit max angular speed
angSpeed = Mathf.Clamp(angSpeed, -maxASpeed, maxASpeed);
curAngle += angSpeed * Time.deltaTime; // apply the rotation to the angle...
// and make the object follow the angle (must be modulo 360)
rigidbody.rotation = Quaternion.Euler(0, curAngle%360, 0);
}
You can set positive or negative angles, and the object will rotate to the desired angle - including angles > 360 or < -360.
BONUS SCRIPT: Since you’re interested in PID controllers, that’s a simple PID position controller entirely based on physics, that tries to reach the position set in targetPos. It works fine, but the parameters must be fine tuned to reach a stable operation:
var targetPos = Vector3.zero; // the desired position
var maxForce: float = 100; // the max force available
var pGain: float = 20; // the proportional gain
var iGain: float = 0.5; // the integral gain
var dGain: float = 0.5; // differential gain
private var integrator = Vector3.zero; // error accumulator
private var lastError = Vector2.zero;
var curPos = Vector3.zero; // actual Pos
var force = Vector3.zero; // current force
function Start(){
targetPos = transform.position;
}
function FixedUpdate(){
curPos = transform.position;
var error = targetPos - curPos; // generate the error signal
integrator += error * Time.deltaTime; // integrate error
var diff = (error - lastError)/ Time.deltaTime; // differentiate error
lastError = error;
// calculate the force summing the 3 errors with respective gains:
force = error * pGain + integrator * iGain + diff * dGain;
// clamp the force to the max value available
force = Vector3.ClampMagnitude(force, maxForce);
// apply the force to accelerate the rigidbody:
rigidbody.AddForce(force);
}