You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I love the new API, thanks for all of the hard work. After implementing it in a test project, I did come across a thorny issue whenever the nature of an entity must change, ex. transitioning from a physics body to an inventory item.
Say we have a scenario where an item is collected and added to a player's inventory. The item initially exists in the scene and has physics components, but after it's added to the inventory, the item should no longer have those components.
import{getRelationTargets}from'bitecs'import{CollectedBy,Has,Targeting}from'@relations.ts'exportfunctioncollectionSystem(world){for(constcollectedofquery(world,[CollectedBy(Wildcard)])){constcollector=getRelationTargets(world,collected,CollectedBy)[0]collect(world,collector,collected)}}functioncollect(world,collector,collected){// Add item to collector's inventory.addComponent(world,collector,set(Has(collected),{amount: collectedAmount+1}))// Stop targeting the item.removeComponent(world,collector,Targeting(collected))// Remove unneeded components from item.removeComponent(world,collected,Target,CollectedBy,Wildcard(Targeting),// Should removing Mesh also remove the inherited RigidBody's // components, or just the ones that Mesh introduces?// Tradeoffs either way.IsA(Mesh),IsA(RigidBody),)// Uh oh... it still has the Position component.console.log(hasComponent(world,collected,Position)// true/* * Current workaround: * removeComponent( world, collected, * IsA( Mesh ), * IsA( RigidBody ), * Target, * CollectedBy, * Wildcard( Targeting ), * Acceleration, * Position, * Velocity, * // ...some other 20 components that need removing, * // with no source of truth on which components need * // to be removed under different conditions. * ) */}
The current workaround is prone to bugs, as individually tracking the components which need removal can be very tedious for systems with hundreds of components. Additionally, you have to remove all prefabs, because I use prefabs to simplify queries and improve performance (instead of querying for every single needed component for physics, for example, I just query IsA( RigidBody )). Tracking all of that manually sounds like a total mess.
I can think of a few other solutions, such as having a BaseItem prefab, then maybe a PhysicsItem and InventoryItem prefab which inherit them, and then removing the original entity and creating a new one to add to the inventory from the InventoryItem prefab.
constitem=addEntity(world)addComponent(world,item,IsA(PhysicsItem),// ...set relevant component data for each kind of item.)removeEntity(item)constnewItem=addEntity(world)addComponent(world,newItem,IsA(InventoryItem),// ...set relevant component data for each kind of item.)
But this would be complicated if I have specific prefabs which inherit from the Item prefab, such as Weapon or something. Would that inherit the BaseItem prefab and require manual, bespoke instantiation of PhysicsItem/InventoryItem each time, or would I need a PhysicsWeapon and InventoryWeapon? I don't think this is the right path.
Either way, while workarounds can be had, there should generally be One Way To Do Something in a framework like an ECS. One of the benefits of an ECS is, after learning how to use one properly, there's rarely a question of how to do something, the Right Way to do it should be immediately obvious.
This doesn't mean sacrificing integral flexibility, a user is still free to use whatever paradigms they want on top of the core ECS, but the ECS semantics should be enough to do anything. Therefore, I think bitECS should provide semantics which make this problem trivial to solve, without needing developers to keep track of individual components in large projects.
Another alternative would be component families, which in the simplest form could just be an array of components, which certainly works with these semantics:
You could perhaps instead have an array of sets or something else like:
constPhysicsComponents=[set(Acceleration,{x: 0,y: 0}),set(Position,{x: 0,y: 0}),set(Velocity{x: 0,y: 0}),]addComponent(world,RigidBody, ...PhysicsComponents)// But it would need support for removeComponent// since it expects components, not set objects.removeComponent(world,RigidBody, ...PhysicsComponents))
A simple wrapper around the set array which behaves differently in addComponent and removeComponent might work, if that isn't too magic. It may look like this:
constPhysicsComponents=addFamily(set(Acceleration,{x: 0,y: 0}),set(Position,{x: 0,y: 0}),set(Velocity{x: 0,y: 0}),)constMeshComponents=addFamily(set(Color,{r: 0,g: 0,b: 0,a: 0}),set(Geometry,DefaultGeometry()),)constRigidBody=addEntity(world)addComponent(world,RigidBody,PhysicsComponents,// Setting/including a component after including the family should just work without issues.// Removing a family later should still remove Position.set(Position,{x: 100,y: 100}))constMesh=addEntity(world)addComponent(world,Mesh,IsA(RigidBody),MeshComponents)constItem=addPrefab(world)addComponent(world,Item,IsA(Mesh))constWeapon=addPrefab(world)addComponent(world,Weapon,IsA(Item))constitem=addEntity(world)addComponent(world,item,IsA(Weapon))// Later...// But should this automatically remove families?removeComponent(world,item,IsA(RigidBody))/* or... */// A wrapper component/relation would be more intentional.removeComponent(world,item,IncludeFamilies(IsA(RigidBody)))/* or... */// More verbose, but still significantly reduces cognitive load in complex systems with hundreds of components.removeComponent(world,item,IsA(RigidBody),PhysicsComponents,/* ...other families */)
Of course, introducing a new primitive increases the learning curve of bitECS, especially clearly differentiating a family vs prefab. And thought needs to be given to how this all works with inheritance.
And of course, we could just have all of our systems check if an item is inventoried or not, but that is a leaky abstraction which spreads to all affected systems, increases coupling and introduces more chances for code to become stale. Not to mention its impact on query performance for games with a large amount of systems.
BTW, I realize Mesh probably shouldn't inherit RigidBody and they should just live side-by-side, but I wanted a toy example that would illustrate the issues around inheritance.
Anyway, I just wanted to get a discussion rolling. Did you already have a solution in mind for this, or are there existing semantics which accomplish this satisfactorily?
The text was updated successfully, but these errors were encountered:
some utils to remove all components is definitely a good idea to add, will get that into 0.4 before release. one for specifically removing components of a prefab is interesting... however, one could also just remove the old entity and create a new one. would that be insufficient for you needs here?
I love the new API, thanks for all of the hard work. After implementing it in a test project, I did come across a thorny issue whenever the nature of an entity must change, ex. transitioning from a physics body to an inventory item.
Say we have a scenario where an item is collected and added to a player's inventory. The item initially exists in the scene and has physics components, but after it's added to the inventory, the item should no longer have those components.
index.ts
collectionSystem.ts
prefabs.ts
The current workaround is prone to bugs, as individually tracking the components which need removal can be very tedious for systems with hundreds of components. Additionally, you have to remove all prefabs, because I use prefabs to simplify queries and improve performance (instead of querying for every single needed component for physics, for example, I just query
IsA( RigidBody )
). Tracking all of that manually sounds like a total mess.I can think of a few other solutions, such as having a
BaseItem
prefab, then maybe aPhysicsItem
andInventoryItem
prefab which inherit them, and then removing the original entity and creating a new one to add to the inventory from the InventoryItem prefab.But this would be complicated if I have specific prefabs which inherit from the
Item
prefab, such asWeapon
or something. Would that inherit theBaseItem
prefab and require manual, bespoke instantiation ofPhysicsItem
/InventoryItem
each time, or would I need aPhysicsWeapon
andInventoryWeapon
? I don't think this is the right path.Either way, while workarounds can be had, there should generally be One Way To Do Something in a framework like an ECS. One of the benefits of an ECS is, after learning how to use one properly, there's rarely a question of how to do something, the Right Way to do it should be immediately obvious.
This doesn't mean sacrificing integral flexibility, a user is still free to use whatever paradigms they want on top of the core ECS, but the ECS semantics should be enough to do anything. Therefore, I think bitECS should provide semantics which make this problem trivial to solve, without needing developers to keep track of individual components in large projects.
Another alternative would be component families, which in the simplest form could just be an array of components, which certainly works with these semantics:
but it doesn't mesh with
set
semantics, so it wouldn't idomatically work with:You could perhaps instead have an array of
sets
or something else like:A simple wrapper around the set array which behaves differently in
addComponent
andremoveComponent
might work, if that isn't too magic. It may look like this:Of course, introducing a new primitive increases the learning curve of bitECS, especially clearly differentiating a family vs prefab. And thought needs to be given to how this all works with inheritance.
And of course, we could just have all of our systems check if an item is inventoried or not, but that is a leaky abstraction which spreads to all affected systems, increases coupling and introduces more chances for code to become stale. Not to mention its impact on query performance for games with a large amount of systems.
BTW, I realize
Mesh
probably shouldn't inheritRigidBody
and they should just live side-by-side, but I wanted a toy example that would illustrate the issues around inheritance.Anyway, I just wanted to get a discussion rolling. Did you already have a solution in mind for this, or are there existing semantics which accomplish this satisfactorily?
The text was updated successfully, but these errors were encountered: