Ejemplo n.º 1
0
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())
Ejemplo n.º 2
0
 def steer(self, position: Vector2, velocity: Vector2, *args,
           **kwargs) -> Vector2:
     direction = bry_vector.compass(velocity.as_polar()[1])
     if kwargs['power']:
         return bry_vector.calc_vector(kwargs['power'], 90 - direction)