Few advices, please

Hi all,
I learn Panda by examining the Roaming Ralph. I have modified the source code to create a mouse-controlled camera, and some other stuff too.

import direct.directbase.DirectStart

from pandac.PandaModules import WindowProperties
from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from pandac.PandaModules import Filename
from pandac.PandaModules import PandaNode,NodePath,Camera
from pandac.PandaModules import CollisionTraverser,CollisionNode,CollisionHandlerQueue,CollisionRay,BitMask32
import sys, math

modelsDir = "Roaming-Ralph/models/"
speed = 3 # Ralph's speed, units per second

class World(DirectObject):

    def __init__(self):

        # Disable defaul camera controls
        base.disableMouse()

        # Initial setup
        props = WindowProperties()
        props.setCursorHidden(True)
        base.win.requestProperties(props)
        base.setBackgroundColor(0, 0, 0)

        self.centerX = base.win.getProperties().getXSize()/2
        self.centerY = base.win.getProperties().getYSize()/2
        base.win.movePointer(0, self.centerX, self.centerY)

        self.floaterHeight = 1
        self.floaterDist = 3
        self.angleToFloater = 0 # Initial orientation of the floater
        self.angleToFloaterRad = math.radians(self.angleToFloater) # Initial orientation of the floater in radians
        self.floaterPos = [math.sin(self.angleToFloaterRad)*self.floaterDist, math.cos(self.angleToFloaterRad)*self.floaterDist] # Initial position of the floater, X and Y

        self.cameraHeight = 10
        self.cameraDist = 10
        self.angleToCamera = self.angleToFloater + 180 # Initial orientation of the camera
        self.angleToCameraRad = math.radians(self.angleToCamera) # Initial orientation of the camera in radians
        self.cameraPos = [math.sin(self.angleToCameraRad)*self.cameraDist, math.cos(self.angleToCameraRad)*self.cameraDist] # Initial position of the camera, X and Y

        # Create the main character, Ralph, with two animations
        self.ralph = Actor(modelsDir+"ralph",
                                 {"run":modelsDir+"ralph-run",
                                  "walk":modelsDir+"ralph-walk"})
        self.ralph.reparentTo(render)
        self.ralph.setScale(.2)
        self.ralph.setPos(0, 0, 0)
        self.ralph.setHpr(180, 0, 0)

        # Create the floater
        self.floater = NodePath(PandaNode("floater"))
        self.floater.reparentTo(render)
        self.floater.setPos(self.ralph.getX()+self.floaterPos[0], self.ralph.getY()+self.floaterPos[1], self.ralph.getZ()+self.floaterHeight)

        # Create the camera
        base.camera.setPos(self.ralph.getX()+self.cameraPos[0], self.ralph.getY()+self.cameraPos[1], self.ralph.getZ()+self.cameraHeight)
        base.camera.lookAt(self.floater)

        # Load world
        self.environ = loader.loadModel(modelsDir+"world")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)

        # Create key map
        self.keyMap = {"left":0,
                       "right":0,
                       "forward":0,
                       "backward":0,
                       "primFire":0,
                       "secFire":0,
                       "changeView":0}

        # Accept the control keys for movement and rotation
        # (primFire and secFire are not used, but reserved for future use)
        self.accept("escape", sys.exit)
        self.accept("w", self.setKey, ["forward",1])
        self.accept("a", self.setKey, ["left",1])
        self.accept("s", self.setKey, ["backward",1])
        self.accept("d", self.setKey, ["right",1])
        self.accept("w-up", self.setKey, ["forward",0])
        self.accept("a-up", self.setKey, ["left",0])
        self.accept("s-up", self.setKey, ["backward",0])
        self.accept("d-up", self.setKey, ["right",0])
        self.accept("mouse1", self.setKey, ["primFire",1])
        self.accept("mouse1-up", self.setKey, ["primFire",0])
        self.accept("mouse2", self.setKey, ["changeView",1])
        self.accept("mouse2-up", self.setKey, ["changeView",0])
        self.accept("mouse3", self.setKey, ["secFire",1])
        self.accept("mouse3-up", self.setKey, ["secFire",0])

        # We repeat self.move every frame.
        taskMgr.add(self.move,"moveTask")

        # Game state variables

        self.prevtime = 0
        self.isMoving = False

        # Collision detection
        self.cTrav = CollisionTraverser()

        self.ralphGroundRay = CollisionRay()
        self.ralphGroundRay.setOrigin(0,0,1000)
        self.ralphGroundRay.setDirection(0,0,-1)
        self.ralphGroundCol = CollisionNode('ralphRay')
        self.ralphGroundCol.addSolid(self.ralphGroundRay)
        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
        self.ralphGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)

    # Records the state of the arrow keys
    def setKey(self, key, value):
        self.keyMap[key] = value
    # Accepts arrow keys to move either the player or the menu cursor,
    # also deals with grid checking and collision detection

    # Here we define how much we rotate Ralph when a move-key is pressed
    def rotateRalph(self, value):
        if value >= 360:
            value -= 360
        self.ralph.setH(value)

    def move(self, task):
        # This method is called every frame, since it is attached to taskMgr.

        # The elapsed time is the current time minus the last saved time
        elapsed = task.time - self.prevtime

        # Save ralph's initial position so that we can restore it,
        # in case he falls off the map or runs into something.
        startpos = self.ralph.getPos()

        # If a move key is pressed, turn ralph in the specified direction.
        if (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0) or (self.keyMap["forward"]!=0) or (self.keyMap["backward"]!=0):
            self.ralph.lookAt(self.floater)
            rotDirections = (self.keyMap["left"], self.keyMap["right"], self.keyMap["forward"], self.keyMap["backward"])
            rotValues = (270, 90, 180, (0, 360))
            rotateBy = 0
            divideBy = 0
            for direction in rotDirections:
                divideBy += direction
            for value in range(0, 4):
                if value != 3:
                    rotateBy += rotDirections[value]*rotValues[value]
                if value == 3:
                    if self.keyMap["right"]!=0:
                        rotateBy += rotDirections[value]*rotValues[value][0]
                    else:
                        rotateBy += rotDirections[value]*rotValues[value][1]
            rotateBy = rotateBy / divideBy
            self.rotateRalph(self.ralph.getH()+rotateBy)

        # If a move-key is pressed, move ralph in the specified direction.
        if (self.keyMap["left"]!=0):
            self.ralph.setPos(self.ralph.getX() - (elapsed*speed), self.ralph.getY(), self.ralph.getZ())
        if (self.keyMap["right"]!=0):
            self.ralph.setPos(self.ralph.getX() + (elapsed*speed), self.ralph.getY(), self.ralph.getZ())
        if (self.keyMap["forward"]!=0):
            self.ralph.setPos(self.ralph.getX(), self.ralph.getY() + (elapsed*speed), self.ralph.getZ())
        if (self.keyMap["backward"]!=0):
            self.ralph.setPos(self.ralph.getX(), self.ralph.getY() - (elapsed*speed), self.ralph.getZ())

        # If ralph is moving, loop the run animation.
        # If he is standing still, stop the animation.
        if (self.keyMap["forward"]!=0) or (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0) or (self.keyMap["backward"]!=0):
            if not self.isMoving:
                self.ralph.loop("run")
                self.isMoving = True
        else:
            if self.isMoving:
                self.ralph.stop()
                self.ralph.pose("walk",5)
                self.isMoving = False

        # Mouse controls
        if self.keyMap["changeView"]!=0: # If changeView button is pressed, change view when moving mouse.
            mouseX = base.win.getPointer(0).getX()
            if base.win.movePointer(0, self.centerX, self.centerY):
                # Update two important angles
                self.angleToFloater += (mouseX - self.centerX)
                self.angleToCamera = self.angleToFloater + 180
            # Calculate resulting floater position
            if self.angleToFloater < 0:
                self.angleToFloater += 360
            if self.angleToFloater >= 360:
                self.angleToFloater -= 360
            self.angleToFloaterRad = math.radians(self.angleToFloater)
            self.floaterPos[0] = math.sin(self.angleToFloaterRad) * self.floaterDist
            self.floaterPos[1] = math.cos(self.angleToFloaterRad) * self.floaterDist
            # Calculate resulting camera position
            if self.angleToCamera < 0:
                self.angleToCamera += 360
            if self.angleToCamera >= 360:
                self.angleToCamera -= 360
            self.angleToCameraRad = math.radians(self.angleToCamera)
            self.cameraPos[0] = math.sin(self.angleToCameraRad) * self.cameraDist
            self.cameraPos[1] = math.cos(self.angleToCameraRad) * self.cameraDist
        else: # Otherwise, just keep cursor position at the center of the screen
            base.win.movePointer(0, self.centerX, self.centerY)

        # Update floater
        self.floater.setPos(self.ralph.getX()+self.floaterPos[0], self.ralph.getY()+self.floaterPos[1], self.ralph.getZ()+self.floaterHeight)

        # Update camera
        base.camera.setPos(self.ralph.getX()+self.cameraPos[0], self.ralph.getY()+self.cameraPos[1], self.ralph.getZ()+self.cameraHeight)
        base.camera.lookAt(self.floater)

        # Now check for collisions.
        self.cTrav.traverse(render)

        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
        # update his Z. If it hit anything else, or didn't hit anything, put
        # him back where he was last frame.
        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.getEntry(i)
            entries.append(entry)
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
            self.ralph.setZ(entries[0].getSurfacePoint(render).getZ())
        else:
            self.ralph.setPos(startpos)

        # Store the task time and continue
        self.prevtime = task.time
        return Task.cont

World()
run()

As you can see here, this code has one great disadvantage: the movements are relative to global coordinates, not to Ralph’s own position. In other words, the forward-backward direction is always along Y axis, left-right - along X axis, doesn’t matter how Ralph is turned. Of course, to determine right movement direction I can try to use sin() and cos() functions again. But I guess that Panda3D is so elaborate that it must have the prettier way to do this. Can you, please, explain how to do it?
Also, if you have any other advices or optimization tips, I am eager to hear them :slight_smile:

To move ralph in the direction of which he’s facing:

self.ralph.setPos(self.ralph,0,-1,0)

specify self.ralph in the arguments to move it relative to itself.

pro-rsoft, I replaced old movement code with this:

        if (self.keyMap["left"]!=0):
            self.ralph.setPos(self.ralph, +(elapsed*speed), 0, 0)
        if (self.keyMap["right"]!=0):
            self.ralph.setPos(self.ralph, -(elapsed*speed), 0, 0)
        if (self.keyMap["forward"]!=0):
            self.ralph.setPos(self.ralph, 0, -(elapsed*speed), 0)
        if (self.keyMap["backward"]!=0):
            self.ralph.setPos(self.ralph, 0, +(elapsed*speed), 0)

But I can’t say it works… All movements are really slow, and not correct (for example, forward and backward keys result in the same movements of Ralph, although their code is different. Can you explain in more detail, please?

i think this should work:

if (self.keyMap["left"]!=0):
            self.ralph.setPos(self.ralph, Vec3( -1, 0, 0 ) * (elapsed*speed))
        if (self.keyMap["right"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 1, 0, 0 ) * (elapsed*speed))
        if (self.keyMap["forward"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 0, 1, 0 ) * (elapsed*speed))
        if (self.keyMap["backward"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 0, -1, 0 ) * (elapsed*speed))

kampfgnu, no, it doesn’t (I also imported Vec3 this time; I didn’t do it before).

About it being slow: that is correct: since ralph is scaled down. If you’d move it relative to itself the movements would also be scaled. Try dividing the whole bunch by ralph’s scale.

pro-rsoft, yes, this solved slow movements, I got the idea. Also, I see that the movements are really relative to the Ralph’s rotation.
But still, left-right-backward movements are not correct. Ralph doesn’t go where he is expected to go, only forward… I tried to change the code in different ways, but without success.
I use this at the moment:

        if (self.keyMap["left"]!=0):
            self.ralph.setPos(self.ralph, Vec3( -1, 0, 0 ) * (elapsed*speed)/0.2)
        if (self.keyMap["right"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 1, 0, 0 ) * (elapsed*speed)/0.2)
        if (self.keyMap["forward"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 0, -1, 0 ) * (elapsed*speed)/0.2)
        if (self.keyMap["backward"]!=0):
            self.ralph.setPos(self.ralph, Vec3( 0, 1, 0 ) * (elapsed*speed)/0.2)

All buttons result in moving forward only!

I changed code into following, and now forward and backward movements work correctly (no idea why, actually…):

        if (self.keyMap["left"]!=0):
            self.ralph.setPos(self.ralph, -(Vec3(-1, 0, 0)*(elapsed*speed)/0.2))
        if (self.keyMap["right"]!=0):
            self.ralph.setPos(self.ralph, (Vec3(1, 0, 0)*(elapsed*speed)/0.2))
        if (self.keyMap["forward"]!=0):
            self.ralph.setPos(self.ralph, Vec3(0, -1, 0)*(elapsed*speed)/0.2)
        if (self.keyMap["backward"]!=0):
            self.ralph.setPos(self.ralph, -(Vec3(0, 1, 0)*(elapsed*speed)/0.2))

Left and right buttons still don’t work…

Just a guess, but maybe it’s something rather simple: have you checked if the “left” and “right” if-conditions ever become true? Perhaps by adding a print statement to the if-blocks.
enn0x

They are true when a and d buttons are pressed. Actually, Ralph moves when I press them, but instead of left-right movements he moves up and down again!

Perfect. Then you are almost done. You know the forward/backward axis, and the up/down axis. So the last one must be left/right :slight_smile:

If you are using the ralph.egg.pz model from the standard Panada3D distribution: unzip it (using punzip command), and then look at the first line of the model file:

<CoordinateSystem> { Y-Up }

The egg loader will, according to the egg syntax documentation, automatically make coordinate system conversions. So perhaps you are using the y-up model in an z-up environment?

enn0x

to fix any of them scaling problems faster just make an empty node use at as you use ralph and reparent the ralph to it … then ralph can scale how he wants and we will have node with no scaling.

Yes, Ralph’s Y is actually up, you are right: { Y-Up }
treeform, I am going to try your solution right now.

Thank you all, gentlemen! Thanks to your advices I figured this out.

(I have asked few other questions related to this code, in this thread: discourse.panda3d.org/viewtopic.php?t=3818)

Another question: in the manual it is said that Roaming Ralph uses very inefficient collision method in order to update Ralph’s Z coordinate. What collision method is better? Where can I find better samples and study them?

In order to help newbies like me in future, here I post the working code with commentaries:

# This code was created with Crimsonland-like shooter concept in my head.
# This is why I placed camera so high and removed camera collisions. They
# can be easily restored, having the original Roaming Ralph sample
# and this code.
# Thanks to all who help to make this code!

import direct.directbase.DirectStart

from direct.actor.Actor import Actor
from direct.showbase.DirectObject import DirectObject
from direct.task.Task import Task
from pandac.PandaModules import WindowProperties
from pandac.PandaModules import PandaNode,NodePath,Camera,Vec3,Filename

from pandac.PandaModules import CollisionTraverser,CollisionNode
from pandac.PandaModules import CollisionHandlerQueue,CollisionRay,BitMask32

import sys

# Path to Roaming Ralph's models directory
modelsDir = "Roaming-Ralph/models/"

# Ralph's speed, units per second
speed = 3

class World(DirectObject):

    def __init__(self):

        # Disable defaul camera controls
        base.disableMouse()

        # Initial setup
        props = WindowProperties()
        props.setCursorHidden(True)
        base.win.requestProperties(props)
        base.setBackgroundColor(0, 0, 0)

        # Find the center of the screen
        self.screenCenterX = base.win.getProperties().getXSize()/2
        self.screenCenterY = base.win.getProperties().getYSize()/2

        # Move mouse to the middle of the screen
        base.win.movePointer(0, self.screenCenterX, self.screenCenterY)

        # Game state variables
        # Previous recorded time in milliseconds (will be used for movements)
        self.prevtime = 0
        # Is Ralph moving now? (will be used to animate Ralph)
        self.isMoving = False

        # Initial orientation of Ralph, floater, and camera
        # (i.e. self.actorPlace) in degrees
        self.angle = 0

        # Set floater and camera position (relative to self.actorPlace).
        # The camera will look at the floater, and not at Ralph himself.
        # Right(-) or left(+), forward (-) or backward (+), down(-) or up(+)
        self.floaterPos = (0, -2, 1)
        self.cameraPos = (0, 15, 10)

        # When we scale nodes, their local coordinate system is scaled too.
        # Also, some models use Y as top-down axis, while the engine uses Z
        # for it. Therefore, in order to avoid confusion, it's better to
        # create an empty node (here it's called "actorPlace"), and reparent
        # Ralph to it. In this case Ralph can be scaled as he needs,
        # and have Y as his top-down axis, still, we can move, rotate or use
        # him in any other way with ease

        # Create Actor's placeholder
        self.actorPlace = NodePath(PandaNode("actorPlace"))
        self.actorPlace.reparentTo(render)
        self.actorPlace.setPos(0, 0, 0)
        self.actorPlace.setHpr(self.angle, 0, 0)

        # Create the main character, Ralph, with two animations
        # and reparent him to the self.actorPlace
        self.ralph = Actor(modelsDir+"ralph",
                                 {"run":modelsDir+"ralph-run",
                                  "walk":modelsDir+"ralph-walk"})
        self.ralph.reparentTo(self.actorPlace)
        self.ralph.setScale(.2)
        self.ralph.setPos(0, 0, 0)
        self.ralph.setHpr(0, 0, 0)

        # Create the floater and reparent it to the self.actorPlace
        self.floater = NodePath(PandaNode("floater"))
        self.floater.reparentTo(self.actorPlace)
        self.floater.setPos(self.floaterPos[0], self.floaterPos[1], self.floaterPos[2])

        # Create the camera, reparent it to the self.actorPlace
        # and make it look at the floater
        base.camera.reparentTo(self.actorPlace)
        base.camera.setPos(self.cameraPos[0], self.cameraPos[1], self.cameraPos[2])
        base.camera.lookAt(self.floater)

        # Load world
        self.environ = loader.loadModel(modelsDir+"world")
        self.environ.reparentTo(render)
        self.environ.setPos(0, 0, 0)

        # Create key map
        # (primFire and secFire are not used, but reserved for future use)
        self.keyMap = {"left":0,
                       "right":0,
                       "forward":0,
                       "backward":0,
                       "changeView":0,
                       "primFire":0,
                       "secFire":0}

        # Accept the control keys for movement and rotation
        # (primFire and secFire are not used, but reserved for future use)
        self.accept("escape", sys.exit)
        self.accept("w", self.setKey, ["forward",1])
        self.accept("a", self.setKey, ["left",1])
        self.accept("s", self.setKey, ["backward",1])
        self.accept("d", self.setKey, ["right",1])
        self.accept("w-up", self.setKey, ["forward",0])
        self.accept("a-up", self.setKey, ["left",0])
        self.accept("s-up", self.setKey, ["backward",0])
        self.accept("d-up", self.setKey, ["right",0])
        self.accept("mouse2", self.setKey, ["changeView",1])
        self.accept("mouse2-up", self.setKey, ["changeView",0])
        self.accept("mouse1", self.setKey, ["primFire",1])
        self.accept("mouse1-up", self.setKey, ["primFire",0])
        self.accept("mouse3", self.setKey, ["secFire",1])
        self.accept("mouse3-up", self.setKey, ["secFire",0])

        # We repeat self.move every frame
        taskMgr.add(self.move,"moveTask")

        # Collision detection
        self.cTrav = CollisionTraverser()
        self.ralphGroundRay = CollisionRay()
        self.ralphGroundRay.setOrigin(0,0,1000)
        self.ralphGroundRay.setDirection(0,0,-1)
        self.ralphGroundCol = CollisionNode('ralphRay')
        self.ralphGroundCol.addSolid(self.ralphGroundRay)
        self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0))
        self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff())
        self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
        self.ralphGroundHandler = CollisionHandlerQueue()
        self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)

    # Records the state of the arrow keys
    def setKey(self, key, value):
        self.keyMap[key] = value

    def move(self, task):
        # This method is called every frame, since it is attached to taskMgr.

        # The elapsed time is the current time minus the last saved time
        elapsed = task.time - self.prevtime

        # Save Ralph's initial position so that we can restore it,
        # in case he falls off the map or runs into something.
        startpos = self.actorPlace.getPos()

        # If a move key is pressed, turn ralph in the specified direction.
        if (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0) or (self.keyMap["backward"]!=0) or (self.keyMap["forward"]!=0):
            rotDirections = (self.keyMap["left"], self.keyMap["right"], self.keyMap["backward"], self.keyMap["forward"])
            rotValues = (90, 270, 180, (0, 360))
            rotateBy = 0
            divideBy = 0
            for direction in rotDirections:
                divideBy += direction
            for value in range(0, 4):
                if value != 3:
                    rotateBy += rotDirections[value]*rotValues[value]
                if value == 3:
                    if self.keyMap["left"]!=0:
                        rotateBy += rotDirections[value]*rotValues[value][0]
                    else:
                        rotateBy += rotDirections[value]*rotValues[value][1]
            rotateBy = rotateBy / divideBy
            self.ralph.setH(rotateBy)

        # If a move-key is pressed, move ralph in the specified direction.
        if (self.keyMap["left"]!=0):
            self.actorPlace.setPos(self.actorPlace, Vec3(1, 0, 0)*(elapsed*speed))
        if (self.keyMap["right"]!=0):
            self.actorPlace.setPos(self.actorPlace, Vec3(-1, 0, 0)*(elapsed*speed))
        if (self.keyMap["forward"]!=0):
            self.actorPlace.setPos(self.actorPlace, Vec3(0, -1, 0)*(elapsed*speed))
        if (self.keyMap["backward"]!=0):
            self.actorPlace.setPos(self.actorPlace, Vec3(0, 1, 0)*(elapsed*speed))

        # If ralph is moving, loop "run" animation.
        # If he is standing still, stop the animation.
        if (self.keyMap["forward"]!=0) or (self.keyMap["left"]!=0) or (self.keyMap["right"]!=0) or (self.keyMap["backward"]!=0):
            if not self.isMoving:
                self.ralph.loop("run")
                self.isMoving = True
        else:
            if self.isMoving:
                self.ralph.stop()
                self.ralph.pose("walk",5)
                self.isMoving = False

        # Mouse controls
        if self.keyMap["changeView"]!=0: # If changeView button is pressed, change view when moving mouse.
            mouseX = base.win.getPointer(0).getX()
            # Update ratation
            if base.win.movePointer(0, self.screenCenterX, self.screenCenterY):
                self.angle -= (mouseX - self.screenCenterX)
            if self.angle < 0:
                self.angle += 360
            if self.angle >= 360:
                self.angle -= 360
        # Otherwise, just keep cursor position at the center of the screen
        else:
            base.win.movePointer(0, self.screenCenterX, self.screenCenterY)

        # Update actorPlace orientation
        self.actorPlace.setH(self.angle)

        # Now check for collisions.
        self.cTrav.traverse(render)

        # Adjust ralph's Z coordinate.  If ralph's ray hit terrain,
        # update his Z. If it hit anything else, or didn't hit anything, put
        # him back where he was last frame.
        entries = []
        for i in range(self.ralphGroundHandler.getNumEntries()):
            entry = self.ralphGroundHandler.getEntry(i)
            entries.append(entry)
        entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(),
                                     x.getSurfacePoint(render).getZ()))
        if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"):
            self.actorPlace.setZ(entries[0].getSurfacePoint(render).getZ())
        else:
            self.actorPlace.setPos(startpos)

        # Store the task time and continue
        self.prevtime = task.time
        return Task.cont

# Create world
World()
run()