So I have been getting more and more into ECS architecture, and I am having a bit of trouble wrapping my head around how to handle custom actions/callbacks.

For instance, let’s say I want to have a concept of a button in my game. One button opens a door. Another button drains the water out of a room. Another button opens a door, and deactivates all the cameras on this level.

With a data-driven ECS, I am struggling to figure out how one would handle this kind of diverse, specific behavior short of writing a massive enum with a case for each specific action.

My ECS is a pure data-driven one: entities are indexes to components, components are just data, and systems are implicit: they are just anything which operates on entities/components.

  • 2
    \$\begingroup\$ Why would an ECS forbid you from also including a scripting system that executes small one-off scripts like this? \$\endgroup\$ – DMGregory Mar 25 '19 at 12:59
  • \$\begingroup\$ @DMGregory my sticking point is how you would tie these things together in such a system. \$\endgroup\$ – sak Mar 25 '19 at 17:15

I'd recommend solving this in pretty much the usual way: with a scripting language. (This could be a text-based language like LUA, a nodegraph based visual scripting tool, pre-digested bytecode, etc.)

ECS exists to help make the things you do hundreds, thousands, or even more times each frame execute as efficiently as possible.

But one-off level scripts like this are not that kind of thing, almost by definition. You'll usually have only a few dozen to deal with, and many will be event-driven like the button example, rather than updating continually. So it's totally OK that they're not componentized — as long as you're careful not to do anything hideously wasteful in these scripts, they shouldn't be a major drain on your framerate.

I'd recommend having one of your ECS systems be your scripting engine. The data it operates on are virtual machine state vectors, representing the current state of execution of each custom script. When this system updates, it runs your scripting virtual machine to tick these scripts forward a frame, doing whatever custom stuff they want to do.

Your buttons, as part of their data, can specify a script entry point that should be fired when they're interacted with. Your button interaction system processes the interaction, and if it needs to call a new script it logs a message for the scripting system, saying "spin up a new VM state at this entry point and start executing it" (optionally with a few IDs pre-populated on the stack so you can refer to which button was pressed & who pressed it)

The scripting system, when its turn comes in the update order, picks up that message and starts executing the script. If it finishes in one frame it can immediately recycle the VM state for the next script to be called. Otherwise the script can yield to resume later (to control staged transitions over time and the like), and the system can keep its state on the back burner to resume on next frame's update.

This gives you the flexibility to combine high-performance, data-oriented components and systems for the stuff you do uniformly/frequently, with flexible/designer-friendly scripts for the stuff that's more one-off or needs frequent adjustment as your game's design evolves.

If you find you're doing something complex & heavyweight enough or frequently enough in one of these scripts that it is impacting performance, then that's a good candidate to pull it out and make it into its own component(s)/system(s) in native code - something you can do incrementally as needed, as you learn more about your game's performance characteristics.

  • \$\begingroup\$ Is that actually done? I've read a few places that scripting languages are basically no longer used (except maybe visual scripting in UE for example) \$\endgroup\$ – sak Mar 25 '19 at 17:11
  • \$\begingroup\$ I'd be interested in who told you that. Almost every public or proprietary engine I know of offers some form of scripting for tasks like this: whether it's a nodegraph-based scripting language like Unreal's, Snowdrop's, or PlayMaker, text-based like LUA or Ruby in some RPG Maker versions, or an integrated quest/scripted event sequencer tool. All this boils down to effectively the same thing in the runtime: a restricted/sandboxed execution environment that allows highly custom behaviours to be strung together from a controlled set of building blocks to keep it safe & (usually) designer-friendly \$\endgroup\$ – DMGregory Mar 25 '19 at 17:19

It is a simple question of how you handle your scripting, how your ECS arcitecture is designed and so on.

In a date-driven ECS, so only data in your entities without any functions, these functions are 'outsourced' to your systems. Your Entities have a graphics component, so the graphic system works with those components. Same with physics components.

So, if your entity got for example an interaction component, this component could hold a reference to a script file in a dedicated script system. Now, when you interact with that Entity, your gamerule system or whatever gets the information to call the referenced script via the script system.

Now to stick to your examples, your component may need aditional information in addition to the script reference:

  • To Open a door, your corresponding interaction component would also need to hold the reference to the door entity
  • clearing out water of a room would either need the reference to the room, the tiles, maybe the water are some entities themselfs

Of course this highly depends on your arcitectures. Maybe you got an event system, that could call your scripts. Your entities could be created by scripts themself and so on.


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.