# How to control acceleration

To move a spaceship in unity, I wrote a script that applies a force in Z-direction when hitting the up-key and an opposite force when pressing the down-key. The velocity is limited with a clamp-function.

Now I’m facing several problems with this setup:

-When rotating the ship (using a 2nd mouselook-script), accelleration in the new direction at certain degrees is not possible

-Depending on rotation the ship can fly backwards (even though the clamp-function should avoid that)

-Console-output for speed doesn’t seem to show the real speed / it depends on ships orientation.

-Regulation of a constant speed is not possible

I think the first bunch of problems have something to do with the local/world-axis. However, the forces are applied to the rigidbody and therefore should be local. Am I wrong?

I found several threats where people had trouble with setting/getting the real local velocity. Also I’m not sure if my way of just limiting the speed is messing things up with the applied forces.

What I propably need is some kind of proportional-integral-regulation to automatically and dynamically let the acceleration apply to a wished speed. I know mathematically speed is the derivation of acceleration. Going backwards makes integral-calculation neccesary, based on user-input rather than a mathematical function. This is higher mathematics and usually found only in special devices.

Does unity provide such a regulator-function? I’m talking about something similar to the devices used in modern cars. The driver sets a target speed and the regulator automatically adjusts the acceleration (a lot at the beginning / very few when close to target-speed). My script right now is very simple and posted below.

``````// This script regulates object-movement with the keyboard
//
// -Forward/backward-movements are done with forces
//
// -Sidewards-movements are done with manually changing position
//
// -Booster also is done with position-change
//
// -The forces are limited by min- and max-speed (unity has to cut the accelleration because of the maxspeed
//    											  and has to stop backwards-drift due to min-speed.)

function Update () {

var stepx : float; // Boster
var stepz : float; // Left-Right-Sliding

var accell : float; //Start-Accelleration
var breaks : float; //Max accelleration

var minspeed : float; //Lowest possible speed
var maxsped : int;	 // Fastest speed

minspeed = 10;  	  //
maxspeed = 30;   // Max-Speed

accell = 80; //Acelleration
breaks = -82; //Breaks-strength  (breaks a bit stronger than accell - Problem: backwards-flying occurs)

stepx = 8.5; //  Booster Forward-steps
stepz = 1.5; //Sideward steps Slider

// Local z- and x-axis are switched
//    -> "Vector3.right" must be used instead of "Vector3.forward"

// UP
if(Input.GetKey("w") || Input.GetKey("up")) {
constantForce.relativeForce.x = accell;
}

// DOWN
if(Input.GetKey("s") || Input.GetKey("down")) {

if(rigidbody.velocity.x > 0) {
constantForce.relativeForce = (Vector3.right * breaks);
}
}

//TABULATOR (Booster)
if(Input.GetKey("tab")) {
transform.Translate(Vector3.right * stepx);  //Booster just moves Obj. forward

}

// LEFT-Slide
if(Input.GetKey("q")) {
transform.Translate(Vector3.forward * stepz);

}

// RIGHT-Slide
if(Input.GetKey("e")) {
transform.Translate(Vector3.forward * -stepz);
}

// Clamp speed to the min and max
rigidbody.velocity.x = Mathf.Clamp (rigidbody.velocity.x, minspeed, maxspeed);

Debug.Log("Current Speed:" +rigidbody.velocity.x);
} // EndFunc Update
``````

You’re doing a big mess here! constantForce.relativeForce is local, but rigidbody.velocity is world referenced, and you’re clamping only the x axis! This is causing the weird direction and speed readings.

You should limit the speed with Vector3.ClampMagnitude: this limits the velocity to a max magnitude while keeping its direction. Another solution could be to set rigidbody.velocity directly - but this would require a complete change in your code.

You can solve both problems changing the two last lines:

```    // Clamp speed to max
rigidbody.velocity = Vector3.ClampMagnitude(rigidbody.velocity, maxspeed);
Debug.Log("Current Speed:" +rigidbody.velocity.magnitude);
```

