| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625 |
- """
- GravityWalker.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
- import math
- class GravityWalker(DirectObject.DirectObject):
- notify = DirectNotifyGlobal.directNotify.newCategory("GravityWalker")
- wantDebugIndicator = base.config.GetBool('want-avatar-physics-indicator', 0)
- wantFloorSphere = base.config.GetBool('want-floor-sphere', 0)
- # special methods
- def __init__(self, gravity = -32.1740, standableGround=0.707,
- hardLandingForce=16.0):
- assert self.notify.debugStateCall(self)
- DirectObject.DirectObject.__init__(self)
- self.__gravity=gravity
- self.__standableGround=standableGround
- self.__hardLandingForce=hardLandingForce
- self.mayJump = 1
- self.jumpDelayTask = None
- self.controlsTask = None
- self.indicatorTask = None
- self.falling = 0
- self.needToDeltaPos = 0
- self.physVelocityIndicator=None
- self.avatarControlForwardSpeed=0
- self.avatarControlJumpForce=0
- self.avatarControlReverseSpeed=0
- self.avatarControlRotateSpeed=0
- self.getAirborneHeight=None
- self.priorParent=Vec3(0)
- self.__oldPosDelta=Vec3(0)
- self.__oldDt=0
- self.moving=0
- self.speed=0.0
- self.rotationSpeed=0.0
- self.slideSpeed=0.0
- self.vel=Vec3(0.0)
- self.collisionsActive = 0
- self.isAirborne = 0
- self.highMark = 0
- def delete(self):
- assert self.notify.debugStateCall(self)
- if self.doLaterTask is not None:
- self.doLaterTask.remove()
- del self.doLaterTask
- #DirectObject.DirectObject.delete(self)
- """
- def spawnTest(self):
- assert self.notify.debugStateCall(self)
- if not self.wantDebugIndicator:
- return
- from pandac.PandaModules import *
- from direct.interval.IntervalGlobal import *
- from toontown.coghq import MovingPlatform
- if hasattr(self, "platform"):
- # Remove the prior instantiation:
- self.moveIval.pause()
- del self.moveIval
- self.platform.destroy()
- del self.platform
- self.platform2.destroy()
- del self.platform2
- model = loader.loadModelCopy('phase_9/models/cogHQ/platform1')
- fakeId = id(self)
- self.platform = MovingPlatform.MovingPlatform()
- self.platform.setupCopyModel(fakeId, model, 'platformcollision')
- self.platformRoot = render.attachNewNode("GravityWalker-spawnTest-%s"%fakeId)
- self.platformRoot.setPos(base.localAvatar, Vec3(0.0, 0.0, 1.0))
- self.platformRoot.setHpr(base.localAvatar, Vec3.zero())
- self.platform.reparentTo(self.platformRoot)
- self.platform2 = MovingPlatform.MovingPlatform()
- self.platform2.setupCopyModel(1+fakeId, model, 'platformcollision')
- self.platform2Root = render.attachNewNode("GravityWalker-spawnTest2-%s"%fakeId)
- self.platform2Root.setPos(base.localAvatar, Vec3(-16.0, 30.0, 1.0))
- self.platform2Root.setHpr(base.localAvatar, Vec3.zero())
- self.platform2.reparentTo(self.platform2Root)
- duration = 5
- self.moveIval = Parallel(
- Sequence(
- WaitInterval(0.3),
- LerpPosInterval(self.platform, duration,
- Vec3(0.0, 30.0, 0.0),
- name='platformOut%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform, duration,
- Vec3(0.0, 0.0, 0.0),
- name='platformBack%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform, duration,
- Vec3(0.0, 0.0, 30.0),
- name='platformUp%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform, duration,
- Vec3(0.0, 0.0, 0.0),
- name='platformDown%s' % fakeId,
- fluid = 1),
- ),
- Sequence(
- WaitInterval(0.3),
- LerpPosInterval(self.platform2, duration,
- Vec3(0.0, -30.0, 0.0),
- name='platform2Out%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform2, duration,
- Vec3(0.0, 30.0, 30.0),
- name='platform2Back%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform2, duration,
- Vec3(0.0, -30.0, 0.0),
- name='platform2Up%s' % fakeId,
- fluid = 1),
- WaitInterval(0.3),
- LerpPosInterval(self.platform2, duration,
- Vec3(0.0, 0.0, 0.0),
- name='platformDown%s' % fakeId,
- fluid = 1),
- ),
- name='platformIval%s' % fakeId,
- )
- self.moveIval.loop()
- """
- def setWalkSpeed(self, forward, jump, reverse, rotate):
- assert self.notify.debugStateCall(self)
- self.avatarControlForwardSpeed=forward
- self.avatarControlJumpForce=jump
- self.avatarControlReverseSpeed=reverse
- self.avatarControlRotateSpeed=rotate
- def getSpeeds(self):
- #assert(self.debugPrint("getSpeeds()"))
- return (self.speed, self.rotationSpeed, self.slideSpeed)
- def setAvatar(self, avatar):
- self.avatar = avatar
- if avatar is not None:
- pass # setup the avatar
- def setupRay(self, bitmask, floorOffset, reach):
- assert self.notify.debugStateCall(self)
- # 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:
- cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0)
- cRayNode = CollisionNode('GW.cRayNode')
- cRayNode.addSolid(cRay)
- self.cRayNodePath = self.avatarNodePath.attachNewNode(cRayNode)
- cRayNode.setFromCollideMask(bitmask)
- cRayNode.setIntoCollideMask(BitMask32.allOff())
- # set up floor collision mechanism
- self.lifter = CollisionHandlerGravity()
- self.lifter.setGravity(32.174 * 2.0)
- self.lifter.addInPattern("enter%in")
- self.lifter.addOutPattern("exit%in")
- 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.lifter.addCollider(self.cRayNodePath, self.avatarNodePath)
- def setupWallSphere(self, bitmask, avatarRadius):
- """
- Set up the collision sphere
- """
- assert self.notify.debugStateCall(self)
- # This is a sphere on the ground to detect collisions with
- # walls, but not the floor.
- self.avatarRadius = avatarRadius
- cSphere = CollisionSphere(0.0, 0.0, avatarRadius, avatarRadius)
- cSphereNode = CollisionNode('GW.cWallSphereNode')
- cSphereNode.addSolid(cSphere)
- cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
- cSphereNode.setFromCollideMask(bitmask)
- cSphereNode.setIntoCollideMask(BitMask32.allOff())
- # set up collision mechanism
- handler = CollisionHandlerPusher()
- #handler.setInPattern("pusher_enter%in")
- #handler.setOutPattern("pusher_exit%in")
- handler.addCollider(cSphereNodePath, self.avatarNodePath)
- self.pusher = handler
- self.cWallSphereNodePath = cSphereNodePath
- def setupEventSphere(self, bitmask, avatarRadius):
- """
- Set up the collision sphere
- """
- assert self.notify.debugStateCall(self)
- # This is a sphere a little larger than the wall sphere to
- # trigger events.
- self.avatarRadius = avatarRadius
- cSphere = CollisionSphere(0.0, 0.0, avatarRadius-0.1, avatarRadius*1.04)
- # Mark it intangible just to emphasize its non-physical purpose.
- cSphere.setTangible(0)
- cSphereNode = CollisionNode('GW.cEventSphereNode')
- cSphereNode.addSolid(cSphere)
- cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
- cSphereNode.setFromCollideMask(bitmask)
- cSphereNode.setIntoCollideMask(BitMask32.allOff())
- # set up collision mechanism
- handler = CollisionHandlerEvent()
- handler.addInPattern("enter%in")
- handler.addOutPattern("exit%in")
- self.event = handler
- self.cEventSphereNodePath = cSphereNodePath
- def setupFloorSphere(self, bitmask, avatarRadius):
- """
- Set up the collision sphere
- """
- assert self.notify.debugStateCall(self)
- # This is a tiny sphere concentric with the wallSphere to keep
- # us from slipping through floors.
- self.avatarRadius = avatarRadius
- cSphere = CollisionSphere(0.0, 0.0, avatarRadius, 0.01)
- cSphereNode = CollisionNode('GW.cFloorSphereNode')
- cSphereNode.addSolid(cSphere)
- cSphereNodePath = self.avatarNodePath.attachNewNode(cSphereNode)
- cSphereNode.setFromCollideMask(bitmask)
- cSphereNode.setIntoCollideMask(BitMask32.allOff())
- # set up collision mechanism
- handler = CollisionHandlerPusher()
- #handler.setInPattern("pusherFloor_enter%in")
- #handler.setOutPattern("pusherFloor_exit%in")
- handler.addCollider(cSphereNodePath, self.avatarNodePath)
- self.pusherFloor = handler
- self.cFloorSphereNodePath = cSphereNodePath
- def setWallBitMask(self, bitMask):
- self.wallBitmask = bitMask
- def setFloorBitMask(self, bitMask):
- self.floorBitmask = bitMask
- def initializeCollisions(self, collisionTraverser, avatarNodePath,
- avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0):
- """
- floorOffset is how high the avatar can reach. I.e. if the avatar
- walks under a ledge that is <= floorOffset above the ground (a
- double floor situation), the avatar will step up on to the
- ledge (instantly).
- Set up the avatar collisions
- """
- assert self.notify.debugStateCall(self)
- assert not avatarNodePath.isEmpty()
- self.avatarNodePath = avatarNodePath
- self.cTrav = collisionTraverser
- self.setupRay(self.floorBitmask, floorOffset, reach)
- self.setupWallSphere(self.wallBitmask, avatarRadius)
- self.setupEventSphere(self.wallBitmask, avatarRadius)
- if self.wantFloorSphere:
- self.setupFloorSphere(self.floorBitmask, avatarRadius)
- self.setCollisionsActive(1)
- def setTag(self, key, value):
- self.cEventSphereNodePath.setTag(key, value)
- def setAirborneHeightFunc(self, unused_parameter):
- assert self.notify.debugStateCall(self)
- self.getAirborneHeight = self.lifter.getAirborneHeight
- def getAirborneHeight(self):
- assert self.notify.debugStateCall(self)
- self.lifter.getAirborneHeight()
- def setAvatarPhysicsIndicator(self, indicator):
- """
- indicator is a NodePath
- """
- assert self.notify.debugStateCall(self)
- self.cWallSphereNodePath.show()
- def deleteCollisions(self):
- assert self.notify.debugStateCall(self)
- del self.cTrav
- self.cWallSphereNodePath.removeNode()
- del self.cWallSphereNodePath
- if self.wantFloorSphere:
- self.cFloorSphereNodePath.removeNode()
- del self.cFloorSphereNodePath
- del self.pusher
- # del self.pusherFloor
- del self.event
- del self.lifter
- del self.getAirborneHeight
- def setCollisionsActive(self, active = 1):
- assert self.notify.debugStateCall(self)
- if self.collisionsActive != active:
- self.collisionsActive = active
- # Each time we change the collision geometry, make one
- # more pass to ensure we aren't standing in a wall.
- self.oneTimeCollide()
- if active:
- if 1:
- # Please let skyler or drose know if this is causing a problem
- # This is a bit of a hack fix:
- self.avatarNodePath.setP(0.0)
- self.avatarNodePath.setR(0.0)
- self.cTrav.addCollider(self.cWallSphereNodePath, self.pusher)
- if self.wantFloorSphere:
- self.cTrav.addCollider(self.cFloorSphereNodePath, self.pusherFloor)
- self.cTrav.addCollider(self.cEventSphereNodePath, self.event)
- self.cTrav.addCollider(self.cRayNodePath, self.lifter)
- else:
- self.cTrav.removeCollider(self.cWallSphereNodePath)
- if self.wantFloorSphere:
- self.cTrav.removeCollider(self.cFloorSphereNodePath)
- self.cTrav.removeCollider(self.cEventSphereNodePath)
- self.cTrav.removeCollider(self.cRayNodePath)
- def getCollisionsActive(self):
- assert(self.debugPrint("getCollisionsActive() returning=%s"%(
- self.collisionsActive,)))
- return self.collisionsActive
- 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.
- """
- assert self.notify.debugStateCall(self)
- self.oneTimeCollide()
- self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.lifter.getAirborneHeight())
- 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.
- """
- assert self.notify.debugStateCall(self)
- self.isAirborne = 0
- self.mayJump = 1
- tempCTrav = CollisionTraverser("oneTimeCollide")
- tempCTrav.addCollider(self.cWallSphereNodePath, self.pusher)
- if self.wantFloorSphere:
- tempCTrav.addCollider(self.cFloorSphereNodePath, self.event)
- tempCTrav.addCollider(self.cRayNodePath, self.lifter)
- tempCTrav.traverse(render)
- def setMayJump(self, task):
- """
- This function's use is internal to this class (maybe I'll add
- the __ someday). Anyway, if you want to enable or disable
- jumping in a general way see the ControlManager (don't use this).
- """
- assert self.notify.debugStateCall(self)
- self.mayJump = 1
- return Task.done
- def startJumpDelay(self, delay):
- assert self.notify.debugStateCall(self)
- if self.jumpDelayTask:
- self.jumpDelayTask.remove()
- self.mayJump = 0
- self.jumpDelayTask=taskMgr.doMethodLater(
- delay,
- self.setMayJump,
- "jumpDelay-%s"%id(self))
- def addBlastForce(self, vector):
- self.lifter.addVelocity(vector.length())
- def displayDebugInfo(self):
- """
- For debug use.
- """
- onScreenDebug.add("w controls", "GravityWalker")
- onScreenDebug.add("w airborneHeight", self.lifter.getAirborneHeight())
- onScreenDebug.add("w falling", self.falling)
- onScreenDebug.add("w isOnGround", self.lifter.isOnGround())
- #onScreenDebug.add("w gravity", self.lifter.getGravity())
- #onScreenDebug.add("w jumpForce", self.avatarControlJumpForce)
- onScreenDebug.add("w contact normal", self.lifter.getContactNormal().pPrintValues())
- onScreenDebug.add("w mayJump", self.mayJump)
- onScreenDebug.add("w impact", self.lifter.getImpactVelocity())
- onScreenDebug.add("w velocity", self.lifter.getVelocity())
- onScreenDebug.add("w isAirborne", self.isAirborne)
- onScreenDebug.add("w hasContact", self.lifter.hasContact())
- def handleAvatarControls(self, task):
- """
- Check on the arrow keys and update the avatar.
- """
- # get the button states:
- run = inputState.isSet("run")
- forward = inputState.isSet("forward")
- reverse = inputState.isSet("reverse")
- turnLeft = inputState.isSet("turnLeft")
- turnRight = inputState.isSet("turnRight")
- slideLeft = inputState.isSet("slideLeft")
- slideRight = inputState.isSet("slideRight")
- jump = inputState.isSet("jump")
- # Determine what the speeds are based on the buttons:
- self.speed=(forward and self.avatarControlForwardSpeed or
- reverse and -self.avatarControlReverseSpeed)
- # Use reverse speed for strafe - that should be about what you want
- self.slideSpeed=(slideLeft and -self.avatarControlReverseSpeed or
- slideRight and self.avatarControlReverseSpeed)
- self.rotationSpeed=not (slideLeft or slideRight) 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.needToDeltaPos:
- self.setPriorParentVector()
- self.needToDeltaPos = 0
- if self.wantDebugIndicator:
- self.displayDebugInfo()
- if self.lifter.isOnGround():
- if self.isAirborne:
- self.isAirborne = 0
- assert(self.debugPrint("isAirborne 0 due to isOnGround() true"))
- impact = self.lifter.getImpactVelocity()
- if impact < -30.0:
- messenger.send("jumpHardLand")
- self.startJumpDelay(0.3)
- else:
- messenger.send("jumpLand")
- if impact < -5.0:
- self.startJumpDelay(0.2)
- # else, ignore the little potholes.
- assert(self.isAirborne == 0)
- self.priorParent = Vec3.zero()
- if jump and self.mayJump:
- # The jump button is down and we're close
- # enough to the ground to jump.
- self.lifter.addVelocity(self.avatarControlJumpForce)
- messenger.send("jumpStart")
- self.isAirborne = 1
- assert(self.debugPrint("isAirborne 1 due to jump"))
- else:
- if self.isAirborne == 0:
- assert(self.debugPrint("isAirborne 1 due to isOnGround() false"))
- self.isAirborne = 1
- self.__oldPosDelta = self.avatarNodePath.getPosDelta(render)
- # How far did we move based on the amount of time elapsed?
- self.__oldDt = ClockObject.getGlobalClock().getDt()
- dt=self.__oldDt
- # Check to see if we're moving at all:
- self.moving = self.speed or self.slideSpeed or self.rotationSpeed or (self.priorParent!=Vec3.zero())
- if self.moving:
- 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() or self.priorParent != Vec3.zero():
- # rotMat is the rotation matrix corresponding to
- # our previous heading.
- rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
- contact = self.lifter.getContactNormal()
- forward = contact.cross(Vec3.right())
- forward = Vec3(rotMat.xform(forward))
- # Consider commenting out this normalize. If you do so
- # then going up and down slops is a touch slower and
- # steeper terrain can cut the movement in half. Without
- # the normalize the movement is slowed by the cosine of
- # the slope (i.e. it is multiplied by the sign as a
- # side effect of the cross product above).
- forward.normalize()
- self.vel=Vec3(forward * distance)
- if slideDistance:
- right = forward.cross(contact)
- right = Vec3(rotMat.xform(Vec3.right()))
- # See note above for forward.normalize()
- right.normalize()
- self.vel=Vec3(self.vel + right * slideDistance)
- step=self.vel + (self.priorParent * dt)
- self.avatarNodePath.setFluidPos(Point3(
- self.avatarNodePath.getPos()+step))
- self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
- else:
- self.vel.set(0.0, 0.0, 0.0)
- if self.moving or jump:
- messenger.send("avatarMoving")
- return Task.cont
- def doDeltaPos(self):
- assert self.notify.debugStateCall(self)
- self.needToDeltaPos = 1
- def setPriorParentVector(self):
- assert self.notify.debugStateCall(self)
- if __debug__:
- onScreenDebug.add("__oldDt", "% 10.4f"%self.__oldDt)
- onScreenDebug.add("self.__oldPosDelta",
- self.__oldPosDelta.pPrintValues())
- velocity = self.__oldPosDelta*(1.0/self.__oldDt)
- self.priorParent = Vec3(velocity)
- if __debug__:
- if self.wantDebugIndicator:
- onScreenDebug.add("priorParent", self.priorParent.pPrintValues())
- def reset(self):
- assert self.notify.debugStateCall(self)
- self.lifter.setVelocity(0.0)
- self.priorParent=Vec3.zero()
- def getVelocity(self):
- return self.vel
- def enableAvatarControls(self):
- """
- Activate the arrow keys, etc.
- """
- assert self.notify.debugStateCall(self)
- assert self.collisionsActive
- #*#if __debug__:
- #*# self.accept("control-f3", self.spawnTest) #*#
- # remove any old
- if self.controlsTask:
- self.controlsTask.remove()
- # spawn the new task
- taskName = "AvatarControls-%s"%(id(self),)
- self.controlsTask = taskMgr.add(self.handleAvatarControls, taskName, 25)
- self.isAirborne = 0
- self.mayJump = 1
- if self.physVelocityIndicator:
- if self.indicatorTask:
- self.indicatorTask.remove()
- self.indicatorTask = taskMgr.add(
- self.avatarPhysicsIndicator,
- "AvatarControlsIndicator-%s"%(id(self),), 35)
- def disableAvatarControls(self):
- """
- Ignore the arrow keys, etc.
- """
- assert self.notify.debugStateCall(self)
- if self.controlsTask:
- self.controlsTask.remove()
- self.controlsTask = None
- if self.indicatorTask:
- self.indicatorTask.remove()
- self.indicatorTask = None
- if self.jumpDelayTask:
- self.jumpDelayTask.remove()
- self.jumpDelayTask = None
- if __debug__:
- self.ignore("control-f3") #*#
- if __debug__:
- def debugPrint(self, message):
- """for debugging"""
- return self.notify.debug(
- str(id(self))+' '+message)
|