gale.ai.perception module

This file contains a perception/vision-cone system for stealth-style games: a guard’s cone of vision, split into a near zone (instant detection) and a far zone (slower, partial detection), together with a simple line-of-sight check against rectangular obstacles and a Perception class that turns sightings into an alert level posted on a Blackboard for a behavior tree to react to.

Author: Alejandro Mujica (aledrums@gmail.com)

gale.ai.perception.has_line_of_sight(origin, target, obstacles=None)[source]

Check whether the straight segment from origin to target is not blocked by any of the given obstacles.

Parameters:
  • origin (Vector2) – Point the sight line starts from.

  • target (Vector2) – Point the sight line is aimed at.

  • obstacles (Sequence[Rect] | None) – Axis-aligned rectangles that block vision. The default value is None, meaning nothing blocks vision.

Returns:

Whether target is visible from origin, i.e. the segment does not cross any obstacle.

Return type:

bool

class gale.ai.perception.VisionCone(origin, range_near=100, range_far=300, half_angle=0.5235987755982988, orientation=0)[source]

Bases: object

Models a guard’s field of view as a cone: an origin, a facing direction (orientation, in radians), and a half_angle spreading on both sides of that direction. Detection is split into two zones: range_near, within which a visible target is spotted right away, and range_far, up to which a visible target is only partially noticed (see awareness_gain), building up over time instead of being instant.

The cone’s pose can be given either as a plain (position, orientation) pair or as an object exposing .position/.orientation (such as a Kinematic), and it is re-read on every call so the cone always follows its owner as it moves and turns.

Usage example:

guard = Kinematic(x=100, y=100, orientation=0) cone = VisionCone(guard, range_near=80, range_far=250, half_angle=math.radians(30)) if cone.can_see_point(player.position, obstacles=walls):

Parameters:
  • origin (Any | Vector2)

  • range_near (float)

  • range_far (float)

  • half_angle (float)

  • orientation (float)

can_see_point(point, obstacles=None)[source]
Parameters:
  • point (Vector2) – The point to test.

  • obstacles (Sequence[Rect] | None) – Axis-aligned rectangles that block vision. The default value is None, meaning nothing blocks vision.

Returns:

Whether point lies within range_far, within half_angle of the facing direction, and (if obstacles are given) has a clear line of sight.

Return type:

bool

awareness_gain(point, dt, obstacles=None)[source]

Compute how much awareness should build up this tick for a target at point: full speed (dt per second, i.e. 1.0 * dt) within range_near, a fraction of that scaled by how far into the near..far band the target is when beyond range_near but still within range_far, and zero if point is not visible at all.

Parameters:
  • point (Vector2) – Position of the potential target.

  • dt (float) – Time elapsed (in seconds) since the last update.

  • obstacles (Sequence[Rect] | None) – Axis-aligned rectangles that block vision. The default value is None, meaning nothing blocks vision.

Returns:

The amount of awareness (in the same 0..1 scale used by Perception) to accumulate for this tick.

Return type:

float

class gale.ai.perception.AlertLevel(*values)[source]

Bases: Enum

How aware a guard currently is of a target.

UNAWARE = 'unaware'
SUSPICIOUS = 'suspicious'
ALERTED = 'alerted'
class gale.ai.perception.Perception(vision_cones, blackboard=None, decay_rate=0.2, suspicious_threshold=0.3, alerted_threshold=1.0)[source]

Bases: object

Layers an alert-level state machine on top of one or more VisionCones, turning raw sightings into a 0..1 “awareness” value for a single tracked target (a stealth guard tracking the player being the primary use case) and posting the resulting AlertLevel, together with the last known target position, onto a Blackboard so a behavior tree’s Condition/Action nodes can react to it without knowing anything about vision cones.

Blackboard keys written by update():

  • “alert_level”: the current AlertLevel.

  • “last_known_target_position”: the target’s position the last time it was seen (kept while SUSPICIOUS or ALERTED, untouched once the guard goes back to UNAWARE).

  • “awareness”: the raw 0..1 awareness float.

Usage example:

blackboard = Blackboard() cone = VisionCone(guard, range_near=80, range_far=250, half_angle=math.radians(30)) perception = Perception([cone], blackboard)

# In the game loop: perception.update(dt, player.position, obstacles=walls) if blackboard.get(“alert_level”) == AlertLevel.ALERTED:

Parameters:
  • vision_cones (Sequence[VisionCone])

  • blackboard (Blackboard | None)

  • decay_rate (float)

  • suspicious_threshold (float)

  • alerted_threshold (float)

vision_cones: Sequence[VisionCone]
blackboard: Blackboard
decay_rate: float
suspicious_threshold: float
alerted_threshold: float
awareness: float
update(dt, target_point, obstacles=None)[source]

Look for target_point through every vision cone, accumulate or decay awareness accordingly, and post the resulting state onto the blackboard.

Parameters:
  • dt (float) – Time elapsed (in seconds) since the last update.

  • target_point (Vector2) – Current position of the tracked target.

  • obstacles (Sequence[Rect] | None) – Axis-aligned rectangles that block vision. The default value is None, meaning nothing blocks vision.

Returns:

The alert level after this update.

Return type:

AlertLevel