NML:Spritelayout

From GRFSpecs
Revision as of 18:27, 2 October 2023 by Andythenorth (talk | contribs) (→‎Sprite: - GROUNDSPRITE_RAIL_X and GROUNDSPRITE_RAIL_Y)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search
Block Syntax

Stations, houses, industry tiles, objects, and airport tiles use spritelayouts to define layouts how sprites are supposed to be arranged on a tile. A spritelayout can combine multiple sprites into one entity, which represents everything that is to be drawn on a particular tile. A simple example to illustrate how it works:

 spritelayout airport_building1 {
 	ground { sprite: GROUNDSPRITE_NORMAL; }
 	childsprite {
 		sprite: spr_small_dirt_ne; // custom spriteset
 		always_draw: 1; // also draw in transparent mode
 	}
 	building {
 		sprite: 0xA67; // reuse this existing base set sprite
 		xoffset: 0x0F;
 		xextent: 1;
 		zextent: 6;
 		recolour_mode: RECOLOUR_REMAP;
 		palette: PALETTE_USE_DEFAULT;
 	}
 }

The sprite layout is composed of one or more sprites. Each sprite is defined by a ground, building or childsprite block. What block to use depends on how the sprite is to be placed on the tile.

  • ground sprites are drawn at the base of the tile, like grass and concrete, rails etc. in normal TTD. With a few exceptions (e.g. when using custom foundations), you should always provide a ground sprite, even when the building above fully covers it. This because in transparent mode, the building becomes invisible. A tile can only have one ground sprite.
  • building sprites are drawn on top of the ground sprite. To determine their location and drawing order (what goes in front of what), they have a 3D bounding box.
  • With childsprite(s), you can effectively compose sprites from multiple parts. When placing these after a building sprite, they will use the same bounding box as the previous building sprite. When placing them before the first building sprite, they will have no bounding box, as if they would use the 'bounding box' of the ground tile. Multiple childsprites may be used per ground / building sprite.

The sprite order is genrally determined by the sprite sorter, that evaluates the bounding box (see below) of each sprite. Child sprites are always part of the building (or ground) sprite that precedes them. In the GUI, sprites are always drawn in the order specified in the layout, so be sure to get this correct for spritelayouts that are to be displayed there.

Per sprite, a number of parameters may be set. These are listed below. Unless otherwise indicated, each may have a value that is dependant on variables, parameters or registers. Accessed variables are always in the SELF scope. Accessing variables inside the purchase list is not supported, make sure that layouts accessed from there do not use them. Using variables and parameters, it's possible to create wildly differing looks with few spritelayouts.

Sprite

The most important parameter is sprite, this determines the actual sprite to be drawn. If you set this to a number, a TTD sprite will be drawn, usually from the base set. Pre-defined constants for some base set sprites are:

  • GROUNDSPRITE_NORMAL[1] default terrain type for that climate (temperate, toyland: normal, arctic: without snow, tropical: rainforest).
  • GROUNDSPRITE_DESERT [1] desert tile
  • GROUNDSPRITE_DESERT_1_2 [1] transition tile between desert and grass
  • GROUNDSPRITE_SNOW [1] snowy ground tile
  • GROUNDSPRITE_SNOW_1_4 [1] 1/4 snowy ground tile
  • GROUNDSPRITE_SNOW_2_4 [1] half-snowy ground tile
  • GROUNDSPRITE_SNOW_3_4 [1] 3/4 snowy ground tile
  • GROUNDSPRITE_SNOW_4_4 [1] snowy ground tile
  • GROUNDSPRITE_CONCRETE concrete ground tile
  • GROUNDSPRITE_WATER flat water tile
  • GROUNDSPRITE_CLEARED [1] ground tile that has just been bulldozed (with brown colour)
  • GROUNDSPRITE_RAIL_X [1] ground tile with rail and terrain appropriate ground, NE-SW orientation
  • GROUNDSPRITE_RAIL_Y [1] ground tile with rail and terrain appropriate ground, NW-SE orientation
  1. 1.00 1.01 1.02 1.03 1.04 1.05 1.06 1.07 1.08 1.09 1.10 These constants can be used for sprites on flat ground. There are also equivalent sprites for sloped ground, however. These sprites follow the initial ground sprite, the builtin function slope_to_sprite_offset(slope) may be used to compute the required offset. Refer to the reference on slopes or the example below.

Alternatively, you can set the value to (the name of) a spriteset, to provide a custom sprite. When the value is just a spriteset identifier, a sprite from that set will be selected depending on the construction stage, see below. All spritesets used in a layout must have the same number of sprites, due to a restriction in the NFO format. If you supply an argument (e.g. sprite: some_sprite_set(2);), the corresponding sprite from the sprite set will be used, with an argument of 0 corresponding to the first sprite. Note that the argument may also be variable, so you could e.g. use sprite: some_set(construction_state); to use the construction state to select the sprite (as is the default). If the spriteset has labels defined, you can use these labels in the argument expression.

  • For stations, objects and airport tiles, there are no construction stages, so the first sprite from the set is used always.
  • For houses and industry tiles, sprite chosen depends on the construction stage and the number of sprites available.
    • If there is only one sprite in the set, it is used always.
    • If there are two sprites, one is used during construction (stages 0-2) and one for the finished building (stage 3)
    • If there are three sprites, one is used for the beginning of construction (stage 0), one for the other construction stages (stages 1-2) and one for the finished building (stage 3).
    • If there are four sprites, one is used for each construction stage.
    • Sprites after the first four are always ignored.

Recolouring

