GDR Forum Index
Podcast Podcast
Dev Dev Logs
Search Search
RSS RSS
Register Register
Log in Log in
Reply to topic GDR Forum Index -> Game Developer's Refuge -> Development Log - My current unnamed project
View previous topic :: View next topic  
Author Message
JonA
Moderator

Joined: 24 Aug 2005
Posts: 396
Location: UK
PostPosted: Fri Nov 16, 2007 3:57 am    Post subject: Development Log - My current unnamed project Reply with quote

So finally after a really busy time at work, I'm starting to get a little more time for thinking about game development.

While I've not really fleshed out the game concept totally, I've been thinking for a while about writing a turn-based strategy game. I'm aiming after a game which I was quite fond of when I ws a kid, Task Force (http://hol.abime.net/5205/screenshot). Quite an obscure game, but I remember it being very good for the time. I want to retain the 2D, tile-based style, but with a few twists.

Interestingly, the original developer of Task Force, Mark Sheeky, is an active self-publishing indie developer, and he has developed a nice fully 3D version of Task Force for Windows. His site is pretty interesting, so take a look: http://www.cornutopia.net/

I checked out my engine code for this game for the first time in about 9 months, and I've decided that some parts of it need a major rewrite. Part of the problem with getting paid to write Python is that I'm always learning new stuff, which means that any old code starts to look crusty very quickly.

Part of what I want to achieve with this engine is a fully destructable environment. In the original game, the only destructable inanimate objects were doors, but I think that it would be much more fun to be able to demolish areas at will (depending on whether you have enough firepower, of course). For this to work properly, I need a fairly robust message passing system.

I've been reading a lot recently about strong-object systems, like Smalltalk (and, I thnk, Objective C), where messages are passed from object to object, and each object can nominate a number of subscribers. While I'm using Python, I can implment most of this with a kind of mutant Observer pattern (http://en.wikipedia.org/wiki/Observer_pattern). With the code below, I can pass messages to a subscribed set of objects, which can then define their own behaviour for passing messages further. This propogation of events will, for example, allow a burning fuel drum to set the surrounding grass on fire, which will hurt any organic objects nearby, which will cause them to flee, and suchlike. Or at least, that is the goal.

The code below if fairly self explaintory, but it comes with a set of tests anyway to demonstrate how this will work. Basically, each object can nominate a set or receiving methods for each message, and that message can be sent forwards from that point:

    1) Object fuel drum receives message "You're on fire"
    2) Object fuel drum looks up that message destination in it's message_dispatchers list, and calls a nominated method
    3) That method then sends on the message to the grass object
    4) GOTO (2), until we run out of subscribers who can interpret a message. For example, a concrete wall object will not have a definition for "You're on fire", do it will disregard the message, and propogation will stop.
I'm happy to admit that none of this is ground-breaking stuff, but it is quite satisfying when your base class is elegant.

Code:

#!/usr/bin/env python
# encoding: utf-8
"""
engineobject.py

Created by Jon Atkinson on 2007-11-15.
Copyright (c) 2007 Jon Atkinson. All rights reserved.
"""

import unittest

