NonPhysicsWalker.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  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 messages that allow a listener to play sounds or
  11. animations based on walker events.
  12. """
  13. from direct.directnotify import DirectNotifyGlobal
  14. from direct.showbase import DirectObject
  15. from direct.controls.ControlManager import CollisionHandlerRayStart
  16. from direct.showbase.InputStateGlobal import inputState
  17. from direct.task.Task import Task
  18. from panda3d.core import *
  19. class NonPhysicsWalker(DirectObject.DirectObject):
  20. notify = DirectNotifyGlobal.directNotify.newCategory("NonPhysicsWalker")
  21. wantDebugIndicator = ConfigVariableBool('want-avatar-physics-indicator', False)
  22. # Ghost mode overrides this:
  23. slideName = "slide-is-disabled"
  24. # special methods
  25. def __init__(self):
  26. DirectObject.DirectObject.__init__(self)
  27. self.worldVelocity = Vec3.zero()
  28. self.collisionsActive = 0
  29. self.speed=0.0
  30. self.rotationSpeed=0.0
  31. self.slideSpeed=0.0
  32. self.vel=Vec3(0.0, 0.0, 0.0)
  33. self.stopThisFrame = 0
  34. def setWalkSpeed(self, forward, jump, reverse, rotate):
  35. assert self.debugPrint("setWalkSpeed()")
  36. self.avatarControlForwardSpeed=forward
  37. #self.avatarControlJumpForce=jump
  38. self.avatarControlReverseSpeed=reverse
  39. self.avatarControlRotateSpeed=rotate
  40. def getSpeeds(self):
  41. #assert self.debugPrint("getSpeeds()")
  42. return (self.speed, self.rotationSpeed, self.slideSpeed)
  43. def setAvatar(self, avatar):
  44. self.avatar = avatar
  45. if avatar is not None:
  46. pass # setup the avatar
  47. def setAirborneHeightFunc(self, getAirborneHeight):
  48. self.getAirborneHeight = getAirborneHeight
  49. def setWallBitMask(self, bitMask):
  50. self.cSphereBitMask = bitMask
  51. def setFloorBitMask(self, bitMask):
  52. self.cRayBitMask = bitMask
  53. def swapFloorBitMask(self, oldMask, newMask):
  54. self.cRayBitMask = self.cRayBitMask &~ oldMask
  55. self.cRayBitMask |= newMask
  56. if self.cRayNodePath and not self.cRayNodePath.isEmpty():
  57. self.cRayNodePath.node().setFromCollideMask(self.cRayBitMask)
  58. def initializeCollisions(self, collisionTraverser, avatarNodePath,
  59. avatarRadius = 1.4, floorOffset = 1.0, reach = 1.0):
  60. """
  61. Set up the avatar for collisions
  62. """
  63. assert not avatarNodePath.isEmpty()
  64. self.cTrav = collisionTraverser
  65. self.avatarNodePath = avatarNodePath
  66. # Set up the collision sphere
  67. # This is a sphere on the ground to detect barrier collisions
  68. self.cSphere = CollisionSphere(0.0, 0.0, 0.0, avatarRadius)
  69. cSphereNode = CollisionNode('NPW.cSphereNode')
  70. cSphereNode.addSolid(self.cSphere)
  71. self.cSphereNodePath = avatarNodePath.attachNewNode(cSphereNode)
  72. cSphereNode.setFromCollideMask(self.cSphereBitMask)
  73. cSphereNode.setIntoCollideMask(BitMask32.allOff())
  74. # Set up the collison ray
  75. # This is a ray cast from your head down to detect floor polygons.
  76. # This ray start is arbitrarily high in the air. Feel free to use
  77. # a higher or lower value depending on whether you want an avatar
  78. # that is outside of the world to step up to the floor when they
  79. # get under valid floor:
  80. self.cRay = CollisionRay(0.0, 0.0, CollisionHandlerRayStart, 0.0, 0.0, -1.0)
  81. cRayNode = CollisionNode('NPW.cRayNode')
  82. cRayNode.addSolid(self.cRay)
  83. self.cRayNodePath = avatarNodePath.attachNewNode(cRayNode)
  84. cRayNode.setFromCollideMask(self.cRayBitMask)
  85. cRayNode.setIntoCollideMask(BitMask32.allOff())
  86. # set up wall collision mechanism
  87. self.pusher = CollisionHandlerPusher()
  88. self.pusher.setInPattern("enter%in")
  89. self.pusher.setOutPattern("exit%in")
  90. # set up floor collision mechanism
  91. self.lifter = CollisionHandlerFloor()
  92. self.lifter.setInPattern("on-floor")
  93. self.lifter.setOutPattern("off-floor")
  94. self.lifter.setOffset(floorOffset)
  95. self.lifter.setReach(reach)
  96. # Limit our rate-of-fall with the lifter.
  97. # If this is too low, we actually "fall" off steep stairs
  98. # and float above them as we go down. I increased this
  99. # from 8.0 to 16.0 to prevent this
  100. self.lifter.setMaxVelocity(16.0)
  101. self.pusher.addCollider(self.cSphereNodePath, avatarNodePath)
  102. self.lifter.addCollider(self.cRayNodePath, avatarNodePath)
  103. # activate the collider with the traverser and pusher
  104. self.setCollisionsActive(1)
  105. def deleteCollisions(self):
  106. del self.cTrav
  107. del self.cSphere
  108. self.cSphereNodePath.removeNode()
  109. del self.cSphereNodePath
  110. del self.cRay
  111. self.cRayNodePath.removeNode()
  112. del self.cRayNodePath
  113. del self.pusher
  114. del self.lifter
  115. def setTag(self, key, value):
  116. self.cSphereNodePath.setTag(key, value)
  117. def setCollisionsActive(self, active = 1):
  118. assert self.debugPrint("setCollisionsActive(active%s)"%(active,))
  119. if self.collisionsActive != active:
  120. self.collisionsActive = active
  121. if active:
  122. self.cTrav.addCollider(self.cSphereNodePath, self.pusher)
  123. self.cTrav.addCollider(self.cRayNodePath, self.lifter)
  124. else:
  125. self.cTrav.removeCollider(self.cSphereNodePath)
  126. self.cTrav.removeCollider(self.cRayNodePath)
  127. # Now that we have disabled collisions, make one more pass
  128. # right now to ensure we aren't standing in a wall.
  129. self.oneTimeCollide()
  130. def placeOnFloor(self):
  131. """
  132. Make a reasonable effor to place the avatar on the ground.
  133. For example, this is useful when switching away from the
  134. current walker.
  135. """
  136. # With these on, getAirborneHeight is not returning the correct value so
  137. # when we open our book while swimming we pop down underneath the ground
  138. # self.oneTimeCollide()
  139. # self.avatarNodePath.setZ(self.avatarNodePath.getZ()-self.getAirborneHeight())
  140. # Since this is the non physics walker - wont they already be on the ground?
  141. return
  142. def oneTimeCollide(self):
  143. """
  144. Makes one quick collision pass for the avatar, for instance as
  145. a one-time straighten-things-up operation after collisions
  146. have been disabled.
  147. """
  148. tempCTrav = CollisionTraverser("oneTimeCollide")
  149. tempCTrav.addCollider(self.cSphereNodePath, self.pusher)
  150. tempCTrav.addCollider(self.cRayNodePath, self.lifter)
  151. tempCTrav.traverse(render)
  152. def addBlastForce(self, vector):
  153. pass
  154. def displayDebugInfo(self):
  155. """
  156. For debug use.
  157. """
  158. onScreenDebug.add("controls", "NonPhysicsWalker")
  159. def _calcSpeeds(self):
  160. # get the button states:
  161. forward = inputState.isSet("forward")
  162. reverse = inputState.isSet("reverse")
  163. turnLeft = inputState.isSet("turnLeft")
  164. turnRight = inputState.isSet("turnRight")
  165. slide = inputState.isSet(self.slideName) or 0
  166. #jump = inputState.isSet("jump")
  167. # Check for Auto-Run
  168. if base.localAvatar.getAutoRun():
  169. forward = 1
  170. reverse = 0
  171. # Determine what the speeds are based on the buttons:
  172. self.speed=(forward and self.avatarControlForwardSpeed or
  173. reverse and -self.avatarControlReverseSpeed)
  174. # Should fSlide be renamed slideButton?
  175. self.slideSpeed=slide and ((reverse and turnLeft and -self.avatarControlReverseSpeed*(0.75)) or
  176. (reverse and turnRight and self.avatarControlReverseSpeed*(0.75)) or
  177. (turnLeft and -self.avatarControlForwardSpeed*(0.75)) or
  178. (turnRight and self.avatarControlForwardSpeed*(0.75)))
  179. self.rotationSpeed=not slide and (
  180. (turnLeft and self.avatarControlRotateSpeed) or
  181. (turnRight and -self.avatarControlRotateSpeed))
  182. def handleAvatarControls(self, task):
  183. """
  184. Check on the arrow keys and update the avatar.
  185. """
  186. if not self.lifter.hasContact():
  187. # hack fix for falling through the floor:
  188. messenger.send("walkerIsOutOfWorld", [self.avatarNodePath])
  189. self._calcSpeeds()
  190. if __debug__:
  191. debugRunning = inputState.isSet("debugRunning")
  192. if debugRunning:
  193. self.speed*=4.0
  194. self.slideSpeed*=4.0
  195. self.rotationSpeed*=1.25
  196. if self.wantDebugIndicator:
  197. self.displayDebugInfo()
  198. # How far did we move based on the amount of time elapsed?
  199. dt=ClockObject.getGlobalClock().getDt()
  200. # Check to see if we're moving at all:
  201. if self.speed or self.slideSpeed or self.rotationSpeed:
  202. if self.stopThisFrame:
  203. distance = 0.0
  204. slideDistance = 0.0
  205. rotation = 0.0
  206. self.stopThisFrame = 0
  207. else:
  208. distance = dt * self.speed
  209. slideDistance = dt * self.slideSpeed
  210. rotation = dt * self.rotationSpeed
  211. # Take a step in the direction of our previous heading.
  212. self.vel=Vec3(Vec3.forward() * distance +
  213. Vec3.right() * slideDistance)
  214. if self.vel != Vec3.zero():
  215. # rotMat is the rotation matrix corresponding to
  216. # our previous heading.
  217. rotMat=Mat3.rotateMatNormaxis(self.avatarNodePath.getH(), Vec3.up())
  218. step=rotMat.xform(self.vel)
  219. self.avatarNodePath.setFluidPos(Point3(self.avatarNodePath.getPos()+step))
  220. self.avatarNodePath.setH(self.avatarNodePath.getH()+rotation)
  221. messenger.send("avatarMoving")
  222. else:
  223. self.vel.set(0.0, 0.0, 0.0)
  224. self.__oldPosDelta = self.avatarNodePath.getPosDelta(render)
  225. self.__oldDt = dt
  226. try:
  227. self.worldVelocity = self.__oldPosDelta*(1/self.__oldDt)
  228. except:
  229. # divide by zero
  230. self.worldVelocity = 0
  231. return Task.cont
  232. def doDeltaPos(self):
  233. assert self.debugPrint("doDeltaPos()")
  234. def reset(self):
  235. assert self.debugPrint("reset()")
  236. def getVelocity(self):
  237. return self.vel
  238. def enableAvatarControls(self):
  239. """
  240. Activate the arrow keys, etc.
  241. """
  242. assert self.debugPrint("enableAvatarControls")
  243. assert self.collisionsActive
  244. taskName = "AvatarControls-%s"%(id(self),)
  245. # remove any old
  246. taskMgr.remove(taskName)
  247. # spawn the new task
  248. taskMgr.add(self.handleAvatarControls, taskName)
  249. def disableAvatarControls(self):
  250. """
  251. Ignore the arrow keys, etc.
  252. """
  253. assert self.debugPrint("disableAvatarControls")
  254. taskName = "AvatarControls-%s"%(id(self),)
  255. taskMgr.remove(taskName)
  256. def flushEventHandlers(self):
  257. if hasattr(self, 'cTrav'):
  258. self.pusher.flush()
  259. self.lifter.flush() # not currently defined or needed
  260. if __debug__:
  261. def debugPrint(self, message):
  262. """for debugging"""
  263. return self.notify.debug(
  264. str(id(self))+' '+message)