NOTE: forget about what I said about Q, E and Tab: you’re using Translate, which
by default operates in local space - thus the teleport will be ok.

EDITED:

That’s a rigidbody.velocity based version of your script: it’s more predictably, and you stop without that back flight problem. I didn’t test this, but the basic idea is correct - let me know if you have any problems with this script:

```function Update () {
var stepx : float = 8.5; // Booster Forward-steps
var stepz : float = 1.5; // Sideward steps Slider
var accell : float = 30.0; // Max speed forward

private var locVel = Vector3.zero; // local velocity you set
private var curVel = Vector3.zero; // current velocity - follow locVel
var force: float = 2.0; // how fast curVel reaches locVel

// Local z- and x-axis are switched
//    -> "Vector3.right" must be used instead of "Vector3.forward"

// UP
if(Input.GetKey("w") || Input.GetKey("up")) {
locVel = Vector3(accell, 0, 0); // desired velocity = accell forward
}
// DOWN
if(Input.GetKey("s") || Input.GetKey("down")) {
locVel = Vector3.zero; // desired velociy = 0
}
// fake a progressive acceleration with MoveTowards:
curVel = Vector3.MoveTowards(curVel, locVel, force * Time.deltaTime);
// convert to world space and apply to the rigidbody velocity:
rigidbody.velocity = transform.TransformDirection(curVel);
//TABULATOR (Booster)
if(Input.GetKey("tab")) { // it's correct: Translate defaults to local space
transform.Translate(Vector3.right * stepx);  //Booster just moves Obj. forward
}
// LEFT-Slide
if(Input.GetKey("q")) {
transform.Translate(Vector3.forward * stepz);
}
// RIGHT-Slide
if(Input.GetKey("e")) {
transform.Translate(Vector3.forward * -stepz);
}
Debug.Log("Current Speed:" + curVel.x);
}
```

EDITED 2:

PI is a Proportional Integral control (you can read about the more complex PID -Proportional Integral Derivative - control in this article: PID controller - Wikipedia)

A simple P (Proportional) control subtracts the measured speed from the target speed, multiply the difference (called error signal) by PGain and use the result (the Proportional signal) to control the force applied. When the desired speed is reached, the error signal becomes zero, what also reduces to zero the force applied. But if friction or some external force reduce the speed, the system can never stay at zero error: there’s always a small error signal to be multiplied by PGain and supply the force necessary to null these external forces.

To ensure zero error, the I letter enters the scene: the error signal is continuously accumulated (Integrated) and the result is multiplied by IGain and added to the Proportional signal. In a PI control, the Integral (accumulated) error plays the role of the small residual error in a simple P control: it provides the necessary force to keep the speed, allowing for zero error signal. Unless you need to control supersonic missile flight, in practice there’s no need to do complex numerical integrations: just accumulate the error signal in a variable, multiply it by the IGain (usually << 1) and add to the Proportional signal.

Fortunately, in the game environment all this sophistication is rarely needed (unless the game goal is to fine-tune PI Control parameters, hardly a block-buster game!) In most cases, we just calculate the ideal behavior and make the objects follow our orders! In my script, for instance, the speed varies linearly up to the desired value, and obediently gets stuck there: it’s the dream-come-true of any control engineer! In real life, one must tweak the gains and available power to barely follow an ideal case - and the wrong parameters very often cause oscillation, overshoot, big residual errors, slow response - it’s hell on earth!

Anyway, if you really really really need to implement a servo control, you can avoid the Integral part (there’s no friction or other forces, so it’s not needed): just calculate the difference between current and target speeds, multiply by a gain factor, clamp to some limit and use the result to apply the force. But if you want to check the PI controller, go ahead and use the code below:

