So first thing's first, to start using the game engine you can create a GameSystem object like so:
Toasty::GameSystem* system = new Toasty::GameSystem();
Now you don't have to use a GameSystem but it is useful in doing these things:
Automatic Unique ID Assignment
This makes use of the template class UniqueIDHandler, which allows any type with a ++ operator to be used. You just call GetNextID and RemoveID(T id) to use it. Anything dealing with this should use the same type, or be able to implicitly convert from it. I use unsigned 32-bit ints in the GameSystem at the moment, so the technical maximum number of entities are 4, 294, 967, 295 (Should be enough). I am still toying around with whether or not the GameSystem should allow selection of the type to use, maybe your game only needs 256 entities in it at any one time?
Automatic Assignment of a Valid MessageSystem* to Entities
Entities are basically a simple container of an ID (of type T, which should match the UniqueIDHandler type that you are using) and position, scale, rotation and a possibly non-unique name. They require a MessageSystem* so it can notify any listeners of position, scale, rotation and name changes.
MessageSystem Creation and Passing to Systems
The way systems and components communicate is via the message system, to facilitate an easy way to make sure systems match the same MessageSystem the GameSystem creates it's own and passes it to a system when it is added, if a System wants it, it can keep it and listen for messages it wants. Now keep in mind performance is not currently the goal, features are so at the moment the MessageSystem will immediately deliver the message to all listeners which thrashes the cache. I would like to perform this for a maximum amount of time at the end of a frame (Before scene unloading \ loading or entity destruction) instead.
Automatic Timing
Using the default timer (std::chrono aka Toasty::ChronoTimer) the GameSystem is able to easily time each frame and calculate Dt and FPS. You are able to extend from BaseTimer and create your own timer for use within the GameSystem. Also making use of the timer, you can get an average fps or the exact last fps!
Scene Loading and Unloading
The GameSystem allows safe Scene loading and unloading into and from systems. It will unload all scenes requiring to be unloaded at the end of the frame and then load any scenes that need to be loaded in. I am toying around with a co-routine style loading of scenes with it continuing onto the next frame if a certain amount of time is taken while loading it. Unloading a scene will not delete the memory.
System Updating
If you call this function:
system->Run();
All systems added to the GameSystem will be updated in reverse order that you added them as well as timing them (This is an infinite loop). If a system returns false in it's update, that means it wants the whole GameSystem to shutdown. Run doesn't have to be used, Tick can be used to perform a single frame. I plan on doing research into multi-threading because I would like to be able to mark systems as multi-threaded and have the GameSystem perform their updates on other threads. Being able to change order of systems is something I might add as well.
Entity Lookup
The gamesystem obviously provides an easy way to get either a group of entities by name (Names aren't unique remember!) or a single entity by ID out of all entities added to it.
Attached component lookup
Being able to find other components in the GameSystem is a must, so you can easily get a vector of all components added to an Entity pointer or ID. On top of that you can also get a specific system by name and get any components attached to your entity that way (some systems may allow multiple components attached to it). If you get it via a vector you will have to dynamic_cast it until you find the one you want.
Plugin Loading
the point of Toasty is to be ultra modular. No graphics, physics or anything is included with it but are done in other modules which you can either link to statically, dynamically or at load them at runtime (Which this handles). Actually loading a plugin at run time instead of linking to it makes it kind of difficult to actually use the things it loads in, but at the moment you could write a Serialiser and then in some text editor write out a file that would load the plugin and use components from it and attach them to entities, if you then wanted to do something in game logic you might need some kind of scripting language or other way to glue it together.
You could do that all yourself manually if you liked though, as the GameSystem class is just a wrapper around a whole bunch of useful classes in Toasty.
Continuing on you would call:
system->Run();
Which would perform the main game loop, although seeing as there isn't any scenes, entities or any other systems nothing will happen. Lets fix that:
Toasty::GameSystem* system = new Toasty::GameSystem();Toasty::BehaviourSystem* behaviourSystem = new Toasty::BehaviourSystem(system);system->AddSystem(behaviourSystem, true);
So, the behaviour system just allows one to add a behaviour to a Entity similar to Unity, but we need a behaviour so let's make one!
#include "BehaviourComponent.h"#include "ConsoleLogger.h"#include class Test: public Toasty::BehaviourComponent{public: Test(Toasty::GameSystem* system, Toasty::Entity* ent) : BehaviourComponent(system, ent) { } virtual ~Test() { } virtual void Initialise() { Toasty::LogInfoPositive(entityAttachedTo->GetName()); } virtual void Shutdown() { } virtual void Update() { Toasty::LogInfo("FPS: " + std::to_string(gameSystem->GetFPS())); }};
and this just logs out the FPS every update of the 'script' and the name of the entity it is attached to, but to actually use it we have to do this:
// Create game system and set it up with a timer. // It automatically gets set with the default ChronoTimer. Toasty::GameSystem* system = new Toasty::GameSystem(); //Create the systems we are going to use. Then add them to the gamesystem we are using. Toasty::BehaviourSystem* behaviourSystem = new Toasty::BehaviourSystem(system); system->AddSystem(behaviourSystem, true); // Create an entity to attach a behaviour component to Toasty::Entity* myFirstEntity = new Toasty::Entity(); // Give the Entity the message system and a UniqueID. gameSystem->AddEntity(myFirstEntity); // Add the test 'script' to the Entity. Test* test = new Test(system, myFirstEntity); behaviourSystem->AddEntity(myFirstEntity, test); // Run the system! system->Run(); // Cleanup delete system; delete test; delete myFirstEntity; delete behaviourSystem;
Notice the cleanup requires you to delete all memory you created? That's by design, at least for the moment I am still contemplating whether or not to use smart pointers to handle memory.
What follows are images of a Windows, Linux and Mac OSX machine (Thankyou cmake!) running this test with an image of their specs and interesting data about the compiled forms on each one.
Windows
Followed by this on the Nth frame:
ToastyGameSystem DLL filesize: 68 Kilobytes
ToastyGameSystem Lib filesize: 72 Kilobytes
ToastySystemTest.exe filesize: 42 Kilobytes
Linux
libToastyGameSystem.so filesize: 103 Kilobytes
ToastySystemTest binary: 19 Kilobytes
Mac OSX
libToastyGameSystem.dylib filesize: 114 kilobytes
ToastySystemTest binary filesize: 25 kilobytes
What's interesting is the performance of the windows computer seems the worst (It doesn't even hit 1k fps!!) even though it has the better specs! Maybe you guys can guess as to why that is...I'll probably give you the answer in the comments later on. On the next post I'll be talking about the coolness of scenes, how they can be used, the simple math side of things, extending the system and things I still need to do before ToastyGameSystem is done!
Hi there. Good to see another Entity/Component framework in the pipeline! I have a few comments I thought of while reading I wanted to share that might provide some improvements.
It might be a little name confusion that you have your whole Framework being called a system (GameSystem in this case) while a "system" is a defined and integral part of your framework already. It's a pretty minor thing, but just something I thought about.
It seems you could reduce some redundancy when creating systems (not GameSystem) considering, currently, you have to pass the GameSystem to the system's constructor, but you also have to call AddSystem(). I would think AddSystem() could pass the GameSystem being used to the system (lots of "systems" heh).
Similar thing with Components, you pass the entity and system to the Component's Constructor, then call GameSystem::AddEntity() and pass both the entity and component. I think you should be able to emove so many references to systems and entity.
BTW, when adding multiple components, do you continually call AddEntity() with the entity and Component? I would think you should call AddComponent() to the entity (or, the reverse, AddEntity() to the Component), and only once call the GameSystem::AddEntity() with the give entity.
You might want to provide a GameSystem::Update() as well as a ::Run() if some user would want to do things outside the Toasty Game Engine.
Also, you might want to provide built in methods of creating objects in your system instead of relying on new and delete (ex: GameSystem::CreateEntity(), which returns a entity, and GameSystem::DeleteEntity(). It may even use smart pointer under the cover).
Just a few thoughts I had while reading this. I'd love to see how you create your systems as well. Good luck, and keep it up!