Overview
How to write and debug mods in C#
Info
Official modding support is planned, including Steam Workshop and tools for authoring content.
This guide show how it’s possible to mod Volcanoids even before official support is there.
Mods created according this guide will be compatible when official modding support arrives, but:
– they’ll have to be repackaged for uploading to Workshop
– some game features will change and mods utilizing these features will need updates
Features under active development:
– combat, weapons, enemies, enemy AI
Required tools
Tools
- Volcanoids
- Visual Studio 2019 Community Edition
- Unity 2020.1.5f1 (optional)
- ILSpy (optional)
You can use any developer environment, but I’ll be describing how things are done in VS2019. When installing Visual Studio, make sure to install C# and .NET Framework. Unity is needed for converting debugging symbols from PDB to MDB. If you won’t be attaching debugger, you don’t need that. ILSpy or other decompiler is very useful tool, you’ll be using it to dig into game DLLs and look for classes and their members.
Suggested workloads when installing Visual Studio 2019, Game Development with Unity is required for debugging.
Project setup
References
Create new “Class Library” project in Visual Studio
Add references to Unity & Volcanoids DLLs
Location: SteamsteamappscommonVolcanoidsVolcanoids_DataManaged
Files: Assembly-CSharp, Rock.*, Unity.*, UnityEngine.*, com.unity.multiplayer-hlapi.Runtime
References are added by right-clicking project and choosing Add, Reference…
Browse to location mentioned above and add ‘Assembly-CSharp’, ‘com.unity.multiplayer-hlapi.Runtime’ and then all dlls starting with ‘Unity’ and starting with ‘Rock’
Post-build event
Add following post-build events (in project properties), they copy built DLL into Mods dir
Final setup
Building and running
Copy-paste this script into Class1.cs, build project, and start Volcanoids through Steam
Now open log file
You should see that your mod has been loaded
Congratulations, you’ve just created and run your first Volcanoids mod!
How it works:
When you build your project, it creates DLL file, which is copied by post-build event into Mods directory: c:UsersUSERNAMEappdatalocallowVolcanoidVolcanoidsMods
When Volcanoids is started, it looks for any DLL files in Mods dir and loads them. Then it looks for any classes inheriting from “GameMod”, creates instances of them and call “Load”.
In the Load method, there’s statement, which writes into log file: “Mod loaded”
First real mod
Delete Class1.cs from your project
Create new files named TestMod.cs, TestBehaviour.cs
Replace their content with code below.
Result:
Text on Exit button in Main Menu will be animated.
How it works:
Mods are loaded before any scene is loaded.
This particular mod hooks into “sceneLoaded” event.
When the scene is loaded, callback method is called
This method tries to find “Exit Button”
When it finds it, it attaches TestBehaviour to it.
TestBehaviour finds Text element on the button and animates character spacing.
Debugging
This is optional step and requires some setup. It allows you to put breakpoint into code of your mod, break Volcanoids execution, inspect / modify variables at runtime and continue execution.
It’s great tool for discovering what is going on in the code and why it’s happening.
Development Builds
Debugging requires development builds, these builds are available on “development” Steam beta branch, password is “FuzzyPuma314”. These builds might be slightly ahead or behind the public / preview branch.
This is how you can tell whether you’re running development build or not.
Debugging symbols
Unity Engine is using Mono runtime, you’ll need to convert debugging symbols from PDB to MDB.
Modify Post-Build events to automate PDB to MDB conversion.
You might need to change UnityPath on the line “SET UnityPath=”, depending on where is your Unity installed and what version you have.
Launch options
Attaching debugger requires specific command line arguments.
Add following to Steam Launch options of Volcanoids: -player-connection-debug 1
If you want to debug the loading of your mod, you’ll need debugger to be attached at launch
Add following to Steam Launch options of Volcanoids: -wait-for-managed-debugger 1
At game launch, it will display message box telling you that you can attach debugger now.
Running
When you’re done, rebuild your project, start Volcanoids through Steam and attach Unity debugger. Make sure to use “Debug”, “Attach Unity Debugger” in Visual Studio menu. Attaching classic debugger to process won’t work. Press F9 to put breakpoint on current line in code.
You need to have ‘Game Development with Unity’ workload installed (part of VS2019), more info at the beginning of the guide in Required tools section.
Game modding
Startup
When your mod loads, no scene, not even Main Menu will be loaded.
Most mods affecting gameplay need to do something when actual game starts.
Easiest way to hook into that is by using SceneManager.sceneLoaded event. Don’t forget to add “using UnityEngine.SceneManagement;” to the top of the script.
In reaction to scene load, you can check whether loaded scene name is “Island” or “MainMenu”.
If you need to run something every frame, you can create new GameObject and attach a script to it with Update method (like in example above).
Player and drillship
Player is available through Player.Local, make sure to check it for null, because player is not available when the game is loaded, it’s spawned short after that. To access player drillship use TrainCrewMember and PrimaryDrillship, also check for null, because player might not have drillship yet.
{
var trainUpgrades = member.PrimaryDrillship.GetComponent<TrainUpgrades>();
}
Game data
Many mods need to modify game data, typical scenario is to increase speed of the drillship, change number of core slots for each upgrade or modify some recipes. Game data can be accessed through GameResources class.
You can use VS2019 autocomplete, ILSpy or other decompiler to discover what’s available there.
Make sure to access game data after “Island” scene is loaded. Game data are not available in main menu.
To modify concrete item, you’ll probably need to know its “name”. You can use Debug.Log to write name of all items / recipes into log file or you can use debugger and inspect variables (both approaches are mentioned above).
Item list: [link]
Item categories: [link]
Recipe list: [link]
Recipe category list: [link]
Crafting upgrades: [link]
{
if (arg0.name == “Island”)
{
var tracksItems = GameResources.Instance.Items.OfType<TrainTracksItemDefinition>();
foreach (var tracks in tracksItems)
{
if (tracks.name == “TracksT1_Basic”)
{
tracks.SurfaceMovementSpeed *= 5;
}
}
}
}
Loading sprites from PNG
You can use this method to load PNG icon from Mods folder. Icon can be assigned to newly created item, more information below.
{
var path = System.IO.Path.Combine(Application.persistentDataPath, “Mods”, iconpath);
var bytes = System.IO.File.ReadAllBytes(path);
var texture = new Texture2D(512, 512, TextureFormat.ARGB32, true);
texture.LoadImage(bytes);
var sprite = Sprite.Create(texture, new Rect(Vector2.zero, new Vector2(texture.width, texture.height)), new Vector2(0.5f, 0.5f), texture.width, 0, SpriteMeshType.FullRect, Vector4.zero, false);
return sprite;
}
Create new game data
It’s possible to create new data, like new items, recipes, modules and more. Here’s example, comments in the code provide explanation.
{
if (arg0.name == “Island”)
{
// Get some items and a copper ingot worktable recipe
var coal = GameResources.Instance.Items.FirstOrDefault(s => s.name == “CoalOre”);
var titanium = GameResources.Instance.Items.FirstOrDefault(s => s.name == “TitaniumIngot”);
var copperIngotRecipe = GameResources.Instance.Recipes.FirstOrDefault(s => s.name == “CopperIngotWorktableRecipe”);
// Create new recipe
var recipe = ScriptableObject.CreateInstance<Recipe>();
recipe.name = “MyRecipe”;
recipe.Inputs = new InventoryItemData[] { new InventoryItemData { Item = coal, Amount = 2 } };
recipe.Output = new InventoryItemData { Item = titanium, Amount = 1 };
recipe.RequiredUpgrades = new ItemDefinition[0];
// Copy categories from copper ingot worktable recipe, this will make it craftable in worktable as well
recipe.Categories = copperIngotRecipe.Categories.ToArray();
// Recipe needs stable GUID across games to work with saves
// This guid was generated through Visual studio, Tools, Generate GUID, dashes were removed
var guid = GUID.Parse(“B0269753FFDA43F6997EE41855B2F2D2”);
// Not ideal, will try to make is more simple in next version
typeof(Definition).GetField(“m_assetId”, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).SetValue(recipe, guid);
// Add recipe among runtime assets so game knows about them
// All will try to make it more simple in next update
AssetReference[] assets = new AssetReference[] { new AssetReference() { Object = recipe, Guid = guid, Labels = new string[0] } };
RuntimeAssetStorage.Add(assets, default);
}
}
Item name and description
Assigning item name and description is not directly possible. It possible only through reflection. This will be fixed in next update, meanwhile use this workaround.
where T : struct, ISerializationCallbackReceiver
{
str.OnAfterDeserialize();
}
public static ItemDefinition PrepareItem(string name, string description)
{
var item = ScriptableObject.CreateInstance<ItemDefinition>();
LocalizedString nameStr = name;
LocalizedString descStr = description;
InitializeLocalizedString(ref nameStr);
InitializeLocalizedString(ref descStr);
typeof(ItemDefinition).GetField(“m_name”, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(item, nameStr);
typeof(ItemDefinition).GetField(“m_description”, BindingFlags.NonPublic | BindingFlags.Instance).SetValue(item, descStr);
return item;
}
Final result
Authoring content with Unity [WIP]
This is something I’d like to explore more and write about it in detail.
The idea is like this:
- Create new Unity project
- Import game DLLs
- Author new assets (new items, recipes, drillship modules)
- Export assets into asset bundle
- Load asset bundle by a mod
Final words
Please leave any feedback here or contact me on Volcanoids Discord.
I’d be glad to help with anything related to modding 🙂
Few example mods:
[link]
Thanks to members of our community and modders who inspired me to write this.
Special thanks to Linus.
Happy modding!