Love expects scripts to be placed in a folder. Save the script inside a folder and name the script main.lua
, then you can drag the entire folder and drop it on the Love executable to run the contained script(s). It's a bit odd, but it works.
At any rate, one of the reasons I prefer a higher level language like Lua for systems like this is that some things can get hairy under C++. Dynamic typing can smooth out a lot of issues. However, I went ahead and did an example implementation of the system I presented, using C++ and SFML, for demonstration purposes. Note that it is a direct port of the system; I didn't stop to build a better collision system, so the problems that exist in the Lua version (wonky collision, ball getting trapped inside moving paddles, sometimes sticking to walls, etc... like I said, it is not robust at all) still exist. This is meant to be demonstration code, not production code, and if I were making production code I would implement a better broadphase collision system, and implement the collision plane detection differently. Anyway...
Before I present the code, some disclaimers. I don't do higher level programming like this in C++ anymore. I just do low level stuff (scene graphs, etc...) So my C++ skills are very rusty. Also, I implemented a very quick hack of a generic message passing system that operates by encoding a message as a string. This way, I can implement a generic interface for components to handle messages, and leave it up to the components themselves (and any calling parties) to worry about the specific content of the message, without having to build some sort of weird templated class to hold messages or anything. It's probably not the optimal way of doing it, since it requires several string manipulation operations, but it works. Since I don't do this kind of thing in C++, I have no desire to build a generic message passing system in C++.
To begin with, I needed to create some base classes. As in the Lua example, the basic architecture is that any SubSystem (Physics, Collision, etc) implements a corresponding component type. So if you want your object to have a visual representation, you have to call the Graphics Subsystem to create a Graphics Component, and add the component to the object. Subsystems maintain internal vectors of the components that have been created, and their update()
methods will simply iterate this internal vector and operate on the components. Doing it this way can have implications as far as cache coherency and data-orientedness are concerned; ie, you can implement your internal component list as a simple flat array of data which is processed sequentially, resulting in fewer cache misses and possibly significant increases in performance.
The implementation I present here, however, does not present the optimal method. Rather than a flat array of data structures, I implement the internal component lists as arrays of pointers
to data structures, and the components are dynamically allocated and freed in the createComponent()
methods. Since objects maintain internal pointers to their components for message-passing, it was necessary that the pointers never change; if the subsystem component list is implemented as a dynamic array, re-allocations will break the object's internal component pointers, causing segfaults. Ways around this might include using smart pointers or other more complex techniques. I was not interested in solving this problem here because, again, I don't spend much time in C++. There are many, many different ways of achieving the same kind of architecture.
All Components must inherit from the BaseComponent class, which presents the common interface all components share. This interface is simple, consisting of the kill()
All Subsystems inherit from the BaseSubsystem class, which presents the common interface all subsystems share. This interface is also very simple, consisting only of the removeComponent()
method. Note that I do not include createComponent()
in the common interface, but rather implement it at the specific subsystem level. This is because the parameters passed to createComponent()
vary depending on component type, and this method is not called in any of the base classes so it is not necessary it be known at the interface level.
All objects will be of the type BaseObject. This is not a class that is inherited from; all objects in the world will be instances of this class itself. It implements the basic interface of an object, including kill()
, message() addComponent()
To see what I'm talking about, here is the base framework:
The BaseComponent and BaseSubsystem classes are pure virtuals, so they must be derived from. However, there does not need to be any deep hierarchies; a single level of derivation is all that is necessary. We just need a common interface for these classes.
With these base systems in place, we can start constructing the subsystems. Here is the graphics subsystem and associated component:
This system works pretty much exactly as in the Lua example, though it uses SFML under the hood. The GraphicsComponent is a simple rectangle of size (width,height), and the owner object is queried for position when drawing. Components are iterated and drawn as simple white rectangles.
Here is the Collision subsystem:
Again, it's a direct port of the Lua version (with attendant problems). Of particular note, however, are the parts in update()
where a message is constructed and passed. The message is built as a string using stringstream to serialize the required data and send it along. This is a hack, but it at least eliminates any kind of linkage dependency between the components. There is a meta-dependency, in that both the message passer and receiver need to know the data layout of the message, but this dependency is separate from any compile or link-time dependencies.
The other components as in the original Lua example:
Of course, you need a kernel or game loop. I implemented a quickie that sets up the systems, creates the walls, ball and paddle objects, then executes a loop similar to what Love provides. There is a load()
function. In main, an SFML window is created then execution is handed off to the kernel()
which calls load()
, then enters a loop until the window is closed.
See the load()
function for how the objects are created. As in the Lua demo, it creates 4 walls, a ball and 2 paddles.
And that's it. It works (I tested it), but it is not an exhaustively tested setup, so I wouldn't trust it without further testing. Still, it demonstrates the same concepts as the Lua example. Hopefully it helps.
Edited by FLeBlanc, 30 April 2012 - 11:20 AM.