ECS and Animated Sprites - where it all fell apart...

After years of prototyping and messing around I’m finally trying to finish a game and man, this ECS thing is a struggle.

So in the past I’ve always written my little games using the, now-out-of-fashion, OOP way of base class with update, draw, etc. I had a lot of success. So I thought since I was going to try this “for real” I would see what the pros are doing and I stumbled across ECS and the myriad of content out there surrounding it.

I didn’t start out with any frameworks as I’m happy to write it myself. At first everything worked great-I had a lean Entity class, Postion component, Renderable component, a RenderingSystem and I was drawing little static sprites all over the screen. Then I tried to adapt my AnimatedSprite class and it all fell apart.

So I then I thought, hey, other people have solved this, let me look at some ECS frameworks. What I found were a lot of highly optimized skeletons with no reference implementations or reference implementations that seemed nothing like what I was told ECS was. I’m truly not being critical of the awesome open-source frameworks out there, but I haven’t found one that implements the concept of an AnimatedSprite using “pure” ECS, meaning an Entity with little more than an ID, Components that only store data and Systems that do all the logic.

In my prototype I have everything all wired up with a nice state machine that can trigger animations and transitional animations and can loop, ping-pong, etc. I’m just having a real helluva time adapting that to ECS without writing really sloppy code.

OK, so for some focused questions:

  1. Man, I’d love to see your ECS state machine and animation system. Can I?

  2. In a property/field-only Component how do you track current delta time other than constantly decorating your Compontent with it? Meaning, I need to know when to switch animation frames based on some kind of speed variable compared to accumulated time. Is it appropriate to store that at the Component level? I feel like my Components are just getting super busy.

  3. How granular should a Component be? Maybe that’s my problem? At a certain point ECS seems pointless if your Components are just versions of OOP-style entities.

Based what I’ve seen I feel like everyone ends up with some kind of hybrid approach.

Thank you for your patience.

EDIT: yes, I’ve looked through this: GitHub - SanderMertens/ecs-faq: Frequently asked questions about Entity Component Systems

I have an ECS in here

Game/GuruEngineTest/GuruEngineTest/GuruEngine/ECS

It is aimed at 3D not 2D , but the principles are the same

I have a game object which contains a load of components

game components can have connections to other game components

So in my context you can add an animation component to a mesh.

The animation component is connected to a data source , usually through a named variable, and connected to the mesh through a pointer

When the update method on the animator is called, it calculates a new animation matrix, then applies it the the connected mesh

Another way of animating objects is to combine animation and function, so I have a AntiAircraftArtilleryComponent

This not only contains the code needed to animate the weapon, but the AI to make it function

In data I am currently just using text files,eventually I will compile these into binary blobs, but at the moment text is much more convenient

An example looks like this

AntiAircraftArtilleryComponent_1
AntiAircraftArtilleryComponent
2
Root#Root#Flak18_88mm:GameComponents
Transform#WorldTransform#WorldTransform_1:Consumers
1
Meshes#Body:Aircraft,
8
FireTime Float 2
RecoilDistance Float -0.35
MaxPitch Float 10
MinPitch Float -85
MinYaw Float 0
MaxYaw Float 360
AnimMode Int 0
Round String AAA/Flak88mm

Breaking this down you have…

The name of the component AntiAircraftArtilleryComponent_1
The class of the component AntiAircraftArtilleryComponent
It has two direct connections
The first connects this component to the root of the game object, required for update to be called
Then a connection to the game objects world transform
Then a single list connection, which connects this component to the mesh list of the object
Then 8 parameters that control the component

The Round parameter link to ammunition data for the physics system and particle system data

An ECS is incredibly powerful when written properly

2 Likes

Thanks for sharing your code. I really appreciate it. It makes sense to me what you’re doing.

Would you describe your game as using a hybrid ECS? I ask, because many of your Components contain an Update(delta) method. My understanding, and struggle, with ECS is that Components should affectively have no logic; that’s the domain of the System.

Perhaps I’m getting too wrapped up in the dogmatism of it all.

I don’t know where you read that, but it’s total bollocks

The whole point is that you can have a designer rather than an expensive coder to put together game objects.

Having no code in the game objects is just plain stupid.

Consider the alternative.

You add a component that is just a block of data. Something has to parse that data and decide what to do with it. So that is effectively wasted cpu cycles.

