A way to make an enum style list modders can add to

Hey guys, I’m currently using enums to define damage types (“fire”, “ice”, etc) so they can be chosen via dropdown on the inspector. The current plan is to have a List that defines what the damage type actually does does (scripts etc), and have the enum look it up.

I’m doing it this way, instead of with strings, for ease of use since the string can be misspelt. However, this doesn’t feel like it’s particularly moddable, since only one mod can apply to the enum at once. Which means the entire damage system is hard coded.

Am I being silly here? Should I just use strings, even though they’re inconvenient? Or is there a better method that I’m not seeing?

Depends on what you mean by moddable.

If you mean making it easy for game designers or yourself in the future to add new types, then using ScriptableObjects instead of enums is a better solution. Here’s a relevant post from Unity: Use ScriptableObject-based Enums in Your Project | Unity

If, however, you want your game to be moddable by players after release, you’ll need to build a system that supports adding new types dynamically. One way is to treat enums as integers, for example (DamageType)4 when DamageType originally has only three values, or to use a different approach for enumerations altogether.

Enums don’t exist at runtime, they’re compiled down to integers. This makes them fast (since they’re just integer cases with readable names) but inflexible. In Unity, this is even more problematic because enums are serialized as integers, changing their order can corrupt your data or break your game.

There are alternative ways to simulate enums, but if you need serialization (e.g., for Unity’s editor or saved data), you’ll have to make your enum-like structures serializable.

For pure C# solutions, consider strongly typed strings, which look similar to enums (e.g., DamageType.Fire). I’ve written about this in my blog post here: Alternatives to Enums - Part 1: Strongly Typed Strings - C# and Unity development
You can also explore more advanced patterns, such as representing each enum value with a class: Alternatives to Enums – Part 2: Advanced Enums Implementing Polymorphic Behavior with Custom Types - C# and Unity development

In all these cases, you’ll still need two additional components:

  1. A serialization of those types for editor support or saving data.
  2. A mechanism to create new enum types, if by “modding” you mean allowing players to add new ones.

Finally, if your players can create new damage types, you’ll also need a way for them to define unique behaviors for those types. New damage types without new effects or logic won’t add much value.

Your system should be set up around key values Dictionary as modders don’t / shouldn’t have access to your source code.

If you want to go the route of drop down in an editor that’s not a problem, I would recommend getting Odin Serialize to easily display inspector values for custom things like this.

Continue to keep your types as enums, and just use them as keys in a Dictionary<myEnum, myType>

Paging @CodeSmile , who has been doing a lot of LUA stuff… LUA is ideal for modding and CodeSmile has a ton of great links related to it.

BTW, reading this about enums…

… this is important because enums are not even stored in YAML as enums, just numeric integer values. This makes enums extremely dangerous unless you are very discplined about how you create them. Here’s more:

Enums enums are bad in Unity3D if you intend them to be serialized:

It is much better to use ScriptableObjects for many enumerative uses. You can even define additional associated data with each one of them, and drag them into other parts of your game (scenes, prefabs, other ScriptableObjects) however you like. References remain rock solid even if you rename them, reorder them, reorganize them, etc. They are always connected via the meta file GUID.

Collections / groups of ScriptableObjects can also be loaded en-masse with calls such as Resources.LoadAll<T>();

Best of all, Unity already gives you a built-in filterable picker when you click on the little target dot to the right side of a field of any given type… bonus!

If you absolutely INSIST on using an enum, always define them with hard numbers on them and once defined, NEVER change them again.

public enum Foo
{
   Bar = 1,
   Bat = 2,
   Yabo = 3,
}

Anything else puts you on the fast track to project wide disaster.

Yeah, Lua used to be my bread and butter 20 years ago. And trying to get back to it again.

For a worry-free Lua integration, check out LuaCSharp (use the v0.5-dev branch due to various API changes). This got me hooked on Lua again because like MoonSharp it’s a pure C# implementation but unlike MoonSharp this is actually fast and well designed.

Even if Lua is just feeding data to your engine, it allows modders for great flexibility and power since they can feed your game with algorithmic data.

Here’s an example how to define data in Lua and return it to the caller. It returns a Lua table (with nested tables inside) which is an array+dictionary in unison:

