Skip to content

How to create a mod for unity game

newman55 edited this page Sep 5, 2024 · 6 revisions

Introduction

Note, this tutorial is more focused on creating scripts than content, but you can still use it to make a new game object or texture. Unity runs on the C# programming language, so creating a mod isn't as complex as it seems. You don't need to be an expert programmer, just understand the basics. The hardest part will be digging into the game's code to find the necessary functions you want to call or modify.

Project creation

  1. Download and install Microsoft Visual Studio Community with C#.
  2. Open the project creation and select Class Library (.NET Framework). Change the name to TestMod.
  3. Open the project properties and ensure the target platform is .NET Framework.
  4. Add references to these game files located in the Managed folder. (Note: UnityModManager should already be installed.) (If you don't know which files you need, you can add them all.)
    • Assembly-CSharp.dll
    • Assembly-CSharp-firstpass.dll
    • UnityEngine.dll
    • UnityEngine.UI.dll
  5. If the Unity version is 2017 or higher, also add these additional files:
    • UnityEngine.CoreModule.dll
    • UnityEngine.IMGUIModule.dll
  6. Add references to these UMM files located in the Managed/UnityModManager folder:
    • UnityModManager.dll
    • 0Harmony.dll

Now you can use the game and Unity Mod Manager functions.

Information file

Create an Info.json file so that the mod manager can determine which files need to load when starting the game. The file should be in JSON format and placed in the Mods folder, for example: \Steam\steamapps\common\YourGame\Mods\TestMod\Info.json.

{
  "Id": "TestMod",
  "DisplayName": "Test Mod",
  "Author": "username",
  "Version": "1.0.0",
  "ManagerVersion": "1.0.0",
  "GameVersion": "1.0.0",
  "Requirements": ["SomeMod-1.0.0", "AnotherMod"],
  "LoadAfter": ["SomeMod", "AnotherMod"],
  "AssemblyName": "TestMod.dll",
  "EntryMethod": "TestMod.Main.Load"
}
  • Id: Unique string. It should match the folder name. (Required)
  • DisplayName: Name or Title. (Optional)
  • Author: The creator of the mod. (Optional)
  • Version: Needed for dependent mods and to check for updates (Format must be 'x.x.x'). (Required)
  • ManagerVersion: Minimum required version of the mod manager (Format must be 'x.x.x'). (Recommended)
  • GameVersion: Minimum required version of the game. Works if the point is preconfigured. (Format must be 'x.x.x'). (Optional)
  • Requirements: Minimum required version of mods or just other required mods. (Optional)
  • LoadAfter: List of mods that will be loaded first. (Optional) [0.22.5]
  • AssemblyName: Filename we are compiling. Defaults to 'Id' (e.g., TestMod.dll). (Optional)
  • EntryMethod: A function that will be called by the mod manager when the game loads. (Required)
  • HomePage: Web address to follow or check for updates from the Nexus site. (Optional)
  • Repository: Web address to check for updates from your own repository. (Optional)

Assembly file

The mod manager supports several variants of the Entry functions. Use only one. Let's call it Load. The name must be the same as in the EntryMethod.

using UnityEngine;
using UnityModManagerNet;

namespace TestMod
{
    static class Main
    {
        // Simply call. Can be compiled without dependencies on UnityModManagerNet.
        static void Load() 
        {
            // Something
        }

        // Transfer a variable with data about the mod.
        static void Load(UnityModManager.ModEntry modEntry) 
        {
            // Something
        }

        // Send a response to the mod manager about the launch status, success or not.
        static bool Load(UnityModManager.ModEntry modEntry)
        {
            // Something
            return true; // If false the mod will show an error.
        }
    }
}

You can add a function that will control on/off mode similar to hot-plug. This function is optional. If the function is missing, the mod may turn on, but it will turn off only after restarting the game.

using UnityEngine;
using UnityModManagerNet;

namespace TestMod
{
    static class Main
    {
        public static bool enabled;

        static bool Load(UnityModManager.ModEntry modEntry)
        {
            modEntry.OnToggle = OnToggle;
            return true;
        }

        // Called when the mod is turned to on/off.
        // With this function you control an operation of the mod and inform users whether it is enabled or not.
        static bool OnToggle(UnityModManager.ModEntry modEntry, bool value /* to active or deactivate */)
        {
            if (value)
            {
                Run(); // Perform all necessary steps to start mod.
            }
            else
            {
                Stop(); // Perform all necessary steps to stop mod.
            }
            
            enabled = value;
            return true; // If true, the mod will switch the state. If not, the state will not change.
        }
    }
}

Details of the ModEntry class

  • Info: Contains all fields from the Info.json file.
  • Path: The path to the mod folder, e.g., \Steam\steamapps\common\YourGame\Mods\TestMod\.
  • Active: Indicates whether the mod is active or inactive.
  • Logger: Writes logs to the Log.txt file.
  • OnToggle: The presence of this function lets the mod manager know that the mod can be safely disabled during the game.
  • OnGUI: Called to draw the UI.
  • OnSaveGUI: Called while saving.
  • OnUpdate: Called by MonoBehaviour.Update.
  • OnLateUpdate: Called by MonoBehaviour.LateUpdate.
  • OnFixedUpdate: Called by MonoBehaviour.FixedUpdate.
  • OnShowGUI: Called when opening the mod GUI.
  • OnHideGUI: Called when closing the mod GUI.
  • OnSessionStart: Called when the game session starts. Works if the point is preconfigured.
  • OnSessionStop: Called when the game session stops. Works if the point is preconfigured.

Examples

A simple example of how to bind any action to keys.

using UnityEngine;
using UnityModManagerNet;

namespace TestMod
{
    static class Main
    {
        public static KeyBinding key = new KeyBinding() { keyCode = KeyCode.F1 };

        static bool Load(UnityModManager.ModEntry modEntry)
        {
            modEntry.OnUpdate = OnUpdate;
            return true;
        }

        static void OnUpdate(UnityModManager.ModEntry modEntry, float dt)
        {
            if (key.Down())
            {
                 Player.health = 9999f;
                 Player.weapon.ammo = 9999f;
            }
        }
    }
}

Harmony

Harmony patches allow you to take complete control over game functions. For more detailed information, please refer to the official Harmony Wiki. Below is a basic example of how to implement a Harmony patch:

using UnityEngine;
using HarmonyLib;
using UnityModManagerNet;
using System.Reflection

namespace TestMod
{
    static class Main
    {
        public static bool enabled;
        public static UnityModManager.ModEntry mod;

        static bool Load(UnityModManager.ModEntry modEntry)
        {
            var harmony = new Harmony(modEntry.Info.Id);
            harmony.PatchAll(Assembly.GetExecutingAssembly());

            mod = modEntry;
            modEntry.OnToggle = OnToggle;

            return true;
        }

        static bool OnToggle(UnityModManager.ModEntry modEntry, bool value) 
        {
            enabled = value;
            modEntry.Logger.Log(Application.loadedLevelName);

            return true;
        }
    }

    [HarmonyPatch(typeof(Application), "loadedLevelName", MethodType.Getter)]
    static class Application_loadedLevelName_Patch
    {
        static void Postfix(ref string __result)
        {
            if (!Main.enabled)
                return;

            try
            {
                __result = "Modified Level Name";
            }
            catch(Exception e)
            {
                mod.Logger.Error(e.ToString());
            }
        }
    }
}

Now the function Application.loadedLevelName will always returns the Modified Level Name string. Similarly, you can change any value in the game. Be sure to wrap the patch in a Try-Catch block to prevent errors from interrupting the parent functions. Errors can occur if the game code changes after updates. Harmony 2 requires at least ManagerVersion 0.22.0

Loading custom textures or predefined assets.

In the Unity editor use AssetBundles to create an asset file. Ensure the editor version is not higher than the Unity version used in the game. Copy it to the mod folder, now you can load it using the code. These functions will require the additional libraries UnityEngine.AssetBundleModule.dll and UnityEngine.ImageConversionModule.dll.

var assets = AssetBundle.LoadFromFile(Path.Combine(modEntry.Path, "modname.assets"));
var go = assets.LoadAsset<GameObject>("go name");
var tex = assets.LoadAsset<Texture2D>("texture name");

Can also be loaded jpg and png directly, but in this case they will be uncompressed and take much more video memory.

var tex = new Texture2D(2, 2, TextureFormat.ARGB32, false);
tex.LoadImage(File.ReadAllBytes(filePath));
tex.wrapMode = TextureWrapMode.Clamp;

Finally

Last, compile this code by going to Build > Build Solution (Ctrl + Shift + B) and copy the file TestMod.dll next to Info.json. After starting the game, you will see messages in the Log.txt file. Also detailed log can be found in YourGame\YourGame_Data\output_log.txt or C:\Users\%USERNAME%\AppData\LocalLow\YourGame\Player.log.

Add the following command to Build Events so that the compiled file is copied automatically.

start XCOPY /Y /R "$(TargetPath)" "D:\Games\YourGame\Mods\$(ProjectName)\$(ProjectName).dll*"

Additionally

You can explore the game code using the dnspy tool. Also available source code of my mods and you find any mod on the nexus.

Runtime Unity Editor

In-game inspector, editor and interactive console for applications made with Unity3D game engine. It's designed for debugging and modding Unity games, but can also be used as a universal trainer. RuntimeUnityEditor