Modding Tutorials

Discuss modding questions and implementation details.
Post Reply
User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Modding Tutorials

Post by LypyL »

In this thread is some information and a few basic examples about how to use the new modding system. Keep in mind it's still very much a work in progress :) Feel free to post any questions!

Overview

Here's a very shallow look at how the modding system works.

Creating mods is all done in the Unity Editor, with the help of a simple editor window. Anything that Unity recognizes as an asset can be included into mods.

The mods are represented by a single file (a unity asset bundle) with a .dfmod extension.

During the setup scene, any Mod Files in the Mod Directory (currently the Streaming Assets Path) are detected by the ModManager and partially loaded. During this stage you can set the load priority for each mod, and also chose to disable Mods that you don't want to run (these will be unloaded completely and unavailable during the game).

Once the main game scene is loaded, the ModManager will go through all the mods, compile any scripts, and then invoke special functions to begin the setup process. How exactly each mod is setup is currently left entirely for you to decide at this point.

The tutorials will include a few different examples of varying degrees of complexity from a simple "hello world" to loading assets from the mod package and setting them up in the scene, and assume basic knowledge of both Unity and Daggerfall Unity.

For the purposes of these tutorials, please make sure you're starting with the Setup scene, and the ModManager object is in the scene. If you don't have the ModManager in the scene, create a new game object and add the ModManager as a component to it.

Known Issues

enums - scripts with enum types defined will fail to compile. You can still use any of the enum types already in the project. This is an issue with the mcs compiler.

generic functions / classes - similar to the above, due to an issue with the compiler scripts that try to define generic functions will not compile.

Unity Versions - mods created in newer versions of unity might not be compatible with older versions of unity. This is noticeable if the mod is in the mod directory, but doesn't show up in the list of mods.

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

Part 1

FILES

This example will show the basics on using the mod builder and loader window.

1. If you don't already have one, create a StreamingAssets directory inside your project's assets path (Assets->Create->Folder and name it StreamingAssets).

1. In the Unity Editor, open the mod builder window (Daggerfall Tools -> Mod Builder) and select "Create New Mod"

Image


2. A simple window with some text fields and buttons will open. Before you can build your mod, you must fill in the Mod Name & title fields (the rest are optional, but recommended). The name field is really just for the file name, while the title is used in game.

For this example type "emptyexample" in the name field, and "Example Mod" in the title field without quotations. Fill in the rest of the fields as you prefer.

Image


3. click the Save Mod File button, and then expand the Files drop down - you should see one file listed, something like Assets\ModBuilder\emptyexample.dfmod.json - these file stores all the information used to build your mod, and can be used by the mod builder window to update or change existing mods.

4. Click the "Build Mod" Button, and then select a directory outside of your assets folder.

5. Browse to the location you selected, find the directory for your operating system type (linux, windows or osx), and then locate the .dfmod file in that folder, and move it into /Assets/StreamingAssets/ in your project.

Image

6. Launch the startup scene. At the options panel, click the Mods button to open the mods loader window. You should see "emptyexample" (or whatever the file name is sans extension) listed one the left side.

Image