class EngineObject:
    """ The engine object represents the most basic class from which all others are derived.
    As part of the state engine for the game, all other in-game objects can 'subscribe' to
    any other in-game objects, which allows messages to be broadcast and passed between them."""

    # This is an array of the subscribers to a given object.
    subscribers = []
   
    # This is a dictionary of methods to call for each given message.
    message_dispatcher = {'foo': 'test_function',
                          'bar': 'test_dictionary_function'}

    def subscribe(self, object):
        """ This function subscribes this object to another object."""
        object.add_subscriber(self)

    def unsubscribe(self, object):
        """ This function unsubscribes this object from another."""
        object.remove_subscriber(self)

    def remove_subscriber(self, object):
        """ This function removes a subscriber from the subscriber list of this object."""
        for subscriber in self.subscribers:
            if subscriber == object:
                self.subscribers.remove(object)

    def add_subscriber(self, subscriber):
        """ This function adds a subscriber to the subscriber list of this object."""
        self.subscribers.append(subscriber)
   
    def broadcast(self, message):
        """ This function sends a message to all subscribed objects."""
        for subscriber in self.subscribers:
            subscriber.receive(message)
        # Return *this* object. This is useful so we can chain messages together.
        return self
           
    def receive(self, message):
        """ This function receives messages from other objects, and dispatches the message
        to the appropriate function. If a given function isn't specified, we send it to a generic
        message processor, which commonly does nothing at all :-) """
       
        try:
            # Test if we're dealing with a dictionary message...
            if type(message) is dict:
                # Dictionaries must have at least a single key, or we have nowhere to send the message to.
                try:
                    message_destination = message.keys()[0]
                # If they don't have a key, we just abort the message. This should probably be logged in
                # future.
                except IndexError:
                    return False
                # Dispatch the message.
                if message_destination in self.message_dispatcher:
                    getattr(self, self.message_dispatcher[message_destination])(message[message_destination])
            # ... or a string message.
            elif type(message) is str:
                # Dispatch the message.
                if message in self.message_dispatcher:
                    getattr(self, self.message_dispatcher[message])(message)
            else:
                raise AttributeError
        # If a destination function for the message is not specified (in self.message_dispatcher), then
        # send it to the generic receive function.
        except AttributeError:
            self.generic_receive(message)

    def generic_receive(self, message):
        """ This is our generic message recipient. It prints the message to the console."""
        print "generic: %s" % (self, message)

    def test_function(self, message):
        """ This is a test function which will display a normal string message when received."""
        print "string_test: %s" % message
   
    def test_dictionary_function(self, message):
        """ This is a test function which receives dictionary messages."""
        print "dict_test: %s" % message

class EngineObjectTests(unittest.TestCase):
    """ This is a unit test for the EngineObject."""

    def setUp(self):
        """ We'll use three objects for testing."""
        self.a = EngineObject()
        self.b = EngineObject()
        self.c = EngineObject()
        self.d = EngineObject()
       
    def test_subscribe(self):
        """ B, C and D subscribe to A."""
        self.b.subscribe(self.a)
        self.c.subscribe(self.a)
        self.d.subscribe(self.a)

        # Test is the subscription was successful.
        self.assertEqual(self.b in self.a.subscribers, True)
        self.assertEqual(self.c in self.a.subscribers, True)
        self.assertEqual(self.d in self.a.subscribers, True)
       
    def test_unsubscribe(self):
        """ D unsubscribes from A"""
        self.d.unsubscribe(self.a)
       
        # Test that is it no longer subscribed.
        self.assertEqual(self.d in self.a.subscribers, False)

    def test_broadcast(self):
        """ Real tests of the sending and receiving of messages will need to be performed
        on the derived classes, however this at least demonstrates the chainability of messages."""
        self.a.broadcast("foo").broadcast({"bar": ['message', 'payload', 'here']})

    def test_broadcast_error(self):
        """ A sends an invalid message to B, containing an empty dictionary, an empty list, and
        an integer."""
        self.assertRaises(AttributeError, self.a.broadcast({}))
        self.assertRaises(AttributeError, self.a.broadcast([]))
        self.assertRaises(AttributeError, self.a.broadcast(1))

if __name__ == '__main__':
    unittest.main()
Anyway, I plan on throwing up some classes to this thread as they become interesting, or I'm pleased with some code. It's nice to be back spending some time playing with game development properly. I've not even given a though to getting anything on the screen yet (and I much more enjoy playing in my world-modelling sandbox), but I'm considering dropping my old friend pygame for Pyglet. It's just gone 1.0, and it has no dependencies - which was always a headache when I was writing pygame code; the SDL dependencies were a beast to deal with.

Cheers,

--Jon
_________________
JonAtkinson.co.uk


Edited by JonA on Sat Dec 15, 2007 10:34 am; edited 1 time
View user's profile Send private message Visit poster's website
sonrisu
Moderator

Joined: 31 Aug 2005
Posts: 4988
Location: Silicon Valley!
PostPosted: Fri Nov 16, 2007 5:07 am    Post subject: Reply with quote

