main.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. #!/usr/bin/env python
  2. # Author: Ryan Myers
  3. # Models: Jeff Styers, Reagan Heller
  4. #
  5. # Last Updated: 2015-03-13
  6. #
  7. # This tutorial provides an example of creating a character
  8. # and having it walk around on uneven terrain, as well
  9. # as implementing a fully rotatable camera.
  10. from direct.showbase.ShowBase import ShowBase
  11. from panda3d.core import CollisionTraverser, CollisionNode
  12. from panda3d.core import CollisionHandlerQueue, CollisionRay
  13. from panda3d.core import CollisionHandlerPusher, CollisionSphere
  14. from panda3d.core import Filename, AmbientLight, DirectionalLight
  15. from panda3d.core import PandaNode, NodePath, Camera, TextNode
  16. from panda3d.core import CollideMask
  17. from direct.gui.OnscreenText import OnscreenText
  18. from direct.actor.Actor import Actor
  19. import random
  20. import sys
  21. import os
  22. import math
  23. # Function to put instructions on the screen.
  24. def addInstructions(pos, msg):
  25. return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), scale=.05,
  26. shadow=(0, 0, 0, 1), parent=base.a2dTopLeft,
  27. pos=(0.08, -pos - 0.04), align=TextNode.ALeft)
  28. # Function to put title on the screen.
  29. def addTitle(text):
  30. return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=.07,
  31. parent=base.a2dBottomRight, align=TextNode.ARight,
  32. pos=(-0.1, 0.09), shadow=(0, 0, 0, 1))
  33. class RoamingRalphDemo(ShowBase):
  34. def __init__(self):
  35. # Set up the window, camera, etc.
  36. ShowBase.__init__(self)
  37. # This is used to store which keys are currently pressed.
  38. self.keyMap = {
  39. "left": 0,
  40. "right": 0,
  41. "forward": 0,
  42. "backward": 0,
  43. "cam-left": 0,
  44. "cam-right": 0,
  45. }
  46. # Post the instructions
  47. self.title = addTitle(
  48. "Panda3D Tutorial: Roaming Ralph (Walking on Uneven Terrain)")
  49. self.inst1 = addInstructions(0.06, "[ESC]: Quit")
  50. self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left")
  51. self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right")
  52. self.inst4 = addInstructions(0.24, "[Up Arrow]: Run Ralph Forward")
  53. self.inst5 = addInstructions(0.30, "[Down Arrow]: Walk Ralph Backward")
  54. self.inst6 = addInstructions(0.36, "[A]: Rotate Camera Left")
  55. self.inst7 = addInstructions(0.42, "[S]: Rotate Camera Right")
  56. # Set up the environment
  57. #
  58. # This environment model contains collision meshes. If you look
  59. # in the egg file, you will see the following:
  60. #
  61. # <Collide> { Polyset keep descend }
  62. #
  63. # This tag causes the following mesh to be converted to a collision
  64. # mesh -- a mesh which is optimized for collision, not rendering.
  65. # It also keeps the original mesh, so there are now two copies ---
  66. # one optimized for rendering, one for collisions.
  67. self.environ = loader.loadModel("models/world")
  68. self.environ.reparentTo(render)
  69. # We do not have a skybox, so we will just use a sky blue background color
  70. self.setBackgroundColor(0.53, 0.80, 0.92, 1)
  71. # Create the main character, Ralph
  72. ralphStartPos = self.environ.find("**/start_point").getPos()
  73. self.ralph = Actor("models/ralph",
  74. {"run": "models/ralph-run",
  75. "walk": "models/ralph-walk"})
  76. self.ralph.reparentTo(render)
  77. self.ralph.setScale(.2)
  78. self.ralph.setPos(ralphStartPos + (0, 0, 1.5))
  79. # Create a floater object, which floats 2 units above ralph. We
  80. # use this as a target for the camera to look at.
  81. self.floater = NodePath(PandaNode("floater"))
  82. self.floater.reparentTo(self.ralph)
  83. self.floater.setZ(2.0)
  84. # Accept the control keys for movement and rotation
  85. self.accept("escape", sys.exit)
  86. self.accept("arrow_left", self.setKey, ["left", True])
  87. self.accept("arrow_right", self.setKey, ["right", True])
  88. self.accept("arrow_up", self.setKey, ["forward", True])
  89. self.accept("arrow_down", self.setKey, ["backward", True])
  90. self.accept("a", self.setKey, ["cam-left", True])
  91. self.accept("s", self.setKey, ["cam-right", True])
  92. self.accept("arrow_left-up", self.setKey, ["left", False])
  93. self.accept("arrow_right-up", self.setKey, ["right", False])
  94. self.accept("arrow_up-up", self.setKey, ["forward", False])
  95. self.accept("arrow_down-up", self.setKey, ["backward", False])
  96. self.accept("a-up", self.setKey, ["cam-left", False])
  97. self.accept("s-up", self.setKey, ["cam-right", False])
  98. taskMgr.add(self.move, "moveTask")
  99. # Set up the camera
  100. self.disableMouse()
  101. self.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
  102. self.cTrav = CollisionTraverser()
  103. # Use a CollisionHandlerPusher to handle collisions between Ralph and
  104. # the environment. Ralph is added as a "from" object which will be
  105. # "pushed" out of the environment if he walks into obstacles.
  106. #
  107. # Ralph is composed of two spheres, one around the torso and one
  108. # around the head. They are slightly oversized since we want Ralph to
  109. # keep some distance from obstacles.
  110. self.ralphCol = CollisionNode('ralph')
  111. self.ralphCol.addSolid(CollisionSphere(center=(0, 0, 2), radius=1.5))
  112. self.ralphCol.addSolid(CollisionSphere(center=(0, -0.25, 4), radius=1.5))
  113. self.ralphCol.setFromCollideMask(CollideMask.bit(0))
  114. self.ralphCol.setIntoCollideMask(CollideMask.allOff())
  115. self.ralphColNp = self.ralph.attachNewNode(self.ralphCol)
  116. self.ralphPusher = CollisionHandlerPusher()
  117. self.ralphPusher.horizontal = True
  118. # Note that we need to add ralph both to the pusher and to the
  119. # traverser; the pusher needs to know which node to push back when a
  120. # collision occurs!
  121. self.ralphPusher.addCollider(self.ralphColNp, self.ralph)
  122. self.cTrav.addCollider(self.ralphColNp, self.ralphPusher)
  123. # We will detect the height of the terrain by creating a collision
  124. # ray and casting it downward toward the terrain. One ray will
  125. # start above ralph's head, and the other will start above the camera.
  126. # A ray may hit the terrain, or it may hit a rock or a tree. If it
  127. # hits the terrain, we can detect the height.
  128. self.ralphGroundRay = CollisionRay()
  129. self.ralphGroundRay.setOrigin(0, 0, 9)
  130. self.ralphGroundRay.setDirection(0, 0, -1)
  131. self.ralphGroundCol = CollisionNode('ralphRay')
  132. self.ralphGroundCol.addSolid(self.ralphGroundRay)
  133. self.ralphGroundCol.setFromCollideMask(CollideMask.bit(0))
  134. self.ralphGroundCol.setIntoCollideMask(CollideMask.allOff())
  135. self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
  136. self.ralphGroundHandler = CollisionHandlerQueue()
  137. self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)
  138. self.camGroundRay = CollisionRay()
  139. self.camGroundRay.setOrigin(0, 0, 9)
  140. self.camGroundRay.setDirection(0, 0, -1)
  141. self.camGroundCol = CollisionNode('camRay')
  142. self.camGroundCol.addSolid(self.camGroundRay)
  143. self.camGroundCol.setFromCollideMask(CollideMask.bit(0))
  144. self.camGroundCol.setIntoCollideMask(CollideMask.allOff())
  145. self.camGroundColNp = self.camera.attachNewNode(self.camGroundCol)
  146. self.camGroundHandler = CollisionHandlerQueue()
  147. self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
  148. # Uncomment this line to see the collision rays
  149. #self.ralphColNp.show()
  150. #self.camGroundColNp.show()
  151. # Uncomment this line to show a visual representation of the
  152. # collisions occuring
  153. #self.cTrav.showCollisions(render)
  154. # Create some lighting
  155. ambientLight = AmbientLight("ambientLight")
  156. ambientLight.setColor((.3, .3, .3, 1))
  157. directionalLight = DirectionalLight("directionalLight")
  158. directionalLight.setDirection((-5, -5, -5))
  159. directionalLight.setColor((1, 1, 1, 1))
  160. directionalLight.setSpecularColor((1, 1, 1, 1))
  161. render.setLight(render.attachNewNode(ambientLight))
  162. render.setLight(render.attachNewNode(directionalLight))
  163. # Records the state of the arrow keys
  164. def setKey(self, key, value):
  165. self.keyMap[key] = value
  166. # Accepts arrow keys to move either the player or the menu cursor,
  167. # Also deals with grid checking and collision detection
  168. def move(self, task):
  169. # Get the time that elapsed since last frame. We multiply this with
  170. # the desired speed in order to find out with which distance to move
  171. # in order to achieve that desired speed.
  172. dt = base.clock.dt
  173. # If the camera-left key is pressed, move camera left.
  174. # If the camera-right key is pressed, move camera right.
  175. if self.keyMap["cam-left"]:
  176. self.camera.setX(self.camera, -20 * dt)
  177. if self.keyMap["cam-right"]:
  178. self.camera.setX(self.camera, +20 * dt)
  179. # If a move-key is pressed, move ralph in the specified direction.
  180. if self.keyMap["left"]:
  181. self.ralph.setH(self.ralph.getH() + 300 * dt)
  182. if self.keyMap["right"]:
  183. self.ralph.setH(self.ralph.getH() - 300 * dt)
  184. if self.keyMap["forward"]:
  185. self.ralph.setY(self.ralph, -20 * dt)
  186. if self.keyMap["backward"]:
  187. self.ralph.setY(self.ralph, +10 * dt)
  188. # If ralph is moving, loop the run animation.
  189. # If he is standing still, stop the animation.
  190. currentAnim = self.ralph.getCurrentAnim()
  191. if self.keyMap["forward"]:
  192. if currentAnim != "run":
  193. self.ralph.loop("run")
  194. elif self.keyMap["backward"]:
  195. # Play the walk animation backwards.
  196. if currentAnim != "walk":
  197. self.ralph.loop("walk")
  198. self.ralph.setPlayRate(-1.0, "walk")
  199. elif self.keyMap["left"] or self.keyMap["right"]:
  200. if currentAnim != "walk":
  201. self.ralph.loop("walk")
  202. self.ralph.setPlayRate(1.0, "walk")
  203. else:
  204. if currentAnim is not None:
  205. self.ralph.stop()
  206. self.ralph.pose("walk", 5)
  207. self.isMoving = False
  208. # If the camera is too far from ralph, move it closer.
  209. # If the camera is too close to ralph, move it farther.
  210. camvec = self.ralph.getPos() - self.camera.getPos()
  211. camvec.setZ(0)
  212. camdist = camvec.length()
  213. camvec.normalize()
  214. if camdist > 10.0:
  215. self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
  216. camdist = 10.0
  217. if camdist < 5.0:
  218. self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
  219. camdist = 5.0
  220. # Normally, we would have to call traverse() to check for collisions.
  221. # However, the class ShowBase that we inherit from has a task to do
  222. # this for us, if we assign a CollisionTraverser to self.cTrav.
  223. #self.cTrav.traverse(render)
  224. # Adjust ralph's Z coordinate. If ralph's ray hit terrain,
  225. # update his Z
  226. entries = list(self.ralphGroundHandler.entries)
  227. entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
  228. for entry in entries:
  229. if entry.getIntoNode().name == "terrain":
  230. self.ralph.setZ(entry.getSurfacePoint(render).getZ())
  231. # Keep the camera at one unit above the terrain,
  232. # or two units above ralph, whichever is greater.
  233. entries = list(self.camGroundHandler.entries)
  234. entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
  235. for entry in entries:
  236. if entry.getIntoNode().name == "terrain":
  237. self.camera.setZ(entry.getSurfacePoint(render).getZ() + 1.5)
  238. if self.camera.getZ() < self.ralph.getZ() + 2.0:
  239. self.camera.setZ(self.ralph.getZ() + 2.0)
  240. # The camera should look in ralph's direction,
  241. # but it should also try to stay horizontal, so look at
  242. # a floater which hovers above ralph's head.
  243. self.camera.lookAt(self.floater)
  244. return task.cont
  245. demo = RoamingRalphDemo()
  246. demo.run()