def shoot(self, force_shot=False): #Force shot tells this to shoot even if a target #is not obviously in view. NPC's will not take such wild shots. #Capital ship initially has 3 guns. one on top, one in front, and one on bottom. for i in range(len(self.weapons)): #If weapon is cool and nearest target is in range then fire. if not self.targets[i] is None and \ self.weapons[i].cooldown == 0 and \ cygeometry.distance(self.targets[i].lead_indicator, self.gunlocs[i]) < \ self.weapons[i].weapon_range: angle = angleFromPosition(self.gunlocs[i], self.targets[i].lead_indicator) self.weapons[i].shoot(angle)
def resetWarps(self): self.warps = [] #Place warp portals for c in self.connections: node_id, location = c #Get the slope of the line from self.loc to this other warp angle = angleFromPosition(self.loc, location) scaledDistance = cygeometry.distance(self.loc, location) * WARP_PORTAL_SCALING #print scaledDistance #TESTING x,y = translate(self.loc, angle, scaledDistance) temp = objInstances.WarpPortal(x=x, y=y, destinationNode=node_id, method=globalvars.scenario_manager.goToInfiniteSpace) self.warps.append(temp)
def bounceOff(self, other): '''Other is another physical object that this physical object just struck and should bounce off of. two objects, A and B, collide. let theta be the angle of the line from the center of A to the center of B. Let A be the smaller of the two. let theta' be a line perpendicular to theta. If A's direction is less than 90 degrees from pointing at B then reflect A's direction over theta'. Reduce both objects' speeds. else move A's direction half way to theta in the direction away from B. Increase A's speed. Decrease B's speed. (This is the case where A is hit from behind despite moving in the same direction as B.) ''' #If either object uses rectangular, we need to bounce off differently if self.useRectangular or other.useRectangular: #Determine if the collision is in the x direction or the y direction. selfRight = self.rect.topleft[0]+self.collisionwidth+self.collisiontopleft[0] otherRight = other.rect.topleft[0]+other.collisionwidth+other.collisiontopleft[0] selfLeft = self.rect.topleft[0]+self.collisiontopleft[0] otherLeft = other.rect.topleft[0]+other.collisiontopleft[0] if selfRight > otherRight or otherLeft > selfLeft: #horizontal collision if self.theta > 0: self.setAngle(180.0 - self.theta) else: self.setAngle(-180.0 - self.theta) else: #vertical collision self.setAngle(-self.theta) #The following is for collisions with objects that are not rectangular. else: angleToOther = self.getAngleToTarget(target=other) if abs(angleToOther) < 90: #Relatively head on collision bounce_off_angle = 90 - angleToOther if angleToOther < 0: self.turnClockwise(bounce_off_angle) else: self.turnCounterClockwise(bounce_off_angle) #Decrease speed by an amount related to the #headon-ed-ness of the collision self.speed = self.speed / (1.0 + float(bounce_off_angle) / 90.0) self.targetSpeed = self.speed else: #Rear end collision #Increase speed, as when an object is hit from behind. #The angle will also change slightly to be more in the #direction of the object that struck us. #Specifically, change our angle to be halfway between #our angle and the angle of other. #First pass for code follows. This is good enough for now. amountToTurn = (180 - abs(angleToOther))/2 if angleToOther < 0: self.turnCounterClockwise(amountToTurn) else: self.turnClockwise(amountToTurn) #Increase speed by adding other object's speed self.speed = self.speed+other.speed #Decrease other object's speed other.speed -= self.speed other.targetSpeed = other.speed #Prevent multiple consecutive collisions with the same object #Get the angle to move away from other's center. angle_to_move = geometry.angleFromPosition(\ other.rect.center, self.rect.center) while self.inCollision(other): self.loc = geometry.translate(self.loc, angle_to_move, 1.0) self.rect.center = self.loc
def run(countdown=-1, track_FPS=False, track_efficiency=False): """Runs the game.""" take_screenshot = False # For more efficient animations dirty_rects = [] # Places where we are drawing new images dirty_covers = [] # Places where we are covering old images with background start_time = datetime.datetime.now() tick_length = 0 offsetx = 0 offsety = 0 offset = offsetx, offsety # key polling: # Use this to keep track of which keys are up and which # are down at any given point in time. keys = [] for _i in range(322): keys.append(False) # mouse is [pos, button1, button2, button3,..., button6]. # new Apple mice think they have 6 buttons. # Use this to keep track of which mouse buttons are up and which # are down at any given point in time. # Also the tuple, mouse[0], is the current location of the mouse. mouse = [(0, 0), 0, 0, 0, 0, 0, 0] # pygame setup: clock = pygame.time.Clock() running = True # Gather some data on efficiency by measuring the relationship between time burned in the ticks method and the number of objects to update on the screen globalvars.time_lapses = [0 for _ in xrange(1900)] globalvars.dirty_rect_size = [0 for _ in xrange(1900)] efficiency_index = 0 # The in-round loop (while player is alive): while running: # Use this for more accurate profiling: if countdown != -1: countdown -= 1 if countdown < 0: exit() # Draw everything on the screen. Do so either using dirty rects or just by # redrawing the whole screen. Dirty rects are usually more efficient. if update_mechanism == DIRTY: pygame.display.update(dirty_rects) pygame.display.update(dirty_covers) elif update_mechanism == FLIP: pygame.display.flip() else: above_threshold = len(dirty_rects) + len(dirty_covers) > UPDATE_THRESHOLD if above_threshold: pygame.display.flip() else: pygame.display.update(dirty_rects) pygame.display.update(dirty_covers) # Cover previous dirty rectangles and empty them out. if update_mechanism != FLIP: # Cover the dirty rectangles with background coverAll(dirty_rects) # Copy dirty rects into dirty covers dirty_covers = dirty_rects[:] # Empty dirty rects dirty_rects = [] if take_screenshot: pygame.image.save(globalvars.screen, "screenshots/" + str(rd.randint(0, 999999999)) + ".jpeg") take_screenshot = False if track_FPS: # Calculate how long we took in the above loop to estimate the number of frames per second. # We want time_lapse to be stable. time_lapse = datetime.datetime.now() - start_time # Used for calculating actual frames per second in # order to determine when we are dropping frames # so that efficiency improvements can be made. start_time = datetime.datetime.now() # Alert user if fraps drops below half the desired threshold. if float(time_lapse.microseconds) / 1000000.0 > (2.0 / float(globalvars.FPS)): print "\nWarning: frames dropping." print "Goal frames per second is " + str(globalvars.FPS) + ". Current is " + str( 1.0 / (float(time_lapse.microseconds) / 1000000.0) )[ :2 ] # Cut off decimal because I don't care. print "Sizes of Sprite Groups follows:" print "Tangibles: " + str(len(globalvars.tangibles)) print "Intangibles_bottom: " + str(len(globalvars.intangibles_bottom)) print "Intangibles_top: " + str(len(globalvars.intangibles_top)) print "Whiskerables: " + str(len(globalvars.whiskerables)) if track_efficiency: tick_length = datetime.datetime.now() # frame maintainance: # aim for globalvars.FPS frames per second. clock.tick(globalvars.FPS) if track_efficiency: # We want this value, tick_length, to be large because that means the processor # is taking long rests because everything else is happening so efficiently. tick_length = datetime.datetime.now() - tick_length # Gather some efficiency data globalvars.time_lapses[efficiency_index] = tick_length.microseconds globalvars.dirty_rect_size[efficiency_index] = len(dirty_rects) + len(dirty_covers) efficiency_index = (efficiency_index + 1) % len(globalvars.time_lapses) # Display the panel if not globalvars.menu.main_panel is None: globalvars.menu.main_panel.draw() pygame.display.flip() # Check for another key press to remove the panel. for event in pygame.event.get(): # Check for event m key pressed to remove the menu. if event.type == pygame.KEYDOWN and event.key == 109: globalvars.menu.main_panel = None redrawWholeBackground() pygame.display.flip() break # Panel event handeling can make the panel itself None so we have # to check if the panel has become None for every event. If the # panel has become None we break and ignore further input events. elif globalvars.menu.main_panel is None: redrawWholeBackground() pygame.display.flip() break # Pass all other events to the panel else: globalvars.menu.main_panel.handleEvent(event) # Skip all the rest while displaying the menu. # This effectively pauses the game. continue # event polling: # See what buttons may or may not have been pushed. for event in pygame.event.get(): if event.type == pygame.QUIT: running = False elif event.type == pygame.MOUSEBUTTONDOWN: mouse[event.button] = 1 mouse[0] = event.pos # Also causes shooting globalvars.player.shoot(force_shot=True) elif event.type == pygame.MOUSEBUTTONUP: mouse[event.button] = 0 # elif event.type == pygame.MOUSEMOTION: # mouse[0] = event.pos elif event.type == pygame.KEYDOWN: keys[event.key % 322] = 1 # print "TODO TESTING: key press "+str(event.key) # Respond to key taps. # Keys that we want to respond to holding them down # will be dealt with below. if event.key == 273 or event.key == 119: # Pressed up arrow or w key # increase speed by a fraction of max up to max. globalvars.player.targetSpeed = min( globalvars.player.maxSpeed, globalvars.player.targetSpeed + globalvars.player.maxSpeed * globalvars.player.speedIncrements, ) elif event.key == 274 or event.key == 115: # Pressed down arrow or s key # decrease speed by a fraction of # max down to zero. globalvars.player.targetSpeed = max( 0, globalvars.player.targetSpeed - globalvars.player.maxSpeed * globalvars.player.speedIncrements, ) elif event.key == 27: # escape key or red button running = False elif event.key == 109: # m key # If player is dead, access the restart panel, not the testing panel. if globalvars.player.isDead(): globalvars.menu.setRestartPanel() elif globalvars.disable_menu: globalvars.menu.setPausePanel() else: globalvars.menu.setShipPanel() continue elif event.key == 98: # b key globalvars.player.parkingBrake() elif event.key == 113: # q key # Obliterate destination. # Change to free flight. globalvars.player.killDestination() elif event.key == 116: # t key # shoot a bunch of hit box testers # in towards the player print "Width: " + str(globalvars.player.image.get_width()) + " vs " + str( globalvars.player.rect.width ) print "Height: " + str(globalvars.player.image.get_height()) + " vs " + str( globalvars.player.rect.height ) test.hitBoxTest(globalvars.player.rect.center) elif event.key == 108: # l key import profilingObject temp = profilingObject.CollisionAvoidanceTester() globalvars.intangibles_bottom.add(temp) elif event.key == 111: # o key import profilingObject temp = profilingObject.ProfilingObject() globalvars.intangibles_bottom.add(temp) # Begin profiling import cProfile cProfile.runctx("run(track_efficiency=True)", globals(), None, "profiling/game.run.profile") exit() elif event.key == 121: # y key # Profile lots of methods, but not game.run() profileEverything(offset) elif event.key == 117: # u key # Profile game.run() import cProfile print "Profiling game.run(countdown=1800). " + "Press escape to quit early. " + "Profiling will stop automatically after 30 seconds." # Run for 1800 frames (or 30 seconds assuming 60 frames per second. cProfile.runctx("run(countdown=1800)", globals(), None, "profiling/game.run.profile") exit() elif event.key == 105: # i key # Record and display efficiency data. # We want time_lapses to be large. # Write efficiency data to file. # filehandle = open('profiling/efficiency_data.txt', 'w') # for i in xrange(len(globalvars.time_lapses)): # filehandle.write(str(globalvars.time_lapses[i])+\ # ', '+str(globalvars.dirty_rect_size[i])+'\n') # filehandle.close() # Plot the data then exit import matplotlib.pyplot as plt plt.plot(globalvars.time_lapses, "ro") plt.plot(globalvars.dirty_rect_size) plt.show() plt.plot(globalvars.dirty_rect_size) plt.show() exit() elif event.key == 47: # forward slash (question mark # without shift) key. # Useful for querying one time info. print "Print player destination: " + str(globalvars.player.destx) + "," + str( globalvars.player.desty ) elif event.key == 104 or event.key == 304: # "h key" lower or upper case. # Display help menu. globalvars.menu.setHelpPanel() elif event.key == 112: # p key - takes a screenshot take_screenshot = True if event.key == 120: # Pressed x key globalvars.player.shoot(force_shot=True, weapon=globalvars.player.missile) if event.key == 122: # Pressed z key globalvars.player.shoot(force_shot=True, weapon=globalvars.player.mine) elif event.type == pygame.KEYUP: # Keep track of which keys are no longer # being pushed. keys[event.key % 322] = 0 ##This will make the player move towards the mouse ##without any clicking involved. ##Set player destination to current mouse coordinates. if mouse[1]: x, y = pygame.mouse.get_pos() x += offsetx y += offsety globalvars.player.setDestination((x, y)) # Respond to key holds. # Keys that we want to respond to tapping them # will be dealt with above. if keys[276] or keys[97]: # Pressed left arrow or a key globalvars.player.turnCounterClockwise() elif keys[275] or keys[100]: # Pressed right arrow or d key globalvars.player.turnClockwise() # This is not part of the above else if. # You can shoot and turn at the same time. if keys[32] or keys[99]: # Pressed space bar or c key # Force shot tells this to shoot even if a target # is not obviously in view. NPC's will not take such wild shots. globalvars.player.shoot(force_shot=True) # Check all collisions collisionHandling() # If arena is non-zero, then make sure player and all # whiskerables are within it if globalvars.arena > 0: # The inner concentric ring bounces the player back # towards center (don't actually bounce, just change # angle directly towards center. The outer # concentric ring, defined by distance from center # plus object radius will bounce asteroids in a # semi random direction towards the center-ish area. # Make sure player is within arena. # If not, change player's heading to be towards 0,0 if cygeometry.distance(globalvars.player.rect.center, (0.0, 0.0)) > globalvars.arena: globalvars.player.theta = geometry.angleFromPosition(globalvars.player.rect.center, (0.0, 0.0)) globalvars.player.updateImageAngle() # Check each whiskerable and if it is more than # arena + diameter from center, then change its # angle to point somewhere within the arena too. for w in globalvars.whiskerables: if cygeometry.distance(w.rect.center, (0.0, 0.0)) > globalvars.arena + w.collisionradius: # Whiskerables reflect randomly # off arena boundaries towards a # point somewhere within 3/4 the # center of the arena. limit = 3 * globalvars.arena / 4 x = rd.randint(-limit, limit) y = rd.randint(-limit, limit) if not w.direction is None: # Asteroids distinguish between their orientation, # theta, and direction of movement, direction. # Thus we want direction changed, not theta. w.direction = geometry.angleFromPosition(w.rect.center, (x, y)) else: w.theta = geometry.angleFromPosition(w.rect.center, (x, y)) # update all sprites: # First tell the ships what is closest to them # so that they can avoid collisions setClosestSprites() # setClosestSpritesAlt() #TODO - This one should be more efficient and superior to the other, but it's not. # Update all the sprites globalvars.intangibles_bottom.update() globalvars.tangibles.update() globalvars.intangibles_top.update() # Get the offset based on the player location. offsetx = globalvars.player.rect.centerx - globalvars.CENTERX offsety = globalvars.player.rect.centery - globalvars.CENTERY offset = offsetx, offsety if update_mechanism == FLIP: # Draw the background over the screen. redrawWholeBackground() # Draw all the things that are on the screen for x in globalvars.intangibles_bottom: if x.isOnScreen(offset): x.draw(offset) for x in globalvars.tangibles: if x.isOnScreen(offset): x.draw(offset) for x in globalvars.intangibles_top: if x.isOnScreen(offset): x.draw(offset) else: # Put on screen rects in dirty rects for x in globalvars.intangibles_bottom: if x.isOnScreen(offset): dirty_rects.append(x.getDirtyRect(offset)) x.draw(offset) for x in globalvars.tangibles: if x.isOnScreen(offset): dirty_rects.append(x.getDirtyRect(offset)) x.draw(offset) for x in globalvars.intangibles_top: if x.isOnScreen(offset): dirty_rects.append(x.getDirtyRect(offset)) x.draw(offset) # Draw player last so the background isn't drawn overtop of the player. globalvars.player.playerUpdate() if not globalvars.player.isDead() and globalvars.player.fuel > 0: if update_mechanism == DIRTY: dirty_rects.append(globalvars.player.getDirtyRect(offset)) elif update_mechanism == FLIP: globalvars.player.drawAt((globalvars.CENTERX, globalvars.CENTERY)) else: dirty_rects.append(globalvars.player.getDirtyRect(offset)) if above_threshold: globalvars.player.drawAt((globalvars.CENTERX, globalvars.CENTERY)) else: # Make player death kick the player back to a menu where player # can choose to restart. Display a death screen then. Reset the # scenario and everything else. # Countdown before kicking player back to menu globalvars.deathcountdown -= 1 if globalvars.deathcountdown < 0: globalvars.menu.setRestartPanel()