Problem with moving multiple characters - Solved

bharling

06-09-2006 10:39:49

Fixed it myself :)

was trying to be too clever for my boots, seems that I cant use a dictionary to store the list of possible Actor classes, so I've swapped the dictionary lookup in the datamanager for a standard 'if name=="robot": actor = robot()' -type setup. Now on with the game!


Hi, (Sorry about the length of this post...)

I've built myself a simple game framework, based on the sourcecode for 'Violent World' (thanks, authors of VW). I'm getting quite far quite quickly (the joys of writing in python), however I've hit a problem which I cant seem to solve.

Like in VW, all the game characters are individual classes, subclassed from a basic gameActor class, and spawned by calling the actor's 'Spawn' method. A datamanager exists which holds a dictionary containing tuples in the form of {'ActorName' : ActorClassInstance()}. When each actor spawns, the datamanager first looks up the class in the dictionary, then calls the actor's __init__() method, then attaches the nodes, and logs the characters animation state, movement speed and direction, just like in VW. Every frame, a framelistener tells the datamanager to loop through its list of actors and update those that require updating. I've got 10 actors which all seem to spawn correctly (their models appear in-game, and their class instances contain all the needed properties and methods, but I can only get one of them to move, no matter what i do :(. The robots have a simple ai, that tells them to periodically choose a new plant in the game and run towards it.

So if anyone can help, heres the bits of code which I think are causing the problem, but I dunno what it is....

GameActor Class:

class GameActor:
def __init__(self):
self.life = 100
self.price = 0
self.id = -1
self.position = (100.0, 0.0, 0.0)
self.moving = False
self.heightAdjust = 0.0
self.adjustHeight = True
self.direction = (1.0, 0.0, 0.0) # X, Y, Z
self.targetDirection = self.normalize((1.0, 0.0, 0.0)) # X, Y, Z
self.defaultDirection = self.normalize((1.0, 0.0, 0.0)) # X, Y, Z
self.calculateTargetEachFrame = False
self.rotateCritical = False
self.rotationSpeed = 5.0
self.speed = 1.0
self.maxSpeed = 0.0
self.target = None
self.name = "actor"
self.mesh = "sphere.mesh"
self.material = "Examples/RustySteel"
self.raySceneQuery = None
self.ray = ogre.Ray()
self.Alive = False
self.IsAnimated = False
self.DyingAnimation = None
self.hasAI = False

def SetTarget(self, gameActor):
self.target = gameActor
self.calculateTargetDirection()


def Spawn(self, position, SceneManager, numActors, RaySceneQuery):
rootnode = SceneManager.rootSceneNode
self.raySceneQuery = RaySceneQuery
self.Entity = SceneManager.createEntity("ent_" + self.name + str(numActors),self.mesh)
self.OgreNode = rootnode.createChildSceneNode("ogrenode" + str(numActors))
self.Node = self.OgreNode.createChildSceneNode("node_" + self.name + str(numActors))
self.name = str(self.name) + str(numActors)
## self.Node.position = position
self.Node.attachObject(self.Entity)
self.position = position
self.OgreNode.position = ogre.Vector3(self.position)
self.Alive = True
self.updateHeight()
...



RunningObject Class:


class RunningObject(GameActor):
def __init__(self):
GameActor.__init__(self)
self.animationSpeed = 1.0
self.animationState = None
self.hasAI = True

def move(self, time):
delta = time * self.speed
self.position = ( self.position[X] + self.direction[X] * delta,
self.position[Y] + self.direction[Y] * delta,
self.position[Z] + self.direction[Z] * delta)
self.OgreNode.position = ogre.Vector3(self.position)
self.updateHeight()


Robot Class:


class Robot(RunningObject):
def __init__(self):
RunningObject.__init__(self)
self.mesh = "Robot.mesh"
self.IsAnimated = True
self.animationState = "Walk"
self.speed = 40.00
self.updateAITime = 3
self.updateAIFrequency = 7

# This is broken somehow
#
def Update(self, actorsList):
# just change target for now
potentialTargets = [t for t in actorsList.keys() if "Plant" in t]

x = random.randrange(2, 60)
self.target = actorsList[potentialTargets[x]]
self.SetTarget(self.target)
self.calculateTargetDirection()


Datamanager which spawns the 'bots


...
def GatherGameObjects(self):
"""Return a dictionary of all game objects, and their
corresponding classes"""
return {"PlantSml":foliage.PlantSml(), "PlantTall":foliage.PlantTall(),
"PoliceGrunt": pigs.PoliceGrunt(), "Robot": pigs.Robot()}
...
def SpawnChar(self, name, position):
# The first point at which any game actor is created

actor = self.GameObjects[name]
actor.__init__()
actor.Spawn(position, self.SceneManager, self.NumActors, self.raySceneQuery)
self.actorsList[actor.name] = actor
self.NumActors = self.NumActors + 1
if (actor.IsAnimated):
actor.animationState = actor.Entity.getAnimationState(actor.animationState)
self.animationStates.append(actor.animationState)
self.animationStates[-1].enabled = True
return actor.name

def GetActiveObjects(self):
self.animationStates = []
self.animationSpeeds = []
for actor in self.actorsList.values():
if actor.IsAnimated and actor.Alive:
self.animationStates.append(actor.animationState)
self.animationSpeeds.append(actor.animationSpeed)
self.animationStates[-1].enabled = True
return self.animationStates

def UpdateGame(self, time):
self.updateAITime += time
self.totalTime += time
mc = 0
for actor in self.actorsList.values():
if actor.Alive and actor.moving:
actor.move(time)
if actor.hasAI:
if abs((self.updateAITime % actor.updateAIFrequency) - actor.updateAITime) <= time/2:
actor.Update(self.actorsList)
if actor.dot(actor.direction, actor.targetDirection) < 0.995:
actor.rotate(time)

