Structuring my game Classes

First of all, Hello! Im new to Panda3D, and loving it so far. Gotta love that its Python! (not that I’m a fundi in that regard, but I’ve just started learning it too, and its great)

I am starting to work on a 8-direction run-and-gun game in the spirit of The Chaos Engine. I want to use 8 directions because it is the best control system for 2 players on one PC (and I love the retro feel)

Im trying to decide how to split up all of my code. I think it makes sense to have every character in the game in its own class, all with pretty much the same set of functions, so that the AI or player could “easily” be made to control any of them.

As an extremely basic example structure, does the following seem logical?

Class Player_Character(controller_type)
        Move(direction)
        Shoot()
        Mod_Health(int) #add or subtract health
        Mod_Lives(int) #add or subtract lives
        Collide() #do we need a collide method?
    
Class Enemy_Character(controller_type)
        Move(direction)
        Shoot()
        Mod_Health(int) #add or subtract health
        Mod_Lives(int) #add or subtract lives
        Collide() #do we need a collide method?
        
Class Controller_Player()
        #I think this class will be called inside the character class, and will presumably start up a task that transforms key presses into instructions every frame
    
Class Controller_AI()
       #I think this class will be called inside the character class if it is to be a computer controlled character, and will presumably start up a task, that gives the character instructions every frame

Class Bullet()
        #not sure why I need this just yet, but Im sure I will

I want my code to be as modular as possible, so that if I later develop a better AI or control system or anything along those lines, that I can just swap it out.

I hope this made sense, I find things always become clearer once I have bounced the ideas off people.

your idea of the classes seem good, but you dont use the concept of object oriented programming yet. this could help you reduce the amount of code you have to write (if the Player_Character and the Enemy_Character share the same code)

in oop you’d do something like

class Character:
  move
  shoot
  modHealth
  modLives
  collide

class PlayerCharacter(Character): # <- inherits from character
  move
  # the other functions will be the same as the ones from the character
  # maybe you dont even need move, because the function move is called
  # by the controller anyway

class EnemyCharacter(Character):
  collide
  # same as in the player, you need to know what's different from 
  # the generic character

# the other concept seems reasonable, controllers probably are so different
# inheriting doesnt make sense there

You might want to consider a component based design (sometimes called aggregation). This way, you can avoid duplicating a lot of code between a bunch of similar classes.

The concept is real simple; You assemble objects (aka entities) out of a series of components. These components are separate systems that can either operate independently, or work together. Your objects would inherit from some central component management class that would handle the assignment and cleanup of components. You can then use a messaging queue to pass data back and forth between these components, in case they need to share data.

Let’s say you have a PlayerObject and EnemyObject class that inherits from a generic GameObject class. In your example, you mentioned a health and life management parts. Using pure OOP would require that you shuffle common code between the two sub-classes up into GameObject. This can get out of hand fast. Instead, You can make your PlayerObject and EnemyObject use components, which work regardless of their inheritance. So, you would assign them a Health component, which tracks health. You could then register that Health component to listen to events from a Collision component, which might send out messages whenever your game object ran into something.

There already exists a framework for this kind of object inside of the Directbase libraries: It’s called DirectObject, and it contains hooks into Panda3d’s built-in messaging and task system. Here’s a manual page for the messaging system.

As a bonus, Python is well suited for an aggregation system, being that it isn’t a strongly typed language.

Here’s an article that expands a bit upon the basic idea.

It might seem a bit silly and complicated, but if your intent is to create a bunch of different objects that share similar but not identical functions, a component system might make more sense than deep-inheritance.

I’m not sure how wise it is, but I personally use a hybrid system. All of my enemies are defined from a single Enemy class, which shares components between different types. However, my Enemy class does not share components with, say, my Projectile class. This is because I want my projectile class to be fairly lightweight, and individual projectiles are defined as a series of nodepaths shared under one management class. This is a different representation than my enemies, which aren’t so numerous, so I can afford to have individual full-fledged Python instances for each one.

Thanks for both of your suggestions.

I did read that paper on component based design, and found it really interesting. It seems pretty intuitive. I’ve decided to try go object orientated for now though, cos I’m not quite sure I’m up to the implementation of the component method just yet.

I have been getting myself into a bit of a knot passing data around between classes and things. I think I have most of it sorted out now, except for the movement.

In order to get a consistent move speed, independant of frame rate, I understand one needs to use a method similar to in RoamingRalph, where the distance moved is a multiple of the elapsed time since the last movement. The problem is, RR movement is contained in a Task, but my character class has a “move” function that is called in response to a key press or some AI event, how do I measure elapsed time then?

Should I get my “move” function to start up a task that just keeps the character moving until it receives another command to stop? That doesnt seem quite right. I think Im missing something again :unamused:

Object oriented is fine if you have fairly rigid design requirements and can express them easiest in an OO manner, so I can understand why you went with it. It’s a bit simpler/faster to implement, too! Just tossing the other out since Python is flexible. :slight_smile:

As for movement, I would use a per-world-time-tick updater for your object. Call this updater once per frame, say, though you could also lock your game logic to a fixed rate.

You can then use a polling method. Basically, when a key is pressed, just set a flag of “movingForward” or whatnot. During this updater, check to see if this flag is set and them sum the appropriate velocity.

If you want to avoid a bunch of objects all having tasks cycling away in taskMgr even if they aren’t moving, you can control the state of the task depending on whether or not the object is actually needing to move. Just pause the task, then resume it, and so on. However, I don’t think there’s much to be gained by doing this. As of Panda 1.6.2 (I believe) there is a new all-C++ task manager implementation and it seems pretty good at handling tons of small tasks.

