class Mainloop(window.Window): _max_scroll_speed = 8 # pixels per tick def __init__(self): super(Mainloop, self).__init__(width*scale_factor, height*scale_factor, 'puit') glEnable(GL_BLEND) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) self.viewport = FixedResolutionViewport(self, width, height, filtered=False) #base resolution we're working in self.font = font.load('Arial', 10) self.media = {} self.scrollarea = Boundingbox(size=(width, height)) self._scroll_speed = (0, 0) self.state = None def scroll_to(self, new_position): old_position = self.scrollarea.bottom_left self.scrollarea.move(self._get_scroll_delta(old_position, new_position)) # TODO: stupid scrolling system # i'd like to move the code that limits the scrollarea to the extents # of the level over here. however, a Mainloop doesn't necessarily have # to run a Level instance, so maybe we need a concept like "all-encompassing # Gameobject"? def draw_text(self, text, position): t = font.Text(self.font, text, position[0], position[1]) t.draw() def play(self, filename): if not self.media.has_key(filename): self.media[filename] = media.load(os.path.join('data', filename), streaming=False) self.media[filename].play() def on_key_press(self, symbol, modifiers): self.state.on_key_press(symbol, modifiers) def on_key_release(self, symbol, modifiers): self.state.on_key_release(symbol, modifiers) def run(self): clock.set_fps_limit(30) sim_time = 0.0 real_time = 0.0 frames_skipped = 0 while not self.has_exit: real_time += clock.tick() self.dispatch_events() media.dispatch_events() draw = sim_time >= real_time if frames_skipped >= 1: draw = True frames_skipped = 0 real_time = sim_time self.tick(draw) if not draw: frames_skipped += 1 sim_time += 1 / 30.0 # print 'FPS is %f' % clock.get_fps() def tick(self, draw=True): # having the core functionality of the run loop in its own method helps # with adding stuff here in subclasses. self.state.update(draw) def please_quit(self): self.has_exit = True def _get_scroll_delta(self, old_position, new_position): def round_to_larger_magnitude(x): if x >= 0: return math.ceil(x) else: return math.floor(x) def throttle(target, limiter): # first of all, don't bother with tiny targets! if abs(target) < 1: return 0 # if target and limiter have same sign, implement our heuristic: if (target >= 0) == (limiter >= 0): # in case limiter is too small, make an exception: if (target >= 1.0) and (limiter < 0.5): limiter = 1.0 elif (target <= -1.0) and (limiter > -0.5): limiter -1.0 limiter *= 2.0 if abs(target) <= abs(limiter): return target else: return limiter else: # but if target and limiter have different signs, do the visually # least jarring thing: limiter /= 2.0 if abs(limiter) < 1.5: if target >= 0: return 1 else: return -1 return limiter # only cover at most half the distance in one tick (except if we're just # one pixel off -- we're rounding up for that reason); this dampens # small erratic movements when they occur in quick succession, and makes # transitions to other places of the map more graceful. delta_x = round_to_larger_magnitude((new_position[0] - old_position[0]) / 2.0) delta_y = round_to_larger_magnitude((new_position[1] - old_position[1]) / 2.0) delta = math.sqrt((delta_x ** 2) + (delta_y ** 2)) # limit maximum scrolling distance per tick: if delta > self._max_scroll_speed: r = self._max_scroll_speed / delta delta_x *= r delta_y *= r # the other half of the 'graceful transitions' formula is to build speed # up slowly in addition to slowing down gently. we do this by remembering # the previous scrolling speed, which is simplistic, but sufficient: delta_x = throttle(delta_x, self._scroll_speed[0]) delta_y = throttle(delta_y, self._scroll_speed[1]) self._scroll_speed = (delta_x, delta_y) return self._scroll_speed
class Gameobject(object): """ Base class for all game objects. Note: Never trust the y velocity to be 0 when on ground. NEVER. IT ISN'T!!! """ gravity = -0.3 collision_group = None container = False def can(self, method): """ returns wether this gameobject can respond to method """ return hasattr(self, method) \ and type(getattr(self, method)) is types.MethodType def __init__(self, position, width=1, height=1): self.is_dead = False self.boundingbox = Boundingbox(position, (width, height), gameobject=self) self.velocity = [0.0, 0.0] self.on_ground = False self._ground_detected = False self._mini_graphic_is_valid = False self._mini_graphic = None def is_on_ground(self): return self.on_ground def facing_left(self): return self.velocity[0] < 0 #use this instead! def init(self): pass def tick(self): self.on_ground = self._ground_detected self._ground_detected = False def move(self): if self.affected_by_gravity: self.velocity[1] += Gameobject.gravity self.boundingbox.move(self.velocity) def draw(self): pass def get_mini_graphic(self, width=None): if not self._mini_graphic_is_valid: self._mini_graphic = self._make_mini_graphic(width) self._mini_graphic_is_valid = True return self._mini_graphic # check wether this collides with the other gameobject (called from gamestate for each object pair twice) #def collide_with(self, gameobject): # pass # one object may call this function of another when they collide def collided_with(self, gameobject, description=None, details=None): if description == 'ground' or (description == 'landingzone'): self.velocity[1] = -2 # this needs to be -2, so that little bumps in the level don't send us to a "in_air" state self._ground_detected = True def die(self, killer=None): self.is_dead = True def revive(self): self.is_dead = False def on_key_press(self, symbol, modifiers): pass def on_key_release(self, symbol, modifiers): pass def set_mid_bottom(self, pos): """move this object so its mid_bottom is at pos. why mid_bottom and not center or lower_left? well, the thing is, we shouldn't need this at all, once the collision system has some wrinkles ironed out. until then, there is just one section of code that wants to change a Gameobject's position from the outside, and that one section wants to specify the mid_bottom, so here we go. apart from that other section of code that needs to set the 'top' attribute ... ugh ... see set_top(). """ self.boundingbox.mid_bottom = pos def set_top(self, top): """move this object so its top is at top. see set_mid_bottom """ self.boundingbox.top = top