A problem we have been having in the development of Tribal Aeronauts is the problem with getting assets into the engine. Ir's rendering engine is extremely flexible, but is limited in the formats that can be used. Ir's rendering engine is based off of another rendering engine called Horde3D, which uses the Collada format as an intermediate format. Converting this to a format the engine can use is accomplished by running the asset through an intermediate tool. As it stands, it currently only exists in command line form. I should have realized from the get-go that this was not going to go well.
There is one thing that I have realized, and that is that most of the time, artists do not have the time or patience to learn how to use the same tools that programmers use and are used to. If our frameworks require special tools, then we can't approach the problem the same way we always do. It is like using Qt's moc from commandline versus from Qt Creator. When artists are involved, it is essential that you automate the workflow as much as you can and provide them a visual method of making sure that the assets they create will appear as they expect them to in-engine.
So to that end, I have been working on an asset conversion tool that I call "ictoanots" [ick-toe-an-ots]... yes it's an acronym. It stands for "I Can't Think of a Name On The Spot". Aren't I just so clever? The experience so far has been rewarding. Conversion of Collada files works, and I am currently in the process of implementing the visualization aspect. I believe it can also be expanded to any other assets that may need to be converted in the future. But this tool may be too little too late as deadlines are fast approaching. This is something I should have been working on from the start. I believe alot of problems could have been solved had a dedicated GUI tool for file conversion existed.
So lesson learned, if it involves artwork and file conversion, just bite the bullet and write a GUI frontend for that thing.
Raging Code-Monkey
A blog containing the ranting and ravings and occasionally insightful observations and commentary from a typical 20-something programmer.
Thursday, October 27, 2011
Wednesday, October 19, 2011
Did You Get That Thing I Sent Ya?
I knew from the start of the design of the Ir Engine that I wanted to do something that game engines haven't seen much of, or at least the engines I have seen on the market, commercial or otherwise. As a Qt abuser, I love the signals and slots paradigm, and I wanted to bring the concept outside the GUI and apply it to inter-object messaging in games; this has been implemented in various forms in my engine each with their own strengths and weaknesses. I thought I would take the time to reflect on the various methods I tried before I settled on the current one.
Note that I will be referencing Angelscript extensively throughout my post, so I suggest that you read up on its concepts here.
My criteria for a messaging interface was the following:
1) Keep the implementation as "developer simple" as possible.
2) Leave as little room for bugs as possible.
3) Utilize existing functionality as much as possible.
4) Be easy to adapt to networking.
That last parameter is key, and a bear to work around. Being able to send messages over a network seems like the perfect way to implement networking in a game engine while remaining flexible.
As an object oriented language, it would be a sin not to bring up the topic of interfaces in this. Angelscript, like Java, allows for single inheritance through interfaces.
interface IDeathListener { void OnObjectDeath( Object@ obj_that_died ); }; interface ISpawnListener { void OnObjectSpawned( Object@ obj_that_spawned ); }; class Player : Object, ISpawnListener, IDeathListener { void OnObjectDeath( Object@ dead_object ) { Println("I SHAL HAVE MY REVENGE FOUL BEAST!"); } void OnObjectSpawn( Object@ dead_object ) { Println("HAHA! I LIVE AGAIN!!!"); } void SetSpawnListener( ISpawnListener@ sl ) { @m_SpawnListener = sl; } void SetDeathListener( IDeathListener@ dl ) { @m_DeathListener = dl; } void ReceiveDamage( uint damage ) { m_Health -= damage; if( m_Health <= 0 && m_DeathListener !is null) m_DeathListener.OnObjectDeath( this ); } void Respawn() { m_Health = 100; m_SpawnListener.OnObjectSpawned( this ); } private uint m_Health; private ISpawnListener@ m_SpawnListener; private IDeathListener@ m_DeathListener; };The idea is then that you have classes that emit these events hold 'handles' (reference counted smart pointers basically) to classes that implement these interfaces. This approach to messaging has the huge advantage that locally it can be implemented in pure Angelscript, with no additional work on the C++ backend required. For a while, this actually worked fairly well. However, when it comes to actually using the paradigm in a real-world scenario, handling all of those interfaces can become extremely unwieldy and inconvenient to maintain. It lacks the conceptual simplicity of other methods. The second iteration of this paradigm came in the form of something similar to Qt's method of signals and slots, minus the meta-object compiler of course. I'll let the code do most of the talking to explain.
class ObjectOne : Object { ObjectOne( Object@ object_to_connect_to ) { @m_ObjectIAmConnectedTo = object_to_connect_to; Connect( m_ObjectIAmConnectedTo, "Signal_Test","Slot_Test" ); } private Object@ m_ObjectIAmConnectedTo; }; class ObjectTwo : Object { void Slot_Test() { Println( "Slot message received" ); } }; void main() { Object@ obj2 = ObjectTwo(); Object@ obj1 = ObjectOne( obj2 ); obj1.Emit("Signal_Test"); }Simple and to the point right? Yeah, I kinda like it to. However, we have a bit of a problem as far as actually implementing this is concerned. The primary problem is that Angelscript is a strongly typed language. Without knowing the types of arguments a slot needs, we are unable to pass any arguments. With Qt, we have the convenient 'emit' (or Q_EMIT, if that's how your roll), and then call a signal like any other function.
emit TheWitchIsDead(time_of_death);The preprocessing involved in such functionality would require more work than I had time for. Finally, I received some inspiration from a book called "Programming Game AI by Design", my Mat Buckland. It is, as you would guess, a book all about the design of AI systems and applying them to videogames. Being the good little programmer I am, I blatantly copied the concepts and applied them to Angelscript and C++, while throwing in a few extra features of my own. The concept of Mat's messaging interface is a simple void pointer passed along with an integer that is enumerated somewhere else in the application, with each object having an OnMessageReceived event.
// we are in C++ now enum Message_Types { DEATH, SPAWN }; class Player : public Object { void OnMessageReceived( int message,void* data ) { switch( message ) { case DEATH: printf("I WILL HAVE MY REVENGE FOUL BEAST!"); break; case SPAWN: printf("HAHAHA! I LIVE AGAIN!"); break; } } }; int main( int ac,char**av) { Object* player = new Player(); Object* god = new Player(); // I AM GOD! MUAHAHAHAHAHAH! god->Dispatch( player,DEATH,(void*)god ); }As cruel as this program is, the concept is just simply amazing, and amazingly simple. While Angelscript may be strongly typed, it does contain an extremely versatile "any" type, which acts as a container for any type of object or primitive you might need. Using the any type as a replacement for the void pointer, we are left with the following.
// Back to angelscript enum Message_Types { DEATH, SPAWN }; class Player : Object { void OnMessageReceived( int message, any@ data ) { switch( message ) { case Message_Types::DEATH: printf("I WILL HAVE MY REVENGE!"); break; case Message_Types::SPAWN: printf("HAHAHA! I LIVE AGAIN!"); break; } } }; int main() { Object@ player = Player(); Object@ god = Player(); // MUAHAHAHAHAHAH! god.Dispatch( player,Message_Types::DEATH,any(@god) ); }This design is so simple that a child could implement it, but it works extremely well. The data can be expanded to entire classes that can hold as much data as needed to get the message across, and even serialized and sent over a network. At the same time, the objects do not need to connect with one another to send messages. Objects can easily send one-off packets of data without worrying about any others. As far as message types are concerned, I have taken that to the Angelscript preprocessor addon. It took only 10 minutes to add to the builder, but greatly simplifies the creating of new message types. Now one simply has to do the following.
// HOLY PREPROCESSOR BATMAN! #message DEATH #message SPAWN class Player : Object { void OnMessageReceived( int message, any@ data ) { switch( message ) { case Messages::DEATH: printf("I WILL HAVE MY REVENGE!"); break; case Messages::SPAWN: printf("HAHAHA! I LIVE AGAIN!"); break; } } }; int main() { Object@ player = Player(); Object@ god = Player(); // MUAHAHAHAHAHAH! god.Dispatch( player,Messages::DEATH,any(@god) ); }
What is more is that it all remains type-safe, as can be seen in the retrieval of the values. It even automatically manages the messages that are available since the types are only added to the internal message enumeration as the preprocessor encounters them. Only the messages that you need are ever compiled in.
Well, I am slowly slipping away into a sleep deprived coma, so until next week. What I will post about... I dunno.
Friday, October 14, 2011
First Poast?
O hi there. Welcome to my blog. Here you will find the ranting, ravings, and occasionally insightful observations and commentary from a typical 20-something programmer.
I am about to graduate with a Bachelors Degree in Computer Science and actually started the blog per the suggestion of my teacher.
I work for a defense contractor, but if I told you my responsibilities, I'd probably have to kill you.
I do have a personal project, and that is mostly what this blog will be about. I will be making posts as I implement new features and think of other things pertaining to it.
About the project, it is a game I am working on with a few friends called Tribal Aeronauts. It is being developed in tandem with a game engine of my own design, so there is always new stuff to do/fix/break. The game itself is an arcadey flight simulator that aims to be faster paced and have an engaging story. I also plan to implement an online multiplayer component that includes a persistent metagame where players form into tribes and compete against each other. This, of course, is much later on down the line.
I am also a huge Linux fan, and an avid supporter of games on Linux. You can be certain that any programs I write will always work on both Windows and Linux. I will be posting about games on Linux later on down the line as well.
I am about to graduate with a Bachelors Degree in Computer Science and actually started the blog per the suggestion of my teacher.
I work for a defense contractor, but if I told you my responsibilities, I'd probably have to kill you.
I do have a personal project, and that is mostly what this blog will be about. I will be making posts as I implement new features and think of other things pertaining to it.
About the project, it is a game I am working on with a few friends called Tribal Aeronauts. It is being developed in tandem with a game engine of my own design, so there is always new stuff to do/fix/break. The game itself is an arcadey flight simulator that aims to be faster paced and have an engaging story. I also plan to implement an online multiplayer component that includes a persistent metagame where players form into tribes and compete against each other. This, of course, is much later on down the line.
I am also a huge Linux fan, and an avid supporter of games on Linux. You can be certain that any programs I write will always work on both Windows and Linux. I will be posting about games on Linux later on down the line as well.
Subscribe to:
Posts (Atom)