HowTo/Hide- & Unhide Meshes

From TrainzOnline
< HowTo
Revision as of 03:00, 20 August 2017 by Trev999 (Talk | contribs)

Jump to: navigation, search

In HowTo/Your first Trainz Script we created the script:


myscript.gs
include "MapObject.gs"

class CMyClass isclass MapObject
{
    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }
};


Today we're gonna use this little example for a vehicle. There is an ice cream truck on the DLS by misterairbrush (<kuid:414943:100987>). This ice cream truck has a door that can be opened or closed. It looks like:

Scripting 02 Truck 1.jpg Scripting 02 Truck 2.jpg

The truck comes equipped with two versions of the door, called meshes. One or the other mesh is visible at any one time but never both together. We'll learn today how to hide the "wrong" and show the right mesh. Also we want users to be able to select an option in the "?"-Window of the scenery object to determine whether the door is to be open or closed.

First we need a member variable to store the (user-selected) open/closed state of the door, then we need functions to save and load the information every time the session starts or is saved.


Variables store values used in the game. These values can be changed by script and are stored in the computer's memory until needed.

In Trainz Script there are four (well, really there are five, but we will ignore one this time!) data Types we can use:

  • bool - boolean constants true/false
  • int - every number between -2147483647 and 2147483647 (a 32 Bit integer)
  • string - characters
  • float - numbers with a decimal point (32 Bit)


Today we'll have a closer look at the bool type. The others will be explained another time :)


In our case, we only need a true/false value.

True: The user wants the door to be open

False: The user wants the door to be closed


How do we declare a member variable? This is pretty easy:


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }
};


We begin by stating the data-type of our variable and then give it a name. Now we can use it. This variable now is available in the whole CIceCreamTruck-Class, because we declared it before any other methods, so we may say it's a global variable (that isn't true, but it's easier to explain). We may also declare variables within methods/functions. These then will only be accessible inside the function or method and not in other parts of the script. In Trainz Script there are now real global variables (that's why I think we may call the members global, even if it's not true). We may only declare variables inside classes.


Next, we will have a look at two new methods:

These methods are implemented in native code. That means we don't have to worry about when they are called. We just have to override them. Trainz will call them whenever needed. To override a class means to replace the built-in class with a new one that extends the functions of the original (the parent) while retaining all the original functionality. Let's override them:


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }

    public Soup GetProperties(void)
    {
        Soup pSoup = inherited();
        return pSoup;
    }

    public void SetProperties(Soup pSoup)
    {
        inherited(pSoup);
    }
};


GetProperties

As I said above, the purpose of this function is to save data into a Soup. It expects a Soup-Object to be returned. You'll see that Soup is a class, not a data type.

But we're able to declare class instances as variables too. (Look at: Soup pSoup = inherited();)

And again: The inherited is in there! Every function/method we override has in most cases to inherit the parent classes method. The inherited statement calls GetProperties of Class MapObject. This makes sure, that every operation inside a method of the parent class is called too. Remember: We overrode the method. If we don't inherit it here again, the parent classes GetProperties-Method will never be called! Only miss the call to inherited if you want to disable functionality for a class, if not, don't forget it.

In the script above, we return the Soup directly, without any changes. What exactly "return types" are will be explained another time :) (it is too much detail to go into now!)


SetProperties

In this method we get a parameter of type soup. We inherit the MapObject-Setproprties. This time we don't have to return any value. Here we will load the data from the Soup we got as parameter.


Saving data

Think of soup as a container into which you throw pieces of paper with names and values. When you want to find out what you wrote on the paper you look through all the pieces until you find the name and then read off the value. I already explained that Soup is a class. So it may have members, and in fact it has. Look at: Class_Soup

I use the variable name as the soup tag name, because it's convenient. The method "SetNamedTag" in class Soup is used to assign a value to each tag in the soup. We can access all public methods inside a class with the dereference operator, that is a single dot (.). So, we call the function like this:

pSoup.SetNamedTag("NAME", [VALUE]);


Now in script, it looks like:


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }

    public Soup GetProperties(void)
    {
        Soup pSoup = inherited();
        pSoup.SetNamedTag("m_bDoorOpened", m_bDoorOpened);
        return pSoup;
    }

    public void SetProperties(Soup pSoup)
    {
        inherited(pSoup);
    }
};


Now you'll understand, why we have to return the Soup back to Trainz. We manipulate the values inside the Soup and Trainz has to be kept informed, if Trainz wants to save the Soup for us.


Loading data

The opposite of "Setting data" (SetNamedTag) is "Getting data" (GetNamedTag). And, yes, Soup has a method called "GetNamedTag", but it'll return the value as a string. We need a boolean value. So, we could use the method "GetNamedTagAsBool". It expects only one parameter, the name of our value inside the Soup. We can directly assign the value of "GetNamedTagAsBool" to our variable. But this function has another variant which expects two parameters. The first one is the name of our value, and the other one is a default value, if the selected value is not set in the Soup. We'll need to use that method because, if we place an object in surveyor, there are no values in the Soup. So, we can set an initial value for the door state. In our case we will set the initial door state to closed (so: m_bDoorOpened has to be false).

