BGE: passing arguments to __init__() when subclassing a KX_GameObject

Extending the KX_GameObject is a powerful way to add extra functionality to game objects. Once built, custom classes can speed up development while maintaining a tidy syntax. However, difficulties arise when trying to pass additional arguments to a subclass of the KX_GameObject. This tutorial looks at the various ways this can be overcome.

The problem

Lets’s take a simple character class as an example:

import bge

own = bge.logic.getCurrentController().owner

class character(bge.types.KX_GameObject):
    
    def __init__(self, own):
        self.stamina = 10
        self.strength = 12
        self.defense = 8
        self.agility = 11

    def printStats(self):
        print(self.stamina)
        print(self.strength)
        print(self.defence)
        print(self.agility)

player = character(own)

What happens what when don’t want to hard-code values into the class but want to pass them when we create the object? For example:

class character(bge.types.KX_GameObject):
    
    def __init__(self, own, stamina, strength, defence, agility):
        self.stamina = stamina
        self.strength = strength
        self.defence = defence
        self.agility = agility

player = character(own, 14, 13, 15, 7)

We get the error: “TypeError: Base PyObjectPlus() takes exactly 1 argument (5 given)”.

So long as we’re only passing the owner to a class that extends KX_GameObject we’ve got a co-operative inheritance tree and everything works as expected. But once our custom class and KX_GameObject has different __init__() signatures errors start to get raised. So how do we get around this? I’ll start by looking at some simple methods then move to the more complex or hackier ways of doing things.

Using 2 initialisers

The most straight forward method is to not try and pass any arguments to __init__() other than the owner and use a separate function to initialise our values:

class character2(bge.types.KX_GameObject):
    
    def __init__(self, own):
        # nothing happens here
        pass
    
    def initialise(self, stamina, strength, defence, agility):
        self.stamina = stamina
        self.strength = strength
        self.defence = defence
        self.agility = agility

player = character2(own)
player.initialise(8, 9, 12, 14)

However, we must ensure that own initialise() function is called before any other member function. If not, there is the potential for a member function to try and use variables that do not exist. The same applies if we try and subclass our custom class. If you were writing a library for the BGE and expecting users to extend the custom game object classes you’ve provided you’d need to make this clear to them.

Using keyword arguments

Another option is to have all the values you plan to use initialise in you __init__() definition, then pass them by name:

class character3(bge.types.KX_GameObject):
    
    def __init__(self, own, stamina, strength, defence, agility):
        self.stamina = stamina
        self.strength = strength
        self.defence = defence
        self.agility = agility

player = character3(own, stamina=12, strength=10, defence=14, agility=9)

Passing values to custom classes is starting to look a little cleaner than our previous attempt. This method will work well when you’ve just got a couple of values to pass, but once you’ve got lots of values it’ll start to get a little ugly.

The other draw back is that it requires the class user to know the names of the variables they’re passing.

To tidy things up a little we could put all our values in a dictionary and then unpack it as a keyword argument:

stats = {'stamina' : 12,
        'strength' : 11,
        'defence' : 14,
        'agility' : 15}

player = character3(own, **stats)

In this particular example, having a dictionary for character stats is a reasonably tidy way to store and pass them. Plus it would make the easier to save/load (think configparser here). But for many other custom classes it wouldn’t make much sense. For instance, having lots of dicts for a particle class’s starting values. So lets move on.

Using a custom constructor

__init__() is not a class constructor, it simply initialises values for us. When creating classes in python it provides us with a default class constructor so that we don’t have to worry about it. But we can define our own when we need to by overriding __new__(). This allows us to control how a class is created and is called before __init__(), so we can control what variables get passed to our custom class’s base class:

class character4(bge.types.KX_GameObject):
    
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, own)
    
    def __init__(self, own, stamina, strength, defence, agility):
        self.stamina = stamina
        self.strength = strength
        self.defence = defence
        self.agility = agility
        
player = character4(own, 8, 7, 15, 16)

Here, __new__() passes the owner onto to our KX_GameObject base class and prevents the other variables from being passed to KX_GameObject. Personally, I like this solution the most. It’s clean, only adds 2 more lines, and allows you pass arguments as you sorta would expect.

However, we’re move into the hackier solutions now. Overriding __new__() sits in the realm of python magic and has a lot of powerful effects (such as being able to return an entirely different object to the one being created). When there are other solutions available it might be best to avoid.

Using an adapter class

