Jump to content

  • Log In with Google      Sign In   
  • Create Account

FREE SOFTWARE GIVEAWAY

We have 4 x Pro Licences (valued at $59 each) for 2d modular animation software Spriter to give away in this Thursday's GDNet Direct email newsletter.


Read more in this forum topic or make sure you're signed up (from the right-hand sidebar on the homepage) and read Thursday's newsletter to get in the running!


Implementing an efficient and powerful modding environment


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
3 replies to this topic

#1 irreversible   Crossbones+   -  Reputation: 1407

Like
2Likes
Like

Posted 02 September 2014 - 03:52 PM

First off, be forewarned that I haven't really done my home work - I haven't studied the capabilities, limitations and details of most modding environments out there and I'm probably pretty much unaware of the complexities of implementing one. I do, however, have a broadstroke idea of what I want to achieve.

 

First off, my target scripting language will most likely be Lua. That's the one I have some experience with and have found to be powerful and easy enough at the same time. That being said, your thoughts are welcome.

 

I haven't looked into too many games as I find myself pretty pressed for time these days, but the games whose capabilities I've come to respect include, foremost:on the UI side, World Of Warcraft and on the overall capabilities side, Skyrim.

 

The target capabilities of the modding system I have in mind are, in the order of importance:

 

- item/mob expansion packs, including custom complex weapon and item behavior

- world expansion via dynamically inserted elements

- AI scripts

- game-wide rule-based replacement of world elements

- full UI replacement/extension, including new layouts/windows 

- post-processing/shader effects

 

The last three are pretty much the least of my worries for now.

 

As for implementation, the most straightforward method I can think of is creating a hooking layer that can override or revert to default behavior based on the input. Mostly, this seems fairly simple:

void OnDamage(IActor * receiver, IEvent * source)
{
res = 0;

for each mod in ondamage_hooklist
  {
  //call script
  res = ondamage_hooklist[i].ondamage(receiver, source);
  if(res != 0)
    break;
  }

if(res == 0)
  OnDamageDefault(receiver, source);
}

This applies to any functionality that supports modding, eg resource loading, item equipping, firing, using, hitting a target etc. Each event simply includes references to the relevant internal structures. For periodic events, a mod can initialize a timer.

 

This is all fine and dandy - until it comes to UI and post-processing mods. This is also the main reason I'm staring this thread - I haven't done anything like this before and if moddability is anything like physics or collision, it would do well to consider a general plan of attack as early in the design phase as possible.

 

I'm assuming the simplest way to support custom post-processing mods would be to simply run them on top of the actual game loop - eg simply apply a provided filter shader after the frame finishes drawing.

 

However, modifying the UI seems fancier and much more resource-heavy - trapping draw calls on exising elements from within Lua is one thing, but it feels that doing this with brute force may easily prove fairly slow.

 

Overall, I'm not yet entirely sure where I'm going with this or what it is precisely that I need. I also can't find too much overly accessible information on the web with respect to how big games do it, which is why I'm asking for ideas, links, caveats, pointers and things to consider from you.



Sponsor:

#2 SeanMiddleditch   Members   -  Reputation: 7241

Like
1Likes
Like

Posted 02 September 2014 - 06:54 PM

As for implementation, the most straightforward method I can think of is creating a hooking layer that can override or revert to default behavior based on the input. Mostly, this seems fairly simple:
void OnDamage(IActor * receiver, IEvent * source)
{
res = 0;

for each mod in ondamage_hooklist
  {
//call script
  res = ondamage_hooklist[i].ondamage(receiver, source);
  if(res != 0)
    break;
  }

if(res == 0)
  OnDamageDefault(receiver, source);
}
This applies to any functionality that supports modding, eg resource loading, item equipping, firing, using, hitting a target etc. Each event simply includes references to the relevant internal structures. For periodic events, a mod can initialize a timer.

You should generalize this more. It's not just mods/hooks that might want to do this, for example. Your game probably wants some kind of generic effects system for modifications, like being able to get a 2X damage buff or adding additional poison damage or the like. Once you have such a system in place, scripts can be allowed to create new effect types.

All three of your first items are handled easily enough by allowing using to drop additional files in your resources folder, particularly scripts. They can add new scripts, add new object definition files, etc. You can also allow replacement of built-in files (e.g. look first in the overrides folder for a file, then in your "official" resources folder). On top of that you can allow partial replacement (load the "official" file first, then load the override file and overwrite any fields specified in the override). For lists or the like, you should have a file format that allows to extend or inherit additional values. Prefer using explicitly named items (or items with UUIDs) for identification purposes for linking overrides to parent data.

e.g. your shipped chain mail armor file might have a list of damage types and absorption rates for each. A user could provide a file that explicitly overrides the slashing damage absorption rate but doesn't modify the others. Or it might add a new damage type.

