Back to Minecraft Lessons
Lesson 4 · Self-Paced

Filling Spaces & Spawning Mobs

Use blocks.fill() to create rooms, arenas, ponds, and habitats — then use mobs.spawn() to bring them to life.

ESSENTIAL QUESTION

How can a program use coordinates to create and populate a whole space at once?

By the end of this lesson, I will...

  • Explain when blocks.fill() is a better tool than the Agent or Builder.
  • Use two positions as opposite corners of a 3D rectangular region.
  • Use FillOperation.REPLACE, HOLLOW, and OUTLINE intentionally.
  • Use mobs.spawn() to place mobs at a specific or random position.
  • Combine blocks.fill(), a for loop, and mobs.spawn() to create an interactive scene.
Every Lesson

Start Here First — Every Single Time

Every Minecraft lesson begins with a brand-new world. Do not skip this. Open Lesson 0, follow the setup steps, then come back here and continue with Step 1.

Open Lesson 0 — World Setup

What you already know

Today's lesson builds on Lessons 1, 2, and 3. Here's the toolkit you bring in:

From Lesson 1Sequences run in order. Events (player.on_chat) start your code.
From Lesson 2for loops repeat instructions; indentation decides what's inside the loop.
From Lesson 3pos(x, y, z) measures from where you stand. Builder commands. "Fewer-lines-is-better" thinking.
New todayblocks.fill() for whole regions and mobs.spawn() to bring them to life.

STEP 1 Warm-up: Which Tool Would You Choose?

You now have three tools for shaping the Minecraft world: the Agent, the Builder, and — new today — blocks.fill(). Each one is right for different jobs.

Look at each task below and decide which tool fits best before reading the answer.

TaskBest toolWhy
Watch a character walk through a mazeAgentVisible movement, easy to debug.
Draw a long straight wallBuildermark() + line() is efficient.
Make a 20×20×10 glass boxblocks.fill()One command fills a whole region.
Add 10 animals inside the boxmobs.spawn() + loopRepeats mob creation.
The big idea: Lesson 3 ended on "fewer lines is better." Lesson 4 takes the same idea further: sometimes even the Builder is more work than necessary. When you want a whole region filled in one go, blocks.fill() is the right tool.

Today's Vocabulary

blocks.fill()Fills every block in a 3D box with the block type you choose.
Opposite cornersThe two positions you give fill() — one corner of the box and the corner farthest from it.
FillOperationTells fill() how to fill: REPLACE, HOLLOW, OUTLINE, KEEP, or DESTROY.
mobs.spawn()Creates a mob (sheep, cow, fish, zombie, …) at a position.
randpos()Returns a random position inside the box between two corners. Great for spreading mobs around.

STEP 2 Open MakeCode (Python Only)

You should already be in your fresh flat world from Lesson 0. If not, go back and complete Lesson 0 first.

  1. In Minecraft, press C to open Code Builder.
  2. Choose MakeCode.
  3. Click New Project and name it Lesson 4 - Fill.
  4. Click Code options, then select Python Only, then click Create.

RECAP Bridge from Lesson 3: The Same Wall, Even Shorter

In Lesson 3 you built a 10-long, 5-tall stone wall with a loop, mark(), and line(). Here's the same wall written with blocks.fill():

One small Python note before we keep going. Inside pos(x, y, z) the numbers are already measured from where the player is standing — you do not need the ~ symbol you'd type in a chat command. So pos(0, 0, 0) means "at your feet" and pos(5, 0, 0) means "5 blocks east of you." The ~ still belongs in chat commands like /tp ~ ~ ~10 — just not inside pos().
def on_chat():
    blocks.fill(STONE,
                pos(0, 0, 0),
                pos(10, 5, 0),
                FillOperation.REPLACE)
player.on_chat("wall", on_chat)
Predict: what part of this is like Lesson 3? What part is brand new?

