Showing posts with label programming. Show all posts
Showing posts with label programming. Show all posts

Wednesday, March 04, 2015

Best Laid Plans of Mice and Mods

I'm finally wading into the guts of the new modding system. Anyone who's made space for mods understands the tradeoffs: where do you leave space for modders? What is hardcoded, what is "softcoded" so modders can interject?

But this project is a bit different. It's intended to be open source, so nothing is truly hard-coded: the players can always edit the core code. Similarly, mods are created in the Unity project and exported from that space. It's not simply open source, it's a project where every modder is going to have the source code, even if they never really interact with it.

During this experiment, I've found myself developing in a strange way as a thought experiment. I've been trying to make absolutely every part of the gameplay a mod.

Some things are largely hardcoded. The way construction is handled, the way the basic menuing works, the light of the sun, the core existence of FacilityObjects and FacilityMOBs. In theory, these could be overridden, but they lay outside the modding infrastructure. A mod would need to aggressively intervene into the core systems in order to change those things. But... that's what most mods actually do in other games. The mod experiment has changed what I would consider "acceptable".

This morning I wrote electrical systems into the game. Power is a critical component of base management, so you can consider this a core feature. The vehicles you drive up in have solar panels attached to them, and can also generate electricity through their chemcell engines using some kind of liquid fuel. They have a specific amount of energy storage. These resources will last you some time, hopefully long enough to establish your base as an energy source instead of an energy sink. This is critical because it takes energy to build some things (for example, sintering sand requires energy, welding requires energy, etc). The more vehicles you build and take with you to your next location, the better off you'll be at the start.

There are a number of things the electrical system play requires. When you mouse over a car or a solar panel or whatever, the context popup needs to tell you how much energy is available/generated there. When you look at the facility overview, you need to know how much total energy is available, how much you're producing, how much you're using with standing facilities, how much you're using for construction or one-off activities. Moreover, it needs to continually update every time you build something new, do something new, toggle the behavior of an energy producing facility object, etc.

Sounds kinda... complicated and core, right? I mean, not complex in the grand scheme of things, but wired into a lot of the core game systems.

It's a mod.

Literally. "Core Electricity mod. Requires: Core Facility View mod. Priority 99"

A flip of a toggle, and the mod turns off. Nothing costs any energy. All the things that produce or consume energy stop caring about energy. Maybe some of them will stop being added to the list of available objects, when I care to bother dividing that stuff up.

Now, in a few small ways this is a cheat. Most of the core content is "integrated" with it, in terms of having an electrical cost to build. If you turn the mod off, that electrical cost stops mattering, but it's still part of the content. Similarly, most additional content later will probably also be integrated with the electrical mod. You can't really "not install" the mod, because it would involve not installing nearly every piece of content.

But you can "disable" the mod - all the content stays, but you are no longer limited by electricity and no longer receive reports about it.

All the reporting - the context popup, the facility overview, the build limitations - is part of the mod.

And this is how I've begun to think about my game systems.

Everything is core. Nothing is core. The systems are all stitched into the framework in the same way.

"Isn't that kind of a nightmare to develop?"

Well, I programmed the heart of the electrical systems before work this morning. In half an hour.

The key is the easy combination of ScriptableObject mod definition, the two GameObjects you specify as the overview menu item and the context menu item, and the UnityEvents that hook in automatically. It gives you an extremely easy "in" for hooking into the game world and running calculations.

"Isn't it painfully limited?"

Well, it's basically an API. The mod itself is fundamentally a way to interface with the game framework. But unlike a rigid mod API, this happens inside the Unity framework. In addition to the ease of integrating with UnityEvents and the inspector, you can use Unity's debugging capabilities and interact with the rest of the systems using actual code.

I've found that it produces extremely crisp results. Game systems tend to get complex and inter-related very fast and in very convoluted ways, at least when I program them. This keeps the systems so crisp you can actually turn them off.

This probably wouldn't work for every game. But in games which are largely statistical, this seems very powerful.

I'm sure I'll encounter drawbacks soon, because otherwise everyone would develop this way already. I'll keep everyone posted.

Tuesday, August 19, 2014

Fluid Time

I realized a little while ago that I've been leaning very heavily on a particular concept nobody else seems to use at all. I call it "fluid time". I have to explain it in order to explain my NPC-mod system, so here we go!