```var targetSpeed = Vector3.zero; // the desired speed
var maxForce: float = 100; // the max force available
var pGain: float = 20; // the proportional gain
var iGain: float = 0.5; // the integral gain
private var integrator = Vector3.zero; // error accumulator
var curSpeed = Vector3.zero; // actual speed
var force = Vector3.zero; //

function FixedUpdate(){
curSpeed = rigidbody.velocity; // measure actual speed
var error = targetSpeed - curSpeed; // generate the error signal
integrator += error;
// calculate the force and limit it to the max force available:
force = error * pGain + integrator * iGain;
force = Vector3.ClampMagnitude(force, maxForce);
// apply the force to accelerate the rigidbody:
rigidbody.AddForce(force);
}
```

This code uses AddForce instead of constantForce - remember to zero any constantForce currently in your code. NOTE: targetSpeed is in world space: if you want do use local speed, define it, convert to world space with transform.TransformDirection and store in targetSpeed.

EDITED 3:

The integration above doesn’t take into account the time elapsed since the last frame. It’s an usual practice in the microcontroller world, because the integration occurs at a fixed rate, and this saves an expensive multiplication (iGain compensates for the different integration result). FixedUpdate also occurs at a fixed rate - or at least tries to do it: in very slow machines, FixedUpdate may not be able to keep a constant pace. To avoid problems in these cases, the integration can include Time.deltaTime, like this:

```var targetSpeed = Vector3.zero; // the desired speed
var maxForce: float = 100; // the max force available
var pGain: float = 20; // the proportional gain
var iGain: float = 0.5; // the integral gain
private var integrator = Vector3.zero; // error accumulator
var curSpeed = Vector3.zero; // actual speed
var force = Vector3.zero; //

function FixedUpdate(){
curSpeed = rigidbody.velocity; // measure actual speed
var error = targetSpeed - curSpeed; // generate the error signal
integrator += error * Time.deltaTime; // integrate the error signal
// calculate the force:
force = error * pGain + integrator * iGain;
// clamp to the max force available:
force = Vector3.ClampMagnitude(force, maxForce);
// apply the force to accelerate the rigidbody:
rigidbody.AddForce(force);
}
```

The iGain in this case must be about 50 times greater, since the regular deltaTime is 1/50 second.

Solution for 2nd part - How to do an autopilot:

“A simple P (Proportional) control subtracts the measured speed from the target speed, multiply the difference (called error signal) by PGain and use the result (the Proportional signal) to control the force applied. When the desired speed is reached, the error signal becomes zero, what also reduces to zero the force applied”, as aldonaletto wrote.

Furthermore the integral part of a P-I-regulator is doing this:
“To ensure zero error […] the error signal is continuously accumulated (Integrated) and the result is multiplied by IGain and added to the Proportional signal. In a PI control, the Integral (accumulated) error plays the role of the small residual error in a simple P control: it provides the necessary force to keep the speed, allowing for zero error signal.”

…Meaning a P - or even better - a P-I-regulator is the motor of the idea. Is there one in unity’s physics-engine?

If not: Did someone write a script already to compensate the lack?

I searched the web for P-I- and P-I-D-controllers and indeed found some code. No Javascript but at least C and C#-versions as well as pseudo-code and a Basic-example.

Some good pictures what P- and PI-controllers do can be found in this well-done explanation.

P-only accelerates longer and higher around the target-value. P-I-controllers make the system getting stable faster and better. In slow responding systems, P-only-controllers sometimes never stabilize on the target-value and always oscillate around the target.

However, my problem now is: I want to transfer the found code into a javascript-version but I can’t find the lines where they calculate the integral. The code in general is not too difficult and basically always the same:

Pseudo-code from Wikipedia:

````
previous_error = 0
integral = 0
start:
error = setpoint - PV [actual_position]
integral = integral + error*dt
derivative = (error - previous_error)/dt
output = Kp*error + Ki*integral + Kd*derivative
previous_error = error
wait(dt)
goto start
```
`

I don’t get the thing with the integral. According to Wikipedia, there are several methods to choose from to integrate (e.g. Romberg). But the PID-code-examples just use

