class SpriteRenderer(): # Loads all the sprites into groups and stuff def __init__(self, game): self.allSprites = pygame.sprite.Group() self.entities = pygame.sprite.Group() self.layer1 = pygame.sprite.Group() # layer 1 self.layer2 = pygame.sprite.Group() # layer 2 self.layer3 = pygame.sprite.Group() # layer 3 self.layer4 = pygame.sprite.Group() # layer 4 is all layers combined self.game = game self.currentLayer = 4 self.level = "" # Hud for when the game is running self.hud = GameHud(self.game) self.menu = GameMenu(self.game) self.messageSystem = MessageHud(self.game) self.personClickManager = PersonClickManager(self.game) self.transportClickManager = TransportClickManager(self.game) self.personHolderClickManager = PersonHolderClickManager(self.game) self.rendering = False # Game timer to keep track of how long has been played self.timer = 0 # Make this dependant on the level and make it decrease # as the number of people who reach their destinations increase self.timeStep = 25 self.lives = DEFAULTLIVES self.score, self.bestScore = 0, 0 self.dt = 1 # Control the speed of whats on screen self.startDt = self.dt self.fixedScale = 1 # Control the size of whats on the screen self.startingFixedScale = 0 self.paused = False # Individual pause for the levels self.setDefaultMap() self.totalPeople, self.completed, self.totalToComplete = 0, 0, 0 self.totalPeopleNone = False self.slowDownMeterAmount = 75 self.debug = False self.darkMode = False # The connection types availabe on the map (always has layer 4) self.connectionTypes = ["layer 4"] def setDefaultMap(self): self.levelData = { "mapName": "", "locked": { "isLocked": False, "unlock": 0 }, # Amount to unlock # Map can / cannot be deleted; maps that cant be # deleted cant be opened in the editor "deletable": True, "saved": False, # Has the map been saved before "width": 18, "height": 10, "difficulty": 1, # Out of 4 "total": 8, # Total to complete the level "score": 0, "completion": { "total": 10, "completed": False, "time": 0 }, "backgrounds": { "layer 1": CREAM, # Default color: CREAM :) "layer 2": CREAM, "layer 3": CREAM, "layer 4": CREAM }, "connections": {}, "transport": {}, "stops": {}, "destinations": {} } # Level data to be stored, for export to JSON # Save function, for when the level has already # been created before (and is being edited) def saveLevel(self): self.game.mapLoader.saveMap(self.levelData["mapName"], self.levelData) def setRendering(self, rendering, transition=False): self.rendering = rendering self.hud.main(transition) if self.rendering else self.hud.close() if self.rendering: self.messageSystem.main() else: self.messageSystem.close() # Create the paused surface when first rendering self.createPausedSurface() def runStartScreen(self): if self.rendering and not self.debug: self.menu.startScreen() def runEndScreen(self, completed=False): if self.rendering and not self.debug: if completed: self.menu.endScreenComplete(True) # Run with transition else: self.menu.endScreenGameOver(True) # Run with transition def setCompleted(self, completed): self.completed = completed self.hud.setCompletedText() # When the player completed the level, set it to complete # in the level data and save the data def setLevelComplete(self): if not hasattr(self, 'levelData'): return # If the level is not already set to completed, complete it if not self.levelData["completion"]["completed"]: self.levelData["completion"]["completed"] = True self.saveLevel() # Use the number of lives left to work out the players score TODO: # Make this use other factors in the future # Return the previous keys and difference so # this can be used in the menu animation def setLevelScore(self): if not hasattr(self, 'levelData'): return self.score = self.lives previousScore = 0 if "score" in self.levelData: previousScore = self.levelData["score"] self.bestScore = previousScore if self.score > previousScore: self.levelData["score"] = self.score self.saveLevel() if self.score - previousScore > 0: scoreDifference = self.score - previousScore else: scoreDifference = 0 # Use this in the menu animation previousKeys = config["player"]["keys"] config["player"]["keys"] += scoreDifference dump(config) return previousKeys, scoreDifference, previousScore def setTotalToComplete(self, totalToComplete): self.totalToComplete = totalToComplete def setSlowDownMeterAmount(self, slowDownMeterAmount): self.slowDownMeterAmount = slowDownMeterAmount def setDt(self, dt): self.dt = dt def setFixedScale(self, fixedScale): self.fixedScale = fixedScale def setStartingFixedScale(self, startingFixedScale): self.startingFixedScale = startingFixedScale def setDebug(self, debug): self.debug = debug def setDarkMode(self): if ("backgrounds" in self.levelData and "darkMode" in self.levelData["backgrounds"] and self.levelData["backgrounds"]["darkMode"]): self.darkMode = True else: self.darkMode = False def setTotalPeople(self, totalPeople): self.totalPeople = totalPeople def setLives(self, lives): self.lives = lives def togglePaused(self): self.dt = 0 self.paused = not self.paused # self.game.paused = not self.game.paused # self.createPausedSurface() def getStartDt(self): return self.startDt def getDt(self): return self.dt def getFixedScale(self): return self.fixedScale def getStartingFixedScale(self): return self.startingFixedScale def getHud(self): return self.hud def getMenu(self): return self.menu def getMessageSystem(self): return self.messageSystem def getLevel(self): return self.level def getLevelData(self): return self.levelData def getPersonClickManager(self): return self.personClickManager def getTransportClickManager(self): return self.transportClickManager def getLayer(self): return self.currentLayer def getCompleted(self): return self.completed def getTotalToComplete(self): return self.totalToComplete def getSlowDownMeterAmount(self): return self.slowDownMeterAmount def getDebug(self): return self.debug def getConnectionTypes(self): return self.connectionTypes def getDarkMode(self): return self.darkMode def getTotalPeople(self): return self.totalPeople def getLives(self): return self.lives def getAllDestination(self): if hasattr(self, 'allDestinations'): return self.allDestinations def getScore(self): return self.score def getBestScore(self): return self.bestScore def getPaused(self): return self.paused def getCurrentLayer(self): return self.currentLayer def getPersonHolderClickManager(self): return self.personHolderClickManager def removeLife(self): self.lives -= 1 # remove a heart from the hud here or something self.hud.setLifeAmount() if self.lives <= 0: # Although we pause the game in the timer, we want to # stop anything else from reducing the life count here whilst the # timer is decreasing (so no one can die whilst it is decreasing) pass def addToCompleted(self): self.completed += 1 # self.timeStep -= 0.5 self.hud.setCompletedAmount() self.meter.addToAmountToAdd(20) # Reset the level back to its default state def clearLevel(self): self.paused = False # Not to confuse the option menu self.startingFixedScale = 0 # reset the scale back to default self.timer = 0 self.lives = DEFAULTLIVES self.totalPeople = 0 self.totalPeopleNone = False self.entities.empty() self.allSprites.empty() self.layer1.empty() self.layer2.empty() self.layer3.empty() self.layer4.empty() # Reset the layers to show the top layer self.currentLayer = 4 self.connectionTypes = [] self.setDefaultMap() def createLevel(self, level, debug=False): self.clearLevel() # Currently this calls the wrong hud as its done before the hud is set self.setCompleted(0) self.debug = debug self.gridLayer4 = Layer4(self, (self.allSprites, self.layer4), level) # Set the name of the level self.level = self.gridLayer4.getGrid().getLevelName() # Set the level data self.levelData = self.gridLayer4.getGrid().getMap() # for running the game in test mode (when testing a level) if self.debug: # Push the level down since we have hud at the top spacing = (1.5, 1.5) self.hud = PreviewHud(self.game, spacing) else: # self.startingFixedScale = -0.05 # spacings = { # (16, 9): (1.5, 1), # (18, 10): (2, 1.5), # (20, 11): (1.5, 1), # (22, 12): (1.5, 1)} # size = (self.levelData["width"], self.levelData["height"]) # spacing = spacings[size] spacing = (1.5, 1) self.hud = GameHud(self.game, spacing) # we want to get which connectionTypes are available in the map for connectionType in self.levelData['connections']: self.connectionTypes.append(connectionType) self.gridLayer3 = Layer3(self, (self.allSprites, self.layer3, self.layer4), level, spacing) self.gridLayer1 = Layer1(self, (self.allSprites, self.layer1, self.layer4), level, spacing) # Walking layer at the bottom so nodes are drawn above metro stations self.gridLayer2 = Layer2(self, (self.allSprites, self.layer2, self.layer4), level, spacing) self.gridLayer4.addLayerLines(self.gridLayer1, self.gridLayer2, self.gridLayer3) self.gridLayer1.grid.loadTransport("layer 1") self.gridLayer2.grid.loadTransport("layer 2") self.gridLayer3.grid.loadTransport("layer 3") self.removeDuplicates() # Set all the destinations to be the destinations from all layers layer1Destinations = self.gridLayer1.getGrid().getDestinations() layer2Destinations = self.gridLayer2.getGrid().getDestinations() layer3Destinations = self.gridLayer3.getGrid().getDestinations() self.allDestinations = (layer1Destinations + layer2Destinations + layer3Destinations) # Set number of people to complete level if "total" not in self.levelData: self.totalToComplete = random.randint(8, 12) else: self.totalToComplete = self.levelData["total"] # self.totalToComplete = 1 self.meter = MeterController(self, self.allSprites, self.slowDownMeterAmount) self.setDarkMode() # If there is more than one layer we want to be able # to see 'all' layers at once (layer 4) # otherwise we only need to see the single layer if len(self.connectionTypes) > 1 or self.debug: self.connectionTypes.append("layer 4") else: self.showLayer( self.getGridLayer(self.connectionTypes[0]).getNumber()) # Draw the level to a surface and return this surface for blitting # (i.e on the level selection screen) def createLevelSurface(self, level): self.clearLevel() self.startingFixedScale = -0.2 spacings = { (16, 9): (3.5, 2), (18, 10): (4, 2.5), (20, 11): (4.5, 2.8), (22, 12): (5, 3) } gridLayer4 = MenuLayer4(self, (), level) levelData = gridLayer4.getGrid().getMap() size = (levelData["width"], levelData["height"]) spacing = spacings[size] gridLayer3 = Layer3(self, (), level, spacing) gridLayer1 = Layer1(self, (), level, spacing) gridLayer2 = Layer2(self, (), level, spacing) gridLayer4.addLayerLines(gridLayer1, gridLayer2, gridLayer3) return gridLayer4.getLineSurface() # Create a new surface when the game is paused with all the sprites # currently in the game, so these don't have to be drawn every frame # (as they are not moving) def createPausedSurface(self): if self.rendering and self.game.paused: self.pausedSurface = pygame.Surface( (int(config["graphics"]["displayWidth"] * self.game.renderer.getScale()), int(config["graphics"]["displayHeight"] * self.game.renderer.getScale()))).convert() lineSurface = ( self.getGridLayer("layer " + str(self.currentLayer)).getLineSurface()) self.pausedSurface.blit(lineSurface, (0, 0)) for entity in self.entities: entity.drawPaused(self.pausedSurface) self.renderPausedLayer(1, self.layer1) self.renderPausedLayer(2, self.layer2) self.renderPausedLayer(3, self.layer3) self.renderPausedLayer(4, self.layer4) # for component in ( # self.hud.getComponents() # + self.messageSystem.getComponents()): # # draw hud and message system # component.drawPaused(self.pausedSurface) return self.pausedSurface def getSpriteLayer(self, connectionType): if connectionType == "layer 1": return self.layer1 elif connectionType == "layer 2": return self.layer2 elif connectionType == "layer 3": return self.layer3 elif connectionType == "layer 4": return self.layer4 def getGridLayer(self, connectionType): if connectionType == "layer 1": return self.gridLayer1 elif connectionType == "layer 2": return self.gridLayer2 elif connectionType == "layer 3": return self.gridLayer3 elif connectionType == "layer 4": return self.gridLayer4 # Get all the nodes from all layers in the spriterenderer def getAllNodes(self, sortNodes=False): layer1Nodes = self.gridLayer1.getGrid().getNodes() layer2Nodes = self.gridLayer2.getGrid().getNodes() layer3Nodes = self.gridLayer3.getGrid().getNodes() allNodes = layer1Nodes + layer2Nodes + layer3Nodes # Sort the node so that the stops are at the top if sortNodes: allNodes = sorted(allNodes, key=lambda x: isinstance(x, Stop)) allNodes = sorted(allNodes, key=lambda x: isinstance(x, Destination)) # Reverse the list so they're at the front allNodes = allNodes[::-1] return allNodes # Remove duplicate nodes on layer 4 for layering def removeDuplicates(self, allNodes=None, removeLayer=None): seen = {} dupes = [] removeLayer = self.layer4 if removeLayer is None else removeLayer if allNodes is None: allNodes = self.getAllNodes() # Make sure stops are at the front of the list, so they are not removed allNodes = sorted(allNodes, key=lambda x: isinstance(x, Stop)) allNodes = sorted(allNodes, key=lambda x: isinstance(x, Destination)) allNodes = allNodes[::-1] # Reverse the list so they're at the front for node in allNodes: if node.getNumber() not in seen: seen[node.getNumber()] = 1 else: if seen[node.getNumber()] == 1: dupes.append(node) for node in dupes: removeLayer.remove(node) # if there is a node above the given node, # return the highest node, else return node def getTopNode(self, bottomNode): allNodes = self.getAllNodes(True) for node in allNodes: if node.getNumber() == bottomNode.getNumber(): return node return bottomNode def update(self): if not self.rendering: return self.allSprites.update() if self.paused: return self.events() self.timer += self.game.dt * self.dt # Always spawn a person if there is no people # left on the map, to stop player having to wait if self.timer > self.timeStep: self.timer = 0 self.gridLayer2.createPerson(self.allDestinations) if self.totalPeople <= 0: if not self.totalPeopleNone: self.timer = 0 self.totalPeopleNone = True # wait 2 seconds before spawing the next # person when there is no people left if self.timer > 2 and self.totalPeopleNone: self.timer = 0 self.gridLayer2.createPerson(self.allDestinations) self.totalPeopleNone = False def events(self): if pygame.key.get_pressed()[pygame.K_SPACE]: self.game.clickManager.setSpaceBar(True) if (self.dt != self.startDt - self.meter.getSlowDownAmount() and not self.meter.getEmpty()): self.game.audioLoader.playSound("slowIn", 1) else: if self.dt != self.startDt: self.game.audioLoader.playSound("slowOut", 1) self.game.clickManager.setSpaceBar(False) # Make people on the current layer clickable, and the rest non-clickable def resetPeopleClicks(self): totalPeople = (self.gridLayer1.getPeople() + self.gridLayer2.getPeople() + self.gridLayer3.getPeople()) currentLayerPeople = self.getGridLayer( "layer " + str(self.currentLayer)).getPeople() for person in totalPeople: if person in currentLayerPeople or self.currentLayer == 4: # Always went every person to be clickable on the top layer person.setCanClick(True) else: person.setCanClick(False) def resetPersonHolderClicks(self): totalNodes = self.getAllNodes() for node in totalNodes: if (node.getConnectionType() == "layer " + str(self.currentLayer) or self.currentLayer == 4): # Always want every person holder to be clickable # on the top layer node.getPersonHolder().setCanClick(True) else: node.getPersonHolder().setCanClick(False) def showLayer(self, layer): if not self.rendering: return # Only switch to a layer that is in the map if "layer " + str(layer) in self.connectionTypes: self.currentLayer = layer # Redraw the nodes so that the mouse cant collide with them self.resize() self.hud.updateLayerText() self.resetPeopleClicks() self.resetPersonHolderClicks() def resize(self): # If a layer has any images, they must be resized here if self.rendering: self.gridLayer1.resize() self.gridLayer2.resize() self.gridLayer3.resize() # Only need to do this if it has components self.gridLayer4.resize() # We want to reset the layer 4 lines with the # new ones (resized) from the other layers self.gridLayer4.addLayerLines(self.gridLayer1, self.gridLayer2, self.gridLayer3) # resize huds and menus self.hud.resize() self.menu.resize() self.messageSystem.resize() for sprite in self.allSprites: sprite.dirty = True self.createPausedSurface() # Draw all the sprites in a layer, based on what layer the player # is currently on def renderLayer(self, layerInt, gridLayer, group): if self.currentLayer == layerInt: gridLayer.draw() for sprite in group: sprite.draw() # Draw all sprites to the paused surface, based on what layer the player # is currently on def renderPausedLayer(self, layerInt, group): if self.currentLayer == layerInt: for sprite in group: sprite.drawPaused(self.pausedSurface) def render(self): if self.rendering: if not self.game.paused: # Entities drawn below the other sprites for entity in self.entities: entity.draw() self.renderLayer(1, self.gridLayer1, self.layer1) self.renderLayer(2, self.gridLayer2, self.layer2) self.renderLayer(3, self.gridLayer3, self.layer3) self.renderLayer(4, self.gridLayer4, self.layer4) else: if hasattr(self, 'pausedSurface'): self.game.renderer.addSurface( self.pausedSurface, (self.pausedSurface.get_rect())) self.hud.display() self.messageSystem.display() self.menu.display()