Open mreinstein opened 2 years ago
I don't have concrete proof, but I think it might also be the case that the not filter !
may not work correctly with the added
and removed
listeners.
There are a few solutions that come to mind:
ECS.cleanup
)Another foot gun: added
and removed
events fire when an entity matches the filter, not just when the entities themselves are added/removed from the world. An example:
const a = ECS.createEntity(world)
ECS.addComponentToEntity(world, a, 'bullet')
ECS.addComponentToEntity(world, a, 'burning')
const r = ECS.getEntities(world, [ 'bullet', 'burning' ], 'added') // works as expected, r.length === 1
This worked as you might expect. Another example, less obvious:
const a = ECS.createEntity(world)
ECS.addComponentToEntity(world, a, 'bullet')
const r = ECS.getEntities(world, [ 'bullet', 'burning' ], 'added') // works as expected, r.length === 0
⋮
// 2 game frames later:
ECS.addComponentToEntity(world, a, 'burning')
const r = ECS.getEntities(world, [ 'bullet', 'burning' ], 'added') // r.length === 1
This last example is confusing, because people might assume 'added'
means the entity was added to the world, but the way it actually works is 'added'
means it started matching the provided query filter ([bullet, burning]
). So even though the entity has been in the game world for a few frames and isn't added, it gets marked as added as soon as it picks up the burning
class.
The same confusion exists with removed
events too.
My gut feeling is this behavior should change, to only track entities that were added/removed from the game, rather than attempting to track when they match the provided filter. I suspect most people just want to know when entities are added/removed rather than when their components match/unmatch the filter.
Perhaps you could add system hooks for entityAdded/entityRemoved/componentAdded/componentRemoved
yeah, not a bad idea
I probably should have split this issue into several because there are several problems in here all related to the add/remove events. I think probably the most serious one is that remove
and add
events could get missed depending on system order.
I've split part of this out into #35
The solution I've come up with is to do ECS.cleanup(world)
after each call to fixedStep:
function gameLoop () {
⋮
while (accumulator >= FIXED_STEP_MS) {
accumulator -= FIXED_STEP_MS
ECS.fixedUpdate(world, FIXED_STEP_MS)
ECS.cleanup(world)
}
ECS.update(world, frameTime)
ECS.postUpdate(world, frameTime)
requestAnimationFrame(gameLoop)
}
And I handle the add/remove event only within the fixedUpdate
calls.
I don't know of a way around this presently. Perhaps this should go into a guidance section in the docs?
Here's an example of how one might shoot themselves in the foot with this. Let's say you have a system that handles moving and colliding bullets against entities, and also needs to cleanup bullets in some way:
Let's say your main game loop looks something like this:
the fixed step runs at 120fps. If this game is running at 60 frames per second on a typical monitor, the fixed update steps will run twice for each call to
gameLoop
. When a bullet is removed from the ECS world, the fixed update step in the bullet system will run twice and see the same removed bullet entity each time. This is because theECS.cleanup
call runs at the end of eachgameLoop
, NOT after each fixed step.