Mini Metro

Mini Metro

102 ratings
Mini Metro - Workshop Guide
By DPC Kasia
This is the official modding guide for Mini Metro, by Dinosaur Polo Club. Here you can learn how to install maps, as well as make your own! It will go through everything you need to know to get a map playable in-game, how to troubleshoot your maps, as well as Frequently Asked Questions
6
2
   
Award
Favorite
Favorited
Unfavorite
Installing Maps
From the Steam Workshop
To play maps from the Steam Workshop, you just need to Subscribe to the map from its the Mini Metro Steam Workshop page. Next time you start the game, Mini Metro will try and load the map. If it doesn’t appear, the map may have an error. The error should appear in your

Unity Player log file.
You can remove Steam Workshop maps by unsubscribing, and deleting its respective folder in the Workshop Content location for Mini Metro, in:
/steamapps/workshop/content/287980/

From a Map Folder
When you run Mini Metro for the first time, it will create a /mod/ folder in your Mini Metro Resources folder.



You can open this folder by clicking this button on the Community Maps screen, or by following the path below.

On Windows, this can be found in:
/AppData/LocalLow/Dinosaur Polo Club/Mini Metro/mod/

On Mac, this is in:
/Library/Application Support/unity.Dinosaur Polo Club.Mini Metro/mod/

On Linux, this is in:
~/.config/unity3d/Dinosaur Polo Club/Mini Metro/mod/

You can play maps that are not available on the Steam workshop by adding folders into the /mod/ folder. The map-maker will need to provide you a download link to a folder containing the map files. Once you have this, copy it into your /mod/ folder. The city folder must contain a city.json file, but may also include theme.json, train.json, audio.json, achievements.json, and steam.json.

Next time you start the game, Mini Metro will try and load the map. If it doesn’t appear, the map may have an error. The error should appear in your Unity Player log file.

You can remove the map by deleting its city folder from the /mod/ folder.

Log Files
Your Unity Player log file is where errors that occur during map-loading will appear. If a map does not appear in the Community Maps list, you should check here.

On Windows, this can be found in:
/AppData/LocalLow/Dinosaur Polo Club/Mini Metro/Player.log

On Mac, this is in:
/Library/Logs/Dinosaur Polo Club/Mini Metro/Player.log

On Linux, this is in:
~/.config/unity3d/Dinosaur Polo Club/Mini Metro/Player.log
Map Creation
To create a new map, you’ll need to create a new sub-folder for the city within the /mod/ folder. You can find the location of this folder, above. Give the folder a unique name, based on the city.

Maps are edited through JSON-formatted files you add into this subfolder. All maps need a city.json, which defines the majority of gameplay info about the city. Other files, which you can add to the folder, change other aspects of your city.
  • theme.json defines the appearance of the map.
  • trains.json defines the speed, capacity, and other information about locomotives in the city.
  • achievements.json lets you configure custom challenges.
  • steam.json contains info that is used to upload to the Steam Workshop, and a preview.png/jpg/gif file will be uploaded for your mod's preview image.

File Format
Mini Metro will only attempt to read files ending with .txt and .json, and the text content of these files must be in JSON format.

JSON format is a readable data structure format made up of dictionaries and arrays. If a file isn’t in correct JSON format, the map will fail to load and try to print an error message to your Unity Player log.

JSON format is generally easy to read, and store information with keys and values in Dictionaries and Lists.

Dictionaries are in {curly brackets}, which contain unordered entries with specific names. Entries in dictionaries are stored with keys between quotation marks, followed by a value after a colon. Values can be strings of text between quotation marks, numbers, or booleans with the values true and false.

{ “type”: ”Locomotive”, ”max”: 0, ”count”: 2, ”weight”: 1.0, ”week”: 0, “maxWeek”: 4, },
Within this upgrade dictionary, you can see the key "type" with a string value "Locomotive", and the key "count" and "weight" with integer (whole number) and float (number with decimal points) values respectively.

{ “tag”: “Pavuna-L2SV”, “type”: “square”, “position”: [-2285, -217.7], “interchange”: true, “invincible”: true, “ghost”: false, },
In this station dictionary, you can also see that arrays can be stored within dictionaries with a key. The key "position" is itself an array, with a list of two numbers.

Arrays are in [square brackets], and contain an ordered list of values without keys to correspond to values. All the values in an array must be the same type. Values can be strings of text between quotation marks, numbers, or booleans with the values true and false.

"initial": [ "Line", "Line", "Locomotive", "Locomotive", "Crossing", "Crossing", "Crossing", "Interchange", "Interchange" ],
This array is a list of strings, which read out the initial upgrades for a city.

“lines”: [ { “maxTrains”: 8, “allowedTrains”: [“Tram”, "Carriage”, “Crossing”,], }, { “maxTrains”: 8, “allowedTrains”: [“Tram”, "Carriage”, “Crossing”,], }, { “maxTrains”: 6, “allowedTrains”: [“Locomotive”, “Carriage”, “Crossing”,], }, ],
In addition, you can have arrays of dictionaries, or arrays of arrays. The above array is a list of dictionaries. Each dictionary configures options for the lines in a city.

Entries in dictionaries and arrays must always be separated by commas.

There are many excellent guides to learn JSON formatting online, such as W3School’s syntax guide[www.w3schools.com], and various tools to debug incorrect JSON format[jsonformatter.curiousconcept.com]. You can download a text editor such as Sublime Text[www.sublimetext.com], which contains built-in JSON format packages that will point out errors as you type.

Debugging
Every time you make a change to a map, you need to restart the game for it to take effect. Maps that you've subscribed to on the Steam Workshop, or from your /mod/ folder, are only loaded when the game starts.

If there's any kind of major issue with your map, Mini Metro will not load it. You can check your Unity Player log file to look for any issues with your map, and try and fix them. Most map-loading warnings or errors will appear on a line with FromUgc, so you can search for that.
City Basics
The city.json file will contain the core gameplay info for your new city.

If you want to get started fast, you can download the Workshop Demo Map file here[drive.google.com], and follow along with this guide. All the information in the JSON files in the Demo Map will follow the order that it appears in this guide.

"id":"kyiv", "customName":"Kyiv", "customDescription":"Keep Kyiv's Soviet-style metro running smoothly.", "customLocalName": "Київ", "customLocalNameLocale": "uk", "crossingStyle": "Bridge", "lineCount":5,
Everything inside a city.json file should be included in one large dictionary. These elements should appear at the top!

"id":"kyiv",
"id" is a unique string that identifies your map. It won’t show up anywhere in-game, but it needs to be different from any other city’s ID.

"customName":"Kyiv", "customDescription":"Keep Kyiv's Soviet-style metro running smoothly.",
These string values determine the what will be shown for the map's name, and description, on the Community Maps Play menu.

"customLocalName": "Київ", "customLocalNameLocale": "uk",
If your city has a unique name in the native language to the region, you can make that local name appear in smaller text beneath the main name by setting "customLocalName".

You also need to set the "customLocalNameLocale" to the language code for that local name, if it uses non-English characters. Not all characters can be shown, especially for CJK languages.

You can check out the Language Codes appendix for a list of all usable values.

"crossingStyle": "Bridge"
Crossing Style determines whether the map will use Tunnels or Bridges. The value can either be “Bridge” or “Tunnel”. This has no gameplay effect and only determines if the game shows the bridge or tunnel symbol in the upgrades toolbar, and in the weekly upgrades screen.

"lineCount": 7
Line Count is a number value (note: no quotations around the number!) determines how many lines are available at most in the city.

Keep in mind that all vanilla maps have between 5 to 7 lines, so keeping the value within this range is a good idea. The maximum allowed is 20 lines.

"audioLoadoutId ": "london"
Optionally, you can change the "audioLoadoutId" string value in city.json to make your map use the sounds of another vanilla city.

The “london” loadout is used by default, but try changing it to “sanfrancisco” or “saintpetersburg”, or the lowercase, space-less name of any other vanilla city, and hear how the game will sound different.

You can use any value from the Audio IDs Appendix section for this value.
Theme
Adding a theme.json file will give you control over how the map looks, including the line colours, water and station appearance and outline weight, night mode options, and so on.

Theme File Basics
“name”: “Dnieper”,
Like the "id" in the city.json file, "name" is a unique ID that each theme.json file must have.

(In city.json:) “theme”: “Dnieper”,
Once you set the "name", make sure to go back to your city.json file, and set the city’s “theme” value to the “name” in your theme.json file.

If you don't want to make a custom theme, you can use any of the values from the Vanilla Theme Appendix as the value for "theme" in the city.json file.

When adding colours, they appear in an array of three numbers, corresponding to the RGB values of a colour (from 0 to 255 with no decimals).
"day": [255, 255, 255] Red Grn Blue

When picking out the colour values, it’s best to reference any existing metro maps of the city that you're making. You can use image editing software such as Photoshop or Gimp to pick out the RGB values of colours in the image with an Eyedropper tool, and then use those values in the theme.

Backgrounds and People
“backgroundColor”: { “day”: [255, 255, 255], “night”: [36, 32, 33] }
The "backgroundColor" dictionary is the most straightforward in the file. This dictionary contains two colour arrays. "day" is a colour array that defines how the background will be coloured if the player has default options, and "night" is a colour array that defines how the background will appear with Night Mode enabled.

If you don't set a "night" colour, the "day" colour will be used.