Same: still describes a wall 10 long, 5 tall using coordinates measured from where the player is standing (pos(0, 0, 0) is at your feet).

New: no Builder movement at all. No loop. The world fills the entire wall in one command. You went from a 5-line program to a 1-line program.

STEP 4 The Big New Idea: Opposite Corners

blocks.fill() does not move like the Agent or Builder. It needs two opposite corners of a rectangular box. The world fills every block between them.

def on_chat():
    blocks.fill(GLASS,
                pos(0, 0, 0),
                pos(10, 5, 10),
                FillOperation.REPLACE)
player.on_chat("solid", on_chat)

That fills a solid glass brick — 11 wide, 6 tall, 11 deep. Now change one word and look at what happens:

def on_chat():
    blocks.fill(GLASS,
                pos(0, 0, 0),
                pos(10, 5, 10),
                FillOperation.HOLLOW)
player.on_chat("room", on_chat)

Same two corners. Same block type. But HOLLOW gives you a room you can stand in instead of a solid cube.

Fill operations — pick the one you want

OperationWhat it does
FillOperation.REPLACEFills every block in the region with the chosen block.
FillOperation.HOLLOWBuilds the outer shell and clears the inside to air. Great for rooms.
FillOperation.OUTLINEBuilds only the outer edge/shell. Leaves whatever was inside alone.
FillOperation.KEEPFills only the empty air blocks — won't overwrite existing blocks.
FillOperation.DESTROYReplaces blocks and drops the old ones as if you mined them.
Step back first. The fill starts at your feet, so anything solid (like REPLACE with stone) will trap you. Before running a fill, press T, type /tp ~ ~ ~10, and press Enter to jump 10 blocks south. Now your build appears in front of you, not around you.
Predict: what happens if you run the HOLLOW version with STONE instead of GLASS?

You get a hollow stone box — same shape, but you can't see in or out. HOLLOW describes how to build; the block type is independent. Try it both ways and notice that you can walk inside either one (the inside is air either way).

STEP 5 Starter Build: A Habitat

Now we'll combine two fills — a hollow glass-pane shell with a grass floor inside. This is the base for almost any habitat, arena, or scene.

def on_chat():
    # Hollow glass shell
    blocks.fill(GLASS_PANE,
                pos(5, 0, -10),
                pos(12, 7, 10),
                FillOperation.HOLLOW)

    # Grass floor inside the shell
    blocks.fill(GRASS,
                pos(6, 0, -9),
                pos(11, 0, 9),
                FillOperation.REPLACE)
player.on_chat("habitat", on_chat)

Read the coordinates before running

  • The first fill goes from (5, 0, -10) to (12, 7, 10). That's 8 wide (12 - 5 + 1), 8 tall, 21 deep.
  • The second fill is smaller — it starts at 6 and ends at 11 on the X axis, and -9 to 9 on Z. It sits inside the shell.
  • Both fills share the same Y axis (0), so the grass becomes the floor of the habitat.
Predict: why is the second fill smaller than the first? What would happen if it weren't?

The grass floor needs to fit inside the glass walls — not on top of them. If you used the same corners as the shell, the grass would replace the bottom layer of glass and the habitat wouldn't have walls down to the ground.

Coordinate quick-check

Which axis controls width? Which one controls height? Which one controls depth?
Answer: X = width, Y = height (up/down), Z = depth (forward/back). Same as Lesson 3.

Why a land habitat first? Start with grass and air, not water. A land habitat works for almost any mob — sheep, cows, pigs, chickens. We'll save the aquarium for the challenge.
Step back first. Open chat (T), type /tp ~ ~ ~10, and press Enter. The habitat will build to the east of you instead of around you. Then run habitat.

STEP 6 Add a Builder Accent

The habitat looks plain. blocks.fill() is great for big regions — whole walls, whole floors. But for a single line — say, a stone path running down the middle of the habitat — the Builder from Lesson 3 is still the right tool. Use the right tool for the size of the job.

