from __future__ import annotations
from typing import Union, List, Tuple, Optional, cast, Type
import pygame.rect
import collections
import miniworlds.base.app as app
import miniworlds.appearances.appearance as appearance
import miniworlds.appearances.costume as costume_mod
import miniworlds.appearances.costumes_manager as costumes_manager
import miniworlds.worlds.world as world_mod
import miniworlds.base.dialogs as ask
import miniworlds.worlds.manager.sensor_manager as sensor_manager
import miniworlds.worlds.manager.position_manager as actor_position_manager
import miniworlds.tools.actor_inspection as actor_inspection
import miniworlds.base.exceptions as exceptions
from miniworlds.base.exceptions import (
MiniworldsError,
NotImplementedOrRegisteredError,
NoWorldError,
RegisterError,
NoValidWorldPositionError,
Missingworldsensor,
MissingPositionManager,
)
class Meta(type):
"""
Why do we need a metaclass:
the token should be added to a world, after __init__, even
if __init__ was overwritten.
"""
def __call__(
cls, position: Optional[Tuple[float, float]] = (0, 0), *args, **kwargs
):
instance = type.__call__(cls, position, *args, **kwargs) # create a new Token
world_connector = instance.world.get_world_connector(instance)
world_connector.add_to_world(position)
return instance
[docs]
class Actor(pygame.sprite.DirtySprite, metaclass=Meta):
"""Actors are objects on your world. Actors can move around the world and have sensors to detect other actors.
The appearance of a actor is determined by its costume.
Examples:
Create a actor:
.. code-block:: python
from miniworlds import *
world = World()
world.size = (100,60)
Actor(position=(10, 10))
world.run()
Output:
.. image:: ../_images/actor1.png
:width: 100px
:alt: Create a actor
Create a actor with an image:
.. code-block:: python
from miniworlds import *
world = World(100,60)
actor = Actor((10, 10))
actor.add_costume("images/player.png")
world.run()
Output:
.. image:: ../_images/actor2.png
:width: 100px
:alt: Create a Actor with image
.. code-block:: python
import miniworlds
class MyActor(miniworlds.Actor):
def on_setup(self):
self.add_costume("images/player.png")
world = World(100,60)
my_actor = MyActor(position = (40,130))
world.run()
Output:
.. image:: ../_images/actor1.png
:width: 100px
:alt: Create a actor
Create a Actor at current mouse position:
.. code-block:: python
from miniworlds import *
world = World()
@world.register
def act(self):
Actor(self.get_mouse_position())
world.run()
.. image:: ../_images/actor3.png
:width: 100px
:alt: Create a actor at mouse position
See Also:
* See: :doc:`Actor <../api/actor>`
* See: :doc:`Shapes <../api/actor.shape>`
* See: :doc:`TextActors and NumberActors <../api/actor.textactor>`
"""
actor_count: int = 0
class_image: str = ""
def __init__(
self, position: Optional[Tuple[float, float]] = (0, 0), *args, **kwargs
):
self._dirty = 0 # must be set before calling __init__ of DirtySprite.
if "world" not in kwargs:
self._world: "world_mod.World" = app.App.running_world
else:
self.world = kwargs["world"]
self._was_setup = False
self._sensor_manager: "sensor_manager.SensorManager" = None
self._position_manager: "actor_position_manager.Positionmanager" = None
self._costume_manager: "costumes_manager.CostumesManager" = None
super().__init__()
self._validate_arguments(position, *args, **kwargs)
self._collision_type: str = "mask"
self.is_display_initialized: bool = False
self._layer: int = 0
self._inner = 0
self._size = (0, 0)
self._static = False
self.children = pygame.sprite.LayeredDirty()
self.actor_id: int = Actor.actor_count + 1
self._has_position_manager = False
self._has_sensor_manager = False
self._has_costume_manager = False
self._is_acting: bool = True # is act method called?
self._is_deleted = False
self.is_focusable = False
self.has_focus = False
self._parent = None # For actors in container
self.children: List["Actor"] = []
try:
self.world.get_world_connector(self).init_managers(position)
except AttributeError as e:
print("error", e)
raise AttributeError(
"Actor could not be created on a World - Did you created a world instance before?"
)
if not self.world:
raise NoWorldError()
pygame.sprite.DirtySprite.__init__(self)
Actor.actor_count += 1
self.speed: int = 1
self.ask: "ask.Ask" = ask.Ask(self.world)
self._dirty = 1
self.origin = kwargs.get("origin") if kwargs.get("origin") else "center"
self._visible = True
def _validate_arguments(self, position, *args, **kwargs):
if not isinstance(position, tuple):
raise exceptions.NoValidPositionOnInitException(self, position)
@property
def origin(self):
return self.position_manager.origin
@origin.setter
def origin(self, value: str):
self.position_manager.origin = value
[docs]
def switch_origin(self, value: str):
self.position_manager.switch_origin(value)
[docs]
@classmethod
def create_on_world(cls, world):
"""Creates a actor to a specific world
overwritten in subclasses
"""
return cls((0, 0), world)
@property
def collision_type(self) -> str:
"""collision_type specifies how collisions should be checked:
* `default`: tile for Tiledworlds, 'mask' for Pixelworlds
* `tile`: Are actors on the same tile? (only TiledWorld)
* `rect`: Are actors colliding when checking their bounding - boxes? (Only PixelWorld)
* `static-rect`: Are actors colliding when checking circle with radius = bounding-box-radius.(Only PixelWorld)
* `circle`: Are actors colliding when checking circle with radius = bounding-box-radius.(Only PixelWorld)
* `mask`: Are actors colliding when checking if their image masks are overlapping.
"""
if self._collision_type == "default":
return "mask"
else:
return self._collision_type
@collision_type.setter
def collision_type(self, value: str):
self._collision_type = value
@property
def is_blockable(self, value):
"""
A actor with the property ``is_blockable`` cannot move through actors with the property ``is_blocking``.
"""
self.position_manager.is_blockable = value
@is_blockable.setter
def is_blockable(self, value: bool):
self.position_manager.is_blockable = value
@property
def is_blocking(self):
"""
A actor with the property ``is_blockable`` cannot move through actors with the property ``is_blocking``.
"""
return self.position_manager.is_blocking
@is_blocking.setter
def is_blocking(self, value: bool):
self.position_manager.is_blocking = value
@property
def layer(self) -> int:
"""Defines the layer on which the actor is drawn if several actors overlap."""
return self._layer
@layer.setter
def layer(self, value: int):
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]:
"""Actor position in last frame
Can be used to track changes.
"""
return self.position_manager.last_center
@property
def last_direction(self) -> int:
return self.position_manager.last_direction
[docs]
@classmethod
def from_topleft(cls, topleft_position: Tuple[float, float], *args, **kwargs):
"""
Creates a actor with center at center_position
Arg`s:
center_position: Center of actor
"""
obj = cls(topleft_position, **kwargs) # temp position
obj.origin = "topleft"
return obj
[docs]
@classmethod
def from_center(cls, center_position: Tuple[float, float], *args, **kwargs):
"""
Creates a actor with center at center_position
Arg`s:
center_position: Center of actor
"""
obj = cls(center_position, **kwargs) # temp position
obj.origin = "center"
return obj
@property
def costume_count(self) -> int:
"""Returns number of costumes of actor, 0 if actor has no costume
Examples:
Add costume and count costumes
.. code-block:: python
from miniworlds import *
world = World()
actor = Actor()
assert actor.costume_count == 0
actor.add_costume((255,0,0,0))
assert actor.costume_count == 1
world.run()
Returns:
int: _description_
"""
return self.costume_manager.length()
@property
def is_flipped(self) -> bool:
"""
When a actor is mirrored, it is mirrored across the y-axis.
You can use this property in 2D platformer games to change the direction of actor.
.. note::
It may be necessary to set ``is_rotatable = True``
Examples:
Flip a costume after 100 frames.
.. code-block::
from miniworlds import *
world = World(100,100)
actor = Actor()
actor.add_costume("images/alien1.png")
actor.height= 400
actor.width = 100
actor.is_rotatable = False
@actor.register
def act(self):
if self.world.frame % 100 == 0:
if self.is_flipped:
self.is_flipped = False
else:
self.is_flipped = True
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=200>
<source src="../_static/# It looks like the code `flip_alien` is not a valid Python
# code. It seems to be a placeholder or a random string. If
# you provide more context or details about what you are
# trying to achieve, I can help you with the code.
flip_alien.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Returns:
True, if actor is flipped
"""
return self.costume.is_flipped
@is_flipped.setter
def is_flipped(self, value: bool):
self.costume.is_flipped = value
[docs]
def flip_x(self) -> int:
"""Flips the actor by 180° degrees. The costume is flipped and the actor's direction changed by 180 degrees.
.. image:: ../_images/flip_x.png
Examples:
Flip a actor in Example flipthefish.py
.. code-block:: python
from miniworlds import *
world=TiledWorld()
world.columns = 4
world.rows = 1
world.add_background("images/water.png")
fish = Actor()
fish.border = 1
fish.add_costume("images/fish.png")
fish.direction = "right"
fish.orientation = -90
@fish.register
def act(self):
self.move()
@fish.register
def on_not_detecting_world(self):
self.move_back()
self.flip_x()
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=200>
<source src="../_static/flipthefish.webm" type="video/webm">
Your browser does not support the video tag.
</video>
"""
return self.position_manager.flip_x()
[docs]
def add_costume(
self, source: Union[None, Tuple, str, List] = None
) -> "costume_mod.Costume":
"""Adds a new costume to actor.
The costume can be switched with self.switch_costume(index)
Args:
source: Path to the first image of new costume or Tuple with color-value
Examples:
Add first costume from image:
.. code-block:: python
from miniworlds import *
world = World((100,60))
actor = Actor((10,10))
costume = actor.add_costume("images/player.png")
world.run()
Output:
.. image:: ../_images/add_costume1.png
:width: 100px
:alt: Create Actor with image as costume
Add first costume from color:
.. code-block:: python
from miniworlds import *
world = World((100,60))
actor = Actor((10,10))
costume = actor.add_costume((255,255,0))
world.run()
Output:
.. image:: ../_images/add_costume2.png
:width: 100px
:alt: Create Actor with image as costume
Create two costumes and switch between costumes
.. code-block:: python
from miniworlds import *
world = World((100,60))
actor = Actor((10,10))
world.speed = 30
costume1 = actor.add_costume((255,255,0))
costume2 = actor.add_costume((255,0,255))
@actor.register
def act(self):
if self.costume == costume1:
self.switch_costume(costume2)
else:
self.switch_costume(costume1)
world.run()
Output:
.. image:: ../_images/add_costume3.png
:width: 100%
:alt: Create multiple costumes and switch between costumes
Returns:
The new costume.
"""
if not source or type(source) in [str, tuple]:
return self.costume_manager.add_new_appearance(source)
elif isinstance(source, list):
return cast(
"costume_mod.Costume",
self.costume_manager.add_new_appearance_from_list(source),
)
else:
raise MiniworldsError(
f"Wrong type for appearance. Expected: list, tuple or str, got {type(source)}"
)
[docs]
def add_costumes(self, sources: list) -> "costume_mod.Costume":
"""Adds multiple costumes"""
return self.costume_manager.add_new_appearances(sources)
[docs]
def remove_costume(self, source: Union[int, "costume_mod.Costume"] = None):
"""Removes a costume from actor
Args:
source: The index of the new costume or costume-object. Defaults to actual costume
"""
if source is None:
source = self.costume
return self.costume_manager.remove_appearance(source)
[docs]
def switch_costume(
self, source: Union[int, "appearance.Appearance"]
) -> "costume_mod.Costume":
"""Switches the costume of actor
Args:
source: Number of costume or Costume object
Examples:
Switch a costume:
.. code-block:: python
from miniworlds import *
world = World(100,60)
t = Actor()
costume =t1.add_costume("images/1.png")
t.add_costume("images/2.png")
t.switch_costume(1)
@timer(frames = 40)
def switch():
t1.switch_costume(0)
world.run()
Returns:
The new costume
"""
return self.costume_manager.switch_costume(source)
[docs]
def set_costume(self, costume: Union[str, tuple, int, "appearance.Appearance"]):
if isinstance(costume, int) or isinstance(costume, appearance.Appearance):
self.switch_costume(costume)
elif type(costume) in [str, tuple]:
costume = self.add_costume(costume)
self.switch_costume(costume)
[docs]
def reset_costumes(self):
self.costume_manager.reset()
[docs]
def set_background_color(self, color: tuple):
self.set_costume(color)
[docs]
def next_costume(self):
"""Switches to the next costume of actor
Returns:
The new costume
"""
self.costume_manager.next_costume()
@property
def costume(self) -> costume_mod.Costume:
"""Gets the costume of actor"""
if hasattr(self, "costume_manager") and self.costume_manager is not None:
return self.costume_manager.get_actual_appearance()
@costume.setter
def costume(self, value):
self.costume_manager.appearance = value
@property
def costumes(self) -> "costumes_manager.CostumesManager":
"""Gets the costume manager
The costume manager can be iterated to get all costumes
"""
return self.costume_manager
@property
def orientation(self) -> float:
return self.costume.orientation
@orientation.setter
def orientation(self, value: float):
value: float = self.position_manager.validate_direction(value)
self.costume.orientation = value
@property
def direction(self) -> int:
"""Directions are handled exactly as in the Scratch programming language,
see: `Scratch Wiki <https://en.scratch-wiki.info/wiki/Direction_(value)>`_
The default direction is ``0°``. All actors are looking ``"up"``
.. image:: /_images/movement.jpg
:width: 100%
:alt: Move on world
**Values for Direction**
* ``0°`` or ``"up"``: up
* ``90°`` or ``"right"``: Move right
* ``-90°`` or ``"left"``: Move left
* ``180°`` or ``"down"``: Move down
* ``"forward"``: Current direction
Sets direction of the actor.
You can use an integer or a string to describe the direction
Options
* ``0``, ``"up"`` - Look up
* ``90``, ``"right"``, - Look right
* ``-90``, ``"left"``, - Look left
* ``-180``, ``180``, ``"down"`` - Look down
.. image:: ../_images/direction.png
Examples:
Move in a direction with WASD-Keys
.. code-block:: python
def on_key_down(self, keys):
if "W" in keys:
self.direction = "up"
elif "S" in keys:
self.direction = "down"
elif "A" in keys:
self.direction = "left"
elif "D" in keys:
self.direction = "right"
self.move()
Move 45°:
.. code-block:: python
from miniworlds import *
world = World(100, 100)
c = Circle ((50,50), 10)
@c.register
def act(self):
c.direction = 45
c.move()
world.run()
.. raw:: html
<video loop autoplay muted width=400>
<source src="../_static/move45.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Move -45°:
.. code-block:: python
from miniworlds import *
world = World(100, 100)
c = Circle ((50,50), 10)
@c.register
def act(self):
c.direction = -45
c.move()
world.run()
.. raw:: html
<video loop autoplay muted width=400>
<source src="../_static/moveminus45.webm" type="video/webm">
Your browser does not support the video tag.
</video>
"""
return self.position_manager.get_direction()
@direction.setter
def direction(self, value: int):
self.position_manager.set_direction(value)
@property
def direction_at_unit_circle(self) -> int:
"""Gets the direction as value in unit circle (0° right, 90° top, 180° left...)"""
return self.position_manager.dir_to_unit_circle(self.direction)
@direction_at_unit_circle.setter
def direction_at_unit_circle(self, value: int):
"""Sets the direction from unit circle
Args:
value: An angle in the unit circle, e.g. 0°: right, 90° top, ...
"""
self.direction = self.position_manager.unit_circle_to_dir(value)
@property
def dirty(self) -> int:
"""If actor is dirty, it will be repainted.
Returns:
int: 1 if actor is dirty/0 otherwise
"""
return self._dirty
@dirty.setter
def dirty(self, value: int):
if (
self.position_manager
and self.world
and self.world.camera
and self.world.camera.is_actor_repainted(self)
and value == 1
):
self._dirty = 1
elif value == 0:
self._dirty = 0
else:
pass
[docs]
def turn_left(self, degrees: int = 90) -> int:
"""Turns actor by *degrees* degrees left
.. image:: ../_images/turn_left.png
Options:
* You can set the value actor.is_rotatable = False if you don't want the actor to be rotated.
Examples:
.. code-block:: python
from miniworlds import *
world = World(100, 100)
t = Actor()
t.add_costume("images/arrow.png")
t.size = (100,100)
@t.register
def act(self):
t.turn_left(1)
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=400>
<source src="../_static/turnleft.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Args:
degrees: degrees in left direction
Returns:
New direction
"""
return self.position_manager.turn_left(degrees)
[docs]
def turn_right(self, degrees: Union[int, float] = 90):
"""Turns actor by *degrees* degrees right
.. image:: ../_images/turn_right.png
Examples:
.. code-block:: python
from miniworlds import *
world = World(100, 100)
t = Actor()
t.add_costume("images/arrow.png")
t.size = (100,100)
@t.register
def act(self):
t.turn_left(1)
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=400>
<source src="../_static/turnright.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Options:
* You can set the value actor.is_rotatable = False if you don't want the actor to be rotated.
Args:
degrees: degrees in left direction
Returns:
New direction
"""
return self.position_manager.turn_right(degrees)
[docs]
def set_direction(self, direction: Union[str, int, float]) -> float:
"""Actor points in given direction.
You can use a integer or a string to describe the direction
Args:
The direction as integer or string (see options)
Options
* ``0``, ``"up"`` - Look up
* ``90``, ``"right"``, - Look right
* ``-90``, ``"left"``, - Look left
* ``-180``, ``180``, ``"down"`` - Look down
.. image:: ../_images/direction.png
Examples:
Move in a direction with WASD-Keys
.. code-block:: python
def on_key_down(self, keys):
if "W" in keys:
self.direction = "up"
elif "S" in keys:
self.direction = "down"
elif "A" in keys:
self.direction = "left"
elif "D" in keys:
self.direction = "right"
self.move()
"""
return self.position_manager.set_direction(direction)
[docs]
def point_towards_position(
self, destination: Tuple[float, float]
) -> Union[int, float]:
"""
Actor points towards a given position
Args:
destination: The position to which the actor should pointing
Returns:
The new direction
Examples:
Point towards mouse_position:
.. code-block:: python
def act(self):
mouse = self.world.get_mouse_position()
if mouse:
self.point_towards_position(mouse)
self.move()
"""
return self.position_manager.point_towards_position(destination)
[docs]
def point_towards_actor(self, other: "Actor") -> int:
"""Actor points towards another actor.
Args:
other: The other actor
Returns:
The new direction
"""
pos = other.position_manager.get_global_rect().center
return self.point_towards_position(pos)
@property
def size(self) -> tuple:
"""Size of the actor"""
return self.position_manager.get_size()
@size.setter
def size(self, value: tuple):
self.set_size(value)
[docs]
def set_size(self, value: tuple):
self.position_manager.set_size(value)
@property
def width(self):
"""The width of the actor in pixels.
When the width of a actor is changed, the height is scaled proportionally.
Examples:
Create a actor and scale width/height proportionally:
.. code-block:: python
from miniworlds import *
world = World(800,400)
def create_actor(x, y):
t = Actor()
t.position = (x, y)
t.add_costume("images/alien1.png")
t.border = 1
return t
t0 = create_actor(0,0)
t1 = create_actor(50,0)
t1.height = 400
t2 = create_actor(300,0)
t2.width = 180
world.run()
.. image:: ../_images/widthheight.png
:alt: Textured image
"""
return self.position_manager.get_size()[0]
@width.setter
def width(self, value):
self.position_manager.set_width(value)
self.on_shape_change()
[docs]
def scale_width(self, value):
old_width = self.size[0]
old_height = self.size[1]
scale_factor = value / old_width
self.size = (value, old_height * scale_factor)
@property
def height(self):
"""The height of the actor in pixels.
When the height of a actor is changed, the width is scaled proportionally.
Examples:
Create a actor and scale width/height proportionally:
.. code-block:: python
from miniworlds import *
world = World(800,400)
def create_actor(x, y):
t = Actor()
t.position = (x, y)
t.add_costume("images/alien1.png")
t.border = 1
return t
t0 = create_actor(0,0)
t1 = create_actor(50,0)
t1.height = 400
t2 = create_actor(300,0)
t2.width = 180
world.run()
.. image:: ../_images/widthheight.png
:alt: Textured image
"""
return self.position_manager.get_size()[1]
@height.setter
def height(self, value):
self.position_manager.set_height(value)
self.on_shape_change()
[docs]
def scale_height(self, value):
old_width = self.size[0]
old_height = self.size[1]
scale_factor = value / old_height
self.size = (old_width * scale_factor, value)
@property
def x(self) -> float:
"""The x-value of a actor"""
return self.position_manager.get_position()[0]
@x.setter
def x(self, value: float):
self.position_manager.set_position((value, self.y))
@property
def y(self) -> float:
"""The y-value of a actor"""
return self.position_manager.get_position()[1]
@y.setter
def y(self, value: float):
self.position_manager.set_position((self.x, value))
@property
def class_name(self) -> str:
return self.__class__.__name__
@property
def topleft_x(self) -> float:
"""x-value of actor topleft-position"""
return self.get_global_rect().get_topleft()[0]
@property
def topleft_y(self) -> float:
"""x-value of actor topleft-position"""
return self.position_manager.get_topleft()[1]
@topleft_x.setter
def topleft_x(self, value: float):
self.position_manager.set_topleft((value, self.topleft_x))
@topleft_y.setter
def topleft_y(self, value: float):
self.position_manager.set_topleft((self.topleft_y, value))
@property
def topleft(self) -> Tuple[float, float]:
return self.position_manager.get_topleft()
@topleft.setter
def topleft(self, value: Tuple[float, float]):
self.position_manager.set_topleft(value)
@property
def local_center(self) -> Tuple[float, float]:
"""x-value of actor center-position inside the current camera-screen"""
return self.position_manager.local_center
@property
def center_x(self) -> float:
"""x-value of actor center-position"""
return self.position_manager.get_center()[0]
@center_x.setter
def center_x(self, value: float):
self.position_manager.set_center((value, self.center_y))
@property
def center_y(self) -> float:
"""y-value of actor center-position"""
return self.position_manager.get_center()[1]
@center_y.setter
def center_y(self, value: float):
self.position_manager.set_center((self.center_x, value))
@property
def center(self) -> Tuple[float, float]:
return self.position_manager.get_center()
@center.setter
def center(self, value: Tuple[float, float]):
self.position_manager.set_center(value)
[docs]
def move(self, distance: int = 0):
"""Moves actor *distance* steps in current direction
.. image:: ../_images/move.png
Args:
distance: Number of steps to move.
If distance = 0, the actor speed will be used.
Returns:
The moved actor
Examples:
if actor is on the world, move forward:
.. code-block:: python
class Robot(Actor):
def act(self):
if self.detecting_world():
self.move()
"""
return self.position_manager.move(distance)
[docs]
def move_vector(self, vector):
"""Moves actor in direction defined by the vector
Returns:
The moved actor
"""
return self.position_manager.move_vector(vector)
[docs]
def move_up(self, distance: int = 1):
return self.position_manager.move_in_direction("up", distance)
[docs]
def move_down(self, distance: int = 1):
return self.position_manager.move_in_direction("down", distance)
[docs]
def move_left(self, distance: int = 1):
return self.position_manager.move_in_direction("left", distance)
[docs]
def move_right(self, distance: int = 1):
return self.position_manager.move_in_direction("right", distance)
[docs]
def move_back(self, distance):
""" """
return self.position_manager.move(-distance)
[docs]
def undo_move(self):
"""Undo the last move. Moves the actor to the last position and resets direction.
.. image:: ../_images/move_back.png
Returns:
The moved actor
Examples:
move_back when field is blocked:
.. code-block:: python
def on_detecting_wall(self, wall):
self.undo_move()
"""
return self.position_manager.undo_move()
[docs]
def move_towards(self, target: Union[Tuple[float, float], "Actor"]):
if isinstance(target, Actor):
target = target.position
return self.position_manager.move_towards_position(target)
[docs]
def move_in_direction(
self,
direction: Union[int, str, Tuple[float, float]],
distance=1,
):
"""Moves actor *distance* steps into a *direction* or towards a position
.. image:: ../_images/move_in_direction.png
Options
* 0, "up" - Look up
* 90, "right", - Look right
* -90, "left", - Look left
* -180, 180, "down" - Look down
.. image:: ../_images/direction.png
Args:
direction: Direction as angle
distance: Detects obj "distance" steps in front of current actor.
Returns:
The actor itself
"""
if type(direction) in [int, str]:
return self.position_manager.move_in_direction(direction, distance)
elif type(direction) == tuple:
return self.position_manager.move_towards_position(direction, distance)
else:
raise MiniworldsError(
f"Expected direction or position, got f{type(direction)}, ({direction})"
)
[docs]
def move_to(self, position: Tuple[float, float]):
"""Moves actor *distance* to a specific world_posiition
Args:
position: The position to which the actor should move. The position can be a 2-tuple (x, y)
which will be converted to a world_position
.. image:: ../_images/move_to.png
Returns:
The actor itself
Examples:
move to (3, 2) on mouse_click
.. code-block:: python
def on_clicked_left(self, position):
self.move_to((3,2))
"""
return self.position_manager.move_to(position)
[docs]
def remove(self, kill=True) -> collections.defaultdict:
"""
Removes this actor from world
Examples:
Removes robots in thecrash.py :
.. code-block:: python
def act(self):
self.move()
other = self.detecting_actor(distance = 0, actor_type=Robot)
if other:
explosion = Explosion(position=self.position)
self.remove()
other.remove()
"""
self.world.get_world_connector(self).remove_actor_from_world(kill = kill)
[docs]
def before_remove(self):
pass
@property
def is_rotatable(self) -> bool:
"""Defines if the costume of a actor should be rotatable. The actor can still be rotated with
the ``direction`` property, but its costume won't be changed
.. note::
You can also use ``actor.costume.is_rotatable``
Examples:
Create a rotatable and a not rotatable actor
.. code-block::
from miniworlds import *
world = World()
t1 = Actor((100,100))
t1.add_costume("images/alien1.png")
t2 = Actor((200,200))
t2.add_costume("images/alien1.png")
t2.is_rotatable = False
@t1.register
def act(self):
self.move()
self.direction += 1
@t2.register
def act(self):
self.move()
self.direction += 1
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=400>
<source src="../_static/rotatable.webm" type="video/webm">
Your browser does not support the video tag.
</video>
"""
return self.costume.is_rotatable
@is_rotatable.setter
def is_rotatable(self, value: bool):
self.costume.is_rotatable = value
[docs]
def bounce_from_border(self, borders: List[str]) -> Actor:
"""The actor "bounces" from a border.
The direction is set according to the principle input angle = output angle.
.. note::
You must check for borders first!
Args:
borders: A list of borders as strings e.g. ["left", "right"]
Examples:
.. code-block:: python
from miniworlds import *
import random
world = World(150, 150)
actor = Actor((50,50))
actor.add_costume("images/ball.png")
actor.direction = 10
@actor.register
def act(self):
self.move()
borders = self.detecting_borders()
if borders:
self.bounce_from_border(borders)
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=240>
<source src="../_static/bouncing_ball.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Returns:
The actor
"""
return self.position_manager.bounce_from_border(borders)
[docs]
def detect_all(
self,
actors: Union[str, "Actor", Type["Actor"]] = None,
direction: int = 0,
distance: int = 0,
) -> List["Actor"]:
"""Detects if actors are on actor position.
Returns a list of actors.
.. image:: ../_images/detecting_actors.png
Args:
actors: filter by actor type. Enter a class_name of actors to look for here
direction: The direction in which actors should be detected.
distance: The distance in which actors should be detected (Start-Point is actor.center)
Returns:
All actors found by Sensor
"""
if distance == 0:
# => search for actors at current position
return self.sensor_manager.detect_actors(actors)
else:
# distance and direction are != 0
# => search for actor in distance/direction.
return self.sensor_manager.detect_actors_at(actors, direction, distance)
[docs]
def detect(self, *args, **kwargs) -> Union["Actor", None]:
"""Detects if actors are on actor position.
Returns the first found actor.
.. image:: ../_images/detecting_actor.png
Args:
actors: filter by actor type. Enter a class_name of actors to look for heredirection: int = 0, distance: int = 0
direction: The direction in which actors should be detected.
distance: The distance in which actors should be detected (Start-Point is actor.center)
Returns:
First actor found by Sensor
Examples:
The green robot pushes the yellow robot:
.. code-block:: python
from miniworlds import *
world = TiledWorld(8,3)
actor = Actor((1,1))
actor.add_costume("images/robo_green.png")
actor.orientation = -90
actor.direction = 90
actor2 = Actor((4,1))
actor2.add_costume("images/robo_yellow.png")
actor2.orientation = -90
actor2.direction = -90
@actor.register
def act(self):
self.move()
actor = self.detecting_actor()
if actor:
actor.move_right()
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=240>
<source src="../_static/pushing.webm" type="video/webm">
Your browser does not support the video tag.
</video>
"""
if not args:
actors = kwargs.pop("filter") if "filter" in kwargs else None
distance = kwargs.pop("distance") if "distance" in kwargs else None
direction = kwargs.pop("direction") if "direction" in kwargs else self.direction
else:
actors = args[0] if len(args) > 0 else None
distance = args[1] if len(args) >= 2 else None
direction = args[2] if len(args) >= 3 else self.direction
if not distance:
return self.sensor_manager.detect_actor(filter=actors)
else:
return self.sensor_manager.detect_actors_at(
filter=actors, direction=direction, distance=distance
)
[docs]
def detect_borders(
self,
distance: int = 0,
) -> List:
"""
Detects borders
.. image:: ../_images/detecting_borders.png
Args:
distance: Specifies the distance in front of the actuator to which the sensors reacts.
Returns:
True if border was found.
"""
return self.sensor_manager.detect_borders(distance)
[docs]
def detect_left_border(self) -> bool:
"""Does the actor touch the left border?
Returns:
True if border was found.
"""
return "left" in self.sensor_manager.detect_borders(0)
[docs]
def detect_right_border(self) -> bool:
"""Does the actor touch the right border?
Returns:
True if border was found.
"""
return "right" in self.sensor_manager.detect_borders(0)
[docs]
def detect_top_border(self) -> bool:
"""Does the actor touch the lower border?
Returns:
True if border was found.
"""
return "top" in self.sensor_manager.detect_borders(0)
[docs]
def detecting_bottom_border(self) -> bool:
"""Does the actor touch the lower border?
Returns:
True if border was found.
"""
return "bottom" in self.sensor_manager.detect_borders(0)
[docs]
def detect_color(self, color: Tuple = None) -> bool:
"""Detects colors in world-background at actor center-position
Args:
color: color as tuple
Returns:
True, if color was found
"""
color = self.sensor_manager.detect_color(
color,
)
return color
[docs]
def detect_color_at(
self, direction: int = None, distance: int = 0
) -> Union[Tuple, List]:
"""Detects colors in world-background at actor-position
Args:
direction: Specifies the direction where the sensors is searching.
distance: Specifies the distance in front of the actuator to which the sensors reacts.
Returns:
All colors found by Sensor
"""
color = self.sensor_manager.detect_color_at(direction, distance)
return color
[docs]
def detect_actors_at(self, direction=None, distance=0, actors=None) -> list:
"""Detects a actor in given direction and distance.
Examples:
.. code-block:: python
from miniworlds import *
world = World()
wall=Rectangle((200,0))
wall.size = (20, 400)
for i in range(7):
actor = Circle((10,i*60 + 20))
actor.range = i * 10
@actor.register
def act(self):
if not self.detect_actors_at(self.direction, self.range):
self.direction = "right"
self.move()
world.run()
:param direction: The direction in which actors should be detected.
:param distance: The distance in which actors should be detected (Start-Point is actor.center)
:return: A list of actors
"""
return self.sensor_manager.detect_actors_at(actors, direction, distance)
[docs]
def detect_actor_at(self, direction=None, distance=0, actors=None) -> "Actor":
found_actors = self.sensor_manager.detect_actors_at(actors, direction, distance)
if found_actors:
return found_actors[0]
[docs]
def detect_actors_in_front(
self,
actors=None,
distance=1,
) -> list:
return self.sensor_manager.detect_actors_at(actors, self.direction, distance)
[docs]
def detect_actor_in_front(
self,
actors=None,
distance=1,
) -> "Actor":
found_actors = self.sensor_manager.detect_actors_at(
actors, self.direction, distance
)
if found_actors:
return found_actors[0]
[docs]
def detect_point(self, position: Tuple[float, float]) -> bool:
"""Is the actor colliding with a specific (global) point?
Warning:
If your want to check if an actor detects a specific pixel, use detect_pixel
Returns:
True if point is below actor
"""
return self.sensor_manager.detect_point(position)
[docs]
def detect_pixel(self, position: Tuple[float, float]) -> bool:
"""Is the actor colliding with a pixel?
Returns:
True if pixel is below actor
"""
return self.sensor_manager.detect_pixel(position)
[docs]
def detect_rect(self, rect: Union[Tuple, pygame.rect.Rect]):
"""Is the actor colliding with a static rect?"""
return self.sensor_manager.detect_rect(rect)
[docs]
def detect_world(self):
"""Is the actor colliding with a static rect?"""
return self.sensor_manager.detect_rect(self.world.rect)
[docs]
def bounce_from_actor(self, other: "Actor"):
self.position_manager.bounce_from_actor(other)
[docs]
def animate(self, speed: int = 10):
self.costume_manager.animate(speed)
[docs]
def animate_costume(self, costume: "costume_mod.Costume", speed: int = 10):
self.costume_manager.animate_costume(costume, speed)
[docs]
def animate_loop(self, speed: int = 10):
"""Animates a costume with a looping animation
Switches through all costume-images every ``speed``-frame.
Examples:
.. code-block:: python
from miniworlds import *
world = World(columns=280, rows=100)
robo = Actor(position=(0, 0))
robo.costume.add_images(["images/1.png", "images/2.png","images/3.png","images/4.png"])
robo.size = (99, 99)
robo.animate_loop()
world.run()
Args:
speed (int, optional): Every ``speed`` frame, the image is switched. Defaults to 10.
"""
self.costume.loop = True
self.costume_manager.animate(speed)
[docs]
def stop_animation(self):
"""Stops current animation.
Costume ``is_animated`` is set to False
Examples:
.. code-block:: python
from miniworlds import *
world = World(columns=280, rows=100)
robo = Actor(position=(0, 0))
robo.costume.add_images(["images/1.png", "images/2.png","images/3.png","images/4.png"])
robo.size = (99, 99)
robo.animate_loop()
@timer(frames = 100)
def stop():
robo.stop_animation()
world.run()
"""
self.costume.is_animated = False
[docs]
def send_message(self, message: str):
"""Sends a message to world.
The message can be received with the ``on_message``-event
Examples:
Send and receive messages:
.. code-block:: python
from miniworlds import *
world = World()
actor1 = Actor((2, 2))
actor1.add_costume((100,0,100,100))
@actor1.register
def on_message(self, message):
print("Received message:" + message)
actor2 = Actor((100,100))
actor2.send_message("Hello from actor2")
@actor2.register
def on_key_down_s(self):
self.send_message("Hello")
world.run()
Args:
message (str): A string containing the message.
"""
self.world.app.event_manager.to_event_queue("message", message)
[docs]
def on_key_down(self, key: list):
"""**on_key_down** is called one time when a key is pressed down.
.. note::
Instead of **on_key_down** you can use **on_key_down_letter**, e.g. **on_key_down_a** or **on_key_down_w**
, if you want to handle an on_key_down event for a specific letter.
Examples:
Register a key_down event:
.. code-block::
actor1 = miniworlds.Actor(position = (2, 2) )
actor1.add_costume((100,0,100,100))
@actor1.register
def on_key_down(self, key):
print(key)
Register on_key_down_a event
.. code-block::
actor1 = miniworlds.Actor(position = (2, 2) )
actor1.add_costume((100,0,100,100))
@actor1.register
def on_key_down_a(self):
print("a")
Args:
key (list): The typed key as list (e.g. ['A', 'a']) containing both uppercase and lowercase of typed letter.
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_key_down)
[docs]
def on_key_pressed(self, key: list):
"""**on_key_pressed** is called when while key is pressed. If you hold the key, on_key_pressed
is repeatedly called again and again until the key is released.
.. note::
Like `on_key_down` the method can be called in the variant `on_key_pressed_[letter]`
(e.g. `on_key_pressed_w(self)`).
Examples:
Register on_key_pressed event:
.. code-block::
actor1 = miniworlds.Actor(position = (2, 2) )
actor1.add_costume((100,0,100,100))
@actor1.register
def on_key_pressed(self, key):
print("pressed", key)
@actor1.register
def on_key_pressed_s(self):
print("pressed s")
Args:
key (list): The typed key as list (e.g. ['C', 'c', 'D', 'd']) containing both uppercase and lowercase
of typed letter.
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_key_pressed)
[docs]
def on_key_up(self, key):
raise NotImplementedOrRegisteredError(self.on_key_up)
[docs]
def on_mouse_over(self, position):
"""on_mouse_over is called, when mouse is moved over actor
:param position: The mouse position
"""
raise NotImplementedOrRegisteredError(self.on_mouse_over)
[docs]
def on_mouse_leave(self, position):
"""on_mouse_over is called, when mouse is moved over actor
:param position: The mouse position
"""
raise NotImplementedOrRegisteredError(self.on_mouse_over)
[docs]
def on_mouse_left(self, position: tuple):
"""on_mouse_left is called when left mouse button was pressed.
You must *register* or *implement* this method as an event.
.. note::
The event is triggered, when mouse-left was clicked, even when the current mouse position
is not related to actor position.
You can use :py:meth:`Actor.detect_pixel` to check, if the mouse_position is *inside* the actor.
Examples:
A circle will be moved, if you click on circle.
.. code-block::
from miniworlds import *
world = World(120,40)
circle = Circle((20, 20))
circle.direction = 90
@circle.register
def on_mouse_left(self, mouse_pos):
if self.detect_pixel(mouse_pos):
self.move()
world.run()
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_left)
[docs]
def on_mouse_right(self, position: tuple):
"""Method is called when right mouse button was pressed.
You must *register* or *implement* this method as an event.
.. note::
The event is triggered, when mouse was clicked,even when the current mouse position is not related
to actor position.
You can use :py:meth:`Actor.detect_pixel` to check, if the mouse_position is *inside* the actor.
Examples:
See: :py:meth:`Actor.on_mouse_left`.
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_right)
[docs]
def on_mouse_motion(self, position: tuple):
"""Method is called when mouse moves. You must *register* or *implement* this method as an event.
.. note::
The event is triggered, when mouse is moved, even when the current mouse position
is not related to actor position.
You can use :py:meth:`Actor.detect_pixel` to check, if the mouse_position is *inside* the actor.
Examples:
A circle will be moved, if you click on circle.
.. code-block::
from miniworlds import *
world = World(120,40)
circle = Circle((20, 20))
circle.direction = 90
@circle.register
def on_mouse_motion(self, mouse_pos):
if self.detect_pixel(mouse_pos):
self.move()
world.run()
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_motion)
[docs]
def on_mouse_left_released(self, position: tuple):
"""Method is called when left mouse key is released.
Examples:
You can use on_mouse_left_release to implement a drag_and_drop event
.. code-block::
from miniworlds import *
world = World(200, 200)
circle = Circle((30, 30), 60)
circle.direction = 90
circle.dragged = False
@circle.register
def on_mouse_left(self, mouse_pos):
if self.detect_pixel(mouse_pos):
self.dragged = True
@circle.register
def on_mouse_left_released(self, mouse_pos):
if not world.is_mouse_pressed():
self.dragged = False
self.center = mouse_pos
world.run()
Output:
.. raw:: html
<video loop autoplay muted width=200>
<source src="../_static/draganddrop.webm" type="video/webm">
Your browser does not support the video tag.
</video>
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_left_released)
[docs]
def on_mouse_right_released(self, position: tuple):
"""Method is called when right mouse key is released. See :py:meth:`Actor.on_mouse_left_released`.
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_mouse_right_released)
[docs]
def on_clicked_left(self, position: tuple):
"""The mouse is on top of a actor and mouse was clicked.
Examples:
Registering a on_click event:
.. code-block::
actor = miniworlds.Actor((2,2))
@actor.register
def on_clicked_left(self, position):
print("clicked" + str(position))
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_clicked_left)
[docs]
def on_clicked_right(self, position):
"""The mouse is on top of a actor and mouse was clicked.
Examples:
Registering a on_click event:
.. code-block::
actor = miniworlds.Actor((2,2))
@actor.register
def on_clicked_right(self, position):
print("clicked" + str(position))
Args:
position (tuple): Actual mouse position as tuple (x,y)
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_clicked_right)
[docs]
def on_detecting_world(self):
"""`on_detecting_world` is called, when actor is on the world
Examples:
Register on_detecting_world method:
.. code-block::
@player.register
def on_detecting_world(self):
print("Player 3: I'm on the world:")
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_detecting_world)
[docs]
def on_not_detecting_world(self):
"""`on_detecting_world` is called, when actor is on the world
Examples:
Register on_detecting_world method:
.. code-block::
@player.register
def on_detecting_world(self):
print("Player 3: I'm on the world:")
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_detecting_world)
[docs]
def on_detecting_actor(self, actor: "Actor"):
"""*on_detect_actor* is called, when actor is detects a actor on same position
Args:
actor (Actor): The found actor
Examples:
Register detect_actor event
.. code-block::
@player.register
def on_detect_actor(self, actor):
print("Player 1: detecting actor:")
if actor == player2:
print("Am i detecting player2?" + str(actor == player2))
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
raise NotImplementedOrRegisteredError(self.on_detecting_actor)
[docs]
def on_detecting_borders(self, borders: List[str]):
"""*on_detecting_border* is called, when actor is near a border
Args:
borders (List): A list of strings with found borders, e.g.: ['left', 'top']
Examples:
Register on_detecting_border_event:
.. code-block::
@player.register
def on_detecting_borders(self, borders):
print("Player 4: detecting borders:")
print("Borders are here!", str(borders))
Raises:
NotImplementedOrRegisteredError: The error is raised when method is not overwritten or registered.
"""
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):
"""The fill color of actor as rgba value, e.g. (255, 0, 0) for red.
When ``fill_color`` is set to a color, the attribute ``is_filled`` of costume
(See: :py:attr:.appearances.appearance.Appearance.is_filled`) is set to ``True``.
.. note::
Aliases: :py:attr:`Actor.color`
.. warning::
If you fill a costume with an image, the image will be completely overwritten,
even if `fill_color` is transparent.
This behaviour may change in later releases!
Examples:
.. code-block:: python
from miniworlds import *
world = World(200,80)
world.default_fill_color = (0,0, 255)
t = Actor()
t2 = Actor((40,0))
t2.is_filled = (0, 255, 0)
t3 = Actor((80, 0))
t3.fill_colorimport miniworlds.actors.actor as actor
= (255, 0, 0)
t4 = Actor((120, 0))
t4.add_costume((0,0,0))
t4.fill_color = (255, 255, 0)
t5 = Actor((160, 0))
t5.add_costume("images/player.png")
t5.fill_color = (255, 255, 0, 100) # image is overwritten
t6 = Circle((0, 40), 20)
t6.position = t6.center
t6.fill_color = (255, 255, 255)
t7 = Ellipse((40, 40), 40, 40)
t7.fill_color = (255, 0, 255)
world.run()
Output:
.. image:: ../_images/fill_color.png
:width: 200px
:alt: Set borders
"""
return self.costume.fill_color
@fill_color.setter
def fill_color(self, value):
self.costume.fill(value)
# Alias
color = fill_color
[docs]
def fill(self, value):
"""Set fill color for borders and lines"""
self.costume.fill(value)
@property
def is_filled(self):
"""Is actor filled with color?"""
return self.costume.is_filled
@is_filled.setter
def is_filled(self, value):
self.costume.fill(value)
@property
def border_color(self):
"""border color of actor.
The border-color is a rgba value, for example (255, 0, 0) for red, (0, 255, 0) for green and (255, 0, 0, 100).
If the color-value has 4 values, the last value defines the transparency:
* 0: Full transparent,
* 255: No transparency
.. note::
You must also set :py:attr:`Actor.border` to a value > 0
Aliases: :py:attr:`Actor.stroke_color`
Examples:
See :py:attr:`Actor.border`
"""
return self.costume.border_color
@border_color.setter
def border_color(self, value):
self.costume.border_color = value
# Alias
stroke_color = border_color
@property
def border(self):
"""The border-size of actor.
The value is 0, if actor has no border.
.. note::
You can also set border with ``costume.border`` or you can set the border with ``world.default_border``
Examples:
Set border of actor:
.. code-block::
from miniworlds import *
world = World(210,80)
world.default_border_color = (0,0, 255)
world.default_border = 1
t = Actor((10,10)) # default-border and color from world
t.add_costume("images/player.png")
t2 = Actor ((60, 10)) # overwrites default border values
t2.add_costume("images/player.png")
t2.border_color = (0,255, 0)
t2.border = 5
t3 = Actor ((110, 10)) # removes border
t3.add_costume("images/player.png")
t3.border = None
world.run()
Output:
.. image:: ../_images/borders.png
:width: 200px
:alt: Set borders
"""
return self.costume.border
@border.setter
def border(self, value):
self.costume.border = value
@property
def visible(self):
return self._visible
@visible.setter
def visible(self, value):
self._visible = value
self.costume.visibility_changed()
[docs]
def hide(self):
"""Hides a actor (the actor will be invisible)"""
self.visible = False
[docs]
def show(self):
"""Displays a actor ( an invisible actor will be visible)"""
self.visible = True
[docs]
def register(self, method: callable, force=False, name=None):
"""This method is used for the @register decorator. It adds a method to an object
Args:
method (callable): The method which should be added to the actor
force: Should register forced, even if method is not handling a valid event?
name: Registers method with specific name
"""
if (
not force
and method.__name__ not in self.world.event_manager.actor_class_events_set
):
raise RegisterError(method.__name__, self)
bound_method = actor_inspection.ActorInspection(self).bind_method(method, name)
if method.__name__ == "on_setup" and not self._was_setup:
self.on_setup()
self._was_setup = True
self.world.event_manager.register_event(method.__name__, self)
return bound_method
[docs]
def register_message(self, *args, **kwargs):
"""This method is used for the @register_message decorator.
It adds a method to an object and reacts to on_message events
Args:
method (callable): The method which should be added to the actor
force: Should register forced, even if method is not handling a valid event?
name: Registers method with specific name
"""
def decorator(method):
if "method" in kwargs:
method = kwargs.pop("method")
name = kwargs.pop("name")
bound_method = actor_inspection.ActorInspection(self).bind_method(method, method.__name__)
self.world.event_manager.register_message_event(method.__name__, self, args[0])
return bound_method
return decorator
[docs]
def register_sensor(self, *args, **kwargs):
"""This method is used for the @register_sensor decorator.
It adds a method to an object and reacts to on_detect_[class] events
Args:
method (callable): The method which should be added to the actor
force: Should register forced, even if method is not handling a valid event?
name: Registers method with specific name
"""
def decorator(method):
if "method" in kwargs:
method = kwargs.pop("method")
name = kwargs.pop("name")
bound_method = actor_inspection.ActorInspection(self).bind_method(method, method.__name__)
self.world.event_manager.register_sensor_event(method.__name__, self, args[0])
return bound_method
return decorator
"""@player.on_message("burn")
def burn(self, sender):
if not fireplace.burning:
fireplace.world.play_sound("sounds/fireplace.wav")
fireplace.switch_costume(fireplace.costume_burned)
fireplace.costume.is_animated = True
"""
[docs]
def get_local_rect(self) -> pygame.Rect:
local_rect = self.position_manager.get_local_rect()
return local_rect
@property
def rect(self) -> pygame.Rect:
"""The surrounding Rectangle as pygame.Rect. The rect coordinates describes the local coordinates and depend on the camera view.
Warning:
If the actor is rotated, the rect vertices are not the vertices of the actor image.
"""
return self.position_manager.get_local_rect()
def __str__(self):
try:
if (
self.world
and hasattr(self, "position_manager")
and self.position_manager
):
return f"**Actor of type [{ self.__class__.__name__}]: ID: { self.actor_id} at pos {self.position} with size {self.size}**"
else:
return f"**Actor of type [{ self.__class__.__name__}]: ID: { self.actor_id}**"
except Exception:
return f"**Actor: {self.__class__.__name__}**"
@property
def world(self):
return self._world
@world.setter
def world(self, new_world):
self.set_world(new_world)
[docs]
def set_world(self, new_world : "world_mod.World") -> "Actor":
_world_connector = new_world.get_world_connector(self)
_world_connector.set_world(self.world, new_world)
return self
[docs]
def new_costume(self):
return self.world.get_world_connector(self).create_costume()
[docs]
def get_costume_class(self) -> type["costume_mod.Costume"]:
return self.world.get_world_connector(self).get_actor_costume_class()
@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.costume_manager.image
@property
def position_manager(self):
try:
return self._position_manager
except AttributeError:
raise MissingPositionManager(self)
@property
def sensor_manager(self):
try:
return self._sensor_manager
except AttributeError:
raise Missingworldsensor(self)
@property
def costume_manager(self):
return self._costume_manager
@property
def position(self) -> Tuple[float, float]:
"""The position of the actor as Position(x, y)"""
return self.position_manager.get_position()
@position.setter
def position(self, value: Tuple[float, float]):
self.set_position(value)
[docs]
def set_position(self, value: Tuple[float, float]):
self.position_manager.set_position(value)
[docs]
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_manager.get_distance_to(obj)
[docs]
def on_shape_change(self):
pass