from __future__ import annotations
import collections
import warnings
from difflib import get_close_matches
from functools import cached_property
from typing import TYPE_CHECKING, List, Optional, Tuple, Type, Union, cast
import pygame.rect
import miniworlds.actors.actor_appearance_facade as actor_appearance_facade
import miniworlds.actors.actor_base as actor_base
import miniworlds.actors.actor_event_facade as actor_event_facade
import miniworlds.actors.actor_initialization_facade as actor_initialization_facade
import miniworlds.actors.actor_movement_facade as actor_movement_facade
import miniworlds.actors.actor_sensor_facade as actor_sensor_facade
import miniworlds.actors.actor_size_facade as actor_size_facade
import miniworlds.appearances.appearance as appearance
import miniworlds.appearances.costume as costume_mod
import miniworlds.appearances.costumes_manager as costumes_manager
import miniworlds.base.api_validation as api_validation
import miniworlds.base.exceptions as exceptions
import miniworlds.worlds.manager.position_manager as actor_position_manager
import miniworlds.worlds.manager.sensor_manager as sensor_manager
from miniworlds.base.exceptions import (
MissingPositionManager,
Missingworldsensor,
NotImplementedOrRegisteredError,
NoValidWorldPositionError,
NoWorldError,
RegisterError,
)
if TYPE_CHECKING:
import miniworlds.worlds.world as world_mod
[Doku]
class Actor(actor_base.ActorBase):
"""Interactive object placed in a world.
Actors can move, change costumes, detect collisions, send messages, and
react to keyboard, mouse, and lifecycle events.
Examples:
::
Create a basic actor:
from miniworlds import Actor, World
world = World(100, 60)
Actor((10, 10), world=world)
world.run()
Create an actor with a costume:
from miniworlds import Actor, World
world = World(100, 60)
player = Actor((10, 10), world=world)
player.add_costume("images/player.png")
world.run()
"""
actor_count: int = 0
class_image: str = ""
__slots__ = (
# Facades (initialized in __init__)
"_initialization_facade",
# Other instance attributes
"_collision_type",
"_layer",
"_world",
"_static",
# Note: __dict__ is inherited from DirtySprite (via ActorBase),
# so dynamic attributes (including @cached_property) work normally
)
def __init__(
self, position: Optional[Tuple[float, float]] = (0, 0), *args, **kwargs
):
position, args = self._normalize_constructor_arguments(position, args)
# Initialization facade is created directly during __init__
# (not via cached_property) because it's needed immediately
self._initialization_facade = (
actor_initialization_facade.ActorInitializationFacade(self)
)
self._initialization_facade.prepare_core_references(kwargs.get("world"))
self._validate_arguments(position, *args, **kwargs)
self._initialization_facade.initialize_runtime_state(Actor.actor_count + 1)
self._initialization_facade.initialize_world_managers(position)
self._initialization_facade.finalize_sprite_state(kwargs.get("origin"))
Actor.actor_count += 1
@classmethod
def _normalize_constructor_arguments(
cls,
position: Optional[Tuple[float, float]] = (0, 0),
args: tuple = (),
) -> tuple[Optional[Tuple[float, float]], tuple]:
if cls is not Actor and cls.__init__ is not Actor.__init__:
return position, args
if isinstance(position, tuple):
return position, args
if (
len(args) >= 1
and isinstance(position, (int, float))
and isinstance(args[0], (int, float))
):
return (position, args[0]), args[1:]
return position, args
# _initialization_facade is set directly in __init__, not via cached_property
# because it's needed immediately for initialization
@cached_property
def _appearance_facade(self) -> actor_appearance_facade.ActorAppearanceFacade:
return actor_appearance_facade.ActorAppearanceFacade(self)
@cached_property
def _event_facade(self) -> actor_event_facade.ActorEventFacade:
return actor_event_facade.ActorEventFacade(self)
@cached_property
def _sensor_facade(self) -> actor_sensor_facade.ActorSensorFacade:
return actor_sensor_facade.ActorSensorFacade(self)
@cached_property
def _movement_facade(self) -> actor_movement_facade.ActorMovementFacade:
return actor_movement_facade.ActorMovementFacade(self)
@cached_property
def _size_facade(self) -> actor_size_facade.ActorSizeFacade:
return actor_size_facade.ActorSizeFacade(self)
def _validate_arguments(self, position, *args, **kwargs):
if position is None:
raise exceptions.NoValidPositionOnInitException(self, None)
if not isinstance(position, tuple):
raise exceptions.NoValidPositionOnInitException(self, position)
@staticmethod
def _type_name(value) -> str:
return api_validation.type_name(value)
@staticmethod
def _with_try_hint(message: str, example: str | None = None) -> str:
return api_validation.with_try_hint(message, example)
def _is_learning_mode(self) -> bool:
world = getattr(self, "_world", None)
return bool(getattr(world, "learning_mode", False))
@staticmethod
def _student_warn(message: str) -> None:
warnings.warn(message, RuntimeWarning, stacklevel=3)
def _coerce_bool_learning(self, value, parameter_name: str):
return api_validation.coerce_bool_learning(
value,
parameter_name,
self._is_learning_mode(),
self._student_warn,
)
def _coerce_position_learning(self, value, parameter_name: str):
return api_validation.coerce_position_learning(
value,
parameter_name,
self._is_learning_mode(),
self._student_warn,
)
@staticmethod
def _ensure_bool(value, parameter_name: str):
api_validation.ensure_bool(
value,
parameter_name,
Actor._with_try_hint,
f"{parameter_name} = True",
)
@staticmethod
def _ensure_real(value, parameter_name: str):
api_validation.ensure_real(
value,
parameter_name,
Actor._with_try_hint,
f"{parameter_name} = 10",
)
@staticmethod
def _ensure_int(value, parameter_name: str):
api_validation.ensure_int(
value,
parameter_name,
Actor._with_try_hint,
f"{parameter_name} = 1",
)
@classmethod
def _ensure_position_tuple(cls, value, parameter_name: str):
api_validation.ensure_position_tuple(
value,
parameter_name,
cls._ensure_real,
cls._with_try_hint,
)
@classmethod
def _ensure_rect_like(cls, value, parameter_name: str):
api_validation.ensure_rect_like(
value,
parameter_name,
pygame.rect.Rect,
cls._ensure_real,
cls._with_try_hint,
)
@classmethod
def _ensure_color_like(cls, value, parameter_name: str):
api_validation.ensure_color_like(
value,
parameter_name,
cls._ensure_real,
cls._with_try_hint,
)
@classmethod
def _ensure_actor_instance(cls, value, parameter_name: str):
if not isinstance(value, Actor):
raise TypeError(
f"{parameter_name} must be an Actor, got {cls._type_name(value)}: {value!r}"
)
@classmethod
def _ensure_actor_filter(cls, value, parameter_name: str = "actors"):
if value is None:
return
if isinstance(value, str):
return
if isinstance(value, Actor):
return
if isinstance(value, type) and issubclass(value, Actor):
return
raise TypeError(
f"{parameter_name} must be None, Actor instance, Actor class, or class name string, got {cls._type_name(value)}: {value!r}"
)
@classmethod
def _ensure_direction_value(
cls, value, parameter_name: str = "direction", allow_none: bool = False
):
api_validation.DirectionInput.ensure(
value,
parameter_name,
allow_none,
cls._ensure_position_tuple,
cls._with_try_hint,
)
@classmethod
def _normalize_direction_input(cls, value, parameter_name: str = "direction"):
del parameter_name
return api_validation.DirectionInput.normalize(value)
@property
def origin(self):
"""Current origin mode used for size and position operations."""
return self._size_facade.get_origin()
@origin.setter
def origin(self, value: str):
if not isinstance(value, str):
raise TypeError(
f"origin must be str ('center' or 'topleft'), got {type(value).__name__}: {value!r}"
)
self._size_facade.set_origin(value)
[Doku]
def switch_origin(self, value: str):
"""Switch the actor origin while preserving its screen position.
Args:
value: `"center"` or `"topleft"`.
Examples:
::
actor.switch_origin("center")
"""
if not isinstance(value, str):
raise TypeError(
f"value must be str ('center' or 'topleft'), got {type(value).__name__}: {value!r}"
)
self._size_facade.switch_origin(value)
[Doku]
@classmethod
def create_on_world(cls, world):
"""Create an actor on a specific world.
Args:
world: World to place the actor on.
Returns:
The created actor.
Examples:
::
actor = Actor.create_on_world(world)
"""
return cls((0, 0), world)
@property
def collision_type(self) -> str:
"""str: Collision strategy used by this actor.
Values:
`default`: Use the world default.
`tile`: Match actors on the same tile.
`rect`: Check bounding rectangles.
`static-rect`: Check cached bounding rectangles.
`circle`: Check bounding circles.
`mask`: Check overlapping image masks.
Examples:
::
actor.collision_type = "rect"
if actor.detect(wall):
actor.undo_move()
"""
if self._collision_type == "default":
return "mask"
else:
return self._collision_type
@collision_type.setter
def collision_type(self, value: str):
allowed_values = {"default", "tile", "rect", "static-rect", "circle", "mask"}
if not isinstance(value, str):
raise TypeError(
f"collision_type must be str, got {type(value).__name__}: {value!r}"
)
value = value.strip().lower().replace("_", "-")
if value not in allowed_values and self._is_learning_mode():
close_match = get_close_matches(
value, sorted(allowed_values), n=1, cutoff=0.75
)
if close_match:
self._student_warn(
f"Learning mode: converted collision_type from {value!r} to {close_match[0]!r}"
)
value = close_match[0]
if value not in allowed_values:
raise ValueError(
f"collision_type must be one of {sorted(allowed_values)}, got {value!r}"
)
self._collision_type = value
@property
def is_blockable(self):
"""bool: Whether this actor is stopped by blocking actors.
Examples:
::
player.is_blockable = True
wall.is_blocking = True
"""
return self.position_manager.is_blockable
@is_blockable.setter
def is_blockable(self, value: bool):
value = self._coerce_bool_learning(value, "is_blockable")
self._ensure_bool(value, "is_blockable")
self.position_manager.is_blockable = value
@property
def is_blocking(self):
"""bool: Whether this actor blocks blockable actors.
Examples:
::
wall.is_blocking = True
"""
return self.position_manager.is_blocking
@is_blocking.setter
def is_blocking(self, value: bool):
value = self._coerce_bool_learning(value, "is_blocking")
self._ensure_bool(value, "is_blocking")
previous_value = self.position_manager.is_blocking
self.position_manager.is_blocking = value
world = self.world
if previous_value == value or world is None:
return
if hasattr(world, "get_world_connector"):
connector = world.get_world_connector(self)
if connector is not None and hasattr(
connector, "sync_blocking_registration"
):
connector.sync_blocking_registration(previous_value, value)
return
if hasattr(world, "_blocking_actors"):
if value:
world._blocking_actors.add(self)
else:
world._blocking_actors.discard(self)
@property
def layer(self) -> int:
"""int: Drawing layer used when actors overlap.
Higher layers are drawn above lower layers.
Examples:
::
player.layer = 10
"""
return self._layer
@layer.setter
def layer(self, value: int):
self._ensure_int(value, "layer")
self._layer = value
if self in self.world.actors:
self.world.actors.change_layer(
self, value
) # changes layer in DirtySpriteGroup.
@property
def last_position(self) -> Tuple[float, float]:
"""tuple[float, float]: Actor center from the previous frame.
Examples:
::
if actor.center != actor.last_position:
print("actor moved")
"""
return self._size_facade.get_last_center()
@property
def last_direction(self) -> int:
"""Direction value from the previous frame."""
return self._size_facade.get_last_direction()
[Doku]
@classmethod
def from_topleft(cls, topleft_position: Tuple[float, float], *args, **kwargs):
"""Create an actor positioned by its top-left corner.
Args:
topleft_position: Top-left position as `(x, y)`.
Returns:
The created actor.
Examples:
::
actor = Actor.from_topleft((20, 40))
"""
cls._ensure_position_tuple(topleft_position, "topleft_position")
obj = cls(topleft_position, **kwargs) # temp position
obj.origin = "topleft"
return obj
[Doku]
@classmethod
def from_center(cls, center_position: Tuple[float, float], *args, **kwargs):
"""Create an actor positioned by its center.
Args:
center_position: Center position as `(x, y)`.
Returns:
The created actor.
Examples:
::
actor = Actor.from_center((100, 80))
"""
cls._ensure_position_tuple(center_position, "center_position")
obj = cls(center_position, **kwargs) # temp position
obj.origin = "center"
return obj
@property
def costume_count(self) -> int:
"""int: Number of costumes attached to the actor.
Examples:
::
actor.add_costume((255, 0, 0))
print(actor.costume_count)
"""
return self._appearance_facade.costume_count
@property
def is_flipped(self) -> bool:
"""bool: Whether the current costume is mirrored horizontally.
Examples:
::
actor.is_flipped = True
"""
return self._appearance_facade.is_flipped
@is_flipped.setter
def is_flipped(self, value: bool):
value = self._coerce_bool_learning(value, "is_flipped")
self._ensure_bool(value, "is_flipped")
self._appearance_facade.is_flipped = value
[Doku]
def flip_x(self) -> int:
"""Flip the costume horizontally and turn the actor around.
Returns:
The new actor direction.
Examples:
::
if actor.detect_borders():
actor.flip_x()
"""
return self._appearance_facade.flip_x()
[Doku]
def add_costume(
self, source: Union[None, Tuple, str, List, "appearance.Appearance"] = None
) -> "costume_mod.Costume":
"""Add a costume to the actor.
Args:
source: Image path, color tuple, list of image sources, existing
`Costume`, or `None` for an empty costume.
Returns:
The new costume.
Examples:
::
player = Actor((20, 20))
player.add_costume("images/player.png")
player.add_costume((255, 255, 0))
idle = player.add_costume("images/idle.png")
run = player.add_costume("images/run.png")
player.switch_costume(run)
"""
return self._appearance_facade.add_costume(source)
[Doku]
def add_costumes(self, sources: list) -> "costume_mod.Costume":
"""Add several costumes.
Args:
sources: List of image paths, color tuples, or costume sources.
Returns:
The last added costume.
Examples:
::
actor.add_costumes(["images/idle.png", "images/run.png"])
"""
if not isinstance(sources, list):
raise TypeError(
f"sources must be list, got {type(sources).__name__}: {sources!r}"
)
return self._appearance_facade.add_costumes(sources)
[Doku]
def remove_costume(self, source: Union[int, "costume_mod.Costume"] = None):
"""Remove a costume.
Args:
source: Costume index or costume object. If omitted, the current
costume is removed.
Examples:
::
actor.remove_costume()
actor.remove_costume(0)
"""
return self._appearance_facade.remove_costume(source)
[Doku]
def switch_costume(
self, source: Union[int, "appearance.Appearance"]
) -> "costume_mod.Costume":
"""Switch to another costume.
Args:
source: Costume index or costume object.
Returns:
The new active costume.
Examples:
::
actor.add_costume("images/idle.png")
actor.add_costume("images/run.png")
actor.switch_costume(1)
"""
return self._appearance_facade.switch_costume(source)
[Doku]
def set_costume(self, costume: Union[str, tuple, int, "appearance.Appearance"]):
"""Set the current costume from an index, source, or appearance object."""
self._appearance_facade.set_costume(costume)
[Doku]
def reset_costumes(self):
"""Remove all costumes and reset appearance state."""
self._appearance_facade.reset_costumes()
[Doku]
def set_background_color(self, color: tuple):
"""Set a background color behind the actor costume image."""
self._ensure_color_like(color, "color")
self._appearance_facade.set_background_color(color)
[Doku]
def next_costume(self):
"""Switch to the next costume.
Returns:
The new active costume.
Examples:
::
actor.next_costume()
"""
self._appearance_facade.next_costume()
@property
def costume(self) -> costume_mod.Costume:
"""Costume: Current active costume.
Examples:
::
actor.costume.is_rotatable = False
"""
return self._appearance_facade.costume
[Doku]
def has_costume(self) -> bool:
"""Return `True` when the actor currently has a costume."""
return self._appearance_facade.has_costume()
@costume.setter
def costume(self, value):
self._appearance_facade.costume = value
@property
def costumes(self) -> "costumes_manager.CostumesManager":
"""CostumesManager: Manager containing all actor costumes.
Examples:
::
for costume in actor.costumes:
costume.border = 1
"""
return self._appearance_facade.costumes
@property
def orientation(self) -> float:
"""float: Costume orientation offset in degrees.
Examples:
::
actor.orientation = -90
"""
return self._appearance_facade.orientation
@orientation.setter
def orientation(self, value: float):
self._ensure_real(value, "orientation")
self._appearance_facade.orientation = value
@property
def direction(self) -> int:
"""int: Actor direction in Miniworlds/Scratch convention.
Common values are `0` or `"up"`, `90` or `"right"`, `-90` or
`"left"`, and `180` or `"down"`.
Examples:
::
@player.register
def on_key_down(self, key):
if "left" in key:
self.direction = "left"
elif "right" in key:
self.direction = "right"
self.move()
actor.direction = 45
actor.move()
"""
return self._movement_facade.get_direction()
@direction.setter
def direction(self, value: int):
value = self._normalize_direction_input(value, "direction")
self._ensure_direction_value(value, "direction")
self._movement_facade.set_direction(value)
@property
def direction_at_unit_circle(self) -> int:
"""int: Direction in unit-circle convention.
In this convention, `0` points right and `90` points up.
Examples:
::
actor.direction_at_unit_circle = 0
"""
return self._movement_facade.get_direction_at_unit_circle()
@direction_at_unit_circle.setter
def direction_at_unit_circle(self, value: int):
self._ensure_real(value, "direction_at_unit_circle")
self._movement_facade.set_direction_at_unit_circle(value)
[Doku]
def turn_left(self, degrees: int = 90) -> int:
"""Turn the actor left.
Args:
degrees: Degrees to turn.
Returns:
The new direction.
Examples:
::
actor.turn_left()
actor.turn_left(45)
"""
self._ensure_real(degrees, "degrees")
return self._movement_facade.turn_left(degrees)
[Doku]
def turn_right(self, degrees: Union[int, float] = 90):
"""Turn the actor right.
Args:
degrees: Degrees to turn.
Returns:
The new direction.
Examples:
::
actor.turn_right()
actor.turn_right(45)
"""
self._ensure_real(degrees, "degrees")
return self._movement_facade.turn_right(degrees)
[Doku]
def set_direction(self, direction: Union[str, int, float]) -> float:
"""Point the actor in a direction.
Args:
direction: Direction as an angle or string such as `"up"`,
`"right"`, `"down"`, or `"left"`.
Returns:
The new direction.
Examples:
::
actor.set_direction("left")
actor.set_direction(45)
"""
direction = self._normalize_direction_input(direction, "direction")
self._ensure_direction_value(direction, "direction")
return self._movement_facade.set_direction_value(direction)
[Doku]
def point_towards_position(
self, destination: Tuple[float, float]
) -> Union[int, float]:
"""Point the actor toward a position.
Args:
destination: Target position as `(x, y)`.
Returns:
The new direction.
Examples:
::
def act(self):
mouse = self.world.mouse.get_position()
if mouse:
self.point_towards_position(mouse)
self.move()
"""
self._ensure_position_tuple(destination, "destination")
return self._movement_facade.point_towards_position(destination)
[Doku]
def point_towards_actor(self, other: "Actor") -> int:
"""Point the actor toward another actor.
Args:
other: Target actor.
Returns:
The new direction.
Examples:
::
enemy.point_towards_actor(player)
"""
self._ensure_actor_instance(other, "other")
return self._movement_facade.point_towards_actor(other)
@property
def size(self) -> tuple:
"""tuple[float, float]: Actor size as `(width, height)`.
Examples:
::
actor.size = (40, 30)
"""
return self._size_facade.get_size()
@size.setter
def size(self, value: tuple):
self.set_size(value)
[Doku]
def set_size(self, value: tuple):
"""Set actor size.
Args:
value: Size as `(width, height)` or a scalar size.
Examples:
::
actor.set_size((40, 30))
"""
value = self._coerce_position_learning(value, "value")
if isinstance(value, tuple):
self._ensure_position_tuple(value, "value")
else:
self._ensure_real(value, "value")
self._size_facade.set_size(value)
@property
def width(self):
"""float: Actor width in pixels.
Examples:
::
actor.width = 80
"""
return self._size_facade.get_width()
@width.setter
def width(self, value):
self._ensure_real(value, "width")
self._size_facade.set_width(value)
[Doku]
def scale_width(self, value):
"""Scale actor width by a factor.
Args:
value: Scale factor.
Examples:
::
actor.scale_width(1.5)
"""
self._ensure_real(value, "value")
self._size_facade.scale_width(value)
@property
def height(self):
"""float: Actor height in pixels.
Examples:
::
actor.height = 60
"""
return self._size_facade.get_height()
@height.setter
def height(self, value):
self._ensure_real(value, "height")
self._size_facade.set_height(value)
[Doku]
def scale_height(self, value):
"""Scale actor height by a factor.
Args:
value: Scale factor.
Examples:
::
actor.scale_height(0.5)
"""
self._ensure_real(value, "value")
self._size_facade.scale_height(value)
@property
def x(self) -> float:
"""float: Actor x-position."""
return self._movement_facade.get_x()
@x.setter
def x(self, value: float):
self._ensure_real(value, "x")
self._movement_facade.set_x(value)
@property
def y(self) -> float:
"""float: Actor y-position."""
return self._movement_facade.get_y()
@y.setter
def y(self, value: float):
self._ensure_real(value, "y")
self._movement_facade.set_y(value)
@property
def class_name(self) -> str:
"""Class name of this actor instance."""
return self.__class__.__name__
@property
def topleft_x(self) -> float:
"""float: X-coordinate of the actor's top-left corner."""
return self._movement_facade.get_topleft_x()
@property
def topleft_y(self) -> float:
"""float: Y-coordinate of the actor's top-left corner."""
return self._movement_facade.get_topleft_y()
@topleft_x.setter
def topleft_x(self, value: float):
self._ensure_real(value, "topleft_x")
self._movement_facade.set_topleft_x(value)
@topleft_y.setter
def topleft_y(self, value: float):
self._ensure_real(value, "topleft_y")
self._movement_facade.set_topleft_y(value)
@property
def topleft(self) -> Tuple[float, float]:
"""tuple[float, float]: Top-left position in world coordinates.
Examples:
::
actor.topleft = (10, 20)
"""
return self._movement_facade.get_topleft()
@topleft.setter
def topleft(self, value: Tuple[float, float]):
value = self._coerce_position_learning(value, "topleft")
self._ensure_position_tuple(value, "topleft")
self._movement_facade.set_topleft(value)
@property
def local_center(self) -> Tuple[float, float]:
"""tuple[float, float]: Actor center in camera-local coordinates."""
return self._movement_facade.get_local_center()
@property
def center_x(self) -> float:
"""float: X-coordinate of the actor center."""
return self._movement_facade.get_center_x()
@center_x.setter
def center_x(self, value: float):
self._ensure_real(value, "center_x")
self._movement_facade.set_center_x(value)
@property
def center_y(self) -> float:
"""float: Y-coordinate of the actor center."""
return self._movement_facade.get_center_y()
@center_y.setter
def center_y(self, value: float):
self._ensure_real(value, "center_y")
self._movement_facade.set_center_y(value)
@property
def center(self) -> Tuple[float, float]:
"""tuple[float, float]: Center position in world coordinates.
Examples:
::
actor.center = (100, 80)
"""
return self._movement_facade.get_center()
@center.setter
def center(self, value: Tuple[float, float]):
value = self._coerce_position_learning(value, "center")
self._ensure_position_tuple(value, "center")
self._movement_facade.set_center(value)
[Doku]
def move(self, distance: int = 0, direction=None):
"""Move the actor.
Args:
distance: Number of steps to move.
direction: Optional direction. If omitted, the current actor
direction is used.
Returns:
The moved actor.
Examples:
::
actor.direction = "right"
actor.move(5)
"""
direction = self._normalize_direction_input(direction, "direction")
self._ensure_real(distance, "distance")
self._ensure_direction_value(direction, "direction", allow_none=True)
return self._movement_facade.move(distance, direction)
[Doku]
def move_vector(self, vector):
"""Move the actor by a vector.
Args:
vector: Vector-like movement delta.
Returns:
The moved actor.
Examples:
::
actor.move_vector(Vector(2, 0))
"""
return self._movement_facade.move_vector(vector)
[Doku]
def move_back(self, distance):
"""Move the actor backward.
Args:
distance: Number of steps to move backward.
Returns:
The actor itself.
Examples:
::
actor.move()
if actor.detect(Wall):
actor.move_back(5)
"""
self._ensure_real(distance, "distance")
return self._movement_facade.move_back(distance)
[Doku]
def undo_move(self):
"""Undo the last move.
Returns:
The moved actor.
Examples:
::
def on_detecting_wall(self, wall):
self.undo_move()
"""
return self._movement_facade.undo_move()
[Doku]
def move_towards(
self,
target: Union[Tuple[float, float], "Actor"],
distance: float = 1,
):
"""Move toward a target actor or position.
Args:
target: Target actor or position.
distance: Step size.
Returns:
The moved actor.
Examples:
::
enemy.move_towards(player, 2)
enemy.move_towards((100, 80), 2)
"""
if isinstance(target, tuple):
self._ensure_position_tuple(target, "target")
else:
self._ensure_actor_instance(target, "target")
self._ensure_real(distance, "distance")
return self._movement_facade.move_towards(target, distance)
[Doku]
def move_away(
self,
target: Union[Tuple[float, float], "Actor"],
distance: float = 1,
):
"""Move away from a target actor or position.
Args:
target: Target actor or position.
distance: Step size.
Returns:
The moved actor.
Examples:
::
@player.register
def act(self):
if self.detect(enemy):
self.move_away(enemy, 3)
"""
if isinstance(target, tuple):
self._ensure_position_tuple(target, "target")
else:
self._ensure_actor_instance(target, "target")
self._ensure_real(distance, "distance")
return self._movement_facade.move_away(target, distance)
[Doku]
def move_in_direction(
self,
direction: Union[int, str, Tuple[float, float]],
distance=1,
):
"""Move in a direction.
`direction` may be a number, a direction string, or a target position.
Args:
direction: Direction angle, direction name, or target position.
distance: Number of steps to move.
Returns:
The moved actor.
Examples:
::
@player.register
def on_key_pressed(self, key):
if "right" in key:
self.move_in_direction("right", 5)
if "up" in key:
self.move_in_direction("up", 5)
player.move_in_direction((100, 80), 2)
"""
direction = self._normalize_direction_input(direction, "direction")
try:
self._ensure_direction_value(direction, "direction")
except TypeError as exc:
raise exceptions.MoveInDirectionTypeError(direction) from exc
self._ensure_real(distance, "distance")
return self._movement_facade.move_in_direction(direction, distance)
[Doku]
def move_to(self, position: Tuple[float, float]):
"""Move the actor to a world position.
Args:
position: Target position as `(x, y)`.
Returns:
The moved actor.
Examples:
::
@player.register
def on_clicked_left(self, position):
self.move_to(position)
"""
position = self._coerce_position_learning(position, "position")
self._ensure_position_tuple(position, "position")
return self._movement_facade.move_to(position)
[Doku]
def go_to(self, position: Tuple[float, float]):
"""Student-friendly alias for `move_to(position)`."""
return self.move_to(position)
[Doku]
def move_forward(self, distance: int = 0):
"""Student-friendly alias for `move(distance)`."""
return self.move(distance)
[Doku]
def face(self, direction: Union[str, int, float]):
"""Student-friendly alias for `set_direction(direction)`."""
return self.set_direction(direction)
[Doku]
def turn(self, degrees: Union[int, float] = 90):
"""Student-friendly alias for `turn_right(degrees)`."""
return self.turn_right(degrees)
[Doku]
def touching(self, *args, **kwargs):
"""Student-friendly alias for `detect(...)`."""
return self.detect(*args, **kwargs)
[Doku]
def touching_all(self, *args, **kwargs):
"""Student-friendly alias for `detect_all(...)`."""
return self.detect_all(*args, **kwargs)
[Doku]
def remove(self, kill=True) -> collections.defaultdict:
"""Remove this actor from its world.
Args:
kill: Whether to remove the underlying pygame sprite too.
Returns:
Removed actor data from the world connector.
Examples:
::
coin = actor.detect(Coin)
if coin:
coin.remove()
"""
kill = self._coerce_bool_learning(kill, "kill")
self._ensure_bool(kill, "kill")
return self.world.get_world_connector(self).remove_actor_from_world(kill=kill)
[Doku]
def before_remove(self):
"""Hook called immediately before the actor is removed from the world."""
pass
@property
def is_rotatable(self) -> bool:
"""bool: Whether the costume rotates with the actor direction.
The actor direction still changes when this is `False`; only the
rendered costume stays unrotated.
Examples:
::
actor.is_rotatable = False
actor.direction = "right"
"""
return self.costume.is_rotatable
@is_rotatable.setter
def is_rotatable(self, value: bool):
value = self._coerce_bool_learning(value, "is_rotatable")
self._ensure_bool(value, "is_rotatable")
self.costume.is_rotatable = value
[Doku]
def bounce_from_border(self, borders: List[str]) -> Actor:
"""Bounce the actor away from world borders.
Args:
borders: Border names such as `"left"` or `"top"`.
Returns:
The actor itself.
Examples:
::
@ball.register
def act(self):
self.move()
borders = self.detect_borders()
if borders:
self.bounce_from_border(borders)
"""
if not isinstance(borders, list):
raise TypeError(
f"borders must be list[str], got {type(borders).__name__}: {borders!r}"
)
invalid_border = next(
(border for border in borders if not isinstance(border, str)), None
)
if invalid_border is not None:
raise TypeError(
f"borders must contain only str values, got {type(invalid_border).__name__}: {invalid_border!r}"
)
return self.position_manager.bounce_from_border(borders)
[Doku]
def detect_all(
self,
actors: Union[str, "Actor", Type["Actor"]] = None,
direction: int = 0,
distance: int = 0,
) -> List["Actor"]:
"""Return all actors detected at an offset from this actor.
Args:
actors: Optional actor filter; use `None` for all actors.
direction: Direction to check.
distance: Distance from the actor center.
Returns:
All matching actors found by the sensor.
Examples:
::
coins = player.detect_all(Coin)
for coin in coins:
coin.remove()
walls_ahead = player.detect_all(Wall, direction="right", distance=10)
"""
self._ensure_actor_filter(actors, "actors")
self._ensure_real(direction, "direction")
self._ensure_real(distance, "distance")
return self._sensor_facade.detect_all(actors, direction, distance)
[Doku]
def detect(self, *args, **kwargs) -> Union["Actor", None]:
"""Return the first detected actor.
Args:
args: Positional arguments forwarded to `detect_all()`.
kwargs: Keyword arguments forwarded to `detect_all()`.
Returns:
First matching actor, or `None`.
Examples:
::
wall = player.detect(Wall)
if wall:
player.undo_move()
coin = player.detect(Coin)
if coin:
player.score += 1
coin.remove()
"""
return self._sensor_facade.detect(*args, **kwargs)
[Doku]
def detect_borders(
self,
distance: int = 0,
) -> List:
"""Return borders detected near the actor.
Args:
distance: Distance in front of the actor to check.
Returns:
List of border names such as `"left"`, `"right"`, `"top"`, or
`"bottom"`.
Examples:
::
@ball.register
def act(self):
self.move()
borders = self.detect_borders()
if borders:
self.bounce_from_border(borders)
"""
self._ensure_real(distance, "distance")
return self._sensor_facade.detect_borders(distance)
[Doku]
def detect_left_border(self) -> bool:
"""Return whether the actor touches the left border.
Returns:
`True` if the left border is detected.
"""
return self._sensor_facade.detect_left_border()
[Doku]
def detecting_left_border(self) -> bool:
"""Return whether the actor touches the left border.
Returns:
`True` if the left border is detected.
"""
return self.detect_left_border()
[Doku]
def detect_right_border(self) -> bool:
"""Return whether the actor touches the right border.
Returns:
`True` if the right border is detected.
"""
return self._sensor_facade.detect_right_border()
[Doku]
def detecting_right_border(self) -> bool:
"""Return whether the actor touches the right border.
Returns:
`True` if the right border is detected.
"""
return self.detect_right_border()
[Doku]
def detect_top_border(self) -> bool:
"""Return whether the actor touches the top border.
Returns:
`True` if the top border is detected.
"""
return self._sensor_facade.detect_top_border()
[Doku]
def detecting_top_border(self) -> bool:
"""Return whether the actor touches the top border.
Returns:
`True` if the top border is detected.
"""
return self.detect_top_border()
[Doku]
def detecting_bottom_border(self) -> bool:
"""Return whether the actor touches the bottom border.
Returns:
`True` if the bottom border is detected.
"""
return self._sensor_facade.detecting_bottom_border()
[Doku]
def detect_bottom_border(self) -> bool:
"""Return whether the actor touches the bottom border.
Returns:
`True` if the bottom border is detected.
"""
return self.detecting_bottom_border()
[Doku]
def detect_color(self, color: Tuple = None) -> bool:
"""Return whether the actor detects a background color.
Args:
color: Optional RGB/RGBA color tuple. If omitted, any color is
detected.
Returns:
`True` if the color is detected at the actor center.
Examples:
::
@player.register
def act(self):
if self.detect_color((0, 0, 0)):
self.remove()
"""
if color is not None:
self._ensure_color_like(color, "color")
return self._sensor_facade.detect_color(color)
[Doku]
def detect_color_at(
self, direction: int = None, distance: int = 0
) -> Union[Tuple, List]:
"""Return background colors at an offset from the actor.
Args:
direction: Direction to check; omit it to use the actor position.
distance: Distance from the actor center.
Returns:
Color or colors found by the sensor.
Examples:
::
color = actor.detect_color_at("right", 5)
"""
direction = self._normalize_direction_input(direction, "direction")
self._ensure_direction_value(direction, "direction", allow_none=True)
self._ensure_real(distance, "distance")
return self._sensor_facade.detect_color_at(direction, distance)
[Doku]
def detect_actors_at(self, direction=None, distance=0, actors=None) -> list:
"""Return all actors at an offset from this actor.
Args:
direction: Direction to check; omit it to use the actor position.
distance: Distance from the actor center.
actors: Optional actor filter.
Returns:
List of matching actors.
Examples:
::
@player.register
def on_key_pressed_right(self):
if not self.detect_actors_at("right", 20, Wall):
self.move_in_direction("right", 5)
"""
direction = self._normalize_direction_input(direction, "direction")
self._ensure_direction_value(direction, "direction", allow_none=True)
self._ensure_real(distance, "distance")
self._ensure_actor_filter(actors, "actors")
return self._sensor_facade.detect_actors_at(direction, distance, actors)
[Doku]
def detect_actor_at(self, direction=None, distance=0, actors=None) -> "Actor":
"""Return the first actor at an offset from this actor.
Args:
direction: Direction to check.
distance: Distance from the actor center.
actors: Optional actor filter.
Returns:
First matching actor, or `None`.
Examples:
::
wall = player.detect_actor_at("right", 20, Wall)
if wall:
player.undo_move()
"""
direction = self._normalize_direction_input(direction, "direction")
self._ensure_direction_value(direction, "direction", allow_none=True)
self._ensure_real(distance, "distance")
self._ensure_actor_filter(actors, "actors")
return self._sensor_facade.detect_actor_at(direction, distance, actors)
[Doku]
def detect_actors_in_front(
self,
actors=None,
distance=1,
) -> list:
"""Return all actors directly in front of this actor.
Args:
actors: Optional actor filter.
distance: Distance in front of the actor.
Returns:
Matching actors.
Examples:
::
enemies = player.detect_actors_in_front(Enemy)
for enemy in enemies:
player.move_away(enemy, 3)
"""
self._ensure_actor_filter(actors, "actors")
self._ensure_real(distance, "distance")
return self._sensor_facade.detect_actors_in_front(actors, distance)
[Doku]
def detect_actor_in_front(
self,
actors=None,
distance=1,
) -> "Actor":
"""Return the first actor directly in front.
Args:
actors: Optional actor filter.
distance: Distance in front of the actor.
Returns:
First matching actor, or `None`.
Examples:
::
wall = player.detect_actor_in_front(Wall)
if wall:
player.turn_left()
"""
self._ensure_actor_filter(actors, "actors")
self._ensure_real(distance, "distance")
return self._sensor_facade.detect_actor_in_front(actors, distance)
[Doku]
def detect_point(self, position: Tuple[float, float]) -> bool:
"""Return whether the actor overlaps a world point.
Args:
position: World position as `(x, y)`.
Returns:
`True` if the actor overlaps the point.
Examples:
::
if actor.detect_point((100, 80)):
actor.hide()
"""
self._ensure_position_tuple(position, "position")
return self._sensor_facade.detect_point(position)
[Doku]
def detect_pixel(self, position: Tuple[float, float]) -> bool:
"""Return whether the actor overlaps a screen pixel.
Args:
position: Pixel position as `(x, y)`.
Returns:
`True` if the actor overlaps the pixel.
Examples:
::
@actor.register
def on_mouse_left(self, position):
if self.detect_pixel(position):
self.hide()
"""
self._ensure_position_tuple(position, "position")
return self._sensor_facade.detect_pixel(position)
[Doku]
def detect_rect(self, rect: Union[Tuple, pygame.rect.Rect]):
"""Return whether the actor overlaps a rectangle.
Args:
rect: Rectangle as `(x, y, width, height)` or `pygame.Rect`.
Returns:
`True` if the actor overlaps the rectangle.
"""
self._ensure_rect_like(rect, "rect")
return self._sensor_facade.detect_rect(rect)
[Doku]
def is_inside_world(self):
"""Return whether the actor is completely inside the world.
Returns:
`True` if the entire actor rectangle is inside the world.
"""
return self._sensor_facade.is_inside_world()
[Doku]
def bounce_from_actor(self, other: "Actor"):
"""Reflect movement direction after colliding with another actor.
Args:
other: Actor to bounce from.
Examples:
::
other = ball.detect()
if other:
ball.bounce_from_actor(other)
"""
self._ensure_actor_instance(other, "other")
self._sensor_facade.bounce_from_actor(other)
[Doku]
def animate(self, speed: int = 10):
"""Animate the current costume.
Args:
speed: Frames between animation steps.
Examples:
::
actor.animate(speed=5)
"""
self._ensure_int(speed, "speed")
self.costume_manager.animate(speed)
[Doku]
def animate_costume(self, costume: "costume_mod.Costume", speed: int = 10):
"""Animate a specific costume.
Args:
costume: Costume to animate.
speed: Frames between animation steps.
Examples:
::
actor.animate_costume(run_costume, speed=5)
"""
if costume is None:
raise TypeError("costume must not be None")
self._ensure_int(speed, "speed")
self.costume_manager.animate_costume(costume, speed)
[Doku]
def animate_loop(self, speed: int = 10):
"""Animate the current costume in a loop.
Args:
speed: Frames between animation steps.
Examples:
::
player.costume.add_images(["images/walk1.png", "images/walk2.png"])
player.animate_loop(speed=8)
"""
self._ensure_int(speed, "speed")
self.costume.loop = True
self.costume_manager.animate(speed)
[Doku]
def stop_animation(self):
"""Stop the current costume animation.
Examples:
::
actor.animate_loop()
actor.stop_animation()
"""
self.costume.is_animated = False
[Doku]
def send_message(self, message: str):
"""Send a message through the world event system.
Args:
message: Message name.
Examples:
::
player.send_message("hit")
@enemy.register
def on_message(self, message):
if message == "hit":
self.remove()
"""
if not isinstance(message, str):
raise TypeError(
f"message must be str, got {type(message).__name__}: {message!r}"
)
self._event_facade.send_message(message)
[Doku]
def on_key_down(self, key: list):
"""Called once when a key is pressed.
Register `on_key_down_<letter>` (for example `on_key_down_a`) if you
want to react to a specific letter only.
For arrow keys use `on_key_down_left`, `on_key_down_right`,
`on_key_down_up`, or `on_key_down_down`.
Args:
key: List of key name variants, for example `["A", "a"]` or
`["left"]`.
Examples:
::
@player.register
def on_key_down(self, key):
if "left" in key:
self.direction = "left"
elif "right" in key:
self.direction = "right"
self.move()
@player.register
def on_key_down_space(self):
self.send_message("jump")
"""
raise NotImplementedOrRegisteredError(self.on_key_down)
[Doku]
def on_key_pressed(self, key: list):
"""Called repeatedly every frame while a key is held down.
This is the right choice for smooth, continuous movement. Like
`on_key_down`, this event supports per-letter handlers such as
`on_key_pressed_w` or `on_key_pressed_left`.
Args:
key: List of key name variants currently held, for example
`["W", "w"]` or `["up"]`.
Examples:
::
@player.register
def on_key_pressed(self, key):
if "left" in key:
self.x -= 3
elif "right" in key:
self.x += 3
"""
raise NotImplementedOrRegisteredError(self.on_key_pressed)
[Doku]
def on_key_up(self, key):
"""Called once when a previously pressed key is released.
Args:
key: List of key name variants, same format as in `on_key_down()`.
Examples:
::
@player.register
def on_key_up(self, key):
if "space" in key:
self.stop_animation()
"""
raise NotImplementedOrRegisteredError(self.on_key_up)
[Doku]
def on_mouse_over(self, position):
"""Called when the mouse cursor enters or moves over the actor area.
Args:
position: Current mouse position as `(x, y)`.
Examples:
::
@actor.register
def on_mouse_over(self, position):
self.costume.transparency = 100
"""
raise NotImplementedOrRegisteredError(self.on_mouse_over)
[Doku]
def on_mouse_leave(self, position):
"""Called when the mouse cursor leaves the actor area.
Args:
position: The mouse position when it left the actor.
Examples:
::
@actor.register
def on_mouse_leave(self, position):
self.costume.transparency = 0
"""
raise NotImplementedOrRegisteredError(self.on_mouse_leave)
[Doku]
def on_mouse_left_down(self, position: tuple):
"""Called when the left mouse button is pressed down.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_left_down)
[Doku]
def on_mouse_right_down(self, position: tuple):
"""Called when the right mouse button is pressed down.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_right_down)
[Doku]
def on_mouse_left(self, position: tuple):
"""Called when the left mouse button is clicked.
The event is triggered for the click itself, independent of whether
the click happened on the actor. Use `detect_pixel(position)` if you
only want clicks on the actor body.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_left)
[Doku]
def on_mouse_right(self, position: tuple):
"""Called when the right mouse button is clicked.
Use `detect_pixel(position)` if you only want clicks on the actor
body.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_right)
[Doku]
def on_mouse_motion(self, position: tuple):
"""Called when the mouse moves.
The event is triggered for movement events in general. Use
`detect_pixel(position)` if you only want motion over the actor body.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_motion)
[Doku]
def on_mouse_left_released(self, position: tuple):
"""Called when the left mouse button is released.
Args:
position: Current mouse position as `(x, y)`.
Examples:
::
@actor.register
def on_mouse_left_released(self, position):
self.center = position
"""
raise NotImplementedOrRegisteredError(self.on_mouse_left_released)
[Doku]
def on_mouse_right_released(self, position: tuple):
"""Called when the right mouse button is released.
Args:
position: Current mouse position as `(x, y)`.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_right_released)
[Doku]
def on_clicked_left(self, position: tuple):
"""Called when the actor is clicked with the left mouse button.
Args:
position: Current mouse position as `(x, y)`.
Examples:
::
@actor.register
def on_clicked_left(self, position):
self.hide()
"""
raise NotImplementedOrRegisteredError(self.on_clicked_left)
[Doku]
def on_clicked_right(self, position):
"""Called when the actor is clicked with the right mouse button.
Args:
position: Current mouse position as `(x, y)`.
Examples:
::
@actor.register
def on_clicked_right(self, position):
self.remove()
"""
raise NotImplementedOrRegisteredError(self.on_clicked_right)
[Doku]
def on_detecting_world(self):
"""Called when the actor is inside the world.
Examples:
::
@player.register
def on_detecting_world(self):
self.move()
"""
raise NotImplementedOrRegisteredError(self.on_detecting_world)
[Doku]
def on_not_detecting_world(self):
"""Called when the actor is **not** touching the world (i.e. outside world bounds).
Useful to remove actors that fly off-screen.
Examples:
::
@rocket.register
def on_not_detecting_world(self):
self.remove()
"""
raise NotImplementedOrRegisteredError(self.on_not_detecting_world)
[Doku]
def on_detecting_not_on_world(self):
"""Alias for ``on_not_detecting_world``.
Both names are accepted so older teaching material and newer examples
can use the wording that reads best in context.
Examples:
::
@actor.register
def on_detecting_not_on_world(self):
self.remove()
"""
raise NotImplementedOrRegisteredError(self.on_detecting_not_on_world)
[Doku]
def on_detecting_actor(self, actor: "Actor"):
"""Called when this actor detects another actor.
Args:
actor: Detected actor.
Examples:
::
@player.register
def on_detecting_actor(self, actor):
if isinstance(actor, Coin):
actor.remove()
"""
raise NotImplementedOrRegisteredError(self.on_detecting_actor)
[Doku]
def on_detecting_borders(self, borders: List[str]):
"""Called when the actor detects one or more world borders.
Args:
borders: Border names, for example `["left", "top"]`.
Examples:
::
@player.register
def on_detecting_borders(self, borders):
self.bounce_from_border(borders)
"""
raise NotImplementedOrRegisteredError(self.on_detecting_borders)
@property
def static(self):
"""Should actor react to events?
You can turn this option off for additional performance boost.
"""
return self._static
@static.setter
def static(self, value):
_world_connector = self.world.get_world_connector(self)
_world_connector.set_static(value)
@property
def fill_color(self):
"""Fill color of the actor as RGBA tuple.
Setting this value also enables filling on the costume.
Notes:
- Alias: `Actor.color`
- Filling an image costume replaces the visible image content.
"""
return self._appearance_facade.fill_color
@fill_color.setter
def fill_color(self, value):
self._ensure_color_like(value, "fill_color")
self._appearance_facade.fill_color = value
# Alias
color = fill_color
[Doku]
def fill(self, value):
"""Set fill color for borders and lines"""
self._ensure_color_like(value, "value")
self._appearance_facade.fill(value)
@property
def is_filled(self):
"""Is actor filled with color?"""
return self._appearance_facade.is_filled
@is_filled.setter
def is_filled(self, value):
if isinstance(value, tuple):
self._ensure_color_like(value, "is_filled")
else:
value = self._coerce_bool_learning(value, "is_filled")
self._ensure_bool(value, "is_filled")
self._appearance_facade.is_filled = value
@property
def border_color(self):
"""Border color as RGBA tuple.
Notes:
- Set `Actor.border` to a value greater than `0` for a visible border.
- Alias: `Actor.stroke_color`.
"""
return self._appearance_facade.border_color
@border_color.setter
def border_color(self, value):
self._ensure_color_like(value, "border_color")
self._appearance_facade.border_color = value
# Alias
stroke_color = border_color
@property
def border(self):
"""Border width of the actor.
A value of `0` means no border.
Notes:
You can also configure borders via `costume.border` or
`world.default_border`.
"""
return self._appearance_facade.border
@border.setter
def border(self, value):
if value is None:
self._appearance_facade.border = None
return
self._ensure_real(value, "border")
self._appearance_facade.border = value
@property
def visible(self):
"""Whether the actor is currently visible."""
return self._appearance_facade.visible
@visible.setter
def visible(self, value):
value = self._coerce_bool_learning(value, "visible")
self._ensure_bool(value, "visible")
self._appearance_facade.visible = value
[Doku]
def hide(self):
"""Hides a actor (the actor will be invisible)"""
self._appearance_facade.hide()
[Doku]
def show(self):
"""Displays a actor ( an invisible actor will be visible)"""
self._appearance_facade.show()
[Doku]
def register_sensor(self, *args, **kwargs):
"""This method is used for the @register_sensor decorator."""
return self._event_facade.register_sensor(*args, **kwargs)
[Doku]
def get_local_rect(self) -> pygame.Rect:
"""Return actor rect in camera-local coordinates."""
local_rect = self.position_manager.get_local_rect()
return local_rect
@property
def world(self):
"""World this actor belongs to."""
return self._world
@world.setter
def world(self, new_world):
self.set_world(new_world)
[Doku]
def set_world(self, new_world: "world_mod.World") -> "Actor":
"""Move the actor to another world and return the actor."""
if new_world is None:
raise TypeError("new_world must not be None")
if not hasattr(new_world, "get_world_connector"):
raise TypeError(
f"new_world must provide get_world_connector(actor), got {type(new_world).__name__}: {new_world!r}"
)
return self._event_facade.set_world(new_world)
[Doku]
def new_costume(self):
"""Create and attach a new empty costume to this actor."""
return self._appearance_facade.new_costume()
@property
def image(self) -> pygame.Surface:
"""
The image of the actor:
.. warning::
Warning: You should not directly draw on the image (with pygame functions)
as the image will be reloaded during animations
"""
return self._appearance_facade.image
@property
def position(self) -> Tuple[float, float]:
"""The position of the actor as Position(x, y)"""
return self._movement_facade.get_position()
@position.setter
def position(self, value: Tuple[float, float]):
self.set_position(value)
[Doku]
def set_position(self, value: Tuple[float, float]):
"""Set actor position in world coordinates."""
if value is None:
return
value = self._coerce_position_learning(value, "value")
self._ensure_position_tuple(value, "value")
self._movement_facade.set_position(value)
[Doku]
def get_distance_to(self, obj: Union["Actor", Tuple[float, float]]) -> float:
"""Gets the distance to another actor or a position
Args:
obj: Actor or Position
Returns:
float: The distance between actor (measured from actor.center) to actor or position.
"""
return self._sensor_facade.get_distance_to(obj)
[Doku]
def on_shape_change(self):
"""Hook called when actor shape-related properties change."""
pass