HowTo/Hide- & Unhide Meshes

From TrainzOnline
< HowTo
Revision as of 19:05, 21 April 2016 by Callavsg (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 set opened or closed. It looks like:

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

We'll learn today how to hide the "wrong" and show the right mesh. Also we want that the users may select the option in de "?"-Window of the scenery object. First we need a member variable to store the information if the door is opened or closed, then we need functions to save and load the information every time the map[the vehicle] loads.


Variables may be described as kind of placeholders. These placeholders offers us the opportunity to save data temporary.

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 - floating numbers (32 Bit)


Today we'll have a closer look on 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 opened

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);
    }
};


As you can see, we just have to begin with the type of our variable and then giving it a name. This is it. Now we can use it. This variable now is available in the whole CIceCreamTruck-Class, because we declared it as member, so we may say it's a global variable (that isn't true, but it's easier to explain). We also may declare variables within methods/functions. These then will only be accessable inside the function or method and not on other places in 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 only may declare variables inside classes.


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

These methods are implemented in native code. That means we don't have to care about when they are called. We just have to override them. Trainz will call it every time it is needed. 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, this functions task is to save data into a Soup. It expects a Soup-Object to be returend. 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 here! Every function/method we override has in most cases to inherit the parent classes method. The inherited in here calls GetProperties of Class MapObject. This makes sure, that every operation inside a method of the parent class is called too. Remember: We overrided 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 functionallity for a class, if not, don't forget it.

In the script above, we return the Soup directly, without any operation. What exactly return types are will be explained another time :) (it will be too much for us now!)


SetProperties

In this method we get a paramter 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 paramter.


Saving data

I already explained that Soup is a class. So it may has members, and in fact, it has. Look at: Class_Soup

A Soup is like a small database. We can set values by a name and a value. I use the variable name as name, because I don't have to look it up every time, and not to think, what name the value inside the soup has. The method "SetNamedTag" in class Soup is the right here for us to save a value. 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 know it, if Trainz should save the Soup for us.


Loading data

The oppisite 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 will use the method "GetNamedTagAsBool". It expects only one paramter, the name of our value inside the Soup. We can directly assign the value of "GetNamedTagAsBool" to our variable. But this is a oveerloaded function. There are some variants. Another variant expects two parameters. The first one is the name of our value, and the other one is a standard value, if the selected value is not set in the Soup. We'll need that one! Because if we place an object in surveyor, there are no values inside 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 put 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 whole where the door is, a door in opened state and a door in closed state. You also may achieve this by using animations, but this way is pretty easier.

Scripting 2 Truck whole.jpg
Truck with whole, where the door is


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 that is called "SetMeshVisible". We can use it directly, because we automatically inherit from MeshObject, too. SetMeshVsible expects three parameters. The name of our mesh-table entry, a state (true, false - [visible, invisible]) and a floating 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 has 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 can 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 hide. The closed state mesh is set with the oppsite value of m_bDoorOpened. This can be easily done by using the "!"-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 an 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

Personal tools