ECS -- is it ok to have a method/function as a component ?

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

I am working on a game using the ECS architecture pattern. It is very raw but I've reached some checkpoint implementing different stuff with that pattern (I am from OOP so biased a bit). Now I'm struggling with the Animation system/component. I have the Camera system which is intended to draw the ... drawable lets say inside its main process method:

Code: Select all

function process(e, dt)
-- draw it
end
So some entites are intended to animate, which in turn means they have the Animation component. I have filter on the Camera system so it accepts only those entities who has the animation component or sprite one. Then I would just:

Code: Select all

function process(e, dt)
  if e.animation then
    e.animation:update(dt)
    e.animation:draw(...)
  else
    -- draw sprite
  end
end
Now the problem. Sometimes the current animation depends on other components, f.e., on the state or direction. So I would like to have the following:

Code: Select all

function process(e, dt)
  if e.animation then
    e.animation = e:getCurrentAnimation() -- this introduces new component
    e.animation:update(dt)
    e.animation:draw(...)
  else
    -- draw sprite
  end
end
So as you might've noticed that means that this system expects the entity has the getCurrentAnimation component. On the one hand, it violates the principle of ECS which states that we don't want components/entites to do some logic (yeah, I know I already did that when wrote that animation:draw but I am using the anim8 library). On the other hand, how would I do that otherwise ? I have two options in mind:

[*] Have the PlayerAnimation specifically for the player. Then for any other entity it would go the same: a separate system for each "type" of entity.
[*] Have a general Animation system that would assume that there is a finite number of mechanisms of setting current animation. For example (very roughly not optimized, pseudo code),

Code: Select all

function process(e, dt)
    e.animation = e.animations[e.state][e.direction] or
                  e.animations[e.state] or
                  e.animation
end
I understand that the above problem is not specific to animation. Anyway what are your thoughts ?

Thanks in advance !
I don't believe you, continue.
RNavega
Party member
Posts: 456
Joined: Sun Aug 16, 2020 1:28 pm

Re: ECS -- is it ok to have a method/function as a component ?

Post by RNavega »

Hi. I'm not that well versed in ECS but inside that 'process' function of your camera system, you're calling the 'update' function of the animation component. This means that you're making the camera system responsible for updating animation components.

How about rephrasing your entities loop so that you can have entity animations being updated by a separate system (like your second option): an animation system that, based on the attributes stored in the animation component, will process it in a particular way?
The camera doesn't know about the animation system and vice-versa.

Code: Select all

function love.update(dt)
    for entity_index = 1, total_entities do
        entity = all_entities[entity_index]
        
        local ac = animation_components[entity.id]
        if ac then
            animation_system:process(ac, dt)
        end
        
        local sc = sprite_components[entity.id]
        if sc then
            camera_system:process(sc, dt)
        end
    end
end
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

Re: ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

Hi, @RNavega, Thank you for your reply. However, you answered a bit different question I am afraid. Yes, of course updating animation should go in another system. It is not a problem. Sorry if my post was ambiguous. Let me explain it in more details.