for index in xrange(0,len(self.animationStates)):
self.animationStates[index].addTime(time * self.animationSpeeds[index])




here is how I am spawning the 'bots from the main game


for i in range(10):
robot = self.datamanager.SpawnChar("Robot", ogre.Vector3(ogre.Math.RangeRandom(100, 500),200.0,500.0))
myrobot = self.datamanager.actorsList[robot]
tarName = "Plant" + str(i + 10)
myrobot.Update(self.datamanager.actorsList)
## myrobot.SetTarget(self.datamanager.actorsList[tarName])
myrobot.Node.yaw(ogre.Degree(90))
myrobot.moving = True
self.datamanager.GetActiveObjects()


lastly, the framelistener does this every frame


def frameStarted(self, frameEvent):
# update all animations
self.datamanager.UpdateGame(frameEvent.timeSinceLastFrame)


So, thats it. If anyone has any ideas, I would appreciate some help! When I've tested this, the first robot spawns, but seems to run at double speed, and all the other robots seem to target the same plant (the last one in the list). As far as I can tell, the datamanager is calling 'move' on all the robots every time it updates, but they are stuck in place. I know all the actors are working (at least initally), because they all update their heights to rest on the landscape properly.

thanks for any replies

:wink:

EDIT: PS. am using pyogre 1.0.6 on win32

willism

07-09-2006 14:40:27

I'm glad to hear that you fixed your problem. However, it brings up an interesting point: Is there a way to make a more "pythonic" factory, similar to the way that you originally tried it? It seems like you shouldn't have to settle for the if ... elsif ... elsif ... elsif ... way.

Does anybody else have a better idea?

Srekel

07-09-2006 18:22:54

I haven't read the entire post, but yeah, I think we've found a better solution for the game we're making now (we're the authors of VW :) ). I'll read through this again later today and explain the solution we've got.

Srekel

07-09-2006 19:26:55

Ok, well, first, your first method didn't work because you the values in the dict were instances of the actors, so you only had one instance per actor.

The easiest solution for this, if you are willing to keep all the actor types in a dict, is not to store the instances, but the classes, like this:


def GatherGameObjects(self):
"""Return a dictionary of all game objects, and their
corresponding classes"""
return {"PlantSml":foliage.PlantSml, "PlantTall":foliage.PlantTall,
"PoliceGrunt": pigs.PoliceGrunt, "Robot": pigs.Robot} #notice that its "foliage.PlantSml", not "foliage.PlantSml()" ## changed this line

def SpawnChar(self, name, position):
# The first point at which any game actor is created

actorClass = self.GameObjects[name] ## changed this line
actor = actorClass() ## changed this line
### the rest just like it was
actor.Spawn(position, self.SceneManager, self.NumActors, self.raySceneQuery)
self.actorsList[actor.name] = actor
self.NumActors = self.NumActors + 1
if (actor.IsAnimated):
actor.animationState = actor.Entity.getAnimationState(actor.animationState)
self.animationStates.append(actor.animationState)
self.animationStates[-1].enabled = True
return actor.name


There is a more "advanced" factory solution which we use. If you're interested, I can post it too. :)

bharling

08-09-2006 10:37:51

Wow, thanks :)

and also thanks again for releasing the VW source.
Your post makes sense of the problem I had, all comes down to a limited understanding of how class structure works.

Yeah, I'd love to see the more advanced version if your happy to post it.

:)

Srekel

11-09-2006 11:19:03

ok :)

We still have a datamanager that has a spawnActor method. We also (still?) have our actors in a subfolder (module) named "Actors". This is enough to get a working factory. :)

This is the code:


def spawnActor(self, actorType, position):
"""
This function will be called directly from different subsystems.
It will create an actor of the type specified in the actorType string. So,
if actorType == "Missile", it will spawn a Missile.
The spawned actor will be added to the self.spawningActors list. It will be
removed from it in the beginning of the next frame (by handleSpawningActors()
"""
### This line dynamically imports the module where the actor class is, find the
### class specification and creates an instance of the class. This works great,
### however I doubt it works too well with py2exe (?).
actor = getattr(getattr(__import__("Actors."+actorType), actorType), actorType)()
actor.spawn(position, self.numActors)
self.spawningActors.append(actor)
self.numActors += 1
#actor.id = self.numActors
print "DM: spawned", actorType, "at", position, "id=", actor.id
return actor



Piece of cake :D

bharling

11-09-2006 12:54:35

Srekel,

many thanks for this, it kind of makes sense :). Violent World has helped me enormously in creating my own project, I am indebted to you guys!

on a side note (if you're still in helpful mood), I was thinking of using pyOde for a simple collision detection system in my game, can you foresee any problems using the VW approach with ODE? did you guys try it?

Once again, great work on VW, looking forward to your next proj.

Srekel

18-09-2006 22:18:43

No problem :) It's awesome that someone found the stuff for VW useful. :)

I've only used ODE a little, but from what I recall, I guess it should be possible to cram into the VW "framework". If you have some more specific question, maybe I can help more. :)

bharling

19-09-2006 16:59:50

I gave up on ODE in the end, seemed like overkill for what I'm doing, (I have no need for proper physics / collision bodies etc, my game would use more 'Mario' type physics). So I went ahead and attempted my own octree collision with mixed results :(. What I have seems to work very well with the VW arrangement, although I think there must be a bug in my octree somewhere, as sometimes it returns the correct collision, and at other times the characters walk straight through the object. I'm working on refinining it at the moment :) If you feel like you could help me with that ( heheh :)) then, once again, I'd be very grateful to you. My octree implementation is on another thread in this forum.