integral = integral + error*dt

for calculating the integral. What am I missing?

C-version of a PID-controller (from Embedded’s Heaven):

 #ifndef PID_H_ #define PID_H_ #include //Define parameter #define epsilon 0.01 #define dt 0.01             //100ms loop time #define MAX  4                   //For Current Saturation #define MIN -4 #define Kp  0.1 #define Kd  0.01 #define Ki  0.005 float PIDcal(float setpoint,float actual_position) { static float pre_error = 0; static float integral = 0; float error; float derivative; float output; //Caculate P,I,D error = setpoint - actual_position; //In case of error too small then stop intergration if(abs(error) > epsilon) { integral = integral + error*dt; } derivative = (error pre_error)/dt; output = Kperror + Kiintegral + Kd*derivative; //Saturation Filter if(output > MAX) { output = MAX; } else if(output < MIN) { output = MIN; } //Update error pre_error = error; return output; } #endif /PID_H_/

An extensive C#-version of a PID-controller (from Codeproject.com):

 *  PID Loop Control example.  By Lowell Cady, LowTech LLC (c) 2009 Distributed under “The Code Project Open License (CPOL) 1.02” http://www.codeproject.com/info/CPOL.zip Modify and/or re-apply code at your own risk. */ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Drawing.Drawing2D; using System.Linq; using System.Text; using System.Windows.Forms; namespace PID_Example { public partial class Form1 : Form { private double pSetpoint = 0; private double pPV = 0;  // actual possition (Process Value) private double pError = 0;   // how much SP and PV are diff (SP - PV) private double pIntegral = 0; // curIntegral + (error * Delta Time) private double pDerivative = 0;  //(error - prev error) / Delta time private double pPreError = 0; // error from last time (previous Error) private double pKp = 0.2, pKi = 0.01, pKd = 1; // PID constant multipliers private double pDt = 100.0; // delta time private double pOutput = 0; // the drive amount that effects the PV. private double pNoisePercent = 0.02; // amount of the full range to randomly alter the PV private StripChart stripChart;  //builds and contains the strip chart bmp private double pNoise = 0;  // random noise added to PV public double setpoint  //SP, the value desired from the process (i.e. desired temperature of a heater.) { get { return pSetpoint; } set { pSetpoint = value; // change the label for setpoint value lblSP.Text = pSetpoint.ToString(); // change the slider possition if it does not match if ( (int)Math.Round(pSetpoint) != trackBarSP.Value) // don’t use doubles in == or != expressions due to bit resolution trackBarSP.Value = (int)Math.Round(pSetpoint); } } public double PV    //Process Value, the measured value of the process (i.e. actual temperature of a heater) { get { return pPV; } set { pPV = value; // place limits on the measured value if (pPV < 0) pPV = 0; else if (pPV > 1000) pPV = 1000; // update the text lblPV.Text = pPV.ToString(); // update progress bar if (pPV > progBarPV.Maximum) progBarPV.Value = progBarPV.Maximum; else if (pPV < progBarPV.Minimum) progBarPV.Value = progBarPV.Minimum; else progBarPV.Value = (int)pPV; } } public double error //difference between Setpoint and Process value (SP - PV) { get { return pError; } set { pError = value; // uodate the lable lblError.Text = pError.ToString(); } } public double integral //sum of recent errors { get { return pIntegral; } set { pIntegral = value; // update the lable lblIntegral.Text = integral.ToString(); } } public double derivative    //How much the error is changing (the slope of the change) { get { return pDerivative; } set { pDerivative = value; lblDeriv.Text = derivative.ToString(); } } public double preError  //Previous error, the error last time the process was checked. { get { return pPreError; } set { pPreError = value; } } public double Kp    //proportional gain, a “constant” the error is multiplied by. Partly contributes to the output as (Kp * error) { get { return pKp; } set { pKp = value; // update the textBox if (pKp.CompareTo(validateDouble(tbKp.Text, pKp)) != 0) tbKp.Text = pKp.ToString(); } } public double Ki    // integral gain, a “constant” the sum of errors will be multiplied by. { get { return pKi; } set { pKi = value; // update the textBox if (pKi.CompareTo(validateDouble(tbKi.Text, pKi)) != 0) tbKi.Text = pKi.ToString(); } } public double Kd    // derivative gain, a “constant” the rate of change will be multiplied by. { get { return pKd; } set { pKd = value; // update the textBox if (pKd.CompareTo(validateDouble(tbKd.Text, pKd)) != 0) tbKd.Text = pKd.ToString(); } } public double Dt    // delta time, the interval between saples (in milliseconds). { get { return pDt; } set { pDt = value; } } public double output    //the output of the process, the value driving the system/equipment.  (i.e. the amount of electricity supplied to a heater.) { get { return pOutput; } set { pOutput = value; // limit the output if (pOutput < 0) pOutput = 0; else if (pOutput > 1000) pOutput = 1000; if (pOutput > progBarOut.Maximum) progBarOut.Value = progBarOut.Maximum; else if (pOutput < progBarOut.Minimum) progBarOut.Value = progBarOut.Minimum; else progBarOut.Value = (int)pOutput; lblOutput.Text = pOutput.ToString(); } } public double noisePercent  //upper limit to the amount of artificial noise (random distortion) to add to the PV (measured value).  0.0 to 1.0 (0 to 100%) { get { return pNoisePercent;} set { pNoisePercent = value; } } public double noise     //amount of random noise added to the process value { get { return pNoise; } set { pNoise = value; } } // form constructor public Form1() { InitializeComponent(); stripChart = new StripChart(); } // set up some of the labels when the form loads. private void Form1_Load(object sender, EventArgs e) { lblInterval.Text = trackBarInterval.Value.ToString(); lblSP.Text = trackBarSP.Value.ToString(); } // set the sample rate private void trackBarInterval_Scroll(object sender, EventArgs e) { lblInterval.Text = trackBarInterval.Value.ToString(); tmrPID_Ctrl.Enabled = false; tmrPID_Ctrl.Interval = trackBarInterval.Value; Dt = tmrPID_Ctrl.Interval; tmrPID_Ctrl.Enabled = true; } /*This represents the speed at which electronics could actualy sample the process values… and do any work on them. * [most industrial batching processes are SLOW, on the order of minutes. *  but were going to deal in times 10ms to 1 second. *  Most PLC’s have relativly slow data busses, and would sample *  temperatures on the order of 100’s of milliseconds. So our *  starting time interval is 100ms] */ private void tmrPID_Ctrl_Tick(object sender, EventArgs e) {   /* Pseudocode from Wikipedia * previous_error = 0 integral = 0 start: error = setpoint - PV(actual_position) integral = integral + error*dt derivative = (error - previous_error)/dt output = Kperror + Kiintegral + Kd*derivative previous_error = error wait(dt) goto start */ // calculate the difference between the desired value and the actual value error = setpoint - PV; // track error over time, scaled to the timer interval integral = integral + (error * Dt); // determin the amount of change from the last time checked derivative = (error - preError) / Dt; // calculate how much drive the output in order to get to the // desired setpoint. output = (Kp * error) + (Ki * integral) + (Kd * derivative); // remember the error for the next time around. preError = error; } //This timer updates the process data. it needs to be the fastest // interval in the system. private void tmrPV_Tick(object sender, EventArgs e) { /* this my version of cruise control. PV = PV + (output * .2) - (PV*.10); The equation contains values for speed, efficiency, *  and wind resistance. Here ‘PV’ is the speed of the car. ‘output’ is the amount of gas supplied to the engine. (It is only 20% efficient in this example) And it looses energy at 10% of the speed. (The faster the car moves the more PV will be reduced.) Noise is added randomly if checked, otherwise noise is 0.0 (Noise doesn’t relate to the cruise control, but can be useful *  when modeling other processes.) */ PV = PV + (output * 0.20) - (PV * 0.10) + noise; // change the above equation to fit your aplication } //change the setpoint private void trackBarSP_Scroll(object sender, EventArgs e) { setpoint = trackBarSP.Value; } // change a double only if the string can be parsed as a double private double validateDouble(string text,double startVal) { double d; if (double.TryParse(text, out d)) return d; else return startVal; } // Change the Proportional gain text, so validate it. private void tbKp_TextChanged(object sender, EventArgs e) { Kp = validateDouble(tbKp.Text, Kp); } // Change the Integral gain text, so validate it. private void tbKi_TextChanged(object sender, EventArgs e) { Ki = validateDouble(tbKi.Text, Ki); } // Change the Derivative gain text, so validate it. private void tbKd_TextChanged(object sender, EventArgs e) { Kd = validateDouble(tbKd.Text, Kd); } // change the amount of noise introduced every tmrPV data update private void nudNoisePercent_ValueChanged(object sender, EventArgs e) { noisePercent = (double)(nudNoisePercent.Value) / 100.0; } // allow the whole process to be toggled on and off (in case you see something interesting in the chart.) private void btnStartProcess_Click(object sender, EventArgs e) { tmrPV.Enabled = !tmrPV.Enabled; tmrPID_Ctrl.Enabled = tmrPV.Enabled; tmrChart.Enabled = tmrPV.Enabled; if (tmrPV.Enabled) btnStart.Text = “Stop Process”; else btnStart.Text = “Start Process”; } // The chart requires more resources when updated. // so the update chart happens at a slower rate than the data update (tmrPV is the data update) // However, setting the chart update too slow (more than 3x the data update) // will cause resolution loss in the graph (the graph will look blurry). private void tmrChart_Tick(object sender, EventArgs e) { // update the stripchart stripChart.addSample(setpoint, PV, output); // update will rebuild the chart bitmap. // put the new chart bitmap in the pictureBox pictureBox1.Image = stripChart.bmp; } private void tmrNoise_Tick(object sender, EventArgs e) { // noise is added if the checkBox has been clicked Random r = new Random(); if (cbNoise.Checked) noise = (progBarPV.Maximum * noisePercent) * (r.NextDouble() - 0.5); else noise = 0; // add a positive or negative noise // first get the max allowable noise, then multiply by a random value between -0.5 and 0.5 /*[The noise doesn’t really represent what happens to a car in the real world, but it is usefull if you model other processes. This part of the code into its own timer in order to have much more control over the noise frequency. */ } } /* this class will build a strip chart bitmap from consecutive *  SP and PV values.  For simplicity, the limits are fixed. */ public class StripChart { private Bitmap pBmp;    // the chart bitmap private Queue qSP;  //collection of Setpoints private Queue qPV;  //collection of Precess Values private Queue qMV;  //collection of Manipulated values (outputs) private int x = 0, y = 0; // used to define points added to line array // define brushes private SolidBrush brSP;    // setpoint brush private SolidBrush brPV;    // process value brush private SolidBrush brMV;    // Manipulated Value brush // define pens private Pen pSP; private Pen pPV; private Pen pMV; public Bitmap bmp   // allow the bitmap to be accessed, but not mutated publicly. { get { return pBmp; } } public StripChart() { // define the bitmap. pBmp = new Bitmap(1007, 1007, System.Drawing.Imaging.PixelFormat.Format24bppRgb); // instatiate the queues qSP = new Queue(1000); qPV = new Queue(1000); qMV = new Queue(1000); // make brushes that are lightly transparent brSP = new SolidBrush(Color.FromArgb(255, Color.Green)); brPV = new SolidBrush(Color.FromArgb(128, Color.Blue)); brMV = new SolidBrush(Color.FromArgb(128, Color.Red)); // make pens that will be used to draw the lines pSP = new Pen(brSP, 5); pPV = new Pen(brPV, 5); pMV = new Pen(brMV, 5); } //enter sampled values into the associated queues public void addSample(double sp, double pv, double mv) { // limit the size to 1000, if more than 1000 remove the first item if (qSP.Count > 999) qSP.Dequeue(); if (qPV.Count > 999) qPV.Dequeue(); if (qMV.Count > 999) qMV.Dequeue(); // place values into the queues qSP.Enqueue(sp); qPV.Enqueue(pv); qMV.Enqueue(mv); // update the bitmap. buildChart(); } // empty out the queues public void clearQueus() { qSP.Clear(); qPV.Clear(); qMV.Clear(); } public void buildChart() { Graphics g = Graphics.FromImage(pBmp); // clear the bmp g.Clear(Color.LightGray); // make point arrays for the lines that will be drawn Point pointSP = new Point[qSP.Count]; Point pointPV = new Point[qPV.Count]; Point pointMV = new Point[qMV.Count]; for (x = 0; x < qSP.Count; x++) { y = 1000 - (int)Math.Round(qSP.ElementAt(x)); //if (y > 1000) y = 1000; // prevent the line from falling out of image. pointSP = new Point(x, y); y = 1000 - (int)Math.Round(qPV.ElementAt(x)); //if (y > 993) y = 993; // prevent the line from falling out of image. pointPV = new Point(x, y); y = 1000 - (int)Math.Round(qMV.ElementAt(x)); //if (y > 993) y = 993; // prevent the line from falling out of image. pointMV = new Point(x, y); } /* foreach (double d in qSP) { y =  1000 - (int)Math.Round(d); //gPath.AddRectangle(new Rectangle(x,y,4,4)); pointSP = new Point(x, y); x++; } */ if (pointSP.Length > 1) { //g.DrawPath(Pens.Red, gPath); g.DrawLines(pSP, pointSP); g.DrawLines(pPV, pointPV); g.DrawLines(pMV, pointMV); } g.Dispose(); } } }

