Mike Xiao

Software Engineer & Game Developer

Create Your First Mod in Cities: Skylines

In the recent days I was playing a popular city builder simulation game Cities: Skylines and I spent a lot of time on it. This is a game that not only great by itself, but also has a large community on the steam workshop with hundreds of thousands of mod authors that are keep providing stunning additional custom contents to the game. In this article I will show you how to get started on your first mod if you are thinking of being a mod author to this game.

This tutorial is for PC only, the project hierarchy may be different and you may need alternate developing tools to create mods on Mac or Linux machines.

  • Visual Studio 2015 (and Above) *
  • ModTools by BloodPenguin (For debug purposes)

*You are more than welcome to use other IDEs and text editors

Special Thanks

I would like to take some time to thank boformer and his awesome modding tutorial series, most of the content in this tutorial is originally created by him.

Get Started

Let's get started by creating a simple mod which shows the detailed zone demand (Residential, Commercial, Workplace) by numbers when you pressed a hotkey, it is not something really useful, but enough to get your hands dirty to start.

Project Hierarchy

Generally, in Cities: Skylines, all mods will be placed at %LOCALAPPDATA%\Colossal Order\Cities_Skylines\Addons\Mods with a folder structure like following:

\Mods
└-- ModName
    |-- ModName.dll(Automatically Compiled)
    └-- \source
        └-- ModName.cs(source code)

However, it is not necessary to create structures in advance like this, since we will configure Visual Studios to do it automatically in the later steps.

Code Your First Mod

Now, let's jump into Visual Studio start coding.

Create your first class

Once VS is opened, create a new project, select Visual C# > Class Library (.Net Framework) as the template. Give it a name like "RCWDemand" or any name you like, and choose a location to place your source code (note: the source code does not need to be placed inside \Mod folder as previously listed, we will configure Visual Studio to automatically copy the compiled .dll file to that folder, which will be mentioned later).

Create Project

Add dependencies to the project

Once we created a class, we need to add all of supported Cities: Skylines APIs to our dependency list:

  • ICities (ICities.dll)
  • UnityEngine (UnityEngine.dll)
  • ColossalFramework (ColossalManaged.dll)
  • Assembly-CSharp(Assembly-CSharp.dll)

Let's do it by right click the reference in Solution Manager (The tab on the upper right corner), then select "Add Reference", on the popup menu, use "Browse..." button to select those .dll files, which located at Steam\steamapps\common\Cities_Skylines\Cities_Data\Managed.

Your Reference Manager will looks like this, make sure all of them are checked and click OK to continue

Automated Deployment

To speed through the development process, we will configure Visual Studio to do two things:

  1. Automatically copy the compiled .dll file to the mod directory in Cities: Skylines.
  2. Make the game to auto hot-reload the mod while running after every rebuild.

To auto copy, right click the project node in the solution explorer, and choose "Properties", select "Build Events" in the left panel, then paste the following command in the "Post-build" event:

mkdir "%LOCALAPPDATA%\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)"
del "%LOCALAPPDATA%\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)\$(TargetFileName)"
xcopy /y "$(TargetPath)" "%LOCALAPPDATA%\Colossal Order\Cities_Skylines\Addons\Mods\$(SolutionName)"

Save (Ctrl + S) and close the tab.

Then simply change the bottom two lines in AssemblyInfo.cs to make the game auto hot-reload our mod while running:

[assembly: AssemblyVersion("1.0.*")]
//[assembly: AssemblyFileVersion("1.0.0.0")]

Working on the Mod (Finally)

After finishing all of the above steps, we can start working on the project.
(Note: everytime you start a brand new project, you need to repeat these steps to get it working.)

double click on Class1.cs and replace the code with this:

using UnityEngine;
using ICities;

namespace RCWDemand
{
	public class RCWDemandMod : IUserMod {
		public string Name => "RCW Demand";

		public string Description => "Show actual RCW Demands in numbers.";
	}
}

Here we created a new class called RCWDemandMod and make it extends the base class IUserMod, then implemented the two virtual funtions Name() and Description(), the => symbol is equivalent to get { return ... }.