"peepColor": { "day": [68, 51, 51], "night": [242, 242, 242] },
Structured similarly, the "peepColor" dictionary defines how peeps (the commuters represented as shapes that appear at stations) will be coloured on the map.

Obstacles and Stations
"waterColor": { "internal": { "day": [228, 236, 244], "night": [228, 236, 244] }, "outline": { "day": [228, 236, 244], "night": [228, 236, 244] }, "outlineWidth": -1.0, "previewOutlineWidth": -1.0, }, "stationColor": { "internal": { "day": [255, 255, 255], "night": [53, 49, 54] }, "outline": { "day": [10, 4, 4], "night": [242, 242, 242] }, "outlineWidth": 4.0 },
The "waterColor" and "stationColor" dictionaries define the colour of water obstacles, and stations in the city, respectively.

These colour dictionaries work a little differently. The "internal" and "outline" dictionaries define the body and outline colour for obstacles and stations, and contain a day and night variant as above.

"outlineWidth": 4.0, "previewOutlineWidth": -1.0,
The "outlineWidth" and "previewOutlineWidth" values set how thick the outline colour should appear, during gameplay and in the city preview respectively. These can be set to -1.0 to not appear at all.

Lines
"lineColors": [ {"day": [221, 37, 21]}, {"day": [37, 129, 196]}, {"day": [53, 171, 82]}, {"day": [240, 171, 0]}, {"day": [0, 191, 255]}, {"day": [255, 221, 85]}, ],
The "lineColors" array is a list of dictionaries with day and (optionally) night colour arrays. The order of colours listed will correspond to the colours of lines that are used in the map.

You must include at least as many Line Colours as your Line Count value, set in city.json. The city will fail to load if it cannot find enough line colours.
Obstacles
Obstacles are the rivers, lakes, water bodies and islands that will shape the way your map plays, as well as other visual elements you want to include in your maps, and background elements to give your map a smooth transition in.

"obstacles":[ { “points”: [ [-200.0,-200.0], [200.0,-200.0], [200.0,200.0], [-200.0,200.0], ], “visual”: true, “inverted”: false, “decoration”: false, “inPreview”: true, “cornerRadius”: -1.0, (Optional Values:) “color”: { “day”: [200, 200, 200], “night”: [55, 55, 55], }, “parent”: 1, "fixPreview": false, }, [...] ],
Obstacles are stored as dictionaries in the "obstacle" array of your city.json file.

The order that obstacles are added is important. The first dictionary in this array is drawn first, then the second on top of that, and so on. That means that islands have to be added after your main body of water, which is added after any background shapes.

Points
“points”: [ [-200.0,-200.0], [200.0,-200.0], [200.0,200.0], [-200.0,200.0], ],
The "points" array provides a list of 2D vertices, which are filled in to create the obstacle visually. Each array inside "points" contains two float numbers, corresponding to the X and Y values in the map.

The first number refers to the left-right X axis, and a higher X number will mean a point further right, and a lower number means a point further left. The second number is for the up-down Y axis where a Y lower number will make a point higher up on the screen, and a higher number means a point lower down.

Tools for Drawing Points
It’s helpful to have a tool to draw your map’s obstacles with. It’s easiest to design maps in SVG format using a vector editor, such as Adobe Illustrator, Inkscape or BoxySVG. Since you’ll just be making relatively simple shapes without complex curvature, most software should let you work on this right out of the box.

It's helpful to import real metro maps or some satellite screenshots of a city at various resolutions, to have some references to trace your water bodies from.

In your SVG editor, you should use a basic Pen or Pencil tool to draw each of your planned obstacles: your background, any colliding water features and islands, and decorative obstacles.

All islands that you draw should be totally contained within a water obstacle. Otherwise, obstacle-loading may fail, and your map won't load correctly.

In your SVG Editor, click without dragging to create straight lines from point to point with the Pen or Pencil tool. Make sure to close each of these objects into a shape, not just an open path, by clicking the first point of the line when you're done. This closes the shape. In addition, make sure to not use the curvature tools that your SVG editor will have to make curved vectors. Everything should be a straight line.

As a general rule of thumb, try and keep the core gameplay features of your map in a 4000 pixel wide, by 3000 pixel high region, centered at the top left [0,0] point of your SVG workspace. Be mindful that you have less vertical space and vision in-game than you do horizontal.

It’s important to have the map centred at [0,0], as this will be the centre of your screen when the map is fully zoomed out. In Illustrator or Inkscape, the [0,0] point is the top-left corner of your artboard or workspace. You might have to drag your obstacle up and off the screen to center them in the top-left corner.

Once you’re happy with the way your map looks in your editor, save the file in .svg format. If you have the option, save with SVG 1.1 format.

Converting SVG to JSON Format
Now you need to use a tool like Sublime Text, or any other text editor, and open your .svg file as if it were plain text. Right click the SVG, and Open With your preferred text editor.

If you’ve done everything right, you’ll find that SVG is saved in a readable format. If you correctly close the paths you were drawing to create polygons, you’ll see your shape. Search for its name, if you labelled it, and you should see a list of all the absolute points.

If you find any letters in the definition of the shape, it's likely you've added curvature somewhere. Alternatively, if the coordinates don't look right, or the numbers aren't large enough, it may be the case that the coordinates are in relative points. In both of these cases, refer to the "Obstacles Troubleshooting" section for a fix.

Copy out the points for each obstacle into a new file, and do some find-and-replaces to get the format correct for the obstacle points array. You can follow these steps:

  1. Delete any line breaks within the obstacle. All the points should then be on one line, separated by spaces, with the X and Y values separated by commas.
  2. Replace all spaces with line breaks, so each point is on a new line.
  3. Because Mini Metro’s negative Y coordinate is up, replace each "," with ",-" to flip the Y value. Then, delete each "--", since a double negative makes a positive!
  4. Finally, add a "[" to the start of each line, and a "]," with a comma to the end of each line, and you have your JSON array of arrays!

You may also want to check for any duplicate points. Duplicate points that immediately follow each other in your obstacles can cause the obstacle to not load improperly. Also keep in mind that the obstacle is a looping path, so the first and last points will automatically connect.

Now just copy out your newly JSON-formatted list into the “points” array, and you have yourself an obstacle!
Obstacle Dictionaries
“visual”: true, “inverted”: false, “decoration”: false, “inPreview”: true, “cornerRadius”: -1.0, “color”: { “day”: [200, 200, 200], “night”: [55, 55, 55], }, “outlineColor”: { “day”: [180, 180, 180], “night”: [75, 75, 75], }, “parent”: 1,

The other values in an obstacle dictionary configure what an obstacle is doing in the map, how it affects gameplay, and so on.

“visual”: true,
The boolean value "“visual” determines if an obstacle is drawn on the map during gameplay.

“inverted”: false,
The boolean value "“inverted” determines if an obstacle is drawn is drawn as a water body, with the water colours set in theme.json, or, as an island or background object with the background colours. Your bodies of water should use the false value, whereas backdrops and islands will be set to true.

“decoration”: false,
The boolean value "decoration" determines if the obstacle will be used to check collision.

Setting this to true disables it as a gameplay element, and won’t require you to spent tunnels to cross it as a water obstacle.

Enabling this on obstacles that you know won’t ever be crossed, or on islands where you know stations shouldn’t appear, you can increase performance and cut loading time, especially on obstacles with many points.

“inPreview”: true,
The boolean value "inPreview" determines whether this obstacle will be drawn in the preview image. Any visual body of water or island should set this to true, but, setting this to false for objects that you know will be outside the preview window can increase performance and cut loading time.

This table shows the combinations of tags you should use for different types of obstacles.
"visual":
"inverted":
"decoration":
"inPreview":
Backgrounds
true
true
true
false
Water with Collision
true
false
false
probably true
Islands with Stations
true
true
false
probably true
Water without Collision
true
false
true
probably true
Islands without Stations
true
true
true
probably true
Preview-Only Water
true
false
true
true
Preview-Only Islands
true
true
true
true

“cornerRadius”: -1.0,
The "cornerRadius" value can be used to round out the edges of an obstacle. Setting this to a float value above 0 will bevel the sharp edges of an obstacle and create smooth corners.

Setting "cornerRadius" to -1.0 disables corner rounding.

“color”: { “day”: [200, 200, 200], “night”: [55, 55, 55], },
The "color" value is an extra optional dictionary you can add. Adding this will let you set custom colours for a specific obstacle, overriding the default water colour, or background colour, set in the theme.json file.

“outlineColor”: { “day”: [180, 180, 180], “night”: [75, 75, 75], },
Linewise, you can set an override outline colour, with the "outlineColor" dictionary.

“parent”: 1,
The "parent" value is a special value that you only need to add if you intend to have multiple bodies of water in a map.

Any island with collision needs to declare which water body it belongs to. The integer value of “parent” declares which water obstacle, starting at the top of your dictionary with 0, the island is a part of. If it’s excluded, the game will default to using the first body of water found from the top of the obstacles list.

Setting this incorrectly will cause obvious gameplay and visual issues with islands and water crossings in the map.

"fixPreview": false,
The "fixPreview" boolean value should be set to true if the obstacle is failing to fill-in correctly in your preview image.

This typically occurs when the preview window size (see the Preview section below) would cut the preview into multiple objects, rather than one connected object.
Obstacle Options
Lastly, to configure some global options for how obstacles work in your map, you can define the "obstacleOptions" dictionary in the main level of your city.json file.

