I really like how the event driven system sounds and is exactly what I'd prefer to build. Can you give any pointers on how you start building such architecture?
Again, I'm doing this in C#, if it matters.
Well, I'm using C++ and I don't know what extra tools you get in C# at all, unfortunately. This article inspired me a lot for my current event system. What I've done most recently is to wrap an interface such as this around ØMQ, allowing me to fairly safely send complete objects as messages. Using ØMQ allows it to be either thread safe in-process messaging, inter-process or over TCP almost completely fluidly. ØMQ is blazing fast too and optimized to take some million requests a second on large servers but still really easy to use compared to handling classic connection sockets. There's C# api (and many others) but I can't vouch for it. It's also not all that common in games from what I understand.
The basic idea anyway, is to have listener objects registering to a dispatcher object. It's basically the observer pattern, but a bit more centralized. You register to certain 'global' events, rather than all events from a specific object. Any object wanting to send an event does so via the dispatcher. Fight the temptation of making it a singleton though.
Event systems have one downside, especially if you're using one big (or a couple*) central one(s) such as I do. It's not very performance optimized, since you've got a big middle hand between all objects that communicate via it. The flexibility added usually outweighs this problem imo and the more performance intensive algorithms are usually isolated in one of the modules. But keep in mind that they can be saturated if there's a lot going on in a real time game.
I'm using two dispatchers in my current project. One for strictly local application events, such as window, input and inter-module communication (I've got a server object running on it's own thread). The other one is for the game events which the rendering and network listens to.
There's also a few varieties of how and when to handle the messages. You can put messages into a queue and poll them when it's the objects turn to update. This is what I do with my ØMQ version. This means the queue has to be handled at regular intervals. A second and not very thread safe way is to make them call instantly. My first did so. The message handling took place inside the send method of the dispatcher, as it looked up and triggered the event handlers of the listeners. It's a matter of taste and need which you'll chose.
Since you're not familiar with the concept yet I'd recommend not to use ØMQ or threads for your first few projects. Make a simple 'instant' message dispatcher and tweak it until you're happy with what it's capable of doing. When you feel more comfortable with that, move on to the more advanced and flexible ones.