This method introduces another class to bridge the __init__() signature between a custom class and the KX_GameObject:

class gameObProxy():
    
    def __init__(self, own):
        self.ob = bge.types.KX_GameObject(own)

class character5(gameObProxy):
        
    def __init__(self, own, stamina, strength, defence, agility):
        super(character5, self).__init__(own)
        
        self.stamina = stamina
        self.strength = strength
        self.defence = defence
        self.agility = agility

player = character5(own, 8, 7, 15, 16)

The proxy class holds the reference to the game object owner and our custom class is does not inherit directly from the KX_GameObject. When we initialise our custom class we deliberately call the proxy class’s initialiser so that we can pass the owner to it. This results in some interesting features. Firstly, inside the class to access KX_GameObject methods we need to go through the variable holding it first:

    def move(self):
        self.ob.applyMovement(self.vec)
        # note, self.ob from the proxy class

Which means outside of the class we can’t access the KX_GameObject methods, without going through the variable holding the owner either. Now, there aren’t any private attributes in python. But lets pretend for a second that there are (say we called our holding variable self._ob to indicate it’s meant to be private). What this means is the custom class interface can provide the user only the functions the class was intended for, while obscuring the other KX_GameObject methods. For instance, if creating a custom particle class it could hide things like changing the dynamics or re-instancing the mesh, or something that might break the particle system.

Using a factory pattern

Rather than introduce another class into our inheritance tree, like the previous example. We can create a class who’s job it is to create classes. This class can then create an extended game object class and initialise values accordingly:

class characterCreator:
    
    @classmethod 
    def make(cls, toMake, own, *args, **kwargs):
        if toMake == wizard:
            obj = toMake(own)
            obj.stamina = args[0]
            obj.strength = args[1]
            obj.defence = args[2]
            obj.agility = args[3]
            obj.intelligence = args[4]
            return obj
        if toMake == warrior:
            obj = toMake(own)
            obj.stamina = args[0]
            obj.strength = args[1]
            obj.defence = args[2]
            obj.agility = args[3]
            obj.rage = args[4]
            obj.armour = args[5]
            return obj
        else:
            return None

class wizard(bge.types.KX_GameObject):
        
    def __init__(self, own):
        # there's nothing to do here
        # our factor takes care of this
        pass

class warrior(bge.types.KX_GameObject):
        
    def __init__(self, own):
        # there's nothing to do here
        # our factor takes care of this
        pass

player = characterCreator.make(wizard, own, 7, 6, 8, 9, 16)

Each time we want to create a new object we go through the factory rather than creating the object directly. This has the advantage of separating the class interface from the implementation. For example, say you have a particle class that stores a movement vector. That vector might want to be randomly determined, or within a particular range or a fixed value. Rather than bog the particle class down with a lengthy initialiser and interface we can let the factory handle that while keeping the particle class light-weight.

The above example is just a simple demonstration, there’s a lot we could do on object creation. Let’s say you’re using a factory pattern to create enemies, after initialising their values you could also run their climb out of the ground animation to get them started.

The other thing we can do with a factory method is register the created object to a container, so we can keep track of objects in play.

class characterCreator2:
    characterCreator.enemies = []
    
    @classmethod 
    def make(cls, toMake, own, *args, **kwargs):
        if toMake == spider:
            obj = toMake(own)
            obj.stamina = args[0]
            obj.strength = args[1]
            obj.defence = args[2]
            obj.agility = args[3]
            obj.intelligence = args[4]
            # keep track of added enemies
            characterCreator.enemies.append(obj)
            return obj
        ## add various other enemy types

        else:
            return None

Of course, you’ll then need to add some way to keep the list of registered objects up-to-date. One way is to have a delete/unregister method, so when an object is deleted it is done through the factory.

Wrapping up

This tutorial has covered a number of methods you can use to extend the KX_GameObject and work around problems initialising values. Which method is best will largely depend on what you are trying to do. If you’ve got a simple class with a few parameters, probably use key words. If you’re creating lots of objects, you may wish to use a factory. If you want to provide a very particular interface, then go with an adapter class. If you’ve got a class with a number of parameters, then it may be tidier to override the constructor. And I can’t think of a reason to use 2 initialisers when there are better solutions.

If you’ve got any other ways to pass multiple parameters to __init__() when extending the KX_GameObject leave them in the comments below. And remember to like and follow!

Advertisements

~ by Jay on February 1, 2015.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

 
%d bloggers like this: