def __init__(self, name, flip_image=False, obey_gravity=False): super(Sprite, self).__init__() # Signals self.moved = Signal() self.direction_changed = Signal() self.reverse_gravity_changed = Signal() # State self.rect = pygame.Rect(0, 0, 0, 0) self.quad_trees = set() self.layer = None self.name = name self.image = None self.visible = 1 self.dirty = 2 self.velocity = (0, 0) self.obey_gravity = obey_gravity self.reverse_gravity = False self.lethal = False self.falling = False self.collidable = True self.grabbed = False self.grabbable = False self.should_check_collisions = False self.use_pixel_collisions = False self.flip_image = flip_image self.collision_rects = [] self.collision_masks = [] self._colliding_objects = set() self._direction = Direction.RIGHT self._flipped_images = {}
class Button(Sprite): def __init__(self, name): super(Button, self).__init__(name) self.pressed = Signal() def handle_collision(self, *args, **kwargs): self.pressed.emit()
class Effect(object): def __init__(self): self.timer = None self.timer_ms = 150 # Signals self.started = Signal() self.stopped = Signal() def pre_start(self): pass def pre_stop(self): pass def start(self): assert not self.timer self.pre_start() self.timer = Timer(self.timer_ms, self.on_tick) self.timer.start() self.started.emit() def stop(self): assert self.timer self.pre_stop() self.timer.stop() self.timer = None self.stopped.emit() def on_tick(self): pass
def __init__(self): self.timer = None self.timer_ms = 150 # Signals self.started = Signal() self.stopped = Signal()
def __init__(self): super(Player, self).__init__('player', flip_image=True, obey_gravity=True) self.engine = get_engine() self.should_check_collisions = True # Sprites self.propulsion_below = Sprite("propulsion_below", flip_image=True) self.propulsion_below.collidable = False self.propulsion_below.visible = 0 self.tractor_beam = TractorBeam(self) # State self.jumping = False self.hovering = False self.block_events = False self.jump_origin = None self.hover_time_ms = 0 self.health = self.MAX_HEALTH self.lives = self.MAX_LIVES self.last_safe_spot = None self.vehicle = None self.vehicle_move_cnx = None # Signals self.health_changed = Signal() self.lives_changed = Signal() self.direction_changed.connect(self.on_direction_changed) self.reverse_gravity_changed.connect(self.on_reverse_gravity_changed)
class ScreenFlashEffect(ScreenEffect): def __init__(self, *args, **kwargs): super(ScreenFlashEffect, self).__init__(*args, **kwargs) self.fade_out = True self.color = (255, 255, 255) self.flash_peaked = Signal() def pre_start(self): self.hit_peak = False self.alpha = 100 self.sprite.show() self._fill(0) def _fill(self, alpha): self.sprite.image.fill((self.color[0], self.color[1], self.color[2], alpha)) def on_tick(self): self._fill(self.alpha) if self.hit_peak: self.alpha = max(self.alpha - 20, 0) else: self.alpha = min(self.alpha + 20, 255) if self.alpha == 255: self.hit_peak = True self.flash_peaked.emit() if not self.fade_out: self.stop() elif self.alpha <= 0: self.stop()
def __init__(self): self.pages = [] self.next_page = 0 self.current_page = None self.allow_escape = True # Signals self.done = Signal()
def __init__(self, engine): self.engine = engine self.time_periods = [] self.active_area = None self.music = None self.crossover_timer = None self.crossovers = [] self.pending_crossovers = {} self.next_crossover_id = 0 self.engine.tick.connect(self.on_tick) # Signals self.area_changed = Signal() self.time_period_changed = Signal()
def __init__(self, *args, **kwargs): super(Item, self).__init__(obey_gravity=True, *args, **kwargs) self.should_check_collisions = True self.grabbable = True self.flip_image = True # Signals self.grab_changed = Signal()
class EventBox(object): def __init__(self, area): self.area = area self.rects = [] area.register_for_events(self) self.entered_objects = set() # Signals self.event_fired = Signal() self.object_moved = Signal() self.object_entered = Signal() self.object_exited = Signal() def watch_object_moves(self, obj): obj.moved.connect(lambda dx, dy: self.handle_object_move(obj)) def handle_event(self, event): return self.event_fired.emit(event) def handle_object_move(self, obj): if obj.layer.area != self.area: return if obj.rect.collidelist(self.rects) != -1: if obj not in self.entered_objects: self.entered_objects.add(obj) self.object_entered.emit(obj) self.object_moved.emit(obj) elif obj in self.entered_objects: self.entered_objects.remove(obj) self.object_exited.emit(obj)
def __init__(self, area): self.area = area self.rects = [] area.register_for_events(self) self.entered_objects = set() # Signals self.event_fired = Signal() self.object_moved = Signal() self.object_entered = Signal() self.object_exited = Signal()
def __init__(self, engine): pygame.font.init() self.ready = Signal() self.engine = engine self.size = engine.screen.get_size() self.surface = pygame.Surface(self.size).convert_alpha() self.widgets = [] self.timers = [] self.default_font = get_font_filename() #self.default_font = pygame.font.get_default_font() self.font = pygame.font.Font(self.default_font, 20) self.small_font = pygame.font.Font(self.default_font, 16) self.engine.level_changed.connect(self.on_level_changed) self.control_panel = None self.paused_textbox = None self.confirm_quit_box = None
class Cutscene(object): def __init__(self): self.pages = [] self.next_page = 0 self.current_page = None self.allow_escape = True # Signals self.done = Signal() def start(self): self.next_page = 0 self.show_next_page() def stop(self): self.next_page = -1 self.current_page.stop() self.current_page = None self.done.emit() def show_next_page(self): if self.next_page == len(self.pages): self.done.emit() elif self.next_page >= 0: self.current_page = self.pages[self.next_page] self.next_page += 1 self.current_page.done.connect(self.show_next_page) self.current_page.start() def draw(self, surface): if self.current_page: self.current_page.draw(surface) def handle_event(self, event): if event.type == KEYDOWN: if self.allow_escape and event.key == K_ESCAPE: self.stop() elif event.key in (K_SPACE, K_RETURN, K_RIGHT): self.current_page.stop()
def __init__(self, screen): set_engine(self) # Signals self.level_changed = Signal() self.tick = Signal() # State and objects self.active_level = None self.active_cutscene = None self.paused = False self.screen = screen self.clock = pygame.time.Clock() self.player = Player() self.ui_manager = UIManager(self) self.camera = None # Debug flags self.debug_rects = False self.god_mode = False self.show_debug_info = False self.area_changed_cnx = None self.ui_ready_cnx = None
def __init__(self, engine): pygame.font.init() self.ready = Signal() self.engine = engine self.size = engine.screen.get_size() self.surface = pygame.Surface(self.size).convert_alpha() self.widgets = [] self.timers = [] self.default_font = get_font_filename() # self.default_font = pygame.font.get_default_font() self.font = pygame.font.Font(self.default_font, 20) self.small_font = pygame.font.Font(self.default_font, 16) self.engine.level_changed.connect(self.on_level_changed) self.control_panel = None self.paused_textbox = None self.confirm_quit_box = None
def __init__(self, *args, **kwargs): super(ScreenFlashEffect, self).__init__(*args, **kwargs) self.fade_out = True self.color = (255, 255, 255) self.flash_peaked = Signal()
class Sprite(pygame.sprite.DirtySprite): FALL_SPEED = 6 def __init__(self, name, flip_image=False, obey_gravity=False): super(Sprite, self).__init__() # Signals self.moved = Signal() self.direction_changed = Signal() self.reverse_gravity_changed = Signal() # State self.rect = pygame.Rect(0, 0, 0, 0) self.quad_trees = set() self.layer = None self.name = name self.image = None self.visible = 1 self.dirty = 2 self.velocity = (0, 0) self.obey_gravity = obey_gravity self.reverse_gravity = False self.lethal = False self.falling = False self.collidable = True self.grabbed = False self.grabbable = False self.should_check_collisions = False self.use_pixel_collisions = False self.flip_image = flip_image self.collision_rects = [] self.collision_masks = [] self._colliding_objects = set() self._direction = Direction.RIGHT self._flipped_images = {} def __repr__(self): return 'Sprite %s (%s, %s, %s, %s)' % \ (self.name, self.rect.left, self.rect.top, self.rect.width, self.rect.height) def set_reverse_gravity(self, reverse_gravity): if self.reverse_gravity != reverse_gravity: self.reverse_gravity = reverse_gravity self.velocity = (self.velocity[0], -self.velocity[1]) self.update_image() self.reverse_gravity_changed.emit() def _set_direction(self, direction): if self.direction != direction: self._direction = direction self.direction_changed.emit() self.update_image() direction = property(lambda self: self._direction, _set_direction) def show(self): if not self.visible: self.visible = 1 self.dirty = 2 self.layer.update_sprite(self) def hide(self): if self.visible: self.visible = 0 self.dirty = 1 self.layer.update_sprite(self) def remove(self): self.hide() self.layer.remove(self) def fall(self): if self.falling or not self.obey_gravity: return self.falling = True if self.reverse_gravity: self.velocity = (self.velocity[0], -self.FALL_SPEED) else: self.velocity = (self.velocity[0], self.FALL_SPEED) def stop_falling(self): if self.obey_gravity: self.falling = False self.velocity = (self.velocity[0], 0) def update_image(self): self.image = self.generate_image() assert self.image self.rect.size = self.image.get_rect().size def generate_image(self): if not self.name: # Must be a custom sprite. return self.image image = load_image(self.name) if (self.flip_image and (self._direction == Direction.LEFT or self.reverse_gravity)): flip_h = (self.direction == Direction.LEFT) flip_v = self.reverse_gravity key = (self.name, flip_h, flip_v) if key not in self._flipped_images: self._flipped_images[key] = \ pygame.transform.flip(image, flip_h, flip_v) image = self._flipped_images[key] return image def move_to(self, x, y, check_collisions=False): self.move_by(x - self.rect.x, y - self.rect.y, check_collisions) def move_by(self, dx, dy, check_collisions=True): if check_collisions: if dx: self._move(dx=dx) if dy: self._move(dy=dy) else: self.rect.move_ip(dx, dy) self.on_moved(dx, dy) def _move(self, dx=0, dy=0): self.rect.move_ip(dx, dy) self.rect.left = max(self.rect.left, 0) self.rect.right = min(self.rect.right, self.layer.area.size[0]) self.check_collisions(dx, dy) def check_collisions(self, dx=0, dy=0): old_colliding_objects = set(self._colliding_objects) self._colliding_objects = set() for obj, self_rect, obj_rect in self.get_collisions(): if (self_rect == self.rect and self.should_adjust_position_with(obj, dx, dy)): self.position_beside(obj_rect, dx, dy) obj.handle_collision(self, obj_rect, dx, dy) self.on_collision(dx, dy, obj, self_rect, obj_rect) self._colliding_objects.add(obj) for obj in old_colliding_objects.difference(self._colliding_objects): obj.handle_stop_colliding(self) def should_adjust_position_with(self, obj, dx, dy): return True def position_beside(self, rect, dx, dy): if dy < 0: self.rect.top = rect.bottom elif dy > 0: self.rect.bottom = rect.top elif dx < 0: self.rect.left = rect.right elif dx > 0: self.rect.right = rect.left def get_collisions(self, tree=None, ignore_collidable_flag=False): if not self.should_check_collisions and not ignore_collidable_flag: raise StopIteration if tree is None: tree = self.layer.quad_tree num_checks = 0 if self.collision_rects: self_rect = self.collision_rects[0].unionall( self.collision_rects[1:]) else: self_rect = self.rect # We want more detailed collision info, so we use our own logic # instead of calling spritecollide. for obj in tree.get_sprites(self_rect): num_checks += 1 self_rect, obj_rect = \ self._check_collision(self, obj, ignore_collidable_flag) if self_rect and obj_rect: yield obj, self_rect, obj_rect #print 'Performing %s checks' % num_checks def _check_collision(self, left, right, ignore_collidable_flag): if (left == right or left.layer.index != right.layer.index or (not ignore_collidable_flag and ((not left.collidable or not right.collidable) or (not left.should_check_collisions and not right.should_check_collisions)))): return None, None left_rects = left.collision_rects or [left.rect] right_rects = right.collision_rects or [right.rect] for left_index, left_rect in enumerate(left_rects): right_index = left_rect.collidelist(right_rects) if right_index == -1: continue right_rect = right_rects[right_index] if left.use_pixel_collisions or right.use_pixel_collisions: left_mask = left._build_mask(left_rect, left_index) right_mask = right._build_mask(right_rect, right_index) offset = (left_rect.left - right_rect.left, left_rect.top - right_rect.top) pos = right_mask.overlap(left_mask, offset) if not pos: continue mask = right_mask.overlap_mask(left_mask, offset) collision_rect = mask.get_bounding_rects()[0] right_rect = pygame.Rect(right_rect.left + collision_rect.left, right_rect.top + collision_rect.top, *collision_rect.size) return left_rect, right_rect return None, None def _build_mask(self, rect, collision_index): mask = None image = None if self.use_pixel_collisions: if collision_index < len(self.collision_masks): mask = self.collision_masks[collision_index] if not mask: mask = getattr(self, 'mask', None) if not mask: if rect == self.rect: image = self.image else: try: image = self.image.subsurface(rect) except ValueError, e: image = None if image: mask = pygame.sprite.from_surface(image) if not mask: mask = pygame.Mask(rect.size) mask.fill() if self.collision_rects: if not self.collision_masks: self.collision_masks = [None] * len(self.collision_rects) self.collision_masks[collision_index] = mask else: self.mask = mask return mask
class UIManager(object): SCREEN_PADDING = 100 TEXTBOX_HEIGHT = 100 CONTROL_PANEL_HEIGHT = 40 def __init__(self, engine): pygame.font.init() self.ready = Signal() self.engine = engine self.size = engine.screen.get_size() self.surface = pygame.Surface(self.size).convert_alpha() self.widgets = [] self.timers = [] self.default_font = get_font_filename() # self.default_font = pygame.font.get_default_font() self.font = pygame.font.Font(self.default_font, 20) self.small_font = pygame.font.Font(self.default_font, 16) self.engine.level_changed.connect(self.on_level_changed) self.control_panel = None self.paused_textbox = None self.confirm_quit_box = None def add_control_panel(self): if not self.control_panel: self.control_panel = ControlPanel(self) self.control_panel.move_to(0, self.size[1] - self.control_panel.rect.height) def show_level(self, level): timeline_attrs = {"font": self.small_font, "padding_top": 20} widget = self.show_textbox( [ ({"padding_top": 10}, level.name), [(timeline_attrs, time_period.name) for time_period in level.time_periods], ], line_spacing=0, ) timer = Timer(2000, lambda: self.close(widget), one_shot=True) timer.start() return widget def show_textbox(self, text, **kwargs): textbox = TextBox(self, text, **kwargs) textbox.resize(self.size[0] - 2 * self.SCREEN_PADDING, self.TEXTBOX_HEIGHT) textbox.move_to(self.SCREEN_PADDING, self.size[1] - textbox.rect.height - self.SCREEN_PADDING) return textbox def close(self, widget): try: self.widgets.remove(widget) widget.closed.emit() except ValueError: # It was already closed pass if len(self.widgets) == 1: assert self.widgets[0] == self.control_panel self.ready.emit() def handle_event(self, event): handled = False if event.type == KEYDOWN: if self.confirm_quit_box: if event.key == K_ESCAPE: self.confirm_quit_box.close() self.confirm_quit_box = None handled = True self.engine.paused = False elif event.key == K_q: self.engine.quit() handled = True else: return True elif event.key in (K_ESCAPE, K_RIGHT, K_SPACE, K_RETURN): for widget in self.widgets: if isinstance(widget, TextBox) and widget != self.paused_textbox: widget.close() handled = True return handled def pause(self): assert not self.paused_textbox self.paused_textbox = self.show_textbox("Paused") def unpause(self): if self.paused_textbox: self.paused_textbox.close() self.paused_textbox = None def confirm_quit(self): if self.confirm_quit_box: self.confirm_quit_box.close() return self.engine.paused = True self.confirm_quit_box = self.show_textbox( [ "Do you want to quit?", ({"font": self.small_font}, "Press 'Q' to quit."), ({"font": self.small_font}, "Press 'Escape' to cancel."), ] ) def draw(self, surface): for element in self.widgets: element.draw(surface) def on_level_changed(self): level = self.engine.active_level self.show_level(level) self.control_panel.level = level
def __init__(self, name): super(Button, self).__init__(name) self.pressed = Signal()
class UIManager(object): SCREEN_PADDING = 100 TEXTBOX_HEIGHT = 100 CONTROL_PANEL_HEIGHT = 40 def __init__(self, engine): pygame.font.init() self.ready = Signal() self.engine = engine self.size = engine.screen.get_size() self.surface = pygame.Surface(self.size).convert_alpha() self.widgets = [] self.timers = [] self.default_font = get_font_filename() #self.default_font = pygame.font.get_default_font() self.font = pygame.font.Font(self.default_font, 20) self.small_font = pygame.font.Font(self.default_font, 16) self.engine.level_changed.connect(self.on_level_changed) self.control_panel = None self.paused_textbox = None self.confirm_quit_box = None def add_control_panel(self): if not self.control_panel: self.control_panel = ControlPanel(self) self.control_panel.move_to( 0, self.size[1] - self.control_panel.rect.height) def show_level(self, level): timeline_attrs = { 'font': self.small_font, 'padding_top': 20, } widget = self.show_textbox([({ 'padding_top': 10 }, level.name), [(timeline_attrs, time_period.name) for time_period in level.time_periods]], line_spacing=0) timer = Timer(2000, lambda: self.close(widget), one_shot=True) timer.start() return widget def show_textbox(self, text, **kwargs): textbox = TextBox(self, text, **kwargs) textbox.resize(self.size[0] - 2 * self.SCREEN_PADDING, self.TEXTBOX_HEIGHT) textbox.move_to( self.SCREEN_PADDING, self.size[1] - textbox.rect.height - self.SCREEN_PADDING) return textbox def close(self, widget): try: self.widgets.remove(widget) widget.closed.emit() except ValueError: # It was already closed pass if len(self.widgets) == 1: assert self.widgets[0] == self.control_panel self.ready.emit() def handle_event(self, event): handled = False if event.type == KEYDOWN: if self.confirm_quit_box: if event.key == K_ESCAPE: self.confirm_quit_box.close() self.confirm_quit_box = None handled = True self.engine.paused = False elif event.key == K_q: self.engine.quit() handled = True else: return True elif event.key in (K_ESCAPE, K_RIGHT, K_SPACE, K_RETURN): for widget in self.widgets: if (isinstance(widget, TextBox) and widget != self.paused_textbox): widget.close() handled = True return handled def pause(self): assert not self.paused_textbox self.paused_textbox = self.show_textbox('Paused') def unpause(self): if self.paused_textbox: self.paused_textbox.close() self.paused_textbox = None def confirm_quit(self): if self.confirm_quit_box: self.confirm_quit_box.close() return self.engine.paused = True self.confirm_quit_box = self.show_textbox([ "Do you want to quit?", ({ 'font': self.small_font }, "Press 'Q' to quit."), ({ 'font': self.small_font }, "Press 'Escape' to cancel.") ]) def draw(self, surface): for element in self.widgets: element.draw(surface) def on_level_changed(self): level = self.engine.active_level self.show_level(level) self.control_panel.level = level
class ForeverEndEngine(object): FPS = 30 def __init__(self, screen): set_engine(self) # Signals self.level_changed = Signal() self.tick = Signal() # State and objects self.active_level = None self.active_cutscene = None self.paused = False self.screen = screen self.clock = pygame.time.Clock() self.player = Player() self.ui_manager = UIManager(self) self.camera = None # Debug flags self.debug_rects = False self.god_mode = False self.show_debug_info = False self.area_changed_cnx = None self.ui_ready_cnx = None def run(self): self.active_cutscene = OpeningCutscene() self.active_cutscene.done.connect(self._setup_game) self.active_cutscene.start() self._mainloop() def quit(self): pygame.quit() sys.exit(0) def dead(self): def on_timeout(): widget.close() self.restart_level() widget = self.ui_manager.show_textbox([ "It's okay! You had another guy!", "%s lives left." % self.player.lives ]) self.paused = True timer = Timer(2000, on_timeout, one_shot=True) def restart_level(self): self.switch_level(self.levels.index(self.active_level)) def game_over(self): def on_timeout(): widget.close() self._setup_game() #sys.exit(0) widget = self.ui_manager.show_textbox('Game Over') self.paused = True timer = Timer(2000, on_timeout, one_shot=True) def show_end_scene(self): self.ui_manager.control_panel.close() self.active_level = None self.active_cutscene = ClosingCutscene() self.active_cutscene.start() def _setup_game(self): self.ui_manager.add_control_panel() self.camera = Camera(self) self.tick.clear() self.active_cutscene = None self.player.lives = self.player.MAX_LIVES self.player.health = self.player.MAX_HEALTH self.player.update_image() self.levels = [level(self) for level in get_levels()] self.switch_level(0) if self.ui_ready_cnx: self.ui_ready_cnx.disconnect() self.paused = True self.ui_ready_cnx = self.ui_manager.ready.connect(self.show_tutorial) def show_tutorial(self): def on_done(): self.active_cutscene = False self.paused = False self.ui_ready_cnx.disconnect() self.ui_ready_cnx = None self.active_cutscene = TutorialCutscene() self.active_cutscene.start() self.active_cutscene.done.connect(on_done) def switch_level(self, num): assert num < len(self.levels) self.paused = False self.player.stop_tractor_beam() self.player.stop_riding() self.player.block_events = False self.player.reverse_gravity = False self.player.jumping = False self.player.falling = False self.player.fall() self.active_level = self.levels[num] self.active_level.reset() if self.area_changed_cnx: self.area_changed_cnx.disconnect() self.area_changed_cnx = \ self.active_level.area_changed.connect(self._on_area_changed) self.active_level.switch_time_period(0) self.player.move_to(*self.active_level.active_area.start_pos) self.camera.update() self.player.show() if False and has_mixer: pygame.mixer.music.stop() if self.active_level.music: pygame.mixer.music.load( get_music_filename(self.active_level.music)) pygame.mixer.music.play(-1) self.level_changed.emit() def next_level(self): def on_timeout(): widget.close() unload_images() self.switch_level(next_level) next_level = self.levels.index(self.active_level) + 1 widget = self.ui_manager.show_textbox([ 'You got the artifact! %s left to go.' % (len(self.levels) - next_level), ]) timer = Timer(2000, on_timeout, one_shot=True) timer.start() def _on_area_changed(self): area = self.active_level.active_area self.surface = pygame.Surface(area.size) if self.camera: self.camera.update() def _mainloop(self): while 1: for event in pygame.event.get(): if not self._handle_event(event): return self.tick.emit() self._paint() self.clock.tick(self.FPS) def _handle_event(self, event): if event.type == QUIT: self.quit() return False if (self.ui_manager and not self.active_cutscene and self.ui_manager.handle_event(event)): return True if event.type == KEYDOWN and event.key == K_F2: self.show_debug_info = not self.show_debug_info elif event.type == KEYDOWN and event.key == K_F3: self.debug_rects = not self.debug_rects elif event.type == KEYDOWN and event.key == K_F4: self.god_mode = not self.god_mode elif self.active_cutscene: self.active_cutscene.handle_event(event) elif self.active_level: if event.type == KEYDOWN and event.key == K_TAB: # Switch time periods self._show_time_periods() elif event.type == KEYDOWN and event.key == K_RETURN: if self.paused: self._unpause() else: self._pause() elif not self.paused: if event.type == KEYDOWN and event.key in (K_1, K_a): self.active_level.switch_time_period(0) elif event.type == KEYDOWN and event.key in (K_2, K_s): self.active_level.switch_time_period(1) elif event.type == KEYDOWN and event.key in (K_3, K_d): self.active_level.switch_time_period(2) elif event.type == KEYDOWN and event.key == K_F5: # XXX For debugging only. i = self.levels.index(self.active_level) if i + 1 == len(self.levels): self.switch_level(0) else: self.switch_level(i + 1) elif event.type == KEYDOWN and event.key == K_ESCAPE: self.ui_manager.confirm_quit() elif not self.player.handle_event(event): area = self.active_level.active_area for box in area.event_handlers: if hasattr(box, 'rects'): rects = box.rects else: rects = [box.rect] if (self.player.rect.collidelist(rects) != -1 and box.handle_event(event)): break return True def _pause(self): self.paused = True self.ui_manager.pause() def _unpause(self): self.paused = False self.ui_manager.unpause() def _show_time_periods(self): self._pause() def _paint(self): if self.camera: self.camera.update() if self.active_cutscene: self.screen.set_clip(None) self.active_cutscene.draw(self.screen) if self.active_level: self.surface.set_clip(self.camera.rect) self.active_level.draw(self.surface) self.screen.blit(self.surface.subsurface(self.camera.rect), (0, 0)) self.ui_manager.draw(self.screen) if self.show_debug_info: debug_str = '%0.f FPS X: %s Y: %s' % (self.clock.get_fps( ), self.player.rect.left, self.player.rect.top) self.screen.blit( self.ui_manager.small_font.render(debug_str, True, (255, 0, 0)), (30, 10)) pygame.display.flip()
class ForeverEndEngine(object): FPS = 30 def __init__(self, screen): set_engine(self) # Signals self.level_changed = Signal() self.tick = Signal() # State and objects self.active_level = None self.active_cutscene = None self.paused = False self.screen = screen self.clock = pygame.time.Clock() self.player = Player() self.ui_manager = UIManager(self) self.camera = None # Debug flags self.debug_rects = False self.god_mode = False self.show_debug_info = False self.area_changed_cnx = None self.ui_ready_cnx = None def run(self): self.active_cutscene = OpeningCutscene() self.active_cutscene.done.connect(self._setup_game) self.active_cutscene.start() self._mainloop() def quit(self): pygame.quit() sys.exit(0) def dead(self): def on_timeout(): widget.close() self.restart_level() widget = self.ui_manager.show_textbox([ "It's okay! You had another guy!", "%s lives left." % self.player.lives ]) self.paused = True timer = Timer(2000, on_timeout, one_shot=True) def restart_level(self): self.switch_level(self.levels.index(self.active_level)) def game_over(self): def on_timeout(): widget.close() self._setup_game() #sys.exit(0) widget = self.ui_manager.show_textbox('Game Over') self.paused = True timer = Timer(2000, on_timeout, one_shot=True) def show_end_scene(self): self.ui_manager.control_panel.close() self.active_level = None self.active_cutscene = ClosingCutscene() self.active_cutscene.start() def _setup_game(self): self.ui_manager.add_control_panel() self.camera = Camera(self) self.tick.clear() self.active_cutscene = None self.player.lives = self.player.MAX_LIVES self.player.health = self.player.MAX_HEALTH self.player.update_image() self.levels = [level(self) for level in get_levels()] self.switch_level(0) if self.ui_ready_cnx: self.ui_ready_cnx.disconnect() self.paused = True self.ui_ready_cnx = self.ui_manager.ready.connect(self.show_tutorial) def show_tutorial(self): def on_done(): self.active_cutscene = False self.paused = False self.ui_ready_cnx.disconnect() self.ui_ready_cnx = None self.active_cutscene = TutorialCutscene() self.active_cutscene.start() self.active_cutscene.done.connect(on_done) def switch_level(self, num): assert num < len(self.levels) self.paused = False self.player.stop_tractor_beam() self.player.stop_riding() self.player.block_events = False self.player.reverse_gravity = False self.player.jumping = False self.player.falling = False self.player.fall() self.active_level = self.levels[num] self.active_level.reset() if self.area_changed_cnx: self.area_changed_cnx.disconnect() self.area_changed_cnx = \ self.active_level.area_changed.connect(self._on_area_changed) self.active_level.switch_time_period(0) self.player.move_to(*self.active_level.active_area.start_pos) self.camera.update() self.player.show() if False and has_mixer: pygame.mixer.music.stop() if self.active_level.music: pygame.mixer.music.load( get_music_filename(self.active_level.music)) pygame.mixer.music.play(-1) self.level_changed.emit() def next_level(self): def on_timeout(): widget.close() unload_images() self.switch_level(next_level) next_level = self.levels.index(self.active_level) + 1 widget = self.ui_manager.show_textbox([ 'You got the artifact! %s left to go.' % (len(self.levels) - next_level), ]) timer = Timer(2000, on_timeout, one_shot=True) timer.start() def _on_area_changed(self): area = self.active_level.active_area self.surface = pygame.Surface(area.size) if self.camera: self.camera.update() def _mainloop(self): while 1: for event in pygame.event.get(): if not self._handle_event(event): return self.tick.emit() self._paint() self.clock.tick(self.FPS) def _handle_event(self, event): if event.type == QUIT: self.quit() return False if (self.ui_manager and not self.active_cutscene and self.ui_manager.handle_event(event)): return True if event.type == KEYDOWN and event.key == K_F2: self.show_debug_info = not self.show_debug_info elif event.type == KEYDOWN and event.key == K_F3: self.debug_rects = not self.debug_rects elif event.type == KEYDOWN and event.key == K_F4: self.god_mode = not self.god_mode elif self.active_cutscene: self.active_cutscene.handle_event(event) elif self.active_level: if event.type == KEYDOWN and event.key == K_TAB: # Switch time periods self._show_time_periods() elif event.type == KEYDOWN and event.key == K_RETURN: if self.paused: self._unpause() else: self._pause() elif not self.paused: if event.type == KEYDOWN and event.key in (K_1, K_a): self.active_level.switch_time_period(0) elif event.type == KEYDOWN and event.key in (K_2, K_s): self.active_level.switch_time_period(1) elif event.type == KEYDOWN and event.key in (K_3, K_d): self.active_level.switch_time_period(2) elif event.type == KEYDOWN and event.key == K_F5: # XXX For debugging only. i = self.levels.index(self.active_level) if i + 1 == len(self.levels): self.switch_level(0) else: self.switch_level(i + 1) elif event.type == KEYDOWN and event.key == K_ESCAPE: self.ui_manager.confirm_quit() elif not self.player.handle_event(event): area = self.active_level.active_area for box in area.event_handlers: if hasattr(box, 'rects'): rects = box.rects else: rects = [box.rect] if (self.player.rect.collidelist(rects) != -1 and box.handle_event(event)): break return True def _pause(self): self.paused = True self.ui_manager.pause() def _unpause(self): self.paused = False self.ui_manager.unpause() def _show_time_periods(self): self._pause() def _paint(self): if self.camera: self.camera.update() if self.active_cutscene: self.screen.set_clip(None) self.active_cutscene.draw(self.screen) if self.active_level: self.surface.set_clip(self.camera.rect) self.active_level.draw(self.surface) self.screen.blit(self.surface.subsurface(self.camera.rect), (0, 0)) self.ui_manager.draw(self.screen) if self.show_debug_info: debug_str = '%0.f FPS X: %s Y: %s' % ( self.clock.get_fps(), self.player.rect.left, self.player.rect.top) self.screen.blit( self.ui_manager.small_font.render(debug_str, True, (255, 0, 0)), (30, 10)) pygame.display.flip()
class Level(object): CROSSOVER_TIME_INTERVAL = (4000, 10000) MAX_CROSSOVERS = 3 def __init__(self, engine): self.engine = engine self.time_periods = [] self.active_area = None self.music = None self.crossover_timer = None self.crossovers = [] self.pending_crossovers = {} self.next_crossover_id = 0 self.engine.tick.connect(self.on_tick) # Signals self.area_changed = Signal() self.time_period_changed = Signal() def add(self, time_period): self.time_periods.append(time_period) def add_artifact(self, area, x, y): # Artifact self.artifact = Artifact(area, 1) area.main_layer.add(self.artifact) self.artifact.move_to(x - self.artifact.rect.width / 2, y - self.artifact.rect.height - 50) self.artifact.grab_changed.connect(self.on_artifact_grabbed) def reset(self): self.active_area = None self.active_time_period = None self.time_periods = [] self._setup() def setup(self): pass def _setup(self): self.setup() for time_period in self.time_periods: time_period.setup() def switch_area(self, area): if area == self.active_area: return player = self.engine.player if self.active_area: self.active_area.main_layer.remove(player) self.active_area = area self.active_area.main_layer.add(player) self.area_changed.emit() player.reset_gravity() for crossover, timer in self.crossovers: timer.stop() crossover.remove() for pending_timer in self.pending_crossovers.itervalues(): pending_timer.stop() self.crossovers = [] self.pending_crossovers = {} def switch_time_period(self, num): time_period = self.time_periods[num] if self.active_area: key = self.active_area.key area = time_period.areas.get(key, None) else: area = time_period.default_area player = self.engine.player if (area and (not self.active_area or not list(player.get_collisions(tree=area.main_layer.quad_tree)))): self.active_time_period = time_period self.time_period_changed.emit() self.switch_area(area) def draw(self, screen): self.active_area.draw(screen) def on_artifact_grabbed(self): if self.artifact.grabbed: player = self.engine.player player.block_events = True player.velocity = (0, 0) player.fall() timer = Timer(1000, self.engine.next_level, one_shot=True) timer.start() def on_tick(self): if not self.engine.paused and self.engine.active_level == self: self.active_area.tick() if (len(self.time_periods) > 1 and len(self.crossovers) + len(self.pending_crossovers) < self.MAX_CROSSOVERS): crossover_id = self.next_crossover_id self.next_crossover_id += 1 timer = Timer( random.randint(*self.CROSSOVER_TIME_INTERVAL), lambda: self.show_crossover(crossover_id), one_shot=True) self.pending_crossovers[crossover_id] = timer def show_crossover(self, crossover_id): def hide_crossover(): crossover_sprite.remove() self.crossovers.remove((crossover_sprite, timer)) self.pending_crossovers[crossover_id].stop() del self.pending_crossovers[crossover_id] key = self.active_area.key time_periods = [ time_period.areas[key] for time_period in self.time_periods if (time_period != self.active_time_period and key in time_period.areas) ] if len(time_periods) - 1 <= 0: return i = random.randint(0, len(time_periods) - 1) crossover_sprite = Crossover(time_periods[i]) crossover_sprite.rect = self.engine.camera.rect if random.randint(0, 5) <= 3: layer = self.active_area.bg_layer else: layer = self.active_area.main_layer layer.add(crossover_sprite) timer = Timer(500, hide_crossover, one_shot=True) timer.start() self.crossovers.append((crossover_sprite, timer))
class Player(Sprite): MOVE_SPEED = 6 JUMP_SPEED = 6 MAX_JUMP_HEIGHT = 100 HOVER_TIME_MS = 1000 MAX_HEALTH = 3 MAX_LIVES = 3 PROPULSION_BELOW_OFFSET = 8 def __init__(self): super(Player, self).__init__('player', flip_image=True, obey_gravity=True) self.engine = get_engine() self.should_check_collisions = True # Sprites self.propulsion_below = Sprite("propulsion_below", flip_image=True) self.propulsion_below.collidable = False self.propulsion_below.visible = 0 self.tractor_beam = TractorBeam(self) # State self.jumping = False self.hovering = False self.block_events = False self.jump_origin = None self.hover_time_ms = 0 self.health = self.MAX_HEALTH self.lives = self.MAX_LIVES self.last_safe_spot = None self.vehicle = None self.vehicle_move_cnx = None # Signals self.health_changed = Signal() self.lives_changed = Signal() self.direction_changed.connect(self.on_direction_changed) self.reverse_gravity_changed.connect(self.on_reverse_gravity_changed) def handle_event(self, event): if self.block_events: return if event.type == KEYDOWN: if event.key == K_RIGHT: self.move_right() elif event.key == K_LEFT: self.move_left() elif event.key == K_SPACE: self.jump() elif event.key in (K_LSHIFT, K_RSHIFT): self.start_tractor_beam() elif event.type == KEYUP: if event.key == K_LEFT: if self.velocity[0] < 0: self.velocity = (0, self.velocity[1]) elif event.key == K_RIGHT: if self.velocity[0] > 0: self.velocity = (0, self.velocity[1]) elif event.key == K_SPACE: self.fall() elif event.key in (K_LSHIFT, K_RSHIFT): self.stop_tractor_beam() def move_right(self): self.velocity = (self.MOVE_SPEED, self.velocity[1]) if self.direction != Direction.RIGHT: self.direction = Direction.RIGHT self.tractor_beam.direction = Direction.RIGHT self.update_image() def move_left(self): self.velocity = (-self.MOVE_SPEED, self.velocity[1]) if self.direction != Direction.LEFT: self.direction = Direction.LEFT self.tractor_beam.direction = Direction.LEFT self.update_image() def start_tractor_beam(self): self.tractor_beam.show() self.tractor_beam.update_position(self) self.calculate_collision_rects() def stop_tractor_beam(self): self.tractor_beam.ungrab() self.tractor_beam.hide() self.calculate_collision_rects() def ride(self, vehicle): self.vehicle = vehicle self.velocity = (self.velocity[0], 0) self.vehicle_moved_cnx = \ self.vehicle.moved.connect(self.on_vehicle_moved) self.calculate_collision_rects() def jump(self): if (self.falling and not self.engine.god_mode) or self.jumping: return self.jump_origin = (self.rect.left, self.rect.top) self.jumping = True self.falling = False if self.reverse_gravity: self.velocity = (self.velocity[0], self.JUMP_SPEED) else: self.velocity = (self.velocity[0], -self.JUMP_SPEED) self.propulsion_below.show() self.stop_riding() def reset_gravity(self): if self.reverse_gravity: self.reverse_gravity = False if self.falling: # Reset the fall self.falling = False self.fall() self.update_image() def stop_riding(self): if self.vehicle: self.vehicle_moved_cnx.disconnect() self.vehicle = None self.calculate_collision_rects() def fall(self): if self.falling: return self.propulsion_below.hide() self.jumping = False self.hovering = False super(Player, self).fall() def hover(self): if self.hovering: return self.propulsion_below.show() self.jumping = False self.hovering = True self.hover_time_ms = 0 self.velocity = (self.velocity[0], 0) def calculate_collision_rects(self): self.collision_masks = [] self.collision_rects = [self.rect] if self.tractor_beam.item: self.collision_rects.append(self.tractor_beam.item.rect) if self.vehicle: self.collision_rects.append(self.vehicle.rect) def check_collisions(self, *args, **kwargs): super(Player, self).check_collisions(*args, **kwargs) def should_adjust_position_with(self, obj, dx, dy): from foreverend.sprites.common import FloatingSprite return (obj != self.vehicle and (dx == 0 or not isinstance(obj, FloatingSprite))) def tick(self): if self.hovering: self.hover_time_ms += 1.0 / self.engine.FPS * 1000 if self.hover_time_ms >= self.HOVER_TIME_MS: self.fall() super(Player, self).tick() def on_added(self, layer): for obj in (self.propulsion_below, self.tractor_beam, self.vehicle): if obj: layer.add(obj) def on_removed(self, layer): for obj in (self.propulsion_below, self.tractor_beam, self.vehicle): if obj: layer.remove(obj) def on_moved(self, dx, dy): if not self.last_safe_spot: self.last_safe_spot = self.rect.topleft self.rect.top = max(self.rect.top, 0) if self.rect.top > self.layer.area.size[1]: self.on_dead() return if (self.jumping and not self.engine.god_mode and ((not self.reverse_gravity and self.jump_origin[1] - self.rect.top >= self.MAX_JUMP_HEIGHT) or (self.reverse_gravity and self.rect.top - self.jump_origin[1] >= self.MAX_JUMP_HEIGHT))): self.hover() if self.propulsion_below.visible: if self.direction == Direction.RIGHT: offset = self.PROPULSION_BELOW_OFFSET if self.direction == Direction.LEFT: offset = self.rect.width - self.propulsion_below.rect.width - \ self.PROPULSION_BELOW_OFFSET if self.reverse_gravity: y = self.rect.top - self.propulsion_below.rect.height else: y = self.rect.bottom self.propulsion_below.move_to(self.rect.left + offset, y) if self.tractor_beam.visible: self.tractor_beam.update_position(self) if self.vehicle and dx != 0: self.vehicle.move_to( self.rect.left - (self.vehicle.rect.width - self.rect.width) / 2, self.vehicle.rect.top) self.calculate_collision_rects() super(Player, self).on_moved(dx, dy) def on_collision(self, dx, dy, obj, self_rect, obj_rect): if obj.lethal and not self.engine.god_mode and self_rect == self.rect: self.on_hit() return self.last_safe_spot = self.rect.topleft if self.tractor_beam.item and self_rect == self.tractor_beam.item.rect: move_player = True if self.tractor_beam.adjust_item_position(obj_rect): move_player = False if move_player: if self.direction == Direction.LEFT: self.move_to(obj_rect.right + (self.rect.left - self.tractor_beam.item.rect.left), self.rect.top) elif self.direction == Direction.RIGHT: self.move_to(obj_rect.left - (self.tractor_beam.item.rect.right - self.rect.left), self.rect.top) if dy > 0 and not self.vehicle and isinstance(obj, Vehicle): self.ride(obj) if self.jumping and dy < 0: self.fall() else: super(Player, self).on_collision(dx, dy, obj, self_rect, obj_rect) def on_direction_changed(self): if self.vehicle: self.vehicle.direction = self.direction def on_reverse_gravity_changed(self): self.propulsion_below.set_reverse_gravity(self.reverse_gravity) self.tractor_beam.set_reverse_gravity(self.reverse_gravity) if self.tractor_beam.item: self.tractor_beam.item.set_reverse_gravity(self.reverse_gravity) self.fall() def on_vehicle_moved(self, dx, dy): if dy != 0: self.move_by(0, dy) def on_hit(self): self.health -= 1 self.health_changed.emit() if self.health == 0: self.on_dead() else: self.move_to(*self.last_safe_spot) self.velocity = (0, 0) self.falling = False self.fall() def on_dead(self): self.lives -= 1 self.lives_changed.emit() self.velocity = (0, 0) if self.lives == 0: self.engine.game_over() else: self.health = self.MAX_HEALTH self.health_changed.emit() self.engine.dead()
def __init__(self): self.done = Signal()
def __init__(self, ui_manager): self.ui_manager = ui_manager self.ui_manager.widgets.append(self) self.rect = pygame.Rect(0, 0, 0, 0) self.closed = Signal()