Tutorials

From FlightGear wiki
Jump to: navigation, search

FlightGear offers a flexible tutorial system, entirely written in the Nasal scripting language. Aircraft that provide support for these scripted tutorials can be found in Category:Interactive Tutorial Support.

News

FlightGear Missions and Adventures

Continue reading at "Mission" for helicopters...

Interfacing Tutorials to web sites

To learn more about interfacing FlightGear to web sites (without having to go through the multiplayer protocol) by making HTTP requests via Nasal, please see this tutorial: Howto:Making HTTP Requests from Nasal.

Supporting Checklists

As of V2.9.0, FlightGear can display aircraft checklists in a standardized way, under Help->Aircraft Checklists.

Checklists are situated under /sim/checklists. Please see Aircraft Checklists.

Also see: http://forum.flightgear.org/viewtopic.php?f=3&t=18240&p=170386#p170386

Some Background Info

The tutorial system itself was largely developed by Stuart.

In recent FGDATA, the Nasal "tutorial" module has become a so called "Nasal sub module", which means that the "tutorial.nas" module now resides in its own sub folder inside $FG_ROOT/Nasal: http://gitorious.org/fg/fgdata/trees/master/Nasal/tutorial.

Support for Nasal sub modules was added by ThorstenB. Modules loaded as Nasal sub modules do automatically support reloading, because they are loaded via a listener.

Please see the Nasal documentation on sub modules for details: Nasal#Nasal sub modules.

Regarding suggested coding practices, you should check out: Nasal scripting language#Memory management and Nasal#Managing timers and listeners.

To learn more about translating tutorials, please see: http://forum.flightgear.org/viewtopic.php?f=42&t=16876&p=164407&hilit=tutorials#p164407

Missions & Adventures

WIP.png Work in progress
This article or section will be worked on in the upcoming hours or days.
See history for the latest developments.

Reloading XML tutorials at runtime

tutorial.nas module is loaded via a listener ("/nasal/tutorial/loaded"). See line 28-37 of tutorial.nas in $FG_ROOT/Nasal/tutorial to see how this is done.

Before you can actually reload a tutorial, you must first of all STOP it. Please see line 112-122 of tutorial.nas in $FG_ROOT/Nasal/tutorial.

Once you have stopped all running tutorials, you can reload the corresponding tutorial. Please see line 477-480 of tutorial.nas in $FG_ROOT/Nasal/tutorial to see how this is done.

Basically, you should be able to come up with your own "reload" function by combining the stop() and the load() functions and adding a new "reload" function to tutorial.nas:

var reload = func(filename,slot) stopTutorial() and load(filename,slot);

Also, see line 450-463 of tutorial.nas in $FG_ROOT/Nasal/tutorial to see how the namespace is initialized. For additional information on namespace, I suggest to have a look at: Namespaces and Methods

Using tutorials

Tutorials can be started and stopped from the "Help" menu. They are defined in XML files. Each of them has to be loaded into /sim/tutorials/ under a separate tutorial[n]/ branch:

  <sim>
      <tutorials>
          <tutorial include="Tutorials/take-off.xml"/>
          <tutorial include="Tutorials/landing.xml"/>
          <tutorial include="/Missions/oilrig-landing.xml"/>
      </tutorial>
  </sim>

Alternatively, all tutorials can be defined in a single file, with <tutorial> tags around each tutorial. This is then included like so:

  <sim>
      <tutorials include="foo-tutorials.xml"/>
  </sim>

Structure

A tutorial has this structure, where some of the elements are described in detail below:

  <tutorial>
      <name>...</name>            mandatory; short identifier, also shown in the
                                             tutorial selection dialog
      <description>...</description> mandatory; longer description for the dialog
      <audio-dir>...</audio-dir>  optional; defines where to load sound samples
      <interval>5</interval>      optional; defines default loop interval in sec
      <step-time>...</step-time>  optional; time between tutorial steps in sec
      <exit-time>...</exit-time>  optional; time between fulfillment of steps in sec
      <timeofday>noon</timeofday> optional; defines daytime; any of "dawn",
                                            "morning", "noon", "afternoon",
                                            "evening", "dusk", "midnight", "real"
      <nasal>
          ...                     optional; initial Nasal code; see below
      </nasal>
 
      <models>
          ...                     optional; scenery objects; see below
      </models>
 
      <targets>
          ...                     optional; targets; see below
      </targets>
 
      <presets>
          ...                     optional; initial simulator state; see below
      </presets>
 
 
      <init>                      optional; initial settings; see below
          <set>
              ...                     optional; property settings; allowed multiple
          </set>                                                   times
          <view>
              ...                     optional; view settings
          </view>
          <marker>
              ...                     optional; marker coordinates
          </marker>
          <nasal>
              ...                     optional; Nasal code
          </nasal>
          <interval>10</inteval>      optional; run loop next in this many seconds
      </init>                                   (default: 5); doesn't change global
                                                interval
 
 
      <step>                      mandatory; well, not really, but if there's not
                                      at least one <step>, then the whole tutorial
                                      won't do anything; see below for details
 
          <message>...</message>      optional; message to be displayed/spoken when
                                      <step> is entered; allowed multiple times, in
                                      which case one is chosen at random
 
          <audio>...</audio>          optional; file name of *.wav sample to be played;
                                                may be used multiple times (random)
          <set>
              ...                     optional; allowed several times
          </set>
          <view>
              ...                     optional
          </view>
          <marker>
              ...                     optional
          </marker>
          <nasal>
              ...                     optional; Nasal code that is executed when the
          </nasal>                              step is entered
 
          <interval>10</inteval>      optional; run loop next in this many seconds
 
          <error>                     optional; allowed several times
              <message>..</message>       optional; text displayed/spoken
              <audio>...</audio>          optional; name of *.wav sample to be played
 
              <condition>
                  ...                     optional, but one should be there to make sense
              </condition>                          see [[$FG_ROOT]]/Docs/README.conditions
 
              <nasal>
                  ...                     optional; Nasal code that is executed when the
              </nasal>                              error condition was fulfilled
 
              <interval>10</inteval>      optional; run loop next in this many seconds
          </error>
 
          <exit>                      optional; defines when to leave this <step>
              <condition>                       see [[$FG_ROOT]]/Docs/README.conditions
                  ...
              </condition>
 
              <nasal>
                  ...                     optional; Nasal code that is executed when the
              </nasal>                              exit condition was met
          </exit>
      </step>
 
 
      <end>                           optional; final settings & actions; see below
          <message>...</message>          optional; multiple times (random)
          <audio>...</audio>              optional; multiple times (random)
          <set>
              ...                         optional
          </set>
          <view>
              ...                         optional
          </view>
          <nasal>
              ...                         optional
          </nasal>
      </end>
  </tutorial>

After the tutorial has finished its initialization, it goes through all <steps>. For each, it outputs the <message> or <audio>, optionally sets a <marker> and/or a <view>, then it checks all <error>s and, if an <error><condition> is fulfilled, outputs the respective <error><message>. If none of the <error>s occurred, then it checks if the <exit><condition> is true, and if so, it jumps to the next <step>. Otherwise the current <step> is endlessly repeated. Finally, after all <step>s were processed, the <end> group is executed.

Elements

Nasal