Another choice is to use something like Panda3d physics and then apply a constant velocity to your object upon keypress and cancel it upon release. Then, polling isn’t necessary, as Panda3d will keep the object moving per frame without you having to do the calculations yourself.

A variant on this idea is to use intervals. Say you have a fairly simple motion you want to implement, such as a rocket moving in a straight line until it hits a certain point. Instead of using a movement updater or physics, you can just create a lerpPos interval that moves the rocket from point A to point B. You can even interrupt this interval at any time in order to stop the motion, and if you set the bakeInStart option on the Lerp interval and it will be aware of any position changes during its execution.

Hope this gives you some ideas!

Ok, I think that makes sense. So each instance of a character object will have an “update” task. It will probably just update the character’s position each frame, based on the movement flags that are set.

Those flags will be set by the character’s “move” method, which can be called by a human_controller class that listens for keypresses, or an AI_controller class that does AI stuff (“does AI stuff” is one heck of a simplification, but you get the picture. :stuck_out_tongue: )

This is the point when I feel like Im going in circles though :confused:

It makes sense to me to implement the AI as a class that the character class creates an instance of during initialisation. But then human control must be a similar class that the character creates an instance of?

Am I making things too complicated by trying to have the same interface to my characters for human and AI controlled things?

I had initially assumed that AI would be one big machine which kept a list of all the characters, and passed them instructions. At least that way it would know where everything was, and have easy access to the characters.

I had a similar idea, to have a common GameEntity class that is a base class for my PlayerAvatar (controlled by keys) and Enemy(controlled by an AI ) classes. “Move” and “UpdatePosition” were methods of the GameEntity class, but eventually I’ve scraped it all. It’s easer (at least for me) to move the enemy from point to point (with lerp intervals) then trying to make them move in a player kind of way (keep moving forward, stop, turn, go back etc.)… but than again my game is kind of straight forward when it comes to AI (should be really AuI - Artificial unInteligence), the desired behaviour of an Enemy object is to move to where the player is, play a attack animation, and send a messenger saying that the player was hit.

similar to programming in general there are a lot of design methods for AI. one of the very commonly used concepts is that the controller is separated from the character, so a “controller” can be eighter a AI or a human player.

the AI itself is often separated into modules that can handle specific tasks and a core part that distributes the tasks and keeps the data.
for example there is a function attackObject, it checks if the target is in view, if not is searches a place where it can see the enemy. using this location it calls a pathfinding module, that uses a move function to get to this position. once it reaches the position the attackobject function targets and shoots if possible, othervise it starts over again.

of course this concept only makes sense if the system has a certain complexity, i often start with a single object and separate it into modules once it reaches a certain complexity.

So… I am slowly making progress, but now I ahve another question about classes.

I have a projectile class which all of the bullets etc. will inherit from. I want it to be a fully enclosed class, so that my character’s “shoot” method can just spawn one and forget about it.

def shoot(self):
    #spawn a new bullet instance
     p = projectile(self.getPos(),self.heading)

That means it needs to delete itself when it hits something or hits some sort of lifetime limit. The following is my bullet class. I know how to remove the bullet from the scene, but I have a feeling that the class instance iteself is not being removed. How do I destroy it without keeping them all in a list or something?

class projectile():
    lifeTime = 1
    model = "bullet.egg"
    speed = 20
    heading = 0
    
    def __init__(self,startPos,direction):
               
        #---Set up the model---
        self.projectile = loader.loadModel("models/projectiles/"+self.model)
        self.projectile.reparentTo(render)
        self.projectile.setPos(startPos)
        self.heading = direction
        
        base.taskMgr.add(self.update,'projectileUpdatePos')
    
    def destroy(self):
        print "remove bullet model etc"
        self.projectile.removeNode()
    
    def update(self,task):
        elapsed = globalClock.getDt()
        if (task.time < self.lifeTime):
            print "update pos etc"
            stepX =  (elapsed*self.speed)*math.sin(self.heading*math.pi/180)
            stepY = -(elapsed*self.speed)*math.cos(self.heading*math.pi/180)
            self.projectile.setFluidPos(self.projectile.getX()+stepX,self.projectile.getY()+stepY,self.projectile.getZ())
            return Task.cont
        else:
            print "call destroy method"
            self.destroy()
            return Task.done

Oh, any other comments on the way I am structuring my classes or the variables inside them etc. are very welcome, cos I’m a complete newbie

Hi Again. Sorry for double posting, but I’m hoping someone can help, because I am still confused.

My projectile class still looks much the same (although It has gotten a bit more bloated due to collision handling etc).

I still dont know how to remove the instance once I am done with it though. I’m sure that just removing the node isn’t enough?

Also, should my projectile class be inheriting from the DirectObject class like it is? It doesn’t really seem to make much difference at the moment.

You also need to remember to remove your task. But, looking at your code, that’s it, at least as it stands today.

In general, you only need to cleanup the Panda objects you create (like nodes and tasks and event hooks). Python objects (like the instance itself) don’t usually require explicit cleanup, because Python manages this for you automatically.

Up to you. It doesn’t do any harm to inherit from DirectObject, but it’s certainly not a requirement. No reason to do so at all unless you want to listen for events; DirectObject adds the ability to call accept().

David

Thanks for the reply.

I guess I just have to learn to trust the garbage collector then :slight_smile:

I am using “accept” now for my projectile collision stuff, so that makes sense.

Thanks drwr!