Component Design in Component Based Game Engine

Started by
31 comments, last by phiwer 10 years, 11 months ago

If the capacity changes as a result of construction being completed, then yes your construction system would increase the capacity. If the capacity goes down because the building is run-down and ill-maintained, let's say, then the "run-down" system would change the capacity. The BuildingComponent could then store the various various capacities, or multipiers (however you want that to work), so the different systems know how the building (namely its capacity) should change in these circumstances.

I'd be wary of having multiple systems modifying the same property of a Component. Instead, I would have one system that is responsible for setting the current population capacity based on various states (e.g. whether a building is in construction, how long has been since it was maintained, etc...).

Of course this doesn't necessarily mean you have a construction system, a run-down system (to track how long it's been since it was maintained), a population-setting system, etc.... They could all be different stages of the Update cycle of the same "building" system (assuming they all rely on the same set of components).

In some cases I have difficulty seeing this fall through 100% though. Say you have a money component, then probably many systems would affect this component, or is there any way of handling this?

Advertisement

Well, I'm assuming population capacity is something that can be expressed as an equation based on other variables. So there should only be one place in code that evaluates it.

You're right that money is different. Generally you would have one time events that subtract or add an amount to this - so there is no equation that gives you the current amount of money. So yes, my claim that multiple systems shouldn't modify the same property of a Component isn't correct. It really depends on what that property expresses. For something like Money, I might make that only accessible through methods (e.g. GetMoney(), AddMoney(), SubtractMoney()).

Each building would also have bonuses that would take effect when the building is completed. Say a building increases attacking effect by x%, then some AttackSubSystem would need this information when a player attacks. But the building's bonus does not go into effect until it is completed. How does one model this state change best, while still maintaining high cohesion in the AttackSubSystem (not having to know too much about the building's logic)? I have two options here that spring to mind: 1) The AttackComponent has two columns, one for active value, and one for "desired/aim/target"-value. The subsystem always uses the active value, while the BuildingSubSystem copies target value to active value when the building is completed. 2) Another AttackModifierComponent is introduced: AttackModifiedInProgressComponent. This component would be connected to the building when first constructed and then later converted to an AttackModifierComponent.

Option 2 looks really ugly and adds complexity IMO. Any other options?

Here are my thoughts:

So, AttackSystem needs to know the multiplier. But we don't want AttackSystem to know about buildings.

I think it's reasonable that there could be an AttackMultiplier component. When we want to figure out the overall attack multiplier, we enumerate all AttackMultipliers for entities associated with that player, and combine them together (the AttackSystem could do this once per cycle). AttackMultipliers don't need to be associated with buildings of course, they could be attached to any entity.

The question now becomes when does the AttackMultiplier component get added to buildings? Seems like the BuildingSystem code could have some logic that does this when construction is completed. Or better yet, buildings always have an AttackMultiplier component. The BuildingSystem (it's ok that the BuidlingSystem knows about AttackMultiplier - someone needs to) checks the current state of the building (constructed/rundown/etc) and assigns an appropriate value to AttackMultiplier (this could be 0% if the building isn't constructed, for instance).

The AttackMultiplier component could be generalized to BonusMultiplier component, and contain other types of bonuses. Then you might actually consider a BonusCalculationSystem that enumerates all the BonusMultiplier components each cycle, and adds them up for the final tally. Other systems would request this info from the BonusCalculationSystem.

Well, I'm assuming population capacity is something that can be expressed as an equation based on other variables. So there should only be one place in code that evaluates it.

You're right that money is different. Generally you would have one time events that subtract or add an amount to this - so there is no equation that gives you the current amount of money. So yes, my claim that multiple systems shouldn't modify the same property of a Component isn't correct. It really depends on what that property expresses. For something like Money, I might make that only accessible through methods (e.g. GetMoney(), AddMoney(), SubtractMoney()).