If you are not really familiar with Object Oriented Programming(OOP) or Polymorphism, you need to study that as soon as possible, but what this does is to let our newly created class to be a mod class so it can be recognized by the game, and give it a name and description.

Before we make any further development, let's see if everything we configured works.

Build the entire solution (Build > Build Solution or CTRL + SHIFT + B), then go to the mods directory of the game (%LOCALAPPDATA%\Colossal Order\Cities_Skylines\Addons\Mods), you should see a folder named as the project you created (RCWDemand in our case) with a compiled .dll file that has the same name inside.

Now start the game, in your content mamager, you should see the mod appears in the "Mods" tab, enable it if it's not, although it doesn't have any functionality yet.

You should see the mod appears in the game (My game uses Simplified Chinese instead of English)

Add Functionalities to the Mod

Let's finish up this mod, now it's time to test your auto hot-reloading! Enter a savegame or create a brand new scene in the game, then leave it for right now and back to Visual Studio.

We will use threading hook to check if the player is pressing the hotkeys (let's use CTRL + D in this project), the IThreadingExtension/ThreadingExtensionBase is a hook provided by the official modding API. Classes implementing them are invoked before, during and after every simulation tick. Since it will be executed many times per second in game, make sure your implementation does not contain jobs that are heavy-workloaded (like I/O Operations or instantiating new game objects), or it may slow down the game.

Create a new class called ShowDemand below Name() and Description():

public class ShowDemand : ThreadingExtensionBase {

		private bool _processed = false;
		public override void OnUpdate(float realTimeDelta, float simulationTimeDelta) {
			Debug.Log(_processed);

			if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKey(KeyCode.D)) {
				if (_processed)
					return;

				_processed = true;
				int rDemand = ZoneManager.instance.m_actualResidentialDemand;
				int cDemand = ZoneManager.instance.m_actualCommercialDemand;
				int wDemand = ZoneManager.instance.m_actualWorkplaceDemand;

				string message = $"Residential: {rDemand}\nCommercial: {cDemand}\nWorkplace: {wDemand}";

				ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");
				panel.SetMessage("Detailed RCW Demand", message, false);
			}

			else {
				_processed = false;
			}
		}
	}

Make sure to include the UI namespace at the top:

using ColossalFramework.UI; 

You can also create the class in the separate folder, I would recommend doing this if our project gets larger, but we will keep this for right now.

Now, your completed code should looks like this:

using UnityEngine;
using ICities;
using ColossalFramework.UI;

namespace RCWDemand
{
	public class RCWDemandMod : IUserMod {
		public string Name => "RCW Demand";

		public string Description => "Show actual RCW Demands in numbers.";
	}

	public class ShowDemand : ThreadingExtensionBase {

		private bool _processed = false;
		public override void OnUpdate(float realTimeDelta, float simulationTimeDelta) {
			Debug.Log(_processed);

			if ((Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl)) && Input.GetKey(KeyCode.D)) {
				if (_processed)
					return;

				_processed = true;
				int rDemand = ZoneManager.instance.m_actualResidentialDemand;
				int cDemand = ZoneManager.instance.m_actualCommercialDemand;
				int wDemand = ZoneManager.instance.m_actualWorkplaceDemand;

				string message = $"Residential: {rDemand}\nCommercial: {cDemand}\nWorkplace: {wDemand}";

				ExceptionPanel panel = UIView.library.ShowModal<ExceptionPanel>("ExceptionPanel");
				panel.SetMessage("Detailed RCW Demand", message, false);
			}

			else {
				_processed = false;
			}
		}
	}
}

Testing

Now it's time to see if it works! Simply build the project (CTRL + SHIFT + B) and jump back into game, it should auto reload the mod withouy quiting the game.

Press CTRL + D to test the mod, it should show up like below:

Debug Your Mod

In case of your mod is not working properly, there's many ways to debug your mod, what I recommend is to look at the debugging page on official developer wiki to choose the way you feel most confortable with. The way I choose is to use Unity's debug logging API:

Debug.Log(...);

and then subscribe to ModTools to see the debug log.

Congrats! You just created your first mod in Cities: Skylines!

Happy Modding!