def leftClick(self): self.mouse1Down = True #Collision traversal pickerNode = CollisionNode('mouseRay') pickerNP = base.camera.attachNewNode(pickerNode) pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask()) pickerRay = CollisionRay() pickerNode.addSolid(pickerRay) myTraverser = CollisionTraverser() myHandler = CollisionHandlerQueue() myTraverser.addCollider(pickerNP, myHandler) if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) myTraverser.traverse(render) # Assume for simplicity's sake that myHandler is a CollisionHandlerQueue. if myHandler.getNumEntries() > 0: # This is so we get the closest object myHandler.sortEntries() pickedObj = myHandler.getEntry(0).getIntoNodePath() objTag = pickedObj.findNetTag('mouseCollisionTag').getTag( 'mouseCollisionTag') if objTag and len(objTag) > 0: messenger.send('object_click', [objTag]) pickerNP.remove()
class RepairMousePicker: def __init__(self): self.pickerNode = CollisionNode('RepairMousePicker.pickerNode') self.pickerNP = base.cam2d.attachNewNode(self.pickerNode) self.pickerRay = CollisionRay() self.pickerNode.addSolid(self.pickerRay) self.collisionTraverser = CollisionTraverser() self.collisionHandler = CollisionHandlerQueue() self.collisionTraverser.addCollider(self.pickerNP, self.collisionHandler) self.clearCollisionMask() self.orthographic = True def destroy(self): del self.pickerNode self.pickerNP.removeNode() del self.pickerNP del self.pickerRay del self.collisionTraverser del self.collisionHandler def setOrthographic(self, ortho): self.orthographic = ortho def setCollisionMask(self, mask): self.pickerNode.setFromCollideMask(mask) def clearCollisionMask(self): self.pickerNode.setFromCollideMask(BitMask32.allOff()) def getCollisions(self, traverseRoot, useIntoNodePaths=False): if not base.mouseWatcherNode.hasMouse(): return [] mpos = base.mouseWatcherNode.getMouse() if self.orthographic: self.pickerRay.setFromLens(base.cam2d.node(), 0, 0) self.pickerNP.setPos(mpos.getX(), 0.0, mpos.getY()) else: self.pickerRay.setFromLens(base.cam2d.node(), mpos.getX(), mpos.getY()) self.pickerNP.setPos(0.0, 0.0, 0.0) self.collisionTraverser.traverse(traverseRoot) pickedObjects = [] if useIntoNodePaths: for i in range(self.collisionHandler.getNumEntries()): pickedObjects.append( self.collisionHandler.getEntry(i).getIntoNodePath()) else: for i in range(self.collisionHandler.getNumEntries()): pickedObjects.append(self.collisionHandler.getEntry(i)) return pickedObjects
class Selector(object): '''A Selector listens for mouse clicks and then runs select. Select then broadcasts the selected tag (if there is one)''' def __init__(self): ''' Should the traverser be shared? ''' LOG.debug("[Selector] Initializing") # The collision traverser does the checking of solids for collisions self.cTrav = CollisionTraverser() # The collision handler queue is a simple handler that records all # detected collisions during traversal self.cHandler = CollisionHandlerQueue() self.pickerNode = CollisionNode('mouseRay') self.pickerNP = camera.attachNewNode(self.pickerNode) self.pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask()) self.pickerRay = CollisionRay() self.pickerNode.addSolid(self.pickerRay) self.cTrav.addCollider(self.pickerNP, self.cHandler) # Start listening to clicks self.resume() def select(self, event): LOG.debug("[Selector] Selecting ") if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) self.cTrav.traverse(render) # TODO - change this to a lower node if self.cHandler.getNumEntries() > 0: #LOG.debug("[Selector] Entries=%d"%self.cHandler.getNumEntries()) self.cHandler.sortEntries() selectionNP = self.cHandler.getEntry(0).getIntoNodePath() selection = selectionNP.findNetTag('SelectorTag').getTag( 'SelectorTag') if selection is not '': LOG.debug("[Selector] Collision with %s" % selection) Event.Dispatcher().broadcast( Event.Event('E_EntitySelect', src=self, data=selection)) else: LOG.debug("[Selector] No collision") #Event.Dispatcher().broadcast(Event.Event('E_EntityUnSelect', src=self, data=selection)) def pause(self): Event.Dispatcher().unregister(self, 'E_Mouse_1') def resume(self): print("unpausing selector") Event.Dispatcher().register(self, 'E_Mouse_1', self.select)
class Gui3D: def __init__(self, game_data, gfx_manager): self.game_data = game_data self.gui_traverser = CollisionTraverser() self.handler = CollisionHandlerQueue() self.selectable_objects = {} for cid, model in gfx_manager.character_models.items(): new_collision_node = CollisionNode('person_' + str(cid)) new_collision_node.addSolid( CollisionTube(0, 0, 0.5, 0, 0, 1.5, 0.5)) new_collision_nodepath = model.attachNewNode(new_collision_node) new_collision_nodepath.setTag("type", "character") new_collision_nodepath.setTag("id", str(cid)) picker_node = CollisionNode('mouseRay') picker_np = camera.attachNewNode(picker_node) self.picker_ray = CollisionRay() picker_node.addSolid(self.picker_ray) self.gui_traverser.addCollider(picker_np, self.handler) self.floor = CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0))) self.floor_np = render.attachNewNode(CollisionNode('floor')) self.floor_np.setTag("type", "ground") self.floor_np.node().addSolid(self.floor) def mouse_click(self): if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() self.picker_ray.setFromLens(base.camNode, mpos.getX(), mpos.getY()) self.gui_traverser.traverse(render) num_entries = self.handler.getNumEntries() if num_entries > 0: self.handler.sortEntries() entry = self.handler.getEntry(0) selected = entry.getIntoNodePath() selected_type = selected.getTag("type") if selected_type == "character": self.game_data.select_character(int(selected.getTag("id"))) elif selected_type == "ground": self.game_data.click_point(entry.getSurfacePoint(render))
class World(DirectObject): #class World, extends DirectObject, builds the world to play the game ###################### INITIALIZATIONS ######################################### def __init__(self): mySplashScreen = SplashScreen() mySplashScreen.loading() mySplashScreen.introduction() self.promptMode() self.turnWallNotification() ##### Creating Scene ##### self.createBackground() self.loadWallModel() self.loadBallModel() self.setCamera() self.createLighting() ##### Create Controls ##### self.createKeyControls() self.keyMap = {"left":0, "right":0, "forward":0, "backward":0, "drop":0} ##### Task Manager ##### timer = 0.2 taskMgr.doMethodLater(timer, self.traverseTask, "tsk_traverse") #scans for collisions every 0.2 seconds taskMgr.add(self.move,"moveTask") #constant smooth movement ##### Collisions ##### self.createBallColliderModel() self.disableForwardMovement = False self.disableBackwardMovement = False self.disableLeftMovement = False self.disableRightMovement = False ##### Game state variables ##### self.isMoving = False self.isDropping = False self.camAngle = math.pi/2 self.direction = "W" #constant; does not change with relativity self.drop = False self.levelHeight = 2.1 self.level = 0 self.maxLevel = 6 self.currentHeight = 13.302 self.cameraHeight = 0.2 self.mode = None self.timer = "" ##### Views ##### self.xray_mode = False self.collision_mode = False self.wireframe = False ##### On-Screen Text ##### self.title = addTitle("aMAZEing") self.instructions = OnscreenText(text="[ i ]: Toggle Instructions", style=1, fg=(0, 0, 0, 1), pos=(1.3, 0.95), align=TextNode.ARight, scale=0.05) self.instr = [] self.messages = [] self.levelText = OnscreenText(text= "Level = " + str(self.level), style=1, fg=(0, 0, 0, 1), pos=(-1.3, -0.95), align=TextNode.ALeft, scale=0.07) self.directionText = OnscreenText(text="Direction = " + self.direction, style=1, fg=(0, 0, 0, 1), pos=(-1.3, -0.85), align=TextNode.ALeft, scale=0.07) self.timerText = OnscreenText(text= self.timer, style=1, fg=(1, 1, 1, 1), pos=(1.3, 0.85), align=TextNode.ARight, scale=0.07) def setKey(self, key, value): #records the state of the arrow keys self.keyMap[key] = value ###################### Onscreen Text ####################################### def postInstructions(self): #posts the instructions onto the screen inst1 = addInstructions(0.95, "[ESC]: Quit") self.instr.append(inst1) inst2 = addInstructions(0.90, "[Left Arrow]: Turn Left") self.instr.append(inst2) inst3 = addInstructions(0.85, "[Right Arrow]: Turn Right") self.instr.append(inst3) inst4 = addInstructions(0.80, "[Up Arrow]: Move Ball Forward") self.instr.append(inst4) inst5 = addInstructions(0.75, "[Down Arrow]: Move Ball Backwards") self.instr.append(inst5) inst6 = addInstructions(0.70, "[Space]: Drop Levels (if level drop is availale)") self.instr.append(inst6) inst7 = addInstructions(0.60, "[x]: Toggle XRay Mode") self.instr.append(inst7) inst8 = addInstructions(0.55, "[c]: Toggle Collision Mode") self.instr.append(inst8) inst9 = addInstructions(0.50, "[z]: Toggle Wireframe") self.instr.append(inst9) inst10 = OnscreenText(text='''Hello! Welcome to aMAZEing! You are this sphere, and your goal is to find the exit of the maze! Each level of the maze has a hole you can drop through, to move on to the next level. This maze has six levels and each maze is a 12x12. If you chose timer mode, you have 5 minutes to finish the maze, or else you lose. Good luck! You're aMAZEing :)''', style = 1, fg=(0, 0, 0, 1), pos=(0, -.1), align=TextNode.ACenter, scale=0.07) self.instr.append(inst10) def deleteInstructions(self): #deletes onscreen instructions for instr in self.instr: instr.destroy() def addNotification(self, txt): #adds a notification to the screen y = 0.9 tex = OnscreenText(text=txt, style=1, fg= (0, 0, 0, 1), pos=(0, y)) self.messages.append(tex) def deleteNotifications(self): #deletes all on-screen notifications for msg in self.messages: msg.destroy() def updateLevelText(self): #updates the level text self.levelText.destroy() levelTextPos = (-1.3, -0.95) levelScale = 0.07 self.levelText = OnscreenText(text= "Level = " + str(self.level), style=1, fg=(0, 0, 0, 1), pos=levelTextPos, align=TextNode.ALeft, scale=levelScale) def updateDirectionText(self): #updates the direction text on the screen self.directionText.destroy() directionTextPos = (-1.3, -0.85) directionScale = 0.07 self.directionText = OnscreenText(text="Direction = " + self.direction, style=1, fg=(0, 0, 0, 1), pos=directionTextPos, align=TextNode.ALeft, scale=directionScale) def updateTimerText(self): #updates timer on screen self.timerText.destroy() timerTextPos = (1.3, 0.85) timerScale = 0.07 if self.mode == "timer": self.timerText = OnscreenText(text= self.timer, style=1, fg=(1, 1, 1, 1), pos=timerTextPos, align=TextNode.ARight, scale=timerScale) def turnWallNotification(self): #give a notification sequence at the beginning notificationSeq = Sequence() notificationSeq.append(Func(addNotification,""" If you just see a blank color, it means you are facing a wall :)""")) notificationSeq.append(Wait(8)) notificationSeq.append(Func(deleteNotifications)) notificationSeq.start() def promptMode(self): #prompts for the mode modeScreen = SplashScreen() modeScreen.mode() def setMode(self, mode): #sets the mode of the game self.mode = mode if self.mode == "timer": self.setTimer() ###################### Initialization Helper Functions ##################### def createBackground(self): #black feautureless space base.win.setClearColor(Vec4(0,0,0,1)) def loadWallModel(self): #loads the wall model (the maze) wallScale = 0.3 wallModelName = self.randomWallModel() #randomly select a maze self.wallModel = loader.loadModel(wallModelName) self.wallModel.setScale(wallScale) self.wallModel.setPos(0, 0, 0) self.wallModel.setCollideMask(BitMask32.allOff()) self.wallModel.reparentTo(render) ### Setting Texture ### texScale = 0.08 self.wallModel.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldNormal) self.wallModel.setTexProjector(TextureStage.getDefault(), render, self.wallModel) self.wallModel.setTexScale(TextureStage.getDefault(), texScale) tex = loader.load3DTexture('/Users/jianwei/Documents/School/Freshman/Semester1/15-112/TERMPROJECT/Project/wallTex/wallTex_#.png') self.wallModel.setTexture(tex) #creating visual geometry collision self.wallModel.setCollideMask(BitMask32.bit(0)) def randomWallModel(self): #generates a random wall in the library of mazes that were #randomly generated by the Blender script "mazeGenerator" #and exported to this computer numMazes = 10 name = str(random.randint(0, numMazes)) #randomly selects a number saved in the computer path = "/Users/jianwei/Documents/School/Freshman/Semester1/15-112/TERMPROJECT/Project/mazeModels/maze" path += name return path def loadBallModel(self): #loads the character, a ball model #ballModelStartPos = (-8, -8, 0.701) #THIS IS THE END ballModelStartPos = (8, 8, 13.301) #level 0 ballScale = 0.01 self.ballModel = loader.loadModel("/Users/jianwei/Documents/School/Freshman/Semester1/15-112/TERMPROJECT/Project/ball") self.ballModel.reparentTo(render) self.ballModel.setScale(ballScale) self.ballModel.setPos(ballModelStartPos) ### Setting ball texture ### texScale = 0.08 self.ballModel.setTexGen(TextureStage.getDefault(), TexGenAttrib.MWorldPosition) self.ballModel.setTexProjector(TextureStage.getDefault(), render, self.ballModel) self.ballModel.setTexScale(TextureStage.getDefault(), texScale) tex = loader.load3DTexture('/Users/jianwei/Documents/School/Freshman/Semester1/15-112/TERMPROJECT/Project/ballTex/ballTex_#.png') self.ballModel.setTexture(tex) def setCamera(self): #sets up the initial camera location #camera will follow the sphere followLength = 2 camHeight = 0.2 base.disableMouse() base.camera.setPos(self.ballModel.getX(), self.ballModel.getY() - followLength, self.ballModel.getZ() + camHeight) base.camLens.setNear(0.4) #creates a floater object - will look at the floater object #above the sphere, so you can get a better view self.floater = NodePath(PandaNode("floater")) self.floater.reparentTo(render) def createKeyControls(self): #creates the controllers for the keys #event handler #describes what each key does when pressed and unpressed self.accept("escape", sys.exit) self.accept("arrow_left", self.turnLeft) self.accept("arrow_right", self.turnRight) self.accept("arrow_up", self.setKey, ["forward",1]) self.accept("arrow_down", self.setKey, ["backward",1]) self.accept("space", self.nowDropping) #unpressed event handlers self.accept("arrow_left-up", self.setKey, ["left",0]) self.accept("arrow_right-up", self.setKey, ["right",0]) self.accept("arrow_up-up", self.setKey, ["forward",0]) self.accept("arrow_down-up", self.setKey, ["backward",0]) self.accept("space_up", self.setKey, ["drop", 0]) #views self.accept('x', self.toggle_xray_mode) self.accept('c', self.toggle_collision_mode) self.accept('z', self.toggle_wireframe) #information self.accept('i', self.postInstructions) self.accept('i-up', self.deleteInstructions) #restart self.accept('r', self.restart) #modes self.accept("t", self.setMode, ["timer"]) self.accept("m", self.setMode, ["marathon"]) def createBallColliderModel(self): #creates the collider sphere around the ball cSphereRad = 9.9 self.cTrav = CollisionTraverser() #moves over all possible collisions self.ballModelSphere = CollisionSphere(0, 0, 0, cSphereRad) #collision mesh around ball is a simple sphere self.ballModelCol = CollisionNode('ballModelSphere') self.ballModelCol.addSolid(self.ballModelSphere) self.ballModelCol.setFromCollideMask(BitMask32.bit(0)) self.ballModelCol.setIntoCollideMask(BitMask32.allOff()) self.ballModelColNp = self.ballModel.attachNewNode(self.ballModelCol) self.ballModelGroundHandler = CollisionHandlerQueue() #collision handler queue stores all collision points self.cTrav.addCollider(self.ballModelColNp, self.ballModelGroundHandler) def createLighting(self): #creates lighting for the scene aLightVal = 0.3 dLightVal1 = -5 dLightVal2 = 5 #set up the ambient light ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(aLightVal, aLightVal, aLightVal, 1)) ambientLight1 = AmbientLight("ambientLight1") ambientLight1.setColor(Vec4(aLightVal, aLightVal, aLightVal, 1)) ambientLight2 = AmbientLight("ambientLight2") ambientLight2.setColor(Vec4(aLightVal, aLightVal, aLightVal, 1)) #sets a directional light directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(dLightVal1, dLightVal1, dLightVal1)) directionalLight.setColor(Vec4(1, 1, 1, 1)) directionalLight.setSpecularColor(Vec4(0, 0, 0, 1)) #sets a directional light directionalLight1 = DirectionalLight("directionalLight2") directionalLight1.setDirection(Vec3(dLightVal2, dLightVal1, dLightVal1)) directionalLight1.setColor(Vec4(1, 1, 1, 1)) directionalLight1.setSpecularColor(Vec4(1, 1, 1, 1)) #attaches lights to scene render.setLight(render.attachNewNode(ambientLight)) render.setLight(render.attachNewNode(ambientLight1)) render.setLight(render.attachNewNode(ambientLight1)) render.setLight(render.attachNewNode(directionalLight)) render.setLight(render.attachNewNode(directionalLight1)) ###################### COLLISION DETECTION ##################################### def traverseTask(self, task=None): # handles collisions with collision handers and a # collision queue # essentially checks region of potential collision for collisions # and stops the ball if a collision is triggered # called by task manager self.ballModelGroundHandler.sortEntries() for i in range(self.ballModelGroundHandler.getNumEntries()): entry = self.ballModelGroundHandler.getEntry(i) if self.drop == True: #we cant drop in this situation self.ballModel.setZ(self.currentHeight) dropFailWait = 4 dropFailSeq = Sequence() dropFailSeq.append(Func(addNotification,"Whoops! You can't drop here!")) dropFailSeq.append(Wait(dropFailWait)) dropFailSeq.append(Func(deleteNotifications)) dropFailSeq.start() self.drop = False elif self.direction == "N": self.northDisableMovements() elif self.direction == "S": self.southDisableMovements() elif self.direction == "E": self.eastDisableMovements() elif self.direction == "W": self.westDisableMovements() if task: return task.cont #exit task # If there are no collisions if task: return task.cont def northDisableMovements(self): #disables movements when direction is north if self.keyMap["forward"] != 0: #if the ball was moving foward self.disableForwardMovement = True #disable forward movement if self.keyMap["backward"] != 0: self.disableBackwardMovement = True def southDisableMovements(self): #disables movements when direction is south if self.keyMap["forward"] != 0: self.disableBackwardMovement = True if self.keyMap["backward"] != 0: self.disableForwardMovement = True def eastDisableMovements(self): #disables movements when direction is east if self.keyMap["forward"] != 0: self.disableRightMovement = True if self.keyMap["backward"] != 0: self.disableLeftMovement = True def westDisableMovements(self): #disables movements when direction is west if self.keyMap["forward"] != 0: self.disableLeftMovement = True if self.keyMap["backward"] != 0: self.disableRightMovement = True def checkCollisions(self): #checks for collisions self.cTrav.traverse(render) def enableAllWalls(self): #enables all walls by disabling all the disable wall functions self.disableLeftMovement = False self.disableRightMovement = False self.disableForwardMovement = False self.disableBackwardMovement = False def inCollision(self): #return true if we are in a collision right now, false otherwise if (self.disableForwardMovement == True or self.disableBackwardMovement == True or self.disableRightMovement == True or self.disableLeftMovement): return True return False def checkForWin(self): #checks for a win, toggles win splash sceen if we win yLoc = self.ballModel.getY() exitBound = -9.1 if yLoc < exitBound: winScreen = SplashScreen() winScreen.win() if self.mode == "timer": self.checkForTimerLoss() def checkForTimerLoss(self): #checks to see the time, will lose if past 5 minutes if self.timer == "0:05:00": loseScreen = SplashScreen() loseScreen.lose() ###################### MOVEMENTS ############################################### def move(self, task): # Accepts arrow keys to move the player front and back # Also deals with grid checking and collision detection step = 0.03 #movement animation self.movementAnimation(step) #rotation animation self.rotationAnimation() base.camera.setX(self.ballModel.getX() + math.sin(self.camAngle)) base.camera.setY(self.ballModel.getY() + math.cos(self.camAngle)) self.resetCamDist() self.checkCollisions() self.lookAtFloater() self.checkForWin() return task.cont def resetCamDist(self): #resets the camera distance to a specific distance #keeps distance relatively constant camFarDist = 0.75 camCloseDist = 0.7 camvec = self.ballModel.getPos() - base.camera.getPos() #vector between ball and camera camvec.setZ(0) camdist = camvec.length() camvec.normalize() if (camdist > camFarDist): base.camera.setPos(base.camera.getPos() + camvec*(camdist-camFarDist)) camdist = camFarDist if (camdist < camCloseDist): base.camera.setPos(base.camera.getPos() - camvec*(camCloseDist-camdist)) camdist = camCloseDist base.camera.lookAt(self.ballModel) def lookAtFloater(self): #looks at the floater above the sphere floaterHeight = 0.23 self.floater.setPos(self.ballModel.getPos()) self.floater.setZ(self.ballModel.getZ() + floaterHeight) base.camera.lookAt(self.floater) ####################### Movement Animation ################################# def ballIsMoving(self): #notes if the ball is moving or not with self.isMoving variable if (self.keyMap["forward"]!=0) or (self.keyMap["backward"]!=0): if self.isMoving == False: self.isMoving = True elif self.keyMap["forward"] == 0 and self.keyMap["backward"] == 0: self.isMoving = False def movementAnimation(self, step): #describes the movement animation if self.drop == True: self.dropMovementAnimation(step) elif self.direction == "N": self.northMovementAnimation(step) elif self.direction == "S": self.southMovementAnimation(step) elif self.direction == "E": self.eastMovementAnimation(step) elif self.direction == "W": self.westMovementAnimation(step) def northMovementAnimation(self, step): #describes animation when direction is north if (self.keyMap["forward"]!=0): #if you are pressing forward if self.disableForwardMovement == False: #if you are just moving through space... self.ballModel.setY(self.ballModel.getY() + step) if self.disableBackwardMovement == True: #if you had moved backwards into a wall #and you want to move forward again self.ballModel.setY(self.ballModel.getY() + step) self.disableBackwardMovement = False if (self.keyMap["backward"]!=0): #if you are pressing backwards if self.disableBackwardMovement == False: #if you are just moving backwards through space... self.ballModel.setY(self.ballModel.getY() - step) if self.disableForwardMovement == True: #if you had moved forward into a wall #and want to back away from the wall self.ballModel.setY(self.ballModel.getY() - step) self.disableForwardMovement = False def southMovementAnimation(self, step): #describes animation when direction is north #same relative set of animations to northMovementAnimation #but opposite if (self.keyMap["forward"]!=0): if self.disableBackwardMovement == False: self.ballModel.setY(self.ballModel.getY() - step) if self.disableForwardMovement == True: self.ballModel.setY(self.ballModel.getY() - step) self.disableForwardMovement = False if (self.keyMap["backward"]!=0): if self.disableForwardMovement == False: self.ballModel.setY(self.ballModel.getY() + step) if self.disableBackwardMovement == True: self.ballModel.setY(self.ballModel.getY() + step) self.disableBackwardMovement = False def eastMovementAnimation(self, step): #describes animation when direction is east #same relative as north and south movement animations #but relative to the x axis #and disabling/enabling right and left movement at collisions if (self.keyMap["forward"]!=0): if self.disableRightMovement == False: self.ballModel.setX(self.ballModel.getX() + step) if self.disableLeftMovement == True: self.ballModel.setX(self.ballModel.getX() + step) self.disableLeftMovement = False if (self.keyMap["backward"]!=0): if self.disableLeftMovement == False: self.ballModel.setX(self.ballModel.getX() - step) if self.disableRightMovement == True: self.ballModel.setX(self.ballModel.getX() - step) self.disableRightMovement = False def westMovementAnimation(self, step): #describes animation when direction is west #relatively same animations as the east movement animations #exact opposite if (self.keyMap["forward"]!=0): if self.disableLeftMovement == False: self.ballModel.setX(self.ballModel.getX() - step) if self.disableRightMovement == True: self.ballModel.setX(self.ballModel.getX() - step) self.disableRightMovement = False if (self.keyMap["backward"]!=0): if self.disableRightMovement == False: self.ballModel.setX(self.ballModel.getX() + step) if self.disableLeftMovement == True: self.ballModel.setX(self.ballModel.getX() + step) self.disableLeftMovement = False def turnRight(self): #turns right in the animation #uses an interval to slowly rotate camera around initial = self.camAngle final = self.camAngle + math.pi/2 #turn animation turnTime = 0.2 turnRightSeq = Sequence() turnRightSeq.append(LerpFunc(self.changeCamAngle, turnTime, initial, final, 'easeInOut')) turnRightSeq.start() self.setKey("right", 1) #notes that the right key is pressed #changes the direction right, based on current direction if self.direction == "N": self.direction = "E" elif self.direction == "E": self.direction = "S" elif self.direction == "S": self.direction = "W" else: self.direction = "N" #when you turn, all the collision disablements should be True #just checking #self.enableAllWalls() #update the label self.updateDirectionText() def turnLeft(self): #turns left initial = self.camAngle final = self.camAngle - math.pi/2 #turn animation turnTime = 0.2 turnRightSeq = Sequence() turnRightSeq.append(LerpFunc(self.changeCamAngle, turnTime, initial, final, 'easeInOut')) turnRightSeq.start() self.setKey("left", 1) #notes that left key is pressed #changes the direction left, based on current direction if self.direction == "N": self.direction = "W" elif self.direction == "W": self.direction = "S" elif self.direction == "S": self.direction = "E" else: self.direction = "N" #when you turn, all the collision disablements should be True #just checking #self.enableAllWalls() #update the label self.updateDirectionText() def changeCamAngle(self, angle): #changes the camAngle to angle self.camAngle = angle def dropMovementAnimation(self, step): #describes movement when drop is hit a = 0.1 if self.keyMap["drop"] != 0: if self.ballModel.getZ() > self.currentHeight - self.levelHeight+ a: self.ballModel.setZ(self.ballModel.getZ() - step) else: self.currentHeight -= self.levelHeight self.level += 1 self.updateLevelText() self.drop = False base.camera.setZ(self.ballModel.getZ() + self.cameraHeight) def nowDropping(self): #toggles isDropping boolean self.drop = True self.setKey("drop", 1) ################## Ball Rotation Animation ################################# def rotationAnimation(self): #describes the rotation movement of sphere self.ballIsMoving() speed=300 inCollision = self.inCollision() if self.isMoving and not inCollision: if self.direction == "N": self.northRotationAnimation(speed) if self.direction == "S": self.southRotationAnimation(speed) if self.direction == "E": self.eastRotationAnimation(speed) if self.direction == "W": self.westRotationAnimation(speed) def northRotationAnimation(self, speed): #describes the rotation animation if direction is north if self.keyMap["forward"] != 0: self.ballModel.setP(self.ballModel.getP()-speed*globalClock.getDt()) elif self.keyMap["backward"] != 0: self.ballModel.setP(self.ballModel.getP()+speed*globalClock.getDt()) def southRotationAnimation(self, speed): #describes the rotaiton animation if the direction is south if self.keyMap["backward"] != 0: self.ballModel.setP(self.ballModel.getP()-speed*globalClock.getDt()) elif self.keyMap["forward"] != 0: self.ballModel.setP(self.ballModel.getP()+speed*globalClock.getDt()) def eastRotationAnimation(self, speed): #describes the rotation animation if the direction is east if self.keyMap["backward"] != 0: self.ballModel.setR(self.ballModel.getR()-speed*globalClock.getDt()) elif self.keyMap["forward"] != 0: self.ballModel.setR(self.ballModel.getR()+speed*globalClock.getDt()) def westRotationAnimation(self, speed): #describes the rotation animation if the direction is west if self.keyMap["forward"] != 0: self.ballModel.setR(self.ballModel.getR()-speed*globalClock.getDt()) elif self.keyMap["backward"] != 0: self.ballModel.setR(self.ballModel.getR()+speed*globalClock.getDt()) ###################### VIEWS ################################################### def toggle_xray_mode(self): #Toggle X-ray mode on and off. #Note: slows down program considerably xRayA = 0.5 self.xray_mode = not self.xray_mode if self.xray_mode: self.wallModel.setColorScale((1, 1, 1, xRayA)) self.wallModel.setTransparency(TransparencyAttrib.MDual) else: self.wallModel.setColorScaleOff() self.wallModel.setTransparency(TransparencyAttrib.MNone) def toggle_collision_mode(self): #Toggle collision mode on and off #Shows visual representation of the collisions occuring self.collision_mode = not self.collision_mode if self.collision_mode == True: # Note: Slows the program down considerably self.cTrav.showCollisions(render) else: self.cTrav.hideCollisions() def toggle_wireframe(self): #toggles wireframe view self.wireframe = not self.wireframe if self.wireframe: self.wallModel.setRenderModeWireframe() else: self.wallModel.setRenderModeFilled() ##################### RESTART ################################################## def restart(self): #restarts the game loading = SplashScreen() loading.loading() self.reset() def reset(self): #resets the maze, resets the location of the character #removes all notes self.wallModel.removeNode() self.ballModel.removeNode() #resets notes self.loadWallModel() self.loadBallModel() self.createBallColliderModel() self.resetCamDist() #resets timers taskMgr.remove("timerTask") self.timer = "" self.timerText.destroy() self.promptMode() #################### TIMER ##################################################### def setTimer(self): #code from panda.egg user on Panda3D, #"How to use Timer, a small example maybe?" forum #creates a timer self.timer = DirectLabel(pos=Vec3(1, 0.85),scale=0.08) taskMgr.add(self.timerTask, "timerTask") def dCharstr(self, theString): #code from panda.egg user on Panda3D, #"How to use Timer, a small example maybe?" forum #turns time string into a readable clock string if len(theString) != 2: theString = '0' + theString return theString def timerTask(self, task): #code from panda.egg user on Panda3D, #"How to use Timer, a small example maybe?" forum #task for resetting timer in timer mode secondsTime = int(task.time) minutesTime = int(secondsTime/60) hoursTime = int(minutesTime/60) self.timer = (str(hoursTime) + ':' + self.dCharstr(str(minutesTime%60)) + ':' + self.dCharstr(str(secondsTime%60))) self.updateTimerText() return Task.cont
class Camera: """A floating 3rd person camera that follows an actor around, and can be turned left or right around the actor. Public fields: self.controlMap -- The camera's movement controls. actor -- The Actor object that the camera will follow. Public functions: init(actor) -- Initialise the camera. move(task) -- Move the camera each frame, following the assigned actor. This task is called every frame to update the camera. setControl -- Set the camera's turn left or turn right control on or off. """ def __init__(self, actor): """Initialise the camera, setting it to follow 'actor'. Arguments: actor -- The Actor that the camera will initially follow. """ self.actor = actor self.prevtime = 0 # The camera's controls: # "left" = move the camera left, 0 = off, 1 = on # "right" = move the camera right, 0 = off, 1 = on self.controlMap = {"left": 0, "right": 0} taskMgr.add(self.move, "cameraMoveTask") # Create a "floater" object. It is used to orient the camera above the # target actor's head. self.floater = NodePath(PandaNode("floater")) self.floater.reparentTo(render) # Set up the camera. base.disableMouse() base.camera.setPos(self.actor.getX(), self.actor.getY() + 2, 2) # uncomment for topdown #base.camera.setPos(self.actor.getX(),self.actor.getY()+10,2) #base.camera.setHpr(180, -50, 0) # A CollisionRay beginning above the camera and going down toward the # ground is used to detect camera collisions and the height of the # camera above the ground. A ray may hit the terrain, or it may hit a # rock or a tree. If it hits the terrain, we detect the camera's # height. If it hits anything else, the camera is in an illegal # position. self.cTrav = CollisionTraverser() self.groundRay = CollisionRay() self.groundRay.setOrigin(0, 0, 1000) self.groundRay.setDirection(0, 0, -1) self.groundCol = CollisionNode('camRay') self.groundCol.addSolid(self.groundRay) self.groundCol.setFromCollideMask(BitMask32.bit(1)) self.groundCol.setIntoCollideMask(BitMask32.allOff()) self.groundColNp = base.camera.attachNewNode(self.groundCol) self.groundHandler = CollisionHandlerQueue() self.cTrav.addCollider(self.groundColNp, self.groundHandler) # Uncomment this line to see the collision rays #self.groundColNp.show() def move(self, task): """Update the camera's position before rendering the next frame. This is a task function and is called each frame by Panda3D. The camera follows self.actor, and tries to remain above the actor and above the ground (whichever is highest) while looking at a point slightly above the actor's head. Arguments: task -- A direct.task.Task object passed to this function by Panda3D. Return: Task.cont -- To tell Panda3D to call this task function again next frame. """ # FIXME: There is a bug with the camera -- if the actor runs up a # hill and then down again, the camera's Z position follows the actor # up the hill but does not come down again when the actor goes down # the hill. elapsed = task.time - self.prevtime # If the camera-left key is pressed, move camera left. # If the camera-right key is pressed, move camera right. # comment out for topdown base.camera.lookAt(self.actor) camright = base.camera.getNetTransform().getMat().getRow3(0) camright.normalize() if (self.controlMap["left"] != 0): base.camera.setPos(base.camera.getPos() - camright * (elapsed * 20)) if (self.controlMap["right"] != 0): base.camera.setPos(base.camera.getPos() + camright * (elapsed * 20)) # If the camera is too far from the actor, move it closer. # If the camera is too close to the actor, move it farther. camvec = self.actor.getPos() - base.camera.getPos() camvec.setZ(0) camdist = camvec.length() camvec.normalize() if (camdist > 10.0): base.camera.setPos(base.camera.getPos() + camvec * (camdist - 10)) camdist = 10.0 if (camdist < 5.0): base.camera.setPos(base.camera.getPos() - camvec * (5 - camdist)) camdist = 5.0 # Now check for collisions. self.cTrav.traverse(render) # Keep the camera at one foot above the terrain, # or two feet above the actor, whichever is greater. # comment out for topdown entries = [] for i in range(self.groundHandler.getNumEntries()): entry = self.groundHandler.getEntry(i) entries.append(entry) entries.sort(lambda x, y: cmp( y.getSurfacePoint(render).getZ(), x.getSurfacePoint(render).getZ())) if (len(entries) > 0) and (entries[0].getIntoNode().getName() == "terrain"): base.camera.setZ(entries[0].getSurfacePoint(render).getZ() + 1.0) if (base.camera.getZ() < self.actor.getZ() + 2.0): base.camera.setZ(self.actor.getZ() + 2.0) # The camera should look in the player's direction, # but it should also try to stay horizontal, so look at # a floater which hovers above the player's head. self.floater.setPos(self.actor.getPos()) self.floater.setZ(self.actor.getZ() + 2.0) #self.floater.setZ(self.actor.getZ() + 10.0) #self.floater.setY(self.actor.getY() + 7.0) # comment out for topdown base.camera.lookAt(self.floater) base.camera.setPos(self.floater.getPos()) # Store the task time and continue. self.prevtime = task.time return Task.cont def setControl(self, control, value): """Set the state of one of the camera's movement controls. Arguments: See self.controlMap in __init__. control -- The control to be set, must be a string matching one of the strings in self.controlMap. value -- The value to set the control to. """ # FIXME: this function is duplicated in Camera and Character, and # keyboard control settings are spread throughout the code. Maybe # add a Controllable class? self.controlMap[control] = value
class NodeRaycaster: def __init__(self, renderer): self.log = logging.getLogger('pano.raycaster') self.renderer = renderer #Stores the collisions of the camera ray with the cubemap self.collisionsQueue = None #Variables for setting up collision detection in Panda self.pickerNP = None self.pickerNode = None self.pickerRay = None self.traverser = None def initialize(self): """ To setup collision detection we need: a. A CollisionNode having a ray as its solid and placed at the position of the camera while also having the same orientation as the camera. b. A new nodepath placed in the scenegraph as an immediate child of the camera. It will be used to insert the collision node in the scenegraph. c. A CollisionRay for firing rays based on mouse clicks. d. A collisions traverser. e. A collisions queue where all found collisions will be stored for later processing. """ self.traverser = CollisionTraverser('Hotspots collision traverser') self.collisionsQueue = CollisionHandlerQueue() self.pickerNode = CollisionNode('mouseRay') self.pickerRay = CollisionRay() self.pickerNode.addSolid(self.pickerRay) self.pickerNP = self.renderer.getCamera().attachNewNode( self.pickerNode) self.traverser.addCollider(self.pickerNP, self.collisionsQueue) def dispose(self): if self.pickerNP is not None: self.traverser.removeCollider(self.pickerNP) self.pickerNode.clearSolids() self.pickerNP.removeNode() def raycastWindow(self, x, y, returnAll=False): ''' Casts a camera ray, whose origin is implicitly defined by the given window coordinates, against the rendered scene returns information regarding the hit point, if any. @param x: The x window coordinate of the ray's origin in render2d space. @param y: The y window coordinate of the ray's origin in render2d space @param returnAll: If set to False then only the closest collided geometry is returned, otherwise all nodepaths whose collision nodes were intersected by the camera ray will be returned. @return: If returnAll was False, then a list containing a tuple of the form (topmost intersected NodePath, contact point Point3f). if returnAll was set to True, a list of tuples in the same form as above, one tuple for each intersection. None if no collision occurred. ''' #This makes the ray's origin the camera and makes the ray point #to the screen coordinates of the mouse self.pickerRay.setFromLens(self.renderer.getCamera().node(), x, y) #Check for collision only with the node self.traverser.traverse(self.renderer.getSceneRoot()) if self.collisionsQueue.getNumEntries() > 0: if not returnAll: self.collisionsQueue.sortEntries() cEntry = self.collisionsQueue.getEntry(0) if cEntry.hasInto(): return [(cEntry.getIntoNodePath(), cEntry.getSurfacePoint())] else: return None else: nodepaths = [] for i in xrange(self.collisionsQueue.getNumEntries()): cEntry = self.collisionsQueue.getEntry(i) if cEntry.hasInto(): # self.log.debug('adding collision into-nodepath: %s' % str(cEntry.getIntoNodePath())) intoNP = cEntry.getIntoNodePath() nodepaths.append( (intoNP, cEntry.getSurfacePoint(intoNP))) return nodepaths
class cWorld: def __init__(self): # set background color base.setBackgroundColor(0, 0, 0) # create target self.createTarget() # create boids self.createBoids() # setup camera self.setupCamera() # setup lights self.setupLights() # setup collision detection self.setupCollision() # add task taskMgr.add(self.steer, 'steer') # steer task taskMgr.add(self.moveTarget, 'moveTarget') # mouse move target task def createBoids(self): self.redBoid = cBoid() # create red boid # setup blue boid with model path, starting location, max force, and max speed self.redBoid.setup('assets/models/boid_one.egg', Vec3(0.0, 0.0, 0.0), 4.0, 0.1) # create blue boid self.blueBoid = cBoid() # setup blue boid with model path, starting location, max force, and max speed self.blueBoid.setup('assets/models/boid_two.egg', Vec3(0.0, 0.0, 0.0), 4.0, 1.0) def createTarget(self): # load in model file self.target = loader.loadModel('assets/models/target.egg') # parent self.target.reparentTo(render) # set location self.target.setPos(Vec3(0.0, 0.0, 0.0)) def setupCamera(self): # disable auto controls base.disableMouse() # set position, heading, pitch, and roll camera.setPosHpr(Vec3(0.0, -45.0, 45.0), Vec3(0.0, -45.0, 0)) def setupLights(self): # create a point light plight = PointLight('plight') # set its color plight.setColor(VBase4(1.0, 1.0, 1.0, 1)) # attach the light to the render plnp = render.attachNewNode(plight) # set position plnp.setPos(0.0, 0.0, 2.0) # turn on light render.setLight(plnp) def setupCollision(self): # create collision traverser self.picker = CollisionTraverser() # create collision handler self.pq = CollisionHandlerQueue() # create collision node self.pickerNode = CollisionNode('mouseRay') # create collision node # attach new collision node to camera node self.pickerNP = camera.attachNewNode( self.pickerNode) # attach collision node to camera # set bit mask to one self.pickerNode.setFromCollideMask(BitMask32.bit(1)) # set bit mask # create a collision ray self.pickerRay = CollisionRay() # create collision ray # add picker ray to the picker node self.pickerNode.addSolid( self.pickerRay) # add the collision ray to the collision node # make the traverser know about the picker node and its even handler queue self.picker.addCollider( self.pickerNP, self.pq) # add the colision node path and collision handler queue #self.picker.showCollisions( render ) # render or draw the collisions #self.pickerNP.show( ) # render picker ray # create col node self.colPlane = CollisionNode('colPlane') # add solid to col node plane self.colPlane.addSolid( CollisionPlane(Plane(Vec3(0, 0, 1), Point3(0, 0, 0)))) # attach new node to the render self.colPlanePath = render.attachNewNode(self.colPlane) #self.colPlanePath.show( ) # render node # make the col plane look at the camera # this makes it alway look at the camera no matter the orientation # we need this because the ray nees to intersect a plane parallel # to the camera self.colPlanePath.lookAt(camera) # prop up the col plane self.colPlanePath.setP(-45) # set bit mask to one # as I understand it, this makes all col nodes with bit mask one # create collisions while ignoring others of other masks self.colPlanePath.node().setIntoCollideMask(BitMask32.bit(1)) def steer(self, Task): # seek after target self.redBoid.seek(Vec3(self.target.getPos())) # run the algorithm self.redBoid.run() # arrive at the target self.blueBoid.arrive(Vec3(self.target.getPos())) # run the algorithm self.blueBoid.run() return Task.cont # continue task def moveTarget(self, Task): # traverse through the render tree self.picker.traverse(render) # go through the queue of collisions for i in range(self.pq.getNumEntries()): entry = self.pq.getEntry(i) # get entry surfacePoint = entry.getSurfacePoint( render) # get surface point of collision self.target.setPos( surfacePoint) # set surface point to target's position if base.mouseWatcherNode.hasMouse(): # if we have a mouse mpos = base.mouseWatcherNode.getMouse( ) # get the path to the mouse # shoot ray from camera # based on X & Y coordinate of mouse self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) return Task.cont # continue task
class CogdoFlyingCameraManager: def __init__(self, cam, parent, player, level): self._toon = player.toon self._camera = cam self._parent = parent self._player = player self._level = level self._enabled = False def enable(self): if self._enabled: return self._toon.detachCamera() self._prevToonY = 0.0 levelBounds = self._level.getBounds() l = Globals.Camera.LevelBoundsFactor self._bounds = ((levelBounds[0][0] * l[0], levelBounds[0][1] * l[0]), (levelBounds[1][0] * l[1], levelBounds[1][1] * l[1]), (levelBounds[2][0] * l[2], levelBounds[2][1] * l[2])) self._lookAtZ = self._toon.getHeight( ) + Globals.Camera.LookAtToonHeightOffset self._camParent = NodePath('CamParent') self._camParent.reparentTo(self._parent) self._camParent.setPos(self._toon, 0, 0, 0) self._camParent.setHpr(180, Globals.Camera.Angle, 0) self._camera.reparentTo(self._camParent) self._camera.setPos(0, Globals.Camera.Distance, 0) self._camera.lookAt(self._toon, 0, 0, self._lookAtZ) self._cameraLookAtNP = NodePath('CameraLookAt') self._cameraLookAtNP.reparentTo(self._camera.getParent()) self._cameraLookAtNP.setPosHpr(self._camera.getPos(), self._camera.getHpr()) self._levelBounds = self._level.getBounds() self._enabled = True self._frozen = False self._initCollisions() def _initCollisions(self): self._camCollRay = CollisionRay() camCollNode = CollisionNode('CameraToonRay') camCollNode.addSolid(self._camCollRay) camCollNode.setFromCollideMask(OTPGlobals.WallBitmask | OTPGlobals.CameraBitmask | ToontownGlobals.FloorEventBitmask | ToontownGlobals.CeilingBitmask) camCollNode.setIntoCollideMask(0) self._camCollNP = self._camera.attachNewNode(camCollNode) self._camCollNP.show() self._collOffset = Vec3(0, 0, 0.5) self._collHandler = CollisionHandlerQueue() self._collTrav = CollisionTraverser() self._collTrav.addCollider(self._camCollNP, self._collHandler) self._betweenCamAndToon = {} self._transNP = NodePath('trans') self._transNP.reparentTo(render) self._transNP.setTransparency(True) self._transNP.setAlphaScale(Globals.Camera.AlphaBetweenToon) self._transNP.setBin('fixed', 10000) def _destroyCollisions(self): self._collTrav.removeCollider(self._camCollNP) self._camCollNP.removeNode() del self._camCollNP del self._camCollRay del self._collHandler del self._collOffset del self._betweenCamAndToon self._transNP.removeNode() del self._transNP def freeze(self): self._frozen = True def unfreeze(self): self._frozen = False def disable(self): if not self._enabled: return self._destroyCollisions() self._camera.wrtReparentTo(render) self._cameraLookAtNP.removeNode() del self._cameraLookAtNP self._camParent.removeNode() del self._camParent del self._prevToonY del self._lookAtZ del self._bounds del self._frozen self._enabled = False def update(self, dt=0.0): self._updateCam(dt) self._updateCollisions() def _updateCam(self, dt): toonPos = self._toon.getPos() camPos = self._camParent.getPos() x = camPos[0] z = camPos[2] toonWorldX = self._toon.getX(render) maxX = Globals.Camera.MaxSpinX toonWorldX = clamp(toonWorldX, -1.0 * maxX, maxX) spinAngle = Globals.Camera.MaxSpinAngle * toonWorldX * toonWorldX / ( maxX * maxX) newH = 180.0 + spinAngle self._camParent.setH(newH) spinAngle = spinAngle * (pi / 180.0) distBehindToon = Globals.Camera.SpinRadius * cos(spinAngle) distToRightOfToon = Globals.Camera.SpinRadius * sin(spinAngle) d = self._camParent.getX() - clamp(toonPos[0], *self._bounds[0]) if abs(d) > Globals.Camera.LeewayX: if d > Globals.Camera.LeewayX: x = toonPos[0] + Globals.Camera.LeewayX else: x = toonPos[0] - Globals.Camera.LeewayX x = self._toon.getX(render) + distToRightOfToon boundToonZ = min(toonPos[2], self._bounds[2][1]) d = z - boundToonZ if d > Globals.Camera.MinLeewayZ: if self._player.velocity[2] >= 0 and toonPos[ 1] != self._prevToonY or self._player.velocity[2] > 0: z = boundToonZ + d * INVERSE_E**(dt * Globals.Camera.CatchUpRateZ) elif d > Globals.Camera.MaxLeewayZ: z = boundToonZ + Globals.Camera.MaxLeewayZ elif d < -Globals.Camera.MinLeewayZ: z = boundToonZ - Globals.Camera.MinLeewayZ if self._frozen: y = camPos[1] else: y = self._toon.getY(render) - distBehindToon self._camParent.setPos(x, smooth(camPos[1], y), smooth(camPos[2], z)) if toonPos[2] < self._bounds[2][1]: h = self._cameraLookAtNP.getH() if d >= Globals.Camera.MinLeewayZ: self._cameraLookAtNP.lookAt(self._toon, 0, 0, self._lookAtZ) elif d <= -Globals.Camera.MinLeewayZ: self._cameraLookAtNP.lookAt(self._camParent, 0, 0, self._lookAtZ) self._cameraLookAtNP.setHpr(h, self._cameraLookAtNP.getP(), 0) self._camera.setHpr( smooth(self._camera.getHpr(), self._cameraLookAtNP.getHpr())) self._prevToonY = toonPos[1] def _updateCollisions(self): pos = self._toon.getPos(self._camera) + self._collOffset self._camCollRay.setOrigin(pos) direction = -Vec3(pos) direction.normalize() self._camCollRay.setDirection(direction) self._collTrav.traverse(render) nodesInBetween = {} if self._collHandler.getNumEntries() > 0: self._collHandler.sortEntries() for entry in self._collHandler.getEntries(): name = entry.getIntoNode().getName() if name.find('col_') >= 0: np = entry.getIntoNodePath().getParent() if not np in nodesInBetween: nodesInBetween[np] = np.getParent() for np in nodesInBetween.keys(): if np in self._betweenCamAndToon: del self._betweenCamAndToon[np] else: np.setTransparency(True) np.wrtReparentTo(self._transNP) if np.getName().find('lightFixture') >= 0: if not np.find('**/*floor_mesh').isEmpty(): np.find('**/*floor_mesh').hide() elif np.getName().find('platform') >= 0: if not np.find('**/*Floor').isEmpty(): np.find('**/*Floor').hide() for np, parent in self._betweenCamAndToon.items(): np.wrtReparentTo(parent) np.setTransparency(False) if np.getName().find('lightFixture') >= 0: if not np.find('**/*floor_mesh').isEmpty(): np.find('**/*floor_mesh').show() elif np.getName().find('platform') >= 0: if not np.find('**/*Floor').isEmpty(): np.find('**/*Floor').show() self._betweenCamAndToon = nodesInBetween
class Mouse(DirectObject): def __init__(self, app): # local variables for mouse class self.app = app self.init_collide() self.has_mouse = None self.prev_pos = None self.pos = None self.drag_start = None self.hovered_object = None self.button2 = False self.mouseTask = taskMgr.add(self.mouse_task, 'mouseTask') self.task = None # set up event and response to this event self.accept('mouse1', self.mouse1) self.accept('mouse1-up', self.mouse1_up) # change the mouse to accept 'right-click' to rotate camera self.accept('mouse3', self.rotateCamera) self.accept('mouse3-up', self.stopCamera) self.accept('wheel_up', self.zoomIn) self.accept('wheel_down', self.zoomOut) # set up the collision for object def init_collide(self): # why the heck he import within method from pandac.PandaModules import CollisionTraverser, CollisionNode from pandac.PandaModules import CollisionHandlerQueue, CollisionRay # init and import collision for object self.cTrav = CollisionTraverser('MousePointer') self.cQueue = CollisionHandlerQueue() self.cNode = CollisionNode('MousePointer') self.cNodePath = base.camera.attachNewNode(self.cNode) self.cNode.setFromCollideMask(GeomNode.getDefaultCollideMask()) self.cRay = CollisionRay() self.cNode.addSolid(self.cRay) self.cTrav.addCollider(self.cNodePath, self.cQueue) # by the collision methods mouse is able to find out which tile mouse is at def find_object(self): if self.app.world.nodePath: self.cRay.setFromLens(base.camNode, self.pos.getX(), self.pos.getY()) self.cTrav.traverse(self.app.world.terrain.nodePath) if self.cQueue.getNumEntries() > 0: self.cQueue.sortEntries() return self.cQueue.getEntry(0).getIntoNodePath() return None # setting task for mouse def mouse_task(self, task): action = task.cont # if the current tile has a mouse point to this self.has_mouse = base.mouseWatcherNode.hasMouse() if self.has_mouse: self.pos = base.mouseWatcherNode.getMouse() if self.prev_pos: self.delta = self.pos - self.prev_pos else: self.delta = None if self.task: action = self.task(task) else: self.pos = None if self.pos: self.prev_pos = Point2(self.pos.getX(), self.pos.getY()) return action # when mouse hover over this hexagon def hover(self, task): if self.hovered_object: self.hovered_object.unhover() self.hovered_object = None if self.button2: self.camera_drag() hovered_nodePath = self.find_object() if hovered_nodePath: tile = hovered_nodePath.findNetTag('tile') if not tile.isEmpty(): tag = tile.getTag('tile') coords = tag.split(',') (x, y) = [int(n) for n in coords] # set the hovered target to be the corresponding hexagon on terrain self.hovered_object = self.app.world.terrain.rows[x][y] self.hovered_object.hover() character = hovered_nodePath.findNetTag('char') if not character.isEmpty(): tag = character.getTag('char') (team_index, char_id) = [int(n) for n in tag.split(',')] self.hovered_object = self.app.world.teams[ team_index].characters_dict[char_id] self.hovered_object.hover() ghost = hovered_nodePath.findNetTag('ghost') if not ghost.isEmpty(): tag = ghost.getTag('ghost') (team_index, char_id) = [int(n) for n in tag.split(',')] for ghostInstance in self.app.ghosts: if (ghostInstance.team.index == team_index) and (ghostInstance.id == char_id): self.hovered_object = ghostInstance self.hovered_object.hover() return task.cont def mouse1(self): self.app.state.request('mouse1') def mouse1_up(self): self.app.state.request('mouse1-up') def camera_drag(self): if self.delta: old_heading = base.camera.getH() new_heading = old_heading - self.delta.getX() * 180 base.camera.setH(new_heading % 360) old_pitch = base.camera.getP() new_pitch = old_pitch + self.delta.getY() * 90 new_pitch = max(-90, min(-10, new_pitch)) base.camera.setP(new_pitch) def rotateCamera(self): self.button2 = True def stopCamera(self): self.button2 = False def zoomIn(self): lens = base.cam.node().getLens() size = lens.getFilmSize() if size.length() >= 75: lens.setFilmSize(size / 1.2) def zoomOut(self): lens = base.cam.node().getLens() size = lens.getFilmSize() if size.length() <= 250: lens.setFilmSize(size * 1.2)
class Game3T(ShowBase): def __init__(self): ShowBase.__init__(self) #base.setFrameRateMeter(True) OnscreenText(text='3T', pos=(0, 0.85), scale=0.2, fg=(0.3, 0.2, 8, 1)) OnscreenText(text='by: Hellmaster - 2011', pos=(0, -0.9), scale=0.1, fg=(0.5, 0, 8, 1)) FPS = 30 globalClock = ClockObject.getGlobalClock() globalClock.setMode(ClockObject.MLimited) globalClock.setFrameRate(FPS) # ***************** SETUP SHITS ***************** base.disableMouse() self.nextmove = True self.camera_plane_angle = 0 # ***************** LOAD GAME LOGIC AND OTHERS CLASSES ************* self.game = gameMecahnics() # ***************** LOAD MODELS AND SOUNDS ****************** self.loadModels() self.loadSounds() # ***************** SELECT PLAYER ****************** self.a = getPlayer() base.taskMgr.add(self.selectPlayer, "selectPlayer") def rotateCamera(self, task): dt = globalClock.getDt() self.camera_plane_angle += dt if self.camera_plane_angle > 360: self.camera_plane_angle = 0 self.cam.setPos(30 * math.cos(self.camera_plane_angle), 30 * math.sin(self.camera_plane_angle), 30) self.cam.lookAt(0, 0, 0) return task.cont def selectPlayer(self, task): self.player = self.a.getSelectedPlayer() if self.player != None: #print "player selectecd" base.taskMgr.add(self.gameLoop, "gameLoop") return task.done return task.cont def gameLoop(self, task): #print "game task" # ***************** SET CAMERA ****************** base.taskMgr.add(self.rotateCamera, "rotateCamera") self.game.setPlayer(self.player) # ***************** PICKABLE SHITS *************** # setup the pickable suqre planes on the board planes = [] id_plane = 0 for k in range(3): for i in range(3): #print "adding plane: ", id_plane p = addPlane(3) p.setPos(3 * i - 2.5, 3 * k - 2.5, 3.1) p.setTag('pickable', str(id_plane)) p.hide() planes.append(p) id_plane += 1 # set picks pickerNode = CollisionNode('mouseRay') pickerNP = self.cam.attachNewNode(pickerNode) pickerNode.setFromCollideMask(GeomNode.getDefaultCollideMask()) self.pickerRay = CollisionRay() pickerNode.addSolid(self.pickerRay) #pickerNP.show() self.rayQueue = CollisionHandlerQueue() self.cTrav = CollisionTraverser() self.cTrav.addCollider(pickerNP, self.rayQueue) # set action in case of pick self.accept('mouse1', self.picked) def loadModels(self): self.arena = loader.loadModel("models/arena/arena") self.arena.reparentTo(render) self.arena.setPos(0, 0, 0) self.playero = loader.loadModel("models/players/o") self.playerx = loader.loadModel("models/players/x") def loadSounds(self): self.sound1 = loader.loadSfx("sounds/sound1.ogg") self.sound2 = loader.loadSfx("sounds/sound2.ogg") # detects if mouse picks some shit # if yes => set piece if valid and must wait 3 seconds for next move else do shit def picked(self): if base.mouseWatcherNode.hasMouse(): mpos = base.mouseWatcherNode.getMouse() self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) self.cTrav.traverse(render) if self.rayQueue.getNumEntries() > 0: self.rayQueue.sortEntries() pickedNP = self.rayQueue.getEntry(0).getIntoNodePath() if pickedNP.hasNetTag('pickable'): if self.nextmove: #print "picked: ", pickedNP, " | id 1: ", pickedNP.findNetTag('pickable'), " id 2: ", pickedNP.getNetTag('pickable') move = int(pickedNP.getNetTag('pickable')) if self.game.isValidMove(move): #print "------VALID------", move self.putPieceIN(move) self.game.setMove(move) self.nextmove = False #else: #print "------INVALID------" #self.game.printBoard() def changeNextMoveValue(self, task): self.nextmove = True return task.done def putPieceIN(self, pos): if self.game.getCurrentPlayer() == 'o': self.newPiece = self.playero.copyTo(render) else: self.newPiece = self.playerx.copyTo(render) base.taskMgr.add(self.falling, "falling") if pos == 0: self.newPiece.setPos(-3, -3, STARTING_Z) return 0 if pos == 1: self.newPiece.setPos(0, -3, STARTING_Z) return 0 if pos == 2: self.newPiece.setPos(3, -3, STARTING_Z) return 0 if pos == 3: self.newPiece.setPos(-3, 0, STARTING_Z) return 0 if pos == 4: self.newPiece.setPos(0, 0, STARTING_Z) return 0 if pos == 5: self.newPiece.setPos(3, 0, STARTING_Z) return 0 if pos == 6: self.newPiece.setPos(-3, 3, STARTING_Z) return 0 if pos == 7: self.newPiece.setPos(0, 3, STARTING_Z) return 0 if pos == 8: self.newPiece.setPos(3, 3, STARTING_Z) return 0 return -1 def falling(self, task): dt = globalClock.getDt() self.newPiece.setZ(self.newPiece.getZ() - dt * 10) if self.newPiece.getZ() < 3: self.nextmove = True if self.game.isWinner() or self.game.isOver(): self.gameOver() else: self.game.setNextPlayer() if self.game.getCurrentPlayer() == 'o': self.sound1.play() else: self.sound2.play() return task.done return task.cont def gameOver(self): #print "--------------------------" #print "-------- GAME OVER -------" #print "--------------------------" if self.game.isWinner(): printWinner(self.game.getCurrentPlayer()) else: printWinner(None) print "--------------------------" button_exit = DirectButton(text='Exit Game', pos=(0, 0, -0.4), scale=0.1, command=exitProg)
class MousePicker(p3d.SingleTask): """ Class to represent a ray fired from the input camera lens using the mouse. """ def __init__(self, *args, **kwargs): p3d.SingleTask.__init__(self, *args, **kwargs) self.fromCollideMask = kwargs.pop('fromCollideMask', None) self.node = None self.collEntry = None # Create collision nodes self.collTrav = CollisionTraverser() #self.collTrav.showCollisions( render ) self.collHandler = CollisionHandlerQueue() self.pickerRay = CollisionRay() # Create collision ray pickerNode = CollisionNode(self.name) pickerNode.addSolid(self.pickerRay) pickerNode.setIntoCollideMask(BitMask32.allOff()) pickerNp = self.camera.attachNewNode(pickerNode) self.collTrav.addCollider(pickerNp, self.collHandler) # Create collision mask for the ray if one is specified if self.fromCollideMask is not None: pickerNode.setFromCollideMask(self.fromCollideMask) # Bind mouse button events eventNames = ['mouse1', 'control-mouse1', 'mouse1-up'] for eventName in eventNames: self.accept(eventName, self.FireEvent, [eventName]) def OnUpdate(self, task, x=None, y=None): # Update the ray's position if self.mouseWatcherNode.hasMouse(): mp = self.mouseWatcherNode.getMouse() x, y = mp.getX(), mp.getY() if x is None or y is None: return self.pickerRay.setFromLens(self.camera.node(), x, y) # Traverse the hierarchy and find collisions self.collTrav.traverse(self.rootNp) if self.collHandler.getNumEntries(): # If we have hit something, sort the hits so that the closest is first self.collHandler.sortEntries() collEntry = self.collHandler.getEntry(0) node = collEntry.getIntoNode() # If this node is different to the last node, send a mouse leave # event to the last node, and a mouse enter to the new node if node != self.node: if self.node is not None: messenger.send('%s-mouse-leave' % self.node.getName(), [self.collEntry]) messenger.send('%s-mouse-enter' % node.getName(), [collEntry]) # Send a message containing the node name and the event over name, # including the collision entry as arguments messenger.send('%s-mouse-over' % node.getName(), [collEntry]) # Keep these values self.collEntry = collEntry self.node = node elif self.node is not None: # No collisions, clear the node and send a mouse leave to the last # node that stored messenger.send('%s-mouse-leave' % self.node.getName(), [self.collEntry]) self.node = None def FireEvent(self, event): """ Send a message containing the node name and the event name, including the collision entry as arguments. """ if self.node is not None: messenger.send('%s-%s' % (self.node.getName(), event), [self.collEntry]) def GetFirstNodePath(self): """ Return the first node in the collision queue if there is one, None otherwise. """ if self.collHandler.getNumEntries(): collEntry = self.collHandler.getEntry(0) return collEntry.getIntoNodePath() return None
class Game(DirectObject.DirectObject): def __init__(self, showbase, usersData, gameData): self.showbase = showbase self.usersData = usersData self.gameData = gameData random.seed(self.gameData.randSeed) # Initialize the collision traverser. self.cTrav = CollisionTraverser() # Initialize the handler. self.collHandEvent = CollisionHandlerEvent() self.collHandEvent.addInPattern('into-%in') self.world = World(showbase) for user in self.usersData: user.centipede = Centipede(showbase, len(self.usersData), self.addToCollisions) if user.thisPlayer: self.centipede = user.centipede self.centipede.attachRing(showbase) self.foods = [] for i in range(self.gameData.maxFoods): self.foods.append(Food(self.showbase, i, self.addToCollisions)) self.ticks = 0 def destroy(self): self.world.destroy() for user in self.usersData: user.centipede.destroy() for food in self.foods: food.destroy() def runTick(self, dt): # run each of the centipedes simulations for user in self.usersData: user.centipede.update(dt) if len(user.centipede.body) > 10: return False for food in self.foods: food.update(dt) self.cTrav.traverse(self.showbase.render) self.ticks += 1 # Return true if game is still not over (false to end game) return True def collideInto(self, collEntry): for user in self.usersData: if collEntry.getFromNodePath( ) == user.centipede.head.collisionNode[0]: for food in self.foods: if collEntry.getIntoNodePath( ) == food.model.collisionNode[0]: user.centipede.addLength(self.showbase) food.reset() if len(user.centipede.body) > 2: if collEntry.getIntoNodePath( ) == user.centipede.tail.collisionNode[0]: user.centipede.reset() for i in range(len(user.centipede.body) - 1 - 2): if collEntry.getIntoNodePath() == user.centipede.body[ i + 2].collisionNode[0]: user.centipede.reset() break def addToCollisions(self, item): # Add this object to the traverser. self.cTrav.addCollider(item[0], self.collHandEvent) # Accept the events sent by the collisions. self.accept('into-' + str(item[1]), self.collideInto)
class Character: """A character with an animated avatar that moves left, right or forward according to the controls turned on or off in self.controlMap. Public fields: self.controlMap -- The character's movement controls self.actor -- The character's Actor (3D animated model) Public functions: __init__ -- Initialise the character move -- Move and animate the character for one frame. This is a task function that is called every frame by Panda3D. setControl -- Set one of the character's controls on or off. """ def __init__(self, model, run, walk, startPos, scale): """Initialise the character. Arguments: model -- The path to the character's model file (string) run : The path to the model's run animation (string) walk : The path to the model's walk animation (string) startPos : Where in the world the character will begin (pos) scale : The amount by which the size of the model will be scaled (float) """ self.controlMap = {"left":0, "right":0, "up":0, "down":0} self.actor = Actor(Config.MYDIR+model, {"run":Config.MYDIR+run, "walk":Config.MYDIR+walk}) self.actor.reparentTo(render) self.actor.setScale(scale) self.actor.setPos(startPos) self.controller = Controller.LocalController(self) taskMgr.add(self.move,"moveTask") # Note: deriving classes DO NOT need # to add their own move tasks to the # task manager. If they override # self.move, then their own self.move # function will get called by the # task manager (they must then # explicitly call Character.move in # that function if they want it). self.prevtime = 0 self.isMoving = False # 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. If it hits anything # else, we rule that the move is illegal. self.cTrav = CollisionTraverser() self.groundRay = CollisionRay() self.groundRay.setOrigin(0,0,1000) self.groundRay.setDirection(0,0,-1) self.groundCol = CollisionNode('ralphRay') self.groundCol.addSolid(self.groundRay) self.groundCol.setFromCollideMask(BitMask32.bit(1)) self.groundCol.setIntoCollideMask(BitMask32.allOff()) self.groundColNp = self.actor.attachNewNode(self.groundCol) self.groundHandler = CollisionHandlerQueue() self.cTrav.addCollider(self.groundColNp, self.groundHandler) # Uncomment this line to see the collision rays # self.groundColNp.show() #Uncomment this line to show a visual representation of the #collisions occuring # self.cTrav.showCollisions(render) def move(self, task): """Move and animate the character for one frame. This is a task function that is called every frame by Panda3D. The character is moved according to which of it's movement controls are set, and the function keeps the character's feet on the ground and stops the character from moving if a collision is detected. This function also handles playing the characters movement animations. Arguments: task -- A direct.task.Task object passed to this function by Panda3D. Return: Task.cont -- To tell Panda3D to call this task function again next frame. """ elapsed = task.time - self.prevtime # save the character's initial position so that we can restore it, # in case he falls off the map or runs into something. startpos = self.actor.getPos() # pass on input self.controller.move(task, elapsed) # If the character is moving, loop the run animation. # If he is standing still, stop the animation. if (self.controlMap["up"]!=0) or (self.controlMap["left"]!=0) or (self.controlMap["right"]!=0) or (self.controlMap["down"]!=0): if self.isMoving is False: self.actor.loop("run") self.isMoving = True else: if self.isMoving: self.actor.stop() self.actor.pose("walk",5) self.isMoving = False # Now check for collisions. self.cTrav.traverse(render) # Adjust the character's Z coordinate. If the character's ray hit terrain, # update his Z. If it hit anything else, or didn't hit anything, put # him back where he was last frame. entries = [] for i in range(self.groundHandler.getNumEntries()): entry = self.groundHandler.getEntry(i) entries.append(entry) entries.sort(lambda x,y: cmp(y.getSurfacePoint(render).getZ(), x.getSurfacePoint(render).getZ())) if (len(entries)>0) and (entries[0].getIntoNode().getName() == "terrain"): self.actor.setZ(entries[0].getSurfacePoint(render).getZ()) else: self.actor.setPos(startpos) # Store the task time and continue. self.prevtime = task.time return Task.cont def setControl(self, control, value): """Set the state of one of the character's movement controls. Arguments: See self.controlMap in __init__. control -- The control to be set, must be a string matching one of the strings in self.controlMap. value -- The value to set the control to. """ # FIXME: this function is duplicated in Camera and Character, and # keyboard control settings are spread throughout the code. Maybe # add a Controllable class? self.controlMap[control] = value
class thirdPerson(DirectObject): def __init__(self, parserClass, mainClass, mapLoaderClass, modelLoaderClass): self.switchState = False #self.t = Timer() self.keyMap = {"left": 0, "right": 0, "forward": 0, "backward": 0} self.ralph = Actor( "data/models/units/ralph/ralph", { "run": "data/models/units/ralph/ralph-run", "walk": "data/models/units/ralph/ralph-walk" }) self.ralph.reparentTo(render) # self.ralph.setPos(42, 30, 0) self.ralph.setPos(6, 10, 0) self.ralph.setScale(0.1) self.accept("escape", sys.exit) self.accept("arrow_left", self.setKey, ["left", 1]) self.accept("arrow_left-up", self.setKey, ["left", 0]) self.accept("arrow_right", self.setKey, ["right", 1]) self.accept("arrow_right-up", self.setKey, ["right", 0]) self.accept("arrow_up", self.setKey, ["forward", 1]) self.accept("arrow_up-up", self.setKey, ["forward", 0]) self.accept("arrow_down", self.setKey, ["backward", 1]) self.accept("arrow_down-up", self.setKey, ["backward", 0]) self.isMoving = False self.cTrav = CollisionTraverser() self.ralphGroundRay = CollisionRay() self.ralphGroundRay.setOrigin(0, 0, 1000) self.ralphGroundRay.setDirection(0, 0, -1) self.ralphGroundCol = CollisionNode('ralphRay') self.ralphGroundCol.addSolid(self.ralphGroundRay) self.ralphGroundCol.setFromCollideMask(BitMask32.bit(0)) self.ralphGroundCol.setIntoCollideMask(BitMask32.allOff()) self.ralphGroundColNp = self.ralph.attachNewNode(self.ralphGroundCol) self.ralphGroundHandler = CollisionHandlerQueue() self.cTrav.addCollider(self.ralphGroundColNp, self.ralphGroundHandler) #self.ralphGroundCol.show() base.cam.reparentTo(self.ralph) base.cam.setPos(0, 9, 7) self.floater2 = NodePath(PandaNode("floater2")) self.floater2.reparentTo(self.ralph) self.floater2.setZ(self.floater2.getZ() + 6) base.cam.lookAt(self.floater2) # Uncomment this line to see the collision rays # self.ralphGroundColNp.show() # self.camGroundColNp.show() #Uncomment this line to show a visual representation of the #collisions occuring # self.cTrav.showCollisions(render) self.floater = NodePath(PandaNode("floater")) self.floater.reparentTo(render) taskMgr.add(self.move, "movingTask", extraArgs=[ mainClass, parserClass, mapLoaderClass, modelLoaderClass ]) #Records the state of the arrow keys def setKey(self, key, value): self.keyMap[key] = value def move(self, mainClass, parserClass, mapLoaderClass, modelLoaderClass): # Get the time elapsed since last frame. We need this # for framerate-independent movement. elapsed = globalClock.getDt() # save ralph's initial position so that we can restore it, # in case he falls off the map or runs into something. startpos = self.ralph.getPos() # If a move-key is pressed, move ralph in the specified direction. if (self.keyMap["left"] != 0): self.ralph.setH(self.ralph.getH() + elapsed * 300) if (self.keyMap["right"] != 0): self.ralph.setH(self.ralph.getH() - elapsed * 300) if (self.keyMap["forward"] != 0): self.ralph.setY(self.ralph, -(elapsed * 50)) #25)) if (self.keyMap["backward"] != 0): self.ralph.setY(self.ralph, +(elapsed * 20)) if (self.keyMap["forward"] != 0) or (self.keyMap["left"] != 0) or (self.keyMap["right"] != 0): if self.isMoving is False: self.ralph.loop("run") self.isMoving = True elif (self.keyMap["backward"] != 0): if self.isMoving is False: self.ralph.stop() self.ralph.pose("walk", 5) self.isMoving = False else: if self.isMoving: self.ralph.stop() self.ralph.pose("walk", 5) self.isMoving = False # Now check for collisions. self.cTrav.traverse(render) # Adjust ralph's Z coordinate. If ralph's ray hit terrain, # update his Z. If it hit anything else, or didn't hit anything, put # him back where he was last frame. entries = [] for i in range(self.ralphGroundHandler.getNumEntries()): entry = self.ralphGroundHandler.getEntry(i) entries.append(entry) entries.sort(lambda x, y: cmp( y.getSurfacePoint(render).getZ(), x.getSurfacePoint(render).getZ())) if (len(entries) > 0) and (entries[0].getIntoNode().getName()[0:4] == "tile"): self.ralph.setZ(entries[0].getSurfacePoint(render).getZ()) elif (len(entries) > 0) and (entries[0].getIntoNode().getName()[0:5] == "solid"): self.ralph.setPos(startpos) x = int(entries[0].getIntoNode().getName() [len(entries[0].getIntoNode().getName()) - 6:len(entries[0].getIntoNode().getName()) - 4]) y = int(entries[0].getIntoNode().getName() [len(entries[0].getIntoNode().getName()) - 2:]) if (mapLoaderClass.tileArray[y][x].drillTime != None): mainClass.changeTile(mapLoaderClass.tileArray[y][x], 0, parserClass, modelLoaderClass, mapLoaderClass) else: self.ralph.setPos(startpos) self.ralph.setP(0) return Task.cont
class World(DirectObject): def __init__(self): base.win.setClearColor(Vec4(0, 0, 0, 1)) # enable physics (and particle) engine self.throwMode = False self.freelook = False self.score = OnscreenText('0', pos=(-1.32, 0.9), fg=(1, 1, 1, 1), bg=(0, 0, 0, 0.5), scale=0.1, align=TextNode.ALeft) # Load the environment in which Eve will walk. Set its parent # to the render variable so that it is a top-lplayerl node. self.env = loader.loadModel('models/world/world.egg.pz') self.env.reparentTo(render) self.env.setPos(0, 0, 0) self.createCollisionHandlers() # Create an Actor instance for Eve. We also specify the animation # models that we want to use as a dictionary, where we can use to # keys to refer to the animations later on. The start point of Eve # is hardcoded in the world model somewhere, so we look that up. self.player = Eve('Eve', self, self.env.find('**/start_point').getPos()) #self.player.nodePath.setZ(self.player.nodePath.getZ() + 10) self.player.nodePath.reparentTo(render) # Create a floater object that always floats 2 units above Eve. # We make sure that it is attached to Eve by reparenting it to # Eve's object instance. self.floater = NodePath(PandaNode('floater')) self.floater.reparentTo(self.player.nodePath) self.floater.setZ(self.floater.getZ() + 2) # load baseball self.baseball = Baseball('baseball', self, self.player.nodePath.getPos()) self.baseball.nodePath.reparentTo(render) self.player.pickUpItem(self.baseball) # Load the panda bear self.panda = Panda('panda', self, self.player.nodePath.getPos()) self.panda.nodePath.reparentTo(render) # Disable controlling the camera using the mouse. Note that this does # not disable the mouse completely, it merely disables the camera # movement by mouse. base.disableMouse() self.hideMouseCursor() # Set the initial position for the camera as X, Y and Z values. base.camera.setPos(self.player.nodePath.getX(), self.player.nodePath.getY() + 10, 2) # Disable modifier button compound events. base.mouseWatcherNode.setModifierButtons(ModifierButtons()) base.buttonThrowers[0].node().setModifierButtons(ModifierButtons()) # Register any control callbacks. self.accept('escape', sys.exit) self.accept('d', self.dropItem) self.accept('f', self.toggleFullscreen) self.accept('space', self.enterThrowMode) self.accept('space-up', self.leaveThrowMode) # Also make sure that we can, at any time, request the state (pressed # or not) for these keys. self.keys = keys.KeyStateManager() self.keys.registerKeys({ 'arrow_left': 'left', 'arrow_right': 'right', 'arrow_up': 'forward', 'arrow_down': 'backward', 'shift': 'shift', 'r': 'reset' }) self.mouse = mouse.MousePointerManager(0) # Schedule the move method to be executed in the game's main loop. taskMgr.add(self.update, 'update') def hideMouseCursor(self): props = WindowProperties() props.setCursorHidden(True) base.win.requestProperties(props) def toggleFullscreen(self): props = WindowProperties() props.setFullscreen(not base.win.getProperties().getFullscreen()) base.win.requestProperties(props) def enableFreelook(self): self.freelook = True # Make sure we reset the MouseMovementManager's last known mouse position, # so we don't get a huge delta on the first attempt. self.mouse.reset() base.camera.setP(0) def disableFreelook(self): self.freelook = False def createCollisionHandlers(self): # Create a new collision traverser instance. We will use this to determine # if any collisions occurred after performing movement. self.cTrav = CollisionTraverser() camGroundRay = CollisionRay() camGroundRay.setOrigin(0, 0, 1000) camGroundRay.setDirection(0, 0, -1) camGroundCol = CollisionNode('camRay') camGroundCol.addSolid(camGroundRay) camGroundCol.setFromCollideMask(BitMask32.bit(0)) camGroundCol.setIntoCollideMask(BitMask32.allOff()) camGroundColNp = base.camera.attachNewNode(camGroundCol) self.camGroundHandler = CollisionHandlerQueue() self.cTrav.addCollider(camGroundColNp, self.camGroundHandler) # register the collision pusher self.pusher = CollisionHandlerPusher() # register collision event pattern names self.pusher.addInPattern('col-%fn-into') def update(self, task): # get the time passed since the last frame timePassed = globalClock.getDt() # update player self.player.forceMove(timePassed) self.panda.forceMove(timePassed) # Do collision detection. This iterates all the collider nodes self.cTrav.traverse(render) # check if player's move is valid self.player.validateMove() self.panda.validateMove() # Set the initial position for the camera as X, Y and Z values. base.camera.setPos(self.player.nodePath.getPos()) if self.throwMode: # Position the camera a bit above the ground. base.camera.setZ(base.camera, 1.5) if self.freelook: mx, my = self.mouse.getDelta() h = -mx * 0.1 p = -my * 0.1 base.camera.setHpr(base.camera, h, p, 0) self.player.nodePath.setH(self.player.nodePath, h) else: # Set the heading, pitch and roll of the camera. base.camera.setHpr(self.player.nodePath.getHpr()) else: # Set the heading, pitch and roll of the camera. base.camera.setHpr(self.player.nodePath.getHpr()) # Position the camera somewhat behind the player. base.camera.setY(base.camera, 10) # Make sure the camera is above the ground. camGroundEntry = self.getGroundEntry(self.camGroundHandler) if camGroundEntry is not None and camGroundEntry.getIntoNode( ).getName() == 'terrain': base.camera.setZ( camGroundEntry.getSurfacePoint(render).getZ() + 1.5) # Let the camera look at the floater object above Eve. base.camera.lookAt(self.floater) return Task.cont def dropItem(self): self.player.dropItem() def getGroundEntry(self, collisionHandler): # Put all the collision entries into a Python list so we can sort it, # properly. entries = [] for i in range(collisionHandler.getNumEntries()): entries.append(collisionHandler.getEntry(i)) # Sort the list by the collision points' Z values, making sure the # highest value ends up at the front of the list. entries.sort(lambda x, y: cmp( y.getSurfacePoint(render).getZ(), x.getSurfacePoint(render).getZ())) if len(entries) > 0: return entries[0] else: return None def enterThrowMode(self): self.throwMode = True self.player.enterStrafeMode() self.enableFreelook() def leaveThrowMode(self): self.throwMode = False self.player.leaveStrafeMode() self.disableFreelook()
class Mode(object): """This is the base Mode class""" def __init__(self, game): self.name = "MODE" self.guiMediaPath = '../Packages/anw/gui/media/' self.alive = 1 self.enableMouseCamControl = 1 self.enableScrollWheelZoom = 1 self.canSelectFlags = {} self.messagePositions = [] self.selectTypes = [] self.gui = [] self.sims = [] self.game = game self.depth = 20.0 self.zoomCameraDepth = 10.0 self.zoomCameraOutDepth = -10.0 self.zoomSpeed = 5 self.panSpeed = 1.0 self.runningTasks = [] if globals.serverMode == 0: self.setMyBackground() camera.setHpr(0,0,0) self.mainmenu = None self.scrollSpeed = 0.1 if globals.serverMode == 0: self.setMousePicker() self.setCameraPosition() self.selector = None self.selector2 = None self.log = logging.getLogger('mode') self.entryFocusList = ('anw.gui.mainmenubuttons','anw.gui.industryvalue', 'anw.gui.cityindustry','anw.gui.weapondirection', 'anw.gui.scrollvalue','anw.gui.shipdesignvalue', 'anw.gui.systemmenu','anw.gui.tradevalue', 'anw.gui.designmenu','anw.gui.shipyardmenu', 'anw.gui.mimenu', 'anw.gui.textentry', 'anw.gui.marketsystemsellvalue', 'anw.gui.sendcreditsvalue') def __getstate__(self): odict = self.__dict__.copy() # copy the dict since we change it del odict['log'] # remove stuff not to be pickled return odict def __setstate__(self,dict): log=logging.getLogger('mode') self.__dict__.update(dict) self.log=log def setMousePicker(self): self.picker = CollisionTraverser() self.pq = CollisionHandlerQueue() self.pickerNode = CollisionNode('mouseRay') self.pickerNP = camera.attachNewNode(self.pickerNode) self.pickerNode.setFromCollideMask(BitMask32.bit(1)) self.pickerRay = CollisionRay() self.pickerNode.addSolid(self.pickerRay) self.picker.addCollider(self.pickerNP, self.pq) self.selectable = render.attachNewNode("selectable") def setCameraPosition(self): self.cameraPos = (camera.getX(), camera.getY(), camera.getZ()) self.cameraMoving = 0 def setCanSelectFlag(self, key): """Set the Flag""" self.clearAllCanSelectFlags() self.canSelectFlags[key] = 1 def clearAllCanSelectFlags(self): """Clear any selection flags""" for key in self.canSelectFlags.keys(): self.canSelectFlags[key] = 0 def isAnyFlagSelected(self): """Return 1 if any flags are selected""" for key in self.canSelectFlags.keys(): if self.canSelectFlags[key] == 1: return 1 return 0 def validateSelection(self): """Can something be selected right now""" if self.cameraMoving == 0: return 1 else: return 0 def removeMyGui(self, myGuiName): """Remove gui""" myGui = getattr(self, myGuiName) if myGui in self.gui: self.gui.remove(myGui) if myGui != None: myGui.destroy() setattr(self, myGuiName, None) def createMainMenu(self, key): self.mainmenu = mainmenubuttons.MainMenuButtons(self.guiMediaPath) self.mainmenu.setMyGame(self.game) self.mainmenu.setMyMode(self) self.mainmenu.enableLastButton(key) self.mainmenu.checkDisableButton(key) self.mainmenu.writeGameInfo() self.mainmenu.acceptSpaceBarKey() self.gui.append(self.mainmenu) def removeMainMenu(self): if self.mainmenu != None: self.mainmenu.destroyMe() self.mainmenu = None def centerCameraOnSim(self, sim): """Center the camera on the sim position""" self.game.app.disableMouseCamControl() camera.setPos(sim.getX(), camera.getY(), sim.getZ()) camera.setHpr(0,0,0) if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() def drawBox(self, x, y, width, height, color='guiblue1', lineWidth=0.15, glow=1): """Draw a box""" #LEFT myLine = line.Line(self.guiMediaPath,(x,y),(x,y+height), 'square_grey', lineWidth, glow) myLine.sim.setColor(globals.colors[color]) self.gui.append(myLine) #TOP myLine = line.Line(self.guiMediaPath,(x,y+height),(x+width,y+height), 'square_grey', lineWidth, glow) myLine.sim.setColor(globals.colors[color]) self.gui.append(myLine) #RIGHT myLine = line.Line(self.guiMediaPath,(x+width,y+height),(x+width,y), 'square_grey', lineWidth, glow) myLine.sim.setColor(globals.colors[color]) self.gui.append(myLine) #BOTTOM myLine = line.Line(self.guiMediaPath,(x+width,y),(x,y), 'square_grey', lineWidth, glow) myLine.sim.setColor(globals.colors[color]) self.gui.append(myLine) def stopCameraTasks(self): taskMgr.remove('zoomInCameraTask') taskMgr.remove('zoomOutCameraTask') self.cameraMoving = 0 self.game.app.enableMouseCamControl() self.enableMouseCamControl=1 def resetCamera(self): self.game.app.disableMouseCamControl() camera.setPos(self.cameraPos[0], self.zoomCameraOutDepth, self.cameraPos[2]) # I don't really understand why this doesn't reset the view when having a planet selected and hitting spacebar? camera.setHpr(0,0,0) if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() def zoomInCamera(self): if camera.getY() <= self.zoomCameraDepth: self.game.app.disableMouseCamControl() taskMgr.add(self.zoomInCameraTask, 'zoomInCameraTask', extraArgs=[self.zoomCameraDepth]) self.runningTasks.append('zoomInCameraTask') def zoomInCameraAmount(self, amount): """Zoom in Camera a certain amount specified""" depth = camera.getY()+amount self.game.app.disableMouseCamControl() taskMgr.add(self.zoomInCameraTask, 'zoomInCameraTask', extraArgs=[depth]) self.runningTasks.append('zoomInCameraTask') def zoomInCameraTask(self, depth): """Zoom in the camera until its at depth""" y = camera.getY() if y + 0.1 >= depth: # or y >= 8.0: # TODO: tacking this on will mess with the design screen but prevents you from zooming in too close everywhere else. self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() camera.setY(y) return Task.done else: camera.setY(y+self.getZoomSpeed(y, depth)) self.cameraMoving = 1 return Task.cont def getZoomSpeed(self, y, depth): """Make Camera zoom in faster if camera is further away""" diff = depth-y return diff/5.0 def zoomOutCamera(self): if camera.getY() >= self.zoomCameraOutDepth: self.game.app.disableMouseCamControl() taskMgr.add(self.zoomOutCameraTask, 'zoomOutCameraTask', extraArgs=[self.zoomCameraOutDepth]) self.runningTasks.append('zoomOutCameraTask') def zoomOutCameraAmount(self, amount): """Zoom out Camera a certain amount sepecified""" depth = camera.getY()-amount self.game.app.disableMouseCamControl() taskMgr.add(self.zoomOutCameraTask, 'zoomOutCameraTask', extraArgs=[depth]) self.runningTasks.append('zoomOutCameraTask') def zoomOutCameraTask(self, depth): """Zoom out the camera until its at 0 Depth""" y = camera.getY() if y - 0.1 <= depth: self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() camera.setY(y) return Task.done else: camera.setY(y+self.getZoomSpeed(y, depth)) self.cameraMoving = 1 return Task.cont def panCameraLeft(self, amount): """Pan Camera""" pos = camera.getX()-amount self.game.app.disableMouseCamControl() taskMgr.add(self.panCameraLeftTask, 'panCameraLeftTask', extraArgs=[pos]) self.runningTasks.append('panCameraLeftTask') def panCameraLeftTask(self, pos): """pan the camera to new position""" x = camera.getX() if x <= pos: self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() return Task.done else: camera.setX(x-self.panSpeed) self.cameraMoving = 1 return Task.cont def panCameraRight(self, amount): """Pan Camera""" pos = camera.getX()+amount self.game.app.disableMouseCamControl() taskMgr.add(self.panCameraRightTask, 'panCameraRightTask', extraArgs=[pos]) self.runningTasks.append('panCameraRightTask') def panCameraRightTask(self, pos): """pan the camera to new position""" x = camera.getX() if x >= pos: self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() return Task.done else: camera.setX(x+self.panSpeed) self.cameraMoving = 1 return Task.cont def panCameraUp(self, amount): """Pan Camera""" pos = camera.getZ()+amount self.game.app.disableMouseCamControl() taskMgr.add(self.panCameraUpTask, 'panCameraUpTask', extraArgs=[pos]) self.runningTasks.append('panCameraUpTask') def panCameraUpTask(self, pos): """pan the camera to new position""" z = camera.getZ() if z >= pos: self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() return Task.done else: camera.setZ(z+self.panSpeed) self.cameraMoving = 1 return Task.cont def panCameraDown(self, amount): """Pan Camera""" pos = camera.getZ()-amount self.game.app.disableMouseCamControl() taskMgr.add(self.panCameraDownTask, 'panCameraDownTask', extraArgs=[pos]) self.runningTasks.append('panCameraDownTask') def panCameraDownTask(self, pos): """pan the camera to new position""" z = camera.getZ() if z <= pos: self.cameraMoving = 0 if self.enableMouseCamControl == 1: self.game.app.enableMouseCamControl() return Task.done else: camera.setZ(z-self.panSpeed) self.cameraMoving = 1 return Task.cont def createSelector(self,type='select',speed=2.0): """Create selector for indication of selected objects""" self.selector = self.loadObject(type, scale=2, parent=render, transparency=True, pos=Point2(0,0), glow=1) self.selector.hide() ival = self.selector.hprInterval((speed), Vec3(0, 0, 360)) ival.loop() def createSelector2(self,type='select',speed=2.0): """Create selector2 for indication of secondary selected objects""" self.selector2 = self.loadObject(type, scale=2, parent=render, transparency=True, pos=Point2(0,0), glow=1) self.selector2.hide() ival = self.selector2.hprInterval((speed), Vec3(0, 0, 360)) ival.loop() def playSound(self, soundName): """Play a Sound based on soundName given, call app""" if globals.serverMode == 0: self.game.app.playSound(soundName) def askForHelp(self): """Ask the Server to analyse Player and provide help""" try: serverResult = self.game.server.askForHelp(self.game.authKey) if type(serverResult) == types.ListType: (message, self.game.myEmpire['help']) = serverResult self.modeMsgBox(message) else: self.modeMsgBox(serverResult) except: self.modeMsgBox('askForHelp->Connection to Server Lost') def assignSelector(self, myObj, scale): """create the Selector and assign to myObj at scale""" if self.selector == None: self.createSelector() self.selector.show() self.selector.setPos(myObj.getX(), myObj.getY(), myObj.getZ()) self.selector.setScale(scale) def assignSelector2(self, myObj, scale): """create the Selector2 and assign to myObj at scale""" if self.selector2 == None: self.createSelector2() self.selector2.show() self.selector2.setPos(myObj.getX(), myObj.getY(), myObj.getZ()) self.selector2.setScale(scale) ##def checkEndTurn(self): ##"""Do a Server Assesment of turn before ending the turn""" ##try: ##if 'EndTurn' in self.game.myEmpire['help']: ### turn not ended yet ##(serverResult, self.game.myEmpire['help']) = self.game.server.askForHelp(self.game.authKey) ##if serverResult == 'Server Assessment: WARNINGS:0, CRITICAL:0 (Check Mail for Assesment)': ### server assessment is good, end the turn without asking ##self.endMyTurn() ##else: ### server assessment has not come back without warnings ask for confirmation ##self.modeYesNoBox('%s - Do you still want to end your turn?' % serverResult, 'endturnYes', 'yesNoBoxNo') ##else: ### turn already ended, unend turn ##self.modeYesNoBox('Do you want to cancel your end turn?' , 'endturnYes', 'yesNoBoxNo') ##except: ##self.modeMsgBox('checkEndTurn->Connection to Server Lost, Login Again') def exitGame(self, doLogout=True): """Exit the game""" self.setEmpireDefaults(self.game.authKey) if doLogout: self.setLogout(self.game.authKey) self.alive = 0 self.game.app.quit() def getCreditInfoFromServer(self): self.getEmpireUpdate(['CR']) def refreshCredit(self): """Ask the Server for an updated Credit Info""" self.mainmenu.updateCR() def getEmpireUpdate(self, listAttr): """Ask the Server for updated Empire info""" try: serverResult = self.game.server.getEmpireUpdate(self.game.authKey, listAttr) if type(serverResult) == types.StringType: self.modeMsgBox(serverResult) else: for key, value in serverResult.iteritems(): self.game.myEmpire[key] = value except: self.modeMsgBox('getEmpireUpdate->Connection to Server Lost') def getMailUpdate(self): """Ask the Server for any updated mail""" try: myMailDict = self.game.myEmpire['mailBox'] serverResult = self.game.server.getMailUpdate(self.game.authKey, myMailDict.keys()) if type(serverResult) == types.StringType: self.modeMsgBox(serverResult) else: for key, value in serverResult.iteritems(): myMailDict[key] = value except: self.modeMsgBox('getMailUpdate->Connection to Server Lost') def getGalaxyUpdate(self, listAttr): """Ask the Server for updated Galaxy info""" try: serverResult = self.game.server.getGalaxyUpdate(listAttr, self.game.authKey) if type(serverResult) == types.StringType: self.modeMsgBox(serverResult) else: for key, value in serverResult.iteritems(): self.game.myGalaxy[key] = value except: self.modeMsgBox('getGalaxyUpdate->Connection to Server Lost') def getSystemUpdate(self, listAttr, systemID): """Ask the Server for updated System info""" try: serverResult = self.game.server.getSystemUpdate(listAttr, systemID, self.game.authKey) if type(serverResult) == types.StringType: self.modeMsgBox(serverResult) else: mySystemDict = self.game.allSystems[systemID] for key, value in serverResult.iteritems(): mySystemDict[key] = value except: self.modeMsgBox('getSystemUpdate->Connection to Server Lost') def enterMode(self): """Enter the mode.""" self.alive = 1 self.setShortcuts() def setShortcuts(self): """Set the default mode shortcuts""" self.game.app.accept('mouse1', self.onMouse1Down) self.game.app.accept('mouse3', self.onMouse2Down) self.game.app.accept('space', self.onSpaceBarClear) if self.enableMouseCamControl == 1: self.game.app.accept('wheel_up', self.onMouseWheelUp) self.game.app.accept('wheel_down', self.onMouseWheelDown) def exitMode(self): """Exit the mode""" self.removeMySims() self.removeAllGui() self.game.app.ignoreAll() self.removeAllTasks() self.alive = 0 def removeAllTasks(self): """Remove and Stop any tasks running""" for taskName in self.runningTasks: taskMgr.remove(taskName) def removeMySims(self): """Remove all sims in mode""" for sim in self.sims: try: sim.destroy() except: sim.removeNode() def removeAllGui(self): """Remove all DirectGUI""" for gui in self.gui: gui.destroy() def setPlanePickable(self, obj, dictName): """Set the plane model itself to be collideable with the mouse ray""" obj.sim.reparentTo(self.selectable) obj.sim.find('**/pPlane1').node().setIntoCollideMask(BitMask32.bit(1)) obj.sim.find('**/pPlane1').node().setTag(dictName, obj.id) def setSpherePickable(self, obj, dictName): """Set the sphere model itself to be collideable with the mouse ray""" obj.sim.reparentTo(self.selectable) obj.sim.find('**/pSphere1').node().setIntoCollideMask(BitMask32.bit(1)) obj.sim.find('**/pSphere1').node().setTag(dictName, obj.id) def setMySelector(self, x, y, z, scale): """Show selector if it is not in current position else return false""" selectorPos = (self.selector.getX(), self.selector.getY(), self.selector.getZ()) if selectorPos != (x,y,z): self.selector.setPos(x,y,z) self.selector.show() self.selector.setScale(scale) return 1 else: self.selector.setPos(-1,-1,-1) return 0 #self.enableScrollWheelZoom = 0 def getListButton(self, id, myScrolledList): """Return Button selected from buttonList gui based on id""" for button in myScrolledList.buttonsList: if button['extraArgs'][1] == id: return button def setMySelector2(self, x, y, z, scale): """Show selector2 if it is not in current position else return false""" selectorPos = (self.selector2.getX(), self.selector2.getY(), self.selector2.getZ()) if selectorPos != (x,y,z): self.selector2.setPos(x,y,z) self.selector2.show() self.selector2.setScale(scale) return 1 else: self.selector2.setPos(-1,-1,-1) return 0 #self.enableScrollWheelZoom = 0 def hideMySelector(self): """Hide the selector, move its position""" self.selector.setPos(-1,-1,-1) self.selector.hide() if self.selector2 != None: self.selector2.hide() def onMouse1Down(self): """Allow dynamic picking of an object within mode""" #Check to see if we can access the mouse. We need it to do anything else if base.mouseWatcherNode.hasMouse(): #get the mouse position mpos = base.mouseWatcherNode.getMouse() #Set the position of the ray based on the mouse position self.pickerRay.setFromLens(base.camNode, mpos.getX(), mpos.getY()) #Do the actual collision pass (Do it only on the selectable for #efficiency purposes) self.picker.traverse(self.selectable) if self.pq.getNumEntries() > 0: #if we have hit something, sort the hits so that the closest #is first, and highlight that node self.pq.sortEntries() for selectable in self.selectTypes: name = self.pq.getEntry(0).getIntoNode().getTag(selectable) if name != '': self.clearAnyGui() mySelectedDict = getattr(self, selectable) mySelected = mySelectedDict[name] myMethod = getattr(self, '%sSelected' % selectable) if self.validateSelection(): myMethod(mySelected) break def onMouseWheelUp(self): """ zoom out """ if self.enableScrollWheelZoom: self.stopCameraTasks() self.zoomInCameraAmount(20.0) def onMouseWheelDown(self): """ zoom in """ if self.enableScrollWheelZoom: self.stopCameraTasks() self.zoomOutCameraAmount(20.0) def onMouse2Down(self): """clear""" self.onSpaceBarClear() def onSpaceBarClear(self): """Space bar should reset the view in the mode""" if self.validateSelection(): self.resetCamera() self.clearMouseSelection() self.zoomOutCamera() self.setShortcuts() self.enableScrollWheelZoom = 1 def clearMouseSelection(self): """Clear mouse selection before selecting something new""" pass def clearAnyGui(self): pass def update(self, interval): """update the mode, return the status, 0 means stop game""" return self.alive def setMyBackground(self): """Set the Background of mode""" base.setBackgroundColor(globals.colors['guiblue3']) def setEmpireDefaults(self, clientKey): """Read the defaults currently set and change them in the database""" try: # setup attributes to send to server defaults = ['viewIndustry', 'viewMilitary', 'viewResources', 'viewTradeRoutes'] d = {} for item in defaults: d[item] = self.game.myEmpire[item] serverResult = self.game.server.setEmpire(clientKey, d) if serverResult == 1: print 'Setup Empire Defaults Success' else: self.modeMsgBox(serverResult) except: self.modeMsgBox('SetEmpireDefaults->Connection to Server Lost, Login Again') def setEmpireValues(self, dValues): """Update Empire with d = key: empire attribute name, value = new value""" try: serverResult = self.game.server.setEmpire(self.game.authKey, dValues) if serverResult == 1: for key, value in dValues.iteritems(): self.game.myEmpire[key] = value print 'Empire Update Success' else: self.modeMsgBox(serverResult) except: self.modeMsgBox('setEmpireValues->Connection to Server Lost, Login Again') def setLogout(self, clientKey): """Send a Logout Request to the Server""" try: serverResult = self.game.server.logout(clientKey) if serverResult == 1: print 'Logout Successful, Exit Program' else: self.modeMsgBox(serverResult) except: self.modeMsgBox('setLogout->Connection to Server Lost, Login Again') def submitDesign(self, name): """Take Ship Design and submit it to Server for verification and storage""" (oldName, hullID, compDict, weaponDict) = self.myShipDesign.getMyDesign() dOrder = {'name':name, 'hullID':hullID, 'compDict':compDict, 'weaponDict':weaponDict} try: serverResult = self.game.server.addShipDesign(self.game.authKey, dOrder) if type(serverResult) == types.StringType: self.modeMsgBox(serverResult) else: # design has been accepted by server, retrieve design ID and add to client (ID,name) = serverResult self.game.shipDesigns[ID] = (name, hullID, compDict, weaponDict) self.getEmpireUpdate(['designsLeft']) except: self.modeMsgBox('submitDesign->Connection to Server Lost, Login Again') def destroyTempFrames(self): """Destroy any Temp Frames""" for frame in self.tempFrames: frame.destroy() self.tempFrames = [] def modeMsgBox(self, messageText): """Create a message for the user""" self.createMessage(messageText) def createMessage(self, text): """Create a new message for user""" myMessage = fadingtext.FadingText(self.guiMediaPath, text, self.messagePositions) self.messagePositions.append(myMessage.getMyPosition()) self.playSound('beep03') def writeToScreen(self, myText, x, z, scale=0.2, color='default', font=3, wordwrap=10): if color == 'default': color = Vec4(.1,.1,.8,.8) text = textonscreen.TextOnScreen(self.guiMediaPath, myText, scale,font=3) text.writeTextToScreen(x, self.depth, z, wordwrap=wordwrap) text.setColor(color) self.gui.append(text) def loadObject(self, tex=None, pos='default', depth=55, scale=1, transparency=True, parent='cam', model='plane', glow=0): if pos == 'default': pos = Point2(0,0) if parent == 'cam': parent = camera scaleX = 187.5 scaleZ = 117.1875 obj = loader.loadModelCopy('%s%s' % (self.guiMediaPath, model)) #default object uses the plane model if parent: obj.reparentTo(parent) #Everything is parented to the camera so #that it faces the screen obj.setPos(Point3(pos.getX(), depth, pos.getY())) #Set initial position obj.setSx(scaleX) obj.setSz(scaleZ) obj.setBin("unsorted", 0) #This tells Panda not to worry about the #order this is drawn in. (it prevents an #effect known as z-fighting) if transparency: obj.setTransparency(1) #All of our objects are trasnparent if tex: tex = loader.loadTexture('%s%s.png' % (self.guiMediaPath, tex)) #Load the texture obj.setTexture(tex, 1) #Set the texture self.sims.append(obj) obj.setShaderInput('glow',Vec4(glow,0,0,0),glow) return obj def onEntryFocus(self): """When a text Entry is in focus disable all shortcut keys""" for gui in self.gui: if gui.__module__ in self.entryFocusList: gui.ignoreShortcuts() def onEntryOutFocus(self): """When an text Entry is out of focus enable all shortcut keys""" for gui in self.gui: if gui.__module__ in self.entryFocusList: gui.setShortcuts()
class AreaMapper(object): def __init__(self, environment): ''' Create a map of the free space in a given area. ''' self.csRadius = 1 self.csHeight = 2 self.avatarRadius = 1.4 self.cs = CollisionSphere(0, 0, 0, 1) self.csNode = CollisionNode("AreaMapperCollisionSphere") self.csNode.setFromCollideMask(OTPGlobals.WallBitmask) self.csNode.setIntoCollideMask(BitMask32.allOff()) self.csNode.addSolid(self.cs) self.environment = environment self.csNodePath = self.environment.getTop().attachNewNode(self.csNode) self.floorRay = CollisionRay() self.floorRay.setDirection(0, 0, -1) self.floorRay.setOrigin(0, 0, 0) self.floorRayNode = CollisionNode("AreaMapperFloorRay") self.floorRayNode.setFromCollideMask(OTPGlobals.FloorBitmask) self.floorRayNode.setIntoCollideMask(BitMask32.allOff()) self.floorRayNode.addSolid(self.floorRay) self.floorRayNodePath = self.environment.getTop().attachNewNode( self.floorRayNode) self.chq = CollisionHandlerQueue() self.traverser = CollisionTraverser() self.startX = 0 self.startY = 0 self.startZ = 0 self.frontierSquares = {(0, 0): 1} self.frontierSquaresQueue = [(0, 0)] self.walkableSquares = {(0, 0): 1} self.blockedSquares = {} self.setSquareSize(2) self.startAtPlayerSpawn() self.visGeom = None self.visGN = None self.visNodePath = None self.triVertexLookup = {} self.triList = [] self.minX = 500 self.maxX = -500 self.minY = 500 self.maxY = -500 self.quadTree = QuadTree(width=1024) self.squares = [] self.runDiscovery(100000) self._subdivide() #self._fixZValues() self.csNodePath.removeNode() self.floorRayNodePath.removeNode() ## def _unstashEnvironment(self): ## # Would be nice if we could just do this :( ## #for np in self.environment.findAllMatches("**/+CollisionNode;+s"): ## # np.unstash() ## b = self.environment.builder ## for s in b.sections.values(): ## s.unstash() ## for o in b.largeObjects.values(): ## o.unstash() def setStart(self, x, y): self.startX = x self.startY = y self.startZ = self.findFloor(x, y) def startAtLocalAvatar(self): startPos = localAvatar.getPos(self.environment) self.setStart(startPos.getX(), startPos.getY()) def startAtPlayerSpawn(self): # XXX Bleugh, this is really pirates-specific. Nasty. for spawnPt in self.environment.world.getAllPlayerSpawnPts(): parentDoId = self.environment.world.uidMgr.getDoId(spawnPt[1]) if parentDoId == self.environment.doId: # Sweet, we found a spawn point for this grid's gamearea. Use it! z = self.findFloor(spawnPt[0][0], spawnPt[0][1]) if not self.isSphereBlocked(spawnPt[0][0], spawnPt[0][1], z): self.setStart(spawnPt[0][0], spawnPt[0][1]) return raise "No player spawn points found for the given game area! D:" def setSquareSize(self, size): self.squareSize = size self.csRadius = math.sqrt( 2 * (self.squareSize * self.squareSize / 4)) + self.avatarRadius self.csNodePath.setScale(self.environment, self.csRadius, self.csRadius, self.csRadius) self.csHeight = self.csRadius * 2 def findFloor(self, x, y): self.floorRayNodePath.setPos(self.environment, x, y, 50000) self.chq.clearEntries() self.traverser.clearColliders() self.traverser.addCollider(self.floorRayNodePath, self.chq) self.traverser.traverse(self.environment) highestZ = -50000 for e in self.chq.getEntries(): assert e.hasInto() assert e.getInto().isTangible() assert e.hasSurfacePoint() z = e.getSurfacePoint(self.environment).getZ() if z > highestZ: highestZ = z return highestZ def isSphereBlocked(self, x, y, z): if z < self.csHeight: return True self.csNodePath.setPos(self.environment, x, y, z) self.chq.clearEntries() self.traverser.clearColliders() self.traverser.addCollider(self.csNodePath, self.chq) self.traverser.traverse(self.environment) for entry in self.chq.getEntries(): if entry.hasInto(): if entry.getInto().isTangible(): return True return False def _neighbors(self, x, y): return [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)] def _explore(self, p): x, y = p x1 = self.startX + self.squareSize * x y1 = self.startY + self.squareSize * y z1 = self.findFloor(x1, y1) if self.isSphereBlocked(x1, y1, z1 + self.csHeight): self.blockedSquares[p] = z1 return else: self.walkableSquares[p] = z1 self.quadTree.fill(x, y) for n in self._neighbors(x, y): if not (n in self.frontierSquares or n in self.walkableSquares or n in self.blockedSquares): self.frontierSquares[n] = 1 self.frontierSquaresQueue.append(n) def _exploreFrontier(self): if len(self.frontierSquaresQueue) == 0: assert len(self.frontierSquares.keys()) == 0 return 0 else: qlen = len(self.frontierSquaresQueue) for i in xrange(qlen): p = self.frontierSquaresQueue.pop(0) del self.frontierSquares[p] self._explore(p) return qlen def runDiscovery(self, maxSquares): print "Discovering walkable space (this will take 30-60 seconds)..." #self._unstashEnvironment() squaresExplored = 1 self.walkableSquares[(0, 0)] = self.findFloor(self.startX, self.startY) while (squaresExplored < maxSquares) and (len( self.frontierSquaresQueue) > 0): squaresExplored += self._exploreFrontier() ## def visualize(self): ## gFormat = GeomVertexFormat.getV3cp() ## self.vertexData = GeomVertexData("OMGVERTEXDATA", gFormat, Geom.UHDynamic) ## self.vertexWriter = GeomVertexWriter(self.vertexData, "vertex") ## self.colorWriter = GeomVertexWriter(self.vertexData, "color") ## numVerts = 0 ## for xa,ya,xb,yb in self.squares: ## x1 = self.startX + self.squareSize*(xa) - self.squareSize*0.5 ## y1 = self.startY + self.squareSize*(ya) - self.squareSize*0.5 ## x2 = self.startX + self.squareSize*(xb) + self.squareSize*0.5 ## y2 = self.startY + self.squareSize*(yb) + self.squareSize*0.5 ## self.vertexWriter.addData3f(x1,y1,self.findFloor(x1,y1)+0.1) ## self.colorWriter.addData4f(0.0, 1.0, 0.0, 0.5) ## self.vertexWriter.addData3f(x2,y1,self.findFloor(x2,y1)+0.1) ## self.colorWriter.addData4f(0.0, 1.0, 0.0, 0.5) ## self.vertexWriter.addData3f(x2,y2,self.findFloor(x2,y2)+0.1) ## self.colorWriter.addData4f(0.0, 1.0, 0.0, 0.5) ## self.vertexWriter.addData3f(x1,y2,self.findFloor(x1,y2)+0.1) ## self.colorWriter.addData4f(0.0, 1.0, 0.0, 0.5) ## numVerts += 4 ## print "NUMVERTS: ", numVerts ## self.pointVis = GeomLinestrips(Geom.UHStatic) ## for i in xrange(numVerts/4): ## self.pointVis.addVertex(i*4) ## self.pointVis.addVertex(i*4+1) ## self.pointVis.addVertex(i*4+2) ## self.pointVis.addVertex(i*4+3) ## self.pointVis.addVertex(i*4) ## self.pointVis.closePrimitive() ## self.visGeom = Geom(self.vertexData) ## self.visGeom.addPrimitive(self.pointVis) ## self.visGN = GeomNode("NavigationGridVis") ## self.visGN.addGeom(self.visGeom) ## self.visNodePath = self.environment.attachNewNode(self.visGN) ## self.visNodePath.setTwoSided(True) ## self.visNodePath.setRenderModeThickness(4) ## #self.visNodePath.setTransparency(1) # ---------- Begin Triangulation Code ------------ ## def _addTriVertex(self,x,y): ## ''' ## lookup[(x,y)] is a reference to the vert located to the UPPER-LEFT of grid square (x,y) ## ''' ## if (x,y) not in self.gridCoordToVertexId: ## vId = self.vertexCounter ## self.vertexCounter += 1 ## self.gridCoordToVertexId[(x,y)] = vId ## x1 = self.startX + self.squareSize*x - (0.5 * self.squareSize) ## y1 = self.startY + self.squareSize*y - (0.5 * self.squareSize) ## z1 = self.findFloor(x1,y1) ## self.vertexIdToXYZ[vId] = (x1,y1,z1) ## self.vertexToTris[vId] = [] ## return self.gridCoordToVertexId[(x,y)] ## def _triangulateGridSquare(self,x,y,left=True): ## a = self._addTriVertex(x,y) ## b = self._addTriVertex(x+1,y) ## c = self._addTriVertex(x+1,y+1) ## d = self._addTriVertex(x,y+1) ## if x < self.minX: ## self.minX = x ## if x > self.maxX: ## self.maxX = x ## if y < self.minY: ## self.minY = y ## if y > self.maxY: ## self.maxY = y ## if left: ## self.triToVertices[self.triCounter] = [a,b,d] ## self.triToAngles[self.triCounter] = [90,45,45] ## self.triToVertices[self.triCounter+1] = [b,c,d] ## self.triToAngles[self.triCounter+1] = [45,90,45] ## self.vertexToTris[a].append(self.triCounter) ## self.vertexToTris[b].append(self.triCounter) ## self.vertexToTris[b].append(self.triCounter+1) ## self.vertexToTris[c].append(self.triCounter+1) ## self.vertexToTris[d].append(self.triCounter) ## self.vertexToTris[d].append(self.triCounter+1) ## else: ## self.triToVertices[self.triCounter] = [a,b,c] ## self.triToAngles[self.triCounter] = [45,90,45] ## self.triToVertices[self.triCounter+1] = [a,c,d] ## self.triToAngles[self.triCounter+1] = [45,45,90] ## self.vertexToTris[a].append(self.triCounter) ## self.vertexToTris[a].append(self.triCounter+1) ## self.vertexToTris[b].append(self.triCounter) ## self.vertexToTris[c].append(self.triCounter) ## self.vertexToTris[c].append(self.triCounter+1) ## self.vertexToTris[d].append(self.triCounter+1) ## self.triCounter += 2 ## def countCruft(self): ## count = 0 ## for s in self.squares: ## if (s[0] == s[2]) and (s[1] == s[3]): ## x = s[0] ## y = s[1] ## numNeighbors = 0 ## for (x1,y1) in [(x+1,y),(x-1,y),(x,y+1),(x,y-1)]: ## if (x1,y1) in self.walkableSquares: ## numNeighbors += 1 ## if numNeighbors < 3: ## count += 1 ## return count ## def killCruft(self): ## for i in xrange(len(self.squares)): ## s = self.squares[i] ## if (s[0] == s[2]) and (s[1] == s[3]): ## x = s[0] ## y = s[1] ## numNeighbors = 0 ## for (x1,y1) in [(x+1,y),(x-1,y),(x,y+1),(x,y-1)]: ## if (x1,y1) in self.walkableSquares: ## numNeighbors += 1 ## if numNeighbors < 3: ## self.squares[i] = None ## self.squares = [s for s in self.squares if s != None] def _addVertexByGridCoords(self, x, y): ''' lookup[(x,y)] is a reference to the vert located at (-0.5,-0.5) from grid square (x,y) ''' if (x, y) not in self.gridCoordToVertexId: vId = self.vertexCounter self.vertexCounter += 1 self.gridCoordToVertexId[(x, y)] = vId x1 = self.startX + self.squareSize * x - (0.5 * self.squareSize) y1 = self.startY + self.squareSize * y - (0.5 * self.squareSize) z1 = self.findFloor(x1, y1) self.vertexIdToXYZ[vId] = (x1, y1, z1) self.vertToPolys[vId] = [] return self.gridCoordToVertexId[(x, y)] def _addOpenSquare(self, gridX1, gridY1, gridX2, gridY2): curSpot = [gridX1, gridY1] verts = [] angles = [] while curSpot[0] <= gridX2: verts.append(self._addVertexByGridCoords(curSpot[0], curSpot[1])) if curSpot[0] == gridX1: angles.append(90) else: angles.append(180) self.vertToPolys[verts[-1]].append(self.polyCounter) curSpot[0] += 1 while curSpot[1] <= gridY2: verts.append(self._addVertexByGridCoords(curSpot[0], curSpot[1])) if curSpot[1] == gridY1: angles.append(90) else: angles.append(180) self.vertToPolys[verts[-1]].append(self.polyCounter) curSpot[1] += 1 while curSpot[0] > gridX1: verts.append(self._addVertexByGridCoords(curSpot[0], curSpot[1])) if curSpot[0] == gridX2 + 1: angles.append(90) else: angles.append(180) self.vertToPolys[verts[-1]].append(self.polyCounter) curSpot[0] -= 1 while curSpot[1] > gridY1: if curSpot[1] == gridY2 + 1: angles.append(90) else: angles.append(180) verts.append(self._addVertexByGridCoords(curSpot[0], curSpot[1])) self.vertToPolys[verts[-1]].append(self.polyCounter) curSpot[1] -= 1 self.polyToVerts[self.polyCounter] = verts self.polyToAngles[self.polyCounter] = angles self.polyCounter += 1 def _subdivide(self): print "Growing squares..." self.vertexCounter = 0 self.polyCounter = 0 self.gridCoordToVertexId = {} self.vertexIdToXYZ = {} self.polyToVerts = {} self.polyToAngles = {} self.vertToPolys = {} self.squares = self.quadTree.squarify() for (gridX1, gridY1, gridX2, gridY2) in self.squares: self._addOpenSquare(gridX1, gridY1, gridX2, gridY2)
class NavMesh(object): notify = directNotify.newCategory("NavMesh") def __init__(self, filepath=None, filename=None): if filename is not None: self._initFromFilename(filepath, filename) def initFromPolyData(self, polyToVerts, vertToPolys, polyToAngles, vertexCoords, environmentHash): ''' Initialize the mesh from a set of polygons. polyToVerts: Dictionary mapping a polygon ID to a set of N vertex IDs vertToPolys: Dictionary mapping a vertex ID to a set of poly IDs (of every poly that includes it) polyToAngles: Dictionary mapping a polygon ID to a set of N angles (in vertex order) vertexCoords: Dictionary mapping a vertex ID to the coordinates of the vertex in worldspace environmentHash: Hash value derived from the same collision geometry as the other arguments. See AreaMapper.getEnvironmentHash(). ''' self.polyToVerts = polyToVerts self.vertToPolys = vertToPolys self.polyToAngles = polyToAngles self.vertexCoords = vertexCoords self.environmentHash = environmentHash self.connectionLookup = {} self.connections = [] self._discoverInitialConnectivity() self.optimizeMesh() def visualize(self, parentNodePath, highlightVerts=[], pathVerts=[], visitedVerts=[]): ''' XXX Should move this into a product-specific class. ''' gFormat = GeomVertexFormat.getV3cp() self.visVertexData = GeomVertexData("OMGVERTEXDATA2", gFormat, Geom.UHDynamic) self.visVertexWriter = GeomVertexWriter(self.visVertexData, "vertex") self.visVertexColorWriter = GeomVertexWriter(self.visVertexData, "color") vertToWriterIndex = {} currIndex = 0 for v in self.vertexCoords.keys(): vertToWriterIndex[v] = currIndex x = self.vertexCoords[v][0] y = self.vertexCoords[v][1] z = self.vertexCoords[v][2] self.visVertexWriter.addData3f(x, y, z + 0.5) if v in highlightVerts: self.visVertexColorWriter.addData4f(1.0, 0.0, 0.0, 1.0) elif v in visitedVerts: self.visVertexColorWriter.addData4f(0.0, 0.0, 1.0, 1.0) else: self.visVertexColorWriter.addData4f(1.0, 1.0, 0.0, 1.0) currIndex += 1 pathOffsetIntoIndex = currIndex for v in pathVerts: self.visVertexWriter.addData3f(v[0], v[1], v[2] + 0.5) self.visVertexColorWriter.addData4f(0.0, 1.0, 0.0, 1.0) currIndex += 1 lines = GeomLinestrips(Geom.UHStatic) for p in self.polyToVerts.keys(): for v in self.polyToVerts[p]: lines.addVertex(vertToWriterIndex[v]) lines.addVertex(vertToWriterIndex[self.polyToVerts[p][0]]) lines.closePrimitive() if len(pathVerts) > 0: for i in xrange(len(pathVerts)): lines.addVertex(pathOffsetIntoIndex + i) lines.closePrimitive() self.visGeom = Geom(self.visVertexData) self.visGeom.addPrimitive(lines) self.visGN = GeomNode("NavMeshVis") self.visGN.addGeom(self.visGeom) self.visNodePath = parentNodePath.attachNewNode(self.visGN) self.visNodePath.setTwoSided(True) def _discoverInitialConnectivity(self): print "Building initial connectivity graph..." for pId in self.polyToVerts.keys(): verts = self.polyToVerts[pId] numVerts = len(verts) candidates = [] neighborPolys = [] for v in verts: candidates += [ p for p in self.vertToPolys[v] if (p not in candidates) and (p != pId) ] for vNum in xrange(numVerts): neighbor = [p for p in candidates if ((verts[vNum] in self.polyToVerts[p]) and \ (verts[(vNum+1)%numVerts] in self.polyToVerts[p]))] if len(neighbor) == 0: neighborPolys.append(None) elif len(neighbor) == 1: neighborPolys.append(neighbor[0]) else: raise "Two neighbors found for the same edge?!?!" self.connectionLookup[pId] = neighborPolys # --------- Begin stitching code --------- def _attemptToMergePolys(self, polyA, polyB): newVerts = [] newAngles = [] newConnections = [] vertsA = self.polyToVerts[polyA] vertsB = self.polyToVerts[polyB] lenA = len(vertsA) lenB = len(vertsB) anglesA = self.polyToAngles[polyA] anglesB = self.polyToAngles[polyB] sharedVerts = [v for v in vertsA if (v in vertsB)] locA = 0 while vertsA[locA] not in sharedVerts: locA += 1 while vertsA[locA] in sharedVerts: locA = (locA - 1) % lenA locA = (locA + 1) % lenA CCWmost = vertsA[locA] CCWmostLocA = locA while vertsA[locA] in sharedVerts: locA = (locA + 1) % lenA locA = (locA - 1) % lenA CWmost = vertsA[locA] CWmostLocA = locA # Convexity Check. # Verify that removing the edge preserves convexity and bail out if not. locA = 0 locB = 0 while vertsA[locA] != CCWmost: locA += 1 while vertsB[locB] != CCWmost: locB += 1 CCWmostAngleSum = anglesA[locA] + anglesB[locB] CCWmostLocB = locB if CCWmostAngleSum > 180: return False locA = 0 locB = 0 while vertsA[locA] != CWmost: locA += 1 while vertsB[locB] != CWmost: locB += 1 CWmostAngleSum = anglesA[locA] + anglesB[locB] if CWmostAngleSum > 180: return False # We've found the CW-most vert of the shared edge. # Now walk A clockwise until we hit the CCW-most vert of the shared edge. newVerts.append(CWmost) newAngles.append(CWmostAngleSum) newConnections.append(self.connectionLookup[polyA][locA]) locA = (locA + 1) % lenA while vertsA[locA] != CCWmost: newVerts.append(vertsA[locA]) newAngles.append(anglesA[locA]) newConnections.append(self.connectionLookup[polyA][locA]) locA = (locA + 1) % lenA # Now we've hit the CCW-most vert of the shared edge. # Walk B clockwise until we get back to the CW-most vert of the shared edge. locB = CCWmostLocB newVerts.append(CCWmost) newAngles.append(CCWmostAngleSum) neighbor = self.connectionLookup[polyB][locB] newConnections.append(neighbor) if neighbor is not None: for i in xrange(len(self.connectionLookup[neighbor])): if self.connectionLookup[neighbor][i] == polyB: self.connectionLookup[neighbor][i] = polyA locB = (locB + 1) % lenB while vertsB[locB] != CWmost: newVerts.append(vertsB[locB]) newAngles.append(anglesB[locB]) neighbor = self.connectionLookup[polyB][locB] newConnections.append(neighbor) if neighbor is not None: for i in xrange(len(self.connectionLookup[neighbor])): if self.connectionLookup[neighbor][i] == polyB: self.connectionLookup[neighbor][i] = polyA locB = (locB + 1) % lenB # We've added every vertex, its proper angle, and connectivity info # to the new polygon. Now replace A with the new guy and remove B. self.polyToVerts[polyA] = newVerts self.polyToAngles[polyA] = newAngles self.connectionLookup[polyA] = newConnections # Make sure we have vertex->poly pointers for all the new verts we added to A. for v in newVerts: if polyA not in self.vertToPolys[v]: self.vertToPolys[v].append(polyA) # Clean up all of B's old vertices. for v in vertsB: self.vertToPolys[v].remove(polyB) if len(self.vertToPolys[v]) == 0: # No one's using this vertex anymore, remove it del self.vertToPolys[v] del self.vertexCoords[v] del self.polyToVerts[polyB] del self.polyToAngles[polyB] del self.connectionLookup[polyB] return True def _attemptToGrowPoly(self, pId): for neighbor in self.connectionLookup.get(pId, []): if (neighbor is not None) and self._attemptToMergePolys( pId, neighbor): return True return False def _growEachPolyOnce(self): grewAtLeastOne = False for pId in self.connectionLookup.keys(): if self._attemptToGrowPoly(pId): grewAtLeastOne = True return grewAtLeastOne def optimizeMesh(self): ''' Takes a mesh that is already functionally complete and optimizes it for better performance. Reduces poly count and cuts out redundant vertices. Also compacts the polygon IDs into a contiguous range from 0 to N. No need to do the same for vertex IDs yet. ''' '''print "Stitching polygons: %s -> " % (len(self.polyToVerts)), orig = len(self.polyToVerts) numPasses = 1 while self._growEachPolyOnce(): print "%s -> " % (len(self.polyToVerts)), numPasses += 1 print "Done!\nPoly count reduced to %0.1f%% of original." % (len(self.polyToVerts)/float(orig)*100.0)''' self._pruneExtraVerts() self._compactPolyIds() self.numNodes = len(self.connections) biggest = 0 biggestPoly = -1 for p in self.polyToVerts: if len(self.polyToVerts[p]) > biggest: biggest = len(self.polyToVerts[p]) biggestPoly = p print "Most verts in a single poly: ", biggest assert biggest < 256 def _cleanPoly(self, polyId): verts = self.polyToVerts[polyId] angles = self.polyToAngles[polyId] neighbors = self.connectionLookup[polyId] numVerts = len(verts) newVerts = [] newAngles = [] newNeighbors = [] for i in xrange(numVerts): if (angles[i] != 180) or \ (len(self.vertToPolys.get(verts[i],[])) > 2) or \ (neighbors[i] != neighbors[(i-1)%numVerts]): # Keep vertex newVerts.append(verts[i]) newAngles.append(angles[i]) newNeighbors.append(neighbors[i]) else: # Remove vertex, this will happen twice so pop it self.vertToPolys.pop(verts[i], None) self.vertexCoords.pop(verts[i], None) if len(verts) != len(newVerts): self.polyToVerts[polyId] = newVerts self.polyToAngles[polyId] = newAngles self.connectionLookup[polyId] = newNeighbors assert len(newVerts) < 256 def _pruneExtraVerts(self): print "Pruning extra vertices..." print "Starting verts: %s" % len(self.vertToPolys) for polyId in self.connectionLookup.keys(): self._cleanPoly(polyId) print "Ending verts: %s" % len(self.vertToPolys) def _compactPolyIds(self): polyList = self.polyToVerts.keys() polyList.sort() oldToNewId = {None: None} newPolyToVerts = {} newPolyToAngles = {} self.connections = [] currId = 0 for oldId in polyList: oldToNewId[oldId] = currId self.connections.append([]) currId += 1 for oldId in polyList: newPolyToVerts[oldToNewId[oldId]] = self.polyToVerts[oldId] newPolyToAngles[oldToNewId[oldId]] = self.polyToAngles[oldId] #self.connections[oldToNewId[oldId]] = [] for edgeNum in xrange(len(self.connectionLookup[oldId])): self.connections[oldToNewId[oldId]].append( oldToNewId[self.connectionLookup[oldId][edgeNum]]) self.polyToVerts = newPolyToVerts self.polyToAngles = newPolyToAngles del self.connectionLookup # --------- Begin pathfinding code --------- def _findCentroid(self, polyId): verts = self.polyToVerts[polyId] numVerts = len(verts) x = 0 y = 0 z = 0 for v in verts: x += self.vertexCoords[v][0] y += self.vertexCoords[v][1] z += self.vertexCoords[v][2] x /= numVerts y /= numVerts z /= numVerts return (x, y, z) ## def _estimateDistanceBetweenPolys(self, polyA, polyB): ## centroidA = self._findCentroid(polyA) ## centroidB = self._findCentroid(polyB) ## dx = centroidA[0] - centroidB[0] ## dy = centroidA[1] - centroidB[1] ## dz = centroidA[2] - centroidB[2] ## return math.sqrt(dx*dx + dy*dy + dz*dz) def _walkToNeighbor(self, currPoly, neighborPoly): currVerts = self.polyToVerts[currPoly] neighborVerts = self.polyToVerts[neighborPoly] lenCurr = len(currVerts) sharedVerts = [v for v in currVerts if (v in neighborVerts)] loc = 0 while currVerts[loc] not in sharedVerts: loc += 1 while currVerts[loc] in sharedVerts: loc = (loc - 1) % lenCurr loc = (loc + 1) % lenCurr CCWmost = currVerts[loc] CCWmostLoc = loc while currVerts[loc] in sharedVerts: loc = (loc + 1) % lenCurr loc = (loc - 1) % lenCurr CWmost = currVerts[loc] CCWmostCoords = self.vertexCoords[CCWmost] CWmostCoords = self.vertexCoords[CWmost] # For now, walk to the midpoint of the connecting edge departingEdge = CCWmostLoc # Don't need this with goal->start search neighborsEdge = 0 while self.connections[neighborPoly][neighborsEdge] != currPoly: neighborsEdge += 1 return (neighborsEdge, ((CWmostCoords[0] + CCWmostCoords[0]) / 2.0, (CWmostCoords[1] + CCWmostCoords[1]) / 2.0, (CWmostCoords[2] + CCWmostCoords[2]) / 2.0)) ## def _remakePath(self,walkBack,currNode): ## if currNode in walkBack: ## p = self._remakePath(walkBack,walkBack[currNode]) ## return p + [currNode,] ## return [currNode,] ## def findRoute(self, startNode, goalNode): ## ''' ## So much love for A*. ## ''' ## nodeToF = {} ## nodeToG = {} ## nodeToH = {} ## walkBack = {} ## #nodeToEntryPoint = {} ## self.nodeToEntryPoint[startNode] = self._findCentroid(startNode) ## nodeToG[startNode] = 0 ## nodeToH[startNode] = self._estimateDistanceBetweenPolys(startNode,goalNode) ## nodeToF[startNode] = nodeToG[startNode] + nodeToH[startNode] ## closedSet = {} ## openSet = {} ## openQueue = PriQueue() # Priority = F score ## openSet[startNode] = 1 ## openQueue.push((nodeToF[startNode],startNode)) ## goalPoint = self._findCentroid(goalNode) ## while len(openSet) > 0: ## f,currNode = openQueue.pop(0) ## del openSet[currNode] ## self.aStarWasHere[currNode] = 1 ## if currNode == goalNode: ## return self._remakePath(walkBack,currNode) ## closedSet[currNode] = 1 ## currPoint = self.nodeToEntryPoint[currNode] ## for neighbor in self.connections[currNode]: ## if (neighbor is not None) and (neighbor not in closedSet): ## departingEdge,newEntryPoint = self._walkToNeighbor(currNode,currPoint,neighbor) ## newG = nodeToG[currNode] + math.sqrt((newEntryPoint[0] - currPoint[0])**2 + \ ## (newEntryPoint[1] - currPoint[1])**2 + \ ## (newEntryPoint[2] - currPoint[2])**2) ## gotHereFasterThanBefore = False ## if neighbor not in openSet: ## openSet[neighbor] = 1 ## gotHereFasterThanBefore = True ## elif newG < nodeToG[neighbor]: ## openQueue.remove((nodeToF[neighbor],neighbor)) ## gotHereFasterThanBefore = True ## if gotHereFasterThanBefore: ## walkBack[neighbor] = currNode ## self.nodeToEntryPoint[neighbor] = newEntryPoint ## nodeToH[neighbor] = math.sqrt((goalPoint[0] - newEntryPoint[0])**2 + \ ## (goalPoint[1] - newEntryPoint[1])**2 + \ ## (goalPoint[2] - newEntryPoint[2])**2) ## nodeToG[neighbor] = newG ## nodeToF[neighbor] = nodeToG[neighbor] + nodeToH[neighbor] ## openQueue.push((nodeToF[neighbor],neighbor)) ## raise "No path found! D:" def _findAllRoutesToGoal(self, goalNode): ''' Find the shortest path from ALL start nodes to the given goal node. (Djikstra) After running, self.pathData[startNode][goalNode] == outgoing edge from startNode to the next node for the given value of goalNode and ALL values of startNode. ''' nodeToG = {} walkBack = {} nodeDeparturePoint = {} nodeDeparturePoint[goalNode] = self._findCentroid(goalNode) nodeToG[goalNode] = 0 closedSet = {} openSet = {} openQueue = PriQueue() openSet[goalNode] = 1 openQueue.push((nodeToG[goalNode], goalNode)) walkBack[goalNode] = (0, goalNode) while len(openSet) > 0: f, currNode = openQueue.pop(0) del openSet[currNode] closedSet[currNode] = 1 currPoint = nodeDeparturePoint[currNode] for neighbor in self.connections[currNode]: if (neighbor is not None) and (neighbor not in closedSet): neighborsEdge, newPoint = self._walkToNeighbor( currNode, neighbor) newG = nodeToG[currNode] + math.sqrt((newPoint[0] - currPoint[0])**2 + \ (newPoint[1] - currPoint[1])**2 + \ (newPoint[2] - currPoint[2])**2) gotHereFasterThanBefore = False if neighbor not in openSet: openSet[neighbor] = 1 gotHereFasterThanBefore = True elif newG < nodeToG[neighbor]: openQueue.remove((nodeToG[neighbor], neighbor)) gotHereFasterThanBefore = True if gotHereFasterThanBefore: walkBack[neighbor] = (neighborsEdge, currNode) nodeDeparturePoint[neighbor] = newPoint nodeToG[neighbor] = newG openQueue.push((nodeToG[neighbor], neighbor)) for startNode in xrange(len(self.connections)): departingEdge = walkBack[startNode][0] assert self.pathData[startNode][goalNode] is None self.pathData[startNode][goalNode] = departingEdge def generatePathData(self, rowRange=None): ''' Entry point for path preprocessing. Solves all pairs shortest path for this mesh. Stores the result in self.pathData. SLOW. Expect 8-10 minutes on Port Royal alone. Currently runs Djikstra on every possible start node. There are faster approaches for APSP, but... ''' if rowRange is None: rowRange = (0, self.numNodes) self.initPathData() for goalNode in xrange(rowRange[0], rowRange[1]): self._findAllRoutesToGoal(goalNode) def createPathTable(self): ''' Takes a 2D array self.pathData and changes it in place. Each row is changed into a run-length encoded string. Then, feeds the data into a new PathTable instance. ''' for row in self.pathData: for val in row: if val == None: raise "Incomplete path data!" shortestPathLookup = self.pathData self.pathData = [] # Run-Length Encode the whole thing! for start in xrange(self.numNodes): row = [] lastVal = None nodesInRow = 0 for goal in xrange(self.numNodes): val = shortestPathLookup[start][goal] if val != lastVal: row.append([goal, val]) lastVal = val nodesInRow += 1 else: nodesInRow += 1 assert nodesInRow == self.numNodes stringsRow = [] # Convert row to a bytestring to save space for item in row: assert item[0] < 65536 assert item[1] < 256 stringsRow.append( chr(item[0] / 256) + chr(item[0] % 256) + chr(item[1])) assert len(stringsRow[-1]) == 3 rowString = string.join(stringsRow, "") self.pathData.append(rowString) self.pathTable = PathTable(self.pathData, self.connections) def printPathData(self): ''' Outputs the pickled path table to stdout. ''' import sys sys.stdout.write(pickle.dumps(self.pathData, protocol=0)) def initPathData(self): self.pathData = [] for i in xrange(self.numNodes): self.pathData.append([ None, ] * self.numNodes) def addPaths(self, partialData): for i in xrange(len(partialData)): for j in xrange(len(partialData[i])): if partialData[i][j] is not None: assert self.pathData[i][j] is None self.pathData[i][j] = partialData[i][j] ## def pathTableLookup(self, startNode, goalNode): ## ''' ## Look up the equivalent of pathData[goalNode][startNode] in our run-length encoded data. ## ''' ## if startNode >= self.numNodes or goalNode >= self.numNodes: ## raise "Invalid node ID. Must be less than self.numNodes (%s)." % self.numNodes ## str = self.pathData[startNode] ## pos = 0 ## while (pos < len(str)) and (256*ord(str[pos]) + ord(str[pos+1]) <= goalNode): ## #print pos, ": ",256*ord(str[pos]) + ord(str[pos+1]) ## pos += 3 ## pos -= 3 ## return ord(str[pos+2]) def findRoute(self, startNode, goalNode): ''' Returns the node-by-node route from startNode to goalNode. ''' return self.pathTable.findRoute(startNode, goalNode) def makeNodeLocator(self, environment): meshNode = CollisionNode("NavMeshNodeLocator") meshNode.setFromCollideMask(BitMask32.allOff()) meshNode.setIntoCollideMask(OTPGlobals.PathFindingBitmask) self.polyHashToPID = {} for pId in self.polyToAngles: vertCount = 0 corners = [] for angle in self.polyToAngles[pId]: if angle != 180: # It's a corner corners.append(vertCount) vertCount += 1 # XXX this code only works for square nodes at present # Unfortunately we can only make triangle or square CollisionPolygons on the fly assert len(corners) == 4 #import pdb #pdb.set_trace() verts = [] for vert in corners: verts.append( (self.vertexCoords[self.polyToVerts[pId][vert]][0], self.vertexCoords[self.polyToVerts[pId][vert]][1], 0)) #import pdb #pdb.set_trace() poly = CollisionPolygon(verts[0], verts[1], verts[2], verts[3]) assert poly not in self.polyHashToPID self.polyHashToPID[poly] = pId meshNode.addSolid(poly) ray = CollisionRay() ray.setDirection(0, 0, -1) ray.setOrigin(0, 0, 0) rayNode = CollisionNode("NavMeshRay") rayNode.setFromCollideMask(OTPGlobals.PathFindingBitmask) rayNode.setIntoCollideMask(BitMask32.allOff()) rayNode.addSolid(ray) self.meshNodePath = environment.attachNewNode(meshNode) self.rayNodePath = environment.attachNewNode(rayNode) self.meshNodePath.setTwoSided(True) self.chq = CollisionHandlerQueue() self.traverser = CollisionTraverser() self.traverser.addCollider(self.rayNodePath, self.chq) def findNodeFromPos(self, environment, x, y): self.rayNodePath.setPos(environment, x, y, 50000) self.chq.clearEntries() self.traverser.traverse(self.meshNodePath) if self.chq.getNumEntries() != 1: self.notify.warning("No node found at position: %s, %s in %s" % (x, y, environment)) return 0 e = self.chq.getEntry(0) assert e.hasInto() if not e.hasInto(): self.notify.warning("No into found for collision %s" % (e)) pId = self.polyHashToPID[e.getInto()] return pId # --------- Begin long-term storage code --------- def writeToFile(self, filename, storePathTable=True): ''' Output the contents of this mesh to the file specified. Saving to a file lets us avoid doing expensive precomputation every time a mesh instance is required. ''' if self.environmentHash is None: raise "Attempted write to file without valid environment hash!" if storePathTable and not self.pathData: raise "Attempted to write empty pathData. Call NavMesh.generatePathTable() first!" f = open(filename, 'wb') if storePathTable: pickle.dump([ self.environmentHash, self.polyToVerts, self.polyToAngles, self.vertexCoords, self.connections, self.pathData ], f, protocol=2) f.close() self.pathData = None else: pickle.dump([ self.environmentHash, self.polyToVerts, self.polyToAngles, self.vertexCoords, self.connections, None ], f, protocol=2) f.close() print "Successfully wrote to file %s." % filename def _initFromString(self, str): contents = pickle.loads(str) self.environmentHash = contents[0] self.polyToVerts = contents[1] self.polyToAngles = contents[2] self.vertexCoords = contents[3] self.connections = contents[4] self.pathData = contents[5] if self.pathData is not None: self.pathTable = PathTable(self.pathData, self.connections) self.pathData = None self.numNodes = len(self.connections) def _initFromFilename(self, filepath, filename): vfs = VirtualFileSystem.getGlobalPtr() filename = Filename(filename) searchPath = DSearchPath() #searchPath.appendDirectory(Filename('.')) #searchPath.appendDirectory(Filename('etc')) #searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('~'))) #searchPath.appendDirectory(Filename.fromOsSpecific(os.path.expandvars('$HOME'))) searchPath.appendDirectory( Filename.fromOsSpecific(os.path.expandvars(filepath))) found = vfs.resolveFilename(filename, searchPath) if not found: raise IOError, "File not found!" str = vfs.readFile(filename, 1) self._initFromString(str) def checkHash(self, envHash): ''' "Does this mesh represent the environment I think it does?" If this check fails, the mesh is out of date (or being used with the wrong environment). In either case, whoever generated this instance should discard it and create a new mesh from scratch. ''' return envHash == self.environmentHash
class ArcadeFlightGame(ShowBase): def __init__(self): ShowBase.__init__(self) self.debug = False self.maxdistance = 400 self.statusLabel = self.makeStatusLabel(0) self.collisionLabel = self.makeStatusLabel(1) self.player = AlliedFlanker(self.loader, self.render, self.taskMgr) self.world = GameWorld(1024, self.loader, self.render, self.camera) self.taskMgr.add(self.updateTask, "update") self.keyboardSetup() # performance and map to player so can't fly beyond visible terrain self.player.setMaxHeight(self.maxdistance) if self.debug == False: self.camLens.setFar(self.maxdistance) else: base.oobe() self.camLens.setFov(60) self.setupCollisions() self.textCounter = 0 def makeStatusLabel(self, i): """ Create a status label at the top-left of the screen, Parameter 'i' is the row number """ return OnscreenText(style=2, fg=(.5,1,.5,1), pos=(-1.3,0.92-(.08 * i)), \ align=TextNode.ALeft, scale = .08, mayChange = 1) def keyboardSetup(self): self.keyMap = {"left":0, "right":0, "climb":0, "fall":0, \ "accelerate":0, "decelerate":0, "fire":0} self.accept("escape", sys.exit) self.accept("a", self.setKey, ["accelerate", 1]) self.accept("a-up", self.setKey, ["accelerate", 0]) self.accept("z", self.setKey, ["decelerate", 1]) self.accept("z-up", self.setKey, ["decelerate", 0]) self.accept("arrow_left", self.setKey, ["left", 1]) self.accept("arrow_left-up", self.setKey, ["left", 0]) self.accept("arrow_right", self.setKey, ["right", 1]) self.accept("arrow_right-up", self.setKey, ["right", 0]) self.accept("arrow_down", self.setKey, ["climb", 1]) self.accept("arrow_down-up", self.setKey, ["climb", 0]) self.accept("arrow_up", self.setKey, ["fall", 1]) self.accept("arrow_up-up", self.setKey, ["fall", 0]) self.accept("space", self.setKey, ["fire", 1]) self.accept("space-up", self.setKey, ["fire", 0]) base.disableMouse() # or updateCamera will fail! def setKey(self, key, value): """ Used by keyboard setup """ self.keyMap[key] = value def setupCollisions(self): self.collTrav = CollisionTraverser() # rapid collisions detected using below plus FLUID pos self.collTrav.setRespectPrevTransform(True) self.playerGroundSphere = CollisionSphere(0, 1.5, -1.5, 1.5) self.playerGroundCol = CollisionNode('playerSphere') self.playerGroundCol.addSolid(self.playerGroundSphere) # bitmasks self.playerGroundCol.setFromCollideMask(BitMask32.bit(0)) self.playerGroundCol.setIntoCollideMask(BitMask32.allOff()) self.world.setGroundMask(BitMask32.bit(0)) self.world.setWaterMask(BitMask32.bit(0)) # and done self.playerGroundColNp = self.player.attach(self.playerGroundCol) self.playerGroundHandler = CollisionHandlerQueue() self.collTrav.addCollider(self.playerGroundColNp, self.playerGroundHandler) # DEBUG as per video: if (self.debug == True): self.playerGroundColNp.show() self.collTrav.showCollisions(self.render) def updateTask(self, task): """ Gets added to the task manager, updates the player, deals with inputs, collisions, game logic etc. """ self.player.calculate() self.actionInput() validMove = self.player.move(self.world.getSize()) # lets not be doing this every frame... if validMove == False and self.textCounter > 30: self.statusLabel.setText("STATUS: MAP END; TURN AROUND") elif self.textCounter > 30: self.statusLabel.setText("STATUS: OK") if self.textCounter > 30: self.textCounter = 0 else: self.textCounter = self.textCounter + 1 self.updateCamera() self.collTrav.traverse(self.render) for i in range(self.playerGroundHandler.getNumEntries()): entry = self.playerGroundHandler.getEntry(i) if (self.debug == True): self.collisionLabel.setText("DEAD:" + str(globalClock.getFrameTime())) self.player.die() return Task.cont def actionInput(self): """ Used by updateTask to process keyboard input """ if (self.keyMap["climb"] != 0): self.player.climb() elif (self.keyMap["fall"] != 0): self.player.dive() else: self.player.unwindVertical() if (self.keyMap["left"] != 0): self.player.bankLeft() elif (self.keyMap["right"] != 0): self.player.bankRight() else: self.player.unwindHorizontal() if (self.keyMap["accelerate"] != 0): self.player.accelerate() elif (self.keyMap["decelerate"] != 0): self.player.brake() def updateCamera(self): self.player.lookAtMe(self.camera)