Each building would also have bonuses that would take effect when the building is completed. Say a building increases attacking effect by x%, then some AttackSubSystem would need this information when a player attacks. But the building's bonus does not go into effect until it is completed. How does one model this state change best, while still maintaining high cohesion in the AttackSubSystem (not having to know too much about the building's logic)? I have two options here that spring to mind: 1) The AttackComponent has two columns, one for active value, and one for "desired/aim/target"-value. The subsystem always uses the active value, while the BuildingSubSystem copies target value to active value when the building is completed. 2) Another AttackModifierComponent is introduced: AttackModifiedInProgressComponent. This component would be connected to the building when first constructed and then later converted to an AttackModifierComponent.

Option 2 looks really ugly and adds complexity IMO. Any other options?

Here are my thoughts:

So, AttackSystem needs to know the multiplier. But we don't want AttackSystem to know about buildings.

I think it's reasonable that there could be an AttackMultiplier component. When we want to figure out the overall attack multiplier, we enumerate all AttackMultipliers for entities associated with that player, and combine them together (the AttackSystem could do this once per cycle). AttackMultipliers don't need to be associated with buildings of course, they could be attached to any entity.

The question now becomes when does the AttackMultiplier component get added to buildings? Seems like the BuildingSystem code could have some logic that does this when construction is completed. Or better yet, buildings always have an AttackMultiplier component. The BuildingSystem (it's ok that the BuidlingSystem knows about AttackMultiplier - someone needs to) checks the current state of the building (constructed/rundown/etc) and assigns an appropriate value to AttackMultiplier (this could be 0% if the building isn't constructed, for instance).

The AttackMultiplier component could be generalized to BonusMultiplier component, and contain other types of bonuses. Then you might actually consider a BonusCalculationSystem that enumerates all the BonusMultiplier components each cycle, and adds them up for the final tally. Other systems would request this info from the BonusCalculationSystem.

I think your last paragraph is the best option! If the building is not yet completed, then no bonus should exist at all. Representing them as 0:s in the model would add complexity when doing the actual calculations: Say you'd have a spell which would cancel a player's attack modifier, then you'd want a row in there where a zero actually meant "cancel all attacker modifiers", and then this would conflict with having a non-finished building's bonuses also represented by a zero.

I'd like some advice on naming my components as well. If a unit is able to be trained, is it prefable to name that component TrainableComponent, or CanBeTrainedComponent? Any pros cons with the naming scheme?

I'd be wary of having multiple systems modifying the same property of a Component. Instead, I would have one system that is responsible for setting the current population capacity based on various states (e.g. whether a building is in construction, how long has been since it was maintained, etc...).

Of course this doesn't necessarily mean you have a construction system, a run-down system (to track how long it's been since it was maintained), a population-setting system, etc.... They could all be different stages of the Update cycle of the same "building" system (assuming they all rely on the same set of components).

I wouldn't solve the issue of multiple systems modifying the same component value at the system design level, by only allowing one system access. One of the main reasons you completely isolate data and logic in this component design is to introduce a level of abstraction that allows the code structure to be independent from the data structure. However only letting one system modify a value couples the two structures together again and you lose the benefit of that abstraction.

Rather, I would solve the problem at the data level and use an approach that lets multiple systems modify a value in a well-defined way. You already touched upon such an approach with the various Modifier and Multiplier components. Generally speaking, you allow values to be backed by formulas, like x * A, (x + A) * B, etc., and allow systems to modify the inputs (A, B), whereas the constants (x) are sensible defaults or initial values. The formulas can have an arbitrary number of constants and inputs, and can vary per value depending on how you want systems to influence them. You don't necessarily have to implement such a system in the context of the component design, with individual components representing the various inputs and whatnot, since that seems as though it would get heavy with components for every input of every formula, and it's not really "game" code per se... however that's neither here nor there. The point is that systems aren't modifying raw values, but rather adjusting them in the context of modifiers/multipiers/bonues/etc., and you aren't gatekeeping them behind single systems. Best of both worlds.

Total amount would be the number of buildings of type x contained in this entity. I don't want a separate row for each type x building, but instead group them into a column (total_amount) instead.

An entity is a single building instance. What you're describing sounds like an entity to manage buildings, which could be the land entity (with a LandComponent) you mention later. However any given building instance wouldn't store the amount of its type. It would be redundant information on each building.