Most of my recent prototypes involve being able to fast forward, pause, even rewind. They feature going to some other location, but still having the first location processing in the background.

Normally, if you have things that change over time in a game, you'd attach them to the frame or physics loop and process them each iteration. But that doesn't scale very well as timescales change and the number of changing objects reaches the thousands or millions.

Using fluid time lets me get around that, and handle extremely large numbers of objects at any timescale I please.

It's a pretty basic idea. You take the statistics that behave predictably, and instead of calculating that predictable change every frame, you just create a function for it.

For example, let's say Anna is the only farmer we have. She produces food. When we plonk her into the game world and assign her that task, the "food" stat begins to change in a predictable way. The formula becomes "at day 1: 0 food. +1 food per day."

Now, if we zoom off to the far side of the galaxy and do a million other things, that function continues to exist. It doesn't take up our CPU, Anna doesn't need to be loaded into the scene. If we want to know how much food we have, we just call that function with the current day and it calculates out how much food we have.

Let's say that farmers stop producing food in winter. In that case, our function would simply have a bound. "At day 1: 0 food. +1 food per day until day 270."

We know when the function becomes invalid, so we know when to revisit and recalculate.

If we want to do it strictly, we can: day 270 dawns, we revisit the situation, and the formula changes to "At day 270: 269 food. +0 food per day until day 360."

Alternately, we could simply pause time in that area until we revisit - time stops there at day 270, and the player gets to take control at that time, even though the day might be 190,028 in another sector.

We also have a lot of options about historical info. Once day 270 rolls around, we can either save that now-obsolete span of time, or we can discard it if we don't need to remember. What would historical info get us? Well, you could rewind time, you could look at historical trends, you could play in multiple times simultaneously...

Another big advantage of this method is that it allows synching multiplayer really cheaply. It doesn't work for everything - for example, it can't respond easily to details like movements or the moment-to-moment choices of other players - but it works well for cheaply keeping track of thousands of low-maintenance shared objects. Of course it does: that's what it's intended to do locally, and it works just as well on a network.

There's one more big advantage, and this one's a doozy: it's easy to mod.

One of the core problems with mods is that they can only usually interact with the "surface" of the game. Mods that affect core functionality are usually not possible, to the point where several Skyrim mods come with a java program that runs in the background to forcefully intervene against the core code base. The reason it's hard to do isn't malice, it's architecture: the code is intermeshed with itself to do a specific kind of job, and there's no "cracks" in the wall for mods to break in through.

An event-driven system is a lot more flexible, as you can either overwrite the delegate assignments or start catching events.

But there are some problems with event-driven frameworks.

The biggest for us is that Unity doesn't serialize event subscriptions or delegate assignations, so saving and loading and transferring them over a network is nightmarish. The biggest problem for everyone else is that events are hard to debug, hard to keep track of - too many asynchronous pieces moving in every direction.

So most games don't expose much event-driven stuff, and mods have to break in. This results in things like Skyrim's dreaded "script cancer", where scripts run every frame to check all the relevant stats and states, bogging things down to a crawl. Events would allow you to do this more fluidly, attaching your functions to events like "onStrengthChanged" or "onEquippedItem" or whatever. But... it doesn't serialize and it's hard to debug.

We can tackle both of these issues at once if we create an event handling framework that can serialize and deserialize properly. The framework can easily keep track of who is signed up for what and make it easy to debug and even visualize in real time.

It's annoying to have to build this kind of framework when the C# event/delegate framework works almost well enough on its own, but on the plus side it lets us integrate into Unity and our specific project much more firmly. For example, press F12 and you can see the rays of connectivity between watchers and watchees.

Fluid time benefits from this due to the large number of interconnected moving pieces. A new farmer, or a storm, or a mod that introduces crop blight - they can all interact with the "food" fluid time system and run in a very low-maintenance, cheap way.

Press F12, and you can see what mods are affecting the crop totals, what mods are waiting for food production to reach certain levels, what mods are reflecting food values in some ways, when the current formula is due to be obsolete, what is making it obsolete...

Anyway, soon I'll write a post about how we can use this to make moddable NPCs, but this is long enough as it is. A hint: it involves scripting in the lexical sense!