Embedded Nasal code is supported on the top level, in <init> in each <step>, in a <step>'s <error> and <exit>, and in <end>. All Nasal runs in a separate namespace __tutorial, so it's possible to define a function in the <init>'s Nasal block, and to use this function in other blocks without prefix. The namespace is preloaded with some functions:


 next([n=1])      ... to switch n <step>s forward
 previous([n=1])  ... to switch n <step>s backwards
 say(what [, who="copilot [, delay=0]])
                  ... says 'what' with voice 'copilot' after 'delay' seconds


A Nasal group looks like this:

  <nasal>
      <script>
          say("Hi, I'm the pilot!", "pilot");
      </script>
      <module>__tutorial</module>           optional; preset with __tutorial
  </nasal>

Models

This loads models into the scenery. It can be used to place, for example, a helicopter landing pad at an airport where normally none is, so that the tutorial can train landing. The layout is the following, with <path> being relative to $FG_ROOT:

  <models>
      <model>
          <path>Models/Airport/supacat_winch.ac</path>     mandatory
          <longitude-deg>-122.4950109</longitude-deg>      mandatory
          <latitude-deg>37.51403798</latitude-deg>         mandatory
          <elevation-ft>51</elevation-ft>                  mandatory
          <heading-deg>2.488888979</heading-deg>           optional (default: 0)
          <pitch-deg>0</pitch-deg>                         optional (default: 0)
          <roll-deg>0</roll-deg>                           optional (default: 0)
      </model>
 
      <model>
          ...                      another model
      </model>
  </models>

The models are only removed before a new tutorial is loaded. Otherwise they remain in the scenery for the whole FlightGear session. They aren't permanently added.

Targets

These are simple pairs of longitude/latitude under an arbitrary name (here "hospital" and "helipad"):

  <targets>
      <hospital>
          <longitude-deg>-122.4950109</longitude-deg>      mandatory
          <latitude-deg>37.51403798</latitude-deg>         mandatory
      </hospital>
 
      <helipad>
          ...
      </helipad>
  </targets>

The tutorial system will for each calculate how the user's aircraft is positioned relative to the respective target, and offer the information in this structure:

  <sim>
      <tutorials>
          <targets>
              <hospital>
                  <direction-deg>12.345</direction-deg>
                  <heading-deg>33.333</heading-deg>
                  <distance-m>12404.932</distance-m>
                  <eta-min>39.2358</eta-min>
              </hospital>
 
              <helipad>
                  ...
              </helipad>
          </targets>
      </tutorials>
  </sim>

Where:

<direction-deg>   is an angle between the aircraft's velocity vector and the
                  azimuth to the target. 0 means that the aircraft is moving
                  right towards the target. 10 means that the target is slightly
                  to the right, -90 means that it's exactly left, and -180 or
                  179.9999 that it's right behind.
 
<heading-deg>     is the absolute heading that the aircraft would currently
                  have to fly with in a straight line to reach the target
 
<distance-m>      is the distance in meters
 
<eta-min>         is the "Estimated Time of Arrival" given the aircraft's
                  current speed towards the target. Positive times mean that
                  the aircraft is getting nearer to the target and can arrive
                  there in this time given the current speed. It will, of course,
                  only arrive there, if <direction-deg> is zero. A negative
                  number means that the aircraft moves away, or in other words:
                  that in this number of minutes it will be away twice as far.

Preset

These set the initial simulator state. All properties are optional. The last three entries are to define the position relative to the airport/runway or the longitude/latitude.

  <presets>
      <airport-id>KHAF</airport-id>
      <on-ground>1</on-ground>
      <runway>12</runway>
    <!--
      <altitude-ft>122.333</altitude-ft>
      <latitude-deg>37.555</latitude-deg>
      <longitude-deg>1000</longitude-deg>
    -->
      <heading-deg>0</heading-deg>
      <airspeed-kt>0</airspeed-kt>
      <glideslope-deg>0</glideslope-deg>
      <offset-azimuth>0</offset-azimuth>
      <offset-distance>0</offset-distance>
  </presets>

Set

<set> groups can be used in <init>, <step>, and <end>. They set a <property> to a given <value> or to the value that a second <property> points to. They can also reset values that were only temporarily changed for the duration of the tutorial. This is desirable for properties that are saved to the aircraft config file or to ~/.fgfs/autosave.xml.

  <set>
      <property>/foo/bar</property>        set /foo/bar to 123
      <value>123</value>
  </set>
 
  <set>
      <property>/foo/bar</property>        set /foo/bar to value of /test
      <property>/test</property>
  </set>

View

These groups can be used in <init>, <step>, and <end>. They smoothly move the view to a new view position/direction. All parameters are optional. If, for example, only <field-of-view> is set, then the view will only zoom in -- the direction and position will remain the same. This feature is meant for cockpit tutorials, where the pilot's view is directed to some switch or instrument.

  <view>
      <heading-offset-deg>20</heading-offset-deg>  positive is left
      <pitch-offset-deg>-4</pitch-offset-deg>      positive is up
      <roll-offset-deg>0</roll-offset-deg>         positive is roll right
      <x-offset-m>0.2</x-offset-m>                 positive is move right
      <y-offset-m>0.2</y-offset-m>                 positive is move up
      <z-offset-m>0.2</z-offset-m>                 positive is move back
      <field-of-view>55</field-of-view>            default: 55; smaller zooms in
  </view>

Marker

These are supported in <init>, <step>, and <end>. They show a magenta colored circle at given position (relative to aircraft origin) in given size. See the last section for how to conveniently find the proper coordinates.

  <marker>
      <x-m>1.3</x-m>                               positive is back
      <y-m>0.3</y-m>                               positive is to the right
      <z-m>0.1</z-m>                               positive is up
      <scale>1.3</scale>                           optional; default: 1
  </marker>

For this to work, the aircraft model needs to include the tutorial marker model in its animation xml file:

  <PropertyList>
      <path>lightning-f1a.ac</path>
 
      <model>
          <path>Models/Aircraft/marker.xml</path>
      </model>
 
      ...
  </PropertyList>

Finding marker coordinates

If an aircraft tutorial wants to use the marker, then the aircraft animation file needs to include the marker model (see above). If this is done, then one can use the "marker-adjust" dialog to find the respective <marker> coordinates. Just type this into the "Help->Nasal Console" dialog:

  tutorial.dialog()


Or temporarily add a key binding to the *-set.xml file:

  <key n="96">
      <name>Backtick</name>
      <desc>Open marker adjust dialog</desc>
      <binding>
          <command>dialog-show</command>
          <dialog-name>marker-adjust</dialog-name>
      </binding>
  </key>

The dialog allows to move a red cross around, which has the blinking marker circle in the middle. Note that ctrl- and shift-modifiers modulate the slider movements. Ctrl makes positioning coarser, and shift finer. The [Reset] button moves the marker back to aircraft origin, the [Center] button centers the sliders, and the [Dump] button dumps the marker coordinates to the terminal, for example:

  <marker>
      <x-m>1.1425</x-m>
      <y-m>0.1994</y-m>
      <z-m>-0.0844</z-m>
      <scale>2.0489</scale>
  </marker>

This just needs to be copied to the tutorial XML file.

Message/audio

Groups <step> and <end> can have one or more <message> entries, and one or more <audio> entries. If more are used of a kind, then the tutorial chooses one at random. If <audio> are available, then the contents are interpreted as file name of a *.wav sample, which is appended to the <audio-dir> path defined at the <tutorial> top level (default: "") and played by the tutorial system. Otherwise the <message> is handed over to the voice system, and synthesized to speech by the Festival speech synthesizer (if installed). In either case the chosen <message> is displayed on top of the screen. Neither <message> nor <audio> are mandatory.

Because one and the same <message> string can be displayed *and* be synthesized, which can be problematic in some cases, there is a way to specify parts for either display *or* voice synthesizer: "{<display part>|<voice part}". Example:

  <message>Press the {No1|number one} button!</message>

Here, "No1" would be displayed on the screen, but "number one" would be sent to the speech synthesis system. This can also be used to add invisible but audible exclamation marks: "Press the button{|!}"

Condition

These are explained in detail in $FG_ROOT/Docs/README.conditions. Here's just one example:

  <condition>
      <less-than>
          <property>/foo/bar</property>
          <value>12</value>
      </less-than>
  </condition>

This condition is true when the value of /foo/bar is less than 12, and false otherwise.