Moving a snake the ECS way.

I’m making a snake game to practice ECS. I’m stuck figuring out the ECS way to move the snake.

For the purpose of this thread, ignore the snake can grow.

My current strategy is:

  1. Save current head position.
  2. Move the head (an entity with a HeadTag component).
  3. Take the body part (an entity with BodyTag component) next to the tail (another entity with a TailTag component), save its position and move it where the head was.
  4. Move the tail to wherever the moved body was.

I was thinking on having a snake entity with components that hold references to its spawned head, body parts and tail entities. The body parts array should work like a circular array: each time a body part is needed, the array will provide the entity on index and then add 1 to index or make it 0 when appropriate.

Does this sound too OOP-ish? Any pointers you can give me to make a more ECS friendly implementation?

1 Like

I think simply making it a linked would be an easy solution to the problem.

The head has 2 components, HeadComponent[Tag] and SnakeComponent.
All body parts have 1 component, SnakeComponent.

SnakeComponent has 1 field
Entity Next;

Then just start with head, move it, iterate to next, move it, iterate to next, move it, etc etc until the SnakeComponent Entity field is Entity.Null
No need for arrays.

Personally, I would prefer the array approach with the use of DynamicBuffers that holds all the body parts. It just makes it easier to iterate over. And I don’t think it’s OOP-ish at all.

1 Like

A simple trick: spawn a body per update, and set it’s life time to length - 1.

1 Like

That sounds interesting. That would get rid of the array and TailTag, although for multiplayer I still need a way to know which body parts belongs to each snake so I can set its correct life time. I wonder if a SharedComponentData (something like PlayerNumber) its the right tool for that.

I think I’ll try this approach. Thanks for the tip, @BenzzzX .

As I’ve argued elsewhere, I don’t think ECS models ordered relationships well, so I don’t see any great benefit shoehorning the snake into ECS form. I’d just use an array to model the snake logic, but then perhaps copy it into ecs form for presentation every time it changes.

I would be definitely using buffers, if not just NativeArrays.
Entity per snake.
Then optionally entity per body part, as child.

10000 snakes, or fewer snakes of thousands parts each, would be like knife cutting butter for DOTS.

Looking at it the data oriented way I would see a set of body parts which can move forward and can change direction.

Whenever input happens a change direction command will be added to a point which any entity at that point should process.
now foreach entity you can check if it is at a direction change point or not and if yes then change direction and t then move forward, therwise just move forward.
you can remove a change direction point if a tail entity is passed of it.

so

Head, smake and tail components are needed for identification, a forward component and a few systems:

MoveSystem
InputSystem (which adds direction points)

a collection for the list of direction changes is needed as well.

o speed it up you need to know the relative size of snakes compared to relative size of the list for change direction commands and number of snakes in the game.
you chose a good problem since it is not linear access all the way ad can challenge you. in fact maybe fastest way would be to not represent each snake body part as an entity and instead represent the snake using a single dynamic buffer which a move function works on when you want to move it. this said if the max size of the snake is not known, just due to better memory management having each body part as an entity wins the competition.

there are ways which use more memory and are more linear like having a component per part for the turn that a change is needed and reducing the components value each turn until it reaches 0 and then check the list of changes for our next direction.

@pyramid_seph Btw, what type of snake you are talking?
Snake moving in the grid?
Or snake moving freely, in any direction and angle?
In grid is even simpler to check for collisions.
But considering using physics, with DOTS wouldn’t be actually bad idea I think.

@Ashkan_gc feels like over complicating.
Resizing buffer is not big deal, specially is not expected doing it every frame.
Either way, capacity can be defined at initialization. Like 100, or 1k, or 1mln.

Iterating through parts of body sequentially in array/buffer i probably the easiest thing you can do.
You know that next index (next body part), will be at position of previous index (body part).
Obviously head is always at index 0 and end tail is at last index.
Depending what you want, you can now render body parts at correct positions.

Really don’t get the obsession with using a array/dynamicbuffer as a solution to this problem.
A linked list seems to fit the problem much more cleanly and let’s you implement a lot of other systems in the future much easier such as resizing snake, mid collision, breaking off parts etc

1 Like

Is not obsession, is convince. It does the job. In the end, they were designed for a reason.
Equally could go with any collection, ignore whole entities bit and just use Jobs with Game Objects.
But of course, we loose other benefits of DOTS.
Anything, as long does the job effectively.

Just showing there is multiple ways to same problem.

With entities as reference to next body part, system still need iterate through each entity sequentially, of given snake, to move its parts correctly. So what is difference from an array solution?

