class Cutscene(Minigame): GAME_NAME = "Cutscene" _default_layer = 1 def __init__(self, scene_name="cutscene001", scene_file_name="test-cutscene.yaml"): self.running = False self.script_runner = ScriptRunner() self._scene_name = scene_name self._scene_file_name = scene_file_name self._border = None self._sprites = LayeredUpdates() self._animations = Group() self._dialog_open = False self._dialog_rect = None self._caption = None self._text = None def initialize(self, context): with open(get_data_asset(self._scene_file_name)) as fp: config = yaml.load(fp) self.script_runner.start(self, config[self._scene_name]) @staticmethod def new_sprite(image, rect): sprite = Sprite() sprite.image = image sprite.rect = rect return sprite def animate(self, *args, **kwargs): ani = animation.Animation(*args, **kwargs) self._animations.add(ani) return ani def add_sprite(self, image, rect, layer=None): self._sprites.add(self.new_sprite(image, rect), layer=ternone(layer, self._default_layer)) def set_background(self, filename): surf = smoothscale(load_image(filename), SCREEN_SIZE).convert() rect = (0, 0), SCREEN_SIZE self.add_sprite(surf, rect, 0) def set_portrait(self, filename): size = 340, 680 # HACK to remove old portrait for sprite in self._sprites: if sprite.image.get_size() == size: self._sprites.remove(sprite) if filename is not None: surf = smoothscale(load_image(filename), size).convert_alpha() rect = (900, 60), size self.add_sprite(surf, rect, 1) def set_caption(self, value): if value is None: self._caption = None return get = self.script_runner.vars.get fcolor = Color(get('caption-fg', 'black')) font = load_font('pixChicago.ttf', 16) if 'caption-bg' in self.script_runner.vars and get( 'caption-bg') is not None: bcolor = Color(get('caption-bg')) image = font.render(value, 0, fcolor, bcolor) margins = image.get_rect().inflate(45, 0) background = pygame.Surface(margins.size) background.fill(bcolor) background.blit(image, (24, 0)) self._caption = background else: self._caption = font.render(value, 0, fcolor) def set_text(self, value): get = self.script_runner.vars.get fcolor = Color(get('text-fg', 'black')) bcolor = none_or_not(self.script_runner.vars, 'text-bg', Color) font = load_font('pixChicago.ttf', 16) w, h = self.final_rect().size w -= 48 final_rect = Rect((0, 0), (w, h)) self._text = pygame.Surface(final_rect.size, pygame.SRCALPHA) draw_text(self._text, value, final_rect, font, fcolor, bcolor) def final_rect(self): sw, sh = SCREEN_SIZE return Rect(sw * .05, sh * .6, sw * .62, sh * .35) def queue_dialog_text(self, value): self.set_text(value) def open_dialog(self): self._dialog_open = True final_rect = self.final_rect() self._dialog_rect = Rect(0, 0, 64, 64, center=final_rect.center) ani = self.animate(self._dialog_rect, height=final_rect.height, width=final_rect.width, duration=100) ani.schedule( lambda: setattr(self._dialog_rect, "center", final_rect.center), 'on update') def close_dialog(self): self._dialog_open = False def run(self, context): flip = pygame.display.flip update = self.update draw = self.draw handle_events = self.handle_event screen = pygame.display.get_surface() clock = time.time frame_time = (1 / 60.) * 1000 last_draw = 0 times = deque(maxlen=10) self.running = True last_frame = clock() while self.running: dt = (clock() - last_frame) * 1000 last_frame = clock() times.append(dt) dt = sum(times) / 10 last_draw += dt handle_events() update(dt) if last_draw >= frame_time: draw(screen) flip() last_draw = 0 pygame.mixer.music.fadeout(800) def draw(self, screen): self._sprites.draw(screen) if self._dialog_open: self.draw_dialog(screen) def draw_dialog(self, surface): with surface_clipping_context(surface, self._dialog_rect): self._border.draw(surface, self._dialog_rect) internal = self._dialog_rect.inflate(-48, -6) with surface_clipping_context(surface, internal): if self._caption: rect = self._caption.get_rect() rect.top = internal.top rect.centerx = internal.centerx surface.blit(self._caption, rect) if self._text: rect = internal.copy() rect.top = internal.top + 75 rect.left = internal.left surface.blit(self._text, rect) def update(self, dt): self._animations.update(dt) def button_press(self): """ Handles the ACTION button :return: """ self.script_runner.dialog_event('press') def handle_event(self): for event in pygame.event.get(): if event.type == KEYDOWN: if event.key == K_ESCAPE: self.running = False if event.key == K_SPACE: self.button_press() if event.type == QUIT: # this will allow pressing the windows (X) to close the game sys.exit(0)
class PlatformerScene(Scene): def __init__(self, game): super().__init__(game) self.player = None self.active = True self.geometry = list() self.space = pymunk.Space() self.space.gravity = (0, 1000) self.sprites = LayeredUpdates() self.event_handler = event_handling.EventQueueHandler() self.background = resources.gfx("background.png", convert=True) self.load() pygame.mixer.music.load(resources.music_path("zirkus.ogg")) pygame.mixer.music.play(-1) def add_static(self, vertices, rect): body = pymunk.Body(body_type=pymunk.Body.STATIC) body.position = rect.x, rect.y shape = pymunk.Poly(body, vertices) shape.friction = 1.0 shape.elasticity = 1.0 self.space.add(body, shape) def load(self): def box_vertices(x, y, w, h): lt = x, y rt = x + w, y rb = x + w, y + h lb = x, y + h return lt, rt, rb, lb filename = path_join("data", "maps", "untitled.tmx") tmxdata = pytmx.util_pygame.load_pygame(filename) for obj in tmxdata.objects: if obj.type == map_fixed: rect = Rect(obj.x, obj.y, obj.width, obj.height) vertices = box_vertices(0, 0, obj.width, obj.height) self.add_static(vertices, rect) elif obj.type == map_yarn_spawn: ball = sprite.Ball(Rect((obj.x, obj.y), (32, 32))) model = BasicModel() model.sprites = [ball] model.pymunk_objects = ball.pymunk_shapes self.add_model(model) self.player = model elif obj.type == map_player_spawn: self.player = unicyclecat.build(self.space, self.sprites) self.player.position = obj.x, obj.y self.fsm = SimpleFSM(control, "idle") def add_model(self, model): self.sprites.add(*model.sprites) self.space.add(model.pymunk_objects) def remove_model(self, model): self.sprites.remove(*model.sprites) self.space.remove(model.pymunk_objects) def render(self): surface = self._game.screen surface.blit(self.background, (0, 0)) self.sprites.draw(surface) return [surface.get_rect()] def tick(self, dt): step_amount = (1 / 30.) / 30 for i in range(30): self.space.step(step_amount) self.sprites.update(dt) def event(self, pg_event): events = self.event_handler.process_event(pg_event) position = self.player.position for event in events: try: cmd, arg = self.fsm((event.button, event.held)) except ValueError as e: continue if cmd == "move": resources.sfx("cat_wheel.ogg", False, True) resources.sfx("cat_wheel.ogg", True) self.player.accelerate(arg) if cmd == "idle": self.player.brake() elif cmd == "jump": resources.sfx("cat_jump.ogg", True) self.player.main_body.apply_impulse_at_world_point((0, -600), position)
class PlatformerScene(Scene): """ Platformer Scene class. """ def __init__(self, game): super().__init__(game) self.player = None self.active = True self.fsm = None self.space = pymunk.Space() self.space.gravity = (0, 1000) self.sprites = LayeredUpdates() self.event_handler = event_handling.EventQueueHandler() self.event_handler.print_controls() self.background = resources.gfx("background.png", convert=True) self.load() pygame.mixer.music.load(resources.music_path("zirkus.ogg")) pygame.mixer.music.play(-1) def add_static(self, vertices, rect): """ Add static object to scene. :param vertices: :param rect: """ body = pymunk.Body(body_type=pymunk.Body.STATIC) body.position = rect.x, rect.y shape = pymunk.Poly(body, vertices) shape.friction = 1.0 shape.elasticity = 1.0 self.space.add(body, shape) def load(self): """ Load a scene in TMX format. """ def box_vertices(box_x, box_y, width, height): top_left = box_x, box_y top_right = box_x + width, box_y bottom_right = box_x + width, box_y + height bottom_left = box_x, box_y + height return top_left, top_right, bottom_right, bottom_left filename = path_join("data", "maps", "untitled.tmx") tmxdata = pytmx.util_pygame.load_pygame(filename) for obj in tmxdata.objects: if obj.type == MAP_FIXED: rect = Rect(obj.x, obj.y, obj.width, obj.height) vertices = box_vertices(0, 0, obj.width, obj.height) self.add_static(vertices, rect) elif obj.type == MAP_YARN_SPAWN: ball = sprite.Ball(Rect((obj.x, obj.y), (32, 32))) model = BasicModel() model.sprites = [ball] model.pymunk_objects = ball.pymunk_shapes self.add_model(model) self.player = model elif obj.type == MAP_PLAYER_SPAWN: self.player = unicyclecat.build(self.space, self.sprites) self.player.position = obj.x, obj.y self.fsm = SimpleFSM(CONTROL, "idle") def add_model(self, model): """ Add a model. :param model: Model to add. """ self.sprites.add(*model.sprites) self.space.add(model.pymunk_objects) def remove_model(self, model): """ Remove a model. :param model: Model to remove. """ self.sprites.remove(*model.sprites) self.space.remove(model.pymunk_objects) def render(self): """ Render scene to the screen surface. """ surface = self._game.screen surface.blit(self.background, (0, 0)) self.sprites.draw(surface) return [surface.get_rect()] def tick(self, time_delta): """ Tick the physics and game update loops. """ step_amount = (1 / 30.0) / 30 for _ in range(30): self.space.step(step_amount) self.sprites.update(time_delta=time_delta) def event(self, event): """ Process an event. :param event: The event to process """ events = self.event_handler.process_event(event) position = self.player.position for evt in events: try: cmd, arg = self.fsm((evt.button, evt.held)) except ValueError: continue if cmd == "move": resources.sfx("cat_wheel.ogg", False, True) resources.sfx("cat_wheel.ogg", True) self.player.accelerate(arg) if cmd == "idle": self.player.brake() elif cmd == "jump": resources.sfx("cat_jump.ogg", True) self.player.main_body.apply_impulse_at_world_point((0, -600), position)