I’m new to DOTS/ECS, but had to build something that has a similar logic. I’d love to hear the flaws in my thinking below - but I’m trying to stick with the “the whole snake needs to move” non-trickery thinking as that is what I had to deal with. It also makes it more flexible (eg: different body part types won’t work with flipping tail to head, etc).
I’d make each snake-body entity have a reference to its ‘parent’ entity, its current position, and the position it wanted to move to (along with rendering stuff, and its ID, and from below, a reference to the player’s data). The movement would have two systems - first system just copies the location of the parent’s position into the “want move to”. Complete that system, then run the second system to set the new position based on its ‘want-to-move-to-position’. That might not be the actual solution for a snake game, but it’s very ECS friendly. In my case, it was a tile map of 250,000 doing 4 neighbor equivalent operations in sequence - about 20ms. Only ever writes to itself, etc.
To eat and grow I would avoid thinking of tagging an entity as a “head”. A “head” is one that has no reference to a parent, and player input only operates on the entity that has no head. It’s also only the head that triggers a collision in a way that it “eats” or “dies”. Eat-grow would trigger spawning a snake-body without a parent, and adding a parent component with that reference to the just spawned new head. ie: the current head is no longer a head because it has a parent component, and the new entity is the new head because it does not have a parent component. I’m fuzzy on the exact ease of getting an entity reference from the ECB because I haven’t had to do it, but I believe it’s possible.
For movement and destruction, I’d use the entity/data that already holds player data - it should also hold its snake state. Every snake-body entity would have a reference to its player-owner’s entity/data.
Movement just needs to give the orientation to the current head (it’s a follow the leader situation), so run a job on entities that do not have a parent (“heads”). One way or another, pass in the player input, check collision and set the state. So, essentially, run a collision check on each head, set the state on the player-owner as “move, destroy, or grow”, and set it’s move-to position (or however you handle the movement after checking). Run DestroySnakeCheck, then run GrowOrMoveHead (setting the head’s desired position), and finally MoveAll.
Since every snake-body entity has a reference to it’s “player-owner” and can check its state from there, it’s easy to trigger destruction for all entities in a single pass without the need to cascade. Just pass in player data and run it on every single snake body entity.
There are a lot of ECB calls during the destroy or grow phases, but they should only trigger occasionally, so I see it as acceptable. I like this approach in part because it is super-flexible with no shortcuts. Portals work fine (transport just the head, and everything will follow through a portal), different body parts aren’t an issue, it’s easy to split a snake, it can be grid or open world, works fine with barriers, is somewhat easy to add new states, and velocity changes/etc are easily applied in sequence (without having to consider the impact of uniform application of velocity).
Would love to hear why this is a bad approach though - might help me fix my own problems!