"obstacleOptions": { "requireTunnelInsideObstacle": true, "creativeStationsInsideObstacle": true, },
This will configure these settings for all obstacles in the map, not just individual obstacles in the "obstacles" array, so it doesn't go in an obstacle dictionary.

Handling Links Inside Obstacles
"requireTunnelInsideObstacle": true,
If "requireTunnelInsideObstacle" is set to "true", it means that a Tunnel or Bridge crossing will be used up whenever you link to a station that's spawned inside an obstacle.

This lets you create maps with underground sections, or similar, where connecting between each station in the obstacle requires another link, like this.


Note how four tunnels are used to connect to four stations inside the obstacle. Each link with any station inside the obstacle costs a tunnel.

"requireTunnelInsideObstacle": false,
On the other hand, if "requireTunnelInsideObstacle" is set to "false", it will only cost a Tunnel for a line to go into or out of the obstacle for the first time. Linking stations that are inside the same obstacle won't cost any extra Crossings!

This might let you create a map with different elevations, requiring Bridges or Tunnels to move between different parts of the city! You can see how this differs from the previous case here.


Compare how only two tunnels are used here, and only on the links where the line crosses over the boundary of the obstacle.

Creative Mode Placement Rules
"creativeStationsInsideObstacle": true,
Finally, setting the "creativeStationsInsideObstacle" flag to true simply lets players place stations inside of obstacles in Creative Mode.
Zoom and Start Area
Now that your obstacles are in place, you ought to configure the Zoom and Start Area values, to make sure you have the scale right. It’s helpful to do this right away, in case you need to rescale your obstacles in your SVG editor.

"origin": [0,0],
The "origin" array defines the center point of your map. It should go in your city.json file's main dictionary.

If you centered your map around the 0,0 point in your SVG Editor, this should stay as [0, 0].

“zoom”: { “start”: 1.3, “end”: 0.4, “delay”: 1, “duration”: 55, “earlyZoom”: 0.5, “lateZoom”: 0.5, },
These values should go inside your city.json file's main dictionary.

“start”: 1.3, “end”: 0.4,
The "start" and "end" float values correspond to the initial and final camera zoom over the course of a map. A higher value means a closer-in camera.

You should play around with these values when your obstacles are in place, paying attention to the zoom at the end. Think about where stations will spawn in your map, or the “play area.”

The player around should roughly correspond to the border of the window with a end zoom scale between 0.35 to 0.55. Your map might be too big if you had to set the value too low, or too small if you had to set the value too high.

“delay”: 1, “duration”: 55,
The "delay" and "duration" integer values refer to the number of days before the camera zoom out begins, and how long it takes to reach the "end" value, respectively.

To test your "start" and "end" values quickly, you can set the "duration" to 1 for now.

“earlyZoom ”: 0.5, “lateZoom ”: 0.5,
The "earlyZoom" and "lateZoom" float values configure the speed at which the camera zooms out, after the delay, in the first and second half of the game. Set early zoom higher if you want the map to sprawl out from the starting area, and set late zoom higher if you want a really dense middle of the map.

Be careful: If your "start" and "earlyZoom" values make the screen too small, it can cause stations to not have enough space to spawn. Once a station type fails to spawn, due to not having enough room on screen, it will prevent all stations of that type from spawning again. Make sure you zoom the screen fast enough that all the stations in your Schedules can spawn! See the Station Spawns section below for more details.

“startArea”:{ “rightBottom”: [-0.3, -0.2], “leftTop”: [-0.3, -0.2] },
The "startArea" dictionary also goes in the main dictionary of the city.json file. It defines the boundaries for where the camera may start on a map. A value of 1.0 or -1.0 means the far edge of the playing area along the X or Y axis.

Keeping the rightBottom and leftTop values identical lets you set a very specific start location.

In this example, it says: start a bit to the left (-0.3), a little up (-0.2), from the [0,0] point of the map. Having different values for "rightBottom" and "leftTop" will define a square of possible starting camera positions.
City Areas
With your obstacles in place, the next step is to add City Areas to your map. City areas are shapes which tell the game where to spawn stations, how close together they can be, and how frequently each area should spawn each type of station.

"cityAreas": [ { "paths": [ { "points": [ [590.42,663.03], [547.83,501.15], [186.43,501.15], [146.68,656.64], [213.41,761.01], [255.3,761.01], [280.15,702.08], [318.49,705.63] ] }, { "points": [ [579.78,418.79], [558.47,361.28], [589.01,290.28], [673.49,246.26], [712.54,138.34], [698.34,91.48], [819.76,58.82], [875.84,73.73], [858.8,257.62], [751.59,462.1] ] } ], "density": 1.0, "stationSpawns": [ {"type": "CIRCLE", "weight": 0.4, "maximum": -1, "activeDay": -1} {"type": "TRIANGLE", "weight": 1.0, "maximum": -1, "activeDay": -1} {"type": "SQUARE", "weight": 0.6, "maximum": -1, "activeDay": -1} {"type": "SPECIAL", "weight": 0.2, "maximum": 1, "activeDay": -1} ], "countSpecialsTogether": false, }, [...] ],
Each city area is an dictionary inside of the "cityAreas" array in the city.json file's main dictionary. You can have as many city areas as you like. Try to stick to somewhere between six and nine city areas for a balanced map.

In addition, to avoid bugs in your map, make sure your city areas don't accidentally overlap your obstacles, and especially try to give them a buffer so that stations won't spawn on the edge of obstacles!

Paths and Points
"paths": [ { "points": [ [590.42,663.03], [547.83,501.15], [186.43,501.15], [146.68,656.64], [...] ] }, { "points": [ [579.78,418.79], [558.47,361.28], [589.01,290.28], [673.49,246.26], [...] ] } ],
Like with obstacles, each city area is define by a list of verticies with a float X and Y value in an array of arrays.

However, a city area can be made up of multiple polygons at once. The "paths" array, is an array of dictionaries. Inside each dictionary a separate polygon can be defined in the "points" array, the same way you would for an obstacle. The example above is a city area with two separate polygons, sharing the same characters.

You can use the exact same steps as in the previous section to draw the city areas in SVG format, and then convert into JSON.

When picking out where the city areas should be, draw on your map research and knowledge of the city. Try looking at the official administrative divisions in the city, and finding demographic information about which areas are more built up or spread out, which areas are primarily residential and which are more commerical or industrial, and so on.

In addition, if you've already set up your map zoom, you have an idea of where the boundaries of the screen are at the zoom end. Your city areas should generally fit inside the screen.

It's very important to make sure that none of your city areas overlap with any water obstacles! Otherwise, you may find that stations will start spawning inside the bodies of water in your map, which can lead to visual and gameplay issues with how crossings are used.

Try and give at least 40 pixels of space between the edge of your water obstacles and the edge of your city areas in the SVG, otherwise, stations may appear to be partially into the obstacles.

Debugging

While working on your map, you may want to check where your city areas are, to get a sense of whether your zoom values are correct, or if any city areas are colliding with obstacles.

"debugShowCityAreas": true,
You can add this value to your main city.json dictionary, to render the city areas as non-gameplay-affecting obstacles in the map. Make sure to set the value to false, before you publish your map!
City Area Dictionaries
Once you setup your city area paths, they can be configured as follows.

Density
Stations will only spawn within city areas during gameplay. The remaining options in a city area dictionary will define how and when they spawn.

"density": 1.0,
The "density" float value configures how close to each other stations are allowed to spawn in this part of the map. A higher number means stations can spawn closer together, and a lower number means stations have to spawn further apart.

The default value is 1.0, and most maps don’t have any city areas that deviate too far from this value. For instance, in London the downtown city areas are at about 1.2, and the furthest-out regions aren’t lower than 0.8.Try and keep density values within that range. Of course, don’t let that stop you from experimenting!

(In the main dictionary of city.json, not the city area:) “stationSeparationScale”: 1.0,
In addition, in the main dictionary of city.json, you can modify the global station spawn density by changing the "stationSeparationScale" float value. Likewise, 1.0 is the default value here, with higher values meaning closer stations in all city areas.

Station Spawns
"stationSpawns": [ {"type": "CIRCLE", "weight": 0.4, "maximum": -1, "activeDay": -1} {"type": "TRIANGLE", "weight": 1.0, "maximum": -1, "activeDay": -1} {"type": "SQUARE", "weight": 0.6, "maximum": -1, "activeDay": -1} {"type": "SPECIAL", "weight": 0.2, "maximum": 2, "activeDay": 22} ],
Finally, "stationSpawns" is an array of dictionaries inside each city area. Each dictionaries sets the spawning options for a specific type of station.

"type": "CIRCLE",
The "type" value defines a new station type that will be allowed to spawn in this city area. The allowed types are "CIRCLE", "TRIANGLE", "SQUARE", and "SPECIAL", as well as the specific Special station types, listed in the Station Spawns section below.

Keep in mind that if there is no dictionary for a type of station in the "stationSpawns" array, that type of station will not spawn inside the city area.

"maximum": 2,
The integer "maximum" value sets the maximum number of this type of station that can spawn in the city area. If this is set to -1, there will be no limit on this type.

Note that for "SPECIAL" stations, by default, this will apply a maximum for each type of special station. With this example, up to two Star stations could appear in the area, along with two Pentagons, and two Diamonds, and so on.

"countSpecialsTogether": true,
Alternatively, if you want to set a limit for the number of total "SPECIAL" stations in a city area, you can set the value of "countSpecialsTogether" to true. In this case, if there was one Star staiton and one Pentagon station in the above city area, no more Special stations would spawn.