JonA wrote:
1) Object fuel drum receives message "You're on fire"
2) Object fuel drum looks up that message destination in it's message_dispatchers list, and calls a nominated method
3) That method then sends on the message to the grass object
4) GOTO (2), until we run out of subscribers who can interpret a message. For example, a concrete wall object will not have a definition for "You're on fire", do it will disregard the message, and propogation will stop.

Sounds pretty awesome. Python seems like a pretty good language to easily implement this. One thing I noticed, however, is that if a fuel drum is on fire, and surrounded by a patch of grass, with the current setup you have I'd expect that the entire patch of grass would burn up in the fire. That is, while the propagation looks like it would work fine, I'm wondering how you might go about implementing some sort of decay. That is, the propagation doesn't always propagate at full force, and it is slimmed down each time it moves from one object to another. With that setup, you could have the oil drum burst and some of the grass gets burned up as well, but not all of it.

I didn't get a chance to fully dissect the code you posted here, so maybe you have plans for this.
_________________
loomsoft :]
View user's profile Send private message Visit poster's website
Bean
Admin

Joined: 20 Aug 2005
Posts: 3769

PostPosted: Fri Nov 16, 2007 5:46 am    Post subject: Reply with quote

I never thought of doing this with a messaging system. It's an interesting design. And I think it could work well.
You mentioned this would be tile based so I'd assume in this example that the grass wouldn't be one big patch, but more like a bunch of tiles, each their own object. So over time you'd see the fire spread out through the grass. Like sonrisu mentioned, there should be a limit to the number of objects the fire can jump or something to that effect.

I've got heat system in my engine but it may be more complex then what your game is after. Every object has it's own var for temp. Events in the game can make that higher or lower. Over time it will always balance out to whatever the environment temp is (which can also change at any time). Each type of material in the game has a pre-defined burning_temp. Any time an object's temp is above their material's burning_temp, it applies damage (and visual effects).

-Bean
_________________
Kevin Reems | Nuclear Playground | Solid Driver
View user's profile Send private message Visit poster's website
JonA
Moderator

Joined: 24 Aug 2005
Posts: 396
Location: UK
PostPosted: Fri Nov 16, 2007 5:56 am    Post subject: Reply with quote

This is possibly where my excessive object orientation can come into question, but I was planning on implementing some passed messages as an Effect object.

You you check out the code above, you can see that I can pass messages either as strings (so, stuff like "die", "delete" etc), but I can also pass a small hash (technically, it's called a dictionary in Python), with a key (which dispatches the message to a particular function), and an associated object, which would be an Effect, which can do several things down the line.

From my previous version of this engine, which I will probably use as a template, I have a class heirarchy something like this:

Code:
              EngineObject
                 Thing
    Actor                     Prop
    Person                    Wall


As in my first post, a Person would respond to a 'fire!' message, while a wall would not, but if I pass the message as an Effect object, then I can implement decay quite easily, as the Effect object can modify itself as it is passed around; the first pass it may have a damage level of 20, on the second pass 15 etc. Again, it's not rocket-science, but it is a nice system. So nice, that I'm going to write it now :-)

--Jon
_________________
JonAtkinson.co.uk
View user's profile Send private message Visit poster's website
JonA
Moderator

Joined: 24 Aug 2005
Posts: 396
Location: UK
PostPosted: Fri Nov 16, 2007 5:59 am    Post subject: Reply with quote

Bean wrote:
Each type of material in the game has a pre-defined burning_temp. Any time an object's temp is above their material's burning_temp, it applies damage (and visual effects).


It's interesting that you've gone for a material system, I didn't think of doing that. I know from exposure to it over the years how flexible your engine is, but do you not find yourself defining a lot of one-off materials for a specific type of object? Or can you do funky stuff like combining base materials or create new ones?

--Jon
_________________
JonAtkinson.co.uk
View user's profile Send private message Visit poster's website
JonA
Moderator

Joined: 24 Aug 2005
Posts: 396
Location: UK
PostPosted: Fri Nov 23, 2007 12:09 pm    Post subject: Reply with quote