local DamageType = { "Fire", "Ice", "Nuclear", }
return
{
    Goblin = { Damage = 3, DamageType = DamageType.Fire },
    GoblinBoss = { Damage = 17, DamageType = DamageType.Nuclear },
}

Now the nice thing is that unlike Json this is both a data storage format and executable code in one. You can use Lua to algorithmically generate more data at the blink of an eye:

local DamageType = { "Fire", "Ice", "Nuclear", }
local data =
{
    Goblin = { Damage = 3, DamageType = DamageType.Fire },
    GoblinBoss = { Damage = 17, DamageType = DamageType.Nuclear },
}

-- add linearly scaling goblins
for 1, 100 do
    data["Goblin_Level" .. i] = { Damage = i, DamageType.Ice }
end

return data

And just like that you got yourself 100 extra Goblins with increasing damage output. :wink:

I also find the Lua table format very easy to edit. It’s human readable, and if you make a syntax error, you get told so where and why, rather than an ominous “json read error” :thinking:.

If you design your modding capabilities around defining data you can already allow users to mod a LOT without even providing them with a custom API. For instance you could scan for a DamageTypeNuclear.lua file and then run it. It expects a Lua function as return value with predefined parameters which allows modders to change how damage values for this type are computed. Perhaps “Nuclear” was added by modders as a damage type, and the function accordingly just returns a high value:

return function CalculateDamage(enemyTable)
    return 100000000000 -- enough to kill everything
end

On the C# side you’ll have a precompiled LuaFunction instance that you can just invoke a lot and it’ll run very fast, thanks to the LuaCSharp bytecode interpreter not having to cross language barriers (which makes xLua, nLua, etc so slow).

Okay I don’t want to use Lua, as I’m still learning C#. But thanks.

Thanks also to @Kurt-Dekker and @meredoth for the warnings about enums. I’m going to work to strip them out of the game. Thankfully, I didn’t go in too deep on them.

@jmgek @meredoth a Dictionary of scriptable objects sounds like a good idea, especially if it was accessible via something LIKE enums (but not actually enums) that players could add to via mod files. However, I feel like the strongly typed strings might make things harder for madders because they would need to add the code in a separate file rather than just dropping in Scriptable Objects as plugins (Something I can easily give them the ability to do).

Do you guys think that’s an issue? If so, I’ll just have the game dynamically load the scriptableobjects into a Dictionary using the filename as a key

Because you’re going back and forth from “player” to “adding code”, it’s had to understand what you’re really doing. You’re currently asking two different questions. One is about designing a system for mods and another for plugins.

Both these systems have two very different ways of architecture:

Mods

  • User-created content delivered as data/assets and optionally scripts in a sandboxed runtime.

Plugins

  • Compiled managed code that plugs into your game API. Trusted, versioned, and distributed to developers (not end users generally).

I’m just going to assume you want players to be able to add content.

Option 1
To get a fully dynamic system you’d just create a structure of your attacks and read them in:

{
  "id": "fireball",
  "name": "Fireball",
  "type": "Projectile",
  "damage": 30,
  "element": "Fire",
  "cooldown": 2.5,
  "effects": ["Burn", "Explosion"],
  "animation": "cast_fire",
  "sound": "fire_cast"
}

With this you have two options, you can pull in a structure and use it if it exists in a resource dir, or if you want it more static if there’s a mod file of fireball.json it would override the in game version of your fireball, someone can easily open up the directory to see the list of attacks and modify them as they choose.


public class AttackRegistry
{
    private readonly Dictionary<string, IAttackHandler> _handlers = new();

    //pass in the resource dir of attacks
    public void Register(string key, IAttackHandler handler)
    {
        _handlers[key] = handler;
    }

    public void Execute(string key, AttackData data, Entity caster, Entity target)
    {
        if (_handlers.TryGetValue(key, out var handler))
            handler.Execute(data, caster, target);
        else
            Debug.LogWarning($"Attack type {key} not found");
    }
}