Most of the functionality here is fairly self explanatory.
  • The higher a mod is in the list (conversely, the lower it's load priority #), the sooner it will be loaded.
  • If the Enabled checkbox is unchecked, the mod will be unloaded as mentioned previously.
  • Hitting the refresh button will look for changes in the modding directory.
  • The Extract Files button will extract text assets from the mod asset bundle file to a sub-directory of the mod directory.
Currently changes made in the mod loader window aren't saved between sessions, this will be added in the future.

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

Part 2 - HelloWorld

FILES

In this example we're going to make another simple example mod that creates a "Hello World!" pop-up text message using Daggerfall Unity's UI.

1. Open the Mod builder window again, but this time chose "Open Existing Mod File", browse to /Assets/ModBuilder and look for the emptyexample.dfmod.json from the last example. If you didn't do the previous example or deleted the file, just create a new one and follow the next steps.

2. change the modname to "example2" and mod title to "Example 2 - Hello World!"

3. Click the save mod file button.

4. If you haven't already, add the HelloWorld c# script to your project. Select the HelloWorld.cs asset in the project pane, then in the mod builder window click the "Add Selected Asset(s)" button. If you expand the Files drop down, you should both the mod info file, and the HelloWorld script.

Image

5. Now build the mod like in tutorial one (remember to select a target location outside of the assets folder), and then drop your new file into your StreamingAssets directory.

6. Run your project, verify the mod was found by checking Mod Loader window.

Image


Once you reach the main game scene, you should see a pop-up message like this:

Image

Open HelloWorld.cs file in your editor of choice. Anything of note has been commented, but you should pay close attention to the Init() method and comments.

The ModManager will go through each mod in order of load priority and invoke these functions, which can be used to setup your mods however you want. You can have as many of these functions as you choose.

1. Marked with the [Invoke] custom attribute
2. Be public & static class method
3. Take in an InitParams struct as the only parameter

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

Example 3 - Asset Loading

Files

This will demonstrate the basics of loading and setting up assets from a mod. Included are 2 scripts – one that simply exists to setup the mod for the sake of an example, and the other plays random audio clips included in the mod.

The audio tracks included are free assets were made by Jon Hillman, and can be found for free in the asset store.

1. Create a new game object in the scene (GameObject -> Create Empty). Rename it "Example".

2. Add an Audio Source component to new game object.

3. Create a prefab of the Example game object by dragging it into your project.

4. If you haven't already, add the ExampleModLoader, RandomAudioClipPlayer scripts to your project as well as the 5 audio clips (Zero Gravity 1.ogg – Zero Gravity 5.ogg)

Image

5. Open up the mod builder window, fill out the fields to your preferences. Then select the following and click the Add Selected Asset(s) button to add to the mod:
  • ExampleModLoader.cs
    RandomAudioClipPlayer.cs
    Example.prefab
    Zero Gravity 1.ogg
    Zero Gravity 2.ogg
    Zero Gravity 3.ogg
    Zero Gravity 4.ogg
    Zero Gravity 5.ogg
Image

6. Click the Build button when you're ready and select a target location outside of your project, then drop the *.dfmod file into your StreamingAssets folder, and play.



On starting a game (either a new game or loading an existing save), ExampleModLoader will create a clone of the Example prefab stored in the mod, then add RandomAudioClipPlayer as a component.

RandomAudioClipPlayer will disable DaggerfallSongPlayers, then load random tracks from the mod and play them. Each time it stops playing, it will load another track, and play it.

Important:

It's important to understand why we're adding the RandomAudioClipPlayer as a component to the Example game object after cloning, rather than the normal way of doing it which would be to make it part of the prefab.

The reason we have to do this is if the scripts attached to a mod aren't part of the project then Unity will have no idea what they are and your prefab won't work. Basically - anything that is included with DaggerfallUnity or is a MonoBehaviour component (transfrom, AudioSource etc) is safe, otherwise you'll have to set it up through code.

Image

For a simple demonstration:

1. Add RandomAudioClipPlayer as a component to the prefab

2. Comment out the following line in ExampleModLoader

Code: Select all

ExampleGo.AddComponent<RandomAudioClipPlayer>();
Then rebuild your mod like before.

3. Move RandomAudioClipPlayer into your StreamingAssets folder before you run your project so it doesn't get compiled.

4. Run, and look at the Example(Clone) object when it's loaded. It should look like the image above, and rather than the music included in the mod you should hear the vanilla daggerfall music.

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

A mod can defines settings to allow customization. Please add a using directive for the namespace DaggerfallWorkshop.Game.Utility.ModSupport.ModSettings and check the documentation for details.

Note: The tutorial inside the spoiler refers to an old version of the game and is to be considered obsolete.
Spoiler!
The example below was kindly written up by TheLacus:


The modding system allow modders to give users the abilit to customize parts of the mod.
This happens in the shape of a INI file, which can be used to import integers, floats, strings, booleans and colors.

Such INI file should start with a section labeled Internal. This is useful for two reasons. First of all, you can use it to provide a version number. This is independent from the mod version, as it's used to inform the game if the old ini file is still valid.
If you make a few changes on your mod that don't affect settings, the game will use the settings created with a previous version of the mod, otherwise they will be reverted to the new defaults.

Code: Select all

[Internal]
SettingsVersion = 1.0
Additionally, you can place here any other setting you want to be edited only from a text editor, for example because it's an advanced setting which you want to provide to experienced users but hide from the majority of players.

A nice thing of this features is, infact, that settings can be easily edited from a GUI in game, like this one:

Image

Access settings in-game it's pretty straightforward, too.

Code: Select all

ModSettings settings = new ModSettings(mod);
int intValue = settings.GetInt("SectionOne", "keyOne");
bool boolValue = settings.GetBool("SectionTwo", "keyTwo");
All the methods accept two parameters: the name of the section and the name of the key.

Code: Select all

[SectionOne]
keyOne = 5

[SectionTwo]
keyTwo = True
It is worth to mention that colors are read as an hex value. The first six characters indicate the color, while the last two are in a range from 00 (full transparency) to ff (full opacity). For example:

Code: Select all

[SectionTwo]
ColorOne = 00ff00ff
is opaque green (0, 255, 0, 255).

When you build your mod, remember to include your ini file saved as modname.ini.txt.
As mentioned above, if the file on disk is outdated or the mod is being installed for the first time, such file will be automatically created.

Download Files
Last edited by TheLacus on Wed May 06, 2020 1:00 pm, edited 1 time in total.
Reason: Added link to updated documentation

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

This is an example created by TheLacus demonstrating how to use the ImportedComponent attribute (also by TheLacus) which allows you to serialize and deserialize custom components attached to gameobjects in your mods by adding a [ImportedComponent] attribute to them. Also be sure to see his write up here on the modding system.

Because your custom Monobehaviours in mods are not known to unity, when you instantiate a gameobject your new Monobehaviour types are unknown and will not be attached to gameObjects like you expect, which would require you to manually add them and set any values. To avoid this problem, any components marked with the [ImportedComponent] attribute will automatically be re-added for you, and the values of serializable fields are saved when you build your mod and restored for you.

Download Example

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

reserved

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Modding Tutorials

Post by TheLacus »

I wanted to convert the grass mod to the mod-system, but i'm facing a weird crash which, apparently, is due to an enum declaration.

Basically, something like this works fine

Code: Select all

using UnityEngine;
using DaggerfallWorkshop;
using DaggerfallWorkshop.Game;
using DaggerfallWorkshop.Game.Utility.ModSupport;

namespace Testmod
{
    public class testEnum
    {
        [Invoke(StateManager.StateTypes.Game, 0)]
        public static void Init(InitParams initParams)
        {
            FlatTypes test = FlatTypes.Editor;

            if (test == FlatTypes.Editor)
                Debug.Log("flat type is editor");
        }
    }
}
But if i use a new enum all i get is mono.dll caused an Access Violation (0xc0000005) , inevitably followed by a ctd.

Code: Select all

using UnityEngine;
using DaggerfallWorkshop;
using DaggerfallWorkshop.Game;
using DaggerfallWorkshop.Game.Utility.ModSupport;

namespace Testmod
{
    public class testEnum
    {
        enum Example { None, firstItem, secondItem }

        [Invoke(StateManager.StateTypes.Game, 0)]
        public static void Init(InitParams initParams)
        {
             Example test = Example.firstItem;

             if (test == Example.firstItem)
                Debug.Log("example is first item");
        }
    }
}
LypyL, can you give it a look?

User avatar
LypyL
Posts: 512
Joined: Sun Mar 22, 2015 3:48 am

Re: Modding Tutorials

Post by LypyL »

This is a known issue, and is apparently a limitation with the mcs compiler. You can use enums that are declared in the project already without problem, but if you try to declare new ones and compile them at runtime it will fail like you saw.

It's ugly, but I get around it by just using constants.

User avatar
TheLacus
Posts: 1305
Joined: Wed Sep 14, 2016 6:22 pm

Re: Modding Tutorials

Post by TheLacus »

ok, thank you :)

Post Reply