-
Notifications
You must be signed in to change notification settings - Fork 1
Helpers
This section covers helper classes that provide common functionality for scripting with FtDSharp.
The PID class implements a PID (Proportional-Integral-Derivative) controller for smooth feedback control. Useful for altitude hold, attitude stabilization, speed control, and custom weapon tracking.
The simplest way to use a PID: bind an input and output, then call Update():
public class AltitudeHold
{
private readonly PID _pid = PID.Bind(
input: () => Game.MainConstruct.Position.y,
output: v => Game.MainConstruct.Propulsion.Hover = v,
setpoint: () => 200f,
kP: 0.1f, kI: 0.02f, kD: 0.5f
);
[OnPhysicsTick]
public void Update() => _pid.Update(Game.GameDeltaTime);
}The PID automatically:
- Reads the current value from
input - Reads the target from
setpoint - Computes the PID output
- Writes the result to
output
// Full: input + output + dynamic setpoint
PID.Bind(Func<float> input, Action<float> output, Func<float> setpoint,
float kP, float kI, float kD,
float outputMin = -1, float outputMax = 1, float integralLimit = 0);
// Static setpoint (constant target value)
PID.Bind(Func<float> input, Action<float> output, float setpoint, ...);
// Input only (handle output manually)
PID.Bind(Func<float> input, float setpoint, ...);
PID.Bind(Func<float> input, Func<float> setpoint, ...);For more control, use the constructor directly:
public class ManualPidExample
{
private readonly PID pid = new(kP: 0.1f, kI: 0.02f, kD: 0.5f, outputMin: -1f, outputMax: 1f, integralLimit: 2f);
[OnPhysicsTick]
public void Update()
{
float current = Game.MainConstruct.Position.y;
float target = 200f;
float output = pid.Update(current, target, Game.GameDeltaTime);
Game.MainConstruct.Propulsion.Hover = output;
}
}Or use pre-calculated error:
float error = target - current;
float output = pid.UpdateWithError(error, deltaTime);| Property | Type | Description |
|---|---|---|
Kp |
float |
Proportional gain (get/set) |
Ki |
float |
Integral gain (get/set) |
Kd |
float |
Derivative gain (get/set) |
IntegralLimit |
float |
Max integral term (0 = unlimited, get/set) |
OutputMin / OutputMax
|
float |
Output clamp range (get/set) |
Setpoint |
float |
Target value (get/set) |
Integral |
float |
Current accumulated integral (read-only) |
PreviousError |
float |
Error from previous update (read-only) |
LastOutput |
float |
Most recent output value (read-only) |
LastError |
float |
Most recent error value (read-only) |
IsFirstUpdate |
bool |
Whether this is the first update (read-only) |
| Parameter | Default |
|---|---|
Kp |
0.05 |
Ki |
250 |
Kd |
0.3 |
OutputMin |
-1 |
OutputMax |
1 |
IntegralLimit |
0 (unlimited) |
Setpoint |
0 |
| Method | Returns | Description |
|---|---|---|
Update(float deltaTime) |
float |
Uses bound input/output. Throws if no input bound. |
Update(float current, float setpoint, float deltaTime) |
float |
Explicit values |
UpdateWithError(float error, float deltaTime) |
float |
Pre-calculated error |
Reset() |
void |
Clears all state (integral, previous error, first-update flag) |
ResetIntegral() |
void |
Clears integral only |
public class Stabilizer
{
private readonly PID altitude = PID.Bind(
() => Game.MainConstruct.Position.y,
v => Game.MainConstruct.Propulsion.Hover = v,
setpoint: () => 200f,
kP: 0.1f, kI: 0.02f, kD: 0.5f, integralLimit: 2f
);
private readonly PID pitch = PID.Bind(
() => Game.MainConstruct.Pitch,
v => Game.MainConstruct.Propulsion.Pitch = v
);
private readonly PID roll = PID.Bind(
() => Game.MainConstruct.Roll,
v => Game.MainConstruct.Propulsion.Roll = v
);
[OnPhysicsTick]
public void Update()
{
altitude.Update(Game.GameDeltaTime);
pitch.Update(Game.GameDeltaTime);
roll.Update(Game.GameDeltaTime);
Log($"Alt: {Game.MainConstruct.Position.y:F1}m (error: {altitude.LastError:F1})");
}
}The WeaponController coordinates multiple weapons and turrets as a group. Use it to create custom weapon groups that aim and fire together.
public class CustomGroups
{
private readonly WeaponController portWeapons;
private readonly WeaponController starboardWeapons;
public CustomGroups()
{
portWeapons = Weapons.CreateController(
Weapons.All.Where(w => w.LocalPosition.x < 0));
starboardWeapons = Weapons.CreateController(
Weapons.All.Where(w => w.LocalPosition.x > 0));
}
[OnPhysicsTick]
public void Update()
{
var target = AI.HighestPriorityMainframe.PrimaryTarget;
if (target == null) return;
portWeapons.Track(target);
if (portWeapons.Controlled.Weapons.Any(w => w.CanFire))
portWeapons.Fire();
starboardWeapons.Track(target);
if (starboardWeapons.Controlled.Weapons.Any(w => w.CanFire))
starboardWeapons.Fire();
}
}WeaponController implements IWeaponController : IWeaponControl, so it has all the same methods as a single weapon:
| Method | Description |
|---|---|
AimAt(Vector3 position) |
Aim all controlled weapons at a position |
Track(ITargetable target) |
Track a target with lead calculation (all overloads) |
Fire() |
Fire all ready weapons (FireOptions.Default) |
Fire(FireOptions options) |
Fire with failsafe / AI firing options |
TryFireAt(Vector3 position) |
Aim and fire if on target |
TryFireAt(Vector3, FireOptions) |
Aim and fire with options |
Additional members:
| Member | Type | Description |
|---|---|---|
Controlled |
ControlledItems |
Access to weapons and turrets in this group |
AllKnownTypes |
bool |
Whether all weapons are known types that can fire |
RebuildHierarchy() |
void |
Rebuild if weapons are added/removed from turrets at runtime |
ControlledItems provides:
-
.Weapons:IReadOnlyList<IWeapon>(excluding turrets) -
.Turrets:IReadOnlyList<ITurret> -
.All:IReadOnlyList<IWeapon>(weapons + turrets) -
.Count: total count