Option 2
If your goal is to let mods use existing damage types rather than define new ones, just expose them as key-value pairs. Provide modders with a clear list of available types either through in-game documentation, a developer console command, or a reference page on your website.
Anyone creating mods expects to follow a documented schema, so make that list easy to find and keep it versioned as your system evolves. Each key already exists as a function that you would invoke “fireball” would have CastFireball() that you would just invoke myTableOfAttacks["fireball"].Invoke()

You are welcome. I have one question:

I still don’t fully understand what you mean by modders. Are you referring to other designers or programmers who have access to the project, or to users who have the finished game and can create mods for it?

If it’s the latter, none of the proposed solutions will work as-is. You’ll still need a user interface in your modding tools that handles the programming aspects of adding new damage types, regardless of the solution. For example, users can’t simply create Scriptable Objects and add them to the game after it’s compiled, since that would require recompiling with the new assets. Moreover, allowing users to create or inject C# code poses a major security risk.

If it’s the former, then you should consult the people involved and see which approach they’re more comfortable working with.

I mean Modders as in users who mod games. My idea is to give modders a ‘pack’ of stripped assets that they can port into Unity to create and customise elements. These elements can then be compiled and put into a mod folder, with a core file that my game looks for. The game scoops the elements then treats them like built in files.

Example:

User wants to make an voidball spell. They take the empty ‘bolt’ prefab I package and add their own void effect. They create a Scriptable for the new damage type, and attach it. They also specify the runes to cast it. And also the language file addon. They compile it. Grab the tiles. Insert into the game. My game then scoops all that from the mod folder, and adds it all to the relevant dictionaries and lists etc.

Though from what you guys are saying, this isn’t going to work?

Can they not just compile scriptable objects and code themselves and then have them be discovered by the game? If they can’t I need to rewrite my entire game to remove all scriptable objects because I use them as item template files.

Your game itself would need to be made to support this. This is not something that works out of the box.

Do you have a working modding framework? If not, you need to do this before anything else. Ideally, you make it project agnostic, too.

Otherwise if your game is interesting enough to begin with, frameworks for modding Unity games already exist and keen enough players will do it on their own.

I would like to make it as easy as possible because it’s the kind of game where you can just infinitely create new levels and so on.

If I wanted to make this work, would it be a better user experience (in terms of moddding and of loading speed) than having everything work from tweakable JSON files?

If so, is there an official guide on doing this? And on modding frameworks I could use? I don’t know what to search here.

Maybe start with “unity modding framework”.

And you really shouldn’t think about writing stuff to support modding without having already figured out modding support first.

In general you shouldn’t write stuff that is designed for certain APIs or features without having said API or feature working first, otherwise you have a 99% chance of writing something that is useless.

Alright then, thank you. I think that I may need to get a predesigned API like Lua Mod to be safe from malicious code. Though I just don’t want to have to learn and implement Lua in my project, so how do you guys feel about uMod? Or perhaps this one that has its own JS modder language?

I’m assuming with uMod that scriptable objects are back on the table, but with the others… not sure. If I go that way I think I may at least look into the idea of using JSON as a way to override things like item stats etc because I know not everyone is going to want to boot up unity just to give themselves a +3.

Does that sound like a plan?

Really my goal here is to make things easy not just for the modder but myself, so I need to factor in how much time this it going to cost me too.

The basic rule is simple: the easier you want it to be for users to modify your game, the harder it will be for you to code it.

Modding tools take significant time to build. Creating tools for data modification is difficult, creating tools for behavior modification is even harder. Supporting behavior changes typically requires integrating a scripting language other than C#, with a limited and safe set of operations exposed to modders.

When choosing a modding solution, it’s best to step away from your main project and build a small prototype game. Use it to test and evaluate different methods for modding both data and behavior. Create a quick prototype for each option and see which fits your workflow best.

ScriptableObjects, or any method that requires modders to access and recompile your source code, are not valid modding solutions. That approach effectively makes your game open source and unprotected, allowing anyone to alter it freely and distribute derivative versions that are considered separate games.

NaughtyAttributes has a bunch of useful options for this.

Not sure what NaughtyAttributes has to do with modding.

You need to use and evaluate the possible options, and see which of them is most suitable.

Like meredoth said, you need to get working what you want on a small scale.