def on_chat():
    # Walls and floor (big regions — fill is the right tool)
    blocks.fill(GLASS_PANE, pos(5, 0, -10), pos(12, 7, 10), FillOperation.HOLLOW)
    blocks.fill(GRASS,      pos(6, 0, -9),  pos(11, 0, 9),  FillOperation.REPLACE)

    # Walkway down the middle (a single line — Builder is the right tool)
    builder.teleport_to(pos(8, 1, -9))
    builder.mark()
    builder.teleport_to(pos(8, 1, 9))
    builder.line(COBBLESTONE)
player.on_chat("habitat", on_chat)

What the Builder is doing

  • builder.teleport_to(pos(8, 1, -9)) — warp the Builder to the north end of the habitat, one block above the grass.
  • builder.mark() — remember this position.
  • builder.teleport_to(pos(8, 1, 9)) — warp to the south end.
  • builder.line(COBBLESTONE) — stamp a cobblestone line between the mark and here.
The toolbox now: blocks.fill() for regions, the Builder for lines, and the Agent for things you want to watch happen. A real program often uses two or three together — fill the big stuff, then add Builder detail on top.
Predict: could you build the same cobblestone path with another blocks.fill() instead? Would that be better or worse?

Yes — blocks.fill(COBBLESTONE, pos(8, 1, -9), pos(8, 1, 9), FillOperation.REPLACE) would work just as well. For a perfectly straight line it's a fine choice.

When the Builder wins: if you want to curve the path or place stones along a route that bends, trace_path walks the curve and stamps blocks behind it — something fill can't do.

STEP 7 Bring It to Life: mobs.spawn()

An empty habitat is just architecture. Add a single mob first, then turn it into a loop.

One mob, exact position

def on_chat():
    blocks.fill(GLASS_PANE, pos(5, 0, -10), pos(12, 7, 10), FillOperation.HOLLOW)
    blocks.fill(GRASS,      pos(6, 0, -9),  pos(11, 0, 9),  FillOperation.REPLACE)

    mobs.spawn(SHEEP, pos(8, 1, 0))
player.on_chat("habitat", on_chat)

The sheep appears at (8, 1, 0) — one block above the grass floor, centered between the walls. Run it. You should see exactly one sheep standing inside the habitat.

