Uneven Terrain
UNEVEN TERRAIN PATHFINDING:
https://www.youtube.com/watch?v=ozja1l4rpo4
The code for this tutorial:
1# PandAI Author: Srinavin Nair 2# Original Author: Ryan Myers 3# Models: Jeff Styers, Reagan Heller 4 5# Last Updated: 6/13/2005 6# 7# This tutorial provides an example of creating a character and having it walk 8# around on uneven terrain, as well as implementing a fully rotatable camera. 9# It uses PandAI pathfinding to move the character. 10 11from direct.showbase.ShowBase import ShowBase 12from panda3d.core import CollisionTraverser, CollisionNode 13from panda3d.core import CollisionHandlerQueue, CollisionRay 14from panda3d.core import Filename 15from panda3d.core import PandaNode, NodePath, TextNode 16from panda3d.core import Vec3, BitMask32 17from direct.gui.OnscreenText import OnscreenText 18from direct.actor.Actor import Actor 19from direct.task.Task import Task 20from direct.showbase.DirectObject import DirectObject 21import sys 22import os 23 24from panda3d.ai import * 25 26base = ShowBase() 27 28SPEED = 0.5 29 30# Figure out what directory this program is in. 31MYDIR = os.path.abspath(sys.path[0]) 32MYDIR = Filename.fromOsSpecific(MYDIR).getFullpath() 33 34font = loader.loadFont("cmss12") 35 36 37# Function to put instructions on the screen. 38def addInstructions(pos, msg): 39 return OnscreenText(text=msg, style=1, fg=(1, 1, 1, 1), font=font, 40 pos=(-1.3, pos), align=TextNode.ALeft, scale=.05) 41 42 43# Function to put title on the screen. 44def addTitle(text): 45 return OnscreenText(text=text, style=1, fg=(1, 1, 1, 1), font=font, 46 pos=(1.3, -0.95), align=TextNode.ARight, scale=.07) 47 48 49class World(DirectObject): 50 51 def __init__(self): 52 self.switchState = True 53 self.switchCam = False 54 self.path_no = 1 55 self.keyMap = { 56 "left": 0, 57 "right": 0, 58 "forward": 0, 59 "cam-left": 0, 60 "cam-right": 0 61 } 62 base.win.setClearColor((0, 0, 0, 1)) 63 base.cam.setPosHpr(17.79, -87.64, 90.16, 38.66, 325.36, 0) 64 # Post the instructions 65 66 addTitle("Pandai Tutorial: Roaming Ralph (Walking on Uneven Terrain) " 67 "working with pathfinding") 68 addInstructions(0.95, "[ESC]: Quit") 69 addInstructions(0.90, "[Space - do Only once]: Start Pathfinding") 70 addInstructions(0.85, "[Enter]: Change camera view") 71 addInstructions(0.80, "[Up Arrow]: Run Ralph Forward") 72 addInstructions(0.70, "[A]: Rotate Camera Left") 73 addInstructions(0.65, "[S]: Rotate Camera Right") 74 75 # Set up the environment 76 # 77 # This environment model contains collision meshes. If you look 78 # in the egg file, you will see the following: 79 # 80 # <Collide> { Polyset keep descend } 81 # 82 # This tag causes the following mesh to be converted to a collision 83 # mesh -- a mesh which is optimized for collision, not rendering. 84 # It also keeps the original mesh, so there are now two copies --- 85 # one optimized for rendering, one for collisions. 86 87 self.environ = loader.loadModel("models/world") 88 self.environ.reparentTo(render) 89 self.environ.setPos(12, 0, 0) 90 91 self.box = loader.loadModel("models/box") 92 self.box.reparentTo(render) 93 self.box.setPos(-29.83, 0, 0) 94 self.box.setScale(1) 95 96 self.box1 = loader.loadModel("models/box") 97 self.box1.reparentTo(render) 98 self.box1.setPos(-51.14, -17.90, 0) 99 self.box1.setScale(1) 100 101 # Create the main character, Ralph 102 103 #ralphStartPos = self.environ.find("**/start_point").getPos() 104 ralphStartPos = Vec3(-98.64, -20.60, 0) 105 self.ralph = Actor("models/ralph", 106 {"run": "models/ralph-run", 107 "walk": "models/ralph-walk"}) 108 self.ralph.reparentTo(render) 109 self.ralph.setScale(1) 110 self.ralph.setPos(ralphStartPos) 111 112 self.ralphai = Actor("models/ralph", 113 {"run": "models/ralph-run", 114 "walk": "models/ralph-walk"}) 115 116 self.pointer = loader.loadModel("models/arrow") 117 self.pointer.setColor(1, 0, 0) 118 self.pointer.setPos(-7.5, -1.2, 0) 119 self.pointer.setScale(3) 120 self.pointer.reparentTo(render) 121 122 self.pointer1 = loader.loadModel("models/arrow") 123 self.pointer1.setColor(1, 0, 0) 124 self.pointer1.setPos(-98.64, -20.60, 0) 125 self.pointer1.setScale(3) 126 #self.pointer.reparentTo(render) 127 128 # Create a floater object. We use the "floater" as a temporary 129 # variable in a variety of calculations. 130 131 self.floater = NodePath(PandaNode("floater")) 132 self.floater.reparentTo(render) 133 134 # Accept the control keys for movement and rotation 135 136 self.accept("escape", sys.exit) 137 self.accept("enter", self.activateCam) 138 self.accept("arrow_left", self.setKey, ["left", 1]) 139 self.accept("arrow_right", self.setKey, ["right", 1]) 140 self.accept("arrow_up", self.setKey, ["forward", 1]) 141 self.accept("a", self.setKey, ["cam-left", 1]) 142 self.accept("s", self.setKey, ["cam-right", 1]) 143 self.accept("arrow_left-up", self.setKey, ["left", 0]) 144 self.accept("arrow_right-up", self.setKey, ["right", 0]) 145 self.accept("arrow_up-up", self.setKey, ["forward", 0]) 146 self.accept("a-up", self.setKey, ["cam-left", 0]) 147 self.accept("s-up", self.setKey, ["cam-right", 0]) 148 149 #taskMgr.add(self.move,"moveTask") 150 151 # Game state variables 152 self.isMoving = False 153 154 # Set up the camera 155 156 #base.disableMouse() 157 #base.camera.setPos(self.ralph.getX(), self.ralph.getY() + 10, 2) 158 159 # We will detect the height of the terrain by creating a collision 160 # ray and casting it downward toward the terrain. One ray will 161 # start above ralph's head, and the other will start above the camera. 162 # A ray may hit the terrain, or it may hit a rock or a tree. If it 163 # hits the terrain, we can detect the height. If it hits anything 164 # else, we rule that the move is illegal. 165 166 self.cTrav = CollisionTraverser() 167 168 self.ralphGroundRay = CollisionRay() 169 self.ralphGroundRay.setOrigin(0, 0, 1000) 170 self.ralphGroundRay.setDirection(0, 0, -1) 171 self.ralphGroundCol = CollisionNode('ralphRay') 172 self.ralphGroundCol.addSolid(self.ralphGroundRay) 173 self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0)) 174 self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff()) 175 self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol) 176 self.ralphGroundHandler = CollisionHandlerQueue() 177 self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler) 178 179 self.camGroundRay = CollisionRay() 180 self.camGroundRay.setOrigin(0, 0, 1000) 181 self.camGroundRay.setDirection(0, 0, -1) 182 self.camGroundCol = CollisionNode('camRay') 183 self.camGroundCol.addSolid(self.camGroundRay) 184 self.camGroundCol.setFromCollideMask(BitMask32.bit(0)) 185 self.camGroundCol.setIntoCollideMask(BitMask32.allOff()) 186 self.camGroundColNp = base.camera.attachNewNode(self.camGroundCol) 187 self.camGroundHandler = CollisionHandlerQueue() 188 self.cTrav.addCollider(self.camGroundColNp, self.camGroundHandler) 189 190 # Uncomment this line to see the collision rays 191 #self.ralphGroundColNp.show() 192 #self.camGroundColNp.show() 193 194 #Uncomment this line to show a visual representation of the 195 #collisions occuring 196 #self.cTrav.showCollisions(render) 197 198 self.setAI() 199 200 def activateCam(self): 201 self.switchCam = not self.switchCam 202 if self.switchCam is True: 203 base.cam.setPosHpr(0, 0, 0, 0, 0, 0) 204 base.cam.reparentTo(self.ralph) 205 base.cam.setY(base.cam.getY() + 30) 206 base.cam.setZ(base.cam.getZ() + 10) 207 base.cam.setHpr(180, -15, 0) 208 else: 209 base.cam.reparentTo(render) 210 base.cam.setPosHpr(17.79, -87.64, 90.16, 38.66, 325.36, 0) 211 #base.camera.setPos(self.ralph.getX(),self.ralph.getY()+10,2) 212 213 # Records the state of the arrow keys 214 def setKey(self, key, value): 215 self.keyMap[key] = value 216 217 # Accepts arrow keys to move either the player or the menu cursor, 218 # Also deals with grid checking and collision detection 219 def move(self): 220 221 # Get the time elapsed since last frame. We need this 222 # for framerate-independent movement. 223 elapsed = globalClock.getDt() 224 225 # If the camera-left key is pressed, move camera left. 226 # If the camera-right key is pressed, move camera right. 227 if self.switchState is False: 228 base.camera.lookAt(self.ralph) 229 if self.keyMap["cam-left"] != 0: 230 base.camera.setX(base.camera, -(elapsed * 20)) 231 if self.keyMap["cam-right"] != 0: 232 base.camera.setX(base.camera, +(elapsed * 20)) 233 234 # save ralph's initial position so that we can restore it, 235 # in case he falls off the map or runs into something. 236 237 startpos = self.ralph.getPos() 238 239 # If a move-key is pressed, move ralph in the specified direction. 240 241 if self.keyMap["left"] != 0: 242 self.ralph.setH(self.ralph.getH() + elapsed * 300) 243 if self.keyMap["right"] != 0: 244 self.ralph.setH(self.ralph.getH() - elapsed * 300) 245 if self.keyMap["forward"] != 0: 246 self.ralph.setY(self.ralph, -(elapsed * 25)) 247 248 # If ralph is moving, loop the run animation. 249 # If he is standing still, stop the animation. 250 251 if self.keyMap["forward"] != 0 or self.keyMap["left"] != 0 or self.keyMap["right"] != 0: 252 if self.isMoving is False: 253 self.ralph.loop("run") 254 self.isMoving = True 255 else: 256 if self.isMoving: 257 self.ralph.stop() 258 self.ralph.pose("walk", 5) 259 self.isMoving = False 260 261 # If the camera is too far from ralph, move it closer. 262 # If the camera is too close to ralph, move it farther. 263 if self.switchState is False: 264 camvec = self.ralph.getPos() - base.camera.getPos() 265 camvec.setZ(0) 266 camdist = camvec.length() 267 camvec.normalize() 268 if camdist > 10.0: 269 base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10)) 270 camdist = 10.0 271 if camdist < 5.0: 272 base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist)) 273 camdist = 5.0 274 275 # Now check for collisions. 276 277 self.cTrav.traverse(render) 278 279 # Adjust ralph's Z coordinate. If ralph's ray hit terrain, 280 # update his Z. If it hit anything else, or didn't hit anything, put 281 # him back where he was last frame. 282 283 #print(self.ralphGroundHandler.getNumEntries()) 284 285 entries = [] 286 for i in range(self.ralphGroundHandler.getNumEntries()): 287 entry = self.ralphGroundHandler.getEntry(i) 288 entries.append(entry) 289 entries.sort(lambda x, y: cmp(y.getSurfacePoint(render).z, 290 x.getSurfacePoint(render).z)) 291 if entries and entries[0].getIntoNode().getName() == "terrain": 292 self.ralph.setZ(entries[0].getSurfacePoint(render).z) 293 else: 294 self.ralph.setPos(startpos) 295 296 # Keep the camera at one foot above the terrain, 297 # or two feet above ralph, whichever is greater. 298 299 if self.switchState is False: 300 entries = [] 301 for i in range(self.camGroundHandler.getNumEntries()): 302 entry = self.camGroundHandler.getEntry(i) 303 entries.append(entry) 304 entries.sort(lambda x, y: cmp(y.getSurfacePoint(render).z, 305 x.getSurfacePoint(render).z)) 306 if entries and entries[0].getIntoNode().getName() == "terrain": 307 base.camera.setZ(entries[0].getSurfacePoint(render).z + 1.0) 308 if base.camera.getZ() < self.ralph.getZ() + 2.0: 309 base.camera.setZ(self.ralph.getZ() + 2.0) 310 311 # The camera should look in ralph's direction, 312 # but it should also try to stay horizontal, so look at 313 # a floater which hovers above ralph's head. 314 315 self.floater.setPos(self.ralph.getPos()) 316 self.floater.setZ(self.ralph.getZ() + 2.0) 317 base.camera.setZ(base.camera.getZ()) 318 base.camera.lookAt(self.floater) 319 320 self.ralph.setP(0) 321 return Task.cont 322 323 def setAI(self): 324 # Creating AI World 325 self.AIworld = AIWorld(render) 326 327 self.accept("space", self.setMove) 328 self.AIchar = AICharacter("ralph", self.ralph, 60, 0.05, 25) 329 self.AIworld.addAiChar(self.AIchar) 330 self.AIbehaviors = self.AIchar.getAiBehaviors() 331 332 self.AIbehaviors.initPathFind("models/navmesh.csv") 333 334 # AI World update 335 taskMgr.add(self.AIUpdate, "AIUpdate") 336 337 def setMove(self): 338 self.AIbehaviors.addStaticObstacle(self.box) 339 self.AIbehaviors.addStaticObstacle(self.box1) 340 self.AIbehaviors.pathFindTo(self.pointer) 341 self.ralph.loop("run") 342 343 # To update the AIWorld 344 def AIUpdate(self, task): 345 self.AIworld.update() 346 self.move() 347 348 if self.path_no == 1 and self.AIbehaviors.behaviorStatus("pathfollow") == "done": 349 self.path_no = 2 350 self.AIbehaviors.pathFindTo(self.pointer1, "addPath") 351 print("inside") 352 353 if self.path_no == 2 and self.AIbehaviors.behaviorStatus("pathfollow") == "done": 354 print("inside2") 355 self.path_no = 1 356 self.AIbehaviors.pathFindTo(self.pointer, "addPath") 357 358 return Task.cont 359 360 361w = World() 362base.run() The full working demo can be downloaded at:
