NonPhysicsWalker.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. """
  2. NonPhysicsWalker.py is for avatars.
  3. A walker control such as this one provides:
  4. - creation of the collision nodes
  5. - handling the keyboard and mouse input for avatar movement
  6. - moving the avatar
  7. it does not:
  8. - play sounds
  9. - play animations
  10. although it does send messeges that allow a listener to play sounds or
  11. animations based on walker events.
  12. """
  13. from ShowBaseGlobal import *
  14. import DirectNotifyGlobal
  15. import DirectObject
  16. class NonPhysicsWalker(DirectObject.DirectObject):
  17. notify = DirectNotifyGlobal.directNotify.newCategory("NonPhysicsWalker")
  18. # Ghost mode overrides this:
  19. slideName = "slide-is-disabled"
  20. # special methods
  21. def __init__(self):
  22. DirectObject.DirectObject.__init__(self)
  23. self.collisionsActive = 0
  24. self.speed=0.0
  25. self.rotationSpeed=0.0
  26. self.vel=Vec3(0.0, 0.0, 0.0)
  27. self.stopThisFrame = 0
  28. def setWalkSpeed(self, forward, jump, reverse, rotate):
  29. assert(self.debugPrint("setWalkSpeed()"))
  30. self.avatarControlForwardSpeed=forward
  31. #self.avatarControlJumpForce=jump
  32. self.avatarControlReverseSpeed=reverse
  33. self.avatarControlRotateSpeed=rotate
  34. def getSpeeds(self):
  35. #assert(self.debugPrint("getSpeeds()"))
  36. return (self.speed, self.rotationSpeed)
  37. def initializeCollisions(self, collisionTraverser, avatarNodePath,
  38. wallCollideMask, floorCollideMask,
  39. avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0):
  40. """
  41. Set up the avatar for collisions
  42. """
  43. assert not avatarNodePath.isEmpty()
  44. self.cTrav = collisionTraverser
  45. self.avatarNodePath = avatarNodePath
  46. # Set up the collision sphere
  47. # This is a sphere on the ground to detect barrier collisions
  48. self.cSphere = CollisionSphere(0.0, 0.0, 0.0, avatarRadius)
  49. cSphereNode = CollisionNode('NPW.cSphereNode')
  50. cSphereNode.addSolid(self.cSphere)
  51. self.cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode)
  52. self.cSphereBitMask = wallCollideMask
  53. cSphereNode.setFromCollideMask(self.cSphereBitMask)
  54. cSphereNode.setIntoCollideMask(BitMask32.allOff())
  55. # Set up the collison ray
  56. # This is a ray cast from your head down to detect floor polygons.
  57. # This ray start is arbitrarily high in the air. Feel free to use
  58. # a higher or lower value depending on whether you want an avatar
  59. # that is outside of the world to step up to the floor when they
  60. # get under valid floor:
  61. self.cRay = CollisionRay(0.0, 0.0, 400000.0, 0.0, 0.0, -1.0)
  62. cRayNode = CollisionNode('NPW.cRayNode')
  63. cRayNode.addSolid(self.cRay)
  64. self.cRayNodePath = avatarNodePath.attachNewNode(cRayNode)
  65. self.cRayBitMask = floorCollideMask
  66. cRayNode.setFromCollideMask(self.cRayBitMask)
  67. cRayNode.setIntoCollideMask(BitMask32.allOff())
  68. # set up wall collision mechanism
  69. self.pusher = CollisionHandlerPusher()
  70. self.pusher.setInPattern("enter%in")
  71. self.pusher.setOutPattern("exit%in")
  72. # set up floor collision mechanism
  73. self.lifter = CollisionHandlerFloor()
  74. self.lifter.setInPattern("on-floor")
  75. self.lifter.setOutPattern("off-floor")
  76. self.lifter.setOffset(floorOffset)
  77. self.lifter.setReach(reach)
  78. # Limit our rate-of-fall with the lifter.
  79. # If this is too low, we actually "fall" off steep stairs
  80. # and float above them as we go down. I increased this
  81. # from 8.0 to 16.0 to prevent this
  82. self.lifter.setMaxVelocity(16.0)
  83. self.pusher.addCollider(self.cSphereNodePath, avatarNodePath)
  84. self.lifter.addCollider(self.cRayNodePath, avatarNodePath)
  85. # activate the collider with the traverser and pusher
  86. self.setCollisionsActive(1)
  87. def setAirborneHeightFunc(self, getAirborneHeight):
  88. self.getAirborneHeight = getAirborneHeight
  89. def deleteCollisions(self):
  90. del self.cTrav
  91. del self.cSphere
  92. self.cSphereNodePath.removeNode()
  93. del self.cSphereNodePath
  94. del self.cRay
  95. self.cRayNodePath.removeNode()
  96. del self.cRayNodePath
  97. del self.pusher
  98. del self.lifter
  99. def setCollisionsActive(self, active = 1):
  100. assert(self.debugPrint("setCollisionsActive(active%s)"%(active,)))
  101. if self.collisionsActive != active:
  102. self.collisionsActive = active
  103. if active:
  104. self.cTrav.addCollider(self.cSphereNodePath, self.pusher)
  105. self.cTrav.addCollider(self.cRayNodePath, self.lifter)
  106. else:
  107. self.cTrav.removeCollider(self.cSphereNodePath)
  108. self.cTrav.removeCollider(self.cRayNodePath)
  109. # Now that we have disabled collisions, make one more pass
  110. # right now to ensure we aren't standing in a wall.
  111. self.oneTimeCollide()
  112. def placeOnFloor(self):
  113. """
  114. Make a reasonable effor to place the avatar on the ground.
  115. For example, this is useful when switching away from the
  116. current walker.
  117. """
  118. # With these on, getAirborneHeight is not returning the correct value so
  119. # when we open our book while swimming we pop down underneath the ground
  120. # self.oneTimeCollide()
  121. # self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.getAirborneHeight())
  122. # Since this is the non physics walker - wont they already be on the ground?
  123. return
  124. def oneTimeCollide(self):
  125. """
  126. Makes one quick collision pass for the avatar, for instance as
  127. a one-time straighten-things-up operation after collisions
  128. have been disabled.
  129. """
  130. tempCTrav = CollisionTraverser("oneTimeCollide")
  131. tempCTrav.addCollider(self.cSphereNodePath, self.pusher)
  132. tempCTrav.addCollider(self.cRayNodePath, self.lifter)
  133. tempCTrav.traverse(render)
  134. def handleAvatarControls(self, task):
  135. """
  136. Check on the arrow keys and update the avatar.
  137. """
  138. if not self.lifter.hasContact():
  139. # hack fix for falling through the floor:
  140. messenger.send("walkerIsOutOfWorld", [self.avatarNodePath])
  141. # get the button states:
  142. forward = inputState.isSet("forward")
  143. reverse = inputState.isSet("reverse")
  144. turnLeft = inputState.isSet("turnLeft")
  145. turnRight = inputState.isSet("turnRight")
  146. slide = inputState.isSet(self.slideName) or 0
  147. #jump = inputState.isSet("jump")
  148. # Determine what the speeds are based on the buttons:
  149. self.speed=(forward and self.avatarControlForwardSpeed or
  150. reverse and -self.avatarControlReverseSpeed)
  151. # Should fSlide be renamed slideButton?
  152. self.slideSpeed=slide and (
  153. (turnLeft and -self.avatarControlForwardSpeed) or
  154. (turnRight and self.avatarControlForwardSpeed))
  155. self.rotationSpeed=not slide and (
  156. (turnLeft and self.avatarControlRotateSpeed) or
  157. (turnRight and -self.avatarControlRotateSpeed))
  158. # How far did we move based on the amount of time elapsed?
  159. dt=ClockObject.getGlobalClock().getDt()
  160. # Check to see if we're moving at all:
  161. if self.speed or self.slideSpeed or self.rotationSpeed:
  162. if self.stopThisFrame:
  163. distance = 0.0
  164. slideDistance = 0.0
  165. rotation = 0.0
  166. self.stopThisFrame = 0
  167. else:
  168. distance = dt * self.speed
  169. slideDistance = dt * self.slideSpeed
  170. rotation = dt * self.rotationSpeed
  171. # Take a step in the direction of our previous heading.
  172. self.vel=Vec3(Vec3.forward() * distance +
  173. Vec3.right() * slideDistance)
  174. if self.vel != Vec3.zero():
  175. # rotMat is the rotation matrix corresponding to
  176. # our previous heading.
  177. rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
  178. step=rotMat.xform(self.vel)
  179. self.avatarNodePath.setFluidPos(Point3(self.avatarNodePath.getPos()+step))
  180. self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
  181. messenger.send("avatarMoving")
  182. else:
  183. self.vel.set(0.0, 0.0, 0.0)
  184. return Task.cont
  185. def doDeltaPos(self):
  186. assert(self.debugPrint("doDeltaPos()"))
  187. def reset(self):
  188. assert(self.debugPrint("reset()"))
  189. def enableAvatarControls(self):
  190. """
  191. Activate the arrow keys, etc.
  192. """
  193. assert(self.debugPrint("enableAvatarControls"))
  194. assert self.collisionsActive
  195. taskName = "AvatarControls%s"%(id(self),)
  196. # remove any old
  197. taskMgr.remove(taskName)
  198. # spawn the new task
  199. taskMgr.add(self.handleAvatarControls, taskName)
  200. def disableAvatarControls(self):
  201. """
  202. Ignore the arrow keys, etc.
  203. """
  204. assert(self.debugPrint("disableAvatarControls"))
  205. taskName = "AvatarControls%s"%(id(self),)
  206. taskMgr.remove(taskName)
  207. if __debug__:
  208. def debugPrint(self, message):
  209. """for debugging"""
  210. return self.notify.debug(
  211. str(id(self))+' '+message)