def test_collidedict__zero_sized_rects(self): """Ensures collidedict works correctly with zero sized rects. There should be no collisions with zero sized rects. """ zero_rect1 = Rect(1, 1, 0, 0) zero_rect2 = Rect(1, 1, 1, 0) zero_rect3 = Rect(1, 1, 0, 1) zero_rect4 = Rect(1, 1, -1, 0) zero_rect5 = Rect(1, 1, 0, -1) no_collide_item1 = ('no collide 1', zero_rect1.copy()) no_collide_item2 = ('no collide 2', zero_rect2.copy()) no_collide_item3 = ('no collide 3', zero_rect3.copy()) no_collide_item4 = ('no collide 4', zero_rect4.copy()) no_collide_item5 = ('no collide 5', zero_rect5.copy()) no_collide_item6 = ('no collide 6', Rect(0, 0, 10, 10)) no_collide_item7 = ('no collide 7', Rect(0, 0, 2, 2)) # Dict to check collisions with values. rect_values = dict((no_collide_item1, no_collide_item2, no_collide_item3, no_collide_item4, no_collide_item5, no_collide_item6, no_collide_item7)) # Dict to check collisions with keys. rect_keys = {tuple(v) : k for k, v in rect_values.items()} for use_values in (True, False): d = rect_values if use_values else rect_keys for zero_rect in (zero_rect1, zero_rect2, zero_rect3, zero_rect4, zero_rect5): collide_item = zero_rect.collidedict(d, use_values) self.assertIsNone(collide_item)
def test_collidedict(self): """Ensures collidedict detects collisions.""" rect = Rect(1, 1, 10, 10) collide_item1 = ('collide 1', rect.copy()) collide_item2 = ('collide 2', Rect(5, 5, 10, 10)) no_collide_item1 = ('no collide 1', Rect(60, 60, 10, 10)) no_collide_item2 = ('no collide 2', Rect(70, 70, 10, 10)) # Dict to check collisions with values. rect_values = dict((collide_item1, collide_item2, no_collide_item1, no_collide_item2)) value_collide_items = (collide_item1, collide_item2) # Dict to check collisions with keys. rect_keys = {tuple(v) : k for k, v in rect_values.items()} key_collide_items = tuple( (tuple(v), k) for k, v in value_collide_items) for use_values in (True, False): if use_values: expected_items = value_collide_items d = rect_values else: expected_items = key_collide_items d = rect_keys collide_item = rect.collidedict(d, use_values) # The detected collision could be any of the possible items. self.assertIn(collide_item, expected_items)
def __init__(self, rect: pygame.Rect, manager: 'ui_manager.UIManager', element_ids: List[str], object_ids: Union[List[Union[str, None]], None] = None): new_element_ids = element_ids.copy() if object_ids is not None: new_object_ids = object_ids.copy() else: new_object_ids = [None] self.window_container = None # need to create the container that holds the elements first if this is the root window # so we can bootstrap everything and effectively add the root window to it's own container. # It's a little bit weird. if len(element_ids) == 1 and element_ids[0] == 'root_window': self._layer = 0 self.window_container = UIContainer(rect.copy(), manager, None, None, None) self.window_stack = manager.get_window_stack() self.window_stack.add_new_window(self) super().__init__(rect, manager, container=None, starting_height=1, layer_thickness=1, object_ids=new_object_ids, element_ids=new_element_ids) if self.window_container is None: self.window_container = UIContainer(self.rect.copy(), manager, None, self, None) self.window_stack = self.ui_manager.get_window_stack() self.window_stack.add_new_window(self) self.image = self.image = pygame.Surface((0, 0))
def test_collidedict__invalid_dict_key_format(self): """Ensures collidedict correctly handles dicts with invalid keys.""" rect = Rect(0, 0, 10, 10) rect_values = {'collide' : rect.copy()} with self.assertRaises(TypeError): collide_item = rect.collidedict(rect_values)
class ExplosionAnimation(Sprite): image = pygame.image.load("images/explosion.png") def __init__(self, screen, position): super().__init__() self.screen = screen self.position = position self.full_rect = self.__class__.image.get_rect() self.width = self.full_rect.width/3 self.height = self.full_rect.height/3 self.rect = Rect(0, 0, self.width, self.height) self.rect.center = self.position self.frame_index = 0 self.clock = Clock() self.time = 0 def update(self): self.time += self.clock.tick() if self.time > Settings.explosion_duration/9: self.frame_index += 1 self.time = 0 def draw(self): subrect = self.rect.copy() subrect.left = self.full_rect.left + (self.frame_index % 3) * self.width subrect.top = self.full_rect.top + (self.frame_index // 3) * self.height self.screen.blit(self.__class__.image, self.rect, subrect)
def draw_on(self, screen: pg.Surface, rect: pg.Rect) -> None: """Рисует все свои спрайты на себе, затем блитает на screen часть себя. Фокусируется на игроке, но не выходя за границы. (У краёв игрок будет не в центре)""" self.fill(pg.Color("black")) # Рисуем спрайты в определёной последовательности self.tiles_group.draw(self) self.objects_group.draw(self) self.enemies_group.draw(self) self.player_group.draw(self) self.participles_group.draw(self) # вычисляем координаты прямоугольника, который будем рисовать target = self.player.rect # фокус на игроке self.visible_area = rect.copy() self.visible_area.x = max( 0, min(self.width - rect.width, target.x + target.width // 2 - rect.width // 2)) self.visible_area.y = max( 0, min(self.height - rect.height, target.y + target.height // 2 - rect.height // 2)) screen.blit(self, rect, self.visible_area) screen.blit(self.player.draw_inventory(), rect)
def delta(self, rect: pg.Rect) -> pg.Rect: rect = rect.copy() rect.x = ceil(rect.x * self.dz + self.dx) rect.y = ceil(rect.y * self.dz + self.dy) rect.w = ceil(rect.w * self.dz) rect.h = ceil(rect.h * self.dz) return rect
class NewTextSprite(TryMovingSprite): def __init__(self, font_render, text, size, bg_color, layers, x=0, y=0, bg_transparent=True): """ Background is transparent by defualt. """ # Render the image self.font = font_render self._text = text self.size = size self.background = bg_color self.bg_transparent = bg_transparent self.layers = layers img = self._render(text) TryMovingSprite.__init__(self, img, x, y, img.get_rect()) @property def text(self): return self._text @text.setter def text(self, value): self._text = value self.image = self._render(value) self.rect = Rect((self.rect[0], self.rect[1]), self.image.get_rect()[2:]) self.col_rect = self.rect.copy() self.mask = mask.from_surface(self.image) self.create_feet() def _render(self, text): """ Renders text using stored options """ img = self.font.render(self.text, self.size, self.background, self.bg_transparent, self.layers) return img
def test_collidedictall(self): """Ensures collidedictall detects collisions.""" rect = Rect(1, 1, 10, 10) collide_item1 = ('collide 1', rect.copy()) collide_item2 = ('collide 2', Rect(5, 5, 10, 10)) no_collide_item1 = ('no collide 1', Rect(60, 60, 20, 20)) no_collide_item2 = ('no collide 2', Rect(70, 70, 20, 20)) # Dict to check collisions with values. rect_values = dict((collide_item1, collide_item2, no_collide_item1, no_collide_item2)) value_collide_items = [collide_item1, collide_item2] # Dict to check collisions with keys. rect_keys = {tuple(v) : k for k, v in rect_values.items()} key_collide_items = [(tuple(v), k) for k, v in value_collide_items] for use_values in (True, False): if use_values: expected_items = value_collide_items d = rect_values else: expected_items = key_collide_items d = rect_keys collide_items = rect.collidedictall(d, use_values) self._assertCountEqual(collide_items, expected_items)
class TextSprite(TryMovingSprite): def __init__(self, font_render, text, color, background=(0,0,0), antialias = False, x = 0, y = 0, bg_transparent = True ): """ Background is transparent by defualt. """ # Render the image self.font = font_render self._text = text self.antialias = antialias self.background = background self.color = color self.bg_transparent = bg_transparent img = self.render(text) if bg_transparent: img.set_colorkey(background) TryMovingSprite.__init__(self, img, x, y, img.get_rect()) @property def text(self): return self._text @text.setter def text(self, value): self._text = value self.image = self.render(value) self.rect = Rect((self.rect[0], self.rect[1]), self.image.get_rect()[2:]) self.col_rect = self.rect.copy() self.mask = mask.from_surface(self.image) self.create_feet() def render(self, text): """ Renders the text using the options first used. """ img = self.font.render(text, self.antialias, self.color, self.background) return img
def test_collidedictall__negative_sized_rects(self): """Ensures collidedictall works correctly with negative sized rects.""" neg_rect = Rect(2, 2, -2, -2) collide_item1 = ('collide 1', neg_rect.copy()) collide_item2 = ('collide 2', Rect(0, 0, 20, 20)) no_collide_item1 = ('no collide 1', Rect(1, 1, 20, 20)) # Dict to check collisions with values. rect_values = dict((collide_item1, collide_item2, no_collide_item1)) value_collide_items = [collide_item1, collide_item2] # Dict to check collisions with keys. rect_keys = {tuple(v) : k for k, v in rect_values.items()} key_collide_items = [(tuple(v), k) for k, v in value_collide_items] for use_values in (True, False): if use_values: expected_items = value_collide_items d = rect_values else: expected_items = key_collide_items d = rect_keys collide_items = neg_rect.collidedictall(d, use_values) self._assertCountEqual(collide_items, expected_items)
def build(self): # calculate the max dimensions max_w, max_h = 0, 0 for item in self.items: width, height = self.font.size(item.text) max_w = max(width, max_w) max_h = max(height, max_h) rect = Rect(0, 0, max_w, max_h).inflate(self.padding, self.padding) # place and initialize each menu item bounds = Rect(0, 0, 0, 0) top = 0 for item in self.items: item.image = Surface(rect.size, SRCALPHA) item.rect = rect.copy() item.rect.top = top top = item.rect.bottom + self.margin bounds.union_ip(item.rect) # tmp, render each sprite initially for item in self.items: item.draw_normal()
def test_collidedict__negative_sized_rects(self): """Ensures collidedict works correctly with negative sized rects.""" neg_rect = Rect(1, 1, -1, -1) collide_item1 = ('collide 1', neg_rect.copy()) collide_item2 = ('collide 2', Rect(0, 0, 10, 10)) no_collide_item1 = ('no collide 1', Rect(1, 1, 10, 10)) # Dict to check collisions with values. rect_values = dict((collide_item1, collide_item2, no_collide_item1)) value_collide_items = (collide_item1, collide_item2) # Dict to check collisions with keys. rect_keys = {tuple(v) : k for k, v in rect_values.items()} key_collide_items = tuple( (tuple(v), k) for k, v in value_collide_items) for use_values in (True, False): if use_values: collide_items = value_collide_items d = rect_values else: collide_items = key_collide_items d = rect_keys collide_item = neg_rect.collidedict(d, use_values) # The detected collision could be any of the possible items. self.assertIn(collide_item, collide_items)
def __init__(self, rect: Rect, **kwargs): self._rect = rect.copy() self._pos = Vector2(self._rect.centerx, self._rect.centery) self._alive = True self._dir = kwargs.get('direction', Vector2( 1, 0)) # defaults to a rightward movement vector self._speed = kwargs.get('speed', 250)
def build(self): # calculate the max dimensions max_w, max_h = 0, 0 for item in self.items: width, height = self.font.size(item.text) max_w = max(width, max_w) max_h = max(height, max_h) rect = Rect(0,0,max_w,max_h).inflate(self.padding, self.padding) # place and initialize each menu item bounds = Rect(0, 0, 0, 0) top = 0 for item in self.items: item.image = Surface(rect.size, SRCALPHA) item.rect = rect.copy() item.rect.top = top top = item.rect.bottom + self.margin bounds.union_ip(item.rect) # tmp, render each sprite initially for item in self.items: item.draw_normal()
def stretched_circle(surface: Surface, rect: Rect, colour: Colour): rounded_rect(surface, rect, colour, min(rect.width, rect.height) * .5) r = rect.copy() r.width -= r.height r.x += r.height / 2 r.height += 2 r.y -= 1 draw_rect(surface, colour, r)
class Slider(Object): def __init__(self, topleft, size, color): super().__init__([ "mouse_down", "mouse_up", "mouse_motion", "value_changed", "value_stabilized" ]) self.connect("mouse_down", lambda event: self.clicked_handler(event)) self.connect("mouse_motion", lambda event: self.motion_handler(event)) self.connect("mouse_up", lambda _: self.mouse_up_handler()) self.volume = 0 self.color = color self.border_rect = Rect(topleft, size) self.moving = False self.ppv = self.border_rect.width // 100 def set_moving(self, state): self.moving = state def motion_handler(self, event): if self.moving: self.change_volume_by_pos(event.pos) def mouse_up_handler(self): if self.moving: self.set_moving(False) self.signal("value_stabilized", self.volume) def change_volume_by_pos(self, pos): self.set_volume((pos[0] - self.border_rect.x) // self.ppv) def clicked_handler(self, event): position = event.pos if self.border_rect.collidepoint(position): self.moving = True self.change_volume_by_pos(position) def set_volume(self, volume): self.volume = min(volume if volume >= 0 else 0, 100) self.signal("value_changed", self.volume) def add_volume(self, volume): self.set_volume(self.volume + volume) def draw(self, screen): volume_rect = self.border_rect.copy() volume_rect.width = self.ppv * self.volume pygame.draw.rect( screen, self.color, volume_rect, ) pygame.draw.rect(screen, (0, 0, 0), self.border_rect, 1)
class ThrowedGrenade(Bullet): def __init__(self, creator, x, y, speedX, speedY, throwDistance): Bullet.__init__(self, creator, x, y, speedX, speedY, "items-1small.png", None, Rect(110, 120, 9, 11)) self.rect = Rect(x, y, 0, 0) self.speedX = speedX self.speedY = speedY # Important if you want pinpoint accuracy self.floatX = float(self.rect.x) self.floatY = float(self.rect.y) if throwDistance > MAX_THROW_DISTANCE: throwDistance = MAX_THROW_DISTANCE self.timeToStop = throwDistance / GRENADE_SPEED self.timeToBoom = TIME_TO_BOOM self.atk = 70 def update(self, time, scene): self.floatX += self.speedX * time * GRENADE_SPEED self.floatY += self.speedY * time * GRENADE_SPEED self.rect.x = roundToInt(self.floatX) self.rect.y = roundToInt(self.floatY) self.timeToBoom -= time if self.timeToBoom <= 0: scene.bulletGroup.remove(self) scene.bulletGroup.add(Explosion(self.rect.x, self.rect.y)) # Kill people! originalRect = self.rect.copy() self.rect.inflate_ip(80, 80) # 80x80 area damage enemies_hit = sprite.spritecollide(self, scene.enemyGroup, False) for enemy in enemies_hit: # Friendly fire if self.creator.__class__.__name__ == enemy.__class__.__name__: continue enemy.receive_attack(self.atk) if sprite.collide_rect(self, scene.player): scene.player.receive_attack(self.atk) self.rect = originalRect # Restore the real rect self.timeToStop -= time if self.timeToStop <= 0: self.speedX = 0 self.speedY = 0
def __init__(self, hit_rect: pg.Rect, pos: Vector2, max_health: int) -> None: hit_rect = hit_rect.copy() hit_rect.center = pos self.motion: Motion = Motion(self, self.timer, self.groups.walls, hit_rect) self.status = Status(max_health) super().__init__(pos) self._base_rect = self.image.get_rect().copy() self.inventory = Inventory()
def test_collidedict__invalid_dict_format(self): """Ensures collidedict correctly handles invalid dict parameters.""" rect = Rect(0, 0, 10, 10) invalid_value_dict = ('collide', rect.copy()) invalid_key_dict = tuple(invalid_value_dict[1]), invalid_value_dict[0] for use_values in (True, False): d = invalid_value_dict if use_values else invalid_key_dict with self.assertRaises(TypeError): collide_item = rect.collidedict(d, use_values)
class ThrowedGrenade(Bullet): def __init__(self,creator, x, y, speedX, speedY, throwDistance): Bullet.__init__(self, creator, x, y, speedX, speedY, "items-1small.png", None, Rect(110, 120, 9, 11)) self.rect = Rect(x, y, 0, 0) self.speedX = speedX self.speedY = speedY # Important if you want pinpoint accuracy self.floatX = float(self.rect.x) self.floatY = float(self.rect.y) if throwDistance > MAX_THROW_DISTANCE: throwDistance = MAX_THROW_DISTANCE self.timeToStop = throwDistance / GRENADE_SPEED self.timeToBoom = TIME_TO_BOOM self.atk = 70 def update(self, time, scene): self.floatX += self.speedX * time * GRENADE_SPEED self.floatY += self.speedY * time * GRENADE_SPEED self.rect.x = roundToInt(self.floatX) self.rect.y = roundToInt(self.floatY) self.timeToBoom -= time if self.timeToBoom <= 0: scene.bulletGroup.remove(self) scene.bulletGroup.add(Explosion(self.rect.x, self.rect.y)) # Kill people! originalRect = self.rect.copy() self.rect.inflate_ip(80, 80) # 80x80 area damage enemies_hit = sprite.spritecollide(self, scene.enemyGroup, False) for enemy in enemies_hit: # Friendly fire if self.creator.__class__.__name__ == enemy.__class__.__name__: continue enemy.receive_attack(self.atk) if sprite.collide_rect(self, scene.player): scene.player.receive_attack(self.atk) self.rect = originalRect # Restore the real rect self.timeToStop -= time if self.timeToStop <= 0: self.speedX = 0 self.speedY = 0
def bounce_in_box(bounce_obj_rect: Rect, bounce_obj_speed, box_rect: Rect): """ The alternative version of `bounce_in_box_ip`. The function returns the result instead of updating the value of `bounce_obj_rect` and `bounce_obj_speed`. @return A tuple (new_bounce_obj_rect, new_bounce_obj_speed) """ new_bounce_obj_rect = bounce_obj_rect.copy() new_bounce_obj_speed = bounce_obj_speed.copy() bounce_in_box_ip(new_bounce_obj_rect, new_bounce_obj_speed, box_rect) return (new_bounce_obj_rect, new_bounce_obj_speed)
def __init__(self, containing_rect: pygame.Rect, theming_parameters: Dict, states: List[str], manager: IUIManagerInterface): self.containing_rect = containing_rect.copy() if self.containing_rect.width < 1: self.containing_rect.width = 1 if self.containing_rect.height < 1: self.containing_rect.height = 1 self.theming = theming_parameters self.shadow_width = 0 self.border_width = 0 self.states = {} for state in states: self.states[state] = DrawableShapeState(state) if 'normal' in states: self.active_state = self.states['normal'] else: raise NotImplementedError( "No 'normal' state id supplied for drawable shape") self.previous_state = None if 'transitions' in self.theming: self.state_transition_times = self.theming['transitions'] else: self.state_transition_times = {} self.ui_manager = manager self.shape_cache = self.ui_manager.get_theme().shape_cache self.aligned_text_rect = None self.click_area_shape = None self.states_to_redraw_queue = deque([]) self.need_to_clean_up = True self.should_trigger_full_rebuild = True self.time_until_full_rebuild_after_changing_size = 0.35 self.full_rebuild_countdown = self.time_until_full_rebuild_after_changing_size self.click_area_shape = None self.border_rect = None self.background_rect = None self.aligned_text_rect = None self.base_surface = None
def bounce_off(bounce_obj_rect: Rect, bounce_obj_speed, hit_obj_rect: Rect, hit_obj_speed): """ The alternative version of `bounce_off_ip`. The function returns the result instead of updating the value of `bounce_obj_rect` and `bounce_obj_speed`. @return A tuple (`new_bounce_obj_rect`, `new_bounce_obj_speed`) """ new_bounce_obj_rect = bounce_obj_rect.copy() new_bounce_obj_speed = bounce_obj_speed.copy() bounce_off_ip(new_bounce_obj_rect, new_bounce_obj_speed, hit_obj_rect, hit_obj_speed) return new_bounce_obj_rect, new_bounce_obj_speed
def __init__(self, input_data_queue: Deque[TextLayoutRect], layout_rect: pygame.Rect, view_rect: pygame.Rect, line_spacing: float): # TODO: supply only a width and create final rect shape or just a final height? self.input_data_rect_queue = input_data_queue.copy() self.layout_rect = layout_rect.copy() self.line_spacing = line_spacing self.view_rect = view_rect self.expand_width = False if self.layout_rect.width == -1: self.layout_rect.width = 0 self.expand_width = True for rect in self.input_data_rect_queue: self.layout_rect.width += rect.width self.layout_rect_queue = None self.finalised_surface = None self.floating_rects: List[TextLayoutRect] = [] self.layout_rows: List[TextBoxLayoutRow] = [] self.row_lengths = [] self.link_chunks = [] self.letter_count = 0 self.current_end_pos = 0 self.alpha = 255 self.pre_alpha_final_surf = None # only need this if we apply non-255 alpha self.layout_rect_queue = self.input_data_rect_queue.copy() current_row = TextBoxLayoutRow(row_start_x=self.layout_rect.x, row_start_y=self.layout_rect.y, row_index=0, layout=self, line_spacing=self.line_spacing) self._process_layout_queue(self.layout_rect_queue, current_row) self.edit_buffer = 2 self.cursor_text_row = None self.selection_colour = pygame.Color(128, 128, 200, 255) self.selected_chunks = [] self.selected_rows = [] self.x_scroll_offset = 0 self.cursor_colour = pygame.Color('#FFFFFFFF')
class Sprite: """ A class to represent a sprite. Used for pygame displaying. Image generated with given color and size. """ # default constructor (must be called if overrided by inheritance) def __init__(self, x: int, y: int, w: int, h: int, color: tuple): self.__color = color self._image = Surface((w, h)) self._image.fill(self.color) self._image = self._image.convert() self.rect = Rect(x, y, w, h) self.camera_rect = self.rect.copy() # Public getters for _image & __color so they remain private @property def image(self) -> Surface: return self._image @property def color(self) -> tuple: return self.__color @color.setter def color(self, new: tuple) -> None: " Called when Sprite.__setattr__('color',x)." assert isinstance(new, tuple) and len(new) == 3, "Value is not a color" self.__color = new #update image surface self._image.fill(self.color) def draw(self, surface: Surface) -> None: """ Render method,Should be called every frame after update. :param surface pygame.Surface: the surface to draw on. """ # If camera instancied: calculate render positon if Camera.instance: self.camera_rect = Camera.instance.apply(self) surface.blit(self._image, self.camera_rect) else: surface.blit(self._image, self.rect)
def world_to_screen_rect(self, rect: pg.Rect, is_world=True, clip=True): rect = rect.copy() if is_world: rect.x -= self.position[0] rect.y -= self.position[1] rect.x *= self.scale rect.y *= self.scale rect.w *= self.scale rect.h *= self.scale if self.cam_alignment[0] == self.ALIGNMENT_RIGHT: rect.x += self.display_port.x elif self.cam_alignment[0] == self.ALIGNMENT_CENTER: rect.x += self.display_port.x + (self.display_port.w // 2) elif self.cam_alignment[0] == self.ALIGNMENT_LEFT: rect.x += self.display_port.x + self.display_port.w if self.cam_alignment[1] == self.ALIGNMENT_BOTTOM: rect.y += self.display_port.y elif self.cam_alignment[1] == self.ALIGNMENT_CENTER: rect.y += self.display_port.y + (self.display_port.h // 2) elif self.cam_alignment[1] == self.ALIGNMENT_TOP: rect.y += self.display_port.y + self.display_port.h if clip: if rect.x < self.display_port.x: difference = self.display_port.x - rect.x rect.x += difference rect.w -= difference if rect.x + rect.w > self.display_port.x + self.display_port.w: difference = (rect.x + rect.w) - (self.display_port.x + self.display_port.w) rect.w -= difference if rect.y < self.display_port.y: difference = self.display_port.y - rect.y rect.y += difference rect.h -= difference if rect.y + rect.h > self.display_port.y + self.display_port.h: difference = (rect.y + rect.h) - (self.display_port.y + self.display_port.h) rect.h -= difference rect.w = max(rect.w, 0) rect.h = max(rect.h, 0) return rect
def draw_in_world(self, camera=None): """ Отрисовка объекта в мире. :param camera: Камера, относительно которой нужно отрисовывать объект :return: """ # Внимание! Меняя что-то здесь, не забывай поменять данную функцию в RotatableWorldObject! if self.visible: if self.image is not None: surface_to_draw = self.image.get_current_and_next() rect_to_draw = Rect.copy(self.object_rect) # TODO: подумать, можно ли избежать здесь ненужного копирования if self.image.get_size() != self.object_rect.size: # Если размер изображения не совпадает с размером объекта surface_to_draw = pygame.transform.scale(self.image.get_current(), (self.object_rect.width, self.object_rect.height)) if camera is not None: rect_to_draw.x += camera.get_coords()[0] rect_to_draw.y += camera.get_coords()[1] self.parent_world.parent_surface.blit(surface_to_draw, rect_to_draw) if self.need_to_animate: self.image.next()
class Sprite(Component): def __init__(self, file, frames, columns, frame_rect, edge_buffer={ "left": 0, "right": 0, "top": 0, "bottom": 0 }): self.file = file self.image = pygame.image.load("resources/" + file) self.frames = frames self.columns = columns self.frame_rect = Rect(frame_rect["x"], frame_rect["y"], frame_rect["w"], frame_rect["h"]) self.edge_buffer = edge_buffer self.curr_frame_rect = self.frame_rect.copy()
def round_rect(surface, rect: pg.Rect, color, rad: int = 20, border: int = 0, inside=(0, 0, 0, 0)): """ Draw a rect with rounded corners to surface. Argument rad can be specified to adjust curvature of edges (given in pixels). An optional border width can also be supplied; if not provided the rect will be filled. Both the color and optional interior color (the inside argument) support alpha. """ rect = pg.Rect(rect) zeroed_rect = rect.copy() zeroed_rect.topleft = 0, 0 image = pg.Surface(rect.size).convert_alpha() image.fill((0, 0, 0, 0)) _render_region(image, zeroed_rect, color, rad) if border: zeroed_rect.inflate_ip(-2 * border, -2 * border) _render_region(image, zeroed_rect, inside, rad) surface.blit(image, rect)
def test_copy(self): r = Rect(1, 2, 10, 20) c = r.copy() self.failUnlessEqual(c, r)
def test_copy(self): r = Rect(1, 2, 10, 20) c = r.copy() self.assertEqual(c, r)
def __init__(self, relative_rect: pygame.Rect, manager: IUIManagerInterface, container: Union[IContainerLikeInterface, None], *, starting_height: int, layer_thickness: int, object_ids: Union[List[Union[str, None]], None] = None, element_ids: Union[List[str], None] = None, anchors: Dict[str, str] = None): self._layer = 0 self.ui_manager = manager super().__init__(self.ui_manager.get_sprite_group()) self.relative_rect = relative_rect.copy() self.rect = self.relative_rect.copy() self.ui_group = self.ui_manager.get_sprite_group() self.ui_theme = self.ui_manager.get_theme() self.object_ids = object_ids self.element_ids = element_ids self.anchors = anchors if self.anchors is None: self.anchors = { 'left': 'left', 'top': 'top', 'right': 'left', 'bottom': 'top' } self.drawable_shape = None # type: Union['DrawableShape', None] self.image = None self.relative_bottom_margin = None self.relative_right_margin = None self.layer_thickness = layer_thickness self.starting_height = starting_height self.is_enabled = True self.hovered = False self.is_focused = False self.hover_time = 0.0 self.pre_debug_image = None self._pre_clipped_image = None self._image_clip = None self._visual_debug_mode = False # Themed parameters self.shadow_width = None # type: Union[None, int] self.border_width = None # type: Union[None, int] self.shape_corner_radius = None # type: Union[None, int] combined_ids = self.ui_manager.get_theme().build_all_combined_ids( self.element_ids, self.object_ids) if combined_ids is not None and len(combined_ids) > 0: self.most_specific_combined_id = combined_ids[0] else: self.most_specific_combined_id = 'no_id' if container is None: if self.ui_manager.get_root_container() is not None: container = self.ui_manager.get_root_container() else: container = self if isinstance(container, IContainerLikeInterface): self.ui_container = container.get_container() if self.ui_container is not None and self.ui_container is not self: self.ui_container.add_element(self) self._update_absolute_rect_position_from_anchors() self._update_container_clip()
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 Object(object): def __init__(self, **kwargs): x, y, w, h = None, None, None, None if kwargs.get("rect", None): x, y, w, h = kwargs.pop("rect", None) elif kwargs.get("pos") or kwargs.get("size"): x, y = kwargs.pop("pos", (0, 0)) w, h = kwargs.pop("size", (0, 0)) else: x, y = kwargs.pop("x", 0), kwargs.pop("y", 0) w, h = kwargs.pop("w", 0), kwargs.pop("h", 0) super().__init__(**kwargs) assert None not in [x, y, w, h] self._rect = Rect(x, y, w, h) def asRect(self): return Rect(self.x, self.y, self.w, self.h) def copy(self): return self._rect.copy() def colliderect(self, collide): return self._rect.colliderect(collide.asRect()) @property def top(self): return self._rect.top @top.setter def top(self, value): self._rect.top = value @property def bottom(self): return self._rect.bottom @bottom.setter def bottom(self, value): self._rect.bottom = value @property def left(self): return self._rect.left @left.setter def left(self, value): self._rect.left = value @property def right(self): return self._rect.right @right.setter def right(self, value): self._rect.right = value @property def x(self): return self._rect.x @x.setter def x(self, value): self._rect.x = value @property def y(self): return self._rect.y @y.setter def y(self, value): self._rect.y = value @property def w(self): return self._rect.w @w.setter def w(self, value): self._rect.w = value @property def h(self): return self._rect.h @h.setter def h(self, value): self._rect.h = value def __repr__(self): return repr(self._rect)
class MultiLineTextSprite(TryMovingSprite): A_LEFT = 0 A_CENTER = 1 A_RIGHT = 2 def __init__(self, font_render, text, size, background, layers, x=0, y=0, align=1, line_spacer=0, bg_transparent=True ): """ Background is transparent by defualt. align: 0 - left 1 - center 2 - right """ # Render the image self.font = font_render self._text = text self.size = size self.background = background self.bg_transparent = bg_transparent self.align = align self.line_spacer = line_spacer self.layers = layers img = self.render(text) if bg_transparent: img.set_colorkey(background) TryMovingSprite.__init__(self, img, x, y, img.get_rect()) @property def text(self): return self._text @text.setter def text(self, value): self._text = value self.image = self.render(value) self.rect = Rect((self.rect[0], self.rect[1]), self.image.get_rect()[2:]) self.col_rect = self.rect.copy() self.mask = mask.from_surface(self.image) self.create_feet() def _calculate_image_size(self, font_render, text, size, line_spacer, layers): lines = text.splitlines() ls = line_spacer xs = 0 ys = 0 for l in lines: w, h = font_render.size(l, size, layers) xs = max(xs, w) ys += h + ls # Don't put a line spacer for one line text if len(lines) == 1: ys -= ls return xs, ys def render(self, text): """ Renders the text using the options first used. """ lines = text.splitlines() img = Surface(self._calculate_image_size(self.font, self.text, self.size, self.line_spacer, self.layers)) if self.bg_transparent: img.set_colorkey(self.background) full_rect = img.get_rect() y = 0 for l in lines: r = self.font.render(l, self.size, self.background, self.bg_transparent, self.layers) r_rect = r.get_rect() if self.bg_transparent: r.set_colorkey(self.background) if self.align == self.A_CENTER: x = self._center_rect_inside_rect(r_rect, full_rect) elif self.align == self.A_LEFT: x = 0 elif self.align == self.A_RIGHT: x = full_rect[3] - r_rect[3] img.blit(r, (x, y)) y += self.line_spacer + r_rect[3] return img def _center_rect_inside_rect(self, rect1, rect2, offset=0): x = (rect2[2] - rect1[2])/2 + offset return x
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 TrySprite(DirtySprite): """ A sprite class with all the needed things for try-engine. TrySprite subclasses pygame.sprite.DirtySprite. It adds some mandatory attributes as col_rect and adds a few methods to control the render order. It also has a host-guest sprite thingy that makes sure the host sprite is updated before the guests sprites. You can attach sprites to sprites and it will look nice. TrySprites adds the rects: col_rect, i_rect, feet_rect dirty_rect, col_rect_left, col_rect_right TODO: This is probably outdated! The dirty attribute does NOT mean the same as in pygame. The meaning is: 0 = The srpite has been drawn in this frame 1 = hasn't been drawn in this frame yet 2 = nothing at the moment """ def __init__(self, img, x, y, col_rect): DirtySprite.__init__(self) self.image = img self.rect = Rect((x,y),self.image.get_rect()[2:]) self.visible = 1 self.dirty = 1 # Directions in which this sprite collide self.col_direction = (True, True, True, True) # Has this sprite sprites attached to him? or the opposite # If this is used the guest sprite will be always updated # after the host sprite. self.guest = False self.guests = Group() self.host = None # Create all the needed rects: self.init_rects(self.rect, col_rect) # Init mask self.init_masks() # if True will print lots of debugging stuff self.debugging = False def init_rects(self, rect, col_rect): """ Create all the rects needed for the sprite Creates: collision rect, feet rect, interpolation rect, dirty rect and collision mask. """ # List holding all the rects, allow for fast iteration # over all the rects self.rect_list = rl = [] rl.append(self.rect) # Rect used for collisions self.col_rect = col_rect.copy().move(rect[:2]) rl.append(self.col_rect) # Used to know where was the last interpolated position # TODO: do we use this???? self.i_rect = rect.copy() rl.append(self.i_rect) # Rect to clear this sprite when drawing self.dirty_rect = rect.copy() rl.append(self.i_rect) # Create a rect representing the feet self.feet_rect = self.create_feet() rl.append(self.feet_rect) # Create left and right collision rects self.col_rect_left, self.col_rect_right = self.create_left_right() rl.append(self.col_rect_left) rl.append(self.col_rect_right) def create_feet(self): """ Creates a pygame Rect that will represent the feet. """ cr = self.col_rect # One pixel height rect at the feet, overlapping the col_rect # PLEASE NOTE: Any additional rect used for collisions needs to # be contained by col_rect, if not bad things happens (colision # is detected by one rect but no the other and both are used in # orther to collide with all the platforms). That is way the # -1 is needed in cr.bottom. return Rect(cr.left, cr.bottom - 1, cr.width, 1) # TODO: This has a problem with col_rects the same size as the # normal rect def create_left_right(self): """ Creates two rects each one being a half of col_rect. These rects are used to detect which side of the sprites touches something. """ cr = self.col_rect col_rect_left = Rect(cr.left, cr.top, cr.width /2 , cr.height) col_rect_right = Rect(cr.left + cr.width/2, cr.top, cr.width /2, cr.height) return col_rect_left, col_rect_right def init_masks(self): """ Init the mask for the sprite. NOTE: any additional rect created must be, in general, inside of col_rect. That way collisions will work properly """ # Create a mask with the image # TODO, NOTE TO SELF: The mask will use the first frame # in the animation! ALWAYS self.mask = mask.from_surface(self.image) #~ self.view_mask() # Feet mask self.feet_mask = mask.Mask((self.rect.width, 1)) h = self.rect.height s_at = self.feet_mask.set_at g_at = self.mask.get_at for x in xrange(self.rect.width): if g_at((x, h - 1)): s_at((x, 0), 1) #~ @property #~ def dirty(self): #~ return self._dirty #~ #~ @dirty.setter #~ def dirty(self, value): #~ if self.dirty == 1: #~ old = self.dirty_rect #~ else: #~ old = self.rect #~ self.dirty_rect = old.union(self.rect) #~ self._dirty = value def add_guest(self, sprite): """ Add a guest sprite to this sprite. The guest sprite will be updated after the host. """ self.guests.add(sprite) sprite.guest = True sprite.host = self def remove_guest(self, sprite): """ Remove a guest sprite to this sprite. """ self.guests.remove(sprite) sprite.guest = False sprite.host = None def add_host(self, sprite): """ Add a host for this sprite. This sprite will be updated after the host sprite. """ self.host = sprite self.guest = True sprite.guests.add(self) def remove_host(self): """ Remove a host from this sprite. """ self.host.guests.remove(self) self.host = None self.guest = False def one_layer_down(self): """ Moves the sprite one layer down in the render group. This will make the sprite be seen below other sprites in the same group. """ for g in self.groups(): if isinstance(g, TryGroup): g.one_layer_down(self) def one_layer_up(self): """ Moves the sprites one layer up in the render group. This will make the sprite be seen over other sprites in the same group. """ for g in self.groups(): if isinstance(g, TryGroup): g.one_layer_up(self) def move_ontop(self, ref_sprite): """ Move this sprite just on top of ref_sprite in render order. """ for g in self.groups(): if isinstance(g, TryGroup): g.ref_render_order_change(ref_sprite, self, 0) def move_below(self, ref_sprite): """ Move this sprite just below of ref_sprite in render order. """ for g in self.groups(): if isinstance(g, TryGroup): g.ref_render_order_change(ref_sprite, self, 1) def properly_add_ontop(self, sprite): """ Add a new sprite on top of this one. sprite will be added to all the groups in which self is and when in render groups, it will be added on top of self. """ for g in self.groups(): if isinstance(g, TryGroup): g.add_ontop(self, sprite) else: g.add(sprite) def properly_add_below(self, sprite): """ Add a new sprite below of this one. sprite will be added to all the groups in which self is and when in render groups, it will be added below of self. """ for g in self.groups(): if isinstance(g,TryGroup): g.add_below(self, sprite) else: g.add(sprite) def change_sprite_pos(self, x, y): """ Changes the sprite position to x, y. Move all the needed rects to the new x, y position. """ rect = self.rect dx, dy = x - rect.left, y - rect.top self.move_sprite(dx, dy) def move_sprite(self, delta_x, delta_y): """ Changes the sprite position by delta_x, delta_y. Move all the needed rects by delta_x, delta_y """ rect = self.rect.copy() dirty_rect = self.dirty_rect # TODO: with movements mods this is probably outdated. # All movement is made at the same moment. # Note to self: This if-else is needed to clean old sprite # positions when two movements are done in the same frame, # for example, teleporting/respawning # if self.dirty == 1: # old = dirty_rect # else: # old = rect old = rect # Move rects [r.move_ip(delta_x, delta_y) for r in self.rect_list] # Update dirty stuff self.dirty_rect = old.union(rect) self.dirty = 1 def move_sprite_float(self, delta_x, delta_y): """ Move the sprite a float amount Uses the integer part to move the sprite and stores the float part in a variable. """ self.fx += delta_x self.fy += delta_y self.fx, dx = modf(self.fx) self.fy, dy = modf(self.fy) # move rects self.move_sprite(dx, dy) def dprint(self,text): """ If debugging is True it will print debug text. """ if self.debugging: print text def _view_mask(self): """ Prints the sprite mask to the terminal and exits For debugging purposes. """ print self print "Printing mask and exiting." for y in xrange(self.mask.get_size()[1]): for x in xrange(self.mask.get_size()[0]): print self.mask.get_at((x,y)), print "" sys.exit(0)
class Monster(object): id = None # type: UUID direction = Direction.UP speed = 0 is_freeze = False position = None # type: Rect old_position = None # type: Rect parent = None # type: Monster SIZE = 8 def __init__(self, x: int, y: int, direction: Direction = None): self.id = uuid4() size = self.SIZE self.position = Rect(x, y, size, size) self.set_old_position() if direction: self.direction = direction def set_old_position(self): self.old_position = self.position.copy() def set_position(self, x: int, y: int): self.position.x = x self.position.y = y def set_speed(self, speed: int): self.speed = speed def set_freeze(self): self.is_freeze = True def unset_freeze(self): self.is_freeze = False def set_direction(self, direction: Direction): self.direction = direction def get_type(self): return self.__class__.__name__.lower() def get_grid_position(self): position = self.position return Rect( # (x >> 4) << 4 is faster than floor(x / 16) * 16 (position.x >> 4) << 4, (position.y >> 4) << 4, 32, 32, ) def check_collision_with_group(self, group, rect=None, callback=None): callback = callback or (lambda m: m.position) rect = rect or self.position if isinstance(group, SlicedArray): group = group.find_nearest(rect) rect_group = list(map(callback, group)) indices = rect.collidelistall(rect_group) return [group[index] for index in indices] def check_collision_with_old_position(self, monster): return self.old_position.colliderect(monster.position) def union_new_position_with_old(self): return self.position.union(self.old_position) def set_parent(self, parent): self.parent = parent def get_position(self): return dict( x=self.position.x, y=self.position.y, ) def move(self): if self.is_freeze: return self.move_with_speed(self.speed) def move_with_speed(self, speed): position = self.position direction = self.direction if direction is Direction.UP: position.y -= speed elif direction is Direction.DOWN: position.y += speed elif direction is Direction.LEFT: position.x -= speed elif direction is Direction.RIGHT: position.x += speed
def wrapper(self, *args, **kwargs): temp = Rect.copy(self) res = method(self, *args, **kwargs) if temp != self: self.notify() return res