I’ve answered similar questions in discord a couple of times; I guess this is as good a time as any to write an impromptu guide. IMO, if you’ve got everything set up and can print “hello world” onto the MelonLoader terminal (using @gusi’s guide linked above), you’re halfway there already.
There are 2 basic ways of getting a code to run via a mod. Firstly, is to use one of the callbacks provided by MelonLoader. These are methods that get called after specific events, your mod can inherit these and any code you write there will get executed at the appropriate time. Some of these hooks get called after a scene is loaded or even every frame, the existing list is available here. That entire page is well worth a read. These call backs are very easy to use and require only light scripting, but the call backs available are common to all Unity games, so they can be somewhat limited.
The second option is to use HarmonyPatch. This uses C# reflection black magic to let you write a function which gets called just before (or just after, or even replace) any specific method in the entire library of code that makes up the game (specifically in Assembly-CSharp.dll). This is very powerful but can be fiddly for various technical reasons. Again, that link is a pretty concise overview of what it can do, and how to use it; the MelonLoader article linked above explains how the two are integrated.
You might expect that one of the things we could do is to simply modify data files before they’re loaded into the game, ie, that there’s a BuildingData.xml file sitting somewhere and we can just change a number in a text file to increase a building’s range. Sadly, we cannot. We have the code but not the data files. So we have to modify the data after it’s been loaded but before it’s been used; there’s a lot of trial and error in figuring out when that is it different cases.
But to do anything useful with any of that, you’ll need to actually read the code to see what you want to change. I use two different tools for that. To “decompile” and then navigate Assembly-CSharp.dll I use dnSpy. Visual Studio does that itself, but I find dnSpny far nicer for this. The ability to search for classes by name and/or inheritance structure is great, as is the “analyze” function which will find all the cases where a particular method is called. Being able to navigate the in-game memory live is also fantastic - it lets me see what values things are by default, experiment with changes quickly, and verifiy that mods are doing what I want them to. This can be done easily using UnityExplorer - which is functionally a mod that works for all Unity mono games. Especially nice is the ability to click on an in-game object (on the map or in the UI), and inspect it.
In terms of learning Unity, there are loads of tutorials out there. What to learn depends on what you want to do. I’d say that it’s good to have a passing familiarity with what MonoBehaviour
, GameObject
, Transform
, Prefab
, and Components
are, and the tree like structure that links them; as well as Scenes
. There’s no need to become an expert on them, but knowing what these are makes navigating the architecture of the game way easier.
To (finally) answer your question about when managers are initialized - it depends, but fairly late. Often I find they’re not fully initialised even after the “Map” scene is initialised. Try inspecting those Managers at different stages of loading using MelonLoader.OnSceneWasLoaded
(and filtering by scene name) in MelonLoader. As to whether it’s a good idea to extend things, my general principle is to use Melon when I can, and Harmony only when I need to. Then it’s to get my code called as early and as rarely as possible. Once on loading is best, once after GameObjects are initialised is fine (the Awake
unity method), every time an attribute is pulled is worse, and every frame a last resort. Needless to say, changing functionality will be different than changing data.
If you prefer learning from examples, you can download any mod and “decompile” it using VS or dnSpy and see how they work. Sadly this erases comments, and replaces limited scope variable names with default ones, which can make code tricky to read. I did comment some of my earlier mods extensively while I was figuring out what was going on, so here’s the source if it helps you (please don’t judge it too harshly).
Examples.zip (7.8 KB)
I used MelonLoader.OnSceneWasInitialized
as my way in. This way I could modify the game data after it’s been loaded into memory, but before the save file (or new map) is processed. To actually get the data I sometimes GlobalAssets
and sometimes Unity’s Resources.FindObjectsOfTypeAll(typeof(_))
to search for all GameObjects of a particular class. The manager classes are also very useful (start with the singleton GameManager
and follow the attributes chain) to get to elements that are specific to an ongoing game (rather than the fixed data). When I need to do more than that, ie, modify the UI, or the actual logic of the game, I use HarmonyPatch. Lastly, I’ve included a utils file that has helper function that simplifies C#'s reflection, very useful because HarmonyPatch extends methods from the “outside” so I’m constantly having to access protected or private fields.
Sorry for the overwhelmingly verbose wall of text, I tried to not assume too much prior knowledge; hopefully this answers your questions. And those of any lurkers that are too shy to ask.