def __init__(self, screen): super(PlatformingScene, self).__init__(screen) self.background_drawn = False self.headersize = 30 self.camera = pygame.rect.Rect(0, 0, game_constants.w, game_constants.h) self.special_chars = SpecialChars() self.movelist = [] self.time_elapsed = 0 self.health = Health(5) self.score = Score(screen) self.player = Player( (screen.get_width() / 2, screen.get_height() / 2 - 30)) self.playergroup = RenderUpdatesDraw(self.player) self.platforms = {} self.platforms[game_constants.h - 80] = [ general.Box(-10000, game_constants.h - 80, 20000, 16) ] self.statics = RenderUpdatesDraw() self.statics.add(self.platforms.values()[0]) self.screenstatics = RenderUpdatesDraw() self.actives = RenderUpdatesDraw() self.powerups = RenderUpdatesDraw() self.place_platforms() self.player.colliders = self.screenstatics self.header = pygame.Surface((screen.get_width(), self.headersize)) self.header.fill(Color.BLACK) self.background = pygame.Surface(screen.get_size()).convert() gradiation = 128.0 for i in xrange(int(gradiation)): color = (150 - (i * 150 / gradiation), 150 - (i * 150 / gradiation), 200) rect = (0, i * game_constants.h / gradiation, game_constants.w, game_constants.h / gradiation + 1) self.background.fill(color, rect) # Move sprites into or out of 'screenstatics' group based on whether they're in camera self.screencheck() # fixme: rearchitect this self.challenging = False
def __init__(self, screen): super(PlatformingScene, self).__init__(screen) self.background_drawn = False self.headersize = 30 self.camera = pygame.rect.Rect(0, 0, game_constants.w, game_constants.h) self.special_chars = SpecialChars() self.movelist = [] self.time_elapsed = 0 self.health = Health(5) self.score = Score(screen) self.player = Player((screen.get_width() / 2, screen.get_height() / 2 - 30)) self.playergroup = RenderUpdatesDraw(self.player) self.platforms = {} self.platforms[game_constants.h - 80] = [general.Box(-10000, game_constants.h - 80, 20000, 16)] self.statics = RenderUpdatesDraw() self.statics.add(self.platforms.values()[0]) self.screenstatics = RenderUpdatesDraw() self.actives = RenderUpdatesDraw() self.powerups = RenderUpdatesDraw() self.place_platforms() self.player.colliders = self.screenstatics self.header = pygame.Surface((screen.get_width(), self.headersize)) self.header.fill(Color.BLACK) self.background = pygame.Surface(screen.get_size()).convert() gradiation = 128.0 for i in xrange(int(gradiation)): color = (150 - (i * 150 / gradiation), 150 - (i * 150 / gradiation), 200) rect = (0, i * game_constants.h / gradiation, game_constants.w, game_constants.h / gradiation + 1) self.background.fill(color, rect) # Move sprites into or out of 'screenstatics' group based on whether they're in camera self.screencheck() # fixme: rearchitect this self.challenging = False
def __init__(self, position): super(Player, self).__init__() self.state = States.falling self.colliders = None self.loadanims() self.direction = 'r' self.jumpdir = 'u' self.moving = False self.delayframes = 0 self.weapon = Weapons.normal self.shots_left = 0 self.bullets = RenderUpdatesDraw() self.velocity = 0 self.selected_opponent = None self.position = position self.setanim() # Also sets self.rect for collisions
class PlatformingScene(BaseScene): """ Class for the main playable game environment. """ def __init__(self, screen): super(PlatformingScene, self).__init__(screen) self.background_drawn = False self.headersize = 30 self.camera = pygame.rect.Rect(0, 0, game_constants.w, game_constants.h) self.special_chars = SpecialChars() self.movelist = [] self.time_elapsed = 0 self.health = Health(5) self.score = Score(screen) self.player = Player( (screen.get_width() / 2, screen.get_height() / 2 - 30)) self.playergroup = RenderUpdatesDraw(self.player) self.platforms = {} self.platforms[game_constants.h - 80] = [ general.Box(-10000, game_constants.h - 80, 20000, 16) ] self.statics = RenderUpdatesDraw() self.statics.add(self.platforms.values()[0]) self.screenstatics = RenderUpdatesDraw() self.actives = RenderUpdatesDraw() self.powerups = RenderUpdatesDraw() self.place_platforms() self.player.colliders = self.screenstatics self.header = pygame.Surface((screen.get_width(), self.headersize)) self.header.fill(Color.BLACK) self.background = pygame.Surface(screen.get_size()).convert() gradiation = 128.0 for i in xrange(int(gradiation)): color = (150 - (i * 150 / gradiation), 150 - (i * 150 / gradiation), 200) rect = (0, i * game_constants.h / gradiation, game_constants.w, game_constants.h / gradiation + 1) self.background.fill(color, rect) # Move sprites into or out of 'screenstatics' group based on whether they're in camera self.screencheck() # fixme: rearchitect this self.challenging = False def lazy_redraw(self): return False def fill_level(self, height): platform_level = self.platforms.setdefault(height, []) max_right = self.camera.right + game_constants.w min_left = self.camera.left - game_constants.w # cull platforms that have gone too far -- TODO this doesn't work yet bad_platforms = [ p for p in platform_level if p.rect.left > max_right or p.rect.right < min_left ] [platform_level.remove(p) for p in bad_platforms] [self.statics.remove(p) for p in bad_platforms] # add on platforms until the limit is reached if not len(platform_level): leftmost = random.randint(-game_constants.w, game_constants.w) rightmost = leftmost = random.randint(-game_constants.w, game_constants.w) else: leftmost = min([p.rect.left for p in platform_level]) rightmost = max([p.rect.right for p in platform_level]) while rightmost < max_right: new_start = rightmost + random.randint(150, 300) new_width = random.randint(200, 1000) new_width = new_width - new_width % 16 # Cut off to the nearest multiple of 16 new_p = Platform(new_start, height, new_width, 16) platform_level.append(new_p) self.statics.add(new_p) rightmost = new_start + new_width while leftmost > min_left: new_start = leftmost - random.randint(150, 300) new_width = random.randint(200, 1000) new_width = new_width - new_width % 16 # Cut off to the nearest multiple of 16 new_p = Platform(new_start - new_width, height, new_width, 16) platform_level.append(new_p) self.statics.add(new_p) leftmost = new_start - new_width def place_platforms(self): # Generate platforms based on camera position: Make sure there are always platforms extending at least as far as +-4*game_constants.w, +-4*game_constants.h from the player max_height = self.camera.top - 2 * game_constants.h lowest = max(self.platforms.keys()) highest = min(self.platforms.keys()) for h in range(highest, lowest, 80): self.fill_level(h) while highest > max_height: highest -= 80 self.fill_level(highest) def draw_background(self): self.screen.blit(self.background, (0, 0)) self.screen.blit(self.header, (0, 0)) pygame.display.update() self.background_drawn = True def draw_header(self): dirty = [] self.screen.set_clip(0, 0, game_constants.w, self.headersize) # Only draw in header area self.score.clear(self.screen, self.header) self.health.clear(self.screen, self.header) dirty += self.score.draw(self.screen, self.time_elapsed) dirty += self.health.draw(self.screen) return dirty def draw(self): if not self.background_drawn: self.draw_background() self.camshift() header_dirty = self.draw_header() dirty = [] # Don't draw over header self.screen.set_clip(0, self.headersize, game_constants.w, game_constants.h) rect_sources = [ self.screenstatics, self.powerups, self.actives, self.playergroup, self.player.bullets, ] for rect_source in rect_sources: rect_source.clear(self.screen, self.background) for rect_source in rect_sources: dirty += rect_source.draw(self.screen, self.camera) # hack: repaint selected_opponent to make sure it's visible if self.player.selected_opponent: dirty += [ self.player.selected_opponent.draw(self.screen, self.camera) ] # Constrain all dirty rectangles in main game area to main game area. dirty = [ dirty_rect.clip(self.screen.get_clip()) for dirty_rect in dirty ] return header_dirty + dirty def camshift(self): newpos = self.player.rect bounds = self.camera.inflate(-game_constants.w + 50, -game_constants.h + 100) # Move the screen if we're near the edge if newpos.right > bounds.right: self.camera = self.camera.move(newpos.right - bounds.right, 0) elif newpos.left < bounds.left: self.camera = self.camera.move(newpos.left - bounds.left, 0) if newpos.bottom > bounds.bottom: self.camera = self.camera.move(0, newpos.bottom - bounds.bottom) elif newpos.top < bounds.top: self.camera = self.camera.move(0, newpos.top - bounds.top) self.screencheck() def screencheck(self): """Classify objects by whether they are on the screen""" for platform_level in self.platforms.values(): for platform in platform_level: if platform not in self.screenstatics: # This platform wasn't on the screen: is it now? if platform.rect.colliderect(self.camera): self.screenstatics.add(platform) # If it's a platform (has the attribute'word') give it an # identifying word while it's onscreen if hasattr(platform, 'word'): platform.word = Word(self.special_chars.new(), platform.font) else: # This platform is on the screen: is it gone now? if not platform.rect.colliderect(self.camera): self.screenstatics.remove(platform) # If it's a platform (has the attribute'word') # and it's out of camera, free up its symbol for later use if hasattr(platform, 'word'): self.special_chars.release(platform.word.string) platform.word = None for sprite in self.powerups.sprites(): if not sprite.rect.colliderect( self.camera.inflate(game_constants.w, 0)): sprite.kill() for sprite in self.actives.sprites( ): # need a list because we're going to delete from it # Opponents are allowed to be a full screen width outside of camera, but no further if not sprite.rect.colliderect( self.camera.inflate(game_constants.w, 0)): if sprite == self.player.selected_opponent: self.player.selected_opponent = None sprite.kill() def is_reachable(self, platform): player_rect = self.player.rect """ Is it possible for the player to jump to this platform in the concievable future? """ # Test 1: the platform must be within jumping height if player_rect.bottom - game_constants.maxjump_height < platform.rect.top < player_rect.bottom: # Test 2: the player cannot be under the platform if (player_rect.right < platform.rect.left): # Test 3: the path from the player position to the optimal jump position must be contiguous jumpdist = platform.rect.left - player_rect.right if jumpdist < game_constants.maxjump_width: # Maximum distance that can be jumped return True else: start = (player_rect.right, player_rect.bottom) size = (jumpdist - game_constants.maxjump_width, 1) testrect = pygame.rect.Rect(start, size) for collider in self.screenstatics: if collider.rect.contains(testrect): return True elif (player_rect.left > platform.rect.right): # Test 3: the path from the player position to the optimal jump position must be contiguous jumpdist = player_rect.left - platform.rect.right if jumpdist < game_constants.maxjump_width: # Maximum distance that can be jumped return True else: start = (platform.rect.right + game_constants.maxjump_width, player_rect.bottom) size = (jumpdist - game_constants.maxjump_width, 1) testrect = pygame.rect.Rect(start, size) for collider in self.screenstatics: if collider.rect.contains(testrect): return True def tick(self, elapsed): self.place_platforms() self.time_elapsed += elapsed for direction in self.movelist: self.player.direct(direction) for active_obj in self.actives: active_obj.tick(self.player.rect.center) for powerup in self.powerups: powerup.tick() for screenstatic in self.screenstatics: # Objects on the screen # Only platforms have a 'word' attribute if hasattr(screenstatic, 'word'): # Will it be possible to get to this object without having to walk on air screenstatic.reachable = self.is_reachable(screenstatic) self.player.tick() if (self.health.value() <= 0): self.switch_to = GameOverScene(self.screen) def type_special(self, key): # Typing a special character for platform_level in self.platforms.values(): for platform in platform_level: if hasattr(platform, 'word'): # HACK to ignore bottom platform if platform.reachable and self.camera.colliderect( platform.rect): if key == platform.contents(): return platform
class Player(WrappedSprite): """ Logic, graphics and methods for a game protagonist """ def __init__(self, position): super(Player, self).__init__() self.state = States.falling self.colliders = None self.loadanims() self.direction = 'r' self.jumpdir = 'u' self.moving = False self.delayframes = 0 self.weapon = Weapons.normal self.shots_left = 0 self.bullets = RenderUpdatesDraw() self.velocity = 0 self.selected_opponent = None self.position = position self.setanim() # Also sets self.rect for collisions @classmethod def loadImages(cls): super(Player, cls).loadImages() cls.images['upright'] = loadframes("gunstar", ["gunup1.png", "gunup2.png"]) cls.images['upleft'] = flippedframes(cls.images['upright']) cls.images['upsideright'] = loadframes( "gunstar", ["gunupside1.png", "gunupside2.png"]) cls.images['upsideleft'] = flippedframes(cls.images['upsideright']) cls.images['sideright'] = loadframes("gunstar", ["gunside1.png", "gunside2.png"]) cls.images['sideleft'] = flippedframes(cls.images['sideright']) cls.images['downsideright'] = loadframes( "gunstar", ["gundownside1.png", "gundownside2.png"]) cls.images['downsideleft'] = flippedframes(cls.images['downsideright']) cls.images['downright'] = loadframes("gunstar", ["gundown1.png", "gundown2.png"]) cls.images['downleft'] = flippedframes(cls.images['downright']) cls.images['idleright'] = loadframes("gunstar", ["rest1.png", "rest2.png"]) cls.images['idleleft'] = flippedframes(cls.images['idleright']) cls.images['runright'] = loadframes("gunstar", n_of("run%i.png", 6)) cls.images['runleft'] = flippedframes(cls.images['runright']) cls.images['jumpright'] = loadframes("gunstar", ["jump1.png", "jump2.png"]) cls.images['jumpleft'] = flippedframes(cls.images['jumpright']) cls.images['fallright'] = loadframes("gunstar", ["jump3.png"]) cls.images['fallleft'] = flippedframes(cls.images['fallright']) cls.images['hitright'] = loadframes("gunstar", ["hit1.png", "hit2.png"]) cls.images['hitleft'] = flippedframes(cls.images['hitright']) def loadanims(self): self.anims = { States.shooting: { 'upright': Anim(self.images['upright'], (5, 5)), 'upleft': Anim(self.images['upleft'], (5, 5)), 'upsideright': Anim(self.images['upsideright'], (5, 5)), 'upsideleft': Anim(self.images['upsideleft'], (5, 5)), 'sideright': Anim(self.images['sideright'], (5, 5)), 'sideleft': Anim(self.images['sideleft'], (5, 5)), 'downsideright': Anim(self.images['downsideright'], (5, 5)), 'downsideleft': Anim(self.images['downsideleft'], (5, 5)), 'downright': Anim(self.images['downright'], (5, 5)), 'downleft': Anim(self.images['downleft'], (5, 5)), }, States.idle: { 'r': Anim(self.images['idleright'], (30, 60)), 'l': Anim(self.images['idleleft'], (30, 60)), }, States.running: { 'r': Anim(self.images['runright'], (14, ) * 6), 'l': Anim(self.images['runleft'], (14, ) * 6), }, States.jumping: { 'r': Anim(self.images['jumpright'], (20, 40)), 'l': Anim(self.images['jumpleft'], (20, 40)), }, States.falling: { 'r': Anim(self.images['fallright'], (20, )), 'l': Anim(self.images['fallleft'], (20, )), }, States.hit: { 'r': Anim(self.images['hitright'], (5, 5)), 'l': Anim(self.images['hitleft'], (5, 5)), }, } def draw(self, surface, campos): return self.current_anim.draw(surface, self.rect.move((-campos[0], -campos[1]))) def setanim(self): # Set animation from state and direction if self.state != States.shooting: self.current_anim = self.anims[self.state][self.direction] # Want to ensure that new bottom = old bottom self.rect = self.current_anim.get_rect() self.rect.bottom = int(self.position[1]) self.rect.centerx = int(self.position[0]) def collide(self): return pygame.sprite.spritecollide(self, self.colliders, False) def direct(self, direction): self.moving = True if direction == K_LEFT: self.direction = 'l' if self.state not in (States.jumping, States.falling, States.hit): self.state = States.running elif direction == K_RIGHT: self.direction = 'r' if self.state not in (States.jumping, States.falling, States.hit): self.state = States.running def tick(self): self.delayframes += 1 if self.state in (States.falling, States.jumping, States.hit): if self.velocity > game_constants.terminal_velocity: self.velocity -= .25 # If we're falling, check how far we are from the nearest ground. If we're further than one tick's distance, # move jumpspeed units. If we're closer than one tick's distance, move directly to the ground. if self.state in (States.falling, States.hit): dist = distfromground(self.rect, self.colliders) if dist <= abs(self.velocity): self.move_vertical(dist) if self.state == States.hit: if self.delayframes > game_constants.hitframes: self.moving = True self.state = States.running elif self.moving: self.state = States.running else: self.state = States.idle else: self.move_vertical(-self.velocity) elif self.state == States.jumping: dist = distfromceiling(self.rect, self.colliders) if dist <= self.velocity: self.move_vertical(-dist) self.state = States.falling self.velocity = 0 else: self.move_vertical(-self.velocity) if self.velocity <= 0 or self.collide(): self.state = States.falling elif self.state == States.running: newdist = distfromground(self.rect, self.colliders) if newdist > 0: self.state = States.falling if not self.moving: self.state = States.idle # Not moving but run animation is being displayed if self.moving: jump_slow = False if self.state == States.jumping and self.jump_target <> None: # Determine if moving laterally too fast will botch the jump # to wit: will the top of the player rect be higher than the bottom of the platform # before the edge of the player closest to the platform is within the platform # Ugly fudge factor of five pixel units included. if self.direction == 'l': if (self.rect.left - game_constants.speed - 5) < self.jump_target.rect.right and \ (self.rect.top > self.jump_target.rect.bottom): jump_slow = True elif self.direction == 'r': if (self.rect.right + game_constants.speed + 5) > self.jump_target.rect.left and \ (self.rect.top > self.jump_target.rect.bottom): jump_slow = True if self.state in ( States.running, States.jumping, States.falling ) and not jump_slow: # Left-Right movement is allowed oldpos, oldrect = self.position, self.rect if self.direction == 'l': self.move_horizontal(-game_constants.speed) elif self.direction == 'r': self.move_horizontal(game_constants.speed) if self.collide(): self.position, self.rect = oldpos, oldrect self.setanim() self.current_anim.tick() if self.selected_opponent: self.point(self.selected_opponent ) # Point weapon in direction of selected thing for bullet in self.bullets: bullet.move() # Move all the laser-things that might be on screen self.moving = False # Require the move() function to refresh this every tick # fixme: these are dumb def move_vertical(self, vdist): self.position = self.position[0], self.position[1] + vdist self.rect.move(0, vdist) def move_horizontal(self, hdist): self.position = self.position[0] + hdist, self.position[1] self.rect.move(hdist, 0) def idle(self): self.state = States.idle def jump(self, jump_target=None): if self.state in (States.jumping, States.falling): return self.jump_target = jump_target self.delayframes = 0 self.anims[States.jumping]['r'].reset() self.anims[States.jumping]['l'].reset() self.state = States.jumping self.velocity = 7 def hit(self): # Player was injured self.moving = False self.state = States.hit self.velocity = 0 self.delayframes = 0 def point(self, selected): # Point gun at some quadrant # 1 _|_ 2 # 4 | 3 if (self.state not in (States.falling, States.jumping)): self.state = States.shooting x = self.rect.center[0] - selected.x y = self.rect.center[1] - selected.y h = math.sqrt(x**2 + y**2) angle = math.degrees(math.asin(y / h)) anim_name = None if angle > 0: # Quadrant 1 or 2 if x < 0: # Quadrant 2: up, diag or side if abs(angle) < 30: anim_name = 'sideright' elif 30 < abs(angle) < 70: anim_name = 'upsideright' else: anim_name = 'upright' else: # Quadrant 1: up, diag or side if abs(angle) < 30: anim_name = 'sideleft' elif 30 < abs(angle) < 70: anim_name = 'upsideleft' else: anim_name = 'upleft' else: # Quadrant 3 or 4 if x < 0: # Quadrant 3: side, diag or down if abs(angle) < 30: anim_name = 'sideright' elif 30 < abs(angle) < 70: anim_name = 'downsideright' else: anim_name = 'downright' else: # Quadrant 4: side, diag or down if abs(angle) < 30: anim_name = 'sideleft' elif 30 < abs(angle) < 70: anim_name = 'downsideleft' else: anim_name = 'downleft' self.current_anim = self.anims[States.shooting][anim_name] def shoot(self, selected): # Gunshots emerge from center of player... # could later make this be exact gun position by dicting anims player = self.rect.center # Reset powerup weapons after 10 shots. if self.weapon != Weapons.normal: self.shots_left -= 1 if self.shots_left < 0: self.weapon = Weapons.normal if self.weapon == Weapons.normal: shotcount = 1 elif self.weapon == Weapons.shotgun: shotcount = 3 for _ in xrange(shotcount): # Fudge the destination point a bit to make it look like shots are being # sent out haphazardly instead of to the same point every time fudgex = selected.x + random.randint(-selected.rect.width / 2, selected.rect.width / 2) fudgey = selected.y + random.randint(-selected.rect.height / 2, selected.rect.height / 2) distx, disty = (player[0] - fudgex, player[1] - fudgey) hypotenuse = math.sqrt(distx**2 + disty**2) nx, ny = distx / hypotenuse, disty / hypotenuse ttl = int(hypotenuse / game_constants.bulletspeed) self.bullets.add( Bullet(self, self.weapon, (player[0] - nx * 4, player[1] - ny * 4), [nx, ny], selected, ttl))
class PlatformingScene(BaseScene): """ Class for the main playable game environment. """ def __init__(self, screen): super(PlatformingScene, self).__init__(screen) self.background_drawn = False self.headersize = 30 self.camera = pygame.rect.Rect(0, 0, game_constants.w, game_constants.h) self.special_chars = SpecialChars() self.movelist = [] self.time_elapsed = 0 self.health = Health(5) self.score = Score(screen) self.player = Player((screen.get_width() / 2, screen.get_height() / 2 - 30)) self.playergroup = RenderUpdatesDraw(self.player) self.platforms = {} self.platforms[game_constants.h - 80] = [general.Box(-10000, game_constants.h - 80, 20000, 16)] self.statics = RenderUpdatesDraw() self.statics.add(self.platforms.values()[0]) self.screenstatics = RenderUpdatesDraw() self.actives = RenderUpdatesDraw() self.powerups = RenderUpdatesDraw() self.place_platforms() self.player.colliders = self.screenstatics self.header = pygame.Surface((screen.get_width(), self.headersize)) self.header.fill(Color.BLACK) self.background = pygame.Surface(screen.get_size()).convert() gradiation = 128.0 for i in xrange(int(gradiation)): color = (150 - (i * 150 / gradiation), 150 - (i * 150 / gradiation), 200) rect = (0, i * game_constants.h / gradiation, game_constants.w, game_constants.h / gradiation + 1) self.background.fill(color, rect) # Move sprites into or out of 'screenstatics' group based on whether they're in camera self.screencheck() # fixme: rearchitect this self.challenging = False def lazy_redraw(self): return False def fill_level(self, height): platform_level = self.platforms.setdefault(height, []) max_right = self.camera.right + game_constants.w min_left = self.camera.left - game_constants.w # cull platforms that have gone too far -- TODO this doesn't work yet bad_platforms = [p for p in platform_level if p.rect.left > max_right or p.rect.right < min_left] [platform_level.remove(p) for p in bad_platforms] [self.statics.remove(p) for p in bad_platforms] # add on platforms until the limit is reached if not len(platform_level): leftmost = random.randint(-game_constants.w, game_constants.w) rightmost = leftmost = random.randint(-game_constants.w, game_constants.w) else: leftmost = min([p.rect.left for p in platform_level]) rightmost = max([p.rect.right for p in platform_level]) while rightmost < max_right: new_start = rightmost + random.randint(150, 300) new_width = random.randint(200, 1000) new_width = new_width - new_width % 16 # Cut off to the nearest multiple of 16 new_p = Platform(new_start, height, new_width, 16) platform_level.append(new_p) self.statics.add(new_p) rightmost = new_start + new_width while leftmost > min_left: new_start = leftmost - random.randint(150, 300) new_width = random.randint(200, 1000) new_width = new_width - new_width % 16 # Cut off to the nearest multiple of 16 new_p = Platform(new_start - new_width, height, new_width, 16) platform_level.append(new_p) self.statics.add(new_p) leftmost = new_start - new_width def place_platforms(self): # Generate platforms based on camera position: Make sure there are always platforms extending at least as far as +-4*game_constants.w, +-4*game_constants.h from the player max_height = self.camera.top - 2 * game_constants.h lowest = max(self.platforms.keys()) highest = min(self.platforms.keys()) for h in range(highest, lowest, 80): self.fill_level(h) while highest > max_height: highest -= 80 self.fill_level(highest) def draw_background(self): self.screen.blit(self.background, (0, 0)) self.screen.blit(self.header, (0, 0)) pygame.display.update() self.background_drawn = True def draw_header(self): dirty = [] self.screen.set_clip(0, 0, game_constants.w, self.headersize) # Only draw in header area self.score.clear(self.screen, self.header) self.health.clear(self.screen, self.header) dirty += self.score.draw(self.screen, self.time_elapsed) dirty += self.health.draw(self.screen) return dirty def draw(self): if not self.background_drawn: self.draw_background() self.camshift() header_dirty = self.draw_header() dirty = [] # Don't draw over header self.screen.set_clip(0, self.headersize, game_constants.w, game_constants.h) rect_sources = [ self.screenstatics, self.powerups, self.actives, self.playergroup, self.player.bullets, ] for rect_source in rect_sources: rect_source.clear(self.screen, self.background) for rect_source in rect_sources: dirty += rect_source.draw(self.screen, self.camera) # hack: repaint selected_opponent to make sure it's visible if self.player.selected_opponent: dirty += [self.player.selected_opponent.draw(self.screen, self.camera)] # Constrain all dirty rectangles in main game area to main game area. dirty = [dirty_rect.clip(self.screen.get_clip()) for dirty_rect in dirty] return header_dirty + dirty def camshift(self): newpos = self.player.rect bounds = self.camera.inflate(-game_constants.w + 50, -game_constants.h + 100) # Move the screen if we're near the edge if newpos.right > bounds.right: self.camera = self.camera.move(newpos.right - bounds.right, 0) elif newpos.left < bounds.left: self.camera = self.camera.move(newpos.left - bounds.left, 0) if newpos.bottom > bounds.bottom: self.camera = self.camera.move(0, newpos.bottom - bounds.bottom) elif newpos.top < bounds.top: self.camera = self.camera.move(0, newpos.top - bounds.top) self.screencheck() def screencheck(self): """Classify objects by whether they are on the screen""" for platform_level in self.platforms.values(): for platform in platform_level: if platform not in self.screenstatics: # This platform wasn't on the screen: is it now? if platform.rect.colliderect(self.camera): self.screenstatics.add(platform) # If it's a platform (has the attribute'word') give it an # identifying word while it's onscreen if hasattr(platform, 'word'): platform.word = Word(self.special_chars.new(), platform.font) else: # This platform is on the screen: is it gone now? if not platform.rect.colliderect(self.camera): self.screenstatics.remove(platform) # If it's a platform (has the attribute'word') # and it's out of camera, free up its symbol for later use if hasattr(platform, 'word'): self.special_chars.release(platform.word.string) platform.word = None for sprite in self.powerups.sprites(): if not sprite.rect.colliderect(self.camera.inflate(game_constants.w, 0)): sprite.kill() for sprite in self.actives.sprites(): # need a list because we're going to delete from it # Opponents are allowed to be a full screen width outside of camera, but no further if not sprite.rect.colliderect(self.camera.inflate(game_constants.w, 0)): if sprite == self.player.selected_opponent: self.player.selected_opponent = None sprite.kill() def is_reachable(self, platform): player_rect = self.player.rect """ Is it possible for the player to jump to this platform in the concievable future? """ # Test 1: the platform must be within jumping height if player_rect.bottom - game_constants.maxjump_height < platform.rect.top < player_rect.bottom: # Test 2: the player cannot be under the platform if (player_rect.right < platform.rect.left): # Test 3: the path from the player position to the optimal jump position must be contiguous jumpdist = platform.rect.left - player_rect.right if jumpdist < game_constants.maxjump_width: # Maximum distance that can be jumped return True else: start = (player_rect.right, player_rect.bottom) size = (jumpdist - game_constants.maxjump_width, 1) testrect = pygame.rect.Rect(start, size) for collider in self.screenstatics: if collider.rect.contains(testrect): return True elif (player_rect.left > platform.rect.right): # Test 3: the path from the player position to the optimal jump position must be contiguous jumpdist = player_rect.left - platform.rect.right if jumpdist < game_constants.maxjump_width: # Maximum distance that can be jumped return True else: start = (platform.rect.right + game_constants.maxjump_width, player_rect.bottom) size = (jumpdist - game_constants.maxjump_width, 1) testrect = pygame.rect.Rect(start, size) for collider in self.screenstatics: if collider.rect.contains(testrect): return True def tick(self, elapsed): self.place_platforms() self.time_elapsed += elapsed for direction in self.movelist: self.player.direct(direction) for active_obj in self.actives: active_obj.tick(self.player.rect.center) for powerup in self.powerups: powerup.tick() for screenstatic in self.screenstatics: # Objects on the screen # Only platforms have a 'word' attribute if hasattr(screenstatic, 'word'): # Will it be possible to get to this object without having to walk on air screenstatic.reachable = self.is_reachable(screenstatic) self.player.tick() if (self.health.value() <= 0): self.switch_to = GameOverScene(self.screen) def type_special(self, key): # Typing a special character for platform_level in self.platforms.values(): for platform in platform_level: if hasattr(platform, 'word'): # HACK to ignore bottom platform if platform.reachable and self.camera.colliderect(platform.rect): if key == platform.contents(): return platform
class Player(WrappedSprite): """ Logic, graphics and methods for a game protagonist """ def __init__(self, position): super(Player, self).__init__() self.state = States.falling self.colliders = None self.loadanims() self.direction = 'r' self.jumpdir = 'u' self.moving = False self.delayframes = 0 self.weapon = Weapons.normal self.shots_left = 0 self.bullets = RenderUpdatesDraw() self.velocity = 0 self.selected_opponent = None self.position = position self.setanim() # Also sets self.rect for collisions @classmethod def loadImages(cls): super(Player, cls).loadImages() cls.images['upright'] = loadframes("gunstar", ["gunup1.png", "gunup2.png"]) cls.images['upleft'] = flippedframes(cls.images['upright']) cls.images['upsideright'] = loadframes("gunstar", ["gunupside1.png", "gunupside2.png"]) cls.images['upsideleft'] = flippedframes(cls.images['upsideright']) cls.images['sideright'] = loadframes("gunstar", ["gunside1.png", "gunside2.png"]) cls.images['sideleft'] = flippedframes(cls.images['sideright']) cls.images['downsideright'] = loadframes("gunstar", ["gundownside1.png", "gundownside2.png"]) cls.images['downsideleft'] = flippedframes(cls.images['downsideright']) cls.images['downright'] = loadframes("gunstar", ["gundown1.png", "gundown2.png"]) cls.images['downleft'] = flippedframes(cls.images['downright']) cls.images['idleright'] = loadframes("gunstar", ["rest1.png", "rest2.png"]) cls.images['idleleft'] = flippedframes(cls.images['idleright']) cls.images['runright'] = loadframes("gunstar", n_of("run%i.png", 6)) cls.images['runleft'] = flippedframes(cls.images['runright']) cls.images['jumpright'] = loadframes("gunstar", ["jump1.png", "jump2.png"]) cls.images['jumpleft'] = flippedframes(cls.images['jumpright']) cls.images['fallright'] = loadframes("gunstar", ["jump3.png"]) cls.images['fallleft'] = flippedframes(cls.images['fallright']) cls.images['hitright'] = loadframes("gunstar", ["hit1.png", "hit2.png"]) cls.images['hitleft'] = flippedframes(cls.images['hitright']) def loadanims(self): self.anims = { States.shooting : { 'upright' : Anim(self.images['upright'], (5, 5)), 'upleft' : Anim(self.images['upleft'], (5, 5)), 'upsideright' : Anim(self.images['upsideright'], (5, 5)), 'upsideleft' : Anim(self.images['upsideleft'], (5, 5)), 'sideright' : Anim(self.images['sideright'], (5, 5)), 'sideleft' : Anim(self.images['sideleft'], (5, 5)), 'downsideright' : Anim(self.images['downsideright'], (5, 5)), 'downsideleft' : Anim(self.images['downsideleft'], (5, 5)), 'downright' : Anim(self.images['downright'], (5, 5)), 'downleft' : Anim(self.images['downleft'], (5, 5)), }, States.idle : { 'r' : Anim(self.images['idleright'], (30, 60)), 'l' : Anim(self.images['idleleft'], (30, 60)), }, States.running : { 'r' : Anim(self.images['runright'], (14,) * 6), 'l' : Anim(self.images['runleft'], (14,) * 6), }, States.jumping : { 'r' : Anim(self.images['jumpright'], (20, 40)), 'l' : Anim(self.images['jumpleft'], (20, 40)), }, States.falling : { 'r' : Anim(self.images['fallright'], (20,)), 'l' : Anim(self.images['fallleft'], (20,)), }, States.hit : { 'r' : Anim(self.images['hitright'], (5, 5)), 'l' : Anim(self.images['hitleft'], (5, 5)), }, } def draw(self, surface, campos): return self.current_anim.draw(surface, self.rect.move((-campos[0], -campos[1]))) def setanim(self): # Set animation from state and direction if self.state != States.shooting: self.current_anim = self.anims[self.state][self.direction] # Want to ensure that new bottom = old bottom self.rect = self.current_anim.get_rect() self.rect.bottom = int(self.position[1]) self.rect.centerx = int(self.position[0]) def collide(self): return pygame.sprite.spritecollide(self, self.colliders, False) def direct(self, direction): self.moving = True if direction == K_LEFT: self.direction = 'l' if self.state not in (States.jumping, States.falling, States.hit): self.state = States.running elif direction == K_RIGHT: self.direction = 'r' if self.state not in (States.jumping, States.falling, States.hit): self.state = States.running def tick(self): self.delayframes += 1 if self.state in (States.falling, States.jumping, States.hit): if self.velocity > game_constants.terminal_velocity: self.velocity -= .25 # If we're falling, check how far we are from the nearest ground. If we're further than one tick's distance, # move jumpspeed units. If we're closer than one tick's distance, move directly to the ground. if self.state in (States.falling, States.hit): dist = distfromground(self.rect, self.colliders) if dist <= abs(self.velocity): self.move_vertical(dist) if self.state == States.hit: if self.delayframes > game_constants.hitframes: self.moving = True self.state = States.running elif self.moving: self.state = States.running else: self.state = States.idle else: self.move_vertical(-self.velocity) elif self.state == States.jumping: dist = distfromceiling(self.rect, self.colliders) if dist <= self.velocity: self.move_vertical(-dist) self.state = States.falling self.velocity = 0 else: self.move_vertical(-self.velocity) if self.velocity <= 0 or self.collide(): self.state = States.falling elif self.state == States.running: newdist = distfromground(self.rect, self.colliders) if newdist > 0: self.state = States.falling if not self.moving: self.state = States.idle # Not moving but run animation is being displayed if self.moving: jump_slow = False if self.state == States.jumping and self.jump_target <> None: # Determine if moving laterally too fast will botch the jump # to wit: will the top of the player rect be higher than the bottom of the platform # before the edge of the player closest to the platform is within the platform # Ugly fudge factor of five pixel units included. if self.direction == 'l': if (self.rect.left - game_constants.speed - 5) < self.jump_target.rect.right and \ (self.rect.top > self.jump_target.rect.bottom): jump_slow = True elif self.direction == 'r': if (self.rect.right + game_constants.speed + 5) > self.jump_target.rect.left and \ (self.rect.top > self.jump_target.rect.bottom): jump_slow = True if self.state in (States.running, States.jumping, States.falling) and not jump_slow: # Left-Right movement is allowed oldpos, oldrect = self.position, self.rect if self.direction == 'l': self.move_horizontal(-game_constants.speed) elif self.direction == 'r': self.move_horizontal(game_constants.speed) if self.collide(): self.position, self.rect = oldpos, oldrect self.setanim() self.current_anim.tick() if self.selected_opponent: self.point(self.selected_opponent) # Point weapon in direction of selected thing for bullet in self.bullets: bullet.move() # Move all the laser-things that might be on screen self.moving = False # Require the move() function to refresh this every tick # fixme: these are dumb def move_vertical(self, vdist): self.position = self.position[0], self.position[1] + vdist self.rect.move(0, vdist) def move_horizontal(self, hdist): self.position = self.position[0] + hdist, self.position[1] self.rect.move(hdist, 0) def idle(self): self.state = States.idle def jump(self, jump_target = None): if self.state in (States.jumping, States.falling): return self.jump_target = jump_target self.delayframes = 0 self.anims[States.jumping]['r'].reset() self.anims[States.jumping]['l'].reset() self.state = States.jumping self.velocity = 7 def hit(self): # Player was injured self.moving = False self.state = States.hit self.velocity = 0 self.delayframes = 0 def point(self, selected): # Point gun at some quadrant # 1 _|_ 2 # 4 | 3 if (self.state not in (States.falling, States.jumping)): self.state = States.shooting x = self.rect.center[0] - selected.x y = self.rect.center[1] - selected.y h = math.sqrt(x**2 + y**2) angle = math.degrees(math.asin(y / h)) anim_name = None if angle > 0: # Quadrant 1 or 2 if x < 0: # Quadrant 2: up, diag or side if abs(angle) < 30: anim_name = 'sideright' elif 30 < abs(angle) < 70: anim_name = 'upsideright' else: anim_name = 'upright' else: # Quadrant 1: up, diag or side if abs(angle) < 30: anim_name = 'sideleft' elif 30 < abs(angle) < 70: anim_name = 'upsideleft' else: anim_name = 'upleft' else: # Quadrant 3 or 4 if x < 0: # Quadrant 3: side, diag or down if abs(angle) < 30: anim_name = 'sideright' elif 30 < abs(angle) < 70: anim_name = 'downsideright' else: anim_name = 'downright' else: # Quadrant 4: side, diag or down if abs(angle) < 30: anim_name = 'sideleft' elif 30 < abs(angle) < 70: anim_name = 'downsideleft' else: anim_name = 'downleft' self.current_anim = self.anims[States.shooting][anim_name] def shoot(self, selected): # Gunshots emerge from center of player... # could later make this be exact gun position by dicting anims player = self.rect.center # Reset powerup weapons after 10 shots. if self.weapon != Weapons.normal: self.shots_left -= 1 if self.shots_left < 0: self.weapon = Weapons.normal if self.weapon == Weapons.normal: shotcount = 1 elif self.weapon == Weapons.shotgun: shotcount = 3 for _ in xrange(shotcount): # Fudge the destination point a bit to make it look like shots are being # sent out haphazardly instead of to the same point every time fudgex = selected.x + random.randint(-selected.rect.width / 2, selected.rect.width / 2) fudgey = selected.y + random.randint(-selected.rect.height / 2, selected.rect.height / 2) distx, disty = (player[0] - fudgex, player[1] - fudgey) hypotenuse = math.sqrt(distx**2 + disty**2) nx, ny = distx / hypotenuse, disty / hypotenuse ttl = int(hypotenuse / game_constants.bulletspeed) self.bullets.add(Bullet( self, self.weapon, (player[0] - nx * 4, player[1] - ny * 4), [nx, ny], selected, ttl))