// ╔════════════════════════════════════════════════════════════════╗
// ║ Copyright © 2025 NWH Coding d.o.o. All rights reserved. ║
// ║ Licensed under Unity Asset Store Terms of Service: ║
// ║ https://unity.com/legal/as-terms ║
// ║ Use permitted only in compliance with the License. ║
// ║ Distributed "AS IS", without warranty of any kind. ║
// ╚════════════════════════════════════════════════════════════════╝
#region
using UnityEngine;
#endregion
namespace NWH.Common.Utility
{
///
/// Proportional-Integral-Derivative controller for smooth value regulation.
/// Used for automated control systems like cruise control, stability systems, and steering assistance.
///
///
/// PID controllers combine three control strategies:
/// - Proportional: Reacts to current error
/// - Integral: Eliminates accumulated error over time
/// - Derivative: Anticipates future error based on rate of change
/// Tune the three gain values to achieve desired response characteristics.
///
public class PIDController
{
///
/// Maximum output value. Output will be clamped to this value.
///
public float maxValue;
///
/// Minimum output value. Output will be clamped to this value.
///
public float minValue;
private float _processVariable;
///
/// Creates a new PID controller with specified gains and output limits.
///
/// Proportional gain (Kp). Higher values increase response to current error.
/// Integral gain (Ki). Higher values eliminate steady-state error faster.
/// Derivative gain (Kd). Higher values dampen oscillations.
/// Minimum output value.
/// Maximum output value.
public PIDController(float gainProportional, float gainIntegral, float gainDerivative, float outputMin,
float outputMax)
{
GainDerivative = gainDerivative;
GainIntegral = gainIntegral;
GainProportional = gainProportional;
maxValue = outputMax;
minValue = outputMin;
}
///
/// The derivative term is proportional to the rate of
/// change of the error
///
public float GainDerivative { get; set; }
///
/// The integral term is proportional to both the magnitude
/// of the error and the duration of the error
///
public float GainIntegral { get; set; }
///
/// The proportional term produces an output value that
/// is proportional to the current error value
///
///
/// Tuning theory and industrial practice indicate that the
/// proportional term should contribute the bulk of the output change.
///
public float GainProportional { get; set; }
///
/// Adjustment made by considering the accumulated error over time
///
///
/// An alternative formulation of the integral action, is the
/// proportional-summation-difference used in discrete-time systems
///
public float IntegralTerm { get; private set; }
///
/// The current value
///
public float ProcessVariable
{
get { return _processVariable; }
set
{
ProcessVariableLast = _processVariable;
_processVariable = value;
}
}
///
/// The last reported value (used to calculate the rate of change)
///
public float ProcessVariableLast { get; private set; }
///
/// The desired value
///
public float SetPoint { get; set; } = 0;
///
/// The controller output
///
///
/// timespan of the elapsed time
/// since the previous time that ControlVariable was called
///
/// Value of the variable that needs to be controlled
public float ControlVariable(float timeSinceLastUpdate)
{
// Guard against zero or very small deltaTime to prevent NaN/Infinity
// This can happen during game pause or time manipulation
const float EPSILON = 0.0001f;
if (timeSinceLastUpdate < EPSILON)
{
// Return proportional-only control when time is too small
return Mathf.Clamp(GainProportional * (SetPoint - ProcessVariable), minValue, maxValue);
}
float error = SetPoint - ProcessVariable;
// integral term calculation
IntegralTerm += GainIntegral * error * timeSinceLastUpdate;
IntegralTerm = Mathf.Clamp(IntegralTerm, minValue, maxValue);
// derivative term calculation
float dInput = _processVariable - ProcessVariableLast;
float derivativeTerm = GainDerivative * (dInput / timeSinceLastUpdate);
// proportional term calculation
float proportionalTerm = GainProportional * error;
float output = proportionalTerm + IntegralTerm - derivativeTerm;
output = Mathf.Clamp(output, minValue, maxValue);
return output;
}
}
}