If you want to limit the number of specific types of special stations, you can use the individual special station types as your "type" value. For instance...

"stationSpawns": [ {"type": "PENTAGON", "weight": 0.4, "maximum": -1, "activeDay": -1} {"type": "STAR", "weight": 1.0, "maximum": 1, "activeDay": -1} ],
This example would allow just one Star station to spawn, and an unlimited number of Pentagon stations to spawn in the city area. However, no other special stations would be permitted, unless they were also included in the array.

A full list of the Special station types can be found in the next section on Station Spawns.

"activeDay": 22
Each dictionary also configures the first in-game day that this station type will spawn, using the "activeDay" integer value. The start of the first Monday of the game is day 1, Monday after the first upgrade screen is day 8, Monday after the second upgrade screen is 15, and so on.

"weight": 0.6,
The weight value determines the likelihood that a station spawns in this area.

When a station of a particular type spawns in the map, it picks a city area to spawn in with a preference towards areas with higher weight for that station type in the city area's Station Spawns array.

This value doesn't increase the likelihood or order in which stations spawn overall. That value is set by your Schedules. See below.

Unlike with density values, where you generally want to stick in a safe range, it’s actually better to have widely variable weight values. For an area with two station types, try giving one type 1.0 and the other 0.4. If there’s three, try giving one type 1.0, another 0.6, and the last 0.2 or 0.1.

Having big ratios differences help make each area feel unique and make gameplay consistent. If the values are too close to each other, station spawning may feel more random and your map will feel muddy or unpredictable.

Frustrating as it is to have all the circles spawn on one side of the river, it will make your map more planned out for your player and create more interesting, predictable and solvable puzzles.
Station Spawns
The Stations Spawns dictionary is the backbone of a map’s progression, and give you the control of the balance of the map. Schedules and other features here let you configure the order and rate that stations spawn.

"stationSpawns": { "schedule": [ { "numDays": 6, "randomness": 0.35, "types": [ "CIRCLE", "CIRCLE", "CIRCLE", "TRIANGLE", ], }, { "numDays": 11, "randomness": 0.0, "types": [ "CIRCLE", "CIRCLE", "CIRCLE", "TRIANGLE", "TRIANGLE", "TRIANGLE", "TRIANGLE", "TRIANGLE", "SQUARE", "SPECIAL", ], }, ], "cooldown": 0, "special": ["PENTAGON", "DIAMOND", "DIAMOND", "STAR", "CROSS", "WEDGE"], },
The "stationSpawns" dictionary must appear in the main dictionary of city.json.

