Grouping Mobs

So far we’ve been applying animations to individual mobs. But often it is more convenient to apply animations to Groups of mobs. Algan handles this defining a parent-child relation between two mobs. Once such a relationship is established, any animation applied to the parent will be propagated to its children (and those children will then propagate the animation to their children, recursively). As such, complex objects can be built up of many simple individual mob components, and animated as one entity.

The parent-child relation is established by calling add_children(), which attaches the parameter mob as a child of the calling mob. Algan also provides the Group class which takes a collection of mobs and attaches them to a new invisible parent Group mob. Group also provides various layout helpers.

Let’s see an example.

Example: ChildMobsBasic

from algan import *

center_square = Square(color=BLUE)
outer_squares = [Square(location=loc) for loc in [LEFT * 2.5, UP * 2.5, RIGHT * 2.5, DOWN * 2.5]]

center_square.add_children(outer_squares)  # this is the crucial step

# Now any change to the parent propagates to the children,
# including spawning.
center_square.scale(0.75).spawn()
center_square.rotate(90, OUT)
center_square.move(RIGHT * 1)
with Seq(runtime=5):
    center_square.rotate(360, OUT, about=ORIGIN)

center_square.wait()
# You can even animate the parent and a child at the same time.
with Sync(runtime=5):
    center_square.rotate(90, OUT)
    outer_squares[0].rotate(180, UP)
Scene.wait()

Scene.save_video()

How changes propagate

What “propagate” means depends on the attribute:

Change to the parent

Effect on each child

location

The child moves by the same displacement.

basis (via rotate() / scale())

The child’s basis is rotated or scaled the same way, and its position relative to the parent is preserved.

color, opacity, glow

The child gets the same change.

spawn() / despawn()

The child spawns or despawns too.

The upshot for geometry is that a child behaves as though bolted to the parent by a rigid pole: rotate the parent and the children swing around with it, keeping their orientation relative to the parent fixed.

Changes made directly to a child ignore the parent relationship entirely, so you can animate a child independently, as in the last block of the example above.

See also

Updaters – for a relation the parent-child rule cannot express, such as one mob following another without also taking its orientation.

Inspecting the hierarchy

Accessor

Returns

children

This Mob’s direct children. Read-only – always add through add_children().

get_descendants()

Children, grandchildren and so on, plus this Mob.

parents

The Mobs whose changes this one follows – there can be several. Read-only – always add through add_parent().

remove_child(), remove_parent()

Detach one link, from either side. Both drop it in both directions.

add_parent(), replace_children()

Attach from the child’s side, or swap the whole child list.

Groups

Group wraps a collection of Mobs so you can treat them as one. It creates an empty Mob at the centre of the collection and adds everything in the collection as its children, so all of the propagation rules above apply: rotating the Group turns each member about the Group’s centre, and setting the Group’s color sets every member’s.

Example: ChildMobsGroup

from algan import *

group = Group([Square().scale(0.35).move(RIGHT * x) for x in (-1, 0, 1)])
group.spawn()

with Seq():
    group.rotate(180, OUT)   # the whole row turns about its centre
    group.color = BLUE       # every member changes
    group.move(UP * 0.5)
Scene.wait()

Scene.save_video()

Arranging

arrange_in_line() spreads the members along a direction; arrange_in_grid() lays them out in rows and columns. Both are ordinary animations, so members slide into place rather than jumping.

Example: ChildMobsArrange

from algan import *

group = Group([Square(color=BLUE).scale(0.3) for _ in range(9)]).spawn()

group.arrange_in_line(RIGHT)                    # a row
Scene.wait()
group.arrange_in_line(DOWN, buffer=0.15)         # a tight column
Scene.wait()
group.arrange_in_grid(3)                        # 3 rows
Scene.wait()
group.arrange_in_grid(3, row_buffer=1.0)        # 3 rows, generously spaced
Scene.wait()

Scene.save_video()

arrange_in_line also takes align_to to line the members up on an edge rather than their centres, and equal_widths to space centres evenly instead of leaving equal gaps. arrange_in_grid takes row_direction / column_direction to control which way the grid fills.

See also

Positioning and Layout – the rest of the positioning methods, including screen-relative placement and fit_to_screen(), which is what you usually want after arranging a group.

Indexing

Groups are indexable and iterable, so you can reach individual members without keeping a separate list:

Example: ChildMobsGroupIndexing

from algan import *

group = Group([Circle(color=BLUE).scale(0.4) for _ in range(6)])
group.arrange_in_line(RIGHT, buffer=0.3).spawn()

with Lag(0.4):
    for circle in group:
        circle.color = YELLOW

group[0].move(UP)

group.arrange_in_grid()
Scene.wait()

Scene.save_video()