NMLTutorial/Tram graphics
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:
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.
Shorten vehicle 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 shorten vehicle callback, incidentally named shorten_vehicle
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; shorten_vehicle: 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 how many times 12,5% (1/8) to reduce the vehicle length with. The first and last part of the vehicle are 28px, which is (1/8) shorter than 32px, so the return value here is 1. The middle part is 24px, which is (2/8) shorter so the return value for this part is 2. The switch block:
switch (FEAT_ROADVEHS, SELF, switch_length_htm_4001, position_in_consist ) { 1: return 2; return 1; }
The second vehicle part (with index 1 in position_in_consist
) returns value 2 and value 1 is returned for all other positions (the first and last part of the vehicle). 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 0xFF; //stop adding vehicle parts } //set length of each part switch (FEAT_ROADVEHS, SELF, switch_length_htm_4001, position_in_consist ) { 1: return 2; return 1; } //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; shorten_vehicle: switch_length_htm_4001; //<-- this one is added now 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