And honestly, should your game get popular enough, whatever modding you make will always be insufficient, and modders will find ways around limitations. This is why Morrowind can be modded via Lua scripts nowadays rather than the game’s own scripting engine: because it lets modders do things they couldn’t before.

This requires an interesting enough game in the first place, of course.

Designing a good and easy modding interface is not that easy. Especially when you worry about mods completely breaking the game or to pose a security risk for the user.

ScriptableObjects are actually a great solution as you can use reflection to accumulate all types derived from the base type. However you have to provide at least a basic external code / DLL loading mechanism in your game. At that point it usually makes sense to provide an interface that allows such modifications to the game. As I said, modding support is actually a quite advanced topic and difficult to get “right”. It also highly depends on how the game is structured. There’s no one-size-fits-all solution.

I agree that lua or some other scripting language is often times the easiest and most secure approach to provide a modding interface as you are in full control of what the user can or can not do. Loading actual arbitrary user compiled code as DLLs can create serious security issues, especially when you want to host and distribute such user created mods yourself as you most likely can hold liable for damages. Nobody wants to play a game where a user mod secretly abuses his PC to do crypto mining, steal the private information or literally nuke their machine. That’s why mod hosting providers for games like Minecraft have a huge responsibility.

Microsofts approach to modding in Minecraft is purely data-driven. This makes doing more complex stuff extremely complicated or even puts some hard limits on what you can modify, especially when it comes to core mechanics. Classical mod loaders like forge, fabric and co are designed to provide an easy interface to most of the core features of the game in a way so that loading multiply mods don’t step on each other toes that easily.

So many things go into modding support as any kind of save game system has to be able to handle that newly added content. As it was already said, modding interfaces take quite a bit of time to create and really need to be throught through. SpaceEngineers for example (which actually runs on a C# baseed in house engine) does have essentially two layers of “modding” support. It provides countless wrapper interfaces and objects for the mod to interact with the core objects. On top of that they added a “programmable block” that can be programmed in-game with a simple C# script. That’s actually clampped down even more as it doesn’t give you full access to the game world which would allow cheating, but only context specific information about the “grid” it was build on or is connected to.

If it’s a multiplayer game, the network layer is another can of worms. Mods often also need to exchange state or even information. This also needs to be as tamperproof as possible.

I got a bit off-track (though still generally on topic). Actual enums (number based entries) are a huge problem when you want to allow more than one mod at a time, because those mods need to share all the resources. Not every mod can know and negotiate with all those other mods who uses which number for what.

“Modding” can also mean a lot of different things. In many cases people consider mods to add new functionality and / or new game content or mechanics to the game. However games like WoW only allow interface modifications. They actually use for almost everything a linear ID. They can do that because they are in full control of what IDs exists. Mods can’t add, remove or change any of that.

I really don’t wand to discourage your ambitions, I just want to be realistic about the work load you add to your game. You said you’re still learning C# and that’s ok. However mod support is really not trivial, especially to get it right. If this is your first bigger project, you may want to lower your ambitions to not get frustrated in the end.

^ ^ ^ This. Your game will occupy a spot somewhere between:

  • “I just made the game, good luck modifying it!”
  • “I made my entire game, but in addition I also made: a level editor, a visual scripting language, as well as exposed every single property and variable to you in my own custom editor because Unity itself will not be present after I build my game, it is supported by my save system, my level menu picking system and every enemy can function on any level you could ever design when modding my game.”

The first is zero work. The latter is thousands of times more work, depending on how much side tooling you make to help modders when they encounter errors, issues.

Oh yes: you WILL have bugs, and modders WILL make bugs, and if you don’t provide a handy means to help them debug, well, why didn’t you? :slight_smile:

Well, I am actually going to try to make a level editor to make the grid based game world itself. I think that may cut down a bit on work in both regards. If I then add an extension that gives the ability to pull in meshes they will be able to make items within the game very easily. Add in portraits, and make the effects modular, and that is a big chunk of mods taken care of. It would mostly be the monsters and more complex feature adding that would be a problem.

So I guess the question becomes “what kinds of mods am I opening up my game to” not “will it be moddable?”. So the main issue I think would be adding new functions to new tiles or new monsters. Which would require an API.