Module code examples
Here is a collection of example code for various tasks you might want to perform in your module.
Contents
Debugging your code
The most important thing to be able to do! This will print to the in-game error console, which you can bring up from anywhere in the game by pressing Alt-F2:
print("hello world!");
Creating a GUI Window and Button
This module will create a new GUI window and button, on button press the part will explode, close the GUI, and be removed.
using UnityEngine; /* This module will create a new GUI window and button, on button press the part will explode, close the GUI, and be removed. */ public class SelfDestruct : Part { protected Rect windowPos; private void WindowGUI(int windowID) { GUI.DragWindow(new Rect(0, 0, 10000, 20)); //create a dragable window GUIStyle mySty = new GUIStyle(GUI.skin.button); mySty.normal.textColor = mySty.focused.textColor = Color.white; mySty.hover.textColor = mySty.active.textColor = Color.yellow; mySty.onNormal.textColor = mySty.onFocused.textColor = mySty.onHover.textColor = mySty.onActive.textColor = Color.green; sty.padding = new RectOffset(8, 8, 8, 8); //It seems you need to put all buttons between vertical or horizontal GUILayout functions //Without doing this GUILayout.Button() will only be true if you right and left click at the same time GUILayout.BeginVertical(); if (GUILayout.Button("DESTROY",mySty,GUILayout.ExpandWidth(true)))//GUILayout.Button is "true" when clicked { this.explode(); this.onPartDestroy (); this.Die (); } GUILayout.EndVertical(); } private void drawGUI() { GUI.skin = HighLogic.Skin; windowPos = GUILayout.Window(1, windowPos, WindowGUI, "Self Destruct", GUILayout.MinWidth(100)); } protected override void onFlightStart() //Called when vessel is placed on the launchpad { RenderingManager.AddToPostDrawQueue(3, new Callback(drawGUI));//start the GUI } protected override void onPartStart() { if ((windowPos.x == 0) && (windowPos.y == 0))//windowPos is used to position the GUI window, lets set it in the center of the screen { windowPos = new Rect(Screen.width / 2, Screen.height / 2, 10, 10); } } protected override void onPartDestroy() { RenderingManager.RemoveFromPostDrawQueue(3, new Callback(drawGUI)); //close the GUI } }
Useful geometry stuff
All these vectors live in a "world" coordinate system. This coordinate system has an arbitrary orientation and an origin that is periodically shifted to keep it reasonably close to the active ship.
Vector3d has lots of useful vector manipulation methods that you should check out.
//position of the ship's center of mass: Vector3d position = vessel.findWorldCenterOfMass(); //unit vectors in the up (normal to planet surface), east, and north (parallel to planet surface) directions Vector3d eastUnit = vessel.mainBody.getRFrmVel(position).normalized; //uses the rotation of the body's frame to determine "east" Vector3d upUnit = (position - vessel.mainBody.position).normalized; Vector3d northUnit = Vector3d.Cross(upUnit, eastUnit); //north = up cross east double altitude = vessel.mainBody.GetAltitude(position); //ship velocity in an external, non-rotating frame: Vector3d nonRotatingFrameVelocity = vessel.orbit.GetVel(); //velocity as seen in planet's rotating frame Vector3d rotatingFrameVelocity = nonRotatingFrameVelocity - vessel.mainBody.getRFrmVel(position); //local acceleration vector due to gravity: Vector3d geeAcceleration = FlightGlobals.getGeeForceAtPosition(position); //given currentVelocity in rotating frame, find coriolis acceleration: //this first one becomes zero when the game switches out of the planet's reference frame Vector3d coriolisAcceleration = FlightGlobals.getCoriolisAcc(currentVelocity, vessel.mainBody); //this doesn't: Vector3d myCoriolisAcc = -2 * Vector3d.Cross(vessel.mainBody.angularVelocity, currentVelocity); //Moment of inertia of the ship around x, y, and z axes (I think y is always the roll axis): Vector3d MOI = vessel.findLocalMOI(vessel.findWorldCenterOfMass()); //direction in which the ship currently points: Vector3d heading = (Vector3d)vessel.transform.up;
It's often useful to transform a vector into a coordinate system affixed to the ship, which will in general be rotated with respect to the world coordinate system. To do this use vessel.transform.InverseTransformDirection( ):
//angular velocity of the ship around three axes, //x-component = pitch rate, y = roll rate, z = yaw rate: Vector3d angularVelocity = (Vector3d)vessel.transform.InverseTransformDirection(vessel.rigidbody.angularVelocity); //to damp out roll (k is some gain): roll_command = k * angularVelocity.y; //to command the ship to slew towards a given direction, dir (k is some gain): //error will be (0, 1, 0) if dir = heading: Vector3d error = (Vector3d)vessel.transform.InverseTransformDirection((Vector3)dir); yaw_command = k * error.x; pitch_command = -k * error.z;
When you print vectors you may find it useful to first cast them to the Unity type Vector3, which by default only prints one digit past the decimal point, whereas Vector3d prints all the digits. Using Vector3's makes your debugging output much easier to read:
print("position = " + (Vector3)position);
Fly-by-wire
You can control the flight from code as if you were the player using w,a,s,d,q,e or a joystick. In addition to yaw, pitch, roll, and throttle as shown below, the FlightCtrlState class gives access to a few other controls that you can check out.
... protected override void onFlightStart() //Called when vessel is placed on the launchpad { //at the beginning of the flight, register your fly-by-wire control function that will be called repeatedly during flight: FlightInputHandler.OnFlyByWire += new FlightInputHandler.FlightInputCallback(fly); ... } //now this function gets called every frame or something and gives you access to the flight controls private void fly(FlightCtrlState s) { s.yaw = -0.2F; //set yaw input to 20% left s.pitch += 0.3F; //set pitch input to whatever the player has input + 30% s.roll = 0.5F; //set roll to 50% (either clockwise or counterclockwise, try it and find out) s.mainThrottle = 0.8F; //set throttle to 80% //the range of yaw, pitch, and roll is -1.0F to 1.0F, and the throttle goes from 0.0F to 1.0F. //if your code might violate that it's probably a good idea to clamp the inputs, e.g.: s.roll = Mathf.Clamp(s.roll, -1.0F, +1.0F); } ...
Setting the warp rate and modifying the available warp rates
using UnityEngine; ... //to set the warp rate: TimeWarp.SetRate(3); //activates the fourth warp rate //to modify the available warp rates: TimeWarp timeWarp = (TimeWarp)FindObjectOfType(typeof(TimeWarp)); //documentation claims this function is slow //so call it once and store the reference timeWarp.warpRates[3] = 45; //set the fourth warp rate to be x45 timeWarp.altitudeLimits[3] = 0.5; //set the altitude limit for the fourth warp rate. Is this measured in planetary radii?
Automatically activating the next stage
Here is some code that can automatically activate the next stage when this one is done. It uses the usual logic you would use to determine when to activate the next stage, staging only when all the engines that would be decoupled by staging have burned out. However it doesn't have any other criteria for staging, so it may e.g. prematurely activate parachutes.
//Determines whether any of the parts under this part in the ship are engines in the ACTIVE or IDLE //state. Engines are IDLE before they are fired, ACTIVE while firing, and DEACTIVATED after running //out of fuel. So if a decoupler has an ACTIVE or IDLE engine descendant we shouldn't blow the decoupler, or //we will shed burning or unused engines. private bool hasActiveOrIdleEngineDescendant(Part p) { //note that if someone makes a new engine that does not subclass LiquidEngine or SolidRocket //then this method will not account for it if ((p.State == PartStates.ACTIVE || p.State == PartStates.IDLE) && (p is SolidRocket || p is LiquidEngine)) return true; foreach (Part child in p.children) { if (hasActiveEngineDescendant(child)) return true; } return false; } //Parts store an "inverseStage" which is just the stage number that appears in the staging display //in the GUI, and which counts is counts downward to zero as you activate successive stages. This //method looks to see if any of the parts in the given inverseStage are decouplers that would //decouple an active or idle engine if we activated the given inverseStage. private bool inverseStageDecouplesActiveEngine(int inverseStage, Part root) { if (root.inverseStage == inverseStage && (root is Decoupler || root is RadialDecoupler) && hasActiveEngineDescendant(root)) return true; foreach (Part child in root.children) { if (inverseStageDecouplesActiveEngine(inverseStage, child)) return true; } return false; } ... //Now we call this code regularly somewhere in the part logic: //Staging.CurrentStage != Staging.StageCount is a way to detect whether liftoff has occurred yet. //This condition is false until the first stage fires. The last stage is stage zero, so we should //only try to fire the next stage if this is not the last stage. if (Staging.CurrentStage != Staging.StageCount && Staging.CurrentStage > 0) { if (!inverseStageDecouplesActiveEngine(Staging.CurrentStage - 1, vessel.rootPart)) { Staging.ActivateNextStage(); } }