Mine does not have to think about what to do with the data, it knows what to do with the data

Then take the case that a component needs to talk to another component, in mine when the object is created it knows the correct update order. So a thruster updates before the physics system. , an animator updates before the mesh.

In the system you are talking about you will have to artificially impose some kind of update ordering , hard coded in advance.

I really don’t know where you found talk about ECS without code, because I would love to have 10 minutes in a sound proof room with them

It has components, which are plain datatypes without behavior

2 Likes

What’s your data for an animated sprite? Why can’t you have a system that transforms some of that data?

Answer to 2: I find my entities sometimes get busy but it’s complexity inherent in the system and any attempt to simplify would just increase complexity.

Answer to 3: A “pure” ECS with no functions seems to me like it would be very similar to OOP with the functions moved elsewhere. Do you have an example of where your OOP and ECS differ in granularity?
Software requires data and code and I feel like ECS and OOP differ the most on how to architect the rest of the code but the underlying data that represents whats going on shouldn’t be much different.

What benefits are you trying to get from ECS over OOP that you aren’t getting?

Just use whatever you’re the most comfortable with. Right now I only see you waste your time.

When I say no code, I mean the components specifically. So Entities are just an ID, Componets are just data and the System owns all the code/logic. That seems to be the pattern that is being pushed. I understand it, I just don’t like it, yet.

That’s the road I started going down, but when I looked down the path I wasn’t sure it was the way forward. Here is what I’m messing with now:

    public class AnimatedSprite : Sprite, IComponent
    {
    public AnimatedSprite(string texturePath, string textureKey, Animation[] animations, int cellWidth, int cellHeight, int columns) : base(texturePath, textureKey)
    {
        Animations = animations;
        CellWidth = cellWidth;
        CellHeight = cellHeight;
        Columns = columns;
    }

    public AnimatedSprite(string texturePath, string textureKey, Animation[] animations, int cellWidth, int cellHeight, int columns, short layer) : base(texturePath, textureKey, layer)
    {
        Animations = animations;
        CellWidth = cellWidth;
        CellHeight = cellHeight;
        Columns = columns;
    }

    public AnimatedSprite(string texturePath, string textureKey, Animation[] animations, int cellWidth, int cellHeight, int columns, short layer, Color color) : base(texturePath, textureKey, layer, color)
    {
        Animations = animations;
        CellWidth = cellWidth;
        CellHeight = cellHeight;
        Columns = columns;
    }

    public Animation[] Animations { get; init; }

    public int CellWidth { get; init; }

    public int CellHeight { get; init; }

    public int Columns { get; init; }

    public int Speed { get; set; }

    public int CurrentFrameIndex { get; set; }

    public int Elapsed { get; set; } //should this go here or in the animation?

    public Animation CurrentAnimation { get; set; } //is this right? Should this just be what ever animation is running?

    public Rectangle SourceBounds //probably should be in the system? What if I want to cache it? who/what handles isDirty?
    {
        get
        {
            var column = CurrentFrameIndex % Columns;
            var row = CurrentFrameIndex / Columns;

            var x = column * CellWidth;
            var y = row * CellHeight;

            return new Rectangle(x, y, CellWidth, CellHeight);
        }
    }
}

public class Animation
{
    public Animation(string name, int[] frames, int speed, SpriteEffects spriteEffect = SpriteEffects.None, bool isLooped = false)
    {
        Name = name;
        Frames = frames;
        IsLooped = isLooped;
        SpriteEffect = spriteEffect;

        Speed = speed;
        IsRunning = false;
        IsCompleted = false;

        CurrentFrameIndex = 0;
    }

    public string Name { get; init; }

    public int[] Frames { get; init; }

    public SpriteEffects SpriteEffect { get; init; }

    public bool IsLooped { get; init; }

    //public LoopType LoopType { get; init; }

    public bool IsRunning { get; set; }

    public bool IsCompleted { get; set; }

    //public int Elapsed { get; set; }

}

One of the things that has tripped me up is the idea of storing the Elapsed (I’m using ticks, so this is the equivalent of deltaTime) on the Component. In my brain storing the elapsed time should be part of the System state, not part of the Component state. Then I started to go down the road of storing storing a Component cache in the System to track it and I got frustrated.

Maybe what I have is fine.