If considering snake as an entity, with buffer(s) you can see all related body parts in inspector, via entity debugger as an example.
You can also view, type of body parts, if implemented. Red body part, green body part. Easy to take control over such. Lots of benefits of using buffers.

A Snake moving on a grid… sort off. I’m moving it 1 unit every x seconds, but there does not exist the concept of grid. The snake can only move left, right, up and down.
I was thinking on raycasting on each step over the snake’s movement direction to detect collisions.

A quick update on my progress: It is moving, yay!. I still need an array of body parts, or at least each body part knowing which snake they are part of so they can be skipped by the system that reduces its life time when the snake needs to grow. I guess the later is less dangerous since the lifetime of the snake is supposed to be larger than the body’s. If the snake entity is removed its body parts won’t know that unless I make some kind of system to remove body parts of the soon to be destroyed snake.

Yeah. I thought it would be an easy exercise. I’ve already make a Snake with auto generated obstacle rooms back in the days of J2ME. Maybe I should try something easier on the side… an Asteroid or whatever.

I can see how your approach opens up some gameplay opportunities, like having bomb explosions breaking the snake.
Something bothers me of the linked list/array approach: having long lived “references” (the body parts a body/head/tail is linked to) is OK for ECS?

When snake dies, you just need propagate from head down to tail and call relevant bodies destroy action.
For example using KillSnakeSystem.

There is no issue with that mechanics, weather you chose entity, or array approach.
With entity approach, when you detach, you just need to know, which entity was affected by explosion. For example using sphere cast.
In case of an array approach, you just need know the index of the body part. You also can get via body sphere cast, or any other means. Then use reference to the snake entity as parent entity, and then resize the array / buffer / list, to new size of the snake.

And in either approach, destroy detached body parts.

But either way, you may want to look, how classic snake is designed.

Just for reference some snake game.
http://slither.io/

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!

1 Like

How do you check destroy condition, in presented case? You check body entity every frame, with reference to parent, to check its status? If so, that a bit wasteful approach.

You probably don’t want remove parent reference component at all, since it holds data. This way you can change parent reference using burst. Use head tag component (empty component), to mark where is head. Tag components are treated a bit different way than components with data.

You pass in the player data to the job (all player entities in the game) and check the corresponding state via entity ‘index’ stored in each snake-body. I think you’d end up doing the same with any method though, although it would certainly be faster with the array method (only one entity to check and destroy)? I’m not sure of the overhead for passing the data in each frame - the player data is read only, and I haven’t run into an issue there even passing large bunches of data.

Actually, now that I think about, what’s the method that is recommended to draw the snake to screen in the list/array/etc. method? Does that not require entities for rendering, or is there a trick to that?

You don’t have to remove a parent reference, only add a single parent component in the case where it ‘eats’. That happens only when “eat-grow” is triggered on the head. Although, the tag might be a better system as it accomplishes the same thing… but better! So it’d be… remove head tag, create new entity with head tag, set reference to new entity vs create new entity, add parent tag, set parent tag.

You can approach problem with different angles.

In OOP way, you would simply have lets say GameObject, which represents snake, containing an array, or list of body parts, which contains all positions and reference to GameObject for rendering purpose. You process Move in update, by simply updating consecutive index of the array.
And of course you just update position of the GameObject(s).

If considering each snake as an entity, having dynamic buffer of body parts, you can store positions of each part in similar manner. Then having system which moves each snake, by iterating through snake arrays indexes and offsetting position of body parts.

Regarding rendering, you could update position of snake body entity, from snake buffer. So you have assigned for each index in dynamic buffer, reference to snake body part entity. This way you control per snake, rather then per body part. I think is nice solution, if you want apply some nice color effects etc.
Mind, I am not objecting against pure entity approach, rather array based. Just an option.

Other way for rendering, is using for example DrawMeshInstanced. This way you don’t need any body parts as entities. You just fetch data from snake entity, to MaterialPropertyBlock.

In either approach, you only need ECB for creating/destroying snakes entities, and body parts entities (if not rendering manually). In either way, you can burst every system.

The problem with using only draw mesh approach, is that now you don’t have entities with colliders, to check for collision, if wanting use raycast for example. So going back to prime rendering approach, as body per entity, this way body can hold collider. But if you actually moving on the fixed size grid, like old classic snake, by fixed steps, you probably wouldn’t need that at all, as you could simply check position index in a grid, if is occupied by anything.

Mind, I have skipped eating case part here.