def at_least(value: Vector2, minimum: [int, float]) -> Vector2: """ :param value - vector to be truncated :param minimum - vector minimum length :return pygame.math.Vector2 object Returns a vector object that has at least the given length. Vector will inherit the angle of the given value vector. """ if value.length() < minimum: value.scale_to_length(minimum) return value
def truncate(value: Vector2, maximum: [int, float]) -> Vector2: """ :param value - vector to be truncated :param maximum - vector length limit :return pygame.math.Vector2 object Returns a vector object that is limited a given length. Vector will inherit the angle of the given value vector. """ if value.length() > maximum: value.scale_to_length(maximum) return value
def raytrace(self, angle: int, max_distance: int = 100, use_absolute_angle: bool = False, return_real_distance: bool = False): """Vérifie si le rayon d'angle donné rencontre un mur avant une certaine distance Parameters ---------- angle: Angle du rayon en degrés, 0 étant l'avant de la voiture max_distance: Distance maximum à prendre en compte [par défaut 100] use_absolute_angle: Si l'angle donné est relatif au plan (1) ou à la voiture (0) [par défaut False] return_real_distance: Si la valeur retournée doit être la distance réelle, et non entre 0 et 1 [par défaut False] Returns ------- :class:`float`: Retourne la distance entre 0 et 'max', 0 étant une collision immédiate et 'max' à la distance maximum, ou -1 si aucune collision. La valeur de 'max' est définie par le paramètre `max_distance` si `return_real_distance = True`, 1 sinon.""" assert all([isinstance(x, Border) for x in self.circuit] ), "La liste du circuit ne doit contenir que des objets de type Border" if not use_absolute_angle: angle = self.abs_rotation + angle angle = math.radians(angle) # direction = vector(round(math.cos(angle), 5), # round(math.sin(angle), 5)) ray_direction = Vector(2 * math.cos(angle), 2 * math.sin(angle)) ray_direction.scale_to_length(max_distance) distances = [] for line in self.circuit: distance1 = Vector( line.start[0]-self.position[0], line.start[1]-self.position[1]).length() distance2 = Vector( line.end[0]-self.position[0], line.end[1]-self.position[1]).length() if distance1 < max_distance or distance2 < max_distance: distances.append(line_ray_intersection_point(self.position, ray_direction, line.start, line.end)) distances = [Vector(x[0]-self.position[0], x[1]-self.position[1]).length() for x in distances if len(x) != 0] if len(distances) == 0: return -1 shortest_distance = min(distances) if shortest_distance > max_distance: return -1 if return_real_distance: return shortest_distance return shortest_distance/max_distance
def __init__(self, mouse_or_enemy_position, game_area, hero, type): super().__init__() self.position = Vector2() self.velocity = Vector2() self.acceleration = Vector2() self.type = type if self.type == "hero": self.position = hero.position self.velocity = Vector2(mouse_or_enemy_position) - ( Vector2(hero.position) + Vector2(hero.camera)) Vector2.scale_to_length(self.velocity, 4) elif self.type == "enemy": self.position = mouse_or_enemy_position self.velocity = ( Vector2(hero.position) + Vector2(random.randint(5, 100), random.randint(5, 100)) - Vector2(mouse_or_enemy_position)) Vector2.scale_to_length(self.velocity, 5) # self.acceleration = self.assetAnimation = [ pygame.image.load("Assets/Sprites/Projectiles/Projectile1.png"), pygame.image.load("Assets/Sprites/Projectiles/Projectile2.png"), pygame.image.load("Assets/Sprites/Projectiles/Projectile3.png") ] self.current_sprite = 0 self.growth_factor = 0.4 self.projectile_fired_time = pygame.time.get_ticks() self.projectile_live_time = 5000 self.image = self.assetAnimation[self.current_sprite] self.rect = self.image.get_rect() self.rect.center = self.position self.hero = hero self.game_area = game_area
class Missile(Entity): def __init__(self, player, entities, xpos, ypos): super().__init__(entities, xpos, ypos, 2 * missile_prox, 2 * missile_prox) self.target = player self.a = Vec(0, 0) self.v = Vec(0, 0) self.prox = missile_prox self.state_counter = 0 self.explosion_radius = missile_explosion_radius self.max_speed = missile_max_speed self.sfd = False # Drawing surface and state counter self.set_state(MissileState.WAITING) def get_bbox(self): return (self.pos - (missile_radius, missile_radius), self.pos + (missile_radius, missile_radius)) def set_state(self, state): if state == MissileState.WAITING: self.a = Vec(0, 0) self.v = Vec(0, 0) self._surface = mk_wait_surface(self.xsize, self.ysize, self.center) elif state == MissileState.TARGETING: self.a = Vec(0, 0) self.v = Vec(0, 0) self._surface = mk_target_surface(self.xsize, self.ysize, self.center) elif state == MissileState.EXPLODING: self.a = Vec(0, 0) self.v = Vec(0, 0) self._surface = mk_exploding_surface(self.xsize, self.ysize, self.center) self.state = state def update(self, time, entities): if (self.state == MissileState.WAITING): # Trigger if the player is close if get_dist(self, self.target) < self.prox: self.set_state(MissileState.TARGETING) # Trigger if the player clicks on something close elif self.target.target is not None: if get_dist(self, self.target.target) < self.prox: self.set_state(MissileState.TARGETING) elif (self.state == MissileState.TARGETING): # Collision with solids for e in entities: if e.is_solid and collide(self, e): self.die("") # Explode near the player if vec_from(self, self.target).length( ) < missile_explosion_radius + self.target.xsize / 2: self.die("") # Accelerate towards the player da = vec_from(self, self.target) da += self.target.v * time da = da.normalize() da *= missile_homingness self.a = da elif (self.state == MissileState.EXPLODING): # TODO: lower velocity self.state_counter += 1 # 10 frames of missile explosion # TODO: time-based? if self.state_counter == 10: self.sfd = True # Kill player if they are close if get_dist(self, self.target ) < missile_explosion_radius + self.target.xsize: self.target.die("Blown up by a missile.") # Physics self.v += self.a if self.v.length() > missile_max_speed: self.v.scale_to_length(missile_max_speed) self.pos += self.v * time self.pos = wrap_around(self.pos) def draw(self, screen): if self.target is not None and self.state == MissileState.TARGETING: draw_line(screen, self.pos, self.target.pos, missile_explosion_color) super().draw(screen) def die(self, msg): self.set_state(MissileState.EXPLODING)
class MovingSprite(CenteredSprite): """ A class compatible with pygame.Sprite which tracks position, motion, rotation in 2d space. """ max_speed = 100.0 # Absolute speed limit for this thing min_speed = 0.5 # ...and the slowest its allowed to go accel_rate = 1.0 # Value added to speed each frame when accelerating decel_rate = 1.0 # Value subtracted from speed each frame when decelerating turning_speed = 0.0 # Current turning speed (rotation in degrees per frame) max_turning_speed = 360 # Max degrees per frame this can turn; 360 = unlimited destination = None # Necessary for seek_motion _motion_function = None # Function called to move this on Sprite.update() _arrival_function = None # Function called when destination reached by seeking motion def __init__(self, x=0.0, y=0.0, speed=None, direction=None): """ MovingSprite constructor. "x" and "y" must be float values which define the position of the object. "speed" must be a float value which sets the magnitude of the motion vector of the object. "direction" must be a float value which sets the motion direction of the object in degrees. If you would like to use a motion vector made up of x/y values, create a "motion" vector with the values you need, and skip this function completely, like so: class MySprite(MovingSprite): def __init__(self): CenteredSprite.__init__(self, <x>, <y>) # Sets Sprite.rect self.motion = Vector(<x>, <y>) # Cartesian self._motion_function = self.cartesian_motion # (example) """ CenteredSprite.__init__(self, x, y) self._motion_function = self.cartesian_motion self.motion = Vector() if speed is None or direction is None: return self.motion.from_polar((speed, direction)) @property def speed(self): """ Get the "magnitude" of this object's motion vector. """ return self.motion.magnitude() @speed.setter def speed(self, value): """ Sets the "magnitude" of this object's motion vector. """ try: self.motion.scale_to_length(value) except ValueError as e: self.motion.from_polar((value, 0.0)) @property def direction(self): """ Gets the screen direction of this MovingSprite in degrees """ mag, deg = self.motion.as_polar() return deg @direction.setter def direction(self, degrees): """ Sets the screen direction of this MovingSprite to the given degrees. """ mag = self.motion.magnitude() if mag == 0.0: self.motion.from_polar((2.22e-222, degrees)) else: self.motion.from_polar((mag, degrees)) def update(self): """ Regular cyclic update task, as called from pygame.Sprite. Calls the current move function, which by default is "cartesian_motion". """ self._motion_function() def shift_position(self, x, y): """ Does a relative cartesian move the position of the object by x, y. """ self.position.x += x self.position.y += y self.rect.centerx = int(self.position.x) self.rect.centery = int(self.position.y) return self def set_motion_polar(self, magnitude, degrees): """ Set the current motion vector from the given magnitude, degrees. """ self.motion.from_polar((magnitude, degrees)) def travel_to(self, target, on_arrival=None): """ High-level command which sets this MovingSprite on a path towards a given target. Each subsequent call to "move()" will move the Thing one frame closer to the target. When travel is complete, the optional "on_arrival" function will be called. """ self.destination = vector(target) self._motion_function = self.seek_motion self._arrival_function = on_arrival return self.make_heading() def make_heading(self): """ Sets the initial speed and direction of an object provided a destination using the "travel_to()" function. The default implementation sets this object's direction to the direction to its destination, without taking into consideration turning speed. The speed of this MovingSprite is set to it's acceration rate, unless it's already moving faster. A true physical moving thing most likely cannot change speed and direction in such an immediate fashion, unless the things you hear about "off world" vehicles visiting our planet are really true. You're free to model that, I suppose. This implementation is sufficient for game pieces such as used in BoardGame, but should be overridden in order to create more sophisticated motion. """ speed = max(self.speed, self.accel_rate) self.motion = (self.destination - self.position) self.motion.scale_to_length(speed) return self #################################################################################### # Move routines which may be called in the "update()" function of the parent Sprite: # Various movement methods, called from the "update" method, depending upon the # instance and circumstances. The "update" method calls the "_motion_function" # which is a reference to one of these methods, or a method in a derived class. #################################################################################### def no_motion(self): """ A "_motion_function" which does nothing. """ pass def cartesian_motion(self): """ Moves one frame by adding the x/y values of this MovingSprite's "motion" vector to the x/y values of this MovingSprite's "position" vector. This is the default "_motion_function". """ self.position.x += self.motion.x self.position.y += self.motion.y return self.update_rect() def seek_motion(self): """ One of the "_motion_function" methods, called from "update". This method points the sprite towards its current "destination" vector and accelerates towards it, decelerating to zero when it approaches. This is the move function set in the "travel_to" method. This implementation is sufficient for game pieces as used in BoardGame, but should be overridden in order to create more sophisticated motion. """ remaining_distance = (self.destination - self.position).magnitude() if remaining_distance <= self.decel_rate: self.position = self.destination self.rect.centerx = int(self.position.x) self.rect.centery = int(self.position.y) self.speed = 0.0 self._motion_function = self.no_motion if self._arrival_function: self._arrival_function() elif remaining_distance <= triangular(self.speed, self.decel_rate): # coming up on target_pos; decelerate: self.speed = max(self.min_speed, self.speed - self.decel_rate) else: # still far away from target_pos; accelate towards it: self.speed = min(self.max_speed, self.speed + self.accel_rate) return self.cartesian_motion() #################################################################################### # High-level movement functions #################################################################################### def turn_towards(self, vector): """ Turn incrementally towards the given position vector, taking into account this MovingSprite's current and max turning speed. """ self.increment_turning_speed(self.direction_to_vector(vector)) self.direction += self.turning_speed def turn_away_from(self, vector): """ Turn incrementally away from the given position vector, taking into account this MovingSprite's current and max turning speed. """ self.increment_turning_speed(self.direction_to_vector(vector) + 180) self.direction += self.turning_speed def increment_turning_speed(self, direction): """ Incrementally adjust this MovingSprite's "turning_speed" attribute towards or away from the given screen direction in degrees, taking into account this MovingSprite's current and max turning speed. Affects only the "turning_speed" attribute - does NOT change the "motion" vector. """ desired_turn = turning_degrees(self.direction - direction) turn_mag = abs(desired_turn) if turn_mag < self.max_turning_speed: self.direction = direction self.turning_speed = 0.0 elif abs(self.turning_speed) < self.max_turning_speed: self.turning_speed -= math.copysign(self.max_turning_speed, desired_turn) #################################################################################### # Relative position methods: #################################################################################### def distance_to_vector(self, vector): """ Returns a float - the distance in pixels from this MovingSprite to the given position vector. """ return self.position.distance_to(vector) def distance_to_thing(self, thing): """ Returns a float - the distance in pixels from this MovingSprite to the given "thing-like" object. """ return self.position.distance_to(thing.position) def direction_to_vector(self, vector): """ Returns the world angle in degrees from this MovingSprite's position to the given position vector. Returns non-normalized degrees (float). If you need a Vector instead of an angle in radians, use Vector subtraction. """ return (vector - self.position).as_polar()[1] def direction_to_thing(self, thing): """ Returns the world angle in degrees from this MovingSprite's position to the given "thing-like" object's position. Returns non-normalized degrees (float). If you need a Vector instead of an angle in radians, use Vector subtraction. """ return (thing.position - self.position).as_polar()[1] def angle_to_vector(self, vector): """ Returns the world angle in degrees from this MovingSprite's position to the given position vector. Returns non-normalized degrees (float). If you need a Vector instead of an angle in radians, use Vector subtraction. Alias of "direction_to_vector". """ return (vector - self.position).as_polar()[1] def angle_to_thing(self, thing): """ Returns the world angle in degrees from this MovingSprite's position to the given "thing-like" object's position. Returns non-normalized degrees (float). If you need a Vector instead of an angle in radians, use Vector subtraction. Alias of "direction_to_thing". """ return (thing.position - self.position).as_polar()[1] def radians_to_vector(self, vector): """ Returns the world angle in radians from this MovingSprite's position to the given position vector. Returns non-normalized radians (float). If you need a Vector, simply subtract this MovingSprite's position from the vector's. """ return math.radians((vector - self.position).as_polar()[1]) def radians_to_thing(self, vector): """ Returns the world angle in radians from this MovingSprite's position to the given "thing-like" object's position. Returns non-normalized radians (float). If you need a Vector, simply subtract this MovingSprite's position from the vector's. """ return math.radians((thing.position - self.position).as_polar()[1]) #################################################################################### # Status methods: #################################################################################### def is_offscreen(self, screen_rect): """ Returns one of the "OFFSCREEN_<direction>" constants if this MovingSprite's position if outside of the boundaries of the Surface it is to be rendered on. """ if self.rect.colliderect(screen_rect): bits = 0 if self.position.x < 0: bits |= OFFSCREEN_LEFT elif self.position.x > screen_rect.width: bits |= OFFSCREEN_RIGHT if self.position.y < 0: bits |= OFFSCREEN_TOP elif self.position.y > screen_rect.height: bits |= OFFSCREEN_BOTTOM return bits return 0 def is_directionless(self): """ Returns boolean "True" if radians and magnitude of this MovingSprite's motion Vector cannot be determined. """ return self.motion.x == 0 and self.motion.y == 0 def ns_direction(self): """ Returns COMPASS_NORTH or COMPASS_SOUTH, depending on if the y-axis of movement is positive or negative """ return COMPASS_NORTH if self.motion.y < 0 else COMPASS_SOUTH def ew_direction(self): """ Returns COMPASS_EAST or COMPASS_WEST, depending on if the y-axis of movement is positive or negative """ return COMPASS_WEST if self.motion.x < 0 else COMPASS_EAST def __str__(self): if self.is_directionless(): return "<Motionless %s at %.1f / %.1f>" % ( type(self).__name__, self.position.x, self.position.y) return "<%s at %.1f / %.1f, moving %d degrees at %.1f pixels-per-second>" % \ (type(self).__name__, self.position.x, self.position.y, self.motion.degrees, self.motion.magnitude())
def update(self, hero): if self.is_moving: self.position += self.velocity self.rect.center = self.position self.current_sprite += 0.3 if self.current_sprite >= len(self.assetAnimation): self.current_sprite = 0 self.image = self.assetAnimation[int(self.current_sprite)] # if not self.game_area.contains(self.rect): # self.kill() self.velocity = Vector2(self.hero.position) - Vector2(self.position) if self.enemy_type == "Mushroom": Vector2.scale_to_length(self.velocity, 1) if self.enemy_type == "Skeletor": Vector2.scale_to_length(self.velocity, 2) if self.enemy_type == "Hydra": Vector2.scale_to_length(self.velocity, 2) if self.enemy_type == "Cacodemon": Vector2.scale_to_length(self.velocity, 2) if self.enemy_type == "Invulnro": Vector2.scale_to_length(self.velocity, 1) if self.enemy_type == "Tetro": Vector2.scale_to_length(self.velocity, 4) if self.enemy_type == "Mergamoth": Vector2.scale_to_length(self.velocity, self.scale_factor) if self.enemy_type == "Cacodemon" or self.enemy_type == "Mergamoth": if random.randint(1, 100) > 99: self.velocity.scale_to_length(0) # self.fired_time = pygame.time.get_ticks() gamestate.all_sprites.add( projectile.Projectile(self.rect.center, gamestate.game_area, hero, "enemy"))
def add_width(pathway: typing.List[tuple], colors: typing.Dict[str, pygame.Color], screen_size: typing.Tuple[int]) -> dict: """Elargit le circuit à partir du tracé de base Pour chaque segment du tracé, on calcule la médiatrice du segment puis on trouve deux points sur cette médiatrice dont la distance respecte les constantes posées. Un nettoyage est ensuite réalisé pour supprimer les angles trop bruts ou inutiles, par la fonction :func:`check_angles`. Parameters ---------- pathway: Liste des points du tracé de base colors: Dictionnaire des couleurs à utiliser screen_size: (:class:`int`, :class:`int`) Taille en X,Y de la fenêtre Returns ------- :class:`dict`: Dictionnaire contenant le premier point supérieur ('point1'), le premier point inférieur ('point2') et toutes les :class:`classes.Border` du circuit ('bordures') """ points_over = list() points_under = list() result = list() delta = -round(MIN_PATH_WIDTH / 12), round(MIN_PATH_WIDTH / 12) new_delta = min(MIN_PATH_WIDTH + random.randrange(*delta), MAX_PATH_WIDTH) # First point vect = Vector(pathway[1][0] - pathway[0][0], pathway[1][1] - pathway[0][1]) vect.rotate_ip(90) vect.scale_to_length(new_delta) points_over.append( (pathway[0][0] - vect.x / 2, pathway[0][1] - vect.y / 2)) points_under.append( (pathway[0][0] + vect.x / 2, pathway[0][1] + vect.y / 2)) # Other points for enum in range(1, len(pathway) - 1): point1, point2, point3 = pathway[enum - 1], pathway[enum], pathway[enum + 1] vect = Vector(point2[0]-point1[0], point2[1]-point1[1]) \ + Vector(point3[0]-point2[0], point3[1]-point2[1]) vect.rotate_ip(90) new_delta = max( min(new_delta + random.randrange(*delta), MAX_PATH_WIDTH), MIN_PATH_WIDTH) vect.scale_to_length(new_delta) # points_over.append(point2) points_over.append((point2[0] - vect.x / 2, point2[1] - vect.y / 2)) points_under.append((point2[0] + vect.x / 2, point2[1] + vect.y / 2)) # Last point vect = Vector(pathway[-1][0] - pathway[-2][0], pathway[-1][1] - pathway[-2][1]) vect.rotate_ip(90) vect.scale_to_length(new_delta) points_over.append( (pathway[-1][0] - vect.x / 2, pathway[-1][1] - vect.y / 2)) points_under.append( (pathway[-1][0] + vect.x / 2, pathway[-1][1] + vect.y / 2)) # Cleanup of points check_angles(points_over) check_angles(points_under) for path in (points_over, points_under): for index in range(len(path) - 1): if colors is None: # debug only - couleur aléatoire color = ((index * 100 + 70) % 255, (index * 90 + 20) % 255, (index * 50 + 40) % 255) else: color = colors["borders"] if path[index][1] > screen_size[1] - 10: path[index][1] = screen_size[1] - 10 elif path[index][1] < 10: path[index][1] = 10 result.append(Border(path[index], path[index + 1], color)) black = (10, 10, 10) result.append( Border(points_over[0], points_under[0], colors["border-begin"] if colors is not None else black)) result.append( Border(points_over[-1], points_under[-1], colors["border-end"] if colors is not None else black)) return { "bordures": result, "point1": points_under[0], "point2": points_over[0] }
class Entity(Sprite): """Base class for all moving Entities""" # Keep a group of all entities for the purpose of updating and drawing group = SpriteGroup() def __init__(self, size=(1, 1), pos=(0, 0)): Sprite.__init__(self) # Radius attribute for collision detection, circle centered on pos self.radius = size[0] / 2 self.image = Surface(size).convert() self.image.set_colorkey(COLOR.BLACK) # set black as transparency color self.orig_img = self.image # Keep an original copy for rotation purposes self.rect = self.image.get_rect(center=pos) self.position = Vector(pos) self.velocity = Vector(0, 0) self.angle = 0 self.rotation_speed = 0 self.max_velocity = 10 self.max_rotation = 10 Entity.group.add(self) def calc_position(self): """Calculates the next position based on velocity""" if self.velocity.length() > self.max_velocity: # Scale velocity to max self.velocity.scale_to_length(self.max_velocity) return self.position + self.velocity def calc_rotation(self): """Calculates the next angle in the rotation""" if self.rotation_speed > self.max_rotation: self.rotation_speed = self.max_rotation if self.rotation_speed < -self.max_rotation: self.rotation_speed = -self.max_rotation return self.angle + self.rotation_speed def move(self, pos=Vector(0, 0)): """Moves the position to the Vector2 given""" # Wrap around the screen if pos.x > SCREEN_WIDTH: pos.x = 0 elif pos.x < 0: pos.x = SCREEN_WIDTH if pos.y > SCREEN_HEIGHT: pos.y = 0 elif pos.y < 0: pos.y = SCREEN_HEIGHT self.position = pos # Set the center of the rect to the position self.rect.center = self.position def rotate(self, angle=0): """Rotates the angle and the sprite image""" # Normalize angle into 360 deg if angle > 360: angle -= 360 elif angle < 360: angle += 360 self.angle = angle self.image = rotate(self.orig_img, -self.angle) self.rect = self.image.get_rect(center=self.rect.center) def update(self): """Called every tick to update the state of the Entity""" self.rotate(self.calc_rotation()) self.move(self.calc_position())