| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295 |
- #!/usr/bin/env python
- # Author: Ryan Myers
- # Models: Jeff Styers, Reagan Heller
- #
- # Last Updated: 2015-03-13
- #
- # This tutorial provides an example of creating a character
- # and having it walk around on uneven terrain, as well
- # as implementing a fully rotatable camera.
- from direct.showbase.ShowBase import ShowBase
- from panda3d.core import CollisionTraverser, CollisionNode
- from panda3d.core import CollisionHandlerQueue, CollisionRay
- from panda3d.core import CollisionHandlerPusher, CollisionSphere
- from panda3d.core import Filename, AmbientLight, DirectionalLight
- from panda3d.core import PandaNode, NodePath, Camera, TextNode
- from panda3d.core import CollideMask
- from direct.gui.OnscreenText import OnscreenText
- from direct.actor.Actor import Actor
- import random
- import sys
- import os
- import math
- # Function to put instructions on the screen.
- def addInstructions(pos, msg):
- return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), scale=.05,
- shadow=(0, 0, 0, 1), parent=base.a2dTopLeft,
- pos=(0.08, -pos - 0.04), align=TextNode.ALeft)
- # Function to put title on the screen.
- def addTitle(text):
- return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), scale=.07,
- parent=base.a2dBottomRight, align=TextNode.ARight,
- pos=(-0.1, 0.09), shadow=(0, 0, 0, 1))
- class RoamingRalphDemo(ShowBase):
- def __init__(self):
- # Set up the window, camera, etc.
- ShowBase.__init__(self)
- # This is used to store which keys are currently pressed.
- self.keyMap = {
- "left": 0,
- "right": 0,
- "forward": 0,
- "backward": 0,
- "cam-left": 0,
- "cam-right": 0,
- }
- # Post the instructions
- self.title = addTitle(
- "Panda3D Tutorial: Roaming Ralph (Walking on Uneven Terrain)")
- self.inst1 = addInstructions(0.06, "[ESC]: Quit")
- self.inst2 = addInstructions(0.12, "[Left Arrow]: Rotate Ralph Left")
- self.inst3 = addInstructions(0.18, "[Right Arrow]: Rotate Ralph Right")
- self.inst4 = addInstructions(0.24, "[Up Arrow]: Run Ralph Forward")
- self.inst5 = addInstructions(0.30, "[Down Arrow]: Walk Ralph Backward")
- self.inst6 = addInstructions(0.36, "[A]: Rotate Camera Left")
- self.inst7 = addInstructions(0.42, "[S]: Rotate Camera Right")
- # Set up the environment
- #
- # This environment model contains collision meshes. If you look
- # in the egg file, you will see the following:
- #
- # <Collide> { Polyset keep descend }
- #
- # This tag causes the following mesh to be converted to a collision
- # mesh -- a mesh which is optimized for collision, not rendering.
- # It also keeps the original mesh, so there are now two copies ---
- # one optimized for rendering, one for collisions.
- self.environ = loader.loadModel("models/world")
- self.environ.reparentTo(render)
- # We do not have a skybox, so we will just use a sky blue background color
- self.setBackgroundColor(0.53, 0.80, 0.92, 1)
- # Create the main character, Ralph
- ralphStartPos = self.environ.find("**/start_point").getPos()
- self.ralph = Actor("models/ralph",
- {"run": "models/ralph-run",
- "walk": "models/ralph-walk"})
- self.ralph.reparentTo(render)
- self.ralph.setScale(.2)
- self.ralph.setPos(ralphStartPos + (0, 0, 1.5))
- # Create a floater object, which floats 2 units above ralph. We
- # use this as a target for the camera to look at.
- self.floater = NodePath(PandaNode("floater"))
- self.floater.reparentTo(self.ralph)
- self.floater.setZ(2.0)
- # Accept the control keys for movement and rotation
- self.accept("escape", sys.exit)
- self.accept("arrow_left", self.setKey, ["left", True])
- self.accept("arrow_right", self.setKey, ["right", True])
- self.accept("arrow_up", self.setKey, ["forward", True])
- self.accept("arrow_down", self.setKey, ["backward", True])
- self.accept("a", self.setKey, ["cam-left", True])
- self.accept("s", self.setKey, ["cam-right", True])
- self.accept("arrow_left-up", self.setKey, ["left", False])
- self.accept("arrow_right-up", self.setKey, ["right", False])
- self.accept("arrow_up-up", self.setKey, ["forward", False])
- self.accept("arrow_down-up", self.setKey, ["backward", False])
- self.accept("a-up", self.setKey, ["cam-left", False])
- self.accept("s-up", self.setKey, ["cam-right", False])
- taskMgr.add(self.move, "moveTask")
- # Set up the camera
- self.disableMouse()
- self.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2)
- self.cTrav = CollisionTraverser()
- # Use a CollisionHandlerPusher to handle collisions between Ralph and
- # the environment. Ralph is added as a "from" object which will be
- # "pushed" out of the environment if he walks into obstacles.
- #
- # Ralph is composed of two spheres, one around the torso and one
- # around the head. They are slightly oversized since we want Ralph to
- # keep some distance from obstacles.
- self.ralphCol = CollisionNode('ralph')
- self.ralphCol.addSolid(CollisionSphere(center=(0, 0, 2), radius=1.5))
- self.ralphCol.addSolid(CollisionSphere(center=(0, -0.25, 4), radius=1.5))
- self.ralphCol.setFromCollideMask(CollideMask.bit(0))
- self.ralphCol.setIntoCollideMask(CollideMask.allOff())
- self.ralphColNp = self.ralph.attachNewNode(self.ralphCol)
- self.ralphPusher = CollisionHandlerPusher()
- self.ralphPusher.horizontal = True
- # Note that we need to add ralph both to the pusher and to the
- # traverser; the pusher needs to know which node to push back when a
- # collision occurs!
- self.ralphPusher.addCollider(self.ralphColNp, self.ralph)
- self.cTrav.addCollider(self.ralphColNp, self.ralphPusher)
- # We will detect the height of the terrain by creating a collision
- # ray and casting it downward toward the terrain. One ray will
- # start above ralph's head, and the other will start above the camera.
- # A ray may hit the terrain, or it may hit a rock or a tree. If it
- # hits the terrain, we can detect the height.
- self.ralphGroundRay = CollisionRay()
- self.ralphGroundRay.setOrigin(0, 0, 9)
- self.ralphGroundRay.setDirection(0, 0, -1)
- self.ralphGroundCol = CollisionNode('ralphRay')
- self.ralphGroundCol.addSolid(self.ralphGroundRay)
- self.ralphGroundCol.setFromCollideMask(CollideMask.bit(0))
- self.ralphGroundCol.setIntoCollideMask(CollideMask.allOff())
- self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol)
- self.ralphGroundHandler = CollisionHandlerQueue()
- self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler)
- self.camGroundRay = CollisionRay()
- self.camGroundRay.setOrigin(0, 0, 9)
- self.camGroundRay.setDirection(0, 0, -1)
- self.camGroundCol = CollisionNode('camRay')
- self.camGroundCol.addSolid(self.camGroundRay)
- self.camGroundCol.setFromCollideMask(CollideMask.bit(0))
- self.camGroundCol.setIntoCollideMask(CollideMask.allOff())
- self.camGroundColNp = self.camera.attachNewNode(self.camGroundCol)
- self.camGroundHandler = CollisionHandlerQueue()
- self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler)
- # Uncomment this line to see the collision rays
- #self.ralphColNp.show()
- #self.camGroundColNp.show()
- # Uncomment this line to show a visual representation of the
- # collisions occuring
- #self.cTrav.showCollisions(render)
- # Create some lighting
- ambientLight = AmbientLight("ambientLight")
- ambientLight.setColor((.3, .3, .3, 1))
- directionalLight = DirectionalLight("directionalLight")
- directionalLight.setDirection((-5, -5, -5))
- directionalLight.setColor((1, 1, 1, 1))
- directionalLight.setSpecularColor((1, 1, 1, 1))
- render.setLight(render.attachNewNode(ambientLight))
- render.setLight(render.attachNewNode(directionalLight))
- # Records the state of the arrow keys
- def setKey(self, key, value):
- self.keyMap[key] = value
- # Accepts arrow keys to move either the player or the menu cursor,
- # Also deals with grid checking and collision detection
- def move(self, task):
- # Get the time that elapsed since last frame. We multiply this with
- # the desired speed in order to find out with which distance to move
- # in order to achieve that desired speed.
- dt = base.clock.dt
- # If the camera-left key is pressed, move camera left.
- # If the camera-right key is pressed, move camera right.
- if self.keyMap["cam-left"]:
- self.camera.setX(self.camera, -20 * dt)
- if self.keyMap["cam-right"]:
- self.camera.setX(self.camera, +20 * dt)
- # If a move-key is pressed, move ralph in the specified direction.
- if self.keyMap["left"]:
- self.ralph.setH(self.ralph.getH() + 300 * dt)
- if self.keyMap["right"]:
- self.ralph.setH(self.ralph.getH() - 300 * dt)
- if self.keyMap["forward"]:
- self.ralph.setY(self.ralph, -20 * dt)
- if self.keyMap["backward"]:
- self.ralph.setY(self.ralph, +10 * dt)
- # If ralph is moving, loop the run animation.
- # If he is standing still, stop the animation.
- currentAnim = self.ralph.getCurrentAnim()
- if self.keyMap["forward"]:
- if currentAnim != "run":
- self.ralph.loop("run")
- elif self.keyMap["backward"]:
- # Play the walk animation backwards.
- if currentAnim != "walk":
- self.ralph.loop("walk")
- self.ralph.setPlayRate(-1.0, "walk")
- elif self.keyMap["left"] or self.keyMap["right"]:
- if currentAnim != "walk":
- self.ralph.loop("walk")
- self.ralph.setPlayRate(1.0, "walk")
- else:
- if currentAnim is not None:
- self.ralph.stop()
- self.ralph.pose("walk", 5)
- self.isMoving = False
- # If the camera is too far from ralph, move it closer.
- # If the camera is too close to ralph, move it farther.
- camvec = self.ralph.getPos() - self.camera.getPos()
- camvec.setZ(0)
- camdist = camvec.length()
- camvec.normalize()
- if camdist > 10.0:
- self.camera.setPos(self.camera.getPos() + camvec * (camdist - 10))
- camdist = 10.0
- if camdist < 5.0:
- self.camera.setPos(self.camera.getPos() - camvec * (5 - camdist))
- camdist = 5.0
- # Normally, we would have to call traverse() to check for collisions.
- # However, the class ShowBase that we inherit from has a task to do
- # this for us, if we assign a CollisionTraverser to self.cTrav.
- #self.cTrav.traverse(render)
- # Adjust ralph's Z coordinate. If ralph's ray hit terrain,
- # update his Z
- entries = list(self.ralphGroundHandler.entries)
- entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
- for entry in entries:
- if entry.getIntoNode().name == "terrain":
- self.ralph.setZ(entry.getSurfacePoint(render).getZ())
- # Keep the camera at one unit above the terrain,
- # or two units above ralph, whichever is greater.
- entries = list(self.camGroundHandler.entries)
- entries.sort(key=lambda x: x.getSurfacePoint(render).getZ())
- for entry in entries:
- if entry.getIntoNode().name == "terrain":
- self.camera.setZ(entry.getSurfacePoint(render).getZ() + 1.5)
- if self.camera.getZ() < self.ralph.getZ() + 2.0:
- self.camera.setZ(self.ralph.getZ() + 2.0)
- # The camera should look in ralph's direction,
- # but it should also try to stay horizontal, so look at
- # a floater which hovers above ralph's head.
- self.camera.lookAt(self.floater)
- return task.cont
- demo = RoamingRalphDemo()
- demo.run()
|