|
John Cletheroe's
Trainz Hintz - TRS2004 Scenario Creation Tutorial |
This tutorial shows how to make an AI train automatically shuttle back and forth on a parallel track, while the user is given a series of instructions to perform.
Create a branch off the lefthand track, in the direction away from the righthand track.
In what follows, please feel free to adapt the details and names as you wish provided you are consistent.
On the lefthand (usertrain) track, create a trackmark called "Trackmark 1" and three triggers called "Trigger 1", "Trigger 2" and "Trigger 3". Each of the triggers should be on a different branch of the junction (LEFT, RIGHT and BACK).
On the righthand (AI train) track, create a trackmark called "Trackmark 2" and two triggers called "Trigger 4" and "Trigger 5".
Make sure each of the trackmarks and triggers is some way from the end of the track, and some way from its neighbours.
Save the layout and Export Scene Data to create a new scenario.
Add the usual statements to place playertrain on Trackmark 1 and aitrain on Trackmark 2.
Add statements to give the user an endless series of objectives, for example:
World.SetGameTimeRate( World.TIME_RATE_1X );
World.SetGameTime( 0.875 ); // 9am
World.SetWeather( World.WEATHER_TYPE_CLEAR, World.WEATHER_CHANGEABILITY_NONE );
World.SetCamera( playertrain, World.CAMERA_EXTERNAL );
World.SetCameraAngle( 350, -15, 75 );
playertrain.SetAutopilotMode( Train.CONTROL_MANUAL );
int mode = 0;
int previousmode = 0;
string destination;
while (1 == 1)
{
while (mode == previousmode)
{
mode = Math.Rand(1, 4); // 1, 2 or 3
}
previousmode = mode;
if (mode == 1)
{
destination = "Trigger 1";
}
if (mode == 2)
{
destination = "Trigger 2";
}
if (mode == 3)
{
destination = "Trigger 3";
}
Interface.SetObjective("Waiting for you to enter " + destination + ".", "");
Navigate.OnTrigger(me, playertrain, destination, Navigate.TRIGGER_ENTER);
Interface.SetObjective("You have entered " + destination + ".\nWait for new instructions.","");
Sleep(5);
}
Check that the scenario works so far. At this stage we are not doing anything with the AI train. The user should be told to drive to one of the triggers on the lefthand track chosen at random, then to a different one, and so on forever. As this section of the program is not the main focus of this tutorial, and it is fairly straightforward, it will not be explained in detail.
When all is ok, make the following amendments to the scenario's gs file:
Immediately above the "main thread" comments, insert these statements (which will be explained later):
void Trigger4Handler(Message msg)
{
if (msg.minor == "Enter")
{
(cast<Train> msg.src).SoundHorn();
(cast<Train> msg.src).SetDCCThrottle(0.5);
Interface.Print("The AI train has entered Trigger 4 - now going forwards.\n\n\n\n");
}
}
void Trigger5Handler(Message msg)
{
if (msg.minor == "Enter")
{
(cast<Train> msg.src).SoundHorn();
(cast<Train> msg.src).SetDCCThrottle(-0.5);
Interface.Print("The AI train has entered Trigger 5 - now going backwards.\n\n\n\n");
}
}
In the main section of the program, after the "string destination;" statement, insert the following:
AddHandler(cast<Trigger> Router.GetGameObject("Trigger 4"), "Object", null, "Trigger4Handler");
AddHandler(cast<Trigger> Router.GetGameObject("Trigger 5"), "Object", null, "Trigger5Handler");
Sleep(5);
aitrain.SoundHorn();
aitrain.SetDCCThrottle(0.5);
Interface.Print("AI train starting to move - going forwards.");
Interface.SetMessageWindowVisible(true);
The two handler functions, called Trigger4Handler and Trigger5Handler, will cause the AI train to sound its horn and change direction. This example assumes that Trigger 5 is forward of Trigger 4.
In the handler functions, the cast function is required because the functions have to know which train to operate on, and they derive this information from the internal inter-object messages. The inter-object message system is a subject in its own right and therefore a full description of it is well beyond the scope of this tutorial. It is extremely useful for performing some very advanced tricks. In brief, every time an event occurs, objects send internal messages to and from each other. Each message has four properties: its source (src), its destination (dst), its major part and its minor part. The handler functions check that the minor part of the message is "Enter" so as to only react when the AI train enters the trigger and not when it stops in the trigger or when it leaves the trigger.
In the main part of the program, the AddHandler functions set things up so the handler functions "lie in wait" and will be obeyed whenever an event occurs on their specified trigger. The cast statements here are required to convert the triggers' names into the trigger objects themselves.
Do not insert any unnecessary spaces in AddHandler functions, as in my experience doing so causes the function to be ignored (with no error message).
The statements after the AddHandler functions should be fairly obvious. Interface.Print displays a message in the radio text box.
When the scenario is run after making these changes, the AI train should automatically run back and forth between the two triggers on the righthand track, with its horn sounded and radio text box messages displayed every time it reverses direction. While this is going on, the user can operate their train in accordance with the instructions displayed in the scenario objective panel. Even though the main program might be waiting for an extended period of time for the user train to enter the next trigger, the AI train will still correctly reverse at each end of its track.
In a real scenario, AI trains can be made to perform more interesting operations than merely running back and forth.
Handler functions can also be used for other purposes.
Be careful to only include commands in handler functions which will be completed very rapidly. This is because while the handler function is being obeyed, the main program is not. It might be possible to use threads to get round this problem but I have not yet investigated that subject.
Most recently modified 1-Aug-09