Rude is an ECS game engine built for LÖVE2D.
Rude is licensed under the MIT license.
An ECS game engine built for LÖVE2D.
License: MIT License
I had some ideas for making the alert module more useful:
The EventEmitterMixin allows a class to be extended with an implementation of the observer pattern. There are a few things about this I'd like to discuss.
First, let's talk about determinism. When I originally started building rude
, I thought it would be nice to have scenes be deterministic, or at least effectively deterministic. What I mean is, for a given scene, if you load a given set of entities and components and call update() with a given dt, then the result should always be the same and not depend on any "hidden" state (e.g. globals, internal system data). This would open up a lot of possibilities for testing, both in development and maybe even in production builds.
With EventEmitterMixin, you are essentially allowing behavior to be modified at runtime. If a system registers a new handler in an update() call, future update() calls may result in different data manipulations, thus making that scene non-deterministic. I don't think this is something rude
should rely on heavily. Furthermore, having a dedicated mixin for this behavior implies that a user should be applying it to their own classes, and for now there's really no reason anything but the Scene class should be using this.
It's worth noting that it's impossible for us to completely prevent a user from breaking a scene's determinism, due to Lua being such an open language. For example, one could do this:
function Scene:onUpdate(dt)
if aBadActorAppears then
Scene.onUpdate = function(dt) print("say goodbye to your updates!") end
end
end
With all that said, here is what I'm proposing:
I haven't been very consistent with return values for functions, in particular what happens when a function "fails". I'd like to clean these up so they are more consistent across the codebase. For example:
Currently, when registering classes (components, systems, subcomponents, etc.) you specify a string ID. This makes it difficult to write modular code. For example, say you have one plugin that registers a component class to ID "sprite". If you want to use another plugin that registers a different "sprite" component, you can't do so without creating issues with the first plugin.
How can this be resolved? One way might be to write the plugins with config options that allow you to override the ID used for each class. This seems kinda hacky though.
Another possible solution would be to do something similar to vuex modules: https://vuex.vuejs.org/guide/modules.html#module-local-state. For example, maybe there could be rude "modules" that define a namespace for registered classes. Still, this would really just be pushing the issue up a level, because then there could be collisions between equivalent module names. This would at least make it easier though, instead of having to configure many different IDs you would only need to configure the module ID. I think for now, having a way to group registered classes into modules would be a sufficient solution.
I thought about it and decided having a way to add plugins to scenes and systems is not really that useful. For scenes in particular, I don't really have a good way to bring in multiple plugins that don't overlap each other, since the callbacks are designed to be overwritten in the scene definition. If I change how this works in the future, maybe it'll make sense, but now there's no reason to have a usePlugin() method for scenes or systems.
Currently, entity IDs are only intended to be numbers. In practice however, having named entities like "player" or "camera" would be convenient. We should allow adding new entities with a string value for the ID.
I'm trying to decide on an overall workflow for this project. Originally it was the Git Flow model since I'm most familiar with it, but now I'm not so sure. master and dev branches seem redundant.
I also need to figure out a release schedule. How do I determine when a certain set of new features should be considered a new version? I don't want to load up the luarocks server with a bunch of versions, but I should have some sort of criteria. Maybe based on number of issues? But issues vary a lot in scope, so...I'll need to think on this.
To resolve this issue, there must be a definitive workflow plan documented in the readme.
With rude
, and ECS in general, the entities/components are really one big blob of state. Every system is essentially just a function that mutates that state. That obviously comes with some pros and cons, pro being that mutations are generally quick, but con being it makes unit testing a hell of a lot harder.
I've been toying with the idea of making certain components, or parts of components, immutable. What would that look like? If I had a position component, for example, that is essentially just a vector, what would happen if I designed that vector to be immutable? Instead of mutating the component table, we would need to drop it and add a new replacement. Each position modification would then require replacing the entire component.
The biggest concern is garbage tables building up over time. My general solution to that is PoolableMixin
and relying on release()
calls. Maybe there could be a setCom()
or replaceCom()
method that automatically releases an existing component and "attaches" a new instance to the entity. This would at least be more elegant than manually calling removeCom()
and addCom()
. Of course, all of these replacements would add additional overhead, so I'll need to test performance and see if the benefits outweigh the performance hit or not.
Overall, I think there may be some benefits to making some components immutable, but definitely not all. I think supporting both approaches adds flexibility and allows the end user to make their own choices based on their specific needs.
Edit: one point of clarification...tables in Lua can never truly be "immutable" due to rawset()
. If I'm treating something as an immutable object, I'm assuming the user only goes through the object's API and doesn't break the rules by directly changing elements.
I'm not a big fan of the current scene "lifecycle" hooks like visibility, updates, onLoad, etc. I think this could be cleaned up a lot.
One thought is to maybe embrace the event mechanism and build everything off that, including updates and drawing. But I may still want dedicated callbacks for these, not really sure at this point. I should keep in mind that the order that systems do certain actions is pretty important in practice, and I should make this easy for the user to manage. I'd like to move toward having an "onEvent" callback function that can be customized for each event ID. Just some thoughts.
My original intention was to have scenes be independent from each other, however I'm starting to think it would be beneficial to allow a scene to access data from another scene. There are scenarios where this would be helpful, for example:
It's worth noting that scenes can technically access each now by going through the engine, but it's not very elegant. I think adding a getComFromScene()
method might be a cleaner way to control this access. If the scene doesn't exist we could return an exception, for example. Once a component table is returned, the system logic could then modify it however they want.
If an enemy is removed in the middle of a frame update, sometimes this can break things. To solve this, I'm proposing a function that flags an entity to be removed at the very end of the frame.
I'm a big fan of Lua; it's a nice, simple, powerful language. That said, there are things about it that are a bit lacking, and I've often wanted to more with my code than it can support. One example is type verification like in typescript - I've tried to do some of this my own way (see: https://github.com/mrrogge/contract) but nothing substantial. I know there are some other efforts on this front, like https://github.com/teal-language and https://github.com/TypeScriptToLua/TypeScriptToLua, but I haven't worked with these much yet and don't know if they're the right approach for me.
I've been learning about Haxe lately (https://haxe.org/) and I like what it has to offer. I think they made a lot of good language design decisions, and although I've spent a grand total of 0 hours coding with Haxe, I could see myself using it for my projects, in particular this one. One big piece that has been missing in rude
is verifying that component data fits a defined schema or type, which Haxe could help a lot with.
The other nice thing is Haxe code can be compiled to Lua, so I could theoretically write rude
in Haxe and still be able to use it in Lua-based projects. There would likely be caveats, and issues with 3rd-party libraries like LOVE2D, bump.lua, etc., but I think the pros may outweigh the cons.
Clearly this project is not well documented at the moment. To be honest, most of my time is being spent on developing a game built around this engine, so unfortunately I haven't been able to contribute much on this front. In the meantime, I've left several comments throughout the codebase which should hopefully offer a little help.
It would be nice to have a built in way to set various configuration parameters on an engine or a scene. For example:
My thought is to add an options param to the initialization functions of Engine and Scene. This option can be a table where keys correspond to various config parameters. Scenes could be extended by handling additional option keys depending on their needs. It might also be helpful to add a config method that allows changing these values later on.
Originally, I intended for scenes to be completely independent and capable of running simultaneously. The reasoning was so a user could make, say, a main scene responsible for the world entities, and a separate UI scene responsible for HUD, pause menu, etc.
I still think this can be useful, but it seems like in practice, having a stack where only one scene is active at a time is a bit more common.
What I'd like to do is make each Engine instance configurable for either "single" scene mode or "multi" scene mode. this mode would affect how certain scene-related methods work. For example, newScene() may build a new scene and add it to the top of the stack of using "single" mode.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.