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: