NMLTutorial/Tram graphics

From TTWiki
Jump to navigationJump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

The example used here is the HTM 4001 from the Dutch Tram Set. The original code and graphics for this are by FooBar. Code and graphics are both licensed according to the GPL v2 or later. The code has been modified for the purpose of this tutorial.

This continues the second part of the tram example. Now graphics will be added to the tram.


The graphics

As this tutorial is about coding NML and not about drawing graphics, we'll give you the graphics for free:

Graphics for each part of the HTM 4001. Drawn by FooBar.

Templates

The graphics in this file are templated (and actually use the templates used as example for the template chapter of this tutorial). That means we'll be using templates here as well. Check back if you don't remember how to do that. These are the templates you should end up with:

template template_tram_28(x, y) {
    //[left_x,  upper_y,    width,     height,     offset_x,     offset_y]
    [x,         y,          10,        28,           -4,         -11]
    [x+ 20,     y,          26,        28,          -17,         -14]
    [x+ 50,     y,          36,        28,          -20,         -20]
    [x+ 90,     y,          26,        28,           -9,         -15]
    [x+120,     y,          10,        28,           -4,         -13]
    [x+140,     y,          26,        28,          -16,         -16]
    [x+170,     y,          36,        28,          -16,         -20]
    [x+210,     y,          26,        28,           -8,         -16]
}

template template_tram_24(x, y) {
    //[left_x,  upper_y,    width,     height,     offset_x,     offset_y]
    [x,         y,          10,        28,           -4,         -11]
    [x+ 20,     y,          26,        28,          -17,         -14]
    [x+ 50,     y,          36,        28,          -22,         -20]
    [x+ 90,     y,          26,        28,           -9,         -15]
    [x+120,     y,          10,        28,           -4,         -15]
    [x+140,     y,          26,        28,          -16,         -16]
    [x+170,     y,          36,        28,          -14,         -20]
    [x+210,     y,          26,        28,           -8,         -16]
}

template template_purchase(x, y) {
    //[left_x,  upper_y,    width,     height,     offset_x,     offset_y]
    [x,         y,          50,        12,          -25,          -6]
}

The purchase menu sprite will be added on the next page, but it doesn't hurt to have the template defined now. Every template has arguments for the position of the top left sprite corner.

Spritesets

With these templates, we can make three spritesets for the three vehicle parts. We give the spritesets proper names and include a reference to the image file:

spriteset(spriteset_real_htm_4001, "gfx/htm_4001.png") {
    template_tram_28(0, 20)
}
spriteset(spriteset_real_htm_4001_1, "gfx/htm_4001.png") {
    template_tram_24(0, 60)
}
spriteset(spriteset_real_htm_4001_2, "gfx/htm_4001.png") {
    template_tram_28(0, 100)
}

Notice that the first and last vehicle part are 28 px long and use the 28 px template. The middle part is only 24 px long and uses the 24 px template.

Because there are no different sprites for "empty", "full", "loaded" and "loading", we don't have to define a spritegroup in this case. If you had different sprites for these different states, you would need to combine them in a spritegroup first.


Attaching the graphics with a switch block

From the graphics block we will use the default callback to reference the spritesets. However, because we can reference only one spriteset (or -group) from the graphics block, we need an intermediate switch block to decide the graphics depending on the position in the vehicle.

So from the graphics block we reference a switch block for the default graphics:

    graphics {
        articulated_part:        switch_articulated_htm_4001;
        cargo_capacity:          switch_capacity_htm_4001;
        default:                 switch_spriteset_htm_4001;
    }

This then links to a switch block. The workings of this switch block will be similar to that of the cargo capacity callback, making a decicion based on the position_in_consist variable. However, this time we need three separate ranges (because three different spritesets) and we mustn't return a value but now reference these spritesets:


switch (FEAT_ROADVEHS, SELF, switch_spriteset_htm_4001, position_in_consist ) {
    1: spriteset_real_htm_4001_1;
    2: spriteset_real_htm_4001_2;
    spriteset_real_htm_4001; //default
}

So for the second vehicle part (with index 1 in position_in_consist) we use the graphics of the 'spriteset_real_htm_4001_1' spriteset block. For the last part (index 2) we use the spriteset_real_htm_4001_2 spriteset. For all other parts (in this case only the first) we'll default to the spriteset_real_htm_4001 spriteset.

This has the graphics sorted and you can encode the NewGRF to see the result ingame.


Vehicle length callback

If you did encode the previous results, you'll notice that the individual vehicle parts are very far apart. This is because we used shorter sprites than the 32px default length. We'll need to tell the game that our vehicle parts are shorter!

This is done by means of the vehicle length callback, incidentally named length for use in the graphics block, look it up here. Because different parts have different lengths, we once more can't just return a value from the graphics block but need an intermediate switch block:

    graphics {
        articulated_part:        switch_articulated_htm_4001;
        length:                  switch_length_htm_4001; //<-- this one is added now
        cargo_capacity:          switch_capacity_htm_4001;
        default:                 switch_spriteset_htm_4001;
    }

The switch block itself will once more make a decision based on the position_in_consist variable. The value to return for the callback is the desired length of the vehicle part, where 8 is longest possible length (32px) and 1 is the shortest possible length (4px). The first and last part of the vehicle are 28px, which equals a value of 7. The middle part is 24px, which equals a value of 6. The switch block:

switch (FEAT_ROADVEHS, SELF, switch_length_htm_4001, position_in_consist) {
    1: return 6;
    return 7;
}

The second vehicle part (with index 1 in position_in_consist) returns value 6 and a default value 7 is returned for all other positions (the first and last part of the vehicle). Counting of the vehicle parts starts at zero and you only have to list the part numbers that differ from the default you'll be specifying. Once more notice that the identifier of the switch block is the same as that used in the graphics block.

This should fix our vehicle and now the parts actually appear connected.


Total code so far

If you put everything in the correct order, you should have this:

// define the newgrf
grf {
	grfid:	"\FB\FB\01\02";
	name:	string(STR_GRF_NAME);
	desc:	string(STR_GRF_DESCRIPTION);
	version:		REPO_REVISION;
	min_compatible_version:	87;
}

///////////////////
//Vehicle sprites//
///////////////////

spriteset(spriteset_real_htm_4001, "gfx/htm_4001.png") {
    template_tram_28(0, 20)
}
spriteset(spriteset_real_htm_4001_1, "gfx/htm_4001.png") {
    template_tram_24(0, 60)
}
spriteset(spriteset_real_htm_4001_2, "gfx/htm_4001.png") {
    template_tram_28(0, 100)
}

switch (FEAT_ROADVEHS, SELF, switch_spriteset_htm_4001, position_in_consist ) {
    1: spriteset_real_htm_4001_1;
    2: spriteset_real_htm_4001_2;
    spriteset_real_htm_4001; //default
}

/////////////////////
//vehicle callbacks//
/////////////////////

//set number of articulated parts
switch (FEAT_ROADVEHS, SELF, switch_articulated_htm_4001, extra_callback_info1) {
    1..2: return item_tram_htm_4001; //use same vehicle for all parts
    return CB_RESULT_NO_MORE_ARTICULATED_PARTS; //stop adding vehicle parts
}

//set length of each part
switch (FEAT_ROADVEHS, SELF, switch_length_htm_4001, position_in_consist) {
    1: return 6;
    return 7;
}

//set capacity of each part
switch (FEAT_ROADVEHS, SELF, switch_capacity_htm_4001, position_in_consist ) {
    1: return 26;
    return 38; //default
}

//////////////////////
//vehicle properties//
//////////////////////

item (FEAT_ROADVEHS, item_tram_htm_4001) {
    property {
        name:                        string(STR_NAME_HTM_4001);
        introduction_date:           date(2006,1,1);
        model_life:                  VEHICLE_NEVER_EXPIRES;
        retire_early:                0;
        vehicle_life:                25;
        loading_speed:               25;
        cost_factor:                 224;
        running_cost_factor:         112;
        speed:                       81 km/h;
        power:                       966 hp;
        weight:                      58 ton;
        cargo_capacity:              (38*2+26)/3;
        tractive_effort_coefficient: 0.5;
        air_drag_coefficient:        0;
        //sound_effect:              no sound
        //visual_effect:             use default (none)
        
        //callback_flags:            no need to set this
        reliability_decay:           20;
        climates_available:          ALL_CLIMATES;
        refittable_cargo_classes:    bitmask(CC_PASSENGERS);
        sprite_id:                   SPRITE_ID_NEW_ROADVEH; //use custom sprites
        misc_flags:                  bitmask(ROADVEH_FLAG_TRAM); //make this a tram
        refit_cost:                  0; //refits are free
        running_cost_base:           RUNNING_COST_ROADVEH;
    }
    
    graphics {
        articulated_part:        switch_articulated_htm_4001;
        length:                  switch_length_htm_4001;
        cargo_capacity:          switch_capacity_htm_4001;
        default:                 switch_spriteset_htm_4001;
    }
}


The graphics are there, the tram seems complete. Or is it? There still is a sprite for the purchase menu left and if you'll look closely you'll notice that the capacity displayed in the purchase menu isn't correct. And remember the text we added to the language file to be displayed in the purchase menu? That's all next, in the last part of this tram example.


NML Tutorial: Tram graphics