def printTransform(self, other=None, sd=2, fRecursive=0): from panda3d.pandac import Vec3 fmtStr = '%%0.%df' % sd name = self.getName() if other == None: transform = self.getTransform() else: transform = self.getTransform(other) if transform.hasPos(): pos = transform.getPos() if not pos.almostEqual(Vec3(0)): outputString = '%s.setPos(%s, %s, %s)' % (name, fmtStr, fmtStr, fmtStr) print outputString % (pos[0], pos[1], pos[2]) if transform.hasHpr(): hpr = transform.getHpr() if not hpr.almostEqual(Vec3(0)): outputString = '%s.setHpr(%s, %s, %s)' % (name, fmtStr, fmtStr, fmtStr) print outputString % (hpr[0], hpr[1], hpr[2]) if transform.hasScale(): if transform.hasUniformScale(): scale = transform.getUniformScale() if scale != 1.0: outputString = '%s.setScale(%s)' % (name, fmtStr) print outputString % scale else: scale = transform.getScale() if not scale.almostEqual(Vec3(1)): outputString = '%s.setScale(%s, %s, %s)' % (name, fmtStr, fmtStr, fmtStr) print outputString % (scale[0], scale[1], scale[2]) if fRecursive: for child in self.getChildren(): child.printTransform(other, sd, fRecursive)
def regenTree(self): forest= render.findAllMatches("Tree Holder") forest.detach() bodydata=GeomVertexData("body vertices", self.format, Geom.UHStatic) treeNodePath=NodePath("Tree Holder") makeFractalTree(bodydata, treeNodePath,Vec3(4,4,7), Vec3(0,0,0),self.numIterations, self.numCopies) treeNodePath.setTexture(self.barkTexture,1) treeNodePath.reparentTo(render)
def addTree(self): bodydata=GeomVertexData("body vertices", self.format, Geom.UHStatic) randomPlace=Vec3(200*random.random()-100, 200*random.random()-100, 0) #randomPlace.normalize() treeNodePath=NodePath("Tree Holder") makeFractalTree(bodydata, treeNodePath,Vec3(4,4,7), randomPlace, self.numIterations, self.numCopies) treeNodePath.setTexture(self.barkTexture,1) treeNodePath.reparentTo(render)
def asteroidHit(self, index): #If the asteroid is small it is simply removed if self.asteroids[index].getScale().getX() <= AST_MIN_SCALE: self.asteroids[index].remove() #This uses a Python feature called slices. Basically it's saying #Make the list the current list up to index plus the rest of the list #after index #This has the effect of removing the object at index self.asteroids = self.asteroids[:index] + self.asteroids[index + 1:] else: #If it is big enough, split it instead #First we update the current asteroid newScale = self.asteroids[index].getScale().getX() * AST_SIZE_SCALE self.asteroids[index].setScale(newScale) #Rescale it #The new direction is chosen as perpendicular to the old direction #This is determined using the cross product, which returns a vector #perpendicular to the two input vectors. By crossing velocity with a #vector that goes into the screen, we get a vector that is perpendicular #to the original velocity in the plane of the screen vel = self.getVelocity(self.asteroids[index]) speed = vel.length() * AST_VEL_SCALE vel.normalize() vel = Vec3(0, 1, 0).cross(vel) vel *= speed self.setVelocity(self.asteroids[index], vel) #Now we create a new asteroid identical to the current one newAst = loadObject(scale=newScale) self.setVelocity(newAst, vel * -1) newAst.setPos(self.asteroids[index].getPos()) newAst.setTexture(self.asteroids[index].getTexture(), 1) self.asteroids.append(newAst)
def updateShip(self, dt): heading = self.ship.getR() #Heading is the roll value for this model #Change heading if left or right is being pressed if self.keys["turnRight"]: heading += dt * TURN_RATE self.ship.setR(heading % 360) elif self.keys["turnLeft"]: heading -= dt * TURN_RATE self.ship.setR(heading % 360) #Thrust causes acceleration in the direction the ship is currently facing if self.keys["accel"]: heading_rad = DEG_TO_RAD * heading #This builds a new velocity vector and adds it to the current one #Relative to the camera, the screen in Panda is the XZ plane. #Therefore all of our Y values in our velocities are 0 to signify no #change in that direction newVel = (Vec3(sin(heading_rad), 0, cos(heading_rad)) * ACCELERATION * dt) newVel += self.getVelocity(self.ship) #Clamps the new velocity to the maximum speed. lengthSquared() is used #again since it is faster than length() if newVel.lengthSquared() > MAX_VEL_SQ: newVel.normalize() newVel *= MAX_VEL self.setVelocity(self.ship, newVel) #Finally, update the position as with any other object self.updatePos(self.ship, dt)
def start(self): #The maze model also has a locator in it for where to start the ball #To access it we use the find command startPos = self.maze.find("**/start").getPos() self.ballRoot.setPos(startPos) #Set the ball in the starting position self.ballV = Vec3(0, 0, 0) #Initial velocity is 0 self.accelV = Vec3(0, 0, 0) #Initial acceleration is 0 #For a traverser to actually do collisions, you need to call #traverser.traverse() on a part of the scene. Fortunatly, base has a #task that does this for the entire scene once a frame. This sets up our #traverser as the one to be called automatically base.cTrav = self.cTrav #Create the movement task, but first make sure it is not already running taskMgr.remove("rollTask") self.mainLoop = taskMgr.add(self.rollTask, "rollTask") self.mainLoop.last = 0
def makeCircle(vdata, numVertices=40,offset=Vec3(0,0,0), direction=1): circleGeom=Geom(vdata) vertWriter=GeomVertexWriter(vdata, "vertex") normalWriter=GeomVertexWriter(vdata, "normal") colorWriter=GeomVertexWriter(vdata, "color") uvWriter=GeomVertexWriter(vdata, "texcoord") drawWriter=GeomVertexWriter(vdata, "drawFlag") #make sure we start at the end of the GeomVertexData so we dont overwrite anything #that might be there already startRow=vdata.getNumRows() vertWriter.setRow(startRow) colorWriter.setRow(startRow) uvWriter.setRow(startRow) normalWriter.setRow(startRow) drawWriter.setRow(startRow) angle=2*math.pi/numVertices currAngle=angle for i in range(numVertices): position=Vec3(math.cos(currAngle)+offset.getX(), math.sin(currAngle)+offset.getY(),offset.getZ()) vertWriter.addData3f(position) uvWriter.addData2f(position.getX()/2.0+0.5,position.getY()/2.0+0.5) colorWriter.addData4f(1.0, 1.0, 1.0, 1.0) position.setZ(position.getZ()*direction) position.normalize() normalWriter.addData3f(position) #at default Opengl only draws "front faces" (all shapes whose vertices are arranged CCW). We #need direction so we can specify which side we want to be the front face currAngle+=angle*direction circle=GeomTrifans(Geom.UHStatic) circle.addConsecutiveVertices(startRow, numVertices) circle.closePrimitive() circleGeom.addPrimitive(circle) return circleGeom
def getGridSizeFromSphere(self, sphereRadius, spherePos, cellWidth, gridRadius): # NOTE: This ensures that the grid is at least a "gridRadius" number # of cells larger than the trigger sphere that loads the grid. This # gives us some room to start setting interest to the grid before we # expect to see any objects on it. xMax = abs(spherePos[0])+sphereRadius yMax = abs(spherePos[1])+sphereRadius sphereRadius = Vec3(xMax,yMax,0).length() # sphereRadius = max(sphereRadius, gridRadius*cellWidth) return max(2 * (sphereRadius // cellWidth), 1)
def setupLights(self): #This function sets up some default lighting lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight( "ambientLight" ) ambientLight.setColor( Vec4(.8, .8, .8, 1) ) lAttrib = lAttrib.addLight( ambientLight ) directionalLight = DirectionalLight( "directionalLight" ) directionalLight.setDirection( Vec3( 0, 45, -45 ) ) directionalLight.setColor( Vec4( 0.2, 0.2, 0.2, 1 ) ) lAttrib = lAttrib.addLight( directionalLight ) render.attachNewNode( directionalLight ) render.attachNewNode( ambientLight ) render.node().setAttrib( lAttrib )
def setupLights(self): lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(.8, .8, .75, 1)) lAttrib = lAttrib.addLight(ambientLight) directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(0, 0, -2.5)) directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1)) lAttrib = lAttrib.addLight(directionalLight) render.attachNewNode(directionalLight) render.attachNewNode(ambientLight) render.node().setAttrib(lAttrib)
def setupLights(self): #Sets up some default lighting lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(.4, .4, .35, 1)) lAttrib = lAttrib.addLight(ambientLight) directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(0, 8, -2.5)) directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1)) lAttrib = lAttrib.addLight(directionalLight) render.attachNewNode(directionalLight) render.attachNewNode(ambientLight) render.node().setAttrib(lAttrib)
def makeCylinder(vdata,numVertices=40): topCircleGeom=makeCircle(vdata, numVertices,Vec3(0,0, 1)) bottomCircleGeom=makeCircle(vdata, numVertices,Vec3(0,0,0),-1) body=GeomTristrips(Geom.UHStatic) j=40 i=0 while i < numVertices+1: body.addVertex(i) body.addVertex(j) i+=1 if j==40: j=2*numVertices-1 else: j-=1 body.addVertex(i) body.addVertex(j) j-=1 i+=1 body.addVertex(numVertices-1) body.addVertex(0) body.addVertex(numVertices) body.closePrimitive() #print body cylinderGeom=Geom(vdata) cylinderGeom.addPrimitive(body) cylinderGeom.copyPrimitivesFrom(topCircleGeom) cylinderGeom.copyPrimitivesFrom(bottomCircleGeom) cylinderGeom.decomposeInPlace() cylinderGeom.unifyInPlace() return cylinderGeom
def setupLights(self): lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(.4, .4, .35, 1)) lAttrib = lAttrib.addLight(ambientLight) directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(0, 8, -2.5)) directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1)) lAttrib = lAttrib.addLight(directionalLight) #set lighting on teapot so steam doesn't get affected self.t.attachNewNode(directionalLight) self.t.attachNewNode(ambientLight) self.t.node().setAttrib(lAttrib)
def __init__(self): #This code puts the standard title and instruction text on screen self.title = OnscreenText(text="Panda3D: Tutorial - Tasks", style=1, fg=(1, 1, 0, 1), pos=(0.8, -0.95), scale=.07, font=font) self.escapeText = genLabelText("ESC: Quit", 0) self.leftkeyText = genLabelText("[Left Arrow]: Turn Left (CCW)", 1) self.rightkeyText = genLabelText("[Right Arrow]: Turn Right (CW)", 2) self.upkeyText = genLabelText("[Up Arrow]: Accelerate", 3) self.spacekeyText = genLabelText("[Space Bar]: Fire", 4) base.disableMouse() #Disable default mouse-based camera control self.bg = loadObject( "stars", scale=146, depth=200, transparency=False) #Load the background starfield self.ship = loadObject("ship") #Load the ship self.setVelocity(self.ship, Vec3(0, 0, 0)) #Initial velocity #A dictionary of what keys are currently being pressed #The key events update this list, and our task will query it as input self.keys = {"turnLeft": 0, "turnRight": 0, "accel": 0, "fire": 0} self.accept("escape", sys.exit) #Escape quits #Other keys events set the appropriate value in our key dictionary self.accept("arrow_left", self.setKey, ["turnLeft", 1]) self.accept("arrow_left-up", self.setKey, ["turnLeft", 0]) self.accept("arrow_right", self.setKey, ["turnRight", 1]) self.accept("arrow_right-up", self.setKey, ["turnRight", 0]) self.accept("arrow_up", self.setKey, ["accel", 1]) self.accept("arrow_up-up", self.setKey, ["accel", 0]) self.accept("space", self.setKey, ["fire", 1]) #Now we create the task. taskMgr is the task manager that actually calls #The function each frame. The add method creates a new task. The first #argument is the function to be called, and the second argument is the name #for the task. It returns a task object, that is passed to the function #each frame self.gameTask = taskMgr.add(self.gameLoop, "gameLoop") #The task object is a good place to put variables that should stay #persistant for the task function from frame to frame self.gameTask.last = 0 #Task time of the last frame self.gameTask.nextBullet = 0 #Task time when the next bullet may be fired self.bullets = [] #This empty list will contain fired bullets self.spawnAsteroids( ) #Complete initialization by spawning the asteroids
def fire(self, time): direction = DEG_TO_RAD * self.ship.getR() pos = self.ship.getPos() bullet = loadObject("bullet", scale=.2) #Create the object bullet.setPos(pos) #Velocity is in relation to the ship vel = (self.getVelocity(self.ship) + (Vec3(sin(direction), 0, cos(direction)) * BULLET_SPEED)) self.setVelocity(bullet, vel) #Set the bullet expiration time to be a certain amount past the current time self.setExpires(bullet, time + BULLET_LIFE) #Finally, add the new bullet to the list self.bullets.append(bullet)
def drawLeaf(nodePath,vdata,pos=Vec3(0,0,0),vecList=[Vec3(0,0,1), Vec3(1,0,0),Vec3(0,-1,0)], scale=0.125): #use the vectors that describe the direction the branch grows to make the right #rotation matrix newCs=Mat4(0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0) newCs.setRow(0, vecList[2]) #right newCs.setRow(1, vecList[1]) #up newCs.setRow(2, vecList[0]) #forward newCs.setRow(3, Vec3(0,0,0)) newCs.setCol(3,Vec4(0,0,0,1)) axisAdj=Mat4.scaleMat(scale)*newCs*Mat4.translateMat(pos) #orginlly made the leaf out of geometry but that didnt look good #I also think there should be a better way to handle the leaf texture other than #hardcoding the filename leafModel=loader.loadModel("models/samples/fractal_plants/shrubbery") leafTexture=loader.loadTexture("models/samples/fractal_plants/material-10-cl.png") leafModel.reparentTo(nodePath) leafModel.setTexture(leafTexture,1) leafModel.setTransform(TransformState.makeMat(axisAdj))
def makeFractalTree(bodydata, nodePath,length, pos=Vec3(0,0,0), numIterations=11, numCopies=4,vecList=[Vec3(0,0,1),Vec3(1,0,0), Vec3(0,-1,0)]): if numIterations>0: drawBody(nodePath, bodydata, pos, vecList, length.getX()) #move foward along the right axis newPos=pos+vecList[0]*length.length() #only branch every third level (sorta) if numIterations%3==0: #decrease dimensions when we branch length=Vec3(length.getX()/2, length.getY()/2, length.getZ()/1.1) for i in range(numCopies): makeFractalTree(bodydata, nodePath,length,newPos, numIterations-1, numCopies,randomAxis(vecList)) else: #just make another branch connected to this one with a small variation in direction makeFractalTree(bodydata, nodePath,length,newPos, numIterations-1,numCopies,smallRandomAxis(vecList)) else: drawBody(nodePath,bodydata, pos, vecList, length.getX(),False) drawLeaf(nodePath,bodydata, pos,vecList)
def setupLights(self): #Create some lights and add them to the scene. By setting the lights on #render they affect the entire scene #Check out the lighting tutorial for more information on lights lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(.4, .4, .35, 1)) lAttrib = lAttrib.addLight(ambientLight) directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(0, 8, -2.5)) directionalLight.setColor(Vec4(0.9, 0.8, 0.9, 1)) lAttrib = lAttrib.addLight(directionalLight) render.attachNewNode(directionalLight) render.attachNewNode(ambientLight) render.node().setAttrib(lAttrib) #Explicitly set the environment to not be lit lAttrib = LightAttrib.makeAllOff() self.env.node().setAttrib(lAttrib)
def addFirefly(self): pos1 = Point3(random.uniform(-50, 50), random.uniform(-100, 150), random.uniform(-10, 80)) dir = Vec3(random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(-1, 1)) dir.normalize() pos2 = pos1 + (dir * 20) fly = self.lightroot.attachNewNode(PandaNode("fly")) glow = fly.attachNewNode(PandaNode("glow")) dot = fly.attachNewNode(PandaNode("dot")) color_r = random.uniform(0.7, 1.0) color_g = 1.0 color_b = 0.8 fly.setShaderInput("lightcolor", color_r, color_g, color_b, 1.0) int1 = fly.posInterval(random.uniform(7, 12), pos1, pos2) int2 = fly.posInterval(random.uniform(7, 12), pos2, pos1) si1 = fly.scaleInterval(random.uniform(0.8, 1.5), Point3(0.2, 0.2, 0.2), Point3(0.2, 0.2, 0.2)) si2 = fly.scaleInterval(random.uniform(1.5, 0.8), Point3(1.0, 1.0, 1.0), Point3(0.2, 0.2, 0.2)) si3 = fly.scaleInterval(random.uniform(1.0, 2.0), Point3(0.2, 0.2, 0.2), Point3(1.0, 1.0, 1.0)) siseq = Sequence(si1, si2, si3) siseq.loop() siseq.setT(random.uniform(0, 1000)) seq = Sequence(int1, int2) seq.loop() self.spheremodel.instanceTo(glow) self.spheremodel.instanceTo(dot) glow.setScale(self.fireflysize * 1.1) glow.hide(BitMask32(self.modelMask | self.plainMask)) dot.setScale(0.6) dot.hide(BitMask32(self.modelMask | self.lightMask)) dot.setColor(color_r, color_g, color_b, 1.0) self.fireflies.append(fly) self.sequences.append(seq) self.glowspheres.append(glow) self.scaleseqs.append(siseq)
def __init__(self): formatArray=GeomVertexArrayFormat() formatArray.addColumn(InternalName.make("drawFlag"), 1, Geom.NTUint8, Geom.COther) format=GeomVertexFormat(GeomVertexFormat.getV3n3cpt2()) format.addArray(formatArray) self.format=GeomVertexFormat.registerFormat(format) bodydata=GeomVertexData("body vertices", format, Geom.UHStatic) self.barkTexture=loader.loadTexture( \ "models/samples/fractal_plants/bark.jpg") treeNodePath=NodePath("Tree Holder") makeFractalTree(bodydata,treeNodePath,Vec3(4,4,7)) treeNodePath.setTexture(self.barkTexture,1) treeNodePath.reparentTo(render) self.accept("q", self.regenTree) self.accept("w", self.addTree) self.accept("arrow_up", self.upIterations) self.accept("arrow_down", self.downIterations) self.accept("arrow_right", self.upCopies) self.accept("arrow_left", self.downCopies) self.numIterations=11 self.numCopies=4 self.upDownEvent = OnscreenText( text="Up/Down: Increase/Decrease the number of iterations ("+str(self.numIterations)+")", style=1, fg=(1,1,1,1), pos=(-1.3, 0.85), font = font, align=TextNode.ALeft, scale = .05, mayChange=True) self.leftRightEvent = OnscreenText( text="Left/Right: Increase/Decrease branching("+str(self.numCopies)+")", style=1, fg=(1,1,1,1), pos=(-1.3, 0.80), font = font, align=TextNode.ALeft, scale = .05, mayChange=True)
def spawnAsteroids(self): self.alive = True #Control variable for if the ship is alive self.asteroids = [] #List that will contain our asteroids for i in range(10): #This loads an asteroid. The texture chosen is random from "asteroid1" to #"asteroid3" self.asteroids.append( loadObject("asteroid" + str(randint(1, 3)), scale=AST_INIT_SCALE)) #This is kind of a hack, but it keeps the asteroids from spawning near #the player. It creates the list (-20, -19 ... -5, 5, 6, 7, ... 20) #and chooses a value from it. Since the player starts at 0 and this list #doesn't contain anything from -4 to 4, it won't be close to the player self.asteroids[i].setX( choice(range(-SCREEN_X, -5) + range(5, SCREEN_X))) #Same thing for Y, but from -15 to 15 self.asteroids[i].setZ( choice(range(-SCREEN_Y, -5) + range(5, SCREEN_Y))) heading = random() * 2 * pi #Heading is a random angle in radians #Converts the heading to a vector and multiplies it by speed to get a #velocity vector v = Vec3(sin(heading), 0, cos(heading)) * AST_INIT_VEL self.setVelocity(self.asteroids[i], v)
def __init__(self): #Our standard title and instructions text self.title = OnscreenText(text="Panda3D: Tutorial - Musicbox(sounds)", font=font, style=1, fg=(1, 1, 1, 1), pos=(0.7, -0.95), scale=.07) self.escapeEventText = OnscreenText(text="ESC: Quit", font=font, style=1, fg=(1, 1, 1, 1), pos=(-1.3, 0.95), align=TextNode.ALeft, scale=.05) #Set up the key input self.accept('escape', sys.exit) #Fix the camera position base.disableMouse() #Loading sounds is done in a similar way to loading other things #Loading the main music box song self.musicBoxSound = base.loadMusic( 'models/samples/music_box/musicbox.mp3') self.musicBoxSound.setVolume(.5) #Volume is a percentage from 0 to 1 self.musicBoxSound.setLoopCount( 0) #0 means loop forever, 1 (default) means #play once. 2 or higher means play that #many times #Sound objects do not have a pause function, just play and stop. So we will #Use this variable to keep track of where the sound is at when it was stoped #to impliment pausing self.musicTime = 0 #Loading the open/close effect #loadSFX and loadMusic are identical. They are often used for organization #(loadMusic is used for background music, loadSfx is used for other effects) self.lidSfx = base.loadSfx('models/samples/music_box/openclose.mp3') #The open/close file has both effects in it. Fortunatly we can use intervals #to easily define parts of a sound file to play self.lidOpenSfx = SoundInterval(self.lidSfx, duration=2, startTime=0) self.lidCloseSfx = SoundInterval(self.lidSfx, startTime=5) #For this tutorial, it seemed appropriate to have on screen controls. The #following code creates them #This is a label for a slider self.sliderText = OnscreenText("Volume", font=font, style=1, fg=(1, 1, 1, 1), pos=(0, 0.8), scale=.07) #The slider itself. It calls self.setMusicBoxVolume when changed self.slider = DirectSlider(pos=Vec3(0, 0, .7), value=.50, command=self.setMusicBoxVolume) #A button that calls self.toggleMusicBox when pressed self.button = DirectButton(pos=Vec3(.7, 0, .7), text="Open Box", scale=.1, pad=(.5, .5), text_font=font, rolloverSound=None, clickSound=None, command=self.toggleMusicBox) #A variable to represent the state of the simulation. It starts closed self.boxOpen = False #Here we load and set up the music box. It was modeled in a complex way, so #setting it up will be complicated self.musicBox = loader.loadModel('models/samples/music_box/musicbox') self.musicBox.setPos(0, 60, -10) self.musicBox.reparentTo(render) #Just like the scene graph contains hierarchies of nodes, so can #models. You can get the NodePath for the node using the find #function, and then you can animate the model by moving its parts #To see the hierarchy of a model, use, the ls function #self.musicBox.ls() prints out the entire hierarchy of the model #Finding pieces of the model self.Lid = self.musicBox.find('**/lid') self.Panda = self.musicBox.find('**/turningthing') #This model was made with the hinge in the wrong place #this is here so we have something to turn self.HingeNode = self.musicBox.find('**/box').attachNewNode( 'nHingeNode') self.HingeNode.setPos(.8659, 6.5, 5.4) #WRT - ie with respect to. Reparents the object without changing #its position, size, or orientation self.Lid.wrtReparentTo(self.HingeNode) self.HingeNode.setHpr(0, 90, 0) #This sets up an interval to play the close sound and actually close the box #at the same time. self.lidClose = Parallel( self.lidCloseSfx, LerpFunc(self.HingeNode.setP, duration=2, fromData=0, toData=90, blendType='easeInOut')) #Same thing for opening the box self.lidOpen = Parallel( self.lidOpenSfx, LerpFunc(self.HingeNode.setP, duration=2, fromData=90, toData=0, blendType='easeInOut')) #The interval for turning the panda self.PandaTurn = self.Panda.hprInterval(7, Vec3(360, 0, 0)) #Do a quick loop and pause to set it as a looping interval so it can be #started with resume and loop properly self.PandaTurn.loop() self.PandaTurn.pause()
def __init__(self): base.disableMouse() base.cam.node().getLens().setNear(10.0) base.cam.node().getLens().setFar(200.0) camera.setPos(0, -50, 0) # Check video card capabilities. if (base.win.getGsg().getSupportsBasicShaders() == 0): addTitle( "Toon Shader: Video driver reports that shaders are not supported." ) return # Enable a 'light ramp' - this discretizes the lighting, # which is half of what makes a model look like a cartoon. # Light ramps only work if shader generation is enabled, # so we call 'setShaderAuto'. tempnode = NodePath(PandaNode("temp node")) tempnode.setAttrib(LightRampAttrib.makeSingleThreshold(0.5, 0.4)) tempnode.setShaderAuto() base.cam.node().setInitialState(tempnode.getState()) # Use class 'CommonFilters' to enable a cartoon inking filter. # This can fail if the video card is not powerful enough, if so, # display an error and exit. self.separation = 1 # Pixels self.filters = CommonFilters(base.win, base.cam) filterok = self.filters.setCartoonInk(separation=self.separation) if (filterok == False): addTitle( "Toon Shader: Video card not powerful enough to do image postprocessing" ) return # Post the instructions. self.title = addTitle( "Panda3D: Tutorial - Toon Shading with Normals-Based Inking") self.inst1 = addInstructions(0.95, "ESC: Quit") self.inst2 = addInstructions( 0.90, "Up/Down: Increase/Decrease Line Thickness") self.inst3 = addInstructions(0.85, "V: View the render-to-texture results") # Load a dragon model and animate it. self.character = Actor() self.character.loadModel('models/samples/cartoon/nik_dragon') self.character.reparentTo(render) self.character.loadAnims({'win': 'models/samples/cartoon/nik_dragon'}) self.character.loop('win') self.character.hprInterval(15, Point3(360, 0, 0)).loop() # Create a non-attenuating point light and an ambient light. plightnode = PointLight("point light") plightnode.setAttenuation(Vec3(1, 0, 0)) plight = render.attachNewNode(plightnode) plight.setPos(30, -50, 0) alightnode = AmbientLight("ambient light") alightnode.setColor(Vec4(0.8, 0.8, 0.8, 1)) alight = render.attachNewNode(alightnode) render.setLight(alight) render.setLight(plight) # Panda contains a built-in viewer that lets you view the # results of all render-to-texture operations. This lets you # see what class CommonFilters is doing behind the scenes. self.accept("v", base.bufferViewer.toggleEnable) self.accept("V", base.bufferViewer.toggleEnable) base.bufferViewer.setPosition("llcorner") self.accept("s", self.filters.manager.resizeBuffers) # These allow you to change cartooning parameters in realtime self.accept("escape", sys.exit, [0]) self.accept("arrow_up", self.increaseSeparation) self.accept("arrow_down", self.decreaseSeparation)
def startCarousel(self): #Here's where we actually create the intervals to move the carousel #The first type of interval we use is one created directly from a NodePath #This interval tells the NodePath to vary its orientation (hpr) from its #current value (0,0,0) to (360,0,0) over 20 seconds. Intervals created from #NodePaths also exist for position, scale, color, and shear self.carouselSpin = self.carousel.hprInterval(20, Vec3(360, 0, 0)) #Once an interval is created, we need to tell it to actually move. #start() will cause an interval to play once. loop() will tell an interval #to repeat once it finished. To keep the carousel turning, we use loop() self.carouselSpin.loop() #The next type of interval we use is called a LerpFunc interval. It is #called that becuase it linearly interpolates (aka Lerp) values passed to #a function over a given amount of time. #In this specific case, horses on a carousel don't move contantly up, #suddenly stop, and then contantly move down again. Instead, they start #slowly, get fast in the middle, and slow down at the top. This motion is #close to a sine wave. This LerpFunc calls the function oscilatePanda #(which we will create below), which changes the hieght of the panda based #on the sin of the value passed in. In this way we achieve non-linear #motion by linearly changing the input to a function for i in range(4): self.moves[i] = LerpFunc( self.oscilatePanda, #function to call duration=3, #3 second duration fromData=0, #starting value (in radians) toData=2 * pi, #ending value (2pi radians = 360 degrees) #Additional information to pass to #self.oscialtePanda extraArgs=[self.models[i], pi * (i % 2)]) #again, we want these to play continuously so we start them with loop() self.moves[i].loop() #Finally, we combine Sequence, Parallel, Func, and Wait intervals, #to schedule texture swapping on the lights to simulate the lights turning #on and off. #Sequence intervals play other intervals in a sequence. In other words, #it waits for the current interval to finish before playing the next #one. #Parallel intervals play a group of intervals at the same time #Wait intervals simply do nothing for a given amount of time #Func intervals simply make a single function call. This is helpful because #it allows us to schedule functions to be called in a larger sequence. They #take virtually no time so they don't cause a Sequence to wait. self.lightBlink = Sequence( #For the first step in our sequence we will set the on texture on one #light and set the off texture on the other light at the same time Parallel(Func(self.lights1.setTexture, self.lightOnTex, 1), Func(self.lights2.setTexture, self.lightOffTex, 1)), Wait(1), #Then we will wait 1 second #Then we will switch the textures at the same time Parallel(Func(self.lights1.setTexture, self.lightOffTex, 1), Func(self.lights2.setTexture, self.lightOnTex, 1)), Wait(1) #Then we will wait another second ) self.lightBlink.loop() #Loop this sequence continuously
def makeSquare(x1, y1, z1, x2, y2, z2): format = GeomVertexFormat.getV3n3cpt2() vdata = GeomVertexData('square', format, Geom.UHDynamic) vertex = GeomVertexWriter(vdata, 'vertex') normal = GeomVertexWriter(vdata, 'normal') color = GeomVertexWriter(vdata, 'color') texcoord = GeomVertexWriter(vdata, 'texcoord') #make sure we draw the sqaure in the right plane if x1 != x2: vertex.addData3f(x1, y1, z1) vertex.addData3f(x2, y1, z1) vertex.addData3f(x2, y2, z2) vertex.addData3f(x1, y2, z2) normal.addData3f(myNormalize(Vec3(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1))) normal.addData3f(myNormalize(Vec3(2 * x2 - 1, 2 * y1 - 1, 2 * z1 - 1))) normal.addData3f(myNormalize(Vec3(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1))) normal.addData3f(myNormalize(Vec3(2 * x1 - 1, 2 * y2 - 1, 2 * z2 - 1))) else: vertex.addData3f(x1, y1, z1) vertex.addData3f(x2, y2, z1) vertex.addData3f(x2, y2, z2) vertex.addData3f(x1, y1, z2) normal.addData3f(myNormalize(Vec3(2 * x1 - 1, 2 * y1 - 1, 2 * z1 - 1))) normal.addData3f(myNormalize(Vec3(2 * x2 - 1, 2 * y2 - 1, 2 * z1 - 1))) normal.addData3f(myNormalize(Vec3(2 * x2 - 1, 2 * y2 - 1, 2 * z2 - 1))) normal.addData3f(myNormalize(Vec3(2 * x1 - 1, 2 * y1 - 1, 2 * z2 - 1))) #adding different colors to the vertex for visibility color.addData4f(1.0, 0.0, 0.0, 1.0) color.addData4f(0.0, 1.0, 0.0, 1.0) color.addData4f(0.0, 0.0, 1.0, 1.0) color.addData4f(1.0, 0.0, 1.0, 1.0) texcoord.addData2f(0.0, 1.0) texcoord.addData2f(0.0, 0.0) texcoord.addData2f(1.0, 0.0) texcoord.addData2f(1.0, 1.0) #quads arent directly supported by the Geom interface #you might be interested in the CardMaker class if you are #interested in rectangle though tri1 = GeomTriangles(Geom.UHDynamic) tri2 = GeomTriangles(Geom.UHDynamic) tri1.addVertex(0) tri1.addVertex(1) tri1.addVertex(3) tri2.addConsecutiveVertices(1, 3) tri1.closePrimitive() tri2.closePrimitive() square = Geom(vdata) square.addPrimitive(tri1) square.addPrimitive(tri2) return square
from panda3d.pandac import LightAttrib, TextNode from panda3d.pandac import Vec3, Vec4, BitMask32 from panda3d.direct.gui.OnscreenText import OnscreenText from panda3d.direct.showbase.DirectObject import DirectObject from panda3d.direct.interval.MetaInterval import Sequence, Parallel from panda3d.direct.interval.LerpInterval import LerpFunc from panda3d.direct.interval.FunctionInterval import Func, Wait from panda3d.direct.task.Task import Task import sys #Some constants for the program ACCEL = 70 #Acceleration in ft/sec/sec MAX_SPEED = 5 #Max speed in ft/sec MAX_SPEED_SQ = MAX_SPEED**2 #Squared to make it easier to use lengthSquared #Instead of length UP = Vec3(0, 0, 1) #We need this vector a lot, so its better to just have one #instead of creating a new one every time we need it font = loader.loadFont("cmss12") class World(DirectObject): def __init__(self): #This code puts the standard title and instruction text on screen self.title = OnscreenText( text="Panda3D: Tutorial - Collision Detection", style=1, fg=(1, 1, 1, 1), pos=(0.7, -0.95), scale=.07, font=font) self.instructions = OnscreenText(text="Mouse pointer tilts the board",
def __init__(self): #This code puts the standard title and instruction text on screen self.title = OnscreenText( text="Panda3D: Tutorial - Collision Detection", style=1, fg=(1, 1, 1, 1), pos=(0.7, -0.95), scale=.07, font=font) self.instructions = OnscreenText(text="Mouse pointer tilts the board", pos=(-1.3, .95), fg=(1, 1, 1, 1), font=font, align=TextNode.ALeft, scale=.05) self.accept("escape", sys.exit) #Escape quits base.disableMouse() #Disable mouse-based camera control camera.setPosHpr(0, 0, 25, 0, -90, 0) #Place the camera #Load the maze and place it in the scene self.maze = loader.loadModel("models/samples/ball_in_maze/maze") self.maze.reparentTo(render) #Most times, you want collisions to be tested against invisible geometry #rather than every polygon. This is because testing against every polygon #in the scene is usually too slow. You can have simplified or approximate #geometry for the solids and still get good results. # #Sometimes you'll want to create and position your own collision solids in #code, but it's often easier to have them built automatically. This can be #done by adding special tags into an egg file. Check maze.egg and ball.egg #and look for lines starting with <Collide>. The part is brackets tells #Panda exactly what to do. Polyset means to use the polygons in that group #as solids, while Sphere tells panda to make a collision sphere around them #Keep means to keep the polygons in the group as visable geometry (good #for the ball, not for the triggers), and descend means to make sure that #the settings are applied to any subgroups. # #Once we have the collision tags in the models, we can get to them using #NodePath's find command #Find the collision node named wall_collide self.walls = self.maze.find("**/wall_collide") #Collision objects are sorted using BitMasks. BitMasks are ordinary numbers #with extra methods for working with them as binary bits. Every collision #solid has both a from mask and an into mask. Before Panda tests two #objects, it checks to make sure that the from and into collision masks #have at least one bit in common. That way things that shouldn't interact #won't. Normal model nodes have collision masks as well. By default they #are set to bit 20. If you want to collide against actual visable polygons, #set a from collide mask to include bit 20 # #For this example, we will make everything we want the ball to collide with #include bit 0 self.walls.node().setIntoCollideMask(BitMask32.bit(0)) #CollisionNodes are usually invisible but can be shown. Uncomment the next #line to see the collision walls #self.walls.show() #We will now find the triggers for the holes and set their masks to 0 as #well. We also set their names to make them easier to identify during #collisions self.loseTriggers = [] for i in range(6): trigger = self.maze.find("**/hole_collide" + str(i)) trigger.node().setIntoCollideMask(BitMask32.bit(0)) trigger.node().setName("loseTrigger") self.loseTriggers.append(trigger) #Uncomment this line to see the triggers #trigger.show() #Ground_collide is a single polygon on the same plane as the ground in the #maze. We will use a ray to collide with it so that we will know exactly #what height to put the ball at every frame. Since this is not something #that we want the ball itself to collide with, it has a different #bitmask. self.mazeGround = self.maze.find("**/ground_collide") self.mazeGround.node().setIntoCollideMask(BitMask32.bit(1)) #Load the ball and attach it to the scene #It is on a root dummy node so that we can rotate the ball itself without #rotating the ray that will be attached to it self.ballRoot = render.attachNewNode("ballRoot") self.ball = loader.loadModel("models/samples/ball_in_maze/ball") self.ball.reparentTo(self.ballRoot) #Find the collison sphere for the ball which was created in the egg file #Notice that it has a from collision mask of bit 0, and an into collison #mask of no bits. This means that the ball can only cause collisions, not #be collided into self.ballSphere = self.ball.find("**/ball") self.ballSphere.node().setFromCollideMask(BitMask32.bit(0)) self.ballSphere.node().setIntoCollideMask(BitMask32.allOff()) #No we create a ray to start above the ball and cast down. This is to #Determine the height the ball should be at and the angle the floor is #tilting. We could have used the sphere around the ball itself, but it #would not be as reliable self.ballGroundRay = CollisionRay() #Create the ray self.ballGroundRay.setOrigin(0, 0, 10) #Set its origin self.ballGroundRay.setDirection(0, 0, -1) #And its direction #Collision solids go in CollisionNode self.ballGroundCol = CollisionNode( 'groundRay') #Create and name the node self.ballGroundCol.addSolid(self.ballGroundRay) #Add the ray self.ballGroundCol.setFromCollideMask( BitMask32.bit(1)) #Set its bitmasks self.ballGroundCol.setIntoCollideMask(BitMask32.allOff()) #Attach the node to the ballRoot so that the ray is relative to the ball #(it will always be 10 feet over the ball and point down) self.ballGroundColNp = self.ballRoot.attachNewNode(self.ballGroundCol) #Uncomment this line to see the ray #self.ballGroundColNp.show() #Finally, we create a CollisionTraverser. CollisionTraversers are what #do the job of calculating collisions self.cTrav = CollisionTraverser() #Collision traverservs tell collision handlers about collisions, and then #the handler decides what to do with the information. We are using a #CollisionHandlerQueue, which simply creates a list of all of the #collisions in a given pass. There are more sophisticated handlers like #one that sends events and another that tries to keep collided objects #apart, but the results are often better with a simple queue self.cHandler = CollisionHandlerQueue() #Now we add the collision nodes that can create a collision to the #traverser. The traverser will compare these to all others nodes in the #scene. There is a limit of 32 CollisionNodes per traverser #We add the collider, and the handler to use as a pair self.cTrav.addCollider(self.ballSphere, self.cHandler) self.cTrav.addCollider(self.ballGroundColNp, self.cHandler) #Collision traversers have a built in tool to help visualize collisions. #Uncomment the next line to see it. #self.cTrav.showCollisions(render) #This section deals with lighting for the ball. Only the ball was lit #because the maze has static lighting pregenerated by the modeler lAttrib = LightAttrib.makeAllOff() ambientLight = AmbientLight("ambientLight") ambientLight.setColor(Vec4(.55, .55, .55, 1)) lAttrib = lAttrib.addLight(ambientLight) directionalLight = DirectionalLight("directionalLight") directionalLight.setDirection(Vec3(0, 0, -1)) directionalLight.setColor(Vec4(0.375, 0.375, 0.375, 1)) directionalLight.setSpecularColor(Vec4(1, 1, 1, 1)) lAttrib = lAttrib.addLight(directionalLight) self.ballRoot.node().setAttrib(lAttrib) #This section deals with adding a specular highlight to the ball to make #it look shiny m = Material() m.setSpecular(Vec4(1, 1, 1, 1)) m.setShininess(96) self.ball.setMaterial(m, 1) #Finally, we call start for more initialization self.start()
def __init__( self ): #The main initialization of our class #This creates the on screen title that is in every tutorial self.title = OnscreenText(text="Panda3D: Tutorial - Lighting", style=1, fg=(1,1,0,1), font = font, pos=(0.87,-0.95), scale = .07) #Creates labels used for onscreen instructions self.ambientText = self.makeStatusLabel(0) self.directionalText = self.makeStatusLabel(1) self.spotlightText = self.makeStatusLabel(2) self.pointLightText = self.makeStatusLabel(3) self.spinningText = self.makeStatusLabel(4) self.ambientBrightnessText = self.makeStatusLabel(5) self.directionalBrightnessText = self.makeStatusLabel(6) self.spotlightBrightnessText = self.makeStatusLabel(7) self.spotlightExponentText = self.makeStatusLabel(8) self.lightingPerPixelText = self.makeStatusLabel(9) self.disco = loader.loadModel("models/samples/disco_lights/disco_hall") self.disco.reparentTo(render) self.disco.setPosHpr(0, 50, -4, 90, 0, 0) # First we create an ambient light. All objects are affected by ambient # light equally #Create and name the ambient light self.ambientLight = render.attachNewNode( AmbientLight( "ambientLight" ) ) #Set the color of the ambient light self.ambientLight.node().setColor( Vec4( .1, .1, .1, 1 ) ) #add the newly created light to the lightAttrib # Now we create a directional light. Directional lights add shading from a # given angle. This is good for far away sources like the sun self.directionalLight = render.attachNewNode( DirectionalLight( "directionalLight" ) ) self.directionalLight.node().setColor( Vec4( .35, .35, .35, 1 ) ) # The direction of a directional light is set as a 3D vector self.directionalLight.node().setDirection( Vec3( 1, 1, -2 ) ) # Now we create a spotlight. Spotlights light objects in a given cone # They are good for simulating things like flashlights self.spotlight = camera.attachNewNode( Spotlight( "spotlight" ) ) self.spotlight.node().setColor( Vec4( .45, .45, .45, 1 ) ) #The cone of a spotlight is controlled by it's lens. This creates the lens self.spotlight.node().setLens( PerspectiveLens() ) #This sets the Field of View (fov) of the lens, in degrees for width and #height. The lower the numbers, the tighter the spotlight. self.spotlight.node().getLens().setFov( 16, 16 ) # Attenuation controls how the light fades with distance. The numbers are # The three values represent the three constants (constant, linear, and # quadratic) in the internal lighting equation. The higher the numbers the # shorter the light goes. self.spotlight.node().setAttenuation( Vec3( 1, 0.0, 0.0 ) ) # This exponent value sets how soft the edge of the spotlight is. 0 means a # hard edge. 128 means a very soft edge. self.spotlight.node().setExponent( 60.0 ) # Now we create three colored Point lights. Point lights are lights that # radiate from a single point, like a light bulb. Like spotlights, they # are given position by attaching them to NodePaths in the world self.redHelper = loader.loadModel('models/samples/disco_lights/sphere') self.redHelper.setColor( Vec4( 1, 0, 0, 1 ) ) self.redHelper.setPos( -6.5, -3.75, 0 ) self.redHelper.setScale(.25) self.redPointLight = self.redHelper.attachNewNode( PointLight( "redPointLight" ) ) self.redPointLight.node().setColor( Vec4( .35, 0, 0, 1 ) ) self.redPointLight.node().setAttenuation( Vec3( .1, 0.04, 0.0 ) ) #The green point light and helper self.greenHelper = loader.loadModel('models/samples/disco_lights/sphere') self.greenHelper.setColor( Vec4( 0, 1, 0, 1 ) ) self.greenHelper.setPos( 0, 7.5, 0 ) self.greenHelper.setScale(.25) self.greenPointLight = self.greenHelper.attachNewNode( PointLight( "greenPointLight" ) ) self.greenPointLight.node().setAttenuation( Vec3( .1, .04, .0 ) ) self.greenPointLight.node().setColor( Vec4( 0, .35, 0, 1 ) ) #The blue point light and helper self.blueHelper = loader.loadModel('models/samples/disco_lights/sphere') self.blueHelper.setColor( Vec4( 0, 0, 1, 1 ) ) self.blueHelper.setPos( 6.5, -3.75, 0 ) self.blueHelper.setScale(.25) self.bluePointLight = self.blueHelper.attachNewNode( PointLight( "bluePointLight" ) ) self.bluePointLight.node().setAttenuation( Vec3( .1, 0.04, 0.0 ) ) self.bluePointLight.node().setColor( Vec4( 0, 0, .35, 1 ) ) self.bluePointLight.node().setSpecularColor( Vec4( 1 ) ) #Create a dummy node so the lights can be spun with one command self.pointLightHelper = render.attachNewNode( "pointLightHelper" ) self.pointLightHelper.setPos(0, 50, 11) self.redHelper.reparentTo( self.pointLightHelper ) self.greenHelper.reparentTo( self.pointLightHelper ) self.blueHelper.reparentTo( self.pointLightHelper ) #Finally we store the lights on the root of the scene graph. #This will cause them to affect everything in the scene. render.setLight( self.ambientLight ) render.setLight( self.directionalLight ) render.setLight( self.spotlight ) render.setLight( self.redPointLight ) render.setLight( self.greenPointLight ) render.setLight( self.bluePointLight ) # Create and start interval to spin the lights, and a variable to # manage them. self.pointLightsSpin = self.pointLightHelper.hprInterval(6, Vec3(360, 0, 0)) self.pointLightsSpin.loop() self.arePointLightsSpinning = True # Per-pixel lighting is initially off self.perPixelEnabled = False # listen to keys for controlling the lights self.accept( "escape", sys.exit) self.accept( "a", self.toggleLights, [[self.ambientLight]] ) self.accept( "d", self.toggleLights, [[self.directionalLight]] ) self.accept( "s", self.toggleLights, [[self.spotlight]] ) self.accept( "p", self.toggleLights, [[self.redPointLight, self.greenPointLight, self.bluePointLight]] ) self.accept( "r", self.toggleSpinningPointLights ) self.accept( "l", self.togglePerPixelLighting ) self.accept( "z", self.addBrightness, [self.ambientLight, -.05] ) self.accept( "x", self.addBrightness, [self.ambientLight, .05] ) self.accept( "c", self.addBrightness, [self.directionalLight, -.05] ) self.accept( "v", self.addBrightness, [self.directionalLight, .05] ) self.accept( "b", self.addBrightness, [self.spotlight, -.05] ) self.accept( "n", self.addBrightness, [self.spotlight, .05] ) self.accept( "q", self.adjustSpotlightExponent, [self.spotlight, -1] ) self.accept( "w", self.adjustSpotlightExponent, [self.spotlight, 1] ) #Finally call the function that builds the instruction texts self.updateStatusLabel()
def getVelocity(self, obj): list = cPickle.loads(obj.getTag("velocity")) return Vec3(list[0], list[1], list[2])
def gameLoop(self, task): #task contains a variable time, which is the time in seconds the task has #been running. By default, it does not have a delta time (or dt), which is #the amount of time elapsed from the last frame. A common way to do this is #to store the current time in task.last. This can be used to find dt dt = task.time - task.last task.last = task.time #If the ship is not alive, do nothing. Tasks return Task.cont to signify #that the task should continue running. If Task.done were returned instead, #the task would be removed and would no longer be called every frame if not self.alive: return Task.cont #update ship position self.updateShip(dt) #check to see if the ship can fire if self.keys["fire"] and task.time > task.nextBullet: self.fire(task.time) #If so, call the fire function #And disable firing for a bit task.nextBullet = task.time + BULLET_REPEAT self.keys[ "fire"] = 0 #Remove the fire flag until the next spacebar press #update asteroids for obj in self.asteroids: self.updatePos(obj, dt) #update bullets newBulletArray = [] for obj in self.bullets: self.updatePos(obj, dt) #Update the bullet #Bullets have an experation time (see definition of fire) #If a bullet has not expired, add it to the new bullet list so that it #will continue to exist if self.getExpires(obj) > task.time: newBulletArray.append(obj) else: obj.remove() #Otherwise remove it from the scene #Set the bullet array to be the newly updated array self.bullets = newBulletArray #Check bullet collision with asteroids #In short, it checks every bullet against every asteroid. This is quite #slow. An big optimization would be to sort the objects left to right and #check only if they overlap. Framerate can go way down if there are too #many bullets on screen, but for the most part it's okay. for bullet in self.bullets: #This range statement makes it step though the astroid list backwards #This is because if an asteroid is removed, the elements after it #will change position in the list. If you go backwards, the #length stays constant for i in range(len(self.asteroids) - 1, -1, -1): #Panda's collision detection is more complicated than we need here. #This is the basic sphere collision check. If the distance between #the object centers is less than sum of the radii of the two objects, #then we have a collision. We use lengthSquared since it is a quicker #vector operation than length if ((bullet.getPos() - self.asteroids[i].getPos()).lengthSquared() < (((bullet.getScale().getX() + self.asteroids[i].getScale().getX()) * .5)**2)): self.setExpires(bullet, 0) #Schedule the bullet for removal self.asteroidHit(i) #Handle the hit #Now we do the same collision pass for the ship for ast in self.asteroids: #Same sphere collision check for the ship vs. the asteroid if ((self.ship.getPos() - ast.getPos()).lengthSquared() < (((self.ship.getScale().getX() + ast.getScale().getX()) * .5)** 2)): #If there is a hit, clear the screen and schedule a restart self.alive = False #Ship is no longer alive #Remove every object in asteroids and bullets from the scene for i in self.asteroids + self.bullets: i.remove() self.bullets = [] #Clear the bullet list self.ship.hide() #Hide the ship self.setVelocity(self.ship, Vec3(0, 0, 0)) #Reset the velocity Sequence( Wait(2), #Wait 2 seconds Func(self.ship.setR, 0), #Reset heading Func(self.ship.setX, 0), #Reset position X Func(self.ship.setZ, 0), #Reset position Y (Z for Panda) Func(self.ship.show), #Show the ship Func(self.spawnAsteroids)).start( ) #And respawn the asteriods return Task.cont #If the player has successfully destroyed all asteroids, respawn them if len(self.asteroids) == 0: self.spawnAsteroids() return Task.cont #Since every return is Task.cont, the task will