1 Like

In my prototype there is a PlayerGameEntity that has some interfaces like IDrawableGameEntity, etc. The class itself ends up with it’s own Update and Draw method. So I fully incapsulate the FSM, Animation, player input, etc. From a, err, traditional coding perspective this is all nice and clean and makes sense.

This is a good question. My prototype was a simple platformer. The game I’m building is a JRPG style game with some procedurally/rouge-like elements. The flexibility of templating characters and enemies simply by defining their components is compelling and I think ECS lends itself to the design.

I’m mostly struggling with the engine elements. The game elements have been a treat with ECS.

I hear ya. I’m getting there.

If you want help thinking how to solve problems with ECS you can jump into the Discord for MonoGame and somebody will definitely be able to help you. I’m writing this below as a warning for you and others that come this way. Almost every week like clockwork there is some one who is confused about ECS on Discord. We even have memes of “ECS bad” and “ECS good”.

There is real benefits with getting serious with data-oriented game dev. To reap these benefits you will have to go dig into C# and learn a few things which are not usually worth it for most C# developers outside game dev like how automatic memory management works and how to avoid it when necessary. I make a claim that there exists a portion of developers using MonoGame who do know C# they just have not done game development. Anyways as others have mentioned, if your goal is to just create a game then perhaps is not the best time to jump into this rabbit hole. This is especially true for your first game.

let me look at some ECS frameworks

The problem is that most ECS frameworks in C# are not really using data-oriented design. Rather they are just applying the pattern of “Entity Component System” while ignoring one of main reasons why the pattern exists in the first place. Yes this includes MonoGame.Extended which is based on Artemis from Java. I can only talk about MonoGame.Extended, but I believe the ideas can transfer as to why others are in the same boat. The reasons are:

  1. We didn’t really know in depth what advocates meant with data-oriented back then. Or rather, we didn’t believe them. We been using C# for years. We knew how to solve problems (or so we thought). So why change? Why does it matter if I use class for everything instead of struct? Is crunching some data really faster than fetching pre-computed data from memory? We were skeptics and it’s not surprising that other people are skeptics too. It’s much easier to just fallback into your comfort zone than to learn something new.

  2. C# and .NET in recent years has had some great advancements for game developers. Span<T> and stack-only-allocated structs for example were not widely available when MonoGame.Extended was looking into ECS at the time. Neither was native-AOT (formerly called CoreRT). In comparison, C# has been around since 2002. 3 out 19 years is relatively short time to see new features adopted into production code.

I would recommend either going all in on this rabbit hole xor not going in at all. I think the consensus here for the sake of sanity and efficient use of time is not go in at all unless you have performance problems at scale. It’s once you have a real reason to learn and get practice using data-oriented design that a bit flips in your brain. Once the bit is flipped you wish you could flip that bit for others too.

With all the warning and “I told you not to” out the way… If you have a good reason to learn how to do this then here are some links:

  1. GitHub - SanderMertens/ecs-faq: Frequently asked questions about Entity Component Systems. Already posted in this thread, but it’s a good go-to resource for introductions.
  2. Component · Decoupling Patterns · Game Programming Patterns. Good primer for the “E” and “C” in ECS without being ECS.
  3. Data Locality · Optimization Patterns · Game Programming Patterns. Good introduction to why data-oriented design matters for game developers. You’ll start to see some early thoughts of where “Entity Component System” is coming from by the end of this chapter.
  4. game_dev_pdfs/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf at master · Michaelangel007/game_dev_pdfs · GitHub. Lecture slides from Sony with examples in C++.
  5. Data-Oriented Design (Or Why You Might Be Shooting Yourself in The Foot With OOP) – Games from Within. Game developer blogging about data-oriented design.
  6. OOP is Fine (In Moderation) | Game Development by Sean Another game developer blogging about data-oriented design. This one is particularly good because the ideas of what we mean by OOP are challenged. There is even examples in C on how to do OOP.
6 Likes

Thank you for your well thought out response. This resonates with what I was thinking and feeling about where I was at trying to implement ECS.

Overall I think I have a better idea how to forge a path forward. I’ll definitely check out the discord.

You can also use the Stride game engine, it’s almost the same as MonoGame but has proper ECS and everything, like sprites, particles, materials works with the ECS.

Porting code is very easy, since it follows the same architecture.