Skip to content

topdownengine.env_object Reference

Resolved: Mutable Default Argument Bug (Fixed in v0.4.3)

Prior to version 0.4.3, omitting the colliders argument in the EnvObject initialization caused colliders to persist across separate instances due to a mutable default list.

The default argument has been changed to None and now safely initializes a fresh, empty list for every individual instance.

EnvObject

Bases: GameObject

This class represents all environment objects in the engine.

Attributes:

Name Type Description
CAUSES_COLLISIONS bool

Overrides GameObject. Defaults to True.

Source code in topdownengine\env_object.py
class EnvObject(GameObject):
    """This class represents all environment objects in the engine.

    Attributes:
        CAUSES_COLLISIONS (bool): Overrides GameObject. Defaults to True.
    """
    CAUSES_COLLISIONS = True

    def __init__(
        self,
        animation_paths: dict[str,str]|None=None, 
        frame_size: tuple[int]|None=None, 
        colliders: list[pg.Rect]=None
    ) -> None:
        """Initialize the EnvObject.

        Args:
            animation_paths (dict[str,str], optional): The animation paths to load animations from. Defaults to None.
            frame_size (tuple[int]|None, optional): The frame size to use to load/generate animations. Defaults to None.
            colliders (list[pygame.Rect]|None, optional): The list of colliders of the EnvObject, relative to itself. If it is set to None, there will be no colliders. Defaults to None.
        """
        # Set animation paths dict and frame size before calling super().__init__()
        # This make it automatically load in the animations without
        # having to call it a second time.
        self.animation_paths = animation_paths
        if frame_size is not None:
            self.frame_size = frame_size

        super().__init__()
        self.colliders = colliders if colliders is not None else []

current_frame property

Current animation frame the GameObject is on.

draw_index property

The draw index of the GameObject.

image property

Image for drawing.

rect property

Rect object for drawing.

world_colliders property

Return a list of collider Rects in world-space, as opposed to GameObject.colliders, which uses relative positioning to the GameObject itself.

__init__(animation_paths=None, frame_size=None, colliders=None)

Initialize the EnvObject.

Parameters:

Name Type Description Default
animation_paths dict[str, str]

The animation paths to load animations from. Defaults to None.

None
frame_size tuple[int] | None

The frame size to use to load/generate animations. Defaults to None.

None
colliders list[Rect] | None

The list of colliders of the EnvObject, relative to itself. If it is set to None, there will be no colliders. Defaults to None.

None
Source code in topdownengine\env_object.py
def __init__(
    self,
    animation_paths: dict[str,str]|None=None, 
    frame_size: tuple[int]|None=None, 
    colliders: list[pg.Rect]=None
) -> None:
    """Initialize the EnvObject.

    Args:
        animation_paths (dict[str,str], optional): The animation paths to load animations from. Defaults to None.
        frame_size (tuple[int]|None, optional): The frame size to use to load/generate animations. Defaults to None.
        colliders (list[pygame.Rect]|None, optional): The list of colliders of the EnvObject, relative to itself. If it is set to None, there will be no colliders. Defaults to None.
    """
    # Set animation paths dict and frame size before calling super().__init__()
    # This make it automatically load in the animations without
    # having to call it a second time.
    self.animation_paths = animation_paths
    if frame_size is not None:
        self.frame_size = frame_size

    super().__init__()
    self.colliders = colliders if colliders is not None else []

generate_colliders()

Default list of Rect objects for collisions.

Source code in topdownengine\game_object.py
def generate_colliders(self) -> list[pg.Rect|pg.FRect]:
    "Default list of Rect objects for collisions."
    elev_pos = self.position - pg.Vector2(0, self.elevation)
    if self.SUBPIXEL:
        r = self.current_frame.get_frect(
            topleft=elev_pos * self.SCALE
        )
    else:
        elev_pos.x = int(elev_pos.x)
        elev_pos.y = int(elev_pos.y)
        r = self.current_frame.get_rect(
            topleft=elev_pos * self.SCALE
        )

    r.height -= r.height / 2 if self.SUBPIXEL else r.height // 2

    return [tde_math.scale_rect(r, 1/self.SCALE)]

load_animations()

Load unscaled animations.

Source code in topdownengine\game_object.py
def load_animations(self) -> None:
    "Load unscaled animations."
    self.animations = dict()

    if getattr(self, "animation_paths", None) is None:
        # When there is no animation path data, add red
        # square idle animation with changing colors.
        self.animations["idle"] = []
        for i in range(4):
            image = pg.Surface(getattr(self, "frame_size", (16, 16)))
            image.fill((255/(i+1), 0, 0))
            self.animations["idle"].append(image.convert_alpha())
    else:
        for k, v in self.animation_paths.items():
            if getattr(self, "directional_anims", False):
                dirs = ["d", "r", "u", "l"]
                all_anims = VisualUtils.load_animations(v, *self.frame_size)
                all_anims.append(VisualUtils.flip_animation(all_anims[1], True, False))
                for i, anim in enumerate(all_anims):
                    self.animations[f"{k}_{dirs[i]}"] = anim

            else:
                self.animations[k] = VisualUtils.load_animation(v, *self.frame_size)

scale_animations()

Scale animations.

Source code in topdownengine\game_object.py
def scale_animations(self) -> None:
    "Scale animations."
    for _, anim in self.animations.items():
        for i, frame in enumerate(anim):
            anim[i] = pg.transform.scale(
                frame,
                (frame.width * self.SCALE, frame.height * self.SCALE)
            )

set_scale(new_scale, game) classmethod

This method sets the target scale of all GameObjects.

Parameters:

Name Type Description Default
new_scale int

The new target scale being set to.

required
game Game | None

The Game object being used. While you may pass in None, you MUST pass in a Game instance if you have already defined GameObjects.

required
Source code in topdownengine\game_object.py
@classmethod
def set_scale(cls, new_scale: int, game: Game|None) -> None:
    """This method sets the target scale of all GameObjects.

    Args:
        new_scale (int): The new target scale being set to.
        game (Game|None): The Game object being used. While you may pass in None, you MUST pass in a Game instance if you have already defined GameObjects.
    """
    if game is not None:
        new_scale = game.set_target_scale(new_scale)
    cls.SCALE = new_scale
    cls.load_and_scale_shadows()
    if game is None: 
        return
    for go in game.game_object_group:
        go.load_animations()
        go.scale_animations()

update(dt, game)

This method updates the GameObject instance.

Parameters:

Name Type Description Default
dt float

The deltatime.

required
game Game

The Game object.

required
Source code in topdownengine\game_object.py
def update(self, dt: float, game: Game) -> None:
    """This method updates the GameObject instance.

    Args:
        dt (float): The deltatime.
        game (Game): The Game object.
    """
    # Gravity
    self.z_vel -= self.gravity * dt
    self.z += self.z_vel * dt
    self.z = max(self.z, self.elevation)

    # Frame Update
    self.frame += self.anim_speed * dt

    # Add Velocity To Position
    if self.velocity.length() <= self.VELOCITY_DEADZONE:
        # Add a "deadzone" where if the velocity is low enough, it just becomes (0, 0)
        self.velocity = pg.Vector2()

    if not self.velocity.length():
        return

    self._handle_collision(pg.Vector2(self.velocity.x, 0), game)
    self._handle_collision(pg.Vector2(0, self.velocity.y), game)
    self._handle_elevation(game)