Many mobs, random positions (callback to Lesson 2's loop)

Now bring the for loop from Lesson 2 back into the program. Each iteration spawns one sheep at a random position inside the habitat.

def on_chat():
    blocks.fill(GLASS_PANE, pos(5, 0, -10), pos(12, 7, 10), FillOperation.HOLLOW)
    blocks.fill(GRASS,      pos(6, 0, -9),  pos(11, 0, 9),  FillOperation.REPLACE)

    for i in range(5):
        mobs.spawn(SHEEP, randpos(pos(6, 1, -9), pos(11, 1, 9)))
player.on_chat("habitat", on_chat)

What's happening

  • range(5) — the loop runs 5 times. Same pattern as Lesson 2.
  • randpos(corner1, corner2) — pick a random position inside the box between those two corners.
  • The corners here match the grass floor's footprint with Y bumped up to 1 so the sheep spawn on the grass, not inside it.
Connect to Lesson 2: This is the same for i in range(N): pattern that controlled how many forward steps the Agent took. Now it controls how many sheep spawn. Loops don't care what's inside them — they just repeat.
Predict: what changes if you change range(5) to range(20)? What if you swap SHEEP for ZOMBIE?

range(20)20 sheep instead of 5. The habitat gets crowded fast.

ZOMBIE → 5 zombies in a glass box. They can't get out (glass blocks them) but they will roam around. Try it — just be ready to run the program a second time to clear them out, or use /kill @e[type=zombie] in chat.

STEP 8 Debugging: Two Bugs to Recognize

Two bugs come up in nearly every Lesson 4 program. Recognize them once and you'll spot them instantly from now on.

Bug 1 — Indentation (callback to Lesson 2)

This program looks right but spawns only one sheep, not five. Can you spot why?

def on_chat():
    blocks.fill(GLASS_PANE, pos(5, 0, -10), pos(12, 7, 10), FillOperation.HOLLOW)
    blocks.fill(GRASS,      pos(6, 0, -9),  pos(11, 0, 9),  FillOperation.REPLACE)

    for i in range(5):
    mobs.spawn(SHEEP, randpos(pos(6, 1, -9), pos(11, 1, 9)))
player.on_chat("broken", on_chat)
Why is this broken?

The mobs.spawn(...) line is not indented inside the loop. Python won't even run this — it's a syntax error. This is the same family of bug you saw in Lesson 2's wall: a misaligned line breaks the loop.

Fix: indent the mobs.spawn line with 4 spaces (or a tab) so it lines up inside the for loop.

Bug 2 — Wrong Y value (callback to Lesson 3)

This one runs without errors — but the sheep fall from the sky every time. Can you see why?

def on_chat():
    blocks.fill(GLASS_PANE, pos(5, 0, -10), pos(12, 7, 10), FillOperation.HOLLOW)
    blocks.fill(GRASS,      pos(6, 0, -9),  pos(11, 0, 9),  FillOperation.REPLACE)

    for i in range(5):
        mobs.spawn(SHEEP, randpos(pos(6, 10, -9), pos(11, 10, 9)))
player.on_chat("falling", on_chat)
Why are the sheep falling from the sky?

The Y values inside randpos are 10, which is 10 blocks above the grass floor (the floor is at Y = 0). Sheep spawn near the ceiling and fall down to the floor.

Fix: use 1 for both Y values so the sheep spawn one block above the grass — right where they belong.

Debugging instinct: When something is wrong with where a mob ends up, look at the Y axis first. Wrong-Y is the most common mobs.spawn() bug there is.

CHALLENGE Build Your Own Scene

Create an original program that uses blocks.fill() and mobs.spawn() to build a habitat, arena, pond, aquarium, or scene of your own design. Pick one of the options below or invent your own.

Option A — Animal Habitat

Glass or fence walls, a floor, and at least 5 mobs spawned with a loop. Use at least two block types. Sheep, cows, pigs, or chickens are easy starters.

Option B — Battle Arena

Walls or outline, a floor, mobs spawned at random positions, and at least one obstacle built with another blocks.fill(). A great place to talk about abstraction and parameters.

Option C — Trident Aquarium

A glass cube filled with water, then tropical fish, pufferfish, and dolphins spawned at random positions — summoned by right-clicking with a trident instead of a chat command. Full working program below.

New event trigger: player.on_item_interacted(TRIDENT, ...) runs your function when the player uses a trident (right-click). It's the same idea as player.on_chat — an event that starts your code — just triggered differently. Any item works: STICK, DIAMOND, CARROT_ON_A_STICK, …

Option C full program — the Trident Aquarium

def trident_summon_aquarium():
    blocks.fill(GLASS, pos(5, 0, -10), pos(12, 7, 10), FillOperation.REPLACE)
    blocks.fill(WATER, pos(6, 1, -9),  pos(11, 7, 9),  FillOperation.REPLACE)
    for i in range(4):
        mobs.spawn(TROPICAL_FISH, randpos(pos(6, 1, -9), pos(11, 6, 9)))
        blocks.place(BUBBLE_CORAL, randpos(pos(6, 1, -9), pos(11, 6, 9)))
        mobs.spawn(PUFFERFISH,    randpos(pos(6, 1, -9), pos(11, 6, 9)))
        mobs.spawn(DOLPHIN,       randpos(pos(6, 1, -9), pos(11, 6, 9)))

        # blocks.place(SEAGRASS, randpos(pos(6, 1, -9), pos(11, 1, 9)))
        # Doesn't work because seagrass can't be placed on glass.
        # Fix it later by filling a dirt layer at the bottom first.

player.on_item_interacted(TRIDENT, trident_summon_aquarium)

What's different about this program

  • Solid then carved: the first fill makes a solid glass cube with REPLACE. The second fill replaces the inside with water. Result: glass shell with water inside — a different way to get the same effect as HOLLOW.
  • Three different mobs in one loop: each iteration spawns a fish, a pufferfish, and a dolphin — so range(4) gives you 4 of each, 12 total.
  • blocks.place() for single blocks: bubble coral is placed one block at a time inside the loop. Use place for one-offs; use fill for regions.
  • The commented bug: the seagrass line is intentionally commented out — seagrass needs dirt under it. Real programs include notes like this to remember what to fix later.

How to actually summon the aquarium (Trident 101)

The program only runs when you use a trident — so first you need one in your hand. If you've never opened a Minecraft inventory before, follow these steps exactly:

  1. Run your code in MakeCode (green play button). Nothing will happen yet — the program is now waiting for you to use a trident.
  2. Switch back to the Minecraft world.
  3. Press the E key. This opens your inventory.
  4. In the search bar at the top, type trident. A trident icon appears.
  5. Click and drag the trident into one of the squares along the bottom row — that's your hotbar, the items you can hold.
  6. Press Esc (or E again) to close the inventory.
  7. Scroll your mouse wheel until the trident is selected in your hotbar (or press the number key matching its slot — 1 through 9). You should now be holding the trident.
  8. Right-click the mouse to use the trident. The aquarium appears!
Minecraft inventory open with the search box showing 'trident' and the trident item displayed below
Inventory open (E), search box typed with trident, the trident item ready to drag into your hotbar.
Right-click does nothing? Make sure: (1) the trident is actually selected in your hotbar (the slot has a white border), (2) you ran the MakeCode program first, and (3) you're aiming at empty air or a block in front of you, not at the sky or out of bounds.

Minimum requirements

  • Your program starts with an event — either a chat command (player.on_chat) or an item interaction (player.on_item_interacted).
  • Uses blocks.fill() at least twice.
  • Uses at least two different block types.
  • Uses mobs.spawn() at least once.
  • Uses a for loop to spawn multiple mobs.
  • Uses randpos(...) or carefully chosen coordinates so the mobs end up inside the structure.
  • Final result is a habitat, arena, pond, aquarium, or other enclosed scene.

Stretch goals

  • Use a different mob type per loop iteration with an if statement.
  • Add a second floor or interior wall with a third blocks.fill().
  • Use FillOperation.OUTLINE for the walls and decorate the inside separately.
  • Add an opening (a door or window) by running another fill of AIR in a small region.
  • Add # comments above each section to explain what it does.

SUBMIT Turn In Your Work

When you've finished the challenge, submit three items using the submission link in your class:

  1. Screenshot of your code in the MakeCode editor. Your blocks.fill() calls, your for loop, and your mobs.spawn() must all be visible.
  2. Screenshot of the result in Minecraft — the habitat, arena, or scene your program created, with the mobs inside.
  3. Short written reflection. Answer all five in 1–2 sentences each:
    • What did your program build?
    • Which two positions did you use as opposite corners for your main blocks.fill()?
    • Which fill operation did you use — REPLACE, HOLLOW, or OUTLINE — and why?
    • How did your loop affect the number of mobs that spawned?
    • Why would this build be harder with the Agent or Builder?
Important: The reflection counts for half the grade. Show that you understand why blocks.fill() was the right tool for this job — not just that it worked.

When You're Done

  • Trade computers and run a classmate's program. Did their habitat surprise you?
  • Look up FillOperation.KEEP — how is it different from REPLACE?
  • Try chaining three or four habitats side-by-side using one chat command and a loop around the fills.
  • Re-write your Lesson 3 wall using a single blocks.fill(). How short can the program get?
Back to Minecraft Lessons