panda3d/direct/src/controls/NonPhysicsWalker.py
2004-09-28 22:11:57 +00:00

284 lines
10 KiB
Python
Executable File

"""
NonPhysicsWalker.py is for avatars.
A walker control such as this one provides:
- creation of the collision nodes
- handling the keyboard and mouse input for avatar movement
- moving the avatar
it does not:
- play sounds
- play animations
although it does send messeges that allow a listener to play sounds or
animations based on walker events.
"""
from direct.showbase.ShowBaseGlobal import *
from direct.directnotify import DirectNotifyGlobal
from direct.showbase import DirectObject
class NonPhysicsWalker(DirectObject.DirectObject):
notify = DirectNotifyGlobal.directNotify.newCategory("NonPhysicsWalker")
wantDebugIndicator = base.config.GetBool('want-avatar-physics-indicator', 0)
# Ghost mode overrides this:
slideName = "slide-is-disabled"
# special methods
def __init__(self):
DirectObject.DirectObject.__init__(self)
self.worldVelocity = Vec3.zero()
self.collisionsActive = 0
self.speed=0.0
self.rotationSpeed=0.0
self.vel=Vec3(0.0, 0.0, 0.0)
self.stopThisFrame = 0
def setWalkSpeed(self, forward, jump, reverse, rotate):
assert(self.debugPrint("setWalkSpeed()"))
self.avatarControlForwardSpeed=forward
#self.avatarControlJumpForce=jump
self.avatarControlReverseSpeed=reverse
self.avatarControlRotateSpeed=rotate
def getSpeeds(self):
#assert(self.debugPrint("getSpeeds()"))
return (self.speed, self.rotationSpeed)
def setAvatar(self, avatar):
self.avatar = avatar
if avatar is not None:
pass # setup the avatar
def setAirborneHeightFunc(self, getAirborneHeight):
self.getAirborneHeight = getAirborneHeight
def setWallBitMask(self, bitMask):
self.cSphereBitMask = bitMask
def setFloorBitMask(self, bitMask):
self.cRayBitMask = bitMask
def initializeCollisions(self, collisionTraverser, avatarNodePath,
avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0):
"""
Set up the avatar for collisions
"""
assert not avatarNodePath.isEmpty()
self.cTrav = collisionTraverser
self.avatarNodePath = avatarNodePath
# Set up the collision sphere
# This is a sphere on the ground to detect barrier collisions
self.cSphere = CollisionSphere(0.0, 0.0, 0.0, avatarRadius)
cSphereNode = CollisionNode('NPW.cSphereNode')
cSphereNode.addSolid(self.cSphere)
self.cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode)
cSphereNode.setFromCollideMask(self.cSphereBitMask)
cSphereNode.setIntoCollideMask(BitMask32.allOff())
# Set up the collison ray
# This is a ray cast from your head down to detect floor polygons.
# This ray start is arbitrarily high in the air. Feel free to use
# a higher or lower value depending on whether you want an avatar
# that is outside of the world to step up to the floor when they
# get under valid floor:
self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0)
cRayNode = CollisionNode('NPW.cRayNode')
cRayNode.addSolid(self.cRay)
self.cRayNodePath = avatarNodePath.attachNewNode(cRayNode)
cRayNode.setFromCollideMask(self.cRayBitMask)
cRayNode.setIntoCollideMask(BitMask32.allOff())
# set up wall collision mechanism
self.pusher = CollisionHandlerPusher()
self.pusher.setInPattern("enter%in")
self.pusher.setOutPattern("exit%in")
# set up floor collision mechanism
self.lifter = CollisionHandlerFloor()
self.lifter.setInPattern("on-floor")
self.lifter.setOutPattern("off-floor")
self.lifter.setOffset(floorOffset)
self.lifter.setReach(reach)
# Limit our rate-of-fall with the lifter.
# If this is too low, we actually "fall" off steep stairs
# and float above them as we go down. I increased this
# from 8.0 to 16.0 to prevent this
self.lifter.setMaxVelocity(16.0)
self.pusher.addCollider(self.cSphereNodePath, avatarNodePath)
self.lifter.addCollider(self.cRayNodePath, avatarNodePath)
# activate the collider with the traverser and pusher
self.setCollisionsActive(1)
def deleteCollisions(self):
del self.cTrav
del self.cSphere
self.cSphereNodePath.removeNode()
del self.cSphereNodePath
del self.cRay
self.cRayNodePath.removeNode()
del self.cRayNodePath
del self.pusher
del self.lifter
def setTag(self, key, value):
self.cSphereNodePath.setTag(key, value)
def setCollisionsActive(self, active = 1):
assert(self.debugPrint("setCollisionsActive(active%s)"%(active,)))
if self.collisionsActive != active:
self.collisionsActive = active
if active:
self.cTrav.addCollider(self.cSphereNodePath, self.pusher)
self.cTrav.addCollider(self.cRayNodePath, self.lifter)
else:
self.cTrav.removeCollider(self.cSphereNodePath)
self.cTrav.removeCollider(self.cRayNodePath)
# Now that we have disabled collisions, make one more pass
# right now to ensure we aren't standing in a wall.
self.oneTimeCollide()
def placeOnFloor(self):
"""
Make a reasonable effor to place the avatar on the ground.
For example, this is useful when switching away from the
current walker.
"""
# With these on, getAirborneHeight is not returning the correct value so
# when we open our book while swimming we pop down underneath the ground
# self.oneTimeCollide()
# self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.getAirborneHeight())
# Since this is the non physics walker - wont they already be on the ground?
return
def oneTimeCollide(self):
"""
Makes one quick collision pass for the avatar, for instance as
a one-time straighten-things-up operation after collisions
have been disabled.
"""
tempCTrav = CollisionTraverser("oneTimeCollide")
tempCTrav.addCollider(self.cSphereNodePath, self.pusher)
tempCTrav.addCollider(self.cRayNodePath, self.lifter)
tempCTrav.traverse(render)
def displayDebugInfo(self):
"""
For debug use.
"""
onScreenDebug.add("controls", "NonPhysicsWalker")
def handleAvatarControls(self, task):
"""
Check on the arrow keys and update the avatar.
"""
if not self.lifter.hasContact():
# hack fix for falling through the floor:
messenger.send("walkerIsOutOfWorld", [self.avatarNodePath])
# get the button states:
forward = inputState.isSet("forward")
reverse = inputState.isSet("reverse")
turnLeft = inputState.isSet("turnLeft")
turnRight = inputState.isSet("turnRight")
slide = inputState.isSet(self.slideName) or 0
#jump = inputState.isSet("jump")
# Determine what the speeds are based on the buttons:
self.speed=(forward and self.avatarControlForwardSpeed or
reverse and -self.avatarControlReverseSpeed)
# Should fSlide be renamed slideButton?
self.slideSpeed=slide and (
(turnLeft and -self.avatarControlForwardSpeed) or
(turnRight and self.avatarControlForwardSpeed))
self.rotationSpeed=not slide and (
(turnLeft and self.avatarControlRotateSpeed) or
(turnRight and -self.avatarControlRotateSpeed))
if __debug__:
debugRunning = inputState.isSet("debugRunning")
if debugRunning:
self.speed*=4.0
self.slideSpeed*=4.0
self.rotationSpeed*=1.25
if self.wantDebugIndicator:
self.displayDebugInfo()
# How far did we move based on the amount of time elapsed?
dt=ClockObject.getGlobalClock().getDt()
# Check to see if we're moving at all:
if self.speed or self.slideSpeed or self.rotationSpeed:
if self.stopThisFrame:
distance = 0.0
slideDistance = 0.0
rotation = 0.0
self.stopThisFrame = 0
else:
distance = dt * self.speed
slideDistance = dt * self.slideSpeed
rotation = dt * self.rotationSpeed
# Take a step in the direction of our previous heading.
self.vel=Vec3(Vec3.forward() * distance +
Vec3.right() * slideDistance)
if self.vel != Vec3.zero():
# rotMat is the rotation matrix corresponding to
# our previous heading.
rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
step=rotMat.xform(self.vel)
self.avatarNodePath.setFluidPos(Point3(self.avatarNodePath.getPos()+step))
self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
messenger.send("avatarMoving")
else:
self.vel.set(0.0, 0.0, 0.0)
self.__oldPosDelta = self.avatarNodePath.getPosDelta(render)
self.__oldDt = dt
self.worldVelocity = self.__oldPosDelta*(1/self.__oldDt)
return Task.cont
def doDeltaPos(self):
assert(self.debugPrint("doDeltaPos()"))
def reset(self):
assert(self.debugPrint("reset()"))
def enableAvatarControls(self):
"""
Activate the arrow keys, etc.
"""
assert(self.debugPrint("enableAvatarControls"))
assert self.collisionsActive
taskName = "AvatarControls-%s"%(id(self),)
# remove any old
taskMgr.remove(taskName)
# spawn the new task
taskMgr.add(self.handleAvatarControls, taskName)
def disableAvatarControls(self):
"""
Ignore the arrow keys, etc.
"""
assert(self.debugPrint("disableAvatarControls"))
taskName = "AvatarControls-%s"%(id(self),)
taskMgr.remove(taskName)
if __debug__:
def debugPrint(self, message):
"""for debugging"""
return self.notify.debug(
str(id(self))+' '+message)