Next, you have the option to apply recolouring, i.e. to change the colours of the sprite. This is done via the following attributes:

  • recolour_mode: This must be compile-time constant. Available recolour modes are:
    • RECOLOUR_NONE: Use no colour translation (default)
    • RECOLOUR_REMAP: Use a colour translation table as defined by palette. This tables maps all colours of the sprite to a new colour.
    • RECOLOUR_TRANSPARENT: Draw the sprite in transparant mode, using a colour translation table as defined by palette. Normally the palette will be set to PALETTE_TO_TRANSPARANT to draw all underlying colours somewhat darker. Note that the selected palette is applied to the colours of the underlying sprite, whatever that happens to be. The supplied sprite is only used to determine what pixels to recolour.
  • palette: This defines the palette which is used for the colour translation. It may only (and must!) be set when recolour_mode is set to RECOLOUR_REMAP or RECOLOUR_TRANSPARENT. The available values are the same as for sprite, i.e. you can use either a default sprite or a sprite from a sprite set. In this case, however, the referenced sprite must not be a real sprite, but a recolour sprite. For available default recolour sprites, see the appendix on available palettes.

Display yes/no

The following attributes allow configuring whether the sprite will be displayed or not.

  • hide_sprite: If set to 1, this sprite will not be drawn at all. Default is 0. If a building sprite is not drawn, all child sprites that share its bounding box are not drawn either. Setting this to a constant value makes little sense, but you can use this to enable/disable drawing certain sprites at runtime depending on certain conditions.
  • always_draw: This must be a compile-time-constant. If set to 1, this sprite will also be drawn when the user has enabled transparant mode. The default value is 0. This is not available for ground sprites (those are drawn always), but it is for child sprites that share their bounding box with the ground sprite.

Positioning

How to position the sprite depends on the type of sprite:

  • ground: Ground sprites cannot be positioned.
  • building: A three-dimensional bounding box may be defined. The X-axis runs from top-right to bottom-left and the Y-axis from top-left to bottom-right. Both X and Y are measured in 1/16 length of the the tile border. The Z axis is vertical and measured in pixels. Extending the bounding box over the edges of a tile is possible, but not recommended as it may lead to glitches. Unless you know exactly what you are doing, do not define any offsets or extents, but use NML's defaults. They are most likely exactly what you need. Default values are 0, 0, 0, 16, 16, 16, respectively. In NewGRF developer mode in OpenTTD, it's possible to view the bounding boxes of all sprites by pressing Ctrl+B. In order to use and define a custom bounding box, the following attributes can be used:
    • xoffset: Offset from the northwestern edge to the start of the bounding box (X). Unit is 1/16 of the tile border
    • yoffset: Offset from the northeastern edge to the start of the bounding box (Y). Unit is 1/16 of the tile border
    • zoffset: Offset from the lowest tile corner (with foundation added) to the start of the bounding box (Z). Unit is pixels
    • xextent: Size of the bounding box, in the X-direction. Must be a compile-time constant. Unit is 1/16 of the tile border
    • yextent: Size of the bounding box, in the Y-direction. Must be a compile-time constant. Unit is 1/16 of the tile border
    • zextent: Size of the bounding box, in the Z-direction. Must be a compile-time constant. Unit is pixels
  • childsprite: Child sprites may be positioned relative to their 'parent' sprite that defines the bounding box. xoffset and yoffset may be set to specify an offset in pixels between the origin of the parent and child sprite. When the parent sprite is the ground sprite, TTDPatch does not support offsets other than 0,0. Note that all child sprites should fit inside the bounding box of the parent sprite to avoid visual glitches.

Example (advanced) Spritelayout

OpenTTD 1.2 (r22723) allows for nice shorthands in defining multiple views, e.g. for different slopes: Spritelayout can have parameters and may use variables and temporary storage inside of a layout. A common usage for such parametrized spritelayout is taking care of the tile slope and ground type as illustrated in this example:

 spritelayout company_land_layout {
 	ground {
 		// normal ground sprite - always draw
 		sprite: LOAD_TEMP(0) + LOAD_TEMP(1);
 	}
 	childsprite {
 		// company-coloured border - always draw
 		sprite:        cc_frame(LOAD_TEMP(0));
 		always_draw:   1;
 		recolour_mode: RECOLOUR_REMAP;
 		palette:       PALETTE_USE_DEFAULT;
 	}
 	childsprite {
 		// again the normal ground sprite. Thus in non-transparent view
 		// only the normal ground sprite is shown. In transparent view
 		// this acts as sprite which darkens the other two sprites via
 		// a translation to transparency.
 		sprite: LOAD_TEMP(0) + LOAD_TEMP(1);
 	}
 }
 
 // A pseudo-switch which sets the temporary parameters for the sprite layout, storing the sprite number
 // which belongs to the terrain type and the corresponding offset due to the tile slope
 switch (FEAT_OBJECTS, SELF, company_land_terrain_switch, [
 			// We store the offset into the spriteset due to the tile slope into the 1st temporary variable
 			STORE_TEMP(slope_to_sprite_offset(tile_slope), 0),
 
 			// We store the offset to the flat groundsprite we use into the 2nd temporary variable
 			STORE_TEMP(GROUNDSPRITE_NORMAL, 1),
 			STORE_TEMP(terrain_type == TILETYPE_DESERT      ? GROUNDSPRITE_DESERT : LOAD_TEMP(1), 1),
 			STORE_TEMP(terrain_type == TILETYPE_SNOW        ? GROUNDSPRITE_SNOW   : LOAD_TEMP(1), 1),
 
 			1
 			]) {
 	company_land_layout;
 }