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