This is useful not just with modding but also with your data definition in the first place. It lets you create common "base" game object definitions that share a ton of properties but which are then specialized. e.g. you might have a Monster.xml that defines all the base components and data for an AI enemy, an Orc.xml that then sets default combat, visual, audio, and debug info for all orcs, and then an OrcSniper.xml that overrides the visual model and AI scripts to get a specific creature.

This is also useful for saved games and the like. Instead of saving the entire state of an object, you just save the differences from its base object. The loading process is then identical between your saved objects and the base objects. To load an object, see which is its base, deserialize those properties into the C++ object recursively, then deserialize the object itself. This allows each "derived" data object to override any properties specified by its base, but doesn't require it to. The objects are then composable from multiple bases (including modder overrides and saved games) easily.

#3 irreversible   Crossbones+   -  Reputation: 1407

Like
0Likes
Like

Posted 04 September 2014 - 04:18 AM

Thanks for the reply!

 

Indeed, that's the thing I'm currently trying to wrap my head around. Generally I think it's okay if a subsystem requires a few rewrites as there are always things that are difficult to foresee. Nevertheless, this seems like a pretty extensive write in the first place so I'm trying to minimize the iteration cycle.

 

These are my initial throughts:

 

I haven't really gone too deep into details yet, but my first impulse is to keep core (built-in) objects as small as possible and extend all additional functionality to mods. As such the core "monster" might only have the 'health' and 'max speed' parameters, but all other properties (armor, shield and whatnot) are stored - or rather appended - via a list of modifiers, which are essentially custom properties. These modifiers are represented as floating point duples of 'value' and 'scale', combined with a  'name' where scale defaults to 100 and value to 0.

 

For instance a gameplay mod might register a number of modifiers for specific monster types (whether these are overrides read from a text file or generated via a script doesn't really matter). Or it might generate a modifier field for all monsters in the game. I would essentially treat the override checker as yet another mod, though it would be built-in.

 

It would look something like in the mod's init proc:

 

//string form mob ID, modifier name, value, scale and a reference to the mod that registered this modifier and will consequently handle it

AddModifier("Archvile", "Armor", 0.5, 100.0, mod); //add the Armor field to Archvile and default it to 50

 

After all modifiers are registered, the mod sets up any callbacks it needs:

 

RegisterHandler("OnDamageActor", "fnModOnDamageActor_ArmorAndHealth", mod, bPostHandler);

RegisterHandler("OnSpawnActor", "fnModOnSpawnActor", mod, bPostHandler);

 

Now, when OnSpawnActor() or OnDamageActor() is called, the default handlers are bypassed and the mod's scripts are called. If more than one handler are defined, the latest loaded one is used and a warning is displayed that there are duplicate handlers. Mods are also classified as Handler level or Post-Handler level: there can only be a one Handler-level mod per event type (eg OnDamageActor), but any number of Post-Handler level mods (in this particular case the latter can be used for things like applying poison/magic damage from weapons etc that may not be related to the core handler and might be extensions that are provided by an entirely different mod).

 

In general, all Handler level handlers are provided by the core game package (which also makes it easy to patch/balance the game) and all user mods should behave on the Post-Handler level. Naturally, a user mod is free to override any core handler.

 

The core game repository can provide a series of these scripts that can be reused and expanded upon.

 

One thing I'm wary about, but that I'm also considering, is exporting all UI drawing and core game items to external scripts/data repositories - effectively making the entire game design mod-based.

 

However, since writing the Lua code interface is something that takes up considerable time I'm fishing for ideas and input first as to whether this (or my outlined approach in general) is a smart idea to start with.



#4 KulSeran   Members   -  Reputation: 2587

Like
0Likes
Like

Posted 04 September 2014 - 05:28 AM

The simplest thing to do is just make the game easy for yourself to edit, and expose the same tools to the moders.

 

Games like Kerbal Space Program had many many mods based on adding new parts and new models.  This was quite simply because the game just looked in a folder for data files and loaded them all.  Part models were in an easy to export format, and part behavior descriptions were all in text files ready to edit.  The more complex mods involved hooking into the code via a plugin system that roughly just exposed the internal game state to the plugin.

 

WoW's UI was so customizable because the game exposed a LUA API for rendering HUD elements and extracting some specific values from the game state.  The entire logic of the HUD including which files to load for textures was all located in LUA code.

 

Many of the skyrim mods are again just asset replacments, and modifications to the spell / item scripts.  But Skyrim also gives out modding tools, which are very likely the same or very similar builds of tools used to make skyrim.

 

The point with all of these is that they didn't bother saying "I'll give the modders an 'onDamage' hook". They implemented something that just exposes the parts they needed to make the game work to start with.  Input asset files and configurations equate to mods directly enough. For everything else expose an API to read game state and send messages back to the game with known points in where you can register the scripts to run.






Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS