def __init__(self, subject_x: float, subject_y: float, object_x: float, object_y: float, extra_damage: float, object_group, all_spites, *groups): super().__init__(all_spites, *groups) dx = object_x - subject_x dy = object_y - subject_y # Увеличиваем расстояние пропорционально # Чтобы заклинание летело далеко за пределы экрана, а не просто к курсору while (abs(dx) < 5000 and abs(dy) < 5000) and (self.spell_type != Spell.FLASH and self.spell_type != Spell.TELEPORT): dx *= 2 dy *= 2 dy += 1 self.point = (subject_x + dx, subject_y + dy) # Увеличения урона/времени действия в зависимости от уровня существа if self.spell_type != Spell.TELEPORT: self.damage = self.__class__.damage * extra_damage if self.spell_type in (self.ICE, self.POISON): self.action_time = self.__class__.action_time * extra_damage # Угол, под которым пущено заклинание (для поворота картинки) if dx >= 0: self.angle = -degrees(atan(dy / max(dx, 0.00001))) else: self.angle = 180 - degrees(atan(dy / min(dx, -0.00001))) self.object_group = object_group self.cur_frame = 0 self.cur_list = 0 self.last_update_time = pygame.time.get_ticks() # Поворачиваем картинку self.frames = [[ pygame.transform.rotate(i, self.angle) for i in self.__class__.frames[0] ], self.__class__.frames[1:]] self.image = self.frames[0][0] self.rect = self.image.get_rect() self.rect.center = subject_x, subject_y self.collider = Collider(*self.rect.center, (TILE_SIZE * 0.2, TILE_SIZE * 0.2)) self.update()
def __init__(self, x: float, y: float, *groups): super().__init__(*groups) self.image = Door.frames[0] self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) self.collider = Collider(x, y) self.opened = False
def __init__(self, tile_type: str, x: float, y: float, *groups): super().__init__(*groups) self.type = tile_type # тип тайла self.image = Tile.IMAGES[self.type] # Изображение по типу self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) # Переменные для удобства half = TILE_SIZE // 2 quarter = TILE_SIZE // 4 # Далее код, в котором каждому виду стены будет присвоен свой размер коллайдера. # Размер и расположение подобраны так, что стены не имеют промежутков между собой, если они смежные # (Как это происходит с ящиками и бочками, кстати это так и задумано) # Но при этом игрок не слишком жестко взаимодействует с ними при перемещении if self.type in ('1', '5'): self.collider = Collider(x, y, (half, half * 3)) elif self.type in ('3', '7'): self.collider = Collider(x, y, (half * 3, half)) elif self.type in ('2', '9'): self.collider = Collider(x - quarter, y + quarter, (half * 2, half * 2)) elif self.type in ('4', '0'): self.collider = Collider(x + quarter, y + quarter, (half * 2, half * 2)) elif self.type in ('6', '-'): self.collider = Collider(x + quarter, y - quarter, (half * 2, half * 2)) elif self.type in ('8', '='): self.collider = Collider(x - quarter, y - quarter, (half * 2, half * 2)) else: self.collider = Collider(x, y)
def __init__(self, tile_type: str, x: float, y: float, new_boxes_seed, index, *groups): super().__init__(*groups) # Сохраняем ссылку на сид и индекс, чтоб потом удалить по индексу из сида этот ящик, # если он будет разрушен self.seed = new_boxes_seed self.index = index self.image = Furniture.IMAGES[tile_type] self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) self.collider = Collider(x, y)
def __init__(self, x, y, new_boxes_seed, index, *args): super().__init__(Chest.chest_group, *args) # Сохраняем индекс и сид, чтоб потом убрать из сида сундук, как открытый self.seed = new_boxes_seed self.index = index self.opened = False self.last_update_time = 0 self.current_frame = 0 self.update_time = self.UPDATE_TIME self.image = self.frames[self.current_frame] self.back_image = self.back_of_chest.copy() self.rect = self.image.get_rect().move(x * TILE_SIZE, y * TILE_SIZE) self.back_image_rect = self.back_image.get_rect() self.collider = Collider(x, y)
def __init__(self, item_type: str, count: int, x: float, y: float, all_sprites, *groups): super().__init__(all_sprites, GroundItem.sprites_group, *groups) self.type = item_type # тип предмета self.count = abs(int(count)) if not self.count: self.kill() self.image, self.sound = GroundItem.IMAGES[ self.type], GroundItem.SOUNDS[self.type] self.sound.set_volume(DEFAULT_SOUNDS_VOLUME * 3) self.image = self.image.copy() self.rect = self.image.get_rect() self.rect.center = x, y self.collider = Collider(x, y) for other in pygame.sprite.spritecollide(self, GroundItem.sprites_group, False): other: GroundItem if self.type == other.type and other != self: self.count = self.count + other.count other.kill() font = load_game_font(32) if self.count > 1: count_text = font.render(str(self.count), True, (255, 255, 255)) rect = count_text.get_rect() rect.center = self.rect.right - rect.w // 2, self.rect.bottom - rect.h // 2 self.image.blit( count_text, count_text.get_rect( bottomright=self.image.get_rect().bottomright))
class Spell(pygame.sprite.Sprite): """ Класс, отвечающий за предстовление базового заклинания в игре """ FIRE = "fire" FLASH = "flash" ICE = "ice" POISON = "poison" VOID = "void" TELEPORT = "teleport" # Номер кадра анимации, на котором происходит действие (телепортация, урон, хил) damage_frame = 0 start_position = None def __init__(self, subject_x: float, subject_y: float, object_x: float, object_y: float, extra_damage: float, object_group, all_spites, *groups): super().__init__(all_spites, *groups) dx = object_x - subject_x dy = object_y - subject_y # Увеличиваем расстояние пропорционально # Чтобы заклинание летело далеко за пределы экрана, а не просто к курсору while (abs(dx) < 5000 and abs(dy) < 5000) and (self.spell_type != Spell.FLASH and self.spell_type != Spell.TELEPORT): dx *= 2 dy *= 2 dy += 1 self.point = (subject_x + dx, subject_y + dy) # Увеличения урона/времени действия в зависимости от уровня существа if self.spell_type != Spell.TELEPORT: self.damage = self.__class__.damage * extra_damage if self.spell_type in (self.ICE, self.POISON): self.action_time = self.__class__.action_time * extra_damage # Угол, под которым пущено заклинание (для поворота картинки) if dx >= 0: self.angle = -degrees(atan(dy / max(dx, 0.00001))) else: self.angle = 180 - degrees(atan(dy / min(dx, -0.00001))) self.object_group = object_group self.cur_frame = 0 self.cur_list = 0 self.last_update_time = pygame.time.get_ticks() # Поворачиваем картинку self.frames = [[ pygame.transform.rotate(i, self.angle) for i in self.__class__.frames[0] ], self.__class__.frames[1:]] self.image = self.frames[0][0] self.rect = self.image.get_rect() self.rect.center = subject_x, subject_y self.collider = Collider(*self.rect.center, (TILE_SIZE * 0.2, TILE_SIZE * 0.2)) self.update() def update(self) -> None: if self.rect.center == self.point: # Значит заклинание достигло цели (либо врезалось) # Теперь оно должно нанести урон и уничтожиться после показа анимации ticks = pygame.time.get_ticks() if ticks - self.last_update_time < self.UPDATE_TIME: return self.last_update_time = ticks if self.cur_frame == self.damage_frame: # Производим действие if isinstance(self, TeleportSpell): self.object_group[0].rect.center = self.rect.center else: if isinstance(self, VoidSpell): # Сделаем, чтоб коллайдер был ровно по границы анимации # Чтоб не было расхождения и не вводить в ступор игрока size = (round((self.rect.w / 3)), ) * 2 self.collider.update(*self.rect.center, size) else: size = (round((self.rect.w / 2)), ) * 2 self.collider.update(*self.rect.center, size) # Убиваем все заклинание-ломаемые вещи, которые задеваем pygame.sprite.spritecollide(self.collider, Spell.furniture_group, True) pygame.sprite.spritecollide(self.collider, Spell.doors_group, True) # Наносим урон группе объектов, на которых направлено заклинание for obj in self.object_group: if pygame.sprite.collide_circle(self.collider, obj): obj.get_damage(self.damage, self.spell_type, self.action_time) # Меняем номер кадра анимации self.cur_frame += 1 if self.cur_frame == len(self.__class__.frames[self.cur_list]): # Если кадр последний, убиваем заклинание self.kill() if isinstance(self, TeleportSpell): # Подчищаем self.start_sprite.kill() return # Меняем кадр анимации self.image = self.__class__.frames[self.cur_list][self.cur_frame] # Выравниваем центр изображения pos = self.rect.center self.rect = self.image.get_rect() self.rect.center = pos if isinstance(self, TeleportSpell): self.start_sprite.image = self.image else: # Иначе ещё летим self.cur_frame = (self.cur_frame + 1) % len( self.__class__.frames[0]) # Вычисляем перемещение self_x, self_y = self.rect.center point_x, point_y = self.point distance_to_object = max( ((point_x - self_x)**2 + (point_y - self_y)**2)**0.5, self.speed) part_move = max(distance_to_object / self.speed, 0.5) dx = round((point_x - self_x) / part_move) dy = round((point_y - self_y) / part_move) self.rect.x = self.rect.x + dx self.rect.y = self.rect.y + dy # Обновляем позицию коллайдера self.collider.update(*self.rect.center) do_kill = False # Проверяем на всевозможные соприкосновения, от которых заклинание может умереть for obj in pygame.sprite.spritecollide(self.collider, self.object_group, False): if obj.alive: do_kill = True for obj in pygame.sprite.spritecollide(self.collider, Spell.barrier_group, False): obj.collider.update(*obj.rect.center) if pygame.sprite.collide_rect(self.collider, obj.collider): do_kill = True for obj in pygame.sprite.spritecollide(self.collider, Spell.doors_group, False): obj.collider.update(*obj.rect.center) if not obj.opened: do_kill = True for obj in pygame.sprite.spritecollide(self.collider, Spell.furniture_group, False): obj.collider.update(*obj.rect.center) if pygame.sprite.collide_rect(self.collider, obj.collider): do_kill = True if do_kill and self.spell_type != Spell.FLASH and self.spell_type != Spell.TELEPORT: self.point = self.rect.center # Проверяем, достигли ли объекта if self.rect.center == self.point: self.cur_frame = 0 self.cur_list = randint(1, len(self.__class__.frames) - 1) # Нужно, чтоб не задваивался звук # Т.к. воспроизводится и в месте каста, и в месте попадания if not isinstance(self, TeleportSpell): pass self.sounds_channel.play(choice(self.SPELL_SOUNDS)) ticks = pygame.time.get_ticks() if ticks - self.last_update_time < self.UPDATE_TIME: return # Обновляем кадр анимации self.last_update_time = ticks self.image = self.frames[0][self.cur_frame] if isinstance(self, TeleportSpell): self.start_sprite.image = self.image @staticmethod def set_global_collisions_group(barrier_group: pygame.sprite.Group): """ Метод устанавливает группу со спрайтами, которые будут считаться физическими объектами для всех сущностей на уровне. (Кроме индивидуальных спрайтов у конкретных объектов, например у врагов будет отдельное взаимодействие с игроком). Метод нужен при инициализации :param barrier_group: Новая группа """ Spell.barrier_group = barrier_group @staticmethod def set_global_breaking_group(doors_group, furniture_group): """ Метод устанавливает группы со спрайтами, которые ломаются от соприкосновений с заклинанием :param doors_group: Группа дверей :param furniture_group: Группа ящиков и бочек :return: None """ Spell.doors_group = doors_group Spell.furniture_group = furniture_group