So, I've done a little more on the message passing system. I decided to totally cut out passing ascii messages, as this would require both composition and the comprehension of the message by the recipient. So I've decided just to pass around Effect objects, which can effect the attributes of the recipient object, and they decay at a given rate (currently in undefined units of time, I'll figure that out when I have a clock ticking).

Again, it's nothing particularly impressive, but it's an tight solution. Code and example:

Code:
#!/usr/bin/env python
# encoding: utf-8
"""
effect.py

Created by Jon Atkinson on 2007-11-16.
Copyright (c) 2007 Jon Atkinson. All rights reserved.
"""

import unittest

from thing import *

class Effect:
   
    # This is some test data which kind of models a 'fire effect' - pain and burning.
    effects = [{'property': 'hp', 'modifier': -10, 'time': 3},
               {'property': 'temperature', 'modifier': 80, 'time': 1},
               {'property': 'foo', 'modifier': 20, 'time': 1}]
   
    def apply(self, object):
        for effect in self.effects:
            try:
                setattr(object, effect['property'], (getattr(object, effect['property']) + effect['modifier']))
            except AttributeError:
                print "Effect %s: Target %s has no attribute %s" % (self, object, effect['property'])
                continue

class EffectTests(unittest.TestCase):
    def setUp(self):
        """ Set up a generic thing, and a generic fire object. Initialise an effect, then
        pass the effect between them both."""
        self.thing = Thing()
        self.fire = Thing()
        self.fire_effect = Effect()
               
    def test_apply(self):
        """ This tests the application of effects. Our basic effect, which has a temperature and a hp effect
        is applied to two generic Things. """
       
        # Get the starting temperatures.
        initial_hp = self.thing.hp
        initial_temp = self.thing.temperature

        # Subscribe to messages from the fire.
        self.thing.subscribe(self.fire)
       
        # Broadcast the fire effect.
        self.fire.broadcast(self.fire_effect)

        # Check that the effects were applied.
        self.assertEquals(self.thing.hp, (initial_hp - 10))
        self.assertEquals(self.thing.temperature, (initial_temp + 80))

if __name__ == '__main__':
    unittest.main()


Cheers,

-Jon
_________________
JonAtkinson.co.uk
View user's profile Send private message Visit poster's website
sonrisu
Moderator

Joined: 31 Aug 2005
Posts: 4988
Location: Silicon Valley!
PostPosted: Fri Nov 23, 2007 12:25 pm    Post subject: Reply with quote

Unit testing: awesome!
_________________
loomsoft :]
View user's profile Send private message Visit poster's website
JonA
Moderator

Joined: 24 Aug 2005
Posts: 396
Location: UK
PostPosted: Fri Nov 23, 2007 2:01 pm    Post subject: Reply with quote

Unit testing: Hell yes.

If I'm working on anything other than a one-file script, I'm writing tests. I don't want to come off like one of those horribly pious agile programmers, but if you're not writing tests, your code will have orders of magnitude more bugs than if you had.

I know that I work with higher level languages than a lot of people on this board, which makes it far easier for my to use a testing framework (well, you can see how easy it is from the code above); but what do other people do? Does CUnit even exist? Or are you building game data which tests the engine as you go along?

--Jon
_________________
JonAtkinson.co.uk
View user's profile Send private message Visit poster's website
sonrisu
Moderator

Joined: 31 Aug 2005
Posts: 4988
Location: Silicon Valley!
PostPosted: Fri Nov 23, 2007 10:24 pm    Post subject: Reply with quote

I usually have some sort of object printing methods that dump a textual representation of what the object looks like which can be analyzed to ensure that it looks right. Honestly, I'm too lazy when it comes to this. What I should really do is write a tester that takes the output from the live object and tests it against a known, hand-crafted version of what should be the same output and compare the two together to ensure they match. :)

I'd actually be interested in some insight into best practices for proper unit style testing in C/C++.
_________________
loomsoft :]
View user's profile Send private message Visit poster's website
Reply to topic GDR Forum Index -> Game Developer's Refuge -> Development Log - My current unnamed project
Game Developer's Refuge
is proudly hosted by,

HostGator

All trademarks and copyrights on this page are owned by their respective owners. All comments owned by their respective posters.
phpBB code © 2001, 2005 phpBB Group. Other message board code © Kevin Reems.