def make_stats_box(screen, player, dungeon_level, box_x_start, box_heigth, box_width): """Create the box displaying the stats @param screen: the screen to draw on @param player: the player object @param dungeon_level: the current dungeon level @param box_x_start: rectangle upper left corner x-coordinate @param box_heigth: height of rectangle @param box_width: width of rectangle """ #create the rectangle stats_box = Rect(box_x_start, 0, box_width, box_heigth) #set font type stats_font = font.SysFont('arial', 20) #render game info player_HP = stats_font.render("Hit Points: " + str(player.getHP()), True, Color('white')) player_AP = stats_font.render("Attack Power: " + str(player.getAttackPower()), True, Color('white')) player_Armor = stats_font.render("Armor: " + str(player.getArmor()), True, Color('white')) level = stats_font.render("Dungeon Level: " + str(dungeon_level), True, Color('white')) #For each line of text, draw it on the screen and move the rectangle for the next line screen.fill(Color('Black'), stats_box) screen.blit(player_HP, stats_box) screen.blit(player_AP, stats_box.move(0, player_HP.get_height())) screen.blit(player_Armor, stats_box.move(0, player_HP.get_height() + player_AP.get_height())) screen.blit(level, stats_box.move(0, player_HP.get_height() + player_AP.get_height() + player_Armor.get_height()))
class Star (object): def __init__ (self, level, pos, got): self.rect = Rect(pos, conf.STAR_SIZE) self.got = got self.bg = level.game.img('star-bg.png') self.fg = level.game.img('star-fg.png') self.sfc = pg.Surface(self.fg.get_size()).convert_alpha() self.glow = 0 self.glow_dirn = 1 def draw (self, screen, offset): r = self.rect.move(offset) screen.blit(self.bg, r) sfc = self.sfc g = self.glow # draw fg with opacity level from glow sfc.fill((255, 255, 255, ir(g * 255))) sfc.blit(self.fg, (0, 0), None, pg.BLEND_RGBA_MULT) screen.blit(sfc, r) # update glow d = self.glow_dirn g += d * conf.STAR_PULSE_SPEED gb = min(1, max(0, g)) if g != gb: d *= -1 self.glow = gb self.glow_dirn = d
def __draw_active_power_ups(self, text_rect: Rect) -> None: if bool(self.__active_power_up_jump): original_rect = self.__active_power_up_jump.sprite.rect self.__active_power_up_jump.draw(self.__surface, text_rect.move((original_rect.width + 5) * -2, -4)) if bool(self.__active_power_up_shield): original_rect = self.__active_power_up_shield.sprite.rect self.__active_power_up_shield.draw(self.__surface, text_rect.move((original_rect.width + 5) * -1, -4))
def show_unoptimized( self): # Requires pygame, will crash if launched without it for i in range(len(self.walls) - self.hwStartIndex): if self.walls[i]: pos = Rect(i % (self.w + 1) * self.cw, i // (self.w + 1) * self.ch, 0, 0) draw.line(self.window, (0, 0, 0), pos[:2], pos.move(0, self.ch)[:2]) for i in range(self.hwStartIndex, len(self.walls)): if self.walls[i]: a = (i - self.hwStartIndex) pos = Rect(a % self.w * self.cw, a // self.w * self.ch, 0, 0) draw.line(self.window, (0, 0, 0), pos[:2], pos.move(self.cw, 0)[:2])
def make_O(rect: Rect, bw: int) -> Sequence[Rect]: rects = [] for i in range(2): for j in range(2): r = rect.move(i*bw, j*bw) rects.append(r) return rects
def _prepare_sprite(self, sprite): """Prepare a sprite before drawing. Return the corresponding rectangle. """ # Aliases old_rect_dct = self.spritedict use_update = self._use_update # Test dirty rects try: sprite.dirty_rects except AttributeError: sprite.dirty_rects = None # Update dirtyness if not sprite.dirty and sprite.dirty_rects: sprite.dirty = 1 # Get actual size try: size_rect = Rect((0,0), sprite.source_rect.size) except AttributeError: size_rect = Rect((0,0), sprite.rect.size) # Compare rect new_rect = size_rect.move(sprite.rect.topleft) has_moved = new_rect != old_rect_dct[sprite] # Whole rect is dirty if not use_update or has_moved or sprite.dirty_rects is None: sprite.dirty_rects = [size_rect] return new_rect # Clip the dirty rects for k, area in enumerate(sprite.dirty_rects): sprite.dirty_rects[k] = area.clip(size_rect) # Return return new_rect
class Platform: def __init__(self, x, y): self.PLATFORM_HEIGHT = c.platform_height self.PLATFORM_WIDTH = c.platform_width self.PLATFORM_COLOR = c.platform_color #self.platform_image = image.load("data/blocks/platform.png") #self.platform_image.set_colorkey(Color(self.PLATFORM_COLOR)) self.platform = Rect(x, y, self.PLATFORM_WIDTH, self.PLATFORM_HEIGHT) '''def left(self): return self.platform.left def right(self): return self.platform.right def top(self): return self.platform.top def bottom(self): return self.platform.bottom''' def draw(self, surface, pos): #surface.blit(self.platform_image, self.platform.move(pos)) draw.rect(surface, self.PLATFORM_COLOR, self.platform.move(pos)) def update(self): pass def move(self, dx, dy): self.platform.x += dx self.platform.y += dy
def test_move(self): r = Rect(1, 2, 3, 4) move_x = 10 move_y = 20 r2 = r.move(move_x, move_y) expected_r2 = Rect(r.left + move_x, r.top + move_y, r.width, r.height) self.assertEqual(expected_r2, r2)
def apply(self, rect: pg.Rect) -> pg.Rect: """Returns a rectangle offset by the camera's position. :param rect: pygame rectangle whose position we wish to offset. :return: A rectangle whose coordinates are the input's, offset by the camera's. """ return rect.move(-self.rect.x, -self.rect.y)
def _prepare_sprite(self, sprite): """Prepare a sprite before drawing. Return the corresponding rectangle. """ # Aliases old_rect_dct = self.spritedict use_update = self._use_update # Test dirty rects try: sprite.dirty_rects except AttributeError: sprite.dirty_rects = None # Update dirtyness if not sprite.dirty and sprite.dirty_rects: sprite.dirty = 1 # Get actual size try: size_rect = Rect((0, 0), sprite.source_rect.size) except AttributeError: size_rect = Rect((0, 0), sprite.rect.size) # Compare rect new_rect = size_rect.move(sprite.rect.topleft) has_moved = new_rect != old_rect_dct[sprite] # Whole rect is dirty if not use_update or has_moved or sprite.dirty_rects is None: sprite.dirty_rects = [size_rect] return new_rect # Clip the dirty rects for k, area in enumerate(sprite.dirty_rects): sprite.dirty_rects[k] = area.clip(size_rect) # Return return new_rect
def test_move( self ): r = Rect( 1, 2, 3, 4 ) move_x = 10 move_y = 20 r2 = r.move( move_x, move_y ) expected_r2 = Rect(r.left+move_x,r.top+move_y,r.width,r.height) self.assertEqual( expected_r2, r2 )
def rectAnchor(posn, size, anchor=NW): "Returns a rectangle using a position relative to the specified anchor point" w, h = size r = Rect(posn + size) dx = 0 if anchor & WEST else w if anchor & EAST else w // 2 dy = 0 if anchor & NORTH else h if anchor & SOUTH else h // 2 return r.move(-dx, -dy)
def cache_inventory(self): '''Making the assumption that we won't really have that many items''' self.inventory_text = [] add = self.inventory_text.append text = self.render_text loc = Rect(375,50,0,0) for i in self.inventory: add(text(i.get_name(),loc)) loc.move_ip(0,25) if len(self.inventory_text) == 0: add(text("Your inventory is empty",loc.move(30,0)))
class Body(): def __init__(self): self.pos = Pt(0, 0) # self.radius = Position(15, -25) self.shape = Rect(0, 0, 30, 50) self.scheme = None self.name = None @property def cpos(self): # return self.pos - self.radius return self.pos @property def rect(self): return self.shape.move(self.pos.x, self.pos.y) def update(self, level, keys): pass def draw(self, surface, camera): tpos = camera.transform(self.cpos) # sprite = self.sprites[self.state.name] # surface.blit(sprite, tpos) pt = camera.transform(self.pos) br = Rect(pt, (0, self.shape.height)).inflate( self.shape.width / 2, 0).move(0, -self.shape.height) # pygame.draw.rect(surface, pygame.Color('azure'), camera.transform(self.rect)) pygame.draw.rect(surface, pygame.Color('skyblue'), br) # helper.marker(pt, surface) # tp = camera.transform(self.pos) # pygame.draw.line(surface, (200, 0, 0), (tp[0], tp[1]-10), (tp[0], tp[1]+10)) # pygame.draw.line(surface, (200, 0, 0), (tp[0]-10, tp[1]), (tp[0]+10, tp[1])) @staticmethod def from_xml(xml_node): def load_tuple(val): return make_tuple(xml_node.attrib[val]) position = load_tuple('pos') size = load_tuple('size') scheme = xml_node.attrib['scheme'] body = Body() body.pos = Pt(position) body.shape = Rect((0, 0), size) body.name = xml_node.attrib['id'] # body.scheme = schemes.get(scheme) body.scheme = scheme return body
class GameObject: def __init__(self, x, y, w, h, speed=(0, 0)): self.bounds = Rect(x, y, w, h) self.speed = speed @property def left(self): return self.bounds.left @property def right(self): return self.bounds.right @property def top(self): return self.bounds.top @property def bottom(self): return self.bounds.bottom @property def width(self): return self.bounds.width @property def height(self): return self.bounds.height @property def center(self): return self.bounds.center @property def centerx(self): return self.bounds.centerx @property def centery(self): return self.bounds.centery def draw(self, surface): pass def move(self, dx, dy): self.bounds = self.bounds.move(dx, dy) def update(self): """""" if self.speed == [0, 0]: return self.move(*self.speed)
def make_stats_box(screen, player, dungeon_level, box_x_start, box_heigth, box_width): """Create the box displaying the stats @param screen: the screen to draw on @param player: the player object @param dungeon_level: the current dungeon level @param box_x_start: rectangle upper left corner x-coordinate @param box_heigth: height of rectangle @param box_width: width of rectangle """ #create the rectangle stats_box = Rect(box_x_start, 0, box_width, box_heigth) #set font type stats_font = font.SysFont('arial', 20) #render game info player_HP = stats_font.render("Hit Points: " + str(player.getHP()), True, Color('white')) player_AP = stats_font.render( "Attack Power: " + str(player.getAttackPower()), True, Color('white')) player_Armor = stats_font.render("Armor: " + str(player.getArmor()), True, Color('white')) level = stats_font.render("Dungeon Level: " + str(dungeon_level), True, Color('white')) #For each line of text, draw it on the screen and move the rectangle for the next line screen.fill(Color('Black'), stats_box) screen.blit(player_HP, stats_box) screen.blit(player_AP, stats_box.move(0, player_HP.get_height())) screen.blit( player_Armor, stats_box.move(0, player_HP.get_height() + player_AP.get_height())) screen.blit( level, stats_box.move( 0, player_HP.get_height() + player_AP.get_height() + player_Armor.get_height()))
class Player(Sprite): def __init__(self, x, y): super().__init__() self.xvel = 0 self.yvel = 0 self.onGround = True self.image = Surface((32, 32)) self.image.fill(Color("#00FFFF")) self.image.convert() self.rect = Rect(x, y, 32, 32) def update(self): self.rect = self.rect.move(3, 0)
def rect (self, rect): # need to set dirty in old and new rects (if changed) last = self.last_rect rect = Rect(rect) if rect != last: sz = rect.size if sz != last.size: self.resize(*sz) else: # handled in resize self._rect = rect self._postrot_rect = Rect(rect.move(self._rot_offset)[:2], self._postrot_rect[2:]) self._mk_dirty()
def _widgetRect(self, widget, rect=None): # simplistic. doesn't account for scaling or translation. If you want those, implement the # _widgetRect(rect) method on your widget class itself. if rect is None: rect = Rect(0, 0, widget.w, widget.h) if widget is self: return rect elif widget.parent is None: return None elif hasattr(widget, '_widgetRect'): return self._widgetRect(widget.parent, widget._widgetRect(rect)) elif issubclass(widget.__class__, Translatable): return self._widgetRect(widget.parent, rect.move(widget.x, widget.y)) else: return self._widgetRect(widget.parent, rect)
class Widget(sprite.Sprite): #a widget has a position def __init__(self, x=None, y=None): sprite.Sprite.__init__(self) self.image, self.rect = self.load_image(self.display, Color('#FF00FF')) self.x = x self.y = y if self.x is not None: if self.y is not None: self.rect = Rect(x*50, y*50, 50, 50) # functions should be implemented in subclasses # if they are relevant to that widget type def eaten(self): return ("boredom", -1) def played(self): return ("boredom", -1) # if creature tries to walk into widget, def intersected(self): return ("boredom", -1) def mated(self, tako): return ("boredom", -1) def load_image(self, name, colorkey=None): fullname = os.path.join('img', name) img = image.load(fullname) img = img.convert() if colorkey is not None: if colorkey is -1: colorkey = img.get_at((0,0)) img.set_colorkey(colorkey, RLEACCEL) return img, img.get_rect() def update_rect(self): self.rect = Rect(self.x*50, self.y*50, 50, 50) def move_rect(self, x, y): self.rect = self.rect.move(x*50, y*50)
def shift_by_topleft(self, rect: pg.Rect) -> pg.Rect: return rect.move(self.rect.topleft)
class PlayerCommand: """ Display pannel for player input/output Controls display area for player input/output """ def __init__(self): self.msg1 = "text area 1" self.msg2 = "text area 2" self.msg3 = "text area 3" self.x = 50 self.width = 20 self.offset = 0 self.back_color = "black" self.message_rect = Rect(600, 0, SCREEN_WIDTH, SCREEN_HEIGHT) self.message_size = (int(self.message_rect[2]) - self.message_rect[0], int(self.message_rect[3])) self.unit_group_rect = Rect(630, 150, 15, 15) self.player_msg = "Player message here" self.tick_counter = "Ticks here" def msg_to_player(self, screen): """ tracks and handles messages to player """ message1_sf = DEFAULT_GAME_FONT.render(self.player_msg, True, Color('white')) screen.blit(message1_sf, (20, 550, SCREEN_WIDTH, SCREEN_HEIGHT)) """ tick counter """ message2_sf = DEFAULT_GAME_FONT.render(self.tick_counter, True, Color('white')) screen.blit(message2_sf, (20, 20, SCREEN_WIDTH, SCREEN_HEIGHT)) def draw_messageboard(self, screen): self.draw_rimmed_box(screen, self.message_rect, (self.x, self.width, self.offset), 4, Color(self.back_color)) DEFAULT_GAME_FONT = pygame.font.SysFont('arial', 18) message1_sf = DEFAULT_GAME_FONT.render(self.msg1, True, Color('white')) message2_sf = DEFAULT_GAME_FONT.render(self.msg2, True, Color('white')) message3_sf = DEFAULT_GAME_FONT.render(self.msg3, True, Color('white')) message4 = "Heroes: " + str(HERO_WINS) message4_sf = DEFAULT_GAME_FONT.render(message4, True, Color('white')) message5 = "Enemies: " + str(ENEMY_WINS) message5_sf = DEFAULT_GAME_FONT.render(message5, True, Color('white')) screen.blit(message1_sf, self.message_rect.move(0, message1_sf.get_height())) screen.blit(message2_sf, self.message_rect.move(0, message2_sf.get_height() * 2)) screen.blit(message3_sf, self.message_rect.move(0, message2_sf.get_height() * 3)) screen.blit(message4_sf, self.message_rect.move(0, message4_sf.get_height() * 4)) screen.blit(message5_sf, self.message_rect.move(0, message5_sf.get_height() * 5)) self.msg_to_player(screen) def draw_player_units(self, screen, unit_group): """ blits player unit text to output display window """ self.draw_rimmed_box(screen, Rect(600, 150, SCREEN_WIDTH, SCREEN_HEIGHT), (self.x, self.width, self.offset), 4, Color(self.back_color)) DEFAULT_GAME_FONT = pygame.font.SysFont('arial', 12) offset = 0 for unit in unit_group: message1_sf = DEFAULT_GAME_FONT.render(unit.info_msg1, True, Color('white')) message1_status = DEFAULT_GAME_FONT.render(unit.txt_status, True, Color('white')) message2_sf = DEFAULT_GAME_FONT.render(unit.info_msg2, True, Color('white')) screen.blit( message1_sf, self.unit_group_rect.move( 0, message1_sf.get_height() * 1 + offset * 24)) screen.blit( message1_status, self.unit_group_rect.move( 100, message1_status.get_height() * 1 + offset * 24)) screen.blit( message2_sf, self.unit_group_rect.move( 0, message2_sf.get_height() * 2 + offset * 24)) for button in unit.unit_btns: button.draw(screen) offset += 2 def draw_rimmed_box(self, screen, box_rect, box_color, rim_width=0, rim_color=Color('black')): """ Draw a rimmed box on the given surface. The rim is drawn outside the box rect. """ if rim_width: rim_rect = Rect(box_rect.left - rim_width, box_rect.top - rim_width, box_rect.width + rim_width * 2, box_rect.height + rim_width * 2) pygame.draw.rect(screen, rim_color, rim_rect) pygame.draw.rect(screen, box_color, box_rect) def in_field(self, pos): """ verify if clicked pos is in playable grid area - returns True/False """ loc = self.coord_to_grid(pos) if loc[0] < 0 or loc[0] >= self.x or loc[1] < 0 or \ loc[1] >= GRID_SIZE[1]: #print("you missed the player_command grid") return (False) else: return (True) def grid_clicked(self, pos): """ tells what grid was clicked on and reports for testing purposes pos: the passed mouse coordinates variable passed through """ if self.in_field(pos): #print("click is in player_command field") #print("pos clicked:", pos) dummy = False ################################################################################ ## TEST ################################################################################ # # if __name__ == "__main__": # # screen = pygame.display.set_mode((params.SCREEN_WIDTH, params.SCREEN_HEIGHT), 0, 32) # create screen area for tiles # # #pawn = SimpleUnit() # pawn_group = SimpleUnitGroup(screen) # for unit in pawn_group.group_list: # print("unit loc:", unit.loc) # #test loop to give coords to units # for unit in pawn_group.group_list: # unit.targ_tile = (25,25) # unit.active = True # print("status:") # test of status message update # print(unit.info_msg1) # test of status message update # print(unit.info_msg2) # test of status message update # #test loop to move units # unit.move_unit() # for unit in pawn_group.group_list: # check locations again to see if moved # print("unit loc:", unit.loc) # # # test player groups - test works # # all_players = PlayerUnitGroup(screen) # - These lines are the lines to call/create the players & units for a game # # all_players.print_all_player_units() # Test to print all player units to shell - works # # all_players.update_players() # Test of update logic path # # print() # print("-- TEST DONE --") # print() # pygame.quit() # sys.exit()
def movement_shoot_monster(self): self.rect_bullet = Rect.move(self.rect_bullet,0,self._shootSpeed) rect(self.surface, self.color_bullet_monster, self.rect_bullet)
class Dialog: def __init__(self, text, options=None): self.options = options if options is not None: self.selection = 0 else: self.selection = None self.fg = pygame.color.Color('white') self.bg = pygame.color.Color('blue') self.tr = pygame.color.Color('black') half = core.screen.get_width() * 4 / 5 self.rect = Rect(0,0,half,0) self.text = text self.font = pygame.font.Font(None, 20) self.split_text() self.render_text() self.window = core.wm.window(half,self.rect.height,'center','center') self.window.update = self.update self.window.handle_event = self.handle_event self.screen = self.window.image self.screen.set_colorkey(self.tr, RLEACCEL) self.rect = Rect(self.window.rect) self.rect.center = self.screen.get_rect().center r = self.rect.inflate(-6,-6) self.bgwin = core.wm.window(r.width,r.height,'center','center',z=3) self.bgwin.image.fill(self.bg) self.bgwin.image.set_alpha(128) self.borderwin = core.wm.window(self.rect.width,self.rect.height, \ 'center','center', z=2) self.borderwin.image.fill(self.tr) dialog.draw_round_border(self.borderwin.image,color=self.fg) self.borderwin.image.set_colorkey(self.tr, RLEACCEL) def hide(self): self.window.hide() self.bgwin.hide() self.borderwin.hide() def nop(self): pass def update(self): self.window.update = self.nop self.screen.fill(self.tr) draw_round_border(self.screen,color=self.fg) current_y = 10 for line in self.rendered: self.screen.blit(line, self.rect.move(10,current_y)) current_y = current_y + self.font.get_linesize() current_y = current_y + self.font.get_linesize() first_option_y = current_y for option in self.rendered_opts: self.screen.blit(option, self.rect.move(20,current_y)) current_y = current_y + self.font.get_linesize() if self.selection is not None: y_pos = first_option_y + self.selection * self.font.get_linesize() y_pos = y_pos + self.font.get_linesize() / 2 # Center it in line rect = self.rect.move(10, y_pos) center = (rect.left, rect.top) draw.circle(self.screen, self.fg, center, 5) def render_one_line(self, line): return self.font.render(line, True, self.fg).convert_alpha() def render_text(self): self.rendered = map(self.render_one_line, self.lines) if self.options is not None: self.rendered_opts = map(self.render_one_line, self.options) line_count = len(self.rendered) + len(self.rendered_opts) + 1 else: self.rendered_opts = [] line_count = len(self.rendered) self.rect.height = self.font.get_linesize() * line_count + 20 def split_text(self): self.lines = [] current = '' for chunk in re.split(' ', self.text): width, height = self.font.size(current + chunk + ' ') if width < self.rect.width - 10: current = current + chunk + ' ' else: self.lines.append(current) current = chunk + ' ' if len(current) > 1: self.lines.append(current) def selection_up(self): if self.selection is not None: if self.selection == 0: self.selection = len(self.options)-1 else: self.selection = self.selection - 1 self.window.update = self.update def selection_down(self): if self.selection is not None: if self.selection < len(self.options)-1: self.selection = self.selection + 1 else: self.selection = 0 self.window.update = self.update def run(self): self.window.show() self.window.focus() core.wm.run() core.wm.running = True self.dispose() core.game.save_data.map.focus() if self.selection is not None: return self.selection else: return None def handle_event(self, event): if event.type == PUSH_ARROW_EVENT or \ event.type == REPEAT_ARROW_EVENT: if core.event_bag.is_up(): self.selection_up() elif core.event_bag.is_down(): self.selection_down() elif event.type == PUSH_ACTION_EVENT: core.wm.running = False def dispose(self): self.rendered = None self.window.destroy() self.bgwin.destroy() self.borderwin.destroy() self.window.update = None
class Level (object): def __init__ (self, game, event_handler = None, ID = 0, cp = -1): self.game = game # input if event_handler is not None: event_handler.add_event_handlers({ pg.KEYDOWN: self.skip, pg.MOUSEBUTTONDOWN: self.skip }) event_handler.add_key_handlers([ (conf.KEYS_BACK, self.pause, eh.MODE_ONDOWN), (conf.KEYS_RESET, self.reset, eh.MODE_ONDOWN), (conf.KEYS_JUMP, self.jump, eh.MODE_ONDOWN_REPEAT, 1, 1) ] + [ (ks, [(self.move, (i,))], eh.MODE_HELD) for i, ks in enumerate((conf.KEYS_LEFT, conf.KEYS_RIGHT)) ]) w, h = conf.RES self.centre = (w / 2, h / 2) ww, wh = conf.WINDOW_SIZE border = (2 * (ww + 5), 2 * (wh + 5)) self.window_bds = pg.Rect(0, 0, w, h).inflate(border) self.clouds = [] self.load_graphics() if event_handler is not None: self.move_channel = game.move_channel self.star_channel = game.star_channel else: self.move_channel = None self.star_channel = None # load first level self.ID = None self.init(ID, cp) def init (self, ID = None, cp = None): self.paused = False self.dying = False self.first_dying = False self.winning = False self.fading = False self.particles = [] self.particle_rects = [] self.void_jitter = [conf.VOID_JITTER_X, conf.VOID_JITTER_Y, conf.VOID_JITTER_T] self.first = True # get level/current checkpoint if ID is None: # same level ID = self.ID if ID != self.ID: # new level self.ID = ID self.current_cp = cp if cp is not None else -1 # clouds: randomise initial positions and velocities self.clouds = cs = [] w, h = conf.RES imgs = self.imgs vx0 = conf.CLOUD_SPEED vy0 = vx0 * conf.CLOUD_VERT_SPEED_RATIO self.cloud_vel = [vx0 * random0(), vy0 * random0()] vx = conf.CLOUD_MOD_SPEED_RATIO vy = vx * conf.CLOUD_VERT_SPEED_RATIO for c in conf.CLOUDS: c_w, c_h = imgs[c].get_size() s = (c_w, c_h) c_w /= 2 c_h /= 2 pos = [randint(-c_w, w - c_w), randint(-c_h, h - c_h)] vel = [vx * random0(), vy * random0()] cs.append((pos, vel, s)) elif cp is not None: self.current_cp = cp data = conf.LEVELS[ID] # background self.bgs = data.get('bgs', conf.DEFAULT_BGS) # player if self.current_cp >= 0: p = list(data['checkpoints'][self.current_cp][:2]) s_p, s_c = conf.PLAYER_SIZE, conf.CHECKPOINT_SIZE for i in (0, 1): p[i] += float(s_c[i] - s_p[i]) / 2 else: p = data['player_pos'] self.player = Player(self, p) # window x, y = Rect(self.to_screen(self.player.rect)).center w, h = conf.HALF_WINDOW_SIZE self.window = Rect(x - w, y - h, 2 * w, 2 * h) self.old_window = self.window.copy() # checkpoints s = conf.CHECKPOINT_SIZE self.checkpoints = [Rect(p + s) for p in data.get('checkpoints', [])] # goal self.goal = Rect(data['goal'] + conf.GOAL_SIZE) self.goal_img = self.goal.move(conf.GOAL_OFFSET) self.goal_img.size = self.imgs['goal'].get_size() # stars self.stars = [Star(self, p, [ID, i] in conf.STARS) for i, p in enumerate(data.get('stars', []))] if self.star_channel is not None and not all(s.got for s in self.stars): self.star_channel.unpause() # rects self.all_rects = [Rect(r) for r in data.get('rects', [])] self.all_vrects = [Rect(r) for r in data.get('vrects', [])] self.arects = [Rect(r) for r in data.get('arects', [])] self.update_rects() def skip (self, evt): if self.dying and self.dying_counter < conf.DIE_SKIP_THRESHOLD and \ not (evt.type == pg.KEYDOWN and evt.key in conf.KEYS_BACK) and \ not self.winning: self.init() elif conf.DEBUG and evt.type == pg.MOUSEBUTTONDOWN: r = self.player.rect c = self.window.center print 'moving to', c for i in (0, 1): r[i] = c[i] - (r[i + 2] / 2) self.player.old_rect = r def pause (self, *args): if self.move_channel is not None: self.move_channel.pause() if self.star_channel is not None: self.star_channel.pause() self.game.start_backend(ui.Paused, self) self.paused = True def reset (self, *args): if not self.winning: self.init() def jump (self, key, mode, mods): self.player.jump(mode == 0) def move (self, key, mode, mods, i): self.player.move(i) def update_window (self): w = self.window wp0 = w.topleft wp1 = w.bottomright s = conf.RES self.inverse_win = rs = [] for px in (0, 1, 2): for py in (0, 1, 2): if px == py == 1: continue r = [0, 0, 0, 0] for i, p in enumerate((px, py)): if p == 0: r[i + 2] = wp0[i] if p == 1: r[i] = wp0[i] r[i + 2] = wp1[i] - wp0[i] elif p == 2: r[i] = wp1[i] r[i + 2] = s[i] - wp1[i] if r[2] > 0 and r[3] > 0: rs.append(Rect(r)) def get_clip (self, r1, r2, err = 0): x01, y01, w, h = r1 x11, y11 = x01 + w, y01 + h x02, y02, w, h = r2 x12, y12 = x02 + w, y02 + h x0, y0 = max(x01, x02), max(y01, y02) x1, y1 = min(x11, x12), min(y11, y12) w, h = x1 - x0, y1 - y0 if w > err and h > err: return (x0, y0, w, h) def update_rects (self): self.update_window() # rects self.rects = rects = [] self.draw_rects = draw = [] w = self.window for r in self.all_rects: c = w.clip(r) if c: rects.append(c) draw.append(r) # vrects self.vrects = rects = [] ws = self.inverse_win for r in self.all_vrects: for w in ws: c = w.clip(r) if c: rects.append(c) def handle_collisions (self): get_clip = self.get_clip p = self.player.rect p0 = list(p) for r in self.rects + self.vrects + self.arects: if get_clip(r, p): r_x0, r_y0, w, h = r r_x1, r_y1 = r_x0 + w, r_y0 + h p_x0, p_y0, w, h = p p_x1, p_y1 = p_x0 + w, p_y0 + h x, dirn = min((p_x1 - r_x0, 0), (p_y1 - r_y0, 1), (r_x1 - p_x0, 2), (r_y1 - p_y0, 3)) axis = dirn % 2 p[axis] += (1 if dirn >= 2 else -1) * x self.player.impact(axis, 0) if axis == 1: self.vert_dirn = dirn # screen left/right if p[0] < 0: p[0] = 0 self.player.impact(0, 0) elif p[0] + p[2] > conf.RES[0]: p[0] = conf.RES[0] - p[2] self.player.impact(0, 0) # die if still colliding axes = set() e = conf.ERR colliding = [r for r in self.rects + self.vrects + self.arects \ if get_clip(r, p, e)] if colliding: for r in colliding: r_x0, r_y0, w, h = r r_x1, r_y1 = r_x0 + w, r_y0 + h p_x0, p_y0, w, h = p p_x1, p_y1 = p_x0 + w, p_y0 + h x, dirn = min((p_x1 - r_x0, 0), (p_y1 - r_y0, 1), (r_x1 - p_x0, 2), (r_y1 - p_y0, 3)) axes.add(dirn % 2) if len(axes) == 2: dirn = .5 else: dirn = .95 if axes.pop() == 0 else .1 self.die(dirn) def die (self, dirn = .5): self.first_dying = True self.dying = True self.dying_counter = conf.DIE_TIME # particles pos = list(Rect(self.to_screen(self.player.rect)).center) self.add_ptcls('die', pos, dirn) # sound if self.move_channel is not None: self.move_channel.pause() self.game.play_snd('die') def next_level (self, save = True, progress = True): if progress: if self.move_channel is not None: self.move_channel.pause() if self.star_channel is not None: self.star_channel.pause() i = self.ID if not conf.COMPLETED and i + 1 in conf.EXISTS: # there's a next level if save: conf.CURRENT_LEVEL = i + 1 if progress: self.init(i + 1) else: if save: conf.COMPLETED = True if progress: self.game.switch_backend(ui.LevelSelect) def win (self): if self.winning: return self.winning = True self.next_level(progress = False) if self.ID not in conf.COMPLETED_LEVELS: conf.COMPLETED_LEVELS.append(self.ID) conf.dump() self.start_fading(lambda: self.next_level(False)) def update (self): # fade counter if self.fading: self.fade_counter -= 1 if self.fade_counter == 0: self.fading = False del self.fade_sfc self.fade_cb() # move player if not self.dying: pl = self.player pl.update() # get amount to move window by w = self.window self.old_window = w.copy() x0, y0 = self.centre if self.paused or self.first: dx = dy = 0 self.first = False else: x, y = pg.mouse.get_pos() dx, dy = x - x0, y - y0 # don't move too far outside the screen w_moved = w.move(dx, dy).clamp(self.window_bds) dx, dy = w_moved[0] - w[0], w_moved[1] - w[1] pg.mouse.set_pos(x0, y0) wx0, wy0, ww, wh = self.total_window = w.union(w.move(dx, dy)) # move window if self.dying: # just move window w.move_ip(dx, dy) self.update_rects() else: self.vert_dirn = 3 if dx == dy == 0: # just handle collisions self.handle_collisions() else: # check if player and window intersect wx1, wy1 = wx0 + ww, wy0 + wh r = pl.rect o_r = pl.old_rect px0, py0 = min(r[0], o_r[0]), min(r[1], o_r[1]) px1 = max(r[0] + r[2], o_r[0] + o_r[2]) py1 = max(r[1] + r[3], o_r[1] + o_r[3]) if px1 > wx0 and py1 > wy0 and px0 < wx1 and py0 < wy1: # if so, move window a few pixels at a time c = conf.WINDOW_MOVE_AMOUNT for axis, d in ((0, dx), (1, dy)): dirn = 1 if d > 0 else -1 while d * dirn > 0: d -= dirn * c rel = [0, 0] rel[axis] += c * dirn + (0 if d * dirn > 0 else d) w.move_ip(rel) self.update_rects() if not self.dying: self.handle_collisions() else: # else move it the whole way w.move_ip(dx, dy) self.update_rects() self.handle_collisions() if self.vert_dirn == 1: pl.on_ground = conf.ON_GROUND_TIME # clouds if self.clouds: # jitter jx = conf.CLOUD_JITTER jy = jx * conf.CLOUD_VERT_SPEED_RATIO v0 = self.cloud_vel v0[0] += jx * random0() v0[1] += jy * random0() r = conf.RES for p, v, s in self.clouds: for i, (i_w, r_w) in enumerate(zip(s, r)): # move x = p[i] x += v0[i] + v[i] # wrap if x + i_w < 0: x = r_w elif x > r_w: x = -i_w p[i] = x # particles ptcls = [] rects = [] for k, j, group in self.particles: g = [] x0, y0 = conf.RES x1 = y1 = 0 for c, p, v, size, t in group: x, y = p # update boundary if x < x0: x0 = x if y < y0: y0 = y if x + size > x1: x1 = x + size if y + size > y1: y1 = y + size t -= 1 if t != 0: # move vx, vy = v x += vx y += vy # update boundary if x < x0: x0 = x if y < y0: y0 = y if x + size > x1: x1 = x + size if y + size > y1: y1 = y + size # damp/jitter vx *= k vy *= k vx += j * random0() vy += j * random0() g.append((c, (x, y), (vx, vy), size, t)) if g: ptcls.append((k, j, g)) if x1 > x0 and y1 > y0: rects.append((int(x0), int(y0), ceil(x1 - x0), ceil(y1 - y0))) self.particles = ptcls self.particle_rects = rects # death counter if self.dying: self.dying_counter -= 1 if self.dying_counter == 0: self.init() return # player velocity pl.update_vel() # die if OoB if pl.rect[1] > conf.RES[1]: self.die() # win if at goal p = pl.rect c = w.clip(self.goal) if c and self.get_clip(p, c): self.win() # check if at checkpoints for c in self.checkpoints[self.current_cp + 1:]: if w.clip(c) and self.get_clip(p, c): self.current_cp += 1 # check if at stars for i, s in enumerate(self.stars): if not s.got and w.clip(s.rect) and self.get_clip(p, s.rect): #self.game.play_snd('collectstar') if self.star_channel is not None and all(s.got for s in self.stars): self.star_channel.pause() s.got = True conf.STARS.append([self.ID, i]) conf.dump() def load_graphics (self): self.imgs = imgs = {} for img in ('void', 'window', 'rect', 'vrect', 'arect', 'checkpoint-current', 'checkpoint', 'goal') + \ conf.BGS + conf.CLOUDS: imgs[img] = self.game.img(img + '.png') self.window_sfc = pg.Surface(conf.WINDOW_SIZE).convert_alpha() def to_screen (self, rect): return [ir(x) for x in rect] def add_ptcls (self, key, pos, dirn = .5): particles = [] data = conf.PARTICLES[key] max_speed = data['speed'] max_size = data['size'] k = data['damping'] j = data['jitter'] max_life = data['life'] dirn *= pi / 2 for c, amount in data['colours']: a, b = divmod(amount, 1) amount = int(a) + (1 if random() < b else 0) while amount > 0: size = randint(1, max_size) amount -= size angle = random() * 2 * pi speed = max_speed * expovariate(5) v = (speed * cos(dirn) * cos(angle), speed * sin(dirn) * sin(angle)) life = int(random() * max_life) if life > 0: particles.append((c, tuple(pos), v, size, life)) self.particles.append((k, j, particles)) def start_fading (self, cb): if not self.fading: self.fading = True self.fade_counter = conf.FADE_TIME self.fade_sfc = pg.Surface(conf.RES).convert_alpha() self.fade_cb = cb def update_jitter (self, jitter): if len(jitter) == 3: jx, jy, t0 = jitter t = t0 ox, oy = randint(0, jx), randint(0, jy) jitter += [ox, oy, t] else: jx, jy, t0, ox, oy, t = jitter if t == 0: ox, oy = randint(0, jx), randint(0, jy) jitter[3] = ox jitter[4] = oy jitter[5] = t0 jitter[5] -= 1 def draw (self, screen): # don't draw on last frame #if not self.game.running: #return False imgs = self.imgs w = self.window pl = self.player pl.pre_draw() # background jitter = self.void_jitter self.update_jitter(jitter) ox, oy = jitter[3], jitter[4] img = imgs['void'] draw_all = jitter[5] == conf.VOID_JITTER_T - 1 or self.fading or self.paused if self.paused: self.paused = False if draw_all: tile(screen, img, (0, 0) + screen.get_size(), ox, oy) else: draw_rects = self.particle_rects + [self.total_window, self.goal_img] if self.first_dying or not self.dying: draw_rects.append(pl.rect_img.union(pl.old_rect_img)) for r in draw_rects: tile(screen, img, r, ox, oy, (0, 0)) # vrects img = imgs['vrect'] for r in self.all_vrects: tile(screen, img, r) # window offset = (-w[0], -w[1]) w_sfc = self.window_sfc # window background: static images for img in self.bgs: if isinstance(img, str): pos = (0, 0) else: img, pos = img w_sfc.blit(imgs[img], Rect(pos + (0, 0)).move(offset)) # clouds for c, (p, v, s) in zip(conf.CLOUDS, self.clouds): w_sfc.blit(imgs[c], Rect(self.to_screen(p + [0, 0])).move(offset)) # rects in window img = imgs['rect'] for r, r_full in zip(self.rects, self.draw_rects): tile(w_sfc, img, r.move(offset), full = r_full.move(offset)) # checkpoints for i, r in enumerate(self.checkpoints): img = imgs['checkpoint' + ('-current' if i == self.current_cp else '')] w_sfc.blit(img, r.move(offset)) # window border w_sfc.blit(imgs['window'], (0, 0), None, pg.BLEND_RGBA_MULT) # copy window area to screen screen.blit(w_sfc, w) # arects img = imgs['arect'] for r in self.arects: tile(screen, img, r) # goal screen.blit(imgs['goal'], self.goal_img) # stars for s in self.stars: if not s.got: s.draw(screen, (0, 0)) # player if not self.dying: pl.draw(screen) # particles for k, j, g in self.particles: for c, p, v, size, t in g: screen.fill(c, p + (size, size)) # fadeout if self.fading: t = conf.FADE_TIME - self.fade_counter alpha = conf.FADE_RATE * float(t) / conf.FADE_TIME alpha = min(255, ir(alpha)) self.fade_sfc.fill((0, 0, 0, alpha)) screen.blit(self.fade_sfc, (0, 0)) draw_all = True if self.first_dying: self.first_dying = False if draw_all: return True else: return draw_rects + self.arects
class PlayerCommand: """ Display pannel for player input/output Controls display area for player input/output """ def __init__(self): self.msg1 = "text area 1" self.msg2 = "text area 2" self.msg3 = "text area 3" self.x = 50 self.width = 20 self.offset = 0 self.back_color = "black" self.message_rect = Rect(600, 0, SCREEN_WIDTH, SCREEN_HEIGHT) self.message_size = (int(self.message_rect[2]) - self.message_rect[0], int(self.message_rect[3])) self.unit_group_rect = Rect(630, 150, 15, 15) self.player_msg = "Player message here" self.tick_counter = "Ticks here" def msg_to_player(self, screen): """ tracks and handles messages to player """ message1_sf = DEFAULT_GAME_FONT.render(self.player_msg, True, Color("white")) screen.blit(message1_sf, (20, 550, SCREEN_WIDTH, SCREEN_HEIGHT)) """ tick counter """ message2_sf = DEFAULT_GAME_FONT.render(self.tick_counter, True, Color("white")) screen.blit(message2_sf, (20, 20, SCREEN_WIDTH, SCREEN_HEIGHT)) def draw_messageboard(self, screen): self.draw_rimmed_box(screen, self.message_rect, (self.x, self.width, self.offset), 4, Color(self.back_color)) DEFAULT_GAME_FONT = pygame.font.SysFont("arial", 18) message1_sf = DEFAULT_GAME_FONT.render(self.msg1, True, Color("white")) message2_sf = DEFAULT_GAME_FONT.render(self.msg2, True, Color("white")) message3_sf = DEFAULT_GAME_FONT.render(self.msg3, True, Color("white")) message4 = "Heroes: " + str(HERO_WINS) message4_sf = DEFAULT_GAME_FONT.render(message4, True, Color("white")) message5 = "Enemies: " + str(ENEMY_WINS) message5_sf = DEFAULT_GAME_FONT.render(message5, True, Color("white")) screen.blit(message1_sf, self.message_rect.move(0, message1_sf.get_height())) screen.blit(message2_sf, self.message_rect.move(0, message2_sf.get_height() * 2)) screen.blit(message3_sf, self.message_rect.move(0, message2_sf.get_height() * 3)) screen.blit(message4_sf, self.message_rect.move(0, message4_sf.get_height() * 4)) screen.blit(message5_sf, self.message_rect.move(0, message5_sf.get_height() * 5)) self.msg_to_player(screen) def draw_player_units(self, screen, unit_group): """ blits player unit text to output display window """ self.draw_rimmed_box( screen, Rect(600, 150, SCREEN_WIDTH, SCREEN_HEIGHT), (self.x, self.width, self.offset), 4, Color(self.back_color), ) DEFAULT_GAME_FONT = pygame.font.SysFont("arial", 12) offset = 0 for unit in unit_group: message1_sf = DEFAULT_GAME_FONT.render(unit.info_msg1, True, Color("white")) message1_status = DEFAULT_GAME_FONT.render(unit.txt_status, True, Color("white")) message2_sf = DEFAULT_GAME_FONT.render(unit.info_msg2, True, Color("white")) screen.blit(message1_sf, self.unit_group_rect.move(0, message1_sf.get_height() * 1 + offset * 24)) screen.blit(message1_status, self.unit_group_rect.move(100, message1_status.get_height() * 1 + offset * 24)) screen.blit(message2_sf, self.unit_group_rect.move(0, message2_sf.get_height() * 2 + offset * 24)) for button in unit.unit_btns: button.draw(screen) offset += 2 def draw_rimmed_box(self, screen, box_rect, box_color, rim_width=0, rim_color=Color("black")): """ Draw a rimmed box on the given surface. The rim is drawn outside the box rect. """ if rim_width: rim_rect = Rect( box_rect.left - rim_width, box_rect.top - rim_width, box_rect.width + rim_width * 2, box_rect.height + rim_width * 2, ) pygame.draw.rect(screen, rim_color, rim_rect) pygame.draw.rect(screen, box_color, box_rect) def in_field(self, pos): """ verify if clicked pos is in playable grid area - returns True/False """ loc = self.coord_to_grid(pos) if loc[0] < 0 or loc[0] >= self.x or loc[1] < 0 or loc[1] >= GRID_SIZE[1]: # print("you missed the player_command grid") return False else: return True def grid_clicked(self, pos): """ tells what grid was clicked on and reports for testing purposes pos: the passed mouse coordinates variable passed through """ if self.in_field(pos): # print("click is in player_command field") # print("pos clicked:", pos) dummy = False
class Window(pygame.sprite.Sprite): def __init__(self, x, y, width, height): pygame.sprite.Sprite.__init__(self) self.image = Surface((width, height)).convert(32, pygame.RLEACCEL) self._windowskin = None self.rect = Rect(x, y, width, height) self.selected_rect = None self.widgets = [] @property def offset(self): return 16 @property def screen_pos(self): pos = self.rect.topleft return (pos[0] + self.offset, pos[1] + self.offset) @property def font(self): if not hasattr(self, '_font'): self._font = pygame.font.Font(None, 24) return self._font @font.setter def font(self, value): self._font = value def create_contents(self): w, h = self.rect.inflate(-2*self.offset, -2*self.offset).size self.contents = Surface((w, h)).convert_alpha() self.font_color = (255,255,255) def draw_text(self, x, y, text): bmp = self.font.render(text, 1, self.font_color) self.contents.blit(bmp, (x,y)) def draw_selected(self, destsurf): if self.selected_rect: rect = self.selected_rect.move(self.rect.topleft) selected_surf = destsurf.subsurface(rect) pygame.transform.scale(self._select_src, self.selected_rect.size, selected_surf) @property def windowskin(self): return self._windowskin @windowskin.setter def windowskin(self, value): self._windowskin = value self._background = self._windowskin.subsurface(0,0,64,64) self._cadre = self._windowskin.subsurface(64,0,64,64) self._select_src = self._windowskin.subsurface(64,64,32,32) w, h = self.rect.size pygame.transform.scale(self._background, (w, h), self.image) #draw the four corners on the window for src, dest in [[(0, 0), (0,0)], [(48, 0), (w-16, 0)], [(0, 48), (0, h-16)], [(48, 48), (w-16, h-16)]]: src_rect = Rect(src, (16, 16)) dest_rect = Rect(dest, (16, 16)) self.image.blit(self._cadre.subsurface(src_rect), dest) def draw(self, destsurf): destsurf.blit(self.image, self.rect) self.draw_selected(destsurf) destsurf.blit(self.contents, self.rect.move(self.offset, self.offset), self.contents_src) def add_widget(self, widget): self.widgets.append(widget) widget.parent = self widget.parent_offset = self.offset @property def contents_size(self): width, height = self.rect.inflate(-2*self.offset, -2*self.offset).size return width, height @property def contents_rect(self): "return the visible rect of contents" return self.rect.inflate(-2*self.offset, -2*self.offset) @property def contents_src(self): return self.contents.get_rect()
def apply_rect(self, rect: Rect) -> Rect: """ Transforms given rect relative to camera position. :param rect pygame.Rect: the rect to transform """ return rect.move((0, -self.state.topleft[1]))
def make_I(rect: Rect, bw: int) -> Sequence[Rect]: return [rect.move(i*bw, 0) for i in range(4)]
class Widget(object): # rect Rect bounds in parent's coordinates # parent Widget containing widget # subwidgets [Widget] contained widgets # focus_switch Widget subwidget to receive key events # fg_color color or None to inherit from parent # bg_color color to fill background, or None # visible boolean # border_width int width of border to draw around widget, or None # border_color color or None to use widget foreground color # tab_stop boolean stop on this widget when tabbing # anchor string of 'ltrb' font = FontProperty('font') fg_color = ThemeProperty('fg_color') bg_color = ThemeProperty('bg_color') bg_image = ThemeProperty('bg_image') scale_bg = ThemeProperty('scale_bg') border_width = ThemeProperty('border_width') border_color = ThemeProperty('border_color') sel_color = ThemeProperty('sel_color') margin = ThemeProperty('margin') menu_bar = overridable_property('menu_bar') is_gl_container = overridable_property('is_gl_container') tab_stop = False enter_response = None cancel_response = None anchor = 'ltwh' debug_resize = False _menubar = None _visible = True _is_gl_container = False redraw_every_event = True tooltip = None tooltipText = None doNotTranslate = False # 'name' is used to track widgets without parent name = 'Widget' # 'focusable' is used to know if a widget can have the focus. # Used to tell container widgets like Columns or Dialogs tofind the next focusable widget # in their children. focusable = True def __init__(self, rect=None, **kwds): if rect and not isinstance(rect, Rect): raise TypeError("Widget rect not a pygame.Rect") self._rect = Rect(rect or (0, 0, 100, 100)) # -# Translation live update preparation self.__lang = albow.translate.getLang() self.__update_translation = False self.shrink_wrapped = False # -# self.parent = None self.subwidgets = [] self.focus_switch = None self.is_modal = False self.set(**kwds) self.root = self.get_root() self.setup_spacings() def __repr__(self): return "{0} {1}, child of {2}".format(super(Widget, self).__repr__(), getattr(self, "text", "\b").encode('ascii', errors='backslashreplace'), self.parent) # -# Translation live update preparation @property def get_update_translation(self): return self.__update_translation def set_update_ui(self, v): if v: self.font = self.predict_font({}) for widget in self.subwidgets: widget.set_update_ui(v) if self.shrink_wrapped: self.shrink_wrap() if hasattr(self, 'calc_size'): self.calc_size() self.invalidate() self.__update_translation = v # -# def setup_spacings(self): def new_size(size): size = float(size * 1000) / float(100) size = int(size * resource.font_proportion / 1000) return size self.margin = new_size(self.margin) if hasattr(self, 'spacing'): self.spacing = new_size(self.spacing) def set(self, **kwds): for name, value in kwds.items(): if not hasattr(self, name): raise TypeError("Unexpected keyword argument '%s'" % name) setattr(self, name, value) def get_rect(self): return self._rect def set_rect(self, x): old_size = self._rect.size self._rect = Rect(x) self._resized(old_size) resizing_axes = {'h': 'lr', 'v': 'tb'} resizing_values = {'': [0], 'm': [1], 's': [0, 1]} def set_resizing(self, axis, value): chars = self.resizing_axes[axis] anchor = self.anchor for c in chars: anchor = anchor.replace(c, '') for i in self.resizing_values[value]: anchor += chars[i] self.anchor = anchor + value def _resized(self, xxx_todo_changeme): (old_width, old_height) = xxx_todo_changeme new_width, new_height = self._rect.size dw = new_width - old_width dh = new_height - old_height if dw or dh: self.resized(dw, dh) def resized(self, dw, dh): if self.debug_resize: print("Widget.resized:", self, "by", (dw, dh), "to", self.size) for widget in self.subwidgets: widget.parent_resized(dw, dh) def parent_resized(self, dw, dh): debug_resize = self.debug_resize or getattr(self.parent, 'debug_resize', False) if debug_resize: print("Widget.parent_resized:", self, "by", (dw, dh)) left, top, width, height = self._rect move = False resize = False anchor = self.anchor if dw: factors = [1, 1, 1] # left, width, right if 'r' in anchor: factors[2] = 0 if 'w' in anchor: factors[1] = 0 if 'l' in anchor: factors[0] = 0 if any(factors): resize = factors[1] move = factors[0] or factors[2] # print "lwr", factors left += factors[0] * dw / sum(factors) width += factors[1] * dw / sum(factors) # left = (left + width) + factors[2] * dw / sum(factors) - width if dh: factors = [1, 1, 1] # bottom, height, top if 't' in anchor: factors[2] = 0 if 'h' in anchor: factors[1] = 0 if 'b' in anchor: factors[0] = 0 if any(factors): resize = factors[1] move = factors[0] or factors[2] # print "bht", factors top += factors[2] * dh / sum(factors) height += factors[1] * dh / sum(factors) # top = (top + height) + factors[0] * dh / sum(factors) - height if resize: if debug_resize: print("Widget.parent_resized: changing rect to", (left, top, width, height)) self.rect = Rect((left, top, width, height)) elif move: if debug_resize: print("Widget.parent_resized: moving to", (left, top)) self._rect.topleft = (left, top) rect = property(get_rect, set_rect) left = rect_property('left') right = rect_property('right') top = rect_property('top') bottom = rect_property('bottom') width = rect_property('width') height = rect_property('height') size = rect_property('size') topleft = rect_property('topleft') topright = rect_property('topright') bottomleft = rect_property('bottomleft') bottomright = rect_property('bottomright') midleft = rect_property('midleft') midright = rect_property('midright') midtop = rect_property('midtop') midbottom = rect_property('midbottom') center = rect_property('center') centerx = rect_property('centerx') centery = rect_property('centery') def get_visible(self): return self._visible def set_visible(self, x): self._visible = x visible = overridable_property('visible') def add(self, arg, index=None): if arg: if isinstance(arg, Widget): if index is not None: arg.set_parent(self, index) else: arg.set_parent(self) else: for item in arg: self.add(item) def add_centered(self, widget): w, h = self.size widget.center = w // 2, h // 2 self.add(widget) def remove(self, widget): if widget in self.subwidgets: widget.set_parent(None) def set_parent(self, parent, index=None): if parent is not self.parent: if self.parent: self.parent._remove(self) self.parent = parent if parent: parent._add(self, index) def all_parents(self): widget = self parents = [] while widget.parent: parents.append(widget.parent) widget = widget.parent return parents def _add(self, widget, index=None): if index is not None: self.subwidgets.insert(index, widget) else: self.subwidgets.append(widget) if hasattr(widget, "idleevent"): # print "Adding idle handler for ", widget self.root.add_idle_handler(widget) def _remove(self, widget): if hasattr(widget, "idleevent"): # print "Removing idle handler for ", widget self.root.remove_idle_handler(widget) self.subwidgets.remove(widget) if self.focus_switch is widget: self.focus_switch = None def draw_all(self, surface): if self.visible: surf_rect = surface.get_rect() bg_image = self.bg_image if bg_image: assert isinstance(bg_image, Surface) if self.scale_bg: bg_width, bg_height = bg_image.get_size() width, height = self.size if width > bg_width or height > bg_height: hscale = width / bg_width vscale = height / bg_height bg_image = rotozoom(bg_image, 0.0, max(hscale, vscale)) r = bg_image.get_rect() r.center = surf_rect.center surface.blit(bg_image, r) else: bg = self.bg_color if bg: surface.fill(bg) self.draw(surface) bw = self.border_width if bw: if self.has_focus() and hasattr(self, "highlight_color"): bc = self.highlight_color else: bc = self.border_color or self.fg_color frame_rect(surface, bc, surf_rect, bw) for widget in self.subwidgets: sub_rect = widget.rect if debug_rect: print("Widget: Drawing subwidget %s of %s with rect %s" % ( widget, self, sub_rect)) sub_rect = surf_rect.clip(sub_rect) if sub_rect.width > 0 and sub_rect.height > 0: try: sub = surface.subsurface(sub_rect) except ValueError as e: if str(e) == "subsurface rectangle outside surface area": self.diagnose_subsurface_problem(surface, widget) else: raise else: widget.draw_all(sub) self.draw_over(surface) def diagnose_subsurface_problem(self, surface, widget): mess = "Widget %s %s outside parent surface %s %s" % ( widget, widget.rect, self, surface.get_rect()) sys.stderr.write("%s\n" % mess) surface.fill((255, 0, 0), widget.rect) def draw(self, surface): pass def draw_over(self, surface): pass def find_widget(self, pos): for widget in self.subwidgets[::-1]: if widget.visible: r = widget.rect if r.collidepoint(pos): return widget.find_widget(subtract(pos, r.topleft)) return self def handle_mouse(self, name, event): self.augment_mouse_event(event) self.call_handler(name, event) self.setup_cursor(event) def mouse_down(self, event): self.call_parent_handler("mouse_down", event) def mouse_up(self, event): self.call_parent_handler("mouse_up", event) def augment_mouse_event(self, event): event.dict['local'] = self.global_to_local(event.pos) def setup_cursor(self, event): global current_cursor cursor = self.get_cursor(event) or arrow_cursor if cursor is not current_cursor: set_cursor(*cursor) current_cursor = cursor def dispatch_key(self, name, event): if self.visible: if event.type == KEYDOWN: menubar = self._menubar if menubar and menubar.handle_command_key(event): return widget = self.focus_switch if widget: widget.dispatch_key(name, event) else: self.call_handler(name, event) else: self.call_parent_handler(name, event) def get_focus(self): widget = self while 1: focus = widget.focus_switch if not focus: break widget = focus return widget def notify_attention_loss(self): widget = self while 1: if widget.is_modal: break parent = widget.parent if not parent: break focus = parent.focus_switch if focus and focus is not widget: self.root.notMove = False focus.dispatch_attention_loss() widget = parent def dispatch_attention_loss(self): widget = self if hasattr(self, "_highlighted"): self._highlighted = False while widget: widget.attention_lost() widget = widget.focus_switch def attention_lost(self): pass def handle_command(self, name, *args): method = getattr(self, name, None) if method: return method(*args) else: parent = self.next_handler() if parent: return parent.handle_command(name, *args) def next_handler(self): if not self.is_modal: return self.parent def call_handler(self, name, *args): method = getattr(self, name, None) if method: return method(*args) else: return 'pass' def call_parent_handler(self, name, *args): parent = self.next_handler() if parent: parent.call_handler(name, *args) def global_to_local(self, p): return subtract(p, self.local_to_global_offset()) def local_to_global(self, p): return add(p, self.local_to_global_offset()) def local_to_global_offset(self): d = self.topleft parent = self.parent if parent: d = add(d, parent.local_to_global_offset()) return d def key_down(self, event): k = event.key # print "Widget.key_down:", k ### if k == K_RETURN or k == K_KP_ENTER: if self.enter_response is not None: self.dismiss(self.enter_response) return elif k == K_ESCAPE: self.root.fix_sticky_ctrl() if self.cancel_response is not None: self.dismiss(self.cancel_response) return elif k == K_TAB: self.tab_to_next() return self.call_parent_handler('key_down', event) def key_up(self, event): self.call_parent_handler('key_up', event) def is_inside(self, container): widget = self while widget: if widget is container: return True widget = widget.parent return False @property def is_hover(self): return self.root.hover_widget is self def present(self, centered=True): # print "Widget: presenting with rect", self.rect if self.root is None: self.root = self.get_root() if "ControlPanel" not in str(self): self.root.notMove = True if centered: self.center = self.root.center self.root.add(self) try: self.root.run_modal(self) self.dispatch_attention_loss() finally: self.root.remove(self) # print "Widget.present: returning", self.modal_result if "ControlPanel" not in str(self): self.root.notMove = False return self.modal_result def dismiss(self, value=True): self.root.notMove = False self.modal_result = value def get_root(self): return root_widget def get_top_widget(self): top = self while top.parent and not top.is_modal: top = top.parent return top def focus(self): parent = self.next_handler() if parent: parent.focus_on(self) if hasattr(self, "_highlighted"): self._highlighted = True def focus_on(self, subwidget): old_focus = self.focus_switch if old_focus is not subwidget: if old_focus: old_focus.dispatch_attention_loss() self.focus_switch = subwidget self.focus() def has_focus(self): return self.is_modal or (self.parent and self.parent.focused_on(self)) def focused_on(self, widget): return self.focus_switch is widget and self.has_focus() def focus_chain(self): result = [] widget = self while widget: result.append(widget) widget = widget.focus_switch return result def shrink_wrap(self): contents = self.subwidgets if contents: rects = [widget.rect for widget in contents] # rmax = Rect.unionall(rects) # broken in PyGame 1.7.1 rmax = rects.pop() for r in rects: rmax = rmax.union(r) self._rect.size = add(rmax.topleft, rmax.bottomright) # -# Translation live update preparation self.shrink_wrapped = True # -# def invalidate(self): if self.root: self.root.bonus_draw_time = False @staticmethod def get_cursor(event): return arrow_cursor def predict(self, kwds, name): try: return kwds[name] except KeyError: return theme.root.get(self.__class__, name) def predict_attr(self, kwds, name): try: return kwds[name] except KeyError: return getattr(self, name) def init_attr(self, kwds, name): try: return kwds.pop(name) except KeyError: return getattr(self, name) def predict_font(self, kwds, name='font'): return kwds.get(name) or theme.root.get_font(self.__class__, name) def get_margin_rect(self): r = Rect((0, 0), self.size) d = -2 * self.margin r.inflate_ip(d, d) return r def set_size_for_text(self, width, nlines=1): if width is not None: font = self.font d = 2 * self.margin if isinstance(width, str): width, height = font.size(width) width += d + 2 else: height = font.size("X")[1] self.size = (width, height * nlines + d) def tab_to_first(self): chain = self.get_tab_order() if chain: chain[0].focus() def _is_next_in_tab(self, top, delta=1): """Helper function to find if the current widget is ne 'next' on when 'tabbing'. :param top: Widget: The 'top' widget to find 'self' in tab order. :param delta: int: The index modifier. Shall be 1 or -1. Defaults to 1. Returns a tuple: (<'self' index in 'top.get_tab_order()' result>, <'top.subwidgets>) If 'self' is not found, the first tuple element is 0, and the second one a tuple of widgets. If self is found, the first elemnt is the index it was found, the second one one a tuple of widgets.""" idx = 0 chain = top.get_tab_order() if self in chain: idx = chain.index(self) + delta return idx, chain def tab_to_next(self): """Give focus to the next focusable widget.""" top = self.get_top_widget() # If SHIFT key is hold down, set the index modifier to -1 to iterate to the previuos widget # instead of the next one. delta = 1 if self.root.get_modifiers()["shift"]: delta = -1 idx, subwidgets = self._is_next_in_tab(top, delta) chain = top.get_tab_order() + subwidgets target = chain[idx % len(chain)] while not target.focusable: _, subwidgets = target._is_next_in_tab(target, delta) chain = chain + subwidgets idx = chain.index(target) + delta target = chain[idx % len(chain)] self.get_focus().dispatch_attention_loss() target.focus() def get_tab_order(self): result = [] self.collect_tab_order(result) return result def collect_tab_order(self, result): if self.visible: if self.tab_stop: result.append(self) for child in self.subwidgets: child.collect_tab_order(result) # def tab_to_first(self, start = None): # if debug_tab: # print "Enter Widget.tab_to_first:", self ### # print "...start =", start ### # if not self.visible: # if debug_tab: print "...invisible" ### # self.tab_to_next_in_parent(start) # elif self.tab_stop: # if debug_tab: print "...stopping here" ### # self.focus() # else: # if debug_tab: print "...tabbing to next" ### # self.tab_to_next(start or self) # if debug_tab: print "Exit Widget.tab_to_first:", self ### # # def tab_to_next(self, start = None): # if debug_tab: # print "Enter Widget.tab_to_next:", self ### # print "...start =", start ### # sub = self.subwidgets # if sub: # if debug_tab: print "...tabbing to first subwidget" ### # sub[0].tab_to_first(start or self) # else: # if debug_tab: print "...tabbing to next in parent" ### # self.tab_to_next_in_parent(start) # if debug_tab: print "Exit Widget.tab_to_next:", self ### # # def tab_to_next_in_parent(self, start): # if debug_tab: # print "Enter Widget.tab_to_next_in_parent:", self ### # print "...start =", start ### # parent = self.parent # if parent and not self.is_modal: # if debug_tab: print "...telling parent to tab to next" ### # parent.tab_to_next_after(self, start) # else: # if self is not start: # if debug_tab: print "...wrapping back to first" ### # self.tab_to_first(start) # if debug_tab: print "Exit Widget.tab_to_next_in_parent:", self ### # # def tab_to_next_after(self, last, start): # if debug_tab: # print "Enter Widget.tab_to_next_after:", self, last ### # print "...start =", start ### # sub = self.subwidgets # i = sub.index(last) + 1 # if debug_tab: print "...next index =", i, "of", len(sub) ### # if i < len(sub): # if debug_tab: print "...tabbing there" ### # sub[i].tab_to_first(start) # else: # if debug_tab: print "...tabbing to next in parent" ### # self.tab_to_next_in_parent(start) # if debug_tab: print "Exit Widget.tab_to_next_after:", self, last ### def inherited(self, attribute): value = getattr(self, attribute) if value is not None: return value else: parent = self.next_handler() if parent: return parent.inherited(attribute) def __contains__(self, event): r = Rect(self._rect) r.left = 0 r.top = 0 p = self.global_to_local(event.pos) return r.collidepoint(p) def get_mouse(self): return self.root.get_mouse_for(self) def get_menu_bar(self): return self._menubar def set_menu_bar(self, menubar): if menubar is not self._menubar: if self._menubar: self.remove(self._menubar) self._menubar = menubar if menubar: if menubar.width == 0: menubar.width = self.width menubar.anchor = 'lr' self.add(menubar) def get_is_gl_container(self): return self._is_gl_container def set_is_gl_container(self, x): self._is_gl_container = x def gl_draw_all(self, root, offset): if not self.visible: return # from OpenGL import GL, GLU rect = self.rect.move(offset) if self.is_gl_container: self.gl_draw_self(root, offset) suboffset = rect.topleft for subwidget in self.subwidgets: subwidget.gl_draw_all(root, suboffset) else: try: surface = Surface(self.size, SRCALPHA) except: # size error? return self.draw_all(surface) data = image.tostring(surface, 'RGBA', 1) w, h = root.size GL.glViewport(0, 0, w, h) GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() GLU.gluOrtho2D(0, w, 0, h) GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() GL.glRasterPos2i(max(rect.left, 0), max(h - rect.bottom, 0)) GL.glPushAttrib(GL.GL_COLOR_BUFFER_BIT) GL.glEnable(GL.GL_BLEND) GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA) GL.glDrawPixels(self.width, self.height, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, fromstring(data, dtype='uint8')) GL.glPopAttrib() GL.glFlush() def gl_draw_self(self, root, offset): pass
class Player( Entity ): def __init__( self, lives, score, bulletMan, maxSpeed, sprite, viewBox, bulletSprite, bulletBox ): # position vars self.SPAWN_X = 300 self.SPAWN_Y = 600 self.xpos = self.SPAWN_X self.ypos = self.SPAWN_Y Entity.__init__( self, 400 , 300, sprite, Rect( self.xpos - 10, self.ypos - 10, self.xpos + 54, self.ypos + 54 ) ) # movement vars self._maxSpeed = maxSpeed self._focusedSpeed = maxSpeed / 2.0 self._xSpeed = 0 self._ySpeed = 0 # Game vars self.lives = lives self.score = score self.invincible = False self._invincibilityTime = 5000 self._bulletMan = bulletMan self._FIRE_COOL_MAX = 10 self._fireCoolDown = 0 # sprite vars self._itemBox = Rect( self.xpos, self.ypos, self.xpos + 64, self.ypos + 64 ) self._viewBox = viewBox self._bulletSprite = bulletSprite self._bulletBox = bulletBox def act( self ): """ Handles movement and other actions for the player """ #TODO possibly think of a more elegant way to handle this focused = False up = False down = False left = False right = False focus = False fire = False bomb = False if pygame.key.get_focused(): keys = pygame.key.get_pressed() up = keys[K_UP] down = keys[K_DOWN] right = keys[K_RIGHT] left = keys[K_LEFT] focus = keys[K_LSHIFT] fire = keys[K_z] bomb = keys[K_x] # Get if focused here if focused == True: if up == True: self._ySpeed = -1 * self._focusedSpeed elif down == True: self._ySpeed = self._focusedSpeed else: self._ySpeed = 0 if left == True: self._xSpeed = -1 * self._focusedSpeed elif right == True: self._xSpeed = self._focusedSpeed else: self._xSpeed = 0 else: if up == True: self._ySpeed = -1 * self._maxSpeed elif down == True: self._ySpeed = self._maxSpeed else: self._ySpeed = 0 if left == True: self._xSpeed = -1 * self._maxSpeed elif right == True: self._xSpeed = self._maxSpeed else: self._xSpeed = 0 if self._viewBox.x < 0 and self._xSpeed < 0: self._xSpeed = 0 elif self._viewBox.x >= 800 - 64 and self._xSpeed > 0: self._xSpeed = 0 if self._viewBox.y < 0 and self._ySpeed < 0: self._ySpeed = 0 elif self._viewBox.y >= 600 - 64 and self._ySpeed > 0: self._ySpeed = 0 if fire and self._fireCoolDown <= 0: self._fire() self._fireCoolDown = self._FIRE_COOL_MAX elif self._fireCoolDown > 0: self._fireCoolDown -= 1 self._move() #TODO invincibility timer def _fire( self ): bullet = Bullet( self.xpos, self.ypos, -5, 1, self._bulletSprite, self._bulletSprite.get_rect()) bullet2 = Bullet( self.xpos, self.ypos, -5, 1, self._bulletSprite, self._bulletSprite.get_rect()) xpos = self._viewBox.x + 16 ypos = self._viewBox.y + 22 bullet._hitbox.move_ip(xpos, ypos) xpos = self._viewBox.x + 30 ypos = self._viewBox.y + 22 bullet2._hitbox.move_ip(xpos, ypos) self._bulletMan.addPlayerBullet( bullet ) self._bulletMan.addPlayerBullet( bullet2 ) def draw( self, screen ): screen.blit(self._sprite,(self._viewBox)) def destroy( self ): """ Moves the player off screen temporally, then respawns them at the starting position """ #TODO Draw explosion self.xpos = -99 self.ypos = -99 self.hitbox = self.hitbox.move( self._xSpeed, self._ySpeed ) self._itemBox = self._itemBox.move( self._xSpeed, self._ySpeed ) def collidesWith( self ): pass def spawn( self ): self.xpos = self.SPAWN_X self.ypos = self.SPAWN_Y self.invincible = True def _move( self ): """ Moves the player based off of the xSpeed and YSpeed """ self.xpos = self.xpos + self._xSpeed self.ypos = self.ypos + self._ySpeed self._viewBox = self._viewBox.move( self._xSpeed, self._ySpeed) self._hitbox = self._hitbox.move( self._xSpeed, self._ySpeed ) self._itemBox = self._itemBox.move( self._xSpeed, self._ySpeed )
class Character(object): """ The character class. An object of this class represents the playable character. Attributes: _state: The current state of the character. This might be STANDING, WALKING or JUMPING. _lives: The current number of lives left. _points: The current points of this character _is_falling: Determines if the character is falling at the moment. _dy: Vertical speed of the character. _direction: The direction the character is looking. This can be LEFT, RIGHT or NONE. _invincible: Determines if the character is invincible at the moment. _walk_animation: Contains the walking animation. _stand_animation: Contains the standing animation _jump_animation: Contains the jumping animation for the NONE direction. _jump_right_animation: Contains the jumping animation for the directions LEFT and RIGHT. _death_animation: Contains the death animation of the character. _collision_rect: The rectangle for collision detection. _top_rect: The rectangle for on the top of the character. _bottom_rect: The rectangle for on the bottom of the character. _left_rect: The rectangle for on the left of the character. _right_rect: The rectangle for on the right of the character. _collect_sound: The sound for collecting coins. """ # Values for the _state attribute. STANDING, WALKING, JUMPING = range(3) # Values for the _direction attribute. LEFT, RIGHT, NONE = range(3) # Horizontal speed. SPEED = 1 # If true more information is displayed. DEBUG = False # Maximal falling speed. MAX_FALLING = 2 # Vertical acceleration. V_FALLING = 0.1 # Width of the character. WIDTH = 27 # Height of the character. HEIGHT = 48 def __init__(self, pos=[0, 0], state=STANDING, lives=3, points=0, walk_animation=None, stand_animation=None, jump_animation=None, jump_right_animation=None, death_animation=None): """ Generates a new instance of this class. Generates a new instance of the character class and sets the field information. If the animation arguments are left empty, the standard animations will be used. Args: pos: The starting position of the character. state: The starting state of the character. lives: The starting number of lives of the character. points: The starting point count of the character. walk_animation: The walking animation of the character. stand_animation: The standing animation of the character. jump_animation: The jumping animation of the character for the direction NONE. jump_right_animation: The jumping animation of the character for the directions LEFT and RIGHT. death_animation: The death animation of the character. """ self._state = state self._lives = lives self._points = points self._is_falling = False self._dy = 0 self._direction = Character.NONE self._invincible = 0 if walk_animation is None or stand_animation is None or jump_animation is None or jump_right_animation is None: animation_manager = AnimationManager.MANAGER animation = animation_manager.get_animation("animations") if walk_animation is None: walk_animation = Animated(PictureManager.MANAGER, animation[0], animation[1]["HeroWalk"], True) if stand_animation is None: stand_animation = Animated(PictureManager.MANAGER, animation[0], animation[1]["HeroStand"]) if jump_animation is None: jump_animation = Animated(PictureManager.MANAGER, animation[0], animation[1]["HeroJump"]) if jump_right_animation is None: jump_right_animation = Animated(PictureManager.MANAGER, animation[0], animation[1]["HeroJumpRight"], True) if death_animation is None: death_animation = (PictureManager.MANAGER, animation[0], animation[1]["HeroDie"]) self._walk_animation = walk_animation self._stand_animation = stand_animation self._jump_animation = jump_animation self._jump_right_animation = jump_right_animation self._death_animation = death_animation self._pos = pos[:] self._collision_rect = Rect(pos[0], pos[1], Character.WIDTH, Character.HEIGHT) self._top_rect = Rect(pos[0], pos[1], Character.WIDTH, 1) self._bottom_rect = Rect(pos[0], pos[1] + Character.HEIGHT - 1, Character.WIDTH, 1) self._left_rect = Rect(pos[0], pos[1], 1, Character.HEIGHT - 1) self._right_rect = Rect(pos[0] + Character.WIDTH - 1, pos[1], 1, Character.HEIGHT - 1) self._collect_sound = SoundManager.MANAGER.get_sound("coin.wav") def draw(self, surface, tick, camera, size): """ The drawing method. This method draws the character on the given surface. Args: surface: The surface the character will be drawn on. tick: The current tick of the game. camera: The position of the camera. size: The size of the window. This argument is not used at the moment. """ if self._state == Character.STANDING: self._stand_animation.draw(surface, self._collision_rect.x - camera[0], self._collision_rect.y - camera[1], tick) elif self._state == Character.WALKING: self._walk_animation.draw(surface, self._collision_rect.x - camera[0], self._collision_rect.y - camera[1], tick, self._direction == Character.LEFT) elif self._state == Character.JUMPING: if self._direction == Character.NONE: self._jump_animation.draw(surface, self._collision_rect.x - camera[0], self._collision_rect.y - camera[1], tick) else: self._jump_right_animation.draw(surface, self._collision_rect.x - camera[0], self._collision_rect.y - camera[1], tick, self._direction == Character.LEFT) if Character.DEBUG: move = camera[:] move[0] *= -1 move[1] *= -1 pygame.draw.rect(surface, (255, 255, 255), self._collision_rect.move(move), 2) pygame.draw.rect(surface, (255, 0, 0), self._top_rect.move(move), 1) pygame.draw.rect(surface, (255, 0, 0), self._bottom_rect.move(move), 1) pygame.draw.rect(surface, (0, 0, 255), self._left_rect.move(move), 1) pygame.draw.rect(surface, (0, 0, 255), self._right_rect.move(move), 1) def tick(self, platforms, collectables): """ Method for handling game ticks. This method should be called every tick to calculate the character changes. Args: platforms: The platforms of the level. collectables: The collectables, like coins, of the level. """ self._direction = Character.NONE if self._invincible > 0: self._invincible -= 1 self._state = Character.STANDING if self._is_falling: if self._dy < Character.MAX_FALLING: self._dy += Character.V_FALLING self._state = Character.JUMPING else: self._dy = 0 self._pos[1] += self._dy self._adjust_rects() movedX = 0 movedY = 0 for platform in platforms: while platform.collides(self._collision_rect): dx = 0 dy = 0 if platform.collides(self._top_rect): dy += 1 if platform.collides(self._bottom_rect): dy -= 1 if platform.collides(self._left_rect): dx += 1 if platform.collides(self._right_rect): dx -= 1 if dx == 0 and dy == 0: # completely in platform print("a") return # TODO: dead if (dx != 0 and dx == -movedX) or (dy != 0 and dy == -movedY): # crushed return # TODO: dead self._pos[0] += dx self._pos[1] += dy self._adjust_rects() movedX = dx movedY = dy for collectable in collectables: if collectable.collides(self._collision_rect): self._points += collectable.get_value() collectables.remove(collectable) self._collect_sound.play() def move(self, dx, platforms): """ This method moves the character. This method moves the character depending of the direction and the platforms of the level. Args: dx: The direction of the movement. platforms: The platforms of the level. """ if dx < 0: self._direction = Character.LEFT elif dx > 0: self._direction = Character.RIGHT self._pos[0] += dx * Character.SPEED self._adjust_rects() if self._state is not Character.JUMPING: self._state = Character.WALKING def jump(self): """ This method lets the character jump. Calling this method, the vertical speed of the character is set to 5 in the up direction. This results in a jump. """ self._is_falling = True self._dy = -5 def get_points(self): """ Method to get the current points. This method returns the current point count of the character. Returns: The current point count of the character. """ return self._points def is_colliding(self, rect): """ Method for checking for collision. This method checks if the character collides with the given rectangle. Args: rect: The rectangle to check with. Returns: True if the character collides with the given rectangle. Else otherwise. """ return rect.colliderect(self._collision_rect) def get_x(self): """ Returns the x coordinate. This method returns the x coordinate of the character. Returns: The x coordinate. """ return self._collision_rect.x + 14 def get_y(self): """ Returns the y coordinate. This method returns the y coordinate of the character. Returns: The y coordinate. """ return self._collision_rect.y + 25 def get_lives(self): """ Returns the lives. This method returns the current live count of the character. Returns: The current lives. """ return self._lives def change_lives(self, dl): """ Changes the current lives. This method changes the current live count by the given amount. Args: dl: The delta to changes the live count by. """ self._lives += dl if dl < 0: self._invincible = 100 self.jump() def change_points(self, dp): """ Changes the current points. This method changes the current point count by the given amount. Args: dp: The delta to change the point count by. """ self._points += dp def is_invincible(self): """ Returns the invincible flag. This method returns if the character is invincible at the moment. Returns: True if the character is invincible at the moment. Else otherwise. """ return self._invincible def get_walking_line(self): """ Returns the walking line. This method returns the character's current walking line. Returns: The character's current walking line. """ return self._bottom_rect.move(0,1) def get_death_animation(self): """ Returns the death animation. This method returns the death animation on the current location of the character. Returns: The death animation of the character. """ return Animation(self._death_animation[0], self._death_animation[1], self._death_animation[2], (self._collision_rect.x - 11, self._collision_rect.y)) def _adjust_rects(self): self._collision_rect.x = self._pos[0] self._collision_rect.y = self._pos[1] self._top_rect.x = self._collision_rect.x self._top_rect.y = self._collision_rect.y self._bottom_rect.x = self._collision_rect.x self._bottom_rect.y = self._collision_rect.y + Character.HEIGHT - 1 self._left_rect.x = self._collision_rect.x self._left_rect.y = self._collision_rect.y self._right_rect.x = self._collision_rect.x + Character.WIDTH - 1 self._right_rect.y = self._collision_rect.y
class Person(sprite.Sprite): image_list_right = [image.load(script_dir + im) for im in []] image_list_left = [image.load(script_dir + im) for im in []] def __init__(self, x, y, speed=5, hp=None, name='', group=None): self.anim_count = 0 self.how_much_animations = len(self.image_list_right) self.image = self.image_list_right[0] self.rect = self.image_list_right[0].get_rect() width, height = self.rect.bottomright self.rect = Rect((x - width, y), (width, height)) self.is_dead = False self.is_go_right = True self.is_go_left = False self.is_fall = True self.is_jump = False self.can_jump = False self.name = name self.speed = speed self.hp = hp self.damage = 0 self.person_rect_list = [] self.time = 0 self.object_list = [] self.cook_count = 0 self.get_damage = False super().__init__(group) def __str__(self): return f'Персонаж - {self.name}. Координаты: {self.rect.x, self.rect.y}.' \ f' Здоровье {self.hp}. Урон {self.damage}.' def set_time(self): '''Запоминает время получения урона''' self.time = time.time() def check_time(self): '''Проверяет, прошло ли время неуязвимости после получения урона или нет.''' return time.time() - self.time > 0.4 def move(self, x=0, y=0): '''Общая функция перемещения персонажей. Возвращает True, если персонаж при перемещении вошел в какой-либо блок.''' self.change_animation() self.rect = self.rect.move(x, y) return self.in_other_rect(x, y) def give_object_list(self, object_list): '''Получает список объектов. ОБъекты всё, с чем на уровне взоимодействует персонаж.''' self.object_list = object_list def go_left(self): self.is_go_left = True self.is_go_right = False self.move(x=-self.speed) def go_right(self): self.is_go_left = False self.is_go_right = True self.move(x=self.speed) def enemy_move(self): '''Правила перемещения врагов, у всех разный метод.''' def change_hp(self, how_much): '''Меняет здоровье на значение. Значение не приводится к отрицательному или положительному числу.''' self.get_damage = True if how_much < 0: # Временная неуязвимость при уроне if self.check_time(): self.hp += how_much self.dead() self.set_time() else: self.hp += how_much def dead(self): if self.hp <= 0: self.is_dead = True def fall(self): '''Падение персонажа. ПЕрсонаж падает всегда когда не прыгает (если на блоке, то его возвращает на местin_other_rect''' if not self.is_jump or not self.can_jump: if not self.move(y=self.speed * 3): self.can_jump = False else: self.can_jump = True self.is_fall = False else: self.is_fall = False def change_animation(self): '''Меняет анимации при перемещении.''' self.anim_count += 1 if self.anim_count >= self.how_much_animations: self.anim_count = 0 if self.is_go_right: self.image = self.image_list_right[self.anim_count] elif self.is_go_left: self.image = self.image_list_left[self.anim_count] def in_other_rect(self, x=0, y=0): '''Если прерсонаж пападет в блок, выталкивает его, причем не всегда в первоначальное положение. Сторона выталкивания зависит от стороны движения персонажа. НЕ ВЫТАЛКИВАЕТ ПЕРСОНАЖА, ЕСЛИ в методе взоимодействия объекта с персонажам указано не выталкивать персоенажа.''' answer = [] for object in self.object_list: if object: # если не мертв(для персонажей), или активен (для блоков) возвращает True. if self.rect.colliderect( object.rect) and self.rect != object.rect: if not object.unique_properties( self ): # мы должны получит True если блок сам уникально меняет координаты персонажа if y < 0: _, rect_y = object.rect.bottomright self.rect = Rect( (self.rect.x, rect_y), (self.rect.width, self.rect.height)) answer.append('u') elif y > 0: self.can_jump = True _, rect_y = object.rect.topright self.rect = Rect( (self.rect.x, rect_y - self.rect.height), (self.rect.width, self.rect.height)) answer.append('d') elif x < 0 and object.type != 'enemy': rect_x, _ = object.rect.bottomright self.rect = Rect( (object.rect.x + self.rect.width, self.rect.y), (self.rect.width, self.rect.height)) answer.append('l') elif x > 0 and object.type != 'enemy': rect_x, _ = object.rect.topleft self.rect = Rect( (object.rect.x - self.rect.width, self.rect.y), (self.rect.width, self.rect.height)) answer.append('r') return answer def change_cook_count(self, how_much=1): self.cook_count += how_much def unique_properties(self, hero): '''Уникальное взоимодействие с персонажами. Некий абстрактный метод, который может быть реализован или не реализован в дочерних классах''' return True
class Monster(sprite.Sprite): def __init__(self, move_time, nodes): sprite.Sprite.__init__(self) self.nodes = nodes self.orig_nodes = nodes self.move_time = move_time self.spawn_time = time.time() self.image = Surface((40, 40)).convert() self.image_inside = Surface((38, 38)).convert() self.image_inside.fill((0, 255, 0)) self.image.blit(self.image_inside, (1, 1)) self.pos = (80, 40) self.real_pos = (80, 40) self.rect = Rect(self.pos, self.image.get_size()) self.speed = 2 self.speed_mod = 1 self.diag_speed = 2 self.target_pos = (880, 560) self.value = 1 self.cost = 0 self.health = 100 self.damage_mod = 1 self.counter = 0 self.cur_node = self.nodes[0] self.the_dir = (0, 0) self.can_move = False self.name = "Monster" self.description = "A basic monster with slow movement speed and moderate health." def update(self, window): if time.time() - self.spawn_time >= self.move_time: self.can_move = True # If it's hit the last block if len(self.nodes) < 1: self.kill() return self.value else: # Figuring direction if self.nodes[0].rect.x > self.cur_node.rect.x: self.the_dir = (1, 0) elif self.nodes[0].rect.x < self.cur_node.rect.x: self.the_dir = (-1, 0) elif self.nodes[0].rect.y > self.cur_node.rect.y: self.the_dir = (0, 1) elif self.nodes[0].rect.y < self.cur_node.rect.y: self.the_dir = (0, -1) # Check to see the most the monster can move for speed in range(0, self.speed + 1): t_dir = tuple( [x * speed * self.speed_mod for x in self.the_dir]) # Monster can only move this much if self.rect.move(t_dir) == self.nodes[0].rect: self.rect.move_ip(t_dir) self.real_pos = tuple( map(sum, zip(self.real_pos, t_dir))) self.cur_node = self.nodes.pop(0) break else: # The monster can move by self.speed a = tuple([ x * self.speed * self.speed_mod for x in self.the_dir ]) self.real_pos = tuple(map(sum, zip(self.real_pos, a))) self.pos = tuple(map(round, self.real_pos)) self.rect.x, self.rect.y = self.pos # Conditions for the monster to die die_conditions = [ self.rect.top >= window.get_height(), self.rect.left >= window.get_width(), self.rect.bottom <= 0 ] if any(die_conditions): self.kill() return self.value # Resetting the modifiers, they'll be changed if the monster is under an effect self.speed_mod = 1 self.damage_mod = 1 return 0 # Does damage to the monster and checks if it dies def damage(self, damage): self.health -= damage * self.damage_mod # Returns the amount of money to grant the player if the monster dies and also how much damage was done if self.health <= 0: self.kill() return self.value, damage * self.damage_mod else: return None, damage * self.damage_mod
def make_Lr(rect: Rect, bw: int) -> Sequence[Rect]: rects = [rect] rects.extend([rect.move(i*bw, bw) for i in range(3)]) return rects
class Body: def __init__(self): self.pos = Pt(0, 0) self.shape = Rect(0, 0, 30, 50) self.scheme = None self.name = None self.speed = 10 self.on_line = None @property def cpos(self): return self.pos def update(self, level, keys, is_player): if is_player: shift = Pt(0, 0) for key, change in { 'UP': (0, 10), 'DN': (0, -10), 'LT': (-10, 0), 'RT': (10, 0) }.iteritems(): if key in keys: shift += change self.pos = self.on_line.try_move(self.pos + shift) switch, dist = None, float('inf') if isinstance(self.on_line, HLine): if 'UP' in keys: switch, dist = self.on_line.up_closest(self.pos) if 'DN' in keys: switch, dist = self.on_line.down_closest(self.pos) if isinstance(self.on_line, VLine): if 'LT' in keys: switch, dist = self.on_line.left_closest(self.pos) if 'RT' in keys: switch, dist = self.on_line.right_closest(self.pos) if switch and dist <= self.speed: self.on_line = switch self.pos = switch.try_move(self.pos) else: # AI pass def draw(self, surface, camera, is_player): tpos = camera.transform(self.cpos) rect = self.shape.move(tpos.x, tpos.y) if is_player: color = pygame.Color('blue') else: color = pygame.Color('gray') pygame.draw.rect(surface, color, rect) if self.on_line: helper.marker(tpos, surface, 'o') else: helper.marker(tpos, surface, 'x')
def make_Z(rect: Rect, bw: int) -> Sequence[Rect]: return [rect, rect.move(bw, 0), rect.move(bw, bw), rect.move(2*bw, bw)]
def make_T(rect: Rect, bw: int) -> Sequence[Rect]: return [rect.move(-bw, 0), rect, rect.move(bw, 0), rect.move(0, bw)]
class PlayerModel(BaseModel): """Player model. Most of the game physics is implemented here.""" # Physics air_friction = 0.5, 0.5 # s-1 gravity = 0, 981 # pixel/s-2 load_speed = 600 # pixel/s-2 init_speed = 250 # pixel/s max_loading_speed = 1000 # pixel/s # Animation period = 2.0 # s pre_jump = 0.25 # s load_factor_min = 5 # period-1 load_factor_max = 10 # period-1 blinking_period = 0.2 # s # Hitbox hitbox_ratio = 0.33 # Direction to Rect attributes for wall collision collide_dct = {Dir.DOWN: "bottom", Dir.LEFT: "left", Dir.RIGHT: "right", Dir.UP: "top"} # Resource to get the player size ref = "player_1" def init(self, pid): """Initialize the player.""" # Attributes self.id = pid self.border = self.parent.border self.resource = self.control.resource # Player rectangle self.size = self.resource.image.get(self.ref)[0].get_size() self.rect = Rect((0, 0), self.size) if pid == 1: self.rect.bottomleft = self.border.rect.bottomleft else: self.rect.bottomright = self.border.rect.bottomright # Player state self.speed = self.remainder = xytuple(0.0, 0.0) self.control_dir = Dir.NONE self.save_dir = Dir.NONE self.pos = Dir.DOWN self.fixed = True self.ko = False self.steps = [self.rect] # Animation timer self.timer = Timer(self, stop=self.period, periodic=True).start() # Loading timer self.loading_timer = Timer(self, start=self.init_speed, stop=self.max_loading_speed) # Delay timer self.delay_timer = Timer(self, stop=self.pre_jump, callback=self.delay_callback) # Dying timer self.blinking_timer = Timer(self.parent.parent, stop=self.blinking_period, periodic=True) # Debug if self.control.settings.debug_mode: RectModel(self, "head", Color("red")) RectModel(self, "body", Color("green")) RectModel(self, "legs", Color("blue")) def register_dir(self, direction): """Register a new direction from the controller.""" if any(direction): self.save_dir = direction self.control_dir = direction @property def delta_tuple(self): """Delta time as an xytuple.""" return xytuple(self.delta, self.delta) @property def colliding(self): """True when colliding with the other player, False otherwise.""" return self.parent.colliding @property def loading(self): """True when the player is loading a jump, False otherwise.""" return self.loading_timer.is_set or not self.loading_timer.is_paused @property def prepared(self): """True when the player is prepared a jump, False otherwise.""" return self.loading or not self.delay_timer.is_paused @property def loading_speed(self): """The current loading speed value.""" return self.loading_timer.get() def set_ko(self): """Knock the player out.""" self.ko = True self.fixed = False def load(self): """Register a load action.""" if self.colliding: return self.delay_timer.reset().start() self.timer.set(self.period*0.9) def delay_callback(self, timer): """Start loading the jump.""" self.loading_timer.reset().start(self.load_speed) self.timer.reset().start() def jump(self): """Make the player jump.""" if self.colliding: return # Check conditions if self.fixed and not self.ko: dir_coef = self.current_dir if any(dir_coef): dir_coef /= (abs(dir_coef),)*2 # Update speed self.speed += dir_coef * ((self.loading_speed,)*2) # Update status if self.pos != Dir.DOWN or any(dir_coef): self.save_dir = Dir.NONE self.pos = Dir.NONE self.fixed = False # Reset loading self.delay_timer.reset() self.loading_timer.reset() # Reset animation self.timer.reset().start() def update_collision(self): """Handle wall collisions.""" collide_dct = dict(self.collide_dct.items()) # Loop over changes while not self.border.rect.contains(self.rect): self.fixed = True self.save_dir = self.control_dir dct = {} # Test against the 4 directions. for direc, attr in collide_dct.items(): rect = self.rect.copy() value = getattr(self.border.rect, attr) setattr(rect, attr, value) distance = abs(xytuple(*rect.topleft) - self.rect.topleft) dct[distance] = rect, direc, attr # Aply the smallest change self.rect, self.pos, _ = dct[min(dct)] del collide_dct[self.pos] # Do not grab the wall when KO if self.ko and self.pos != Dir.DOWN: self.fixed = False @property def loading_ratio(self): """Loading ratio between 0 and 1.""" res = float(self.loading_speed - self.init_speed) return res / (self.max_loading_speed - self.init_speed) @property def current_dir(self): """Current direction with x and y in (-1, 0, 1).""" # Static case if self.fixed: if not any(self.save_dir) or \ sum(self.save_dir*self.pos) > 0: return xytuple(0, 0) current_dir = self.save_dir - self.pos sign = lambda arg: cmp(arg, 0) return current_dir.map(sign) # Dynamic case return Dir.closest_dir(self.speed) def get_rect_from_dir(self, direction): """Compute a hitbox inside the player in a given direction.""" size = xytuple(*self.size) * ((self.hitbox_ratio,)*2) attr = Dir.DIR_TO_ATTR[direction] rect = Rect((0, 0), size) value = getattr(self.rect, attr) setattr(rect, attr, value) return rect @property def head(self): """Head hitbox. Currently not used.""" if self.ko: return Rect(0, 0, 0, 0) if self.fixed: return self.get_rect_from_dir(self.pos * (-1, -1)) return self.get_rect_from_dir(self.current_dir * (-1, -1)) @property def body(self): """Body hitbox. Currently not used.""" if self.ko: return Rect(0, 0, 0, 0) return self.get_rect_from_dir(Dir.NONE) @property def legs(self): """Legs hitbox.""" if self.ko or self.fixed: return Rect(0, 0, 0, 0) return self.get_rect_from_dir(self.current_dir) def update(self): """Update the player state.""" # Get acc acc = -self.speed * self.air_friction acc += self.gravity # Update speed self.speed += self.delta_tuple * acc if self.fixed: self.speed *= 0, 0 # Get step step = self.delta_tuple * self.speed step += self.remainder intstep = step.map(round) self.remainder = step - intstep # Register steps args = Rect(self.rect), self.rect.move(intstep) self.steps = list(Dir.generate_rects(*args)) # Update timer if self.loading: delta = self.load_factor_max - self.load_factor_min ratio = self.load_factor_min + self.loading_ratio * delta self.timer.start(ratio)
class Monster(sprite.Sprite): def __init__(self, move_time, nodes): sprite.Sprite.__init__(self) self.nodes = nodes self.orig_nodes = nodes self.move_time = move_time self.spawn_time = time.time() self.image = Surface((40, 40)).convert() self.image_inside = Surface((38, 38)).convert() self.image_inside.fill((0, 255, 0)) self.image.blit(self.image_inside, (1, 1)) self.pos = (80, 40) self.real_pos = (80, 40) self.rect = Rect(self.pos, self.image.get_size()) self.speed = 2 self.speed_mod = 1 self.diag_speed = 2 self.target_pos = (880, 560) self.value = 1 self.cost = 0 self.health = 100 self.damage_mod = 1 self.counter = 0 self.cur_node = self.nodes[0] self.the_dir = (0, 0) self.can_move = False self.name = "Monster" self.description = "A basic monster with slow movement speed and moderate health." def update(self, window): if time.time() - self.spawn_time >= self.move_time: self.can_move = True # If it's hit the last block if len(self.nodes) < 1: self.kill() return self.value else: # Figuring direction if self.nodes[0].rect.x > self.cur_node.rect.x: self.the_dir = (1, 0) elif self.nodes[0].rect.x < self.cur_node.rect.x: self.the_dir = (-1, 0) elif self.nodes[0].rect.y > self.cur_node.rect.y: self.the_dir = (0, 1) elif self.nodes[0].rect.y < self.cur_node.rect.y: self.the_dir = (0, -1) # Check to see the most the monster can move for speed in range(0, self.speed+1): t_dir = tuple([x * speed * self.speed_mod for x in self.the_dir]) # Monster can only move this much if self.rect.move(t_dir) == self.nodes[0].rect: self.rect.move_ip(t_dir) self.real_pos = tuple(map(sum, zip(self.real_pos, t_dir))) self.cur_node = self.nodes.pop(0) break else: # The monster can move by self.speed a = tuple([x * self.speed * self.speed_mod for x in self.the_dir]) self.real_pos = tuple(map(sum, zip(self.real_pos, a))) self.pos = tuple(map(round, self.real_pos)) self.rect.x, self.rect.y = self.pos # Conditions for the monster to die die_conditions = [self.rect.top >= window.get_height(), self.rect.left >= window.get_width(), self.rect.bottom <= 0] if any(die_conditions): self.kill() return self.value # Resetting the modifiers, they'll be changed if the monster is under an effect self.speed_mod = 1 self.damage_mod = 1 return 0 # Does damage to the monster and checks if it dies def damage(self, damage): self.health -= damage*self.damage_mod # Returns the amount of money to grant the player if the monster dies and also how much damage was done if self.health <= 0: self.kill() return self.value, damage*self.damage_mod else: return None, damage*self.damage_mod
class Animation(Sprite): __mElapsedTime = None __mLooping = None __mRunning = None __mIsDone = None __mCurrentFrame = None __mCurrentRow = None __mDrawRect = None def __init__(self, sprite, framesX, framesY, animationtime, size, looping = True, running = True): super(Animation, self).__init__(sprite) self.__mElapsedTime = 0.0 self.__mLooping = looping self.__mRunning = running self.__mIsDone = False self.__mCurrentFrame = 0 self.__mCurrentRow = 0 self.__mDrawRect = None Sprite.setSize(self, size) self.__mFramesX = framesX self.__mFramesY = framesY self.__mFrameHeight = Sprite.getHeight(self) / framesY self.__mFrameWidth = Sprite.getWidth(self) / framesX self.__mMaxTime = animationtime / self.__mFramesX self.__mDrawRect = Rect(self.__mCurrentFrame * self.__mFrameWidth, self.__mCurrentRow * self.__mFrameHeight, self.__mFrameWidth, self.__mFrameHeight) def draw(self, delta, position): if self.__mRunning: self.__mElapsedTime += delta if self.__mElapsedTime > self.__mMaxTime: self.__mCurrentFrame += 1 if self.__mCurrentFrame >= self.__mFramesX: self.__mCurrentFrame = 0 if not self.__mLooping: self.__mIsDone = True self.__mRunning = False self.__mElapsedTime = 0.0 Sprite.draw(self, position, self.__mDrawRect.move(self.__mCurrentFrame * self.__mFrameWidth, self.__mCurrentRow * self.__mFrameHeight)) def freeze(self, frameX, frameY = 0): self.__mRunning = False self.__mCurrentFrame = frameX self.__mCurrentRow = frameY def continueAnimation(self): self.__mRunning = True def setLooping(self, looping): self.__mLooping = looping def gotoRow(self, row): if row <= self.__mFramesY: self.__mCurrentRow = row def getRect(self): return Rect(self.__mCurrentFrame * self.__mFrameWidth, self.__mCurrentRow * self.__mFrameHeight, self.__mFrameWidth, self.__mFrameHeight) def getFrame(self): return self.__mCurrentFrame def getRow(self): return self.__mCurrentRow def isAnimationDone(self): return self.__mIsDone def reset(self): self.__mIsDone = False self.__mCurrentFrame = 0 self.__mCurrentRow = 0 self.__mRunning = True def isLooping(self): return self.__mLooping
def move_paddle(self, paddle: pygame.Rect, control): if control[0] == 1: paddle = paddle.move(0, -PADDLE_SPEED) elif control[1] == 1: paddle = paddle.move(0, PADDLE_SPEED) return paddle
class PlayerModel(BaseModel): """Player model. Most of the game physics is implemented here.""" # Physics air_friction = 0.5, 0.5 # s-1 gravity = 0, 981 # pixel/s-2 load_speed = 600 # pixel/s-2 init_speed = 250 # pixel/s max_loading_speed = 1000 # pixel/s # Animation period = 2.0 # s pre_jump = 0.25 # s load_factor_min = 5 # period-1 load_factor_max = 10 # period-1 blinking_period = 0.2 # s # Hitbox hitbox_ratio = 0.33 # Direction to Rect attributes for wall collision collide_dct = { Dir.DOWN: "bottom", Dir.LEFT: "left", Dir.RIGHT: "right", Dir.UP: "top", } # Resource to get the player size ref = "player_1" def init(self, pid): """Initialize the player.""" # Attributes self.id = pid self.border = self.parent.border self.resource = self.control.resource # Player rectangle self.size = self.resource.image.get(self.ref)[0].get_size() self.rect = Rect((0, 0), self.size) if pid == 1: self.rect.bottomleft = self.border.rect.bottomleft else: self.rect.bottomright = self.border.rect.bottomright # Player state self.speed = self.remainder = xytuple(0.0, 0.0) self.control_dir = Dir.NONE self.save_dir = Dir.NONE self.pos = Dir.DOWN self.fixed = True self.ko = False self.steps = [self.rect] # Animation timer self.timer = Timer(self, stop=self.period, periodic=True).start() # Loading timer self.loading_timer = Timer( self, start=self.init_speed, stop=self.max_loading_speed ) # Delay timer self.delay_timer = Timer(self, stop=self.pre_jump, callback=self.delay_callback) # Dying timer self.blinking_timer = Timer( self.parent.parent, stop=self.blinking_period, periodic=True ) # Debug if self.control.settings.debug_mode: RectModel(self, "head", Color("red")) RectModel(self, "body", Color("green")) RectModel(self, "legs", Color("blue")) def register_dir(self, direction): """Register a new direction from the controller.""" if any(direction): self.save_dir = direction self.control_dir = direction @property def delta_tuple(self): """Delta time as an xytuple.""" return xytuple(self.delta, self.delta) @property def colliding(self): """True when colliding with the other player, False otherwise.""" return self.parent.colliding @property def loading(self): """True when the player is loading a jump, False otherwise.""" return self.loading_timer.is_set or not self.loading_timer.is_paused @property def prepared(self): """True when the player is prepared a jump, False otherwise.""" return self.loading or not self.delay_timer.is_paused @property def loading_speed(self): """The current loading speed value.""" return self.loading_timer.get() def set_ko(self): """Knock the player out.""" self.ko = True self.fixed = False def load(self): """Register a load action.""" if self.colliding: return self.delay_timer.reset().start() self.timer.set(self.period * 0.9) def delay_callback(self, timer): """Start loading the jump.""" self.loading_timer.reset().start(self.load_speed) self.timer.reset().start() def jump(self): """Make the player jump.""" if self.colliding: return # Check conditions if self.fixed and not self.ko: dir_coef = self.current_dir if any(dir_coef): dir_coef /= (abs(dir_coef),) * 2 # Update speed self.speed += dir_coef * ((self.loading_speed,) * 2) # Update status if self.pos != Dir.DOWN or any(dir_coef): self.save_dir = Dir.NONE self.pos = Dir.NONE self.fixed = False # Reset loading self.delay_timer.reset() self.loading_timer.reset() # Reset animation self.timer.reset().start() def update_collision(self): """Handle wall collisions.""" collide_dct = dict(list(self.collide_dct.items())) # Loop over changes while not self.border.rect.contains(self.rect): self.fixed = True self.save_dir = self.control_dir dct = {} # Test against the 4 directions. for direc, attr in list(collide_dct.items()): rect = self.rect.copy() value = getattr(self.border.rect, attr) setattr(rect, attr, value) distance = abs(xytuple(*rect.topleft) - self.rect.topleft) dct[distance] = rect, direc, attr # Aply the smallest change self.rect, self.pos, _ = dct[min(dct)] del collide_dct[self.pos] # Do not grab the wall when KO if self.ko and self.pos != Dir.DOWN: self.fixed = False @property def loading_ratio(self): """Loading ratio between 0 and 1.""" res = float(self.loading_speed - self.init_speed) return res / (self.max_loading_speed - self.init_speed) @property def current_dir(self): """Current direction with x and y in (-1, 0, 1).""" # Static case if self.fixed: if not any(self.save_dir) or sum(self.save_dir * self.pos) > 0: return xytuple(0, 0) current_dir = self.save_dir - self.pos sign = lambda arg: (arg > 0) - (arg < 0) return current_dir.map(sign) # Dynamic case return Dir.closest_dir(self.speed) def get_rect_from_dir(self, direction): """Compute a hitbox inside the player in a given direction.""" size = xytuple(*self.size) * ((self.hitbox_ratio,) * 2) attr = Dir.DIR_TO_ATTR[direction] rect = Rect((0, 0), size) value = getattr(self.rect, attr) setattr(rect, attr, value) return rect @property def head(self): """Head hitbox. Currently not used.""" if self.ko: return Rect(0, 0, 0, 0) if self.fixed: return self.get_rect_from_dir(self.pos * (-1, -1)) return self.get_rect_from_dir(self.current_dir * (-1, -1)) @property def body(self): """Body hitbox. Currently not used.""" if self.ko: return Rect(0, 0, 0, 0) return self.get_rect_from_dir(Dir.NONE) @property def legs(self): """Legs hitbox.""" if self.ko or self.fixed: return Rect(0, 0, 0, 0) return self.get_rect_from_dir(self.current_dir) def update(self): """Update the player state.""" # Get acc acc = -self.speed * self.air_friction acc += self.gravity # Update speed self.speed += self.delta_tuple * acc if self.fixed: self.speed *= 0, 0 # Get step step = self.delta_tuple * self.speed step += self.remainder intstep = step.map(round) self.remainder = step - intstep # Register steps args = Rect(self.rect), self.rect.move(intstep) self.steps = list(Dir.generate_rects(*args)) # Update timer if self.loading: delta = self.load_factor_max - self.load_factor_min ratio = self.load_factor_min + self.loading_ratio * delta self.timer.start(ratio)