Schedules
"schedule": [ { "numDays": 6, "randomness": 0.35, "types": [ "CIRCLE", "CIRCLE", "CIRCLE", "TRIANGLE", ], },
The "schedule" array is an array of dictionaries. Schedules are picked by the game to choose which stations to spawn during gameplay.

"types": [ "CIRCLE", "CIRCLE", "CIRCLE", "TRIANGLE", ],
At the start of a game, one schedule will be picked at random. All the stations inside of the randomly-chosen schedule's "types" array will be queued up to spawn.

The valid types are "CIRCLE", "TRIANGLE", "SQUARE", and "SPECIAL".

"numDays": 6,
The stations will spawn over the course of the integer value of days, set by "numDays" in the schedule.

"randomness": 0.35
The "randomness" float value, which must be set between 0.0 and 1.0, determines how regular or random the timing of the station spawns will be. A value of 0.0 means that the four stations would spawn exactly every 1.5 days in-game. A value of 1.0 means that the four stations would spawn at completely random times.

"cooldown": 0,"
In the main "stationSpawn" dictionary again, the "cooldown" integer value sets how many days the game will wait before picking a new schedule, after all the stations in "types" are spawned.

Keep in mind, as mentioned in the section above on Zoom and Start Areas, that if you spawn too many stations before the screen zooms out it can stop stations of a particular type from spawning ever again. Make sure you set the zoom fast enough to accommodate for the rate of stations spawning!

Special Stations
"special": [ "PENTAGON", "DIAMOND", "DIAMOND", "DIAMOND", "STAR", "CROSS", "WEDGE" ],
"SPECIAL" stations are the extra, unique shapes of stations that can spawn or replace existing stations.

To control exactly which types of unique stations show up, you can modify the "special" array. This is a list of the Special stations that will appear in the game.

Duplicates in this list will allow for multiple stations of that Special type to spawn. In the example above, it would allow three "DIAMOND" stations to spawn, and one of all the other lists types.

The types of Special stations are "CROSS", "DIAMOND", "EGG", "GEM", "PENTAGON", "STAR", and "WEDGE".

Station Replacements

Every time a "WEDGE", "STAR", or "CROSS" station spawns, there's a 50% chance it will replace an existing non-Special station. This is a hard-coded value and can't be modified.

Keep this in mind when planning out which special stations to use, in case you don't want your regular stations to be replaced!

Balance Advice
When picking out the types to use across schedules, Vanilla maps tend to have more CIRCLEs than TRIANGLEs, and more TRIANGLEs than SQUAREs and SPECIALs, which have around the same amount as each other. You can experiment and modify this as you like!

You can also make a map feel more or less random based on how different each possible schedule is. In the example above, the first schedule has no SQUARE or SPECIAL stations, meaning it’s possible that the player may not see one of these stations for over two weeks if that station is randomly selected multiple times in a row.

Keep in mind the overall ratio of CIRCLE-to-TRIANGLE-to-SQUARE-to- SPECIAL, and a ratio of stations-per-day for each schedule. Try and pick a baseline value for each of these, and make two to four schedules varied around these values (with more or fewer stations or days) depending on how random you want the map to feel on each new playthrough.

(In the city.json main dictionary:) "passengerSpawnScale": 1.1
Lastly, if a map is too hard and spawning too many stations, or too easy and spawning too few, the "passengerSpawnScale" float value in the main city.json dictionary can be modified. 1.0 is the baseline, and higher numbers will cause more peeps to spawn, and vice versa.

Playtest often and tweak these values to make your map a fun experience to play!
Initial Upgrades
The "upgrades" dictionary in your city.json file's main dictionary will define the lines, trains, and other upgrades that the player has available at the start of the game, and what they can gain throughout.

"upgrades": { "initial": [ "Line", "Line", "Line", "Locomotive", "Locomotive", "Tram", "Shinkansen", "Crossing", "Crossing", "Crossing", "Carriage", "Interchange", "Interchange" ], [...] },
Inside of the "upgrades" dictionary, the "initial" array defines the upgrades that will be given to the player when starting a new game on your map.

“Line” unlocks a new line for the player. Every vanilla map gives the player
three Lines to begin.

“Locomotive”, “Tram”, and “Shinkansen” unlock new trains for the player. Locomotives are the default train type, Trams are the slower trains in maps such as Melbourne, and Shinkansen are the faster trains in maps like Osaka.

Make sure the player has at least one train per starting line. You can give the player more, but if you give them less then they won’t be able to use all the lines you’ve provided at the start of the game.

“Crossing” gives the player a new Bridge or Tunnel, depending on the value you set for "crossingStyle" above. Vanilla maps tend to start you out with two to four crossings.

"Carriage" and "Interchange" respectively unlock a carriage or interchange upgrade for the player, to apply to trains or stations to increase capacity.

No vanilla map gives the player any of these upgrades in the initial setup, but giving the player unique initial upgrades can create interesting puzzles and make your map stand out.

(In the city.json main dictionary, not the upgrades dictionary:) “stationCapacity”: 6, “interchangeCapacity”: 18,
By default, an Interchange upgrade increases the station capacity from six peeps to eighteen. However, both these values can be modified through the "stationCapacity" and "interchangeCapacity" integer values in the city.json file main dictionary.
Weekly Upgrades
“upgrades”: { [...], “unlocks”: [ [ {“type”: ”Locomotive”, ”max”: 0, ”count”: 1, ”weight”: 1.0, ”week”: 0, “maxWeek”: 4}, {“type”: ”Locomotive”, ”max”: 0, ”count”: 2, ”weight”: 1.0, ”week”: 5}, {“type”: ”Tram”, ”max”: 0, ”count”: 3, ”weight”: 1.0, ”week”: 5}, ], [ {“type”: ”Line”, ”max”: 4, ”count”: 1, ”weight”: 0.5, ”week”: 0}, {“type”: ”Crossing”, ”max”: 0, ”count”: 2, ”weight”: 1.0, ”week”: 0}, {“type”: ”Carriage”, ”max”: 0, ”count”: 1, ”weight”: 1.0, ”week”: 0}, {“type”: ”Interchange”, ”max”: 0, ”count”: 1, ”weight””: 0.5, ”week”: 0}, ], ], “numOptions”: [2,3], },
Once you pick out which upgrades the player will start with, next you’ll have to configure which upgrades they can get at the start of each new week. This is done through the "unlocks" array of arrays inside the "upgrades" dictionary.

Each array of dictionaries, inside the "unlocks" array, is a new round of upgrades. At the start of each new week in-game, the player will get a choice of one upgrade from each round.

All vanilla maps have a default two upgrade rounds, where the first gives a train and the second gives another upgrade. However, you can add more or less arrays inside "unlocks" to give the player as many rounds of upgrade choices as you like, and pick any type of upgrade for each screen.

The example above shows a map where two upgrade rounds are shown every week. The first will show a choice of two upgrades, either a Locomotive or Tram. The second screen will show a choice between three upgrades, which could be a Line, extra Crossings, or a bonus Carriage or Interchange.

You must include at least one round of upgrades.

“numOptions”: [2,3],
By default, in each round, the game will randomly select two choices out of the upgrades in the round to be available.

"numOptions" is an array of integers that sets the maximum number of choices available in each round. Here, you can give more or fewer choices in each upgrade round.

In this example, it tells the game to try and show a choice of three upgrades in the second round, if possible.

Upgrade Choice Options
{ “type”: ”Locomotive”, ”max”: 0, ”count”: 2, ”weight”: 1.0, ”week”: 0, “maxWeek”: 4, },
Each dictionary with a round array sets the options for how and when each upgrade type can appear, and what the award will be.

“type”: ”Locomotive”,
The "type" value, of course, sets the type of upgrade that will be shown.

”count”: 2,
Count determines how many of an upgrade is given to the player when they take this upgrade option. In this example, the player would get two new trains.

”max”: 0,
Max configures how many of this type of upgrade the player is allowed to have. If they already have at least that many of the upgrade, this option will not be shown. A setting of 0 or -1 means that there is no limit on the upgrade.

You typically only need to set this for Line upgrades. When you setup your Line upgrades, it’s important to make sure the player can’t get more new Lines than are allowed by the "lineCount" value in city.json.

”weight”: 1.0,
Weight, similar to the setting for station spawns, determines the likelihood that an option will be chosen, relative to all other upgrades in a given upgrade round.

”week”: 0,
“maxWeek”: 4,
Week and maxWeek configure the first and last week that an upgrade option is allowed to appear. MaxWeek will default to infinity — as in, the upgrades will always be available — if the key isn’t present, or set to -1.

Note that, week 1 is the first week of the game, but upgrade screens count for the upcoming set of seven days. The first upgrade screen occurs for week 2, so a “week”: 2 setting is identical to “week”: 0 for all intents and purposes. "week": 3, refers to the second upgrade screen, and so on.

Balance Advice
It’s important to not go too overboard with your upgrade system, or else you risk making your map too easy, or too dependent on getting lucky upgrades.

For most weekly upgrades in most vanilla maps, you’ll get one new train in the first round, and one other upgrade.

The major exceptions are with Crossings, where the player often will get two at once, and maps like Osaka where the player has the choice between one faster or two slower trains.

The important thing, as always, is to know what you want your map to play like, and pick upgrades that play to its strengths. And of course, playtest lots!

Zen Mode
Be warned, when a map is played in Zen mode, it will only ever offer one upgrade, which the game decides on its own, out of the first obstacle dictionary you post.

If you're intending for people to play in Zen mode, just be vary of this limitation! Because of this, it's best to offer a Locomotive, or a choice between two Trains, or another consistent upgrade in the first dictionary.
Weekly Upgrade Names
If you want the name of certain upgrades to appear differently on the weekly upgrades screen, you can add the "assetName"s array to your city.json file's main dictionary.

“assetNames”: [ { “asset”: ”Locomotive”, ”title”: “Subway Car”, ”description”: “Runs on lines 4 and up.”, ”forceDescription”: true, }, { “asset”: ”Tram”, ”title”: “Streetcar”, ”description”: “Runs on lines 1 to 3.”, ”forceDescription”: true, }, ],
Each entry in the "assetNames" array is a dictionary with a unique type, which will override the name and description of one upgrade. Each type of asset can only be overriden once.

“asset”: ”Locomotive”,
Within each dictionary, "asset" picks which upgrade type this will apply to.

”title”: “Subway Car”,
Title sets the text that will appear as the asset’s name on the upgrade screen. In this case, it will make “Subway Car” show up instead of “Locomotive” on the weekly upgrade screen.

”description”: “Runs on lines 4 and up.”,
Description sets a description that will appear underneath the upgrade. It’s helpful to include this, especially if your train types are restricted to certain lines. See below, in the Lines section.

”forceDescription”: true,
To make sure this description always appears, set forceDescription to true.
Trains
“trainDefinition”: “default”,
In your city.json file main dictionary, the "trainDefinition" value lets you change which types of trains to use.

The value "default" will use the regular types of Locomotives, Trams, and Shinkansen trains. The value "small" will use the 4-peep capacity trains from Cairo.

Custom Trains
You may want to modify the train settings to have certain types of trains carry more or less passengers, drive or accelerate faster, and so on. You can do this by creating a new trains.json file in your city folder.

{ “id”: “VLTCarioca”, “capacity”: 6, “locomotives”: [ { “type”: “Locomotive”, “speed”: 360, “acceleration”: 120, “deceleration”: 300, “capacity”: 6, }, { “type”: “Shinkansen”, “speed”: 480, “acceleration”: 180, “deceleration”: 300, “capacity”: 8, }, { “type”: “Tram”, “speed”: 250, “acceleration”: 1000, “deceleration”: 1000, “capacity”: 4, } ] }
The trains.json file should contain this JSON format.

“id”: “VLTCarioca”,
The "id" value needs to be a unique identifier for your train definition file.

Make sure that you copy this id into your city.json “trainDefinition” value.

“capacity”: 6,
The first "capacity" integer value sets the default capacity for each train. This refers to how many peeps can ride in a train, or connected carriage, at one time.

In vanilla maps, this value is 6 by default, and 4 in Cairo.

The "locomotives" array contains three dictionaries, one for each type of train: Locomotive, Shinkansen, and Tram. This lets you configure custom values for each train type.

“type”: “Tram”,
The string "type" sets the train type. Valid types are "Locomotive", "Shinkansen", and "Tram". There should be exactly one dictionary of each type in a trains.json file, with no duplicates or missing types, even if your map won't give the player that train type.

“speed”: 360, “acceleration”: 120, “deceleration”: 300,
The "speed" number value sets the maximum possible speed for the train, and "acceleration" and "deceleration" change how fast the train will reach that top speed.

“capacity”: 4,
By setting the "capacity" integer value in a locomotive definition, you can override the default "capacity" at the top of the trains.json file, to ses how many peepscan fit in this type of train or in an attached carriage.

If a capacity value isn’t given in the unique train dictionary, it defaults to the capacity value given in the overall dictionary.

In vanilla maps, this value is 6 by default, and 4 in Cairo.
Initial Stations
The "stations" array in the city.json main dictionary gives the map a list of stations to spawn at the start of the game. In a vanilla map, where you start with three randomly-placed stations, one each of a Circle, Triangle, and Square, you would use the following JSON.

“stations”: [ { “tag”: “start-circle”, “type”: “circle”, }, { “tag”: “start-triangle”, “type”: “triangle”, }, { “tag”: “start-square”, “type”: “square”, }, ],
Each dictionary in the array corresponds to a unique station.

“tag”: “start-circle”,
Every station must have a totally unique "tag" string value.

“type”: “circle”,
Furthermore, every station needs a type. Valid types are "circle", "triangle", "square", and the Special types "pentagon", "diamond", "star", "cross", "egg", "gem", and "wedge". The exact type of Special station must be specified, and "special" alone is not valid.

Extra Station Values
Without any other information that shown above, the game will try to place each dictionary as a new station, in a valid city area at the start of the game. Extra details can be added to make stations spawn in specific ways. These are especially useful if you want to create Permanent Lines, or just have unique kinds of starting stations that your player will have to account for while building their map.

{ “tag”: “Pavuna-L2SV”, “type”: “square”, “position”: [-2285, -217.7], “interchange”: true, “invincible”: true, “ghost”: false, },
This JSON dictionary shows the full possible list of values for a single station.

“position”: [-2285, -217.7],
The "position" array contains two floats, corresponding to the X and Y position of the station on the map. If "position" is not present, a random position inside a valid city area will be chosen.

If you plan to have fixed station positions, it can be helpful to add them as a shape or point in your SVG map file, and copy the position from there, so you know it won't be inside an obstacle. Spawning city areas inside water can lead to strange visuals and gameplay issues.

“interchange”: true,
Setting "interchange" to true means that it will start the game with an interchange applied. This value defaults to false if not present.

“invincible”: true,
Setting "invincible" to true overrides normal passenger limits, and forces the station to never overflow no matter how many people pile up. This value defaults to false if not present.

This is useful to set to true if you plan to have stations off-screen connected to a permanent line, which trains will bring into the map. This way, stations off-screen will not cause a Game Over.

“ghost”: false,
Setting "ghost" to true means that the station won’t be visible, passengers won’t spawn there, and the player can’t connect lines to it during gameplay. This value defaults to false if not present.

Ghost should only be set to true when you need to connect permanent lines through invisible stations. This can be used to create more complex curves in permanent lines.
Lines
For every line in your city, you need to define some settings about how it will interact with upgrades, and other gameplay elements.

“lines”: [ { “maxTrains”: 8, “allowedTrains”: [“Tram”, "Carriage”, “Crossing”,], }, { “maxTrains”: 8, “allowedTrains”: [“Tram”, "Carriage”, “Crossing”,], }, { “maxTrains”: 8, “allowedTrains”: [“Tram”, "Carriage”, “Crossing”,], }, { “maxTrains”: 6, “allowedTrains”: [“Locomotive”, “Carriage”, “Crossing”,], }, [...] ],
The "lines" array is an array of dictionaries which is in your city.json file main dictionary.

You need to have a dictionary in this array for each line in the level. Therefore, the array should have as many dictionaries as your previously-set "lineCount" value. Your first line corresponds to the first dictionary in the array, and so on.

The line colours will correspond to the RGB values you set in your theme.json "lineColors" array.

{ “maxTrains”: 6, “allowedTrains”: [“Locomotive”, “Carriage”, “Crossing”,], },
Each line dictionary must have these two values.

The "maxTrains" value is an integer that sets the maximum number of total trains — Trams, Locomotives, and Shinkansen counted together — allowed on this line.

The default setting in the vanilla game, or if this value is not present, is 11 minus the total number of lines in the city. So in a 7-line city, you’d allow 4 trains per line, or 6 trains per line in a 5-line city. If you have 8 or more lines, make sure you set this higher, so that the player won't run into restrictions with the trains allowed per line.

“allowedTrains”: [ “Locomotive”, “Carriage”, “Crossing”, ],
The "allowedTrains" array is the other required value. This lets you configure which types of upgrades can be used on each line.

By default, you should allow all five possible line upgrades: "Locomotive", "Tram", "Shinkansen", "Carriage", and "Crossing". If the array is missing, all assets will be allowed on the line by default.

The example at the top of this section is from the Mini Metro More - Toronto map, and shows a case where the initial three lines are only allowed to run Trams, and the rest of the lines can only run Locomotives, by deleting the other types of trains in the "allowedTrains" array. All lines in the example are allowed to use Carriages and Crossings.

If "Carriage" is not present, trains on this line will not be able to add Carriages.

If "Crossing is not present, the line will not be allowed to cross water obstacles.

If no type of train is present, trains will not be able to be placed on the line at all. Make sure you include at least one of "Locomotive", "Tram" or "Shinkansen".

There is no way to disallow Interchanges from being used on stations on this line.

If you do opt to disallow certain trains in your map, it’s very important that you clearly communicate this to the player! Try adding a note in the map description, use a recognizable set of line colours in the theme for Tram lines, and use custom upgrade names or descriptions for the upgrade screen via the "assetNames" array.
Permanent Lines
Permanent Lines are a new feature for Community Maps. Lines in your map can be configured to appear at the start of the game and be connected to your initial stations. Trains can be setup to run on these lines from the start. Permanent lines can be configured to allow or disallow editing the line, and separately allow or disallow editing trains on the line.

This might be commuter rail lines that connect to stations off the map, which will bring passengers into the city. A permanent line could also be used to add a fixed downtown line or loop that the player has to build around or on top of. Or, they can just give your map some extra flair! You should experiment with this new mechanic.

“lines”: [ { “maxTrains”: 4, “allowedTrains”: [“Locomotive”, “Carriage”], “links”: [ { “station”: “Uruguai-L1”, “stops”: [ {“direction”: “EAST”, “platform”: 0}, ], }, { “station”: “Estacio-L1”, “stops”: [ {“direction”: “SOUTHWEST”, “platform”: 0}, {“direction”: “NORTHEAST”, “platform”: 0}, ], }, { “station”: “Central-L1L2”, “stops”: [ {“direction”: “WEST”, “platform”: 0}, {“direction”: “EAST”, “platform”: 0}, ], }, [...] { “station”: “Botafogo-L1L2”, “stops”: [ {“direction”: “NORTHEAST”, “platform”: 0}, ], }, ], “startAssets”: [ “Locomotive”, “Locomotive”, “Carriage”, “Carriage”, ], “canEditTrains”: false, “loop”: false, }, [...]
To set up a permanent line, you must have "links" and "startAssets" in the corresponding line dictionary.

Links - Drawing the Permanent Line
“links”: [ { “station”: “Uruguai-L1”, “stops”: [ {“direction”: “EAST”, “platform”: 0}, ], }, { “station”: “Estacio-L1”, “stops”: [ {“direction”: “SOUTHWEST”, “platform”: 0}, {“direction”: “NORTHEAST”, “platform”: 0}, ], }, [...] { “station”: “Botafogo-L1L2”, “stops”: [ {“direction”: “NORTHEAST”, “platform”: 0}, ], }, ],
The links value contains an array of dictionaries, with each corresponding to a station that the line will link to, in order from first to last. A permanent line must have at least two links.

“station”: “Uruguai-L1”,
The "station" string must refers to the unique station "tag" that you configured while creating the "stations" dictionary.

Make sure that you only connect to stations where you have set a "position" array, and that the value here matches the station tag exactly.

“stops”: [ {“direction”: “SOUTHWEST”, “platform”: 0}, {“direction”: “NORTHEAST”, “platform”: 0}, ],
Within each link, the "stops" array of dictionaries describes how the line will enter and exit the station.

The first and last links in a permanent line must have one stop. All other links in-between must have two stops.

Each stop dictionary contains a "direction value". The first dictionary's "direction" will determine which way the line will enter the station from, and the second determines the direction that the line leaves the station from.

Valid "direction" values are "NORTH", SOUTH, EAST, WEST, NORTHWEST, NORTHEAST, SOUTHWEST, and SOUTHEAST.

Make sure you don't have more than three lines going in or out of a station in one direction!

“platform”: 0,
The platform value is an integer that determines the offset of the line, left or right, as it enters and exits the station. You should set this to 0, 1, or -1.

When a line goes out the opposite direction it comes in from, e.g. in from "WEST" and out from "EAST", you should set both stops to platform 0, or one to -1 and the other to 1, so that the line looks straight.

You’ll only need to change this if you have multiple lines coming into or out of the station in the same direction. Two permanent lines should not enter or exit from the same "direction" with the same "platform" value.

Once again, make sure you don't have more than three lines going in or out of a station in one direction! There's only three platform values: 0, -1, and 1, and you shouldn't try to use more than this.

Start Assets - Running Trains on Permanent Line
“startAssets”: [ “Locomotive”, “Locomotive”, “Carriage”, “Carriage”, ],
The "startAssets" array provides the line with a list of Train and Carriage assets to setup on the line at the start of the game. You can include "Locomotive", "Tram", "Shinkansen", and "Carriage" assets in this array.

Listing all of the Train assets before listing Carriages works best to ensure the carriages are evenly distributed among trains.

Assets in this array will not be deduced from your initial upgrades list, nor will any required "Crossing" assets if the line goes over a water obstacle. The game will add as many as needed, automatically.

Be aware that assets added will count towards the maximum number of assets, when set for a weekly upgrade option, or for the maximum number of trains on a line, and so on.

Other Permanent Line Values
“canEdit”: false,
The "canEdit" boolean value configures whether this line can be moved by the player after it's initially generated. If this value is not present, it will default to false.

“canEditTrains”: false,
The "canEditTrains" boolean value configures whether trains on this line can be picked up and moved, and if new lines from the player’s supply can be added to the line. If this value is not present, it will default to false.

“loop”: false,
If the "loop" boolean is set to true, trains on the line will loop instead of moving back and forth between the two terminal stations.

For a loop, make sure that the first and last link are connected to the same station tag. Otherwise, the trains will appear to teleport from one terminal to the other.
Transitions
It’s important to include a background obstacle in your city, since this will let the background map colour show up correctly during the transition from the menu to your level. If you leave out a background, you’ll notice that as the gameplay begins, your background colour will very suddenly
pop in.

While you draw your map, think about the direction the camera is going to follow to get into your map. In your SVG Editor, try to match the background object to any water at the edge of the map, so that the camera can pass from the menu, over water, then onto your map's background.

Camera Transitions
In addition, the "transitions" array in your city.json main dictionary lets you confirm how the camera will move to enter your map.

“transitions”: [ { “points”: [ [-10000, 8000], [0, 0], ], “id”: “menu-game”, }, { “points”: [ [-10000, 8000], [0, 0], ], “id”: “menu-pause”, }, ],

The "id" value configures which camera path you're setting, with each dictionary in the array. You should only need "menu-game", which sets the path from the Main Menu into the map, and "menu-pause", which sets the path from your Pause Menu in-game back to the Main Menu.

You can modify the path that the camera take into the map by editing the "points" array.

“points”: [ [-10000, 8000], [0, 0], ],
The "points" array defines a path that the camera will follow during the transition into or out of the game. Each array inside the array needs an X and Y float value.

Your transitions should always end at [0,0], and needs at least two points.

You can draw your path as a series of points in your SVG Editor. As said before, it's best to try and have it move over any major water obstacles, like lakes or oceans, to swoop into your city.
Previews
The "preview" dictionary inside your city.json file's main dictionary lets you define how the map icon will appear in the Play menu, before starting a new game.

“preview”: { “window”: { “position”: [-250, -175], “size”: 800 }, "hidePermanentLines": true, },
For a basic preview, you must have a "window" dictionary. This will pick out a square area of the map obstacles to show as a preview. Make sure that "inPreview" is set to true in the obstacle dictionary, for any obstacles that you want to appear in the preview window.

Inside the "window" dictionary, the "position" array configures the [X, Y] coordinates for the bottom-left corner of the preview window.

The "size" value sets how large the window will be on each side of the square.

It's helpful to create a square in your SVG editor for where you want the preview to be, and play around with the size in that tool to make sure you include all the obstacles you want to see.

"hidePermanentLines": true,
The boolean value "hidePermanentLines" can be set to true to hide the colours of any permanent lines in the row of circles corresponding to the line colours, which appear below the preview window.

Adding Stations and Lines
To draw stations and lines inside the preview window, you can add these inside of the "preview" dictionary, the same way you would define stations and lines in the map for a permanent line.

"preview": { "window": { "position": [-400,-450], "size": [1000,1000] }, "stations": [ { "ghost": false, "position": [15.0, 80.0], "tag": "triangle.br", "type": "square", }, { "ghost": false, "position": [-185.0,80.0], "tag": "triangle.rg", "type": "triangle", }, { "ghost": false, "position": [-85.0, -100.0], "tag": "triangle.bg", "type": "triangle", }, { "ghost": false, "position": [-350.0, 150.0], "tag": "red1", "type": "circle", }, { "ghost": true, "position": [410.0, 0.0], "tag": "red2", "type": "circle", }, { "ghost": false, "position": [525.0,100.0], "tag": "red3", "type": "circle", }, { "ghost": false, "position": [-250.0, 282.0], "tag": "green1", "type": "circle", }, { "ghost": false, "position": [70.0, -360.0], "tag": "green2", "type": "circle", }, { "ghost": false, "position": [15.0, 474.0], "tag": "blue1", "type": "circle", }, {"ghost": false, "position": [-250.0, -360.0], "tag": "blue2", "type": "circle", }, ], "lines": [ { "index": 0, "links": [ { "station": "red1", "stops": [] }, { "station": "triangle.rg", "stops": [ { "direction": "WEST", "platform": 0 }, { "direction": "EAST", "platform": 0 } ] }, { "station": "triangle.br", "stops": [ { "direction": "WEST", "platform": 0 }, { "direction": "SOUTHEAST", "platform": 0 } ] }, { "station": "red2", "stops": [ { "direction": "WEST", "platform": 0 }, { "direction": "EAST", "platform": 0 } ] }, { "station": "red3", "stops": [] } ] }, { "index": 1, "links": [ { "station": "blue1", "stops": [] }, { "station": "triangle.br", "stops": [ { "direction": "NORTH", "platform": 0 }, { "direction": "SOUTH", "platform": 0 } ] }, { "station": "triangle.bg", "stops": [ { "direction": "NORTHEAST", "platform": 0 }, { "direction": "SOUTHWEST", "platform": 0 } ] }, { "station": "blue2", "stops": [] } ] }, { "index": 1, "links": [ { "station": "green1", "stops": [] }, { "station": "triangle.rg", "stops": [ { "direction": "NORTH", "platform": 0 }, { "direction": "SOUTH", "platform": 0 } ] }, { "station": "triangle.bg", "stops": [ { "direction": "NORTHWEST", "platform": 0 }, { "direction": "SOUTHEAST", "platform": 0 } ] }, { "station": "green2", "stops": [] } ] }, ], "hidePermanentLines": true, },
The example above is from the Mini Metro More - Kyiv map, and shows how to setup preview stations and preview lines.

Stations and Lines in the "preview" dictionary will only appear in the preview, and don't affect gameplay otherwise.

Stations all must have a unique "tag" string, "position" array with an X and Y value, and a "type". The "ghost" boolean can be set to true to hide stations from being drawn, but still connect them to a line to create more complex line curvature.

Each line needs a "links" array, as if it was a Permanent Line. Refer to the Permanent Line section on how to setup the Links array. The link "station" values in preview lines can only refer to the tag of preview stations, and not stations in the general gameplay "stations" array.

Unique to Preview Lines, you can include an "index" value on each line, to denote which line colour should be used. The first line colour in your theme.json Line Colours will be index 0, the second will be index 1, and so on.

Troubleshooting
If an obstacle isn't appear correctly in the preview window and does not fill, you can try adding "fixPreview": true, to the obstacle dictionary that is improperly rendering.

If this doesn't fix the issue, you can try making custom preview obstacles. Set "inPreview" to false for your other gameplay obstacles. In your SVG editor, and add a box where you want the preview to appear. Then, copy or trace over your regular obstacles with new shapes, without adding any points outside of the preview box. You can then import these as obstacle dictionaries with the settings "decoration": true, and "inPreview": true, underneath your other "inPreview": false gameplay obstacles.

You could also draw custom preview obstacles in your preview window this way.
Achievements
You can setup custom challenges by adding an achievements.json file into your city folder. This file is a dictionary formatted like the JSON below.
{ “achievements”: [ { “id”: “casablanca-1”, “city”: “casablanca”, “difficultyPriority”: 0, “type”: “Score”, “score”: 500, “customDescription”: “Score 500 points with two lines on Casablanca.”, “icon”: “passenger”, “restrictions”: [ {“max”: 2, “type”: “Line”, “scope”: “Global”} ], }, [...] ], }
The "achievements" array contains a list of unique dictionaries. Each dictionary will correspond to one challenge in your map.

“id”: “casablanca-1”,
Each achievment must have a unique "id" string.

“city”: “casablanca”,
The "city" string must correspond to the city "id" in your city.json file.

“difficultyPriority”: 0,
If you’re making multiple achievements for the map, you can set the "difficultyPriority" integer value to order of your challenges from lowest to highest, in the Achievements screen.

Achievements have to have one of two possible type values: Score or Day.

“type”: “Score”, “score”: 500,
If the "type" is Score, then the "score" value will set the number of passengers that the player has to move to complete the challenge.

“type”: “Day”, “day”: 35,
If the "type" is Day, then the "score" or "day" value will set the number of days that the player has to pass without having a station overflow, to complete the challenge.

“customDescription”: “Score 500 points with two lines on Casablanca.”, “icon”: “passenger”,
The string value "customDescription" lets you describe the challenge for the map in your own words, and this will appear on the Achivement screen for the map.

The "icon" string value will pick which icon to use for the map. You can choose various icons, which are listed in the Achievement Icons appendix.

The icon is strictly cosmetic, so pick whatever suits the challenge best!

Restrictions
“restrictions”: [ { “max”: 2, “type”: “Line”, “scope”: “Global”, }, { “min”: 1, “type”: “Loop”, “scope”: “Line”, }, ],
Finally, the "restrictions" array of dictionaries let you add extra rules that the player needs to follow to beat the challenge. Each dictionary adds a restriction, and you can include no restrictions at all, or as many as you like.

“max”: 2, “type”: “Line”,
Restrictions have a "type", and a "max" or "min" value, or both.

Together, these effectively say, “don’t have more than the max number, or less than the min number, of this type of thing.” The example above says, “don’t use more than two lines”.

Valid types are as follows:
  • "Line": Counts the lines actively used, but not ones unused in the toolbar, including any permanent lines.
  • "Locomotive": Counts the Locomotives, Trams, and Shinkansen actively used, but not ones unused in the toolbar, including on any permanent lines.
  • "Carriage": Counts the Carriages actively used, but not ones unused in the toolbar, including on any permanent lines.
  • "Crossing": Counts the number of Tunnels or Bridges used, as in, the number of times lines have to cross a water obstacle, include permanent lines.
  • "Loop": Counts the number of loops. Each line that is looping counts as one loop, including permanent lines.
  • "Station": Counts the number of stations connected to a line.
  • "Hub": Counts the number of stations which are linked to all lines, including permanent lines.

“scope”: “Global”,
In addition, the "scope" value sets the context that the restriction applies.

Valid scopes are as follows:
  • "Global": Means anywhere in the map. This works for all types, except for "Station".
  • "Line": Counts the "type" per each line. This lets you set a max or min
    value allowed of the type for each line individually. This can be used with any type except "Line", or "Hub". Note that type "Loop" per scope "Line" with either be 0, is not looping, or 1, if looping. This restriction will apply to permanent lines as well.
  • "Square": Only allowed with type "Line". This lets you set the maximum or minimum number of lines connected to each Square type station. This includes permanent lines.
  • "Crossing": Only allowed with type "Line". This lets you set the maximum number of lines that use any Tunnels or Bridges at all. This includes permanent lines.

Examples
Here are some example achievement restrictions.

{“max”: 6, “type”: “Carriage”, “scope”: “Global”}
“Don’t use more than 6 carriages anywhere”
{“max”: 3, “type”: “Locomotive”, “scope”: “Line”}
“Don’t use more than 3 locomotives on each line.”
{“max”: 1, “type”: “Loop”, “scope”: “Global”}
“Don’t use more than one loop line in total.”
{“min”: 1, “type”: “Hub”, “scope”: “Global”}
“Have at least one station connected to all lines.”
{“min”: 1, “type”: “Loop”, “scope”: “Line”}
“Use at least one loop on each line.”, or, “Make every line a loop”
{“max": 0, “type”: “Loop”, “scope”: “Line”}
“Don't use any loops."
{“max”: 2, “type”: “Line”, “scope”: “Square”}
“Have at most two lines with squares”
{“max”: 1, “type”: “Line”, “scope”: “Crossing”}
“Have only one line with any tunnels”.
{“max”: 2, “type”: “Crossing”, “scope”: “Global”}
“Only use two tunnels in total”.
Steam Upload
Once you're happy with your map, and you've playtested it lots, you can upload cities from
your /mod/ folder to the Steam Workshop.

Maps that are loading from the /mod/ folder have a folder icon underneath their map preview on the Play menu. By pressing this icon, you'll upload or update the map on the Workshop.



Preview Image
Before you upload your map, you must include a preview image in your city folder! This must be titled preview.png, preview.jpg, or preview.gif. This will be uploaded as the preview image for your map in the Steam Workshop pages. The image should be square, and at least 500x500 pixels.

You'll be able to add extra images on your map's page on the Steam Workshop, but your preview file must be uploaded or updated with the map.

Steam JSON
The first time you upload a map, it will generate a steam.json file in the city folder. Here you can configure some information for your upload, the mod name, description, and changelog names, and update these values if you want to re-upload or update the map.

This file should just contain a dictionary, like this:
{ “publishFieldId”:”0123456789”, “itemTitle”: ”Your New Map”, “itemDescription”: ”Your description here!”, “itemTags”: “africa,demo”, “itemChangelog”: “1.1 update, more cool stuff!”, }
The "publishFieldId" value will be auto-generated the first time you upload your map. Make sure not to edit this, since it contains a key referring to your unique Steam Workshop item, and you won’t be able to update your map if you forget it or delete it.

When you press the Upload button again, if this value exists it will let you update the same map. If you've deleted the item on the Steam Workshop that this ID refers to, or changed the ID to something invalid, Mini Metro will try and create a new item.

The string values "itemTitle" and "itemDescription" set the title and description of your Steam
Workshop item. You can also configure these directly on the Steam Workshop interface.

The string value "itemTags" contains a comma-separated list of tags for your map to help people find it in the Workshop. Try including the country or continent where you city is in the world, or any custom mechanics that you made use of.

Finally, "itemChangelog" is a string value which will be added to your item’s changelog if you update the map. You should change this string every time you Update a map.

If your map uploaded successfully, you should be able to find it under "Your Files", on the right-side of the Mini Metro Steam Workshop page. Make sure to go into your map options and set the Visibility to Public, so that everyone can play it!

Troubleshooting
If you run into an error when uploading the map, the details will be printed in your Log file. The location of your Unity Player Log file can be found at the start of this guide.

If you need help uploading a map, make sure you include any error messages you got that include SteamPlatform in the line, in your post! You can search for this string to find the error messages in your Log file.

Sharing Your Maps
If you'd like to share your maps with friends not playing the game on Steam, you can zip up your mod file and distribute it anywhere you like! If they drop that into their Mod Folder, it should show up in just the same way as it does for you.
Leaderboards
Once you upload a map to the Steam Workshop, players who Subscribe to your map will be able to see a leaderboard for it. You can compete with friends and other users for a top score!

You won't see the leaderboard in your local version of the map, and will have to Subscribe to your own creations in the Workshop to check out the leaderboard.

Updates and Resetting Leaderboards

To account for possible changes to the balance of your map, and prevent cheating, the leaderboard will be reset whenever you post an update with changes to either the city.json or trains.json files. Effectively, every version of your map will have it's own leaderboard.

You can make changes to any of the other files - theme.json, achievements.json, steam.json, or audio.json - without causing the leaderboard to be reset.
FAQ
We'll add to this section as questions come up! Open a Discussion thread in the Mini Metro Steam Workshop if you have any questions. Always make sure to check your Unity Player Log for warnings or errors that occur during map loading.

My AppData folder on Windows is hidden.

Open file explorer, and type %appdata% into the menu bar. It should take you directly to the correct folder.
Appendix - Vanilla Themes
If you want to use the theme of an existing vanilla Mini Metro map, you can use the corresponding value in the table below for the "theme" value in the city.json file. Make sure not to include your own theme.json.

Use the value exactly as it appears, with the correct capitalization.

Make sure that the original map would have enough lines as your custom map, otherwise, your custom map may not load.

City
Theme ID Value
Auckland
"Hop"
Barcelona
"Collserola"
Berlin
"BVG"
Cairo
"Giza"
Canberra
"Canberra"
Chicago
"Michigan"
Guangzhou
"Zhujiang"
Hong Kong
"Octopus"
Istanbul
"Metrosu"
Lagos
"Lagoon"
London
"Tube"
London, 1960 Variant
"Tube1960"
Melbourne
"myki"
Montreal
"Opus"
Mumbai
"Thane"
New York City
"Subway"
NYC, 1972 Variant
"Subway1972"
Osaka
"Kansai"
Paris
"Metro"
Paris, 1937 Variant
"Metro1937"
San Francisco
"Clipper"
Santiago
"Mapocho"
Sao Paulo
"Luz"
Seoul
"Han"
Shanghai
"Huangpu"
Singapore
"EZ-Link"
Stockholm
"Malaren"
St Petersburg
"Neva"
Washington DC
"Potomac"
Appendix - Vanilla Audio IDs
If you want to use the audio loadout of an existing vanilla Mini Metro map, you can use the corresponding value in the table below for the "audioLoadoutId" value in the city.json file.

Use the value exactly as it appears, with the correct capitalization.

City
Audio Loadout ID Value
Auckland
"auckland"
Barcelona
"barcelona"
Berlin
"berlin"
Cairo
"cairo"
Canberra
"canberra"
Chicago
"chicago"
Guangzhou and Shanghai
"shanghai"
Hong Kong
"hongkong"
Istanbul
"istanbul"
Lagos
"lagos"
London and Variants
"london"
Melbourne
"melbourne"
Montreal
"montreal"
Mumbai
"mumbai"
New York City and Variants
"nyc"
Osaka
"osaka"
Paris and Variants
"paris"
San Francisco
"sanfrancisco"
Santiago
"santiago"
Sao Paulo
"saopaulo"
Seoul
"seoul"
Singapore
"singapore"
Stockholm
"stockholm"
St Petersburg
"saintpetersburg"
Washington DC
"washingtondc"
Appendix - Custom Audio
If you'd like to try your hand at making your very own audio file for a map, Rich Vreeland has put together a tutorial here [github.com]on how to build the file!

In addition, Rich has uploaded a video explaining the process, which you can watch below.


You can follow the steps in the links above, and include your audio.json file in the same folder with everything else for your mod map.

Using custom audio may cause bugs during gameplay, so be sure to test diligently so that everyone can enjoy your auditory creations!
Appendix - Language Codes
Use these codes for the "customLocalNameLocale" key in the city.json file, to allow the custom city name to use certain non-English characters from the language.

Use the value exactly as it appears, with the correct capitalization.

Language
Value
Arabic
"ar"
Bulgarian
"bg"
Catalan
"ca"
Czech
"cs"
Welsh
"cy"
Danish
"da"
German
"de"
Greek
"el"
European Spanish
"es_ES"
Mexican Spanish
"es_MX"
Finnish
"fi"
French
"fr"
Gaelic
"ga_IE"
Hindi
"hi"
Bosnian
"hr"
Hungarian
"hu"
Indonesian
"id"
Italian
"it"
Japanese
"ja"
Korean
"ko"
Māori
"mi"
Malay
"ms"
Dutch
"nl"
Norwegian, Nynorsk
"nn_NO"
Norwegian, Bokmål
"no"
Polish
"pl"
Brazilian Portuguese
"pt_BR"
European Portuguese
"pt_PT"
Russian
"ru"
Slovak
"sk"
Slovene
"sl"
Serbian, Cyrillic
"sr"
Serbian, Latin
"sr@latin"
Swedish, Finland
"sv_FI"
Swedish, Sweden
"sv_SE"
Tajik
"tg"
Thai
"th"
Turkish
"tr"
Ukrainian
"uk"
Chinese, Simplified
"zh_CN"
Chinese, Traditional, Hong Kong
"zh_HK"
Chinese, Traditional, Taiwanese
"zh_TW"
Appendix - Achievement Icons
This a list of icons you can use for the achievement definition "icon" value. Use the values exactly as written, in lowercase
  • "achievement" - the default achievement icon.
  • "passenger"
  • "tea"
  • "baguette"
  • "hot_dog"
  • "crossing"
  • "bridge"
  • "locomotive"
  • "tram"
  • "shinkansen"
  • "line"
  • "carriage"
  • "interchange"
In a future update, we'll be making extra assets icons available to use for custom challenges.
Appendix - Obstacle Troubleshooting
My shape's coordinates aren't right, they're too small and don't refer to the actual shape.
If the coordinates don't look right, or the numbers aren't large enough, it may be the case that the coordinates are in relative points. You need to convert them to absolute points.

You can try to use a tool such as PathToPoints[alpha.inkscape.org] to fix this. Drop the SVG file there, and find your path, and convert it to an absolute list of points. You'll still need to manually flip the Y axis coordinates.

In Inkscape, you can follow these steps to fix the path, taken from this forum page[alpha.inkscape.org].

  1. Open the preferences for 'SVG Output > Path Data' (or in newer versions 'Edit > Preferences > Input/Output > SVG Output').
  2. Set 'Path String Format' to 'Absolute'.
  3. This will only affect newly created paths, or existing objects for which a rewrite of the path data is triggered. Make sure your shape is visible, and select 'Edit > Select All in All Layers'.
  4. Nudge the selection with the arrow keys one step up and one back down again. This will trigger a rewrite of the path data.
  5. Reopen the SVG file in your text editor. All the letters should appear uppercase now, indicating that they're absolute values.

There are letters in the shape coordinates!
If you find any letters in the definition of the shape, it's likely you've added curvature somewhere. This can cause the shape to appear with relative points to define the curvature from point to point. You can follow the same instructions as for the previous issue and see if the result looks right. If not, you may have to re-draw the shape in your SVG editor.

If the letters are all capital "M"s, though, you can replace the letters with line breaks. Double check that each line has two numbers, corresponding to the X and Y coordinates of the points.

An obstacle looks wrong, it's upside down.
Mini Metro's Y coordinate will be inverted compared to your SVG editor! Make sure to follow the steps to convert the shape into obstacle JSON format carefully. Flip the signs (add a minus where there isn't one, and remove it where there is one), for each of the Y coordinates.