Creating A 'Combat Log' System

Started by
2 comments, last by Strewya 10 years, 8 months ago

Hello!

I am currently making a turn based game written in Java. I would like to implement a 'Combat Log' feature that displays what is currently happening in the game (e.g. "Orc hits you for X damage!") . The game will not utilize the console, so I can not just print these messages to the console as they occur. I was studying the Observer Pattern and I think that I can use it to my advantage; however, I am not sure how I should implement the Observer into my system. I have the UML for three different approaches, and I would like to know which one (if any) you all would recommend. The classes that I have provided in the following UML are simplified so that I can get my point across without wasting too much of your time:

1. Static Observer

The first thought I had was making the Observer class static. The Observer class would have a private static member variable in the form of a collection; the collection would be used to store each message. The Observer class would also have a public static method used to add a new message to the collection and a public static method used to retrieve the collection. I am really not fond of this design because it reminds me of a global variable !

Static Observer UML here !

2. Singleton Observer

The second design I thought of was implementing the Observer class as a Singleton. A Singleton version of the Observer class would have a collection in the form of an instance variable instead of a static variable. I like this design more than the Static Observer design that I previously explained, but it still makes me feel like I am cheating. I have also read that most people are not very fond of the Singleton pattern for various reasons.

Singleton Observer UML here !

3. 'Has-A' Observer

The third design that I have contemplated is having the Observer be a part of the class composition of each class that it needs to observe. This design seems like it follows the Observer Pattern to a better degree. My only concern with this design is the number of objects that the Observer will be observing. There will be 8 x Hero objects to observe, and there will be 64 x Skill objects to observe; there will eventually be many conditional effects on each of the Heros that I think the Observer will need to keep an eye on as well. Should I worry about the number of objects that the Observer is watching?

'Has-A' Observer UML here !

So, these are the three designs that I have drawn up to help me solve this problem. I would appreciate any insight on which one of these may be the best option to use (or an alternative if anyone has an ideas). Thank you for your help, and I hope that I made my thoughts clear to you.

Advertisement

The first two aren't really that much different IMHO. You still have a statically acquired instance on which you call the method to add messages. Singletons are global variables in disguise, so if you don't like the first approach because it remind you of a global variable (which it is), what makes you like the second any more?

The third approach is a step in the right direction.

Ask yourself if the observer really needs to know all the conditional stuff about the Hero or Skill, which couples it to those classes.

A better way would be to make the Observer an interface, and implement it in a CombatObserver. Also, to make it work, the addMessage method should have a messageType discriminator, which can be Combat, Trade, ExperienceGain (or what have you), and each inherited observer class only listens to a certain type of message, and logs it to the appropriate GUI element. And in your Hero and Skill classes, have a list of Observers (the interfaces) which were registered to listen to events in that class instance. Every time you do something you might want to log, simply invoke the addMessage on all observers with the type of the message and its contents, and let them decide if they should log it or not. This decouples your Hero/Skill/other classes from knowing what observer is listening to them, and makes it possible for more than one observer to listen to messages from that class instance.

A full blown solution might be implementing some sort of event system, so the observer simply subscribes to events that you classify as "combat event" and logs them appropriately, which decouples your Hero and Skill classes from even knowing they are being observed.

devstropo.blogspot.com - Random stuff about my gamedev hobby


And in your Hero and Skill classes, have a list of Observers (the interfaces) which were registered to listen to events in that class instance.

So you saying have a static list in the classes (so I guess like a scoped global), or have a per instance list and some way to populate it without a global (perhaps a UnitCreated observer setup at the start of the game/level/map/whatever)?

One thing I found to be helpful, although more difficult if you want to have an event queue, was to have different methods for stuff, e.g. rather than "addMessage(type, parama, paramb)" or "addMessage(conts Message *message)" where Message can have derived types, is to just have like "unitKilled(const Unit *uint, const Unit *by, const Weapon *with)" and other specific things. Not sure what the standard way is as such, seen all three around. The first one I have only seen for Win32 events, and is a pain because in complex cases people start bitpacking the parameters, or say if type == foo then paramb is actually a pointer...


And in your Hero and Skill classes, have a list of Observers (the interfaces) which were registered to listen to events in that class instance.

So you saying have a static list in the classes (so I guess like a scoped global), or have a per instance list and some way to populate it without a global (perhaps a UnitCreated observer setup at the start of the game/level/map/whatever)?

One thing I found to be helpful, although more difficult if you want to have an event queue, was to have different methods for stuff, e.g. rather than "addMessage(type, parama, paramb)" or "addMessage(conts Message *message)" where Message can have derived types, is to just have like "unitKilled(const Unit *uint, const Unit *by, const Weapon *with)" and other specific things. Not sure what the standard way is as such, seen all three around. The first one I have only seen for Win32 events, and is a pain because in complex cases people start bitpacking the parameters, or say if type == foo then paramb is actually a pointer...

No, have it on a per instance basis, and fill it up in a factory, factory method or a special method that creates these objects for you. Maybe at some point you want to create a Hero instance, but not listen to any of it's combat logs? Simply don't register any observers. A static list might be useful if you want to listen to every combat (or any other type) event regardless of instance, but IMO that makes the system inflexible.

As for having specific functions for logging specific events, i see that as an evolution of the simple method addMessage(type, content), but it makes your event logging very specific and closed for reuse an another game. Passing a Message* into the method makes it a bit more generic if the Message class is an interface with a getEventString() method which the observer implementation can call to get the log text, again, not caring of what the text actually is.

I believe that every class should be made to know as little about other classes or their specific implementation as possible. You can do it either by using interfaces (thus only depending on the interface) or by passing events around, and dispatching them to handlers, again, via interfaces.

devstropo.blogspot.com - Random stuff about my gamedev hobby

This topic is closed to new replies.

Advertisement