def getPlayerSprite(self, playerId, ignoreLocal=False): player = self.universe.getPlayer(playerId) if player is None: return None if playerId == self.localPlayerId and not ignoreLocal: p = self.localPlayerSprite if p.spriteTeam != p.player.team: self.localPlayerSprite = PlayerSprite(self.app, self, p.player) if p is self.gameViewer.viewManager.target: self.gameViewer.viewManager.setTarget( self.localPlayerSprite) p = self.localPlayerSprite return p try: p = self.playerSprites[playerId] except KeyError: self.playerSprites[player.id] = p = PlayerSprite( self.app, self, player) return p if p.spriteTeam != player.team: # Player has changed teams. self.playerSprites[player.id] = p = PlayerSprite( self.app, self, player) return p
def drawLoading(self, screen): if self.loadingPlayer is None: player = Player(self.universe.universe, 'Loading...', None, '') player.motionState = 'ground' player.updateState('right', True) self.loadingPlayer = PlayerSprite(self.app, self.universe, player, timer=timeNow) if self.loadingScreen is None: self.loadingScreen = pygame.Surface(screen.get_size()) self.loadingScreen.fill((255, 255, 255)) font = self.app.screenManager.fonts.mainMenuFont colour = self.app.theme.colours.mainMenuColour text = font.render(self.app, 'Loading...', True, colour, (255, 255, 255)) r = text.get_rect() r.center = self.loadingScreen.get_rect().center self.loadingScreen.blit(text, r) self.loadingPlayer.rect.midbottom = r.midtop if random.random() < 0.03: self.loadingPlayer.unit.lookAt(random.random() * 2 * math.pi) screen.blit(self.loadingScreen, (0, 0)) self.loadingPlayer.update() screen.fill((255, 255, 255), self.loadingPlayer.rect) screen.blit(self.loadingPlayer.image, self.loadingPlayer.rect)
def detailLevelChanged(self): # Clear everything long-lived self.playerSprites = {} self.collectableCoinSprites = {} self.trosballSprite = None if self.localPlayerId is not None: p = self.localPlayerSprite self.localPlayerSprite = PlayerSprite(self.app, self, p.player)
def overridePlayer(self, player): self.localPlayerId = player.id self.localPlayerSprite = PlayerSprite(self.app, self, player)
class ViewManager(framework.Element): '''A ViewManager object takes a given universe, and displays a screenfull of the current state of the universe on the specified screen object. This class displays only a section of the universe and no other information (scores, menu etc.). Note: self._focus represents the position that the ViewManager is currently looking at. self.target is what the ViewManager should be trying to look at. self.target = None - the ViewManager will use its algorithm to follow a point of action. self.target = (x, y) - the ViewManager will look at the specified point. self.target = player - the ViewManager will follow the specified player. ''' # The fastest speed that the viewing position can shift in pixels per sec maxSpeed = 1800 acceleration = 1080 # How far ahead of the targeted player we should look. lengthFromPlayer = 125 def __init__(self, app, parent, universe, target=None, replay=False): ''' Called upon creation of a ViewManager object. screen is a pygame screen object. universe is the Universe object to draw. target is either a point, a PlayerSprite object, or None. If target is None, the view manager will follow the action, otherwise it will follow the specified point or player. ''' super(ViewManager, self).__init__(app) self.universe = universe self.parent = parent self.replay = replay # self._focus represents the point where the viewManager is currently # looking. self._focus = (universe.map.layout.centreX, universe.map.layout.centreY) self._oldTargetPt = self._focus self.lastUpdateTime = timeNow() self.autoFocusInfo = (0, set(), set()) self.speed = 0 # Speed that the focus is moving. self.loadingScreen = None self.loadingPlayer = None self.backgroundDrawer = BackgroundDrawer(app, universe) self.sRect = None # Now fill the backdrop with what we're looking at now. self.appResized() self.setTarget(target) def reset(self): if self.target is None: if self.autoFocusInfo == [0, set(), set()]: self._focus = (self.universe.map.layout.centreX, self.universe.map.layout.centreY) def stop(self): self.backgroundDrawer.stop() def tick(self, deltaT): if not self.active: return super(ViewManager, self).tick(deltaT) self.updateMumble() def updateMumble(self): mumbleUpdater.update(self.getTargetPlayer(), self.getTargetPoint(), serverId='') def appResized(self): self.loadingScreen = None self.sRect = sRect = pygame.Rect((0, 0), self.app.screenManager.size) centre = sRect.center if not self.replay: settings = self.app.displaySettings sRect.width = min(settings.maxViewportWidth, sRect.width) sRect.height = min(settings.maxViewportHeight, sRect.height) sRect.center = centre def setTarget(self, target): '''Makes the viewManager's target the specified value.''' self.target = target if isinstance(self.target, PlayerSprite): # Move directly to looking at player. self._focus = target.pos elif isinstance(self.target, (tuple, list)): self.target = self.trimTargetToMap(self.target) else: countdown, players, zones = self.autoFocusInfo if not players and not zones: self._oldTargetPt = self._focus def getTargetPlayer(self): if isinstance(self.target, PlayerSprite): return self.target return None def getTargetPoint(self): '''Returns the position of the current target.''' if self.target is None: return self._focus return getattr(self.target, 'pos', self.target) def drawLoading(self, screen): if self.loadingPlayer is None: player = Player(self.universe.universe, 'Loading...', None, '') player.motionState = 'ground' player.updateState('right', True) self.loadingPlayer = PlayerSprite(self.app, self.universe, player, timer=timeNow) if self.loadingScreen is None: self.loadingScreen = pygame.Surface(screen.get_size()) self.loadingScreen.fill((255, 255, 255)) font = self.app.screenManager.fonts.mainMenuFont colour = self.app.theme.colours.mainMenuColour text = font.render(self.app, 'Loading...', True, colour, (255, 255, 255)) r = text.get_rect() r.center = self.loadingScreen.get_rect().center self.loadingScreen.blit(text, r) self.loadingPlayer.rect.midbottom = r.midtop if random.random() < 0.03: self.loadingPlayer.unit.lookAt(random.random() * 2 * math.pi) screen.blit(self.loadingScreen, (0, 0)) self.loadingPlayer.update() screen.fill((255, 255, 255), self.loadingPlayer.rect) screen.blit(self.loadingPlayer.image, self.loadingPlayer.rect) def draw(self, screen, drawCoins=True): '''Draws the current state of the universe at the current viewing location on the screen. Does not call pygame.display.flip()''' if self.universe.universe.loading: self.drawLoading(screen) return # Update where we're looking at. self.updateFocus() if self.sRect.topleft != (0, 0): screen.fill((0, 0, 0)) oldClip = screen.get_clip() screen.set_clip(self.sRect) self.backgroundDrawer.draw(screen, self.sRect, self._focus, drawCoins) self._drawSprites(screen) self.drawOverlay(screen) screen.set_clip(oldClip) def drawOverlay(self, screen): area = self.sRect target = self.getTargetPlayer() if target is not None: physics = target.world.physics gunRange = physics.shotLifetime * physics.shotSpeed radius = int(gunRange * MAP_TO_SCREEN_SCALE + 0.5) pygame.draw.circle(screen, (192, 64, 64), area.center, radius, 1) def _drawSprites(self, screen): focus = self._focus area = self.sRect # Go through and update the positions of the players on the screen. ntGroup = set() visPlayers = set() for player in self.universe.iterPlayers(): self.addSpritesForPlayer(player, visPlayers, ntGroup) hook = player.items.get(GrapplingHook) if hook and hook.hookState != HOOK_NOT_ACTIVE: pygame.draw.line( screen, (255, 0, 0), mapPosToScreen(player.pos, focus, area), mapPosToScreen(hook.hookPosition, focus, area), 5) # Draw the on-screen players and nametags. for s in visPlayers: s.update() screen.blit(s.image, s.rect) for s in ntGroup: screen.blit(s.image, s.rect) def drawSprite(sprite, pos=None): # Calculate the position of the sprite. if pos is None: pos = sprite.pos sprite.rect.center = mapPosToScreen(pos, focus, area) if sprite.rect.colliderect(area): sprite.update() screen.blit(sprite.image, sprite.rect) # Draw the shots. for shot in self.universe.iterShots(): drawSprite(shot) for coin in self.universe.iterCollectableCoins(): drawSprite(coin) try: # Draw the grenades. for grenade in self.universe.iterGrenades(): drawSprite(grenade) except Exception as e: log.exception(str(e)) for sprite in self.universe.iterExtras(): drawSprite(sprite) if __debug__ and globaldebug.enabled: if globaldebug.showSpriteCircles: for pos, radius in globaldebug.getSpriteCircles(): screenPos = mapPosToScreen(pos, focus, area) pygame.draw.circle(screen, (255, 255, 0), screenPos, radius, 2) for sprite in visPlayers: sprite.player.onOverlayDebugHook(self, screen, sprite) for region in self.universe.universe.regions: region.debug_draw(self, screen) def addSpritesForPlayer(self, player, visPlayers, ntGroup): focus = self._focus area = self.sRect targetPlayer = self.getTargetPlayer() showPlayer = (not player.invisible or targetPlayer is None or player.isFriendsWith(targetPlayer.player)) if showPlayer: # Calculate the position of the player. if player is targetPlayer: player.rect.center = area.center else: player.rect.center = mapPosToScreen(player.pos, focus, area) # Check if this player needs its nametag shown. if player.rect.colliderect(area): visPlayers.add(player) if ntGroup is None: return if player.unit.items.has(Bomber): player.countdown.update() player.countdown.rect.midbottom = player.rect.midtop ntGroup.add(player.countdown) lastPoint = player.rect.midbottom shield = player.unit.items.get(Shield) if shield: shieldBar = player.shieldBar shieldBar.setHealth(shield.protections, shield.maxProtections) if shieldBar.visible: ntGroup.add(shieldBar) shieldBar.rect.midtop = lastPoint lastPoint = shieldBar.rect.midbottom healthBar = player.healthBar healthBar.setHealth( player.unit.health, player.unit.world.physics.playerRespawnHealth) if healthBar.visible: ntGroup.add(healthBar) healthBar.rect.midtop = lastPoint lastPoint = healthBar.rect.midbottom player.nametag.rect.midtop = lastPoint # Check that entire nametag's on screen. if player.nametag.rect.left < area.left: player.nametag.rect.left = area.left elif player.nametag.rect.right > area.right: player.nametag.rect.right = area.right if player.nametag.rect.top < area.top: player.nametag.rect.top = area.top elif player.nametag.rect.bottom > area.bottom: player.nametag.rect.bottom = area.bottom ntGroup.add(player.nametag) if not player.dead: # Place the coin rectangle below the nametag. mx, my = player.nametag.rect.midbottom player.coinTally.setCoins(player.getCoinDisplayCount()) player.coinTally.rect.midtop = (mx, my - 5) ntGroup.add(player.coinTally) def updateFocus(self): '''Updates the location that the ViewManager is focused on. First calculates where it would ideally be focused, then moves the focus towards that point. The focus cannot move faster than self.maxSpeed pix/s, and will only accelerate or decelerate at self.acceleration pix/s/s. This method returns the negative of the amount scrolled by. This is useful for moving the backdrop by the right amount. ''' # Calculate where we should be looking at. if isinstance(self.target, PlayerSprite): # Take into account where the player's looking. targetPt = self.target.pos # If the player no longer exists, look wherever we want. if not self.universe.hasPlayer(self.target.player): self.setTarget(None) elif isinstance(self.target, (tuple, list)): targetPt = self.target else: targetPt = self.followAction() # Calculate time that's passed. now = timeNow() deltaT = now - self.lastUpdateTime self.lastUpdateTime = now # Calculate distance to target. self._oldTargetPt = targetPt sTarget = sum((targetPt[i] - self._focus[i])**2 for i in (0, 1))**0.5 if sTarget == 0: return (0, 0) if self.target is not None: s = sTarget else: # Calculate the maximum velocity that will result in deceleration # to reach target. This is based on v**2 = u**2 + 2as vDecel = (2. * self.acceleration * sTarget)**0.5 # Actual velocity is limited by this and maximum velocity. self.speed = min(self.maxSpeed, vDecel, self.speed + self.acceleration * deltaT) # Distance travelled should never overshoot the target. s = min(sTarget, self.speed * deltaT) # How far does the backdrop need to move by? # (This will be negative what the focus moves by.) deltaBackdrop = tuple(-s * (targetPt[i] - self._focus[i]) / sTarget for i in (0, 1)) # Calculate the new focus. self._focus = tuple( round(self._focus[i] - deltaBackdrop[i], 0) for i in (0, 1)) def getZoneAtPoint(self, pt): x, y = screenToMapPos(pt, self._focus, self.sRect) i, j = MapLayout.getMapBlockIndices(x, y) try: return self.universe.map.zoneBlocks[i][j].getZoneAtPoint(x, y) except IndexError: return None def followAction(self): # Follow the action. countdown, players, zones = self.autoFocusInfo targetPt = tuple(self._focus) if self.universe.getPlayerCount() == 0: # No players anywhere. No change. self.autoFocusInfo = (0, set(), set()) return targetPt # First check for non-existent players. for p in list(players): if not self.universe.hasPlayer(p): players.remove(p) # Every 10 iterations recheck for players that have entered # view area. r = pygame.Rect(self.sRect) r.width //= MAP_TO_SCREEN_SCALE r.height //= MAP_TO_SCREEN_SCALE r.center = self._oldTargetPt if countdown <= 0: players.clear() for p in self.universe.iterPlayers(): if r.collidepoint(p.pos): players.add(p) zones.clear() for z in self.universe.map.zones: if r.collidepoint(z.defn.pos): if any(t != z.owner for t in z.teamsAbleToTag()): zones.add(z) countdown = 10 else: # Keep track of which players are still visible. for p in list(players): if not r.collidepoint(p.pos): players.remove(p) countdown -= 1 if len(players) + len(zones) <= 1: # Nothing interesting in view. Look for action. maxP = 0 curZone = None for z in self.universe.zones: count = len(self.universe.getPlayersInZone(z)) if any(t != z.owner for t in z.teamsAbleToTag()): count += 2 if count > maxP: maxP = count curZone = z if curZone is None: players = set() else: players = set(self.universe.getPlayersInZone(curZone)) countdown = 20 # Look at centre-of-range of these players. if players or zones: interestingPoints = [] for p in players: interestingPoints.append(p.pos) for z in zones: interestingPoints.append(z.defn.pos) minPos = [min(pt[i] for pt in interestingPoints) for i in (0, 1)] maxPos = [max(pt[i] for pt in interestingPoints) for i in (0, 1)] targetPt = [0.5 * (minPos[i] + maxPos[i]) for i in (0, 1)] # No need to ever look beyond the boundary of the map targetPt = self.trimTargetToMap(targetPt) r.center = targetPt self.autoFocusInfo = (countdown, players, zones) return targetPt def trimTargetToMap(self, targetPt): # No need to ever look beyond the boundary of the map mapRect = pygame.Rect((0, 0), self.universe.map.layout.worldSize) r = pygame.Rect(self.sRect) r.width //= MAP_TO_SCREEN_SCALE r.height //= MAP_TO_SCREEN_SCALE r.center = targetPt if r.width > mapRect.width: r.centerx = mapRect.centerx else: r.right = min(r.right, mapRect.right) r.left = max(r.left, mapRect.left) if r.height > mapRect.height: r.centery = mapRect.centery else: r.bottom = min(r.bottom, mapRect.bottom) r.top = max(r.top, mapRect.top) return r.center