I started game dev with love2d which is a pretty minimalist framework. When I participated in a game jam, I needed a very flexible system that would allow for quick prototyping and would handle many different entities. I ended up writing something which I later realized would be an ECS system. It worked great and I would copy it over for other games.
I know there is that ECS-faction that is talking about performance benefit and stuff but honestly I don't care. I don't even know most of the terminology these people use. I just use ECS to structure my code while being very flexible. I use OO in other domains but it would never occur to me to structure games that way. OO just feels way to brittle for a domain where you do lot's of experimentation and can't really know what you will need and how your entities might end up looking.
"Component Software: Beyond Object-Oriented Programming"
https://www.amazon.com/-/en/Clemens-Szyperski/dp/0201745720
One of the first publications on the matter.
> ECS only starts to make sense when you are big enough to have multiple teams, with one team building the engine and the other using the engine to make the game; or you are an engine company and your customer makes the game. If that is not your situation, do not rathole. https://twitter.com/Jonathan_Blow/status/1427358365357789199
Most of the arguments I've seen for ECS in Rust suggest it helps to work with memory management/borrowing. For example, here's Patrick Walton's take:
> There's a reason why Rust game engines all use ECS and it's not just because the traditional Unity OO style is out of fashion. It's because mutable everywhere just doesn't work in Rust. Mutex and RefCell explosion. https://twitter.com/pcwalton/status/1440519425845723139.
And here's Cora Sherratt's discussion of ECS options in Rust:
> So why do you need one? Well all ECS developers will claim it’s largely about performance, and show you a mountain of numbers to back it up. The more honest answer probably comes down to the difficulty of making ownership work in Rust without some type of framework to manage the transfer of ownership for you. Rust punishes poor architecture, so ECS’ are here to help. https://csherratt.github.io/blog/posts/specs-and-legion/ (This post also has the best visualisation and explanation of an ECS I've read.)
I've read Hands-On Rust and you could definitely implement the game without an ECS. But at the same time it was useful to play with that pattern because it's in common usage in the Rust community. (Bevy also makes heavy use of it, for example, where it feels pretty lightweight because they made some good design decisions: https://bevyengine.org/news/bevys-first-birthday/#bevy-ecs.)
[1] https://github.com/three-rs/three [2] https://github.com/kvark/froggy
I definitely feel like the most "rustic" way to do game design, just based on what's easy to do in the language itself without resorting to workarounds, is to have individual actors maintain their individual state, and then have a common interface via a trait that would call update() or render() virtually in a loop or whatever. Then have them message each other via mpsc channels. That's pretty much rust straight from the book, and, it's also a bog standard GameObject architecture straight outta the 2000s.
Also, having collections of entities works well for Rust's borrow checker.
Personally, while my experience is a bit limited, I quite like the ECS style. It just makes logical sense to me as a way of composing entities from different parts. The implementation details (cache friendliness or whatever) are not important to me since I've never made anything of a scale where it matters, but that style of developing/designing how things act and interact is logical to me personally. I haven't watched Bob Nystrom's talk yet though (or, actually, I may have, it seems familiar, but I don't remember any details. I plan to watch it tonight).
With that said, many people find the Godot style more natural and it certainly is nice too.
Of course, he didn't go into much detail about when you might need an ECS other than "when you have something graphically complex" (paraphrased), I would extend that to include: 1) when your entity modelling is becoming a performance bottleneck, 2) when your entities are becoming complex enough that you want to have anything become anything; you could build on what he proposed to make this work by making more complex and flexible components but at some point using an ECS may well just save on effort, especially if you use an existing one like what Bevy has or EnTT in C++, 3) your logic can naturally decompose into systems that operate on sets of components and you have a lot of components to operate on.
In all three of my additions, scale is a factor. If you only have 10 entities, then you can solve all 3 with less effort in other ways. Similarly, if you only have 10 components or 10 systems, its not such a big deal. If you have tens of thousands of entities, a hundred components and a few dozen systems, then an ECS is probably the right choice.
For me, I like the ECS style. I also like the solutions proposed in his talk. I'm also not planning on writing my own ECS ever but using existing ones (I've tinkered a lot with EnTT in C++).
We explored the topic quite thoroughly in [0] and came to the conclusion that Roguelikes are more suited for something in-between ECS and OO, named just "EC".
ECS is better suited to real-time games, or games which have a lot of iteration over large arrays and parallel (not as in multi-threading, but as in, batchable) computations, where ECS can really shine due to its data layout. Most turn-based games like roguelikes don't iterate over large arrays in the same way.
With EC on the other hand, you're a little more free to group components into their parent entity, and use interfaces (traits), which can make things more understandable.
Whether idiomatic Rust likes EC is an open question though. Idiomatic Rust tends to dislike heap allocation and virtual dispatch, both which can make life a lot easier for Roguelike games, which tend to prioritize flexibility and features, and don't need the data-oriented optimization.
Additionally, a lot of people suggest using ECS in Rust because that's the only architecture that the borrow checker doesn't fight you in, for various reasons.
[0]: https://www.reddit.com/r/roguelikedev/comments/i3xekn/ec_vs_...
Maybe Rust the way most people end up writing it, but Rust the language has no issue with these things. I think the idioms come more from the fact that Rust empowers you to avoid these things, not that it's poorly-suited to them.
There's a great talk [0] from the Overwatch developers where they talk about how they built Overwatch using the ECS architecture. They barely mention performance at all, but instead talk about how it helped deal with complexity.
[0] https://www.gdcvault.com/play/1024001/-Overwatch-Gameplay-Ar...
Consider for example, a snippet that reads along the lines of:
when there is a new stripe charge
send a slack message
It's very nice to attach something like a "ScopeContext" component to each Entity that has a "TokenLine" component, that is a Vec of EntityIds that are sharing values into scope. Very easy to model and add without interfering with other data structures.I think a key strategy that is not touched on too much in the talk is flexibility in your effect system, whatever your effect system may be. It should be trivial to take an effect (heal, damage, stat boost, etc) and let anything in your game apply it to actors with just a few lines of code: items, spells, tiles, regions, the whole game, etc.
As an example, lots of games will specifically give certain classes hps/mana/better chance to hit/new skills as they gain levels.
I just use my effect system for this. Now I can have items that give skills too, because items use the same effect system.
I don't think it's very entrenched, although it's certainly present. The reason for this perception, I guess, is that Bevy (which is based on ECS) is all the rage.
Amethyst was ECS as well, but it's dead now (and Bevy is essentially its successor). Veloren is ECS though, and active.
The remaining major engine (AFAIK) is RG3D, but it's not ECS. Macroquad provides a node graph (but it's a smaller engine).
All the other game engines are comparatively small, and they don't provide storages/ECS engines.
(edit: added Veloren)
I think it's important to separate the EC from the S in ECS. Entity-Component storage is basically a fast, in-memory database. It's a great way to store global state, and provides for really efficient querying. Using it as a database gives you some advantages:
* Composition over inheritance (especially in Rust, which doesn't really have inheritance - although you can fake it with traits). It becomes easier to glue on new functionality without realizing that you need to rearrange your object tree, and there's real performance boosts to not doing virtual function calls or dynamic casting to see what an object is.
* Replication; if you want to replicate state across multiple nodes, a good ECS can really help you.
* Mutability; as mentioned above, you don't need mutable access to everything at all times, and your code is definitely safer if you have explicit mutability control.
* Surprising flexibility; Rust EC setups typically let you put anything into a component. I have one slightly crazy setup that stores an Option<Enum> as a component with the enum featuring a number of different union setups.
So what about systems? Sometimes systems have some real advantages:
* For simulation type games, it's great to be able to add a simulation feature and have it apply everywhere. For example, when I added gravity to Nox Futura it instantly worked for player characters, NPCs, and objects. (It also worked on flying creatures, killing them instantly - but I fixed that).
* It really helps with parallelism. Your systems declare the data to which they will write, allowing the ECS to order your systems in such a way that you get parallelism without having to think about it too much (especially in Rust).
* If you're in a team, it's a great way to break out work between team-members.
* It's often helpful for finding bugs, because functionality of one type is localized to that system. You can get the same result by being careful in a non-system setup.
Sometimes, systems aren't so great. It can be really tricky to ensure that linked events occur in the correct order. You can make a bit of a mess when you want something to work one way for one type of entity and another for a different type.
But here's the thing: the systems part is optional. You can easily have your main loop query the EC data-storage directly and work like a traditional game loop - without losing the benefits of the storage mechanism. If you prefer, you can attach methods to components and call those. Or you can build a message-passing system and go that way. There's no real right way to do it. Once you've got the hang of your ECS's query/update model, you can tailor the game logic however you want. (I happen to like systems, but that's a personal choice more than a "you must do this" belief).
(Edit: My formatting was awful, sorry.)
This does not require ECS, you can happily have something like
struct Entity {
components: Vec<Box<dyn Component>>,
}
(Nor do I think this is a good way of setting up a traditional roguelike)> Replication; if you want to replicate state across multiple nodes, a good ECS can really help you
I'm not sure what you exactly mean by nodes here, but making something serializable in Rust for easy replication is hardly an issue when we have access to tools like serde.
> Mutability; as mentioned above, you don't need mutable access to everything at all times, and your code is definitely safer if you have explicit mutability control
Addressed above, but while ECS solves the mutability issue it's not a unique way of solving it and bringing it in to deal with that is overkill at the very least.
> Surprising flexibility; Rust EC setups typically let you put anything into a component.
Again, you can do this with regular old components too. This is also an oversold feature of components / mixins / anything like this in general, I think. You can never "just add a component", you need to fix all the issues that come with that, like making sure that gravity doesn't kill your birds.
> For simulation type games, it's great to be able to add a simulation feature and have it apply everywhere.
Yes, but that's not what a turn based tile based game is. Generally you want to iterate over things in order - gravity (if a roguelike has such a thing) gets applied on the player's turn, and only for the player. If you step over a ledge you don't wait for the "gravity" system to kick in and apply gravity to all entities, it is resolved in the same instant for only the entity that has just moved
> It really helps with parallelism.
Sure, although gameplay logic is not what's going to have to be parallel in a roguelike. Building up pathfinding maps and similar is useful to do in parallel, but ECS doesn't really help you with that.
> If you're in a team, it's a great way to break out work between team-members.
Not a particular strength of ECS, and if anything I could see issues arising from the fact that you basically have dynamic typing when it comes to what behaviors an entity has.
> But here's the thing: the systems part is optional.
If you're not doing query-based ECS with systems there's also no particular reason to not use vecs of components within entity structs.
I believe what you're doing here is adding a bunch of complexity to something that could be much simpler, and it really does the language a disservice.
As a final note, in the excerpt about items you have this justification for not using an enum instead of components:
Each item you’re adding provides only one effect. It’s tempting to
create a generic UseEffect component containing an enumeration.
Enums can only have one value—if you want to make an item with
multiple effects, you’d be out of luck. It’s a good idea to separate
effects into their own components in case you decide to create an
item that does more than one thing.
Not only does this violate YAGNI, it's trivial to work around: enum ItemEffect {
Heal(i32),
Poison(i32),
Explode,
MultiEffect<Vec<Box<dyn ItemEffect>>>,
}
It just feels like you're looking for problems to solve.struct Player { health: Health, }
// ECS style
fn update_player(query: Query<&mut Player>) {
}
// Traditional "OOP" style
impl Component for Player {
fn update() {
}
}* Structures persistency (save/load/stream)
* Optmized seamless world expansion
* Where to get assets and content from (licenses, generated, etc.)
* Affordable and responsive multiplayer
However, being completely new to Rust I find that the author doesn't spend enough time discussing the language, it's syntax and nuances. It is hard to talk both about rust alongside video game design techniques.
I put it down after reading 1/4th of it. I'm planning to spend some time on a book that focuses on the language first and then get back to it ;)
If you're in a similar boat, I would recommend checking the framework out. Lua's a pleasure to program in and you can focus on the game development itself instead of getting bogged down in the details of rust / cpp. In fact I've been thinking lately about how easy it would be to use it for things other than games -- quick prototyping of graphical simulations, psychophysics experiments, etc.
[0]: https://love2d.org/
ECS is a pattern to manage composable logic and shared behavior. very very loosely like splitting logic in to class level and object level.
and on Github: https://github.com/amethyst/rustrogueliketutorial
Hands-on Rust is designed for the newcomer to Rust, and carefully maps tutorial sections through teaching beginner-to-intermediate Rust concepts. It starts with some basic Rust exercises, works through a Flappy Bird clone, and then uses Roguelike development to teach a lot of underlying Rust concepts. It also teaches gamedev, and tries to do so in a way you can reuse in other games.
The tutorial is all Roguelike, all the time - focused on building a working roguelike.
Mentioning this here since the tutorial linked above has a lot of procedural generation content
I found it especially satisfying to port the game from racketlib/rltk to bevy afterwards.
Translating the implementation to bevy on the fly as you read the book would probably be much more difficult than what I described.
Bevy didn't exist when I started writing, or I'd have probably used it. Legion is a great ECS, but it's heavier than I'd like - and Bevy makes a lot of things really easy.
is there a promo code for Latin-american countries?