And finally an old Basic-PID-controller:

 ' Variables: ’    Input          Process input ’    InputD         Process input plus derivative ’    InputLast      Process input from last pass, used in deriv. calc. ’    Err            Error, Difference between input and set point ’    SetP           Set point ’    OutPutTemp     Temporary value of output ’    OutP           Output of PID algorithm ’    Feedback       Result of lag in positive feedback loop. ’    Mode           value is ‘AUTO’ if loop is in automatic ’    Action         value is ‘DIRECT’ if loop is direct acting ’ ’    The PID emulation code: ’ IF Mode = ‘AUTO’ THEN InputD=Input+(Input-InputLast)*Derivative *60  derivative. InputLast = Input Err=SetP-InputD             'Error based on reverse action. IF Action = ‘DIRECT’ THEN Err=0 – Err  'Change sign if direct. ENDIF OutPutTemp = Err*Gain+Feedback 'Calculate the gain time the error and add the feedback. IF OutPutTemp > 100 THEN OutPutTemp =100  'Limit output to between IF OutPutTemp < 0 THEN OutPutTemp =0       '0 and 100 percent. OutP = OutPutTemp                             'The final output of the controller. Feedback=Feedback+(OutP - Feedback)*ResetRate/60 ELSE InputLast=Input   'While loop in manual, stay ready for bumpless switch to Auto. Feedback=OutP ENDIF ’ 'If external feedback is used, the variable “OutP” in line 11 is replaced with the variable containing the external feedback.

The C#-version is not complete - it’s the main-file from the project’s source-code. It won’t work in unity without modification - and I’m not familiar with C#.

If there was a ready-to-go Unity-Javascript (or for professionals a C#-script), a PID-regulator could be used for any purpose in future: No matter if simulating automatic-gears in a car, some stove in a puzzle-game, cruise-controls in airplanes or automatic spotlight-adjustment in games with day-night-cycles. Possible applications are countless. So please help.