# -*- coding: utf-8 -*-
""" Blended Steering Behaviors
This module implements a class that Blends a list of
:py:class:`~.KinematicSteeringBehavior` s and provides
a weighted sum of their outputs as a steering request.
This is the bread and butter of **Steering Behaviors** since it easily
combines different behaviors that allow for semi-complex AI behaviors.
Derives from :py:class:`~.kinematic.KinematicSteeringBehavior`.
Example
--------
This is how you would normally create your own :py:class:`~BlendedSteering` ,
in this case we are making a more complex version of :py:class:`~steering.kinematic.Arrive`
where the character looks where it's going and also tries to avoid any
obstacle in the way.
.. code-block:: python
flocking_behavior = BlendedSteering(
character = character
behaviors = [
BehaviorAndWeight(kinematic.Arrive(character, target), weight = 1),
BehaviorAndWeight(kinematic.LookWhereYoureGoing(character), weight = 1),
BehaviorAndWeight(kinematic.ObstacleAvoidance(character, obstacles), weight = 2),
]
)
This module also includes a couple of pre-implemented :py:class:`BlendedSteering`.
.. todo:: Make BehaviorAndWeight prettier, maybe use named tuples?
"""
from . import kinematic
from . import path
from pygame_ai.gameobject import DummyGameObject
[docs]class BehaviorAndWeight(object):
""" Container for Behavior and Weight values
Parameters
----------
behavior: :py:class:`~.KinematicSteeringBehavior`
weight: int
"""
def __init__(self, behavior, weight):
self.behavior = behavior
self.weight = weight
[docs]class BlendedSteering(kinematic.KinematicSteeringBehavior):
""" Base Blended Steering
This class provides methods neccesary to combine the list of steering
behaviors and produce a single :py:class:`~.kinematic.SteeringOutput`.
Derives from :py:class:`~.KinematicSteeringBehavior`, currently
:py:class:`BlendedSteering` with :py:class:`~.StaticSteeringBehavior`
is not supported.
Parameters
----------
character: :py:class:`~gameobject.GameObject`
Character with this behavior
behaviors: list(:py:class:`~.BehaviorAndWeight`)
List of behaviors that compose this :py:class:`~.BlendedSteering`
"""
def __init__(self, character, behaviors):
self.character = character
self.behaviors = behaviors
def __repr__(self):
return 'BlendedSteering '+super(BlendedSteering, self).__repr__()
[docs] def draw_indicators(self, screen, offset = (lambda pos: pos)):
""" Draws appropiate indicators for this :py:class:`BlendedSteering`
Draws the indicators of all :py:class:`~.KinematicSteeringBehavior`
that compose this :py:class:`~.BlendedSteering`.
Parameters
----------
screen: :pgsurf:`Surface`
Surface in which to draw indicators, normally this would be the screen Surface
offset: function, optional
Function that applies an offset to the object's position
This is meant to be used together with scrolling cameras,
leave empty if your game doesn't implement one,it defaults
to a linear function f(pos) -> pos
"""
for behavior in self.behaviors:
behavior.behavior.draw_indicators(screen, offset)
[docs] def get_steering(self):
""" Returns the combined steering request of this :py:class:`~BlendedSteering`
Returns
-------
:py:class:`SteeringOutput`
Requested steering
"""
# Output steering for accumulating
steering = kinematic.SteeringOutput()
# Accumulate all accelerations
for behavior in self.behaviors:
steering += behavior.behavior.get_steering() * behavior.weight
# Crop the results and return
steering_lin_accel = steering.linear.length()
if steering_lin_accel > self.character.max_accel:
steering.linear /= steering_lin_accel
steering.linear *= self.character.max_accel
steering_angular_accel = steering.angular
if steering_angular_accel > self.character.max_angular_accel:
steering.angular /= steering_angular_accel
steering.angular *= self.character.max_angular_accel
return steering
[docs]class Flocking(BlendedSteering):
""" :py:class:`~.BlendedSteering` that makes the character move in a flock-like way
This behavior is meant to be used with several characters, they will all
try to **Arive** at the same target location while **Looking Where They're Going**
and keeping **Separated** from eachother.
Parameters
----------
character: :py:class:`~.GameObject`
swarm: iterable(:pgsprite:`Sprite`)
Rest of the entities that conform the Flock
target: :py:class:`~.GameObject`
"""
def __init__(self, character, swarm, target):
behaviors = [
BehaviorAndWeight(kinematic.Separation(character, swarm), 3),
BehaviorAndWeight(kinematic.Arrive(character, target), 1),
BehaviorAndWeight(kinematic.LookWhereYoureGoing(character), 1),
]
super(Flocking, self).__init__(character, behaviors)
[docs]class Wander(BlendedSteering):
""" :py:class:`~.BlendedSteering` that makes the character **Wander** around.
This behaviors is a more complex version of :py:class:`~.kinematic.Wander`
that also tries to **Avoid Obstacles**.
Parameters
----------
character: :py:class:`~.GameObject`
obstacles: iterable(:pgsprite:`Sprite`)
Solid obstacles
"""
def __init__(self, character, obstacles):
behaviors = [
# Wander
BehaviorAndWeight(
kinematic.Wander(character),
weight = 1),
# ObstacleAvoidance
BehaviorAndWeight(
kinematic.ObstacleAvoidance(character, obstacles),
weight = 4),
# LookWhereYoureGoing
BehaviorAndWeight(
kinematic.LookWhereYoureGoing(character),
weight = 2),
]
super(Wander, self).__init__(character, behaviors)
[docs]class Arrive(BlendedSteering):
""" :py:class:`~.BlendedSteering` that makes the character **Arrive** at a target
This is behavior is a more complex version of :py:class:`~.kinematic.Arrive`
that also **Looks Where it's Going** and tries to **Avoid Obstacles**.
Parameters
----------
character: :py:class:`~.GameObject`
target: :py:class:`~.GameObject`
obstacles: iterable(:pgsprite:`Sprite`)
Solid obstacles
"""
def __init__(self, character, target, obstacles, target_radius = None, slow_radius = None):
behaviors = [
# Arrive
BehaviorAndWeight(
kinematic.Arrive(character, target, target_radius, slow_radius),
weight = 1),
# ObstacleAvoidance
BehaviorAndWeight(
kinematic.ObstacleAvoidance(character, obstacles),
weight = 2),
# LookWhereYoureGoing
BehaviorAndWeight(
kinematic.LookWhereYoureGoing(character),
weight = 1),
]
super(Arrive, self).__init__(character, behaviors)
class Surround(BlendedSteering):
""" :py:class:`~.BlendedSteering` that makes the character **Surround** a target
This behavior uses :py:class:`~.FollowPath` and :py:class:`~.Face`
to make the character revolve around the target while looking at it.
Parameters
----------
character: :py:class:`~.GameObject`
target: :py:class:`~.GameObject`
radius: int
The radius of the circle the character will surround the target with
"""
def __init__(self, character, target, radius):
circumpath = path.PathCircumference(lambda: target.position, radius)
behaviors = [
BehaviorAndWeight(
kinematic.FollowPath(character, circumpath),
weight = 2),
BehaviorAndWeight(
kinematic.Face(character, target),
weight = 1),
]
super(Surround, self).__init__(character, behaviors)