Now, after the time left is 0 a set of actions can happen. Removal of components, setting new components, creating/deleting entities for example.
Unfortunately i cannot see a clear way of how to store/implement what actions should be executed?
It seems the clean way would be add a TimeElapesd component, but that would require a large set of EntitySets just to keep waiting until the timer reached his end.
Is this the way to go? Adding logic to a component feels wrong. Like a
For similar cases where more than pure ttl data is used, I actually use a timedEventQueue logic. (that is executed centrally by a system)
Upone time reaching it just fires an Event containing the id. So basically it just centralizes the time check logic, how the event is processed is than a matter for the subscribed systems.
A character casts a spell that requires cooldown time. After cooldown is over i would like to set the SpellCooldownReady flag.
The spell itself spawn an area of effect that increases weapon damage by 10% and stays for 10 seconds. After the time is over i want to delete the AOE entity.
Now it seems that i need an additional entityset processing routine for each action that should be executed. In the above example, one that sets the SpellCooldownReady flag, and one deleting entities such as the AOE or DamageOverTime âeffectsâ.
Only speaking about gamelogic, it has absolutely nothing to do with visualisation
This part seems unnecessary to me. The spell is ready again at a certain time and that is all thatâs needed to be tracked, right? Much less updating going on in that case.
The 'delete the AOE entity" is the classic case. I use a Decay component for this. Itâs only job is to let the DecaySystem delete entities when their time is up.
I have yet to run across other time-to-live use-cases which is why I asked. Mostly, things get created. Things get deleted.
The spell casting object is not yet working. Beside the cooldown stuff i am missing another important part:
//Generic
Component Interactible{
}
//When a interactible gets triggered player adds a
Component InteractsWith{
EntityId actor;
}
//A door for example is defined as
Component Door{
boolean isOpen;
}
Component CollisionData{
}
Component Interactible{
}
//The system that control the door then simply can react to requested interaction
However i cannot find a way to do the same thing for spells, since it is not possible to know what actual spell-data should get created. (Unless processing a EntitySet for each available spell) That would seem like a lot of repeated code, which should not be necessary, or am i wrong?
Iâm sorry if Iâm being dense but Iâm still not understanding something about what a spell is.
To me a spell is an instance thing. It creates an effect in the game that may last a long time but thatâs entirely up to the spell⌠which is code. Game logic. The exploding fireball spell may create a âthis is all on fireâ area of effect. It may also set something on the player (or one of its entities) that says the player canât cast it again for a while. These are all separate things.
So, for example, letâs say you have a spell entity that is attached to the player. Essentially itâs there just like any other tool and has some âuseâ logic associated with it. When the player invokes âuseâ (could be Java code, could be a script) that âuseâ code adds some entities to the world. Some of those entities may have a âtime to liveâ. The spell is essentially done now, though.
As part of its âuseâ logic, it may set a LastUsedAt component or something that itâs âuseâ logic could check to see if itâs ready to be used again. (Spell icon displays could also use it to gray out the icon or something.)
Gamewise, I see no real difference between tools and spells and skills and so on. They will all share essentially the same working components.
I think maybe your mistake is trying to use components to do the actual âinteractionâ logic. Thatâs game logic and not really ES stuff. Some things actually have to run code.
You are right about the spell, it can be anythingâŚ
I was thinking the last hour about the problem, and it seems one of my biggest problem was that i wanted to create the whole required component set in the same piece of code. But it turns out that if i split up the creation, it allows to reuse each piece of code for various effectsâŚ
Have to implement some features to see the next limitations
I really enjoy these discussions, by the way. They either tickle some new area I hadnât thought of or re-enforce patterns of things that turn out to be more straight forward once unraveled.
Plus, I will hit 99% of these same design choices myself.
Careful with the dark side of the granularity. In my implementation, Iâve splitted components and processors so much that there is no processor that take more than two pages of code, and most likely only one. I discovered it was too much and now, itâs a mess. I have to merge things on.
But I still think itâs a good idea to get into a lot of splitting, and then come back. This way you understand better what you really need at the end.
Yes, splitting is always the problems for a Component-based architecture.
I think the âspellâ along with âintervalâ problem are some how a gotcha which cause head-each to everyone use ES, itâs there and will suddenly happen. So again, how i translate the problem here is the need of an Event system that can play well with Entity processing system.
Locally, the Interval or the TimeLeft component of an Entity tell how much (no suprise) timeLeft for that Entity to be process by one âActionSystemâ or âSpellSystemâ or âCoolDownSystemâ. I say locally because the Entity shouldnât take that info personally, but should also let those System who care about it know about info. Or the system should aware of that info⌠The system that need this characteristic can be implemented by an Event-base system with an interval. Down this road, there is no free thread interchangable states! So you have to decide what system are in this category and carefully build it. I build mine but using RxJava!
ThreadExecutorForEntitySystemTimer{
// Build your working threads here
// Let RxJava take care of rigging the threads and pub-sub between theme
// I use Thead and also Actor for some case, because our system involve transaction of money and commercial stuffs that already built in Actors.
}
SpellEventSystem implements EventIntervalSystem {
// Bunch of observables that represent infos and bananas
// And the intervals
}
So the main part is to write the appropriate schedulers for your core systems that run those EntitySystem⌠I know the different mind-set between each Java developer when come to solve concurrent problem. Iâm in group of developers who prefer to use libraries instead of writing one. I just go kick some shit up with RxJava to test out a lot of stragegy which resulted in best performance and easier to understand. I donât freaking try to design first
@methusalah
I love how clean ES make a big code base revolute to after migrating to ES. A bunch of tangled lines become very long long straigh lines. And hey, we should also have tools for the job: creating, grouping and spliting Component in the name of Java language, hope that the Editor of @methusalah will become one editor like that. Remember I once told you in one topic about my BluePrint system I did for my Editor Not really open source but I told you the idea behind it.
The thing about splitting is that it should be for the right reasons. In general, DRY (donât repeat yourself) is not a good reason on its own⌠though itâs one that gets software developers in trouble in lots of ways.
To me, itâs important to remember the main features of ES as your driving motivators. The main one I keep coming back to in design is: easily add new âsystemsâ without affecting other systems.
This implies a lot of things. One, your systems should be as independent as possible. If you require a lot of interaction between 3 different systems and itâs not in the form of waterfall side-effects then chances are that those arenât really different systems. Whenever I see threads about âSome event framework to let my systems communicateâ I always want to drill into that because itâs a âbad smellâ from an ES perspective. Likely, those are one system that has been erroneously split out of some non-ES pseudo-need.
Also, I like to think of the entity systems as managing the state (and low level rules) of the world⌠not necessarily the game logic. The fact that there is a fire and itâs burning everyone standing in it is part of the game world state and low level game world rules. That casting a fireball level 8 spell created this fire, deducted mana, took spell ingredients, etc⌠thatâs game logic. Thatâs the part you code in more traditional ways.
So maybe letâs tease out the spell example a little bit and see
Letâs say we have a player entity and some kind of âtool/spell/usable thingâ entities attached to that player. What might they look like? Iâm going to spitball for a second so hopefully I donât design myself into a corner. (Iâm going to use a sort of pseudo-groovy style for representing components to try to be clear without having to define them all.)
This can be used to present a list of âthings the player can doâ to the player⌠either as icons or whatever. Itâs up to the game logic to decide what to do when itâs enabled and executed. Thatâs a UI level system and a player-actor level system depending on whether single or multiplayer.
In this case, letâs pretend that the fireball spell is an instant effect and just plops a fire entity down somewhere.
fire = [Position(spot on the ground), Range(some radius), Damage(type:Fire, power:10)]
We have a few options here. We could have a special system that watched just position + range and created linking entities between them. The the damage system could watch for these⌠but itâs an extra level of indirection that feeds our sense of DRY without actually feeding the needs of the ES.
Instead, we could have a AOE damage system that keeps track of two entity sets:
entities with Position, HitPoints⌠no reason to apply damage to things that canât be damaged.
entities with Position, Range, Damage
The system does all of the work of either spatial indexing or not based on the scale of the game. Letâs pretend weâll only ever have a few hundred AOEs at a time so we can just track them in the standard way.
Whenever something from set (1) moves we see if itâs entered or exited one of our AOEs. If it (say the player) enters, we attach a Damage entity to it:
fireDamange = [TargetTo(target:player), Damage(type:Fire, power:10)]
If any of the entities in the second set are removed from the set then we remove any damage objects weâve attached to entities in that AOE. (Or set them to decay.) This is the same as if the entity exited the AOE.
So if an entity disappears from set (1) or leaves the range of an entity in set (2) or is in the range of something in that is removed from set(2)⌠we add a decay component to the damage entity we created before.
fireDamage += Decay(0.1) or whatever⌠or we just delete the entity.
Some other damage applier system is responsible for taking all damage entities and applying them to object hitpoints. The principle here is that we only want one thing modifying hitpoints⌠so all hitpoint adjustments whether positive or negative should be attached to the player as either damage or healing entities. So the HealthDamageSystem or whatever name has at least one entity set so far in this discussion (health application is left as an exercise for the reader)
entities with TargetTo and Damage.
At an interval that the game decides (probably not once a frame but maybe) the system loops over all of its entities, grabs the entity specified in TargetTo, and applies the damage. At this time, it can keep track of any buffs the player might have, etc. against fire damage, magic damage, immunity to heat, whatever. Every interval it will keep applying damage until the TargetTo, Damage entity has disappeared from its set.
Here we have represented most of the game-level ârulesâ for AOE spells. Lots of other systems could be added that deal with visualization and sound. All of the information is there for them. They just have to watch the proper entity sets.
Furthermore, we can damage any object in any way we choose just by attaching a damage entity to it. We donât have to worry about how buffs are handled, whether healing is applied before damage, etc⌠there is some system that has already codified that set of world rules.
If we want to add a poison gas AOE then we add another damage type. Maybe poison gas also decreases visibility or something and thatâs a brand new feature that can now be easily added without touching any of the existing systems. Just have a system that watches for Range and Damage of type:PoisonGas.
This approach also nicely damages any object that can be damaged. If wooden chairs and tables are in the fire AOE then they can be burned if they have hitpoints. NPCs, MOBs, whatever all handled.
âWhat about spell cool down?â (the original issue)
This could be done in two ways⌠either add another AvailableAtTime() component or create a whole other âentity attached to the spellâ that can be part of a system that watches those entities and enables/disables the target entity based on cool down time. The first is simpler⌠always go with simpler first.
âWhat about NPCs? Do I have to keep an active spell entity set per NPC?â
Nope. Part of AI (which will be a big system on its own) will be first setting up the world state for the AI âbrainsâ at the beginning of every AI interval. So you can run through ALL of the enabled spell entities in the whole game and simply mark them as usable in the local memory for that AI entity. This is all internal to the AI system and is part of any of the other half-dozen world state gathering entity sets you might have for the AI actors. Else for a relatively low number of actors, you could just query it when needed.
Ok⌠thatâs enough of my rambling. Iâm not going to check the spelling or coherence because Iâve already wasted one can of soda on this.
Well I have actually at least one case where I frequently need events:
Some system tries to send a status to the spaceship computers:
â Generater just used a fuelcell
->Ammo empty for turret 00432 Ballistic SizeS
As the computer touches very many things, I opted against it polling the state itself, as this would create excessive state checking load, while the event is basically free, as I have the logic at that place anyway.
Sure, I too have events. I even have an event bus for such purposes. (For example, in Mythruna a block change is an event.)
âŚbut these general exist entirely outside the entity system. ie: not a system â system event. That would be strange as the components are generally already enough of an âeventâ.
My damage system works exaclty as you described beside that it still holds a EnumMap<DamageType,Float> since some spells might deal physical and fire damage at the same time. (Might change that if it turns out to be bad)
It seems i have it running by now, well i will know it hopefully soon, when i added a more complex spell with influencing AOE effects. But at least in my mind it is working already
Any general hints left?
Is it âallowedâ to use EntityData.getComponent(Id,Class) for some checks, or is that also a sign of bad design. For example
Is it a âbad smellâ for one ES Implementation that have event processing along with it?
As you said, Entity is basicly enough to represent Event and Component also can represent a lot of kind of Event.
I also do that âproper wayâ in games and donât âneedâ to communicate a lot between things.
For those who âneedâ communicate between âsystemâ because âsystemâ can be anything and be designed by a lot of people who donât really read an ES cookbook first⌠or because they think itâs simplier to code that way; or simplier to adapt with editors, or to explain to non-developer that concept⌠well, i only have one choice that to do what ever work with less effort for the whole team.
In our game there is not too much systems but a lot of types of components. We have to limit those types to below 300. Because having too much types will make a team with more then ten devs lost: donât know to find what Component, to do what with this System. The entities are composed with few ways but usually a Prefab somewhere and we just drag it in the level. Some entity procedure signal (they have Signal component), some receive signal (like AI stuff). Here are some stories Iâve meet over years, and it not only related to Java ES:
Our designer once mistake to add an a Signal component to a Rock Prefab instead of a LightSwitch Prefab, we have a lot of Signal what didnât mean anything and drive our event processing crazy. AI depends on signals, result as they gets a lot of them and go very slow when it come close to specific area.
Another example is formation of troops, a bunch of entity have Signal that they get hurt and need to retreat personally. As we design one system to add âGetHurtComponentâ when a character get attacked, one system that need to decide 50% of the troops are hurt to retreat the whole army. But itâs always lost in counting, because the signals fly in different threads with no overall order⌠Thatâs freaking hard to debug to find out why, and our team later decide that composing signal and trigger as Component in level design is way to hard to control.
So we build an (no suprise) EventSystem that we can: design, test, throw away, control it ⌠and of course it can corporate with Entity and Component. Like 4 year ago, itâs started with concepts like Scope, Actor, Event, Message, Zone to wrap around entities and spatial infos. Much later after playing with ES a lot and using Java day by day I find out a way to compose âObservableâ like what the Rx pattern suggest. And itâs translate directly into language of the tools, visually. The team can understand it. They know that component donât need to be split because they procedure different smell (signal, event, message, whatever) with different interval and order⌠they just need to be add in Entity that subcribe to specific routine (so call scheduler system). And with tools, yes, even a non-dev can easily make a complex level with triggers, AIs and entities. Thatâs the story. Beyond that I still believe I have more energy for this topic.
Well, in that vein, just some random things I will say as a purist. Maybe they tickle some more discussion as itâs hard to consult on someoneâs design with only a partial view.
300 components implies about 300 systems. That seems high.
It sounds from your event discussion that you are illustrating my point⌠again hard to say without more specifics. Systems sending other systems events implies that two systems are really one system. I guess if itâs the world sending events to the AI then maybe thatâs ok. If itâs one AI system sending events to another AI system then Iâd question the need to have split those. I generally see AI as one system, personally⌠and then only a system like the player is a system. Just another user of the world.
In the end I guess you have to do what your team supports. Harder still to stay âpureâ if you all are still bumping along the learning curve as you go. I just see a lot of people make things more complicated instead of less⌠and with an ES thatâs sometimes a bad smell, too.