Your player has different states: walk, fly, swim, fire. Each of those states has its own animation. Even more, in each of those states the player can have different direction (let's say only two) : right and left. So the number of possible animations grows as S * D, where S - the number of states, and D - the number of directions. But states are (may be) completely independent on direction. Therefore, they highly likely are changed in different systems. We can think of another "dimension" like wearing different clothes, etc. Now the question: how do I handle what current animation should be ? Should it be some system called getCurrentAnimation ? If so, how different entities should fit this system ? The first think that comes to mind is the one from the post:

Code: Select all

function process(e, dt)
    e.animation = e.animations[e.state][e.direction] or
                  e.animations[e.state] or
                  e.animation
end
But I am thinking on a more rigid one.
I don't believe you, continue.
RNavega
Party member
Posts: 456
Joined: Sun Aug 16, 2020 1:28 pm

Re: ECS -- is it ok to have a method/function as a component ?

Post by RNavega »

From these references:
https://discussions.unity.com/t/how-to- ... /698129/11
https://www.dataorienteddesign.com/dodmain/node8.html

...each state has its own system. Walking is a system. Flying is a system. When the entity has a "walking component" (actually a tag), the system that filters for that tag (the walking system) will change the state of the entity with its specific logic.
So at any time, the current animation of the entity is given by the state tag that it holds.
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

Re: ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

Hm. Quite interesting. I took a quick look at it. Sounds something that might fit my needs. Thank you ! Will be back with the response later.
I don't believe you, continue.
RNavega
Party member
Posts: 456
Joined: Sun Aug 16, 2020 1:28 pm

Re: ECS -- is it ok to have a method/function as a component ?

Post by RNavega »

I'm thinking of variations on how to do the FSMs with that paradigm:
  • A component holding an integer value that's interpreted as the identifier of the state that the entity is in. So this state component is read by the state system, and that system routes the entity to be processed by the state identified by the value of that component.
  • Having a tag component (a component without values, it's like a flag) for each state, so for an entity to be in that state, you add the appropriate tag for that state into that entity, and it'll be picked up to be processed by the corresponding state system. Similar to what I described in the previous comment.
I welcome any ideas.

PS for simpler things like game states (title screen, settings screen, gameplay) you can get away with a simple flat FSM, but for things like actor AI I think you should research how to implement hierarchical FSMs or parallel FSMs or whatever you want to call it -- having more than one state affecting the entity at once. I think with at least 2 simultaneous states you can achieve almost all possible behaviors that you'll need in a game.
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

Re: ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

I like the second approach. Looks more flexible and generic. However, this approach might be non-efficient when states are often changed from one to another. Oh the other hand for that I described: player states are not supposed to change frequently. I will try to implement this approach during this weekend.
I don't believe you, continue.
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

Re: ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

I kinda did that a week ago but met same initial problem. This is the walk system:

Code: Select all

local system    = tiny.processingSystem({ nocache = true })
system.filter   = tiny.requireAll('walk')

function system:process(e, dt)
    if not e.surface then
        e.walk = nil
        e.fly  = true
        return
    end

    e.animation.current = e.animation['walk'][e.direction]
end
This is the fly system:

Code: Select all

local system    = tiny.processingSystem({ nocache = true })
system.filter   = tiny.requireAll('fly')

function system:process(e, dt)
    if e.surface then
        e.fly  = nil
        e.walk = true
        return
    end

    e.animation.current = e.animation['fly'][e.direction]
end
The problem again seen in the last line. How the system knows what animation it should set ? In this case we expect that animation is stored in some table, and can be indexed with two keys: state and direction. And please, don't take animation as the cause of the problem. It can be any component which value depends on a product type.
I don't believe you, continue.
RNavega
Party member
Posts: 456
Joined: Sun Aug 16, 2020 1:28 pm

Re: ECS -- is it ok to have a method/function as a component ?

Post by RNavega »

lrdprdx wrote: Sat Mar 22, 2025 5:57 am How the system knows what animation it should set ?
Can you think of a single animation system that will work for all animated entities in your game? How should the animation component used by that system be structured so that it can be used on all animated entities?

An entity in ECS is just an identifier. A component is a modular piece of data that gives meaning to that entity.

In many examples around the web you see people using a "position" component, usually a table with "x" and "y" properties, as opposed to having individual "position_x" and "position_y" components. In the same way, the animation component can have one or more properties as well.
How to design the components and their properties is up to you (like what to group together as properties of the same component vs. what to separate into different components), but from this article here:
https://joshmccord.dev/blog/sprite-animation-ecs/
...it's within standard to have a complex "animation" component that has many properties that describe the animation that the entity is performing.
The animation system then processes that animation component to update it, and this system doesn't affect the behavior of the entity.

Each behavior system (like you're doing right now with separate fly, walk etc systems) will monitor the context of the game (input, physics, AI and other game events) and react to these events, such as changing one or more properties of the animation component of the entity (or changing nothing, letting it continue on as it is).
So the animation system worries about performing the animation based on the properties of the animation component (modifying some of those properties), while the behavior systems change some properties of that animation component as well, but with the intent of preparing an animation to be performed.

So to answer your question, the fly behavior system already knows the possible animations that the animation component can be set with, as the entity with the fly tag is either flying or touching something right now, after which the fly system will remove the fly tag from the entity and apply another behavior tag to it so that another behavior system can pick up from there.
By the way, the entity doesn't have to go from a flying tag to another tag directly. You can have a transition tag whose system sets up animations etc, like a fly-to-idle tag component when the character lands after flying. After it's done, the fly-to-land system tags the entity with the actual "idle" component.
User avatar
lrdprdx
Prole
Posts: 11
Joined: Mon Dec 16, 2024 2:37 am

Re: ECS -- is it ok to have a method/function as a component ?

Post by lrdprdx »

First of all, one more time thank you for your participation and help. It's insane how you're involved !
___
but from this article here:
I met this article before. First of all, I am not intrested in the animation loop which changes frames inside the animation which that article is mostly about. Yes it's a part of the animation processing but it's orthogonal to the problem I want to solve. Secondly, that article involves nothing new -- there is just a usual **one dimensional** map which maps state to the animation. If that was so easy I wouldn't have created that post. And even worse, I don't see where he handles the direction of the player: as one can see from the "result" section the player changes direction. He probably just has the 'flipped' property of the animation/player/whatever. But we're guessing here. What if the animation depends on not only *state* but f.e. on the "mood" of the character (when he's happy his head is rised, and when he's sad it looks down) ? In this case either his states are growing in number resulting is states like "RUN_SAD", "RUN_HAPPY", etc. OR. We assume that there is a std::map<std::pair<EState, EMood>, std::vector<animation>>. Same problem. IMHO it should've been provided the full picture. How the transition looks like ? Why `AnimationComponent` should store current state and a map if I only see usage like `map[current_state]` and current state just doesn't change anywhere. Why don't store just `std::vector` directly ? What is the `EntityManager` ? I bet it is a singleton with not trivial structure. Etc. Etc. Sorry but I am sick of the blogs/posts/articles in the Internet when people show and teach something popular in a simple way. Their examples are often "hello worlds" which are, of course, trivial to implement and don't meet the problems that would have appeared once they had changed their examples a bit. I am not saying that article is such kind of thing but it is quite close to it.

P.S. I apologize for any kind of rude words if any --- it is just my reaction in this particular case and context.
Can you think of a single animation system that will work for all animated entities in your game? How should the animation component used by that system be structured so that it can be used on all animated entities?
Yeah, probably it is the most important questions.These are my initial questions.
Another idea is to have a property string inside the animation component which is a general key inside its animation pool table to get the right animation. For example,

Code: Select all

-- Player
animation {
  key = "state.mood.direction", -- this is path to the animation indices
  [walk] = { -- state
    [happy] = { -- mood
      [left] = { 4, 5, 6, 7 } -- direction
    },
  },

  [fly] = {
    [happy] = {
      [left] = { 16, 17, 18, 19 }
    },
  },
}
And then system just uses the `key` value to get current animation:

Code: Select all

local animation = getField(e.animation, e.animation.key) -- this function is quite easy to implement
if not animation then
  return
end

-- process animation
I didn't think too much about it. What do you think ?
By the way, the entity doesn't have to go from a flying tag to another tag directly. You can have a transition tag whose system sets up animations etc, like a fly-to-idle tag component when the character lands after flying. After it's done, the fly-to-land system tags the entity with the actual "idle" component.
That's a good point for further improvement. Thank you !
I don't believe you, continue.
Post Reply

Who is online

Users browsing this forum: No registered users and 1 guest