m_bDoorOpened = pSoup.GetNamedTagAsBool("m_bDoorOpened", false);

If we add that part into our script it'll look like:


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }

    public Soup GetProperties(void)
    {
        Soup pSoup = inherited();
        pSoup.SetNamedTag("m_bDoorOpened", m_bDoorOpened);
        return pSoup;
    }

    public void SetProperties(Soup pSoup)
    {
        inherited(pSoup);
        m_bDoorOpened = pSoup.GetNamedTagAsBool("m_bDoorOpened", false);
    }
};


Now we can display and hide the meshes. But the object itself has to be prepared for it. The Ice cream truck has three meshes: The truck with a hole where the door should be, a door in the opened state and a door in the closed state. You also may achieve this by using animations, but this way is much easier.

Scripting 2 Truck whole.jpg
Truck with hole, where the door should be


Scripting 2 Doors open.jpg
Door in opened state


Scripting 2 Doors closed.jpg
Door in closed state


In script we reference the mesh-table entries of config.txt. It may look like:

[...]

mesh-table
{  
   default
   {
     mesh                                "truck.im"
     auto-create                         1
   }
  
   door-opened
   {
     mesh                                "door_opened.im"
     auto-create                         0
   }
  
   door-closed
   {
     mesh                                "door_closed.im"
     auto-create                         0
   }

   [...]
}

[...]

All meshes we want to control by script may never have an auto-create value of 1! It has to be 0, otherwise we can't take the control over the meshes.

Our CIceCreamTruck's parent class is MapObject. And MapObject's parent class is MeshObject, and there we will find a method "SetMeshVisible". We can use it directly, because we automatically inherit from MeshObject, too. SetMeshVsible expects to receive three parameters: The name of our mesh-table entry, a state (true or false - [visible, invisible]) and a floating-point number that sets a fading time in seconds. We'll leave the last parameter 0.0f.

SetMeshVisible("NAME", true/false, 0.0f);


We have two meshes that have to be set. In script we'll hide/show them like this:


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }

    public Soup GetProperties(void)
    {
        Soup pSoup = inherited();
        pSoup.SetNamedTag("m_bDoorOpened", m_bDoorOpened);
        return pSoup;
    }

    public void SetProperties(Soup pSoup)
    {
        inherited(pSoup);
        m_bDoorOpened = pSoup.GetNamedTagAsBool("m_bDoorOpened", false);

        SetMeshVisible("door-opened", m_bDoorOpened, 0.0f);
        SetMeshVisible("door-closed", !m_bDoorOpened, 0.0f);
    }
};


We use our bool variable for indicating the mesh state. The opened mesh is set with the value of m_bDoorOpened. If m_bDoorOpened is true, the mesh will display, otherwise it'll be hidden. The closed state mesh is set with the opposite value of m_bDoorOpened. This can be easily done by using the 'not' operator "!". Just place it in front of a boolean variable, to use the opposite value.

Now you know how to control meshes. Because it will be too much for now, I'll give the last part of the script without explanation and move it to another time :)


icecreamtruck.gs
include "MapObject.gs"

class CIceCreamTruck isclass MapObject
{
    bool        m_bDoorOpened;

    public void Init(Asset pAsset)
    {
        inherited(pAsset);
    }

    public Soup GetProperties(void)
    {
        Soup pSoup = inherited();
        pSoup.SetNamedTag("m_bDoorOpened", m_bDoorOpened);
        return pSoup;
    }

    public void SetProperties(Soup pSoup)
    {
        inherited(pSoup);
        m_bDoorOpened = pSoup.GetNamedTagAsBool("m_bDoorOpened", false);

        SetMeshVisible("door-opened", m_bDoorOpened, 0.0f);
        SetMeshVisible("door-closed", !m_bDoorOpened, 0.0f);
    }

    //////////////////////////////////////////////////////////////////////////////////////////////////////////////

    public string GetDescriptionHTML(void)
    {
        string sHtml = inherited();
        sHtml = sHtml + HTMLWindow.CheckBox("live://property/m_bDoorOpened", m_bDoorOpened) + "&nbsp;Open door";
        return sHtml;
    }

    public string GetPropertyType(string sPropertyId)
    {
        string sRet = "link";
        if(sPropertyId == "m_bDoorOpened")sRet = "link";
        return sRet;
    }

    public void LinkPropertyValue(string sPropertyId)
    {
        if(sPropertyId == "m_bDoorOpened")
        {
            m_bDoorOpened = !m_bDoorOpened;
        }
    }
};

Hope you enjoyed and learned!
Full example here: Media:TrainzDevIceCreamTruck.zip (Thanks to misterairbursh for the permission!)

If you want to contact me, you may use this e-mail: callavsg@gmx.de

callavsg

Edited by --Trev999 (talk) 02:28, 20 August 2017 (AEST)

Personal tools