Last active
October 7, 2025 14:20
-
-
Save mattogodoy/910ef7612950161f4a9871c09b62fec7 to your computer and use it in GitHub Desktop.
PID Controller for Godot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extends Node | |
class_name PIDController | |
enum DerivativeMeasurement { | |
VELOCITY, | |
ERROR_RATE_OF_CHANGE | |
} | |
# PID coefficients | |
@export var proportional_gain: float | |
@export var integral_gain: float | |
@export var derivative_gain: float | |
@export var output_min: float = -1 | |
@export var output_max: float = 1 | |
@export var integral_saturation: float | |
var derivative_measurement: DerivativeMeasurement = DerivativeMeasurement.VELOCITY | |
var value_last: float | |
var error_last: float | |
var integration_stored: float | |
var derivative_initialized: bool = false | |
func reset(): | |
derivative_initialized = false | |
func update(current_value: float, target_value: float, delta: float) -> float: | |
if delta <= 0: | |
push_error("delta must be greater than 0") | |
return 0.0 | |
var error = target_value - current_value | |
# calculate P term | |
var P = proportional_gain * error | |
# calculate I term | |
integration_stored = clamp(integration_stored + (error * delta), -integral_saturation, integral_saturation) | |
var I = integral_gain * integration_stored | |
# calculate both D terms | |
var error_rate_of_change = (error - error_last) / delta | |
error_last = error | |
var value_rate_of_change = (current_value - value_last) / delta | |
value_last = current_value | |
# choose D term to use | |
var derive_measure = 0.0 | |
if derivative_initialized: | |
if derivative_measurement == DerivativeMeasurement.VELOCITY: | |
derive_measure = -value_rate_of_change | |
else: | |
derive_measure = error_rate_of_change | |
else: | |
derivative_initialized = true | |
var D = derivative_gain * derive_measure | |
var result = P + I + D | |
return clamp(result, output_min, output_max) | |
func angle_difference(a: float, b: float) -> float: | |
return fmod((a - b + 540.0), 360.0) - 180.0 # calculate modular difference, and remap to [-180, 180] | |
func update_angle(current_angle: float, target_angle: float, delta: float) -> float: | |
if delta <= 0: | |
push_error("delta must be greater than 0") | |
return 0.0 | |
var error = angle_difference(target_angle, current_angle) | |
# calculate P term | |
var P = proportional_gain * error | |
# calculate I term | |
integration_stored = clamp(integration_stored + (error * delta), -integral_saturation, integral_saturation) | |
var I = integral_gain * integration_stored | |
# calculate both D terms | |
var error_rate_of_change = angle_difference(error, error_last) / delta | |
error_last = error | |
var value_rate_of_change = angle_difference(current_angle, value_last) / delta | |
value_last = current_angle | |
# choose D term to use | |
var derive_measure = 0.0 | |
if derivative_initialized: | |
if derivative_measurement == DerivativeMeasurement.VELOCITY: | |
derive_measure = -value_rate_of_change | |
else: | |
derive_measure = error_rate_of_change | |
else: | |
derivative_initialized = true | |
var D = derivative_gain * derive_measure | |
var result = P + I + D | |
return clamp(result, output_min, output_max) |
Good afternoon,
This scripts works better then any other PID controller I found. I like this script so much I ported it to a C++ module for Godot. I hope you don't mind.
Good work on the script :)
Hi there.
That's great! Thanks for taking the time to do it and make it more accessible to other users.
Keep it up!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This script is a very simple, yet useful PID controller for the Godot Game Engine.
It's the GDScript implementation of the Unity script mentioned in this video: https://www.youtube.com/watch?v=y3K6FUgrgXw
Use the
update
function to calculate linear movement, andupdate_angle
to calculate angles.This is an example of the use of
update_angle(rotation.z, 0, delta)
:Screen.Recording.2024-07-16.at.15.21.28.mov