I'd be wary of having multiple systems modifying the same property of a Component. Instead, I would have one system that is responsible for setting the current population capacity based on various states (e.g. whether a building is in construction, how long has been since it was maintained, etc...).

Of course this doesn't necessarily mean you have a construction system, a run-down system (to track how long it's been since it was maintained), a population-setting system, etc.... They could all be different stages of the Update cycle of the same "building" system (assuming they all rely on the same set of components).

I wouldn't solve the issue of multiple systems modifying the same component value at the system design level, by only allowing one system access. One of the main reasons you completely isolate data and logic in this component design is to introduce a level of abstraction that allows the code structure to be independent from the data structure. However only letting one system modify a value couples the two structures together again and you lose the benefit of that abstraction.

Rather, I would solve the problem at the data level and use an approach that lets multiple systems modify a value in a well-defined way. You already touched upon such an approach with the various Modifier and Multiplier components. Generally speaking, you allow values to be backed by formulas, like x * A, (x + A) * B, etc., and allow systems to modify the inputs (A, B), whereas the constants (x) are sensible defaults or initial values. The formulas can have an arbitrary number of constants and inputs, and can vary per value depending on how you want systems to influence them. You don't necessarily have to implement such a system in the context of the component design, with individual components representing the various inputs and whatnot, since that seems as though it would get heavy with components for every input of every formula, and it's not really "game" code per se... however that's neither here nor there. The point is that systems aren't modifying raw values, but rather adjusting them in the context of modifiers/multipiers/bonues/etc., and you aren't gatekeeping them behind single systems. Best of both worlds.

Should the systems be responsible for state logic (hard coded), or would this better be put into the data section of a component based system? One situation is when a LandComponent is not yet finished constructing a building. During this time the LandComponent should have a different set of attributes compared to when it's finished.

To me it seems as if there are two ways here:

1) Predefine/hard-code the known states of Land (Developed, Constructing, Empty), and then let the SubSystem shift values depending on current state.

2) Define states in the data, and let the LandComponentSystem implement a state machine.

Any other way of doing this? Comments regarding my two suggestions?

Total amount would be the number of buildings of type x contained in this entity. I don't want a separate row for each type x building, but instead group them into a column (total_amount) instead.

An entity is a single building instance. What you're describing sounds like an entity to manage buildings, which could be the land entity (with a LandComponent) you mention later. However any given building instance wouldn't store the amount of its type. It would be redundant information on each building.

Yes ofcourse. I forgot about the LandComponent when first posting, and like you say a building is an entity connected to a LandComponent.

Should the systems be responsible for state logic (hard coded), or would this better be put into the data section of a component based system? One situation is when a LandComponent is not yet finished constructing a building. During this time the LandComponent should have a different set of attributes compared to when it's finished.

To me it seems as if there are two ways here:

1) Predefine/hard-code the known states of Land (Developed, Constructing, Empty), and then let the SubSystem shift values depending on current state.

2) Define states in the data, and let the LandComponentSystem implement a state machine.

Any other way of doing this? Comments regarding my two suggestions?

If any of the states confer some sort of special logic in the LandComponent that only applies to that particular state, then they'd have to be hard-coded somewhere in order for the component's code to act on that state (option 1). Otherwise you'd need to make the logic data-driven as well, i.e. a scripting system of some sort, but that's another subject altogether.

The exception is if the logic of each state follows some similar form that can be coded in a generic way, i.e. each state just corresponds to a population capacity modifier, and the logic to transition states isn't specific to the current state. Then the states can be purely data because they're not tightly coupled to state-specific logic, and you can write a single piece of logic that knows how to set the population capacity modifier and transition states (basically, option 2).

Should the systems be responsible for state logic (hard coded), or would this better be put into the data section of a component based system? One situation is when a LandComponent is not yet finished constructing a building. During this time the LandComponent should have a different set of attributes compared to when it's finished.

To me it seems as if there are two ways here:

1) Predefine/hard-code the known states of Land (Developed, Constructing, Empty), and then let the SubSystem shift values depending on current state.

2) Define states in the data, and let the LandComponentSystem implement a state machine.

Any other way of doing this? Comments regarding my two suggestions?

If any of the states confer some sort of special logic in the LandComponent that only applies to that particular state, then they'd have to be hard-coded somewhere in order for the component's code to act on that state (option 1). Otherwise you'd need to make the logic data-driven as well, i.e. a scripting system of some sort, but that's another subject altogether.

The exception is if the logic of each state follows some similar form that can be coded in a generic way, i.e. each state just corresponds to a population capacity modifier, and the logic to transition states isn't specific to the current state. Then the states can be purely data because they're not tightly coupled to state-specific logic, and you can write a single piece of logic that knows how to set the population capacity modifier and transition states (basically, option 2).

True! I'll have to do futher analysis of this. As a first resort I think I'll go with option 1.

I've now started developing the component engine, and one question has come up. Although I don't want to do premature optimization I feel it's worthy of mentioning. I currently have a component called ChildEntityComponent, which is attached to entities that are children of some entity (e.g. Player has a lot of land). I've also developed a SubSystem for this component which many other subsystems will use, and as such it's important that it does fast lookup.

Some of the functions on this subsystem include: IsRoot, GetChildren, GetSiblings, GetParentEntity, GetRootEntity.

Right now there is only one way of traversing the relationship between entities (child to parent), so the GetChildren operation is expensive.

Does anyone have any good ideas for fast lookup of these operations? One idea I have is to let the ChildEntitySubSystem listen on events from the ChildEntityComponents and update a data structure for fast lookup for entities connected to one another. This encapsulates the knowledge of relationships between entities to the subsystem while still maintaining the overall component design (of parents not knowing directly about their children).

Any other suggestions?

I've got a Children component that can be attached to an entity, and which contains a list of the child entities' ids.

In addition, each entity has an owner id. This isn't in a component, it's actually part of the Entity itself.

So the parent-child relationship is expressed in both directions. There is a system that takes care of keeping these in sync. Basically all add/remove requests go through the system.

I'd be interested to hear how others solve this.

I've got a Children component that can be attached to an entity, and which contains a list of the child entities' ids.

What persistence mechanism do you use when saving this component with lists of entities? NoSQL?

In addition, each entity has an owner id. This isn't in a component, it's actually part of the Entity itself.

So the parent-child relationship is expressed in both directions. There is a system that takes care of keeping these in sync. Basically all add/remove requests go through the system.

I'd be interested to hear how others solve this.

Hmm. Given that you have the parent/owner on the entity itself, why not store the children there too?

What persistence mechanism do you use when saving this component with lists of entities? NoSQL?

Each Component type knows how to serialize itself to/from a stream. So it just stores the number of children, and then their persistent ids.

Hmm. Given that you have the parent/owner on the entity itself, why not store the children there too?

Good question. The reason is that every entity has an owner, but very few have children.

Also, I was simplifying above - there are actually different classes of children. An entity can have transform children (such as a fire particle system attached to an entity for the torch model) or inventory children (that torch is now owned by the player, say). I re-use the same ownerId in either case. I'm not saying this is a good system (my framework is still very much a work in progress). In fact, it seems kind of flawed since an entity then can't be a member of both groups (although that never happens anyway in my scenarios).

I also should explain "every entity has an owner". I actually partition my id-space into two regions. One is for entities, and one is for top level "regions" of the game. So "top-level" entities are actually owned by a particular region. This is to support an open world, where regions (and all their child entities) are dehydrated and re-hydrated as the player moves about the world. (I could probably model this as a region entity that acts as an owner though, rather than artificially partitioning my id-space).

Anyway, I wonder if it would be useful to try to formalize a general group ownership mechanism. What type of ownership is there? Ones I can think of:

- An entity can be owned by a region of the world (let's call this a "persistence group")

- An entity can be attached physically to another (transform)

- An entity can belong to a player entity (e.g. this building belongs to 'x')

- An entity can be in a player's inventory

- ???

This topic is closed to new replies.

Advertisement