You're on the right track: systems are where the logic resides and where behaviors are implemented. Systems are basically functions where the arguments are components. So if you wanted to create a damage system you need:
- A health component which will be decremented
- A damage component which defines the amount to decrement
Then your system iterates through all entities that have both a damage and health component and decrements the health accordingly.
Once you've done that you'll most likely need some kind of death system. You can implement that either with a system that iterates through all entities that have health and checks if they are at 0 or below, or else by adding a dead
component to entities with 0 health in your damage system and have your death system iterate all entities with a dead
component.
Now there's lot's of ways you can implement a damage/death system in ECS, but the takeaways are that when working in ECS:
- If you want something to happen you need to do it with a system.
- Data exists in components, so when your systems need to do something they will get their parameters from some component (e.g. if you want a damage system that applies different damage amounts you need a damage component to store that data)
- You want to keep systems minimal and focused on very narrow behaviors. To expand on @Philipp's comment, if you have a game that has a variety of mechanics around health (e.g. poison, elemental, healing, etc.) you should resist the urge to make one health system that handles all those cases and break those up into a variety of systems (e.g. a poison system, an elemental damage system, etc.).
NOTE: There are exceptions to the rules above but they are uncommon.
Why Make Systems Small?
@Vaillancourt asked what the rationale is for keeping "systems minimal and focused on very narrow behaviors", so here goes...
At a high level we're all idiosyncratic humans and we should use whatever suits our particular brains best, so if you find lots of files confusing, go ahead and centralize things in larger systems. At the end of the day the point is to build games with a minimum of pain and suffering.
That said ECS architecture is very aggressively tilted towards composition. It's designed with the intent that everything is broken into tiny modules that can be easily plugged together and rearranged, and that philosophy extends to systems as well as components. When first starting out with it I would advise someone to go with the grain of ECS and then start figuring out their own sweet spot for composition.
So as an example imagine we're making an RPG and I code up a health system that handles all the types of damage, healing and death - basically anything that affects health or is derived from it.
The first problem I might run into is performance: there's lots of components associated with this system and most of them won't be present on an entity at any given time (i.e. most entities won't have a heal component, or a damage component on any given frame). Many ECS implementations are optimized for concrete sets of components (e.g. anything with health AND damage) over more abstract queries (e.g anything with health AND/OR damage AND/OR heal, etc.) and there's going to be a lot of conditionals in your system to handle all the cases.
However, you shrug this off - your game simulates small parties fighting not massive battles, so the performance issues are negligible, and you keep plugging along with your full-fat health system.
However, designers being designers you start getting curve balls. First they want to add a spell that prevents damage from poison. Then they add another spell that gives your neighbors a speed boost when you take damage. Then they get weird and decide that whenever the Oneiromancer class takes damage it creates a nightmare token in their inventory. At this point your choice is either to fold half of your game into your health system - which would be madness - or just break it up into tiny little chunks. That way when a designer modifies a mechanic that intersects with health, you can either modify or add a small system, rather than going spelunking through a huge one.
I know that's an extreme example, but in my experience when I bundle up a bunch of stuff into something closed (e.g. a large function or class) I